美圖DPOS以太坊教程(Docker版)
一、前言
最近,需要接觸區塊鏈專案的主鏈開發,在EOS
、BTC
、ethereum
、超級賬本
這幾種區塊鏈技術當中,相互對比後,最終還是以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/Shanghai meitu_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/Shanghai meitu_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
將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://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea9902[email protected][::]:30303"
"enode://6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d[email protected][::]:30303"
"enode://e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204[email protected][::]: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://6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d[email protected][172.20.0.3]:30303")
# 新增節點3的監視器
admin.addPeer("enode://e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204[email protected][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/Shanghai
meitu_node_2:
image: meitugeth
container_name: meitu_node_2
build:
context: ..
command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 --bootnodes enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea9902[email protected][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/Shanghai
meitu_node_3:
image: meitugeth
container_name: meitu_node_3
build:
context: ..
command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 --bootnodes enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea9902[email protected][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)
根據美圖解釋,這裡
validator
和coinbase
的區別:
- 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')