搭建以太坊私链网络
大量过时文章充斥于网络,本文基于官方go-tehereum 1.6.7版本整理而出,在geth1.6之后引入了一个puppeth工具,它就是用来初始一个私链创世块配置的。
准备工具环境
下载go-ethereum代码
(go的开发环境准备,不在此文范围)
# 下载源码
go get github.com/ethereum/go-ethereum
cd $GOPATH/src/github.com/ethereum/go-ethereum
# 编译1.6.7版本的代码
git checkout -b v1.6.7 v1.6.7
make all
# 安装
sudo ln -s $PWD/build/bin/* /usr/local/bin/
# 检查是否安装OK
geth version
Geth
Version: 1.6.7-stable
Git Commit: ab5646c532292b51e319f290afccf6a44f874372
Architecture: amd64
Protocol Versions: [63 62]
Network Id: 1
Go Version: go1.8.3
Operating System: linux
GOPATH=/home/xxp/go
GOROOT=/home/xxp/Software/go
生成私链创世块的配置
- 创建账户
# 创建testnet目录
➜ mkdir testnet && cd testnet
# 创建3个普通账户,密码自定
➜ geth --datadir node0 account new
# 把密码记录到文件里,后面会频繁输入
➜ echo node0 > node0/password
➜ geth --datadir node1 account new
➜ echo node1 > node1/password
➜ geth --datadir node2 account new
➜ echo node2 > node2/password
如上打印的一串16进制的字符串就代表账户的userID(理解成网络中的IP地址),后面puppeth需要用到。
- 生成genesis文件
genesis文件定义了私链的第一个块生成,直接看操作吧(省略了些输出)
➜ testnet puppeth
Please specify a network name to administer (no spaces, please)
> testnet
Sweet, you can set this via --network=testnet next time!
INFO [08-21|23:04:14] Administering Ethereum network name=testnet
WARN [08-21|23:04:14] No previous configurations found path=/home/xxp/.puppeth/testnet
What would you like to do? (default = stats)
1. Show network stats
2. Configure new genesis
3. Track new remote server
4. Deploy network components
> 2
Which consensus engine to use? (default = clique)
1. Ethash - proof-of-work
2. Clique - proof-of-authority
> 2
# 设置5秒出一个块
How many seconds should blocks take? (default = 15)
> 5
# 输入有签名权限的账户
Which accounts are allowed to seal? (mandatory at least one)
> 0x799a8f7796d1d20b8198a587caaf545cdde5de13
> 0x1458eac314d8fc922029095fae20483f55726017
> 0x3ca60eb49314d867ab75a3c7b3a5aa61c3d6ef71
> 0x
# 输入有预留余额的账户
Which accounts should be pre-funded? (advisable at least one)
> 0x799a8f7796d1d20b8198a587caaf545cdde5de13
> 0x1458eac314d8fc922029095fae20483f55726017
> 0x3ca60eb49314d867ab75a3c7b3a5aa61c3d6ef71
> 0x
Specify your chain/network ID if you want an explicit one (default = random)
> 378
Anything fun to embed into the genesis block? (max 32 bytes)
>
What would you like to do? (default = stats)
1. Show network stats
2. Save existing genesis
3. Track new remote server
4. Deploy network components
> 2
Which file to save the genesis into? (default = testnet.json)
> genesis.json
INFO [08-21|23:05:36] Exported existing genesis block
最后会在当前目录生成genesis.json文件。
启动私链网络
这里通过单机不同端口模拟多节点,
- 默认geth一启动就会发出discovery,以发现其他节点,源码里内置了几个初始节点,可以通过--bootnode参数重置。如果真要不同节点互组网络的话,还需要主要时间同步,
- 还可以通过--nodiscover参数,停掉自动发现,并利用admin.addPeer()或者static-node功能组成网络。
# 启动节点0, 关闭自动发现(防止不希望的节点进入)
geth --datadir node0 init genesis.json
geth --datadir node0 --port 30000 --nodiscover --unlock '0' --password ./node0/password console
# 不加console的话,可以通过geth attach ipc:node0/geth.ipc来访问
# 启动节点1,另起一个终端,通过不同端口模拟
geth --datadir node1 init genesis.json
geth --datadir node1 --port 30001 --nodiscover --unlock '0' --password ./node1/password console
# 启动节点2,genesis.json里已经指定networkID,启动时无需指定了
geth --datadir node2 init genesis.json
geth --datadir node2 --port 30002 --nodiscover --unlock '0' --password ./node2/password console
目前启动了三个节点,但都默认关闭了发现功能,需要手动添加peer节点。
在每个console输入admin.nodeInfo.enode
, 把输出记录下来到static-nodes.json
文件,我这里情况如下:
➜ testnet cat node0/static-nodes.json
[
"enode://2ffb53ede7de8dabf8f12343a7b2aba6b09263a53d8db5b4669309c5913f72969ce469cf09299f13e9d6cba8a98e18ad43811439326d7152f21d2e03ddc6be17@[::]:30000?discport=0",
"enode://910d1bfcd763bb5157bc62f8b121eb21fb305d17e4e4437c0b094d3d6f2d72f1964b80eb8fa2cf6cd7d4cc2d44cfc1ed9b74275ea7fd42ab89b4d089023fb7d5@[::]:30001?discport=0",
"enode://acab97a2a287b740b5efc3af465ba7330b3d4948b05e26818822d1aee659ec1b8f54ee9501576dc08ea4021d7ede01431691a27310a7dcbda2437bcd3b9c451d@[::]:30002?discport=0"
]
把static-nodes.json文件放入各自(node0/,node1/, node2/)的admin.datadir
目录下,并Ctrl+D
终止掉console,并重新执行geth --datadir <dir> --prot <port> --nodiscover console
,如果觉得这样麻烦的话,或者以后动态添加节点时候,可考虑在每个节点的console里输入admin.addPeer("nodeInfo_encode")
来完成。
然后在console中可以如下验证,是否互相发现组成以太坊私链网络了
# 这样可看到其他节点的明细
> admin.peers
[{
caps: ["eth/63"],
id: "910d1bfcd763bb5157bc62f8b121eb21fb305d17e4e4437c0b094d3d6f2d72f1964b80eb8fa2cf6cd7d4cc2d44cfc1ed9b74275ea7fd42ab89b4d089023fb7d5",
name: "Geth/v1.6.7-stable-ab5646c5/linux-amd64/go1.8.3",
network: {
localAddress: "[::1]:43928",
remoteAddress: "[::1]:30001"
},
protocols: {
eth: {
difficulty: 1,
head: "0xa43519868915a64d3798abf1867b7bc769d1239442c69ff1eca8e3dfcd13209b",
version: 63
}
}
}, {
caps: ["eth/63"],
id: "acab97a2a287b740b5efc3af465ba7330b3d4948b05e26818822d1aee659ec1b8f54ee9501576dc08ea4021d7ede01431691a27310a7dcbda2437bcd3b9c451d",
name: "Geth/v1.6.7-stable-ab5646c5/linux-amd64/go1.8.3",
network: {
localAddress: "[::1]:60310",
remoteAddress: "[::1]:30002"
},
protocols: {
eth: {
difficulty: 1,
head: "0xa43519868915a64d3798abf1867b7bc769d1239442c69ff1eca8e3dfcd13209b",
version: 63
}
}
}]
>
# 可看到此节点发现了另外两个节点
> net.peerCount
2
>
目前整个testnet的目录结构如下
➜ tree
.
├── genesis.json
├── node0
│ ├── geth
│ │ ├── chaindata
│ │ │ ├── 000002.ldb
| | | ├── ...
│ │ ├── lightchaindata
│ │ │ ├── 000001.log
│ │ │ ├── ...
│ │ ├── LOCK
│ │ └── nodekey
│ ├── geth.ipc
│ ├── history
│ ├── keystore
│ │ └── UTC--2017-08-21T15-03-25.499242705Z--799a8f7796d1d20b8198a587caaf545cdde5de13
│ └── static-nodes.json
├── node1
│ ├── geth
│ │ ├── chaindata
│ │ │ ├── 000002.ldb
│ │ │ ├── ...
│ │ │ └── MANIFEST-000007
│ │ ├── lightchaindata
│ │ │ ├── ...
│ │ │ └── MANIFEST-000000
│ │ ├── LOCK
│ │ └── nodekey
│ ├── geth.ipc
│ ├── history
│ ├── keystore
│ │ └── UTC--2017-08-21T15-03-35.020270645Z--1458eac314d8fc922029095fae20483f55726017
│ └── static-nodes.json
└── node2
├── geth
│ ├── chaindata
│ │ ├── 000002.ldb
│ │ ├── ...
│ │ └── MANIFEST-000007
│ ├── lightchaindata
│ │ ├── ...
│ │ └── MANIFEST-000000
│ ├── LOCK
│ └── nodekey
├── geth.ipc
├── history
├── keystore
│ └── UTC--2017-08-21T15-03-46.899273318Z--3ca60eb49314d867ab75a3c7b3a5aa61c3d6ef71
└── static-nodes.json
15 directories, 56 files
开始挖矿
在每个节点的console输入如下,启动挖矿(共识记账):
# 这一步可以不需要,因为我们geth启动的时候,已经传入unlock参数了,因为console里执行unlock是有过期时间机制的,私网直接来。。。。
> personal.unlockAccount(eth.coinbase)
Unlock account 0x799a8f7796d1d20b8198a587caaf545cdde5de13
Passphrase:
true
>
# 这一个很重要,为后面的eth设置了默认账户
> eth.defaultAccount = eth.coinbase
"0x799a8f7796d1d20b8198a587caaf545cdde5de13"
>
> miner.start()
通过钱包交易
通过mist钱包界面,来查看基本信息和进行交易,不过之前需要在console开启RPC, node0的console里如下
> admin.startRPC("127.0.0.1", 8545, "*", "eth,net,web3,admin,personal")
启动的mist钱包
mist --node geth --node-datadir ./node0 --rpc http://localhost:8545
# 不启动rpc的话,也可以如下直接通过ipc通信
# mist --rpc ./node0/geth.ipc --node-networkid 378 --node-datadir ./node0
需要去node1上通过eth.accounts[0]
拿到账号信息,填入红框,点击发送交易,输入密码即可
另外也可以通过console命令行来完成上面操作:
>
> var sender = eth.accounts[0]
undefined
> var receiver = "0x1458eac314d8fc922029095fae20483f55726017"
undefined
> var amount = web3.toWei(10, "ether")
undefined
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x799a8f7796d1d20b8198a587caaf545cdde5de13
Passphrase:
true
> eth.sendTransaction({from:sender, to:receiver, value: amount})
"0x97ca1f5fa27df083e14b2ffb82c2a60744aeae0f1a7b5e735ca4d0c05c16f7b6"
>
写个合约
通过mist界面点击的“开发”->"Open Remix IDE", 会自动打开IDE工具,并且默认集成了一个投票的合约,合约内容不表
“create”按钮后面输入 10
-> 点击, 这时候需要输入密码解锁,因为需要支付一定的gas费用。
- 这个合约就是个投票程序
- 第一个部署的默认是主席身份,拥有指定别人投票的权利,因为我这里的mist是连接的node0,所以
- 合约初始化(也就是界面点击“create”时),需要输入"投案"个数
在每个节点的的console里,通过eth.contract(ABI).at(Address);
拿到合约对象,
- 其中node0的节点需要用
giveRightToVote
给其他账户授权投票权限 - ABI的信息,就是remix里Interface框对应的信息
- 合约地址通过
Copy address
得到
> var b = eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"delegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"winningProposal","outputs":[{"name":"_winningProposal","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"voter","type":"address"}],"name":"giveRightToVote","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"proposal","type":"uint8"}],"name":"vote","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"_numProposals","type":"uint8"}],"payable":false,"type":"constructor"}]).at('0xc825238a3348f0a679924796fcf1b1b64a8c1706')
undefined
> b.vote(9)
"0x1646e6547606a8ad0e183f1c9145eff08bbdfd860961d6c7d7367f5b70779cbd"
>
> b.giveRightToVote('0x1458eac314d8fc922029095fae20483f55726017')
"0x2759928ad03a2ed5bc4b9c54531eb83e25c4a468e71682f67b160ad3328c8173"
>
> b.giveRightToVote('0x3ca60eb49314d867ab75a3c7b3a5aa61c3d6ef71')
"0x46f756e613499f836e392011c8f6d7c23d378fd5a656bae775ecda8bf286c5b6"
> b.winningProposal()
9
其中值得注意的是b.vote(Web3 deploy
框的信息也可以直接部署合约。