天天看點

美圖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. 檢視驗證人是否設定成功

docker exec -it meitu_node_3 /bin/sh
           

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/Shanghai

  meitu_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/Shanghai

  meitu_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)
           
根據美圖解釋,這裡

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')
           

七、參考

  • 美團DPOS以太坊節點網絡啟動和測試(Docker版)
  • 以太坊測試區塊鍊環境搭建

繼續閱讀