美图DPOS以太坊教程(Docker版)

一、前言 最近,需要接触区块链项目的主链开发,在EOSBTCethereum超级账本这几种区块链技术当中,相互对比后,最终还是以go-ethereum为解决方案。
ethereum为基准去找解决方案,最终找到了2个符合自己要求的方案,分别如下:美图、gttc。本来是想用gttc的这个解决方案的,但是它是基于go-ethereum最新源码来进行二次开发的,相对不稳定,因此还是用美图的解决方案了,毕竟这公司大一点,没那么多坑。
二、源码 为了方便测试,我们需要将节点最大验证器数修改一下,这样便于我们进行简单测试
修改consensus/dpos/dpos.go文件的maxValidator

// 新值 maxValidatorSize = 21 // 旧值 maxValidatorSize = 3

三、构建
# 进入源码根目录 docker build . -t meitugeth

注意:构建过程中会用到build\env.sh文件,会提示无权限,因此你需要给该文件赋予执行权限。
sudo chmod 777 build\env.sh

四、部署 1. 创建节点数据目录 最好事先创建好相应的目录,否则运行后,动态创建目录,会有权限的问题,当然也可以通过命令进行设置目录的权限。
mkdir ~/data mkdir ~/data/meitu mkdir ~/data/meitu/node1 mkdir ~/data/meitu/node2 mkdir ~/data/meitu/node3

2. 编写docker-compose.yml文件
version: '3' services: meitu_node_1: image: meitugeth container_name: meitu_node_1 build: context: . command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 ports: - 15450:8545 - 15460:8546 - 10303:30303 - 10303:30303/udp - 10304:30304/udp volumes: - /etc/localtime:/etc/localtime - ~/data/meitu/node1/:/root/.ethereum/ environment: TZ: Asia/Shanghaimeitu_node_2: image: meitugeth container_name: meitu_node_2 build: context: . command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 depends_on: - meitu_node_1 ports: - 25450:8545 - 25460:8546 - 20303:30303 - 20303:30303/udp - 20304:30304/udp volumes: - /etc/localtime:/etc/localtime - ~/data/meitu/node2/:/root/.ethereum/ environment: TZ: Asia/Shanghaimeitu_node_3: image: meitugeth container_name: meitu_node_3 build: context: . command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 depends_on: - meitu_node_1 ports: - 45450:8545 - 45460:8546 - 40303:30303 - 40303:30303/udp - 40304:30304/udp volumes: - /etc/localtime:/etc/localtime - ~/data/meitu/node3/:/root/.ethereum/ environment: TZ: Asia/Shanghai

3. 启动节点 在根目录下启动3个以太坊节点
docker-compose up --build -d

4. 启动思路 美图以太坊这块有2种启动网络:
  • 在创世块里配置好第一验证节点,然后启动
  • 混合POW和DPOS,用POW进行投票,产生第一批验证节点,并自动切换到DPOS
5. 首次启动 1. 进入容器
进入容器命令如下:
# 模板 docker exec -it [容器名|容器ID] /bin/sh # 例子 docker exec -it meitu_node_1 /bin/sh docker exec -it meitu_node_2 /bin/sh docker exec -it meitu_node_3 /bin/sh

2. 进入geth JavaScript控制台
# 方式一 geth attach ipc:/root/.ethereum/geth.ipc # 方式二 docker exec -it meitu_node_1 geth attach ipc:/root/.ethereum/geth.ipc # 方式三:使用别名 alias geth="docker exec -it meitu_node_1 geth attach ipc:/root/.ethereum/geth.ipc"

3. 创建账户
进入geth JavaScript 控制台后,需哟啊创建账户,命令如下;
# 模板 personal.newAccount('名称') # 例子 personal.newAccount('test001') "0x849f9442198282fb21539351edb0378463e4c251" personal.newAccount('test002') "0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed" personal.newAccount('test003') "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02"

创建成功后,需要将返回的地址记录起来
loop:重复1-3步骤,在node1、node2、node3分别创建账户
4. 编写创世块配置文件
将上一步操作生成的地址写入到创世块文件中,三个节点的地址分别为:
0x849f9442198282fb21539351edb0378463e4c251 0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed 0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02

【美图DPOS以太坊教程(Docker版)】将3个节点地址列入第一批验证人列表
{ "config": { "chainId": 7777, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock":0, "dpos":{ "validators":[ "0x849f9442198282fb21539351edb0378463e4c251", "0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed", "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02" ] } }, "nonce": "0x0000000000000042", "difficulty": "0x020000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", "gasLimit": "0x500000", "alloc": {} }

创世配置参数说明:
  • nonce:64位随机数,用于挖矿
  • timestamp:创世块的时间戳
  • parentHash:上一个区块的hash值,因为是创世块,所以这个值是0
  • mixHash:与nonce配合用于挖矿,由上一个区块的一部分生成hash
  • extraData:附加信息,任意填写
  • gasLimit:对GAS的消耗总量限制,用来限制区块能包含的交易信息总和
  • difficulty:难度值,越大越难
  • coinbase:矿工账号,第一个区块挖出后将给这个矿工账号发送奖励的以太币
  • alloc:预设账号以及账号的以太币数量,测试链挖矿比较容易可以不配置
  • chainId:指定了独立的区块链网络ID,不同ID网络的节点无法互相连接
5. 初始化创世目录
1. 删除每个节点下geth目录,保留keystore 移除geth目录,便于移除旧的无效数据,因为节点刚启动的时候,使用的是默认创世配置,而自定义的又不一样,因此需要移除。
sudo rm -rf ~/data/meitu/node1/geth sudo rm -rf ~/data/meitu/node2/geth sudo rm -rf ~/data/meitu/node3/geth

2. 拷贝创世配置到数据目录 将创世配置拷贝到数据目录中,便于容器内能访问,至于这个目录跟docker-compose.yml映射的目录有关。
因此,命令也要相应的变更。
cp genesis.json ~/data/meitu/node1 cp genesis.json ~/data/meitu/node2 cp genesis.json ~/data/meitu/node3

3. 初始化创世配置 进入容器,并执行初始化命令。
# 节点1 docker exec -it meitu_node_1 /bin/sh geth init /root/.ethereum/genesis.json # 节点2 docker exec -it meitu_node_2 /bin/sh geth init /root/.ethereum/genesis.json # 节点3 docker exec -it meitu_node_3 /bin/sh geth init /root/.ethereum/genesis.json

**loop:重复在node2、node3上分别执行init
6. 重启节点网络
以下命令需要在docker-compose.yml文件的当前目录方可运行。
# 移除容器 docker-compose down # 启动容器 docker-compose up -d

7. 查看验证人是否设置成功
1. 进入容器
docker exec -it meitu_node_3 /bin/sh

2. 进入geth JavaScript控制台
geth attach ipc:/root/.ethereum/geth.ipc

3. 执行获取验证人列表命令
dpos.getValidators() ["0x849f9442198282fb21539351edb0378463e4c251", "0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed", "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02"]

8. 节点互联
1. 查看节点是否互联
admin.peers []

返回的数据为[],说明节点之间没有互相发现。
2. 设置节点互联 1. 查看每个节点信息
admin.nodeInfo

确认:enode都不一样,protocols都一样。
记下三个enode
"enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[::]:30303" "enode://6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d3404b9c9711538289fa76504fecf33cf0e36cce7b0414604f673abe93012413@[::]:30303" "enode://e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204a541e00ef0b497704ec0e91017799a73283e53f6dffdeef492a4230626b10b6@[::]:30303"

2. 查看docker容器网络信息
# 显示docker所有网络信息 docker network ls # 查看具体网络信息 docker network inspect [网络名称] # 例子 docker network inspect docker_default

执行查看网络信息命令后,会返回一下内容
[ { "Name": "docker_default", "Id": "984fabf7e51b07c1984114720f98f305cc61cc26546cf9da4bcbbbc36a591351", "Created": "2018-12-05T06:06:13.435067024Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.20.0.0/16", "Gateway": "172.20.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "263f1553108cc8ea00b76598adb6d66649287943c7d0f50d0be02963863ff45c": { "Name": "meitu_node_2", "EndpointID": "9f4114a81133bd490b501ac45fd50fe512309f9096b5468054bd074bad45a07d", "MacAddress": "02:42:ac:14:00:03", "IPv4Address": "172.20.0.3/16", "IPv6Address": "" }, "3acd943c8e0759dc241d7cc623b1a1ca45096480dcd2fd0daa8b951407eb40bf": { "Name": "meitu_node_3", "EndpointID": "0480296c4e8217862358143a084da2d1563cd4f60105e6020434f222320681a5", "MacAddress": "02:42:ac:14:00:04", "IPv4Address": "172.20.0.4/16", "IPv6Address": "" }, "416fe5eb074c24f032d5bd49a6be68cf293a30af17780133b9fa63663e4b7097": { "Name": "meitu_node_1", "EndpointID": "0dc00e9c6f63f844502525b34e87baf62f4de852ac46907951d4b689dd89635f", "MacAddress": "02:42:ac:14:00:02", "IPv4Address": "172.20.0.2/16", "IPv6Address": "" } }, "Options": {}, "Labels": { "com.docker.compose.network": "default", "com.docker.compose.project": "docker", "com.docker.compose.version": "1.23.1" } } ]

找到不同容器中,相应的IP地址。
记录下每个节点的IP,也可以用127.0.0.1加节点映射到本机的不同网络端口。
meitu_node_1 172.20.0.2 meitu_node_2 172.20.0.3 meitu_node_3 172.20.0.4

3. 添加监视器 进入节点1 geth JavaScript 控制台后,执行以下命令:
# 添加节点2的监视器 admin.addPeer("enode://6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d3404b9c9711538289fa76504fecf33cf0e36cce7b0414604f673abe93012413@[172.20.0.3]:30303") # 添加节点3的监视器 admin.addPeer("enode://e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204a541e00ef0b497704ec0e91017799a73283e53f6dffdeef492a4230626b10b6@[172.20.0.4]:30303")

4.查看节点网络 在执行完添加监视器后,执行admin.peers即可看到节点已经互联起来。
admin.peers [{ caps: ["eth/62", "eth/63"], id: "6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d3404b9c9711538289fa76504fecf33cf0e36cce7b0414604f673abe93012413", name: "Geth/v1.7.4-stable-a487fc95/linux-amd64/go1.9.7", network: { localAddress: "172.20.0.2:60018", remoteAddress: "172.20.0.3:30303" }, protocols: { eth: { difficulty: 131189, head: "0xa00badd4041033da53c0a34cce5aa59885d7f638e00e1e307b04c02ee640df19", version: 63 } } }, { caps: ["eth/62", "eth/63"], id: "e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204a541e00ef0b497704ec0e91017799a73283e53f6dffdeef492a4230626b10b6", name: "Geth/v1.7.4-stable-a487fc95/linux-amd64/go1.9.7", network: { localAddress: "172.20.0.2:36700", remoteAddress: "172.20.0.4:30303" }, protocols: { eth: { difficulty: 131076, head: "0xe30ff3a8d1ae16384369c45d106841ef44e83c12eae2e2c66dce1bdccc9ba4d6", version: 63 } } }]

注意:这一步完成了,仅仅是临时的,每次重启docker之后admin.peers会重新为空。
9. 配置永久互联
临时互联不方便,可以将bootnodes配置到启动文件中。
修改docker-compose.yml文件,将节点1的连接配置进去。
version: '3' services: meitu_node_1: image: meitugeth container_name: meitu_node_1 build: context: .. command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 ports: - 15450:8545 - 15460:8546 - 10303:30303 - 10303:30303/udp - 10304:30304/udp volumes: - /etc/localtime:/etc/localtime - ~/data/meitu/node1/:/root/.ethereum/ environment: TZ: Asia/Shanghaimeitu_node_2: image: meitugeth container_name: meitu_node_2 build: context: .. command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 --bootnodes enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[172.20.0.2]:30303 depends_on: - meitu_node_1 ports: - 25450:8545 - 25460:8546 - 20303:30303 - 20303:30303/udp - 20304:30304/udp volumes: - /etc/localtime:/etc/localtime - ~/data/meitu/node2/:/root/.ethereum/ environment: TZ: Asia/Shanghaimeitu_node_3: image: meitugeth container_name: meitu_node_3 build: context: .. command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 --bootnodes enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[172.20.0.2]:30303 depends_on: - meitu_node_1 ports: - 45450:8545 - 45460:8546 - 40303:30303 - 40303:30303/udp - 40304:30304/udp volumes: - /etc/localtime:/etc/localtime - ~/data/meitu/node3/:/root/.ethereum/ environment: TZ: Asia/Shanghai

这个时候,“主网”启动成功了!!!
五、运行 1. 解锁账户 分别在3个节点上把validator无限期解锁,谁不解锁谁别出块、跳过你。
这里源码默认10秒1块。
进入geth JavaScript控制台后,执行以下命令:
# 模板 personal.unlockAccount(eth.validator,'名称',0) # 例子 personal.unlockAccount(eth.validator,'jce001',0) personal.unlockAccount(eth.validator,'jce002',0) personal.unlockAccount(eth.validator,'jce003',0)

根据美图解释,这里validatorcoinbase的区别:
  • coinbase:收取挖矿奖励
  • validator:可以设置为其他地址,但默认和coinbase一样。
2. 启动挖矿 进入geth JavaScript控制台后,执行以下命令:
miner.start()

3. 获取区块信息 进入geth JavaScript控制台后,执行以下命令:
# 模板 eth.getBlock(区块编号) # 例子 eth.getBlock(1) # 结果 { coinbase: "0x849f9442198282fb21539351edb0378463e4c251", difficulty: 1, extraData: "0xd783010704846765746887676f312e392e37856c696e7578000000000000000084c3b20f15eb99c19bb8567d3a27a52947efb816f647cbc4491540ee5de685d54f4126d236f031d33312dfab1a5d7a895bbd4d154afd366b30918a9af6868ab300", gasLimit: 5237761, gasUsed: 0, hash: "0x30964585add8b4ef65529f38ebe00bb6581fc9ae7323327f7dfd666754de883b", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000", nonce: "0x0000000000000000", number: 1, parentHash: "0x9390ffeae9812417704193667a0e106c8cd9e701217deb054737dab0325191d3", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 794, stateRoot: "0xc5a87ecb7262f6c507f488f5f93efa27df81550f1e7691c1ab2093a4218d2ca0", timestamp: 1543990290, totalDifficulty: 131073, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [], validator: "0x849f9442198282fb21539351edb0378463e4c251" }

六、测试 1. 获取账户余额
# 获取账户 eth.accounts# 获取余额 eth.getBalance(账户地址)# 格式化长度 web3.fromWei(数值)# 获取格式化后的余额,默认取第一个账户 web3.fromWei(eth.getBalacne(eth.accounts[0]))# 获取指定账户余额 web3.fromWei(eth.getBalance("0x849f9442198282fb21539351edb0378463e4c251"))

2. 交易
# 模板 eth.sendTransaction({from: "发送者", to: "接受者", value: 数量}) # 例子 eth.sendTransaction({from: "0x849f9442198282fb21539351edb0378463e4c251", to: "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02", value: 1000000000000000000})

通过获取余额判断是否转账成功,也可通过返回的交易编号查询情况。
web3.eth.getTransactionReceipt('0x8a4104da45c736c7a671ff7974b9b9a1848ff4c001f3cbcd4eb427aab50d604f')

七、参考
  • 美团DPOS以太坊节点网络启动和测试(Docker版)
  • 以太坊测试区块链环境搭建
转载于:https://www.cnblogs.com/jianxuanbing/p/10071238.html

    推荐阅读