如何启动geth节点对大家来说已经不是什么难事,今天博主就带大家学习一下如何搭建两个节点的联盟链。
在之前的文章中我们已经讲到过私有链的创建,本篇文章我们会有道私有链创建的知识,就重新温故一下。创建私有链首先需要指定创始块的配置,也就是genesis.json的配置。此文件就是一个内容格式为json的文本文件。
配置文件的内容格式基本如下:
{
"alloc": {},
"config": {
"chainID": 72,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"nonce": "0x0000000000000000",
"difficulty": "0x4000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
"gasLimit": "0xffffffff"
}
我们对配置项的内容进行一下简单的介绍。
alloc: 用来预置账号以及账号的以太币数量,因为私有链挖矿比较容易,所以不需要预置有币的账号,需要的时候自己创建即可以。实例代码如下:
"alloc": {
"de1e758511a7c67e7db93d1c23c1060a21db4615": {
"balance": "1000"
},
"27dc8de9e9a1cb673543bd5fce89e83af09e228f": {
"balance": "1100"
},
"d64a66c28a6ae5150af5e7c34696502793b91ae7": {
"balance": "900"
}
nonce:一个64位随机数,用于挖矿,和mixhash的设置需要满足以太坊的Yellow paper, 4.3.4.Block Header Validity (44)章节所描述的条件。
difficulty: 设置计算区块的难度,如果数值过大,挖矿时间较长,在测试环境为节省算力和等带时间可设置较小值。
mixhash:与nonce配合用于挖矿,由上一个区块的一部分生成的hash。和nonce的设置需要满足以太坊的Yellow paper, 4.3.4. Block Header Validity, (44)章节所描述的条件。
coinbase: 矿工账号,随便填写。
timestamp: 设置创世块的时间戳。
parentHash: 上一个区块的hash值,因为是创世块,所以这个值是0。
extraData: 附加信息,随便填,可以填你的个性信息,必须为十六进制的字符串。
gasLimit: 该值设置对GAS的消耗总量限制,用来限制区块能包含的交易信息总和,因为是私有链,所以填最大。
本教程以mac操作系统为例,其他操作系统可对照执行。 执行以下命令来启动初始化创世块的命令,最简单的一组操作就是制定dir文件路径,和初始化文件目录。前面我们已经写好了genesis.json的配置文件,下面就执行一下初始化操作,涉及到操作参数为init。
ershixiongdeMacBook-Pro:geth zzs$ ./geth --datadir ./data-init1/ init genesis.json
本教程将初始化json文件放在geth同级目录下,如果放在其他目录下,指定具体的路径即可。同时创建了一个data-init1目录专门存储节点数据,执行完成会发现在该目录下多出两个目录,一个为geth一个为keystore。其中geth里面放数据相关信息,keystore里面放加密过的私钥文件。
执行时打印日志如下:
WARN [12-28|19:12:03] No etherbase set and no accounts found as default
INFO [12-28|19:12:03] Allocated cache and file handles database=/Users/zzs/develop/eth/geth/data-init1/geth/chaindata cache=16 handles=16
INFO [12-28|19:12:03] Writing custom genesis block
INFO [12-28|19:12:03] Successfully wrote genesis state database=chaindata hash=942f59…a2588a
INFO [12-28|19:12:03] Allocated cache and file handles database=/Users/zzs/develop/eth/geth/data-init1/geth/lightchaindata cache=16 handles=16
INFO [12-28|19:12:03] Writing custom genesis block
INFO [12-28|19:12:03] Successfully wrote genesis state database=lightchaindata hash=942f59…a2588a
经过以上命令,我们已经完成了私有连的初始化工作。因为我们要建立联盟链,因此需要再创建执行一遍同样的命令,json文件必须相同,datadir目录必须不同。博主使用data-init2目录来存储第二个节点的数据。
ershixiongdeMacBook-Pro:geth zzs$ ./geth --datadir ./data-init2/ init genesis.json
根据具体的操作系统,开两个窗口来启动两个节点。这里有一点需要注意的是,虽然是两个节点,但他们的启动程序都是geth,只不过datadir目录不同而已。 在第一个窗口执行以下命令启动一个节点,注意启动之后不要关闭窗口。
./geth --datadir ./data-init1/ --networkid 88 --nodiscover console
参数简介:
networkid 指定网路ID,确保不适用1-4。
nodiscover 此参数确保geth不去寻找peers,主要是为了严格控制联盟链连入的节点。
这里我们需要注意的是在启动第一个节点时并没有指定port参数,因此此处采用了默认的port,也就是30303。 以下为执行时打印的日志,并进入控制台。通过日志我们也可以发现端口为30303。
WARN [12-28|19:23:16] No etherbase set and no accounts found as default
INFO [12-28|19:23:16] Starting peer-to-peer node instance=Geth/v1.7.3-stable-4bb3c89d/darwin-amd64/go1.9.2
INFO [12-28|19:23:16] Allocated cache and file handles database=/Users/zzs/develop/eth/geth/data-init2/geth/chaindata cache=128 handles=1024
WARN [12-28|19:23:16] Upgrading database to use lookup entries
INFO [12-28|19:23:16] Database deduplication successful deduped=0
INFO [12-28|19:23:16] Initialised chain configuration config="{ChainID: 72 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: <nil> EIP155: 0 EIP158: 0 Byzantium: <nil> Engine: unknown}"
INFO [12-28|19:23:16] Disk storage enabled for ethash caches dir=/Users/zzs/develop/eth/geth/data-init2/geth/ethash count=3
INFO [12-28|19:23:16] Disk storage enabled for ethash DAGs dir=/Users/zzs/.ethash count=2
INFO [12-28|19:23:16] Initialising Ethereum protocol versions="[63 62]" network=88
INFO [12-28|19:23:16] Loaded most recent local header number=0 hash=942f59…a2588a td=16384
INFO [12-28|19:23:16] Loaded most recent local full block number=0 hash=942f59…a2588a td=16384
INFO [12-28|19:23:16] Loaded most recent local fast block number=0 hash=942f59…a2588a td=16384
INFO [12-28|19:23:16] Regenerated local transaction journal transactions=0 accounts=0
INFO [12-28|19:23:16] Starting P2P networking
INFO [12-28|19:23:16] RLPx listener up self="enode://aa621c010c685665ef217044dac4d57f4d1d682c682a5b3f92ca23b40982383240a05b680060ce8b0ce020a96c49c9c2c3628c4ea3281845211bd4cf4f03b35c@[::]:30303?discport=0"
INFO [12-28|19:23:16] IPC endpoint opened: /Users/zzs/develop/eth/geth/data-init2/geth.ipc
Welcome to the Geth JavaScript console!
instance: Geth/v1.7.3-stable-4bb3c89d/darwin-amd64/go1.9.2
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> INFO [12-28|19:23:18] Mapped network port proto=tcp extport=30303 intport=30303 interface="UPNP IGDv1-IP1"
下面在另外一个窗口,换一个端口,比如30306,再换一下datadir,来启动第二个节点。
./geth --datadir ./data-init2/ --port 30306 --networkid 88 --nodiscover console
执行上面命令,完成节点2的启动,并进入控制台。
上面的日志我们也看到警告信息,提示没有账户存在,那么现在我们就在第一个节点上创建一个账户,具体在控制台操作命令如下:
> personal.listAccounts
[]
> personal.newAccount("123456")
"0x60c8abe58c9dbc52a4ee9f8510f1799c432c0f3e"
>
上面的命令先是查看了节点下的地址,结果为空,然后创建了一个秘密为123456的账号。
同样的,在另外一个窗口我们执行同样的命令:
> personal.listAccounts
[]
> personal.newAccount("123456")
"0x02b7344004c45465796f779b7b95d7912c2ef572"
>
这样,两个节点就拥有了两个地址。同时,在它们的keystore目录下对应生成了加密的私钥文件。
我们也可以再次执行list命令查看添加账户之后的情况。同时可以执行以下命令查看coinbase账号:
> eth.coinbase
"0x02b7344004c45465796f779b7b95d7912c2ef572"
>
由于只有一个地址,因此该地址就作为coinbase地址。如果想查看更多的信息可以执行以下命令:
> personal.listWallets
[{
accounts: [{
address: "0x02b7344004c45465796f779b7b95d7912c2ef572",
url: "keystore:///Users/zzs/develop/eth/geth/data-init2/keystore/UTC--2017-12-28T11-36-18.185974427Z--02b7344004c45465796f779b7b95d7912c2ef572"
}],
status: "Locked",
url: "keystore:///Users/zzs/develop/eth/geth/data-init2/keystore/UTC--2017-12-28T11-36-18.185974427Z--02b7344004c45465796f779b7b95d7912c2ef572"
}]
这里不仅打印了账户信息,还打印出了私钥存储的位置和账户状态等信息。
上面分别是在两个节点上进行的操作,下面我们需要把两个节点之间建立起链接。首先,我们执行以下命令查看以下节点的peers的情况。
> admin.peers
[]
发现节点并没有链接上任何其他节点,这也是我们的nodiscover参数发挥了效果。
下面就通过分享enode地址的方式来让两个节点建立链接。
> admin.nodeInfo.enode
"enode://aa621c010c685665ef217044dac4d57f4d1d682c682a5b3f92ca23b40982383240a05b680060ce8b0ce020a96c49c9c2c3628c4ea3281845211bd4cf4f03b35c@[::]:30306?discport=0"
>
通过上面命令,我们获得了节点2的encode信息。这是geth用来连接到不同节点的enode信息,在这些不同的节点它们能够分享交易和成功挖掘信息。
其实这个信息如果留心的话,在启动节点的打印日志中已经打印出每个节点的encode信息。比如:
INFO [12-28|19:23:16] RLPx listener up self="enode://aa621c010c685665ef217044dac4d57f4d1d682c682a5b3f92ca23b40982383240a05b680060ce8b0ce020a96c49c9c2c3628c4ea3281845211bd4cf4f03b35c@[::]:30303?discport=0"
现在,我们要告知一个节点,另外一个节点的encode信息。首先复制节点2的日志中self等号后面的信息,在节点1的控制台执行以下命令:
> admin.addPeer("enode://aa621c010c685665ef217044dac4d57f4d1d682c682a5b3f92ca23b40982383240a05b680060ce8b0ce020a96c49c9c2c3628c4ea3281845211bd4cf4f03b35c@[::]:30306?discport=0")
true
返回true,说明执行成功。再次验证一下:
> admin.peers
[{
caps: ["eth/63"],
id: "aa621c010c685665ef217044dac4d57f4d1d682c682a5b3f92ca23b40982383240a05b680060ce8b0ce020a96c49c9c2c3628c4ea3281845211bd4cf4f03b35c",
name: "Geth/v1.7.3-stable-4bb3c89d/darwin-amd64/go1.9.2",
network: {
localAddress: "[::1]:49426",
remoteAddress: "[::1]:30306"
},
protocols: {
eth: {
difficulty: 16384,
head: "0x942f596f99dc8879b426b59080824662e1f97587353d087487fea0a0e2a2588a",
version: 63
}
}
}]
>
发现节点1已经有一个peer了,同时我们可以看到remoteAddress: “[::1]:30306”,正是我们节点2的端口信息。在节点2执行admin.peers会发现有类似的信息,指向的peer正是节点1的。
执行查看余额命令:
> eth.getBalance(eth.coinbase)
0
>
发现两个节点的账号余额都为0。
在节点1执行miner.start()进行挖矿,执行miner.stop()停止挖矿。停止挖矿的时候开业忽略控制台输出,只要正确拼写命令回车即可。
当我们在节点1执行挖矿时,我们会发现节点2的控制台出现了这样的日志信息:
> INFO [12-28|20:05:32] Block synchronisation started
INFO [12-28|20:05:33] Imported new state entries count=1 elapsed=47.661µs processed=1 pending=0 retry=0 duplicate=0 unexpected=0
WARN [12-28|20:05:33] Discarded bad propagated block number=1 hash=ab49ba…1cf32f
INFO [12-28|20:05:33] Imported new block headers count=2 elapsed=9.208ms number=2 hash=738225…000e3b ignored=0
INFO [12-28|20:05:33] Imported new chain segment blocks=2 txs=0 mgas=0.000 elapsed=1.724ms mgasps=0.000 number=2 hash=738225…000e3b
INFO [12-28|20:05:33] Fast sync complete, auto disabling
INFO [12-28|20:05:34] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=5.978ms mgasps=0.000 number=3 hash=b069a9…426060
INFO [12-28|20:05:38] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=6.930ms mgasps=0.000 number=4 hash=21217e…526253
INFO [12-28|20:05:41] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=6.419ms mgasps=0.000 number=5 hash=3fa6ff…cf2794
INFO [12-28|20:05:43] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=6.557ms mgasps=0.000 number=6 hash=4c35b9…78b3ec
INFO [12-28|20:05:45] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=6.514ms mgasps=0.000 number=7 hash=328e62…1bd3d3
INFO [12-28|20:05:46] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=6.513ms mgasps=0.000 number=8 hash=12287e…0465b5
INFO [12-28|20:06:19] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=7.048ms mgasps=0.000 number=9 hash=8e844b…b99d6c
INFO [12-28|20:06:22] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=8.156ms mgasps=0.000 number=10 hash=159b36…d4dde5
INFO [12-28|20:06:24] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=6.549ms mgasps=0.000 number=11 hash=969100…5658a5
也就是说,节点1挖矿,节点2在同步数据信息。
停止节点1的挖矿,并查看coinbase地址金额:
> miner.stop()
true
> eth.getBalance(eth.coinbase)
140000000000000000000
> eth.coinbase
"0x60c8abe58c9dbc52a4ee9f8510f1799c432c0f3e"
这里我们知道了节点一种的地址信息和余额信息,那我们拿节点1的这个地址在节点2的控制台查询一下信息:
> eth.getBalance("0x60c8abe58c9dbc52a4ee9f8510f1799c432c0f3e")
140000000000000000000
>
很显然,节点2中也能查询到节点1中地址的余额。以上信息说明,节点1和节点2的数据是完全同步的。
现在我们从节点1的coinbase账户发一笔交易到节点2的coinbase账户。
> personal.unlockAccount("0x60c8abe58c9dbc52a4ee9f8510f1799c432c0f3e")
Unlock account 0x60c8abe58c9dbc52a4ee9f8510f1799c432c0f3e
Passphrase:
true
> eth.sendTransaction({from: eth.coinbase, to: '0x02b7344004c45465796f779b7b95d7912c2ef572', value: 100000000})
INFO [12-28|20:13:21] Submitted transaction fullhash=0xc32e40f0f606a6368aa2c4c6a27db20f5a1d728fb9ef1f50a0410f4889e095a0 recipient=0x02b7344004c45465796F779B7b95d7912C2eF572
"0xc32e40f0f606a6368aa2c4c6a27db20f5a1d728fb9ef1f50a0410f4889e095a0"
>
解锁账户并发送1币交易信息。现在查看一下节点2的地址内是否有余额:
> eth.getBalance("0x02b7344004c45465796f779b7b95d7912c2ef572")
0
>
发现余额是0,为什么呢?因为虽然我们发起了交易,单并没有矿工挖矿打包交易。再次执行miner.start()。再次查询,就会发现节点2的coinbase地址已经有金额了。
> eth.getBalance("0x02b7344004c45465796f779b7b95d7912c2ef572")
100000000
通过以上的操作我们已经建立了一个拥有两个节点的联盟链,如果想建立更多节点的联盟链,可以此添加新的节点。
需要注意的是,如果一个节点重启,则需要重新添加peer。另外,在实际生产中可去掉console命令,通过之前介绍的attach命令进入控制台。