1. 程式人生 > >以太坊聯盟鏈quorum搭建(一)

以太坊聯盟鏈quorum搭建(一)

關於quorum的介紹可以看一些這幾篇文章:

官網教程中建立了7個節點,由這7個節點來模擬聯盟鏈網路。

作為聯盟鏈,節點接入是有許可權要求的,通過permissioned-nodes.json檔案和permissioned引數來控制哪些節點可以和本節點建立連線,後面會詳解。對原來教程中有些比較省略,本文會將一些省略的地方介紹。

一、下載資源

1、首先安裝quorum

git clone --recursive https://github.com/jpmorganchase/quorum.git

cd quorum

make all

編譯完後,quorum/build/bin目錄下會生成如下檔案列表

abigen bootnode evm examples faucet geth p2psim puppeth rlpdump swarm wnode

將上面這些加入系統path中。

2、獲取costellation-node可執行檔案

下載下來後解壓,得到constellation-node,加入系統PATH中。

3、下載教程樣例:

git clone https://github.com/jpmorganchase/quorum-examples

二、搭建raft共識的聯盟鏈

因為聯盟間一般是存在信任關係,因此可以使用raft共識演算法,這個演算法可以容忍節點掛掉,但不能容忍拜占庭錯誤,即不能存在惡意節點。這樣就不用以太坊中的pow演算法了,節省cup、記憶體,而且出塊時間塊。另外,這個演算法中不會存在空塊,節省大量空間,如果沒有交易,那麼生成區塊就暫停。

1、初始化,建立7個節點,其中1-4號節點是permissioned節點,即這些節點只允許和permissioned-nodes.json中的節點建立連線,其他節點無法連線到這個節點。初始化的指令碼如下:

raft-init.sh

#!/bin/bash
set -u
set -e

echo "[*] Cleaning up temporary data directories"
rm -rf qdata
mkdir -p qdata/logs

echo "[*] Configuring node 1 (permissioned)"
mkdir -p qdata/dd1/{keystore,geth}
cp permissioned-nodes.json qdata/dd1/static-nodes.json
cp permissioned-nodes.json qdata/dd1/
cp keys/key1 qdata/dd1/keystore
cp raft/nodekey1 qdata/dd1/geth/nodekey
geth --datadir qdata/dd1 init genesis.json

echo "[*] Configuring node 2 (permissioned)"
mkdir -p qdata/dd2/{keystore,geth}
cp permissioned-nodes.json qdata/dd2/static-nodes.json
cp permissioned-nodes.json qdata/dd2/
cp keys/key2 qdata/dd2/keystore
cp raft/nodekey2 qdata/dd2/geth/nodekey
geth --datadir qdata/dd2 init genesis.json

echo "[*] Configuring node 3 (permissioned)"
mkdir -p qdata/dd3/{keystore,geth}
cp permissioned-nodes.json qdata/dd3/static-nodes.json
cp permissioned-nodes.json qdata/dd3/
cp keys/key6 qdata/dd3/keystore
cp keys/key3 qdata/dd3/keystore
cp raft/nodekey3 qdata/dd3/geth/nodekey
geth --datadir qdata/dd3 init genesis.json

echo "[*] Configuring node 4 (permissioned)"
mkdir -p qdata/dd4/{keystore,geth}
cp permissioned-nodes.json qdata/dd4/static-nodes.json
cp permissioned-nodes.json qdata/dd4/
cp keys/key4 qdata/dd4/keystore
cp raft/nodekey4 qdata/dd4/geth/nodekey
geth --datadir qdata/dd4 init genesis.json

echo "[*] Configuring node 5"
mkdir -p qdata/dd5/{keystore,geth}
cp permissioned-nodes.json qdata/dd5/static-nodes.json
cp keys/key5 qdata/dd5/keystore
cp raft/nodekey5 qdata/dd5/geth/nodekey
geth --datadir qdata/dd5 init genesis.json

echo "[*] Configuring node 6"
mkdir -p qdata/dd6/{keystore,geth}
cp permissioned-nodes.json qdata/dd6/static-nodes.json
cp raft/nodekey6 qdata/dd6/geth/nodekey
cp keys/key7 qdata/dd6/keystore
geth --datadir qdata/dd6 init genesis.json

echo "[*] Configuring node 7"
mkdir -p qdata/dd7/{keystore,geth}
cp permissioned-nodes.json qdata/dd7/static-nodes.json
cp raft/nodekey7 qdata/dd7/geth/nodekey
cp keys/key8 qdata/dd7/keystore
geth --datadir qdata/dd7 init genesis.json

以第一個節點的配置為例解釋命令:

第一行:建立節點1的目錄

第二行:permissioned-nodes.json中存了已經設定好的enode值,即節點的地址,通過這個值,執行geth客戶端時可以將不同的節點連線起來。與普通以太坊的geth客戶端不同,這裡需要一個static-nodes,應該是指定和哪些節點建立連線吧。如果啟動geth時不指定--permissioned,啟動後本節點就會尋找static-node裡面的節點建立連線。

另外,這個enode的值是與nodekey相關的,就是第五行中的nodekey檔案,兩者應該是一一對應的。所以第五行中將nodekey複製到這個目錄中,就相當於把一個節點的唯一標識複製過去。nodekey的生成方法:執行bootnode -genkey nodekey即可生成nodekey檔案,再執行bootnode -nodekey nodekey就會輸出本節點的enode資訊,記錄下來即可。

第三行:將permissioned-node.json複製到節點1的目錄下,這裡面記錄了哪些節點可以與本節點建立連線,不在這個列表裡的節點是不能和本節點連線的。

第四行:將一個賬戶檔案複製到節點1下的目錄,啟動geth後,用eth.accounts查賬戶,就會顯示這個檔案中的賬戶地址。

第五行:將nodekey複製到節點1目錄下,它作用如上面所述。

第六行:根據創世區塊資訊genesis.json初始化區塊鏈。

2、啟動constellation和geth節點

(1)constellation:可以將其看成是分散式金鑰伺服器、PGP加密和郵件傳輸代理的合併,用它來實現quorum的私有機制。

使用方法:生成金鑰constellation-node --generatekeys node,就會生成node.pub和node.key兩個金鑰檔案,分別是公私鑰檔案。如果在生成是使用密碼加密,那麼在啟動contellation-node時是需要輸入密碼的,為了方便,下面的方法中不輸入密碼。

下面來看啟動節點的constellation-start.sh指令碼:

#!/bin/bash
set -u
set -e

for i in {1..7}
do
    DDIR="qdata/c$i"
    mkdir -p $DDIR
    mkdir -p qdata/logs
    cp "keys/tm$i.pub" "$DDIR/tm.pub"
    cp "keys/tm$i.key" "$DDIR/tm.key"
    rm -f "$DDIR/tm.ipc"
    CMD="constellation-node --url=https://127.0.0.$i:900$i/ --port=900$i --workdir=$DDIR --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/"
    echo "$CMD >> qdata/logs/constellation$i.log 2>&1 &"
    $CMD >> "qdata/logs/constellation$i.log" 2>&1 &
done

DOWN=true
while $DOWN; do
    sleep 0.1
    DOWN=false
    for i in {1..7}
    do
	if [ ! -S "qdata/c$i/tm.ipc" ]; then
            DOWN=true
	fi
    done
done

其中,

第五行是代表一共啟動7個節點,需要在不同的節點目錄

第7-9行:建立節點目錄

第10-11行:將之前生成的公私鑰對放到對應幾點目錄中

第12行:刪除之前存在的舊的 .ipc檔案(如果存在的話)

第15行:將控制檯的輸出定向到指定檔案裡,如果啟動錯誤,可以去對應檔案檢視錯誤內容

第18-28行:等待所有constellation-node啟動完畢,其中24行 -S 是檢視對應檔案是否存在。

可以執行./constellation-start.sh來啟動測試,啟動完畢後輸入ps -ef | grep constellation-node可以檢視程序情況,輸入killall constellation-node 來終止程式執行。

(2)下面來啟動7個geth節點

raft-start.sh

#!/bin/bash
set -u
set -e

mkdir -p qdata/logs
echo "[*] Starting Constellation nodes"
./constellation-start.sh

echo "[*] Starting Ethereum nodes"
set -v
ARGS="--nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints"
PRIVATE_CONFIG=qdata/c1/tm.ipc nohup geth --datadir qdata/dd1 $ARGS --permissioned --raftport 50401 --rpcport 22000 --port 21000 --unlock 0 --password passwords.txt 2>>qdata/logs/1.log &
PRIVATE_CONFIG=qdata/c2/tm.ipc nohup geth --datadir qdata/dd2 $ARGS --permissioned --raftport 50402 --rpcport 22001 --port 21001 --unlock 0 --password passwords.txt 2>>qdata/logs/2.log &
PRIVATE_CONFIG=qdata/c3/tm.ipc nohup geth --datadir qdata/dd3 $ARGS --permissioned --raftport 50403 --rpcport 22002 --port 21002 --unlock 0 --password passwords.txt 2>>qdata/logs/3.log &
PRIVATE_CONFIG=qdata/c4/tm.ipc nohup geth --datadir qdata/dd4 $ARGS --permissioned --raftport 50404 --rpcport 22003 --port 21003 --unlock 0 --password passwords.txt 2>>qdata/logs/4.log &
PRIVATE_CONFIG=qdata/c5/tm.ipc nohup geth --datadir qdata/dd5 $ARGS --raftport 50405 --rpcport 22004 --port 21004 --unlock 0 --password passwords.txt 2>>qdata/logs/5.log &
PRIVATE_CONFIG=qdata/c6/tm.ipc nohup geth --datadir qdata/dd6 $ARGS --raftport 50406 --rpcport 22005 --port 21005 --unlock 0 --password passwords.txt 2>>qdata/logs/6.log &
PRIVATE_CONFIG=qdata/c7/tm.ipc nohup geth --datadir qdata/dd7 $ARGS --raftport 50407 --rpcport 22006 --port 21006 --unlock 0 --password passwords.txt 2>>qdata/logs/7.log &
set +v

echo
echo "All nodes configured. See 'qdata/logs' for logs, and run e.g. 'geth attach qdata/dd1/geth.ipc' to attach to the first Geth node."
echo "To test sending a private transaction from Node 1 to Node 7, run './runscript.sh private-contract.js'"

其中

第7行:呼叫上面寫的constellation-start.sh指令碼,啟動7個constellation

第11行:啟動geth節點需要的引數,這裡需要制定--raft來使用raft共識方法

第12-18行,啟動7個geth節點,需要指定raftport埠號,用 --permissioned引數來指定那些節點是有連線許可權的。

這樣,constellation-node和geth節點都啟動起來了,也就是環境已經搭建好了,可以在上面開發了。開發之前先做一下測試。

3、測試環境

[email protected]:~/project/test_chain/quorum/quorum-examples/examples/7nodes$ ps -ef | grep constellation-node
zhj      17150  2994  4 09:23 pts/3    00:00:03 constellation-node --url=https://127.0.0.1:9001/ --port=9001 --workdir=qdata/c1 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/
zhj      17174  2994  3 09:23 pts/3    00:00:02 constellation-node --url=https://127.0.0.2:9002/ --port=9002 --workdir=qdata/c2 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/
zhj      17200  2994  3 09:23 pts/3    00:00:02 constellation-node --url=https://127.0.0.3:9003/ --port=9003 --workdir=qdata/c3 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/
zhj      17218  2994  3 09:23 pts/3    00:00:03 constellation-node --url=https://127.0.0.4:9004/ --port=9004 --workdir=qdata/c4 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/
zhj      17226  2994  3 09:23 pts/3    00:00:03 constellation-node --url=https://127.0.0.5:9005/ --port=9005 --workdir=qdata/c5 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/
zhj      17265  2994  4 09:23 pts/3    00:00:03 constellation-node --url=https://127.0.0.6:9006/ --port=9006 --workdir=qdata/c6 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/
zhj      17282  2994  3 09:23 pts/3    00:00:03 constellation-node --url=https://127.0.0.7:9007/ --port=9007 --workdir=qdata/c7 --socket=tm.ipc --publickeys=tm.pub --privatekeys=tm.key --othernodes=https://127.0.0.1:9001/
zhj      17690 11030  0 09:24 pts/6    00:00:00 grep --color=auto constellation-node
[email protected]:~/project/test_chain/quorum/quorum-examples/examples/7nodes$ ps -ef |grep geth
zhj      17354  2994  1 09:23 pts/3    00:00:05 geth --datadir qdata/dd1 --nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints --permissioned --raftport 50401 --rpcport 22000 --port 21000 --unlock 0 --password passwords.txt
zhj      17355  2994  1 09:23 pts/3    00:00:05 geth --datadir qdata/dd2 --nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints --permissioned --raftport 50402 --rpcport 22001 --port 21001 --unlock 0 --password passwords.txt
zhj      17356  2994  2 09:23 pts/3    00:00:08 geth --datadir qdata/dd3 --nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints --permissioned --raftport 50403 --rpcport 22002 --port 21002 --unlock 0 --password passwords.txt
zhj      17357  2994  1 09:23 pts/3    00:00:05 geth --datadir qdata/dd4 --nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints --permissioned --raftport 50404 --rpcport 22003 --port 21003 --unlock 0 --password passwords.txt
zhj      17358  2994  1 09:23 pts/3    00:00:05 geth --datadir qdata/dd5 --nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints --raftport 50405 --rpcport 22004 --port 21004 --unlock 0 --password passwords.txt
zhj      17359  2994  1 09:23 pts/3    00:00:04 geth --datadir qdata/dd6 --nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints --raftport 50406 --rpcport 22005 --port 21005 --unlock 0 --password passwords.txt
zhj      17360  2994  1 09:23 pts/3    00:00:04 geth --datadir qdata/dd7 --nodiscover --raft --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum --emitcheckpoints --raftport 50407 --rpcport 22006 --port 21006 --unlock 0 --password passwords.txt
zhj      17946 11030  0 09:29 pts/6    00:00:00 grep --color=auto geth

說明節點都已正常執行,下面連線第一個節點的geth控制檯:

輸入geth attach qdata/dd1/geth.ipc,檢視基本資訊:

[email protected]:~/project/test_chain/quorum/quorum-examples/examples/7nodes$ geth attach qdata/dd1/geth.ipc
Welcome to the Geth JavaScript console!

instance: Geth/v1.7.2-stable-fd0e3b9d/linux-amd64/go1.10.1
coinbase: 0xed9d02e382b34818e88b88a309c7fe71e65f419d
at block: 0 (Thu, 01 Jan 1970 08:00:00 CST)
 datadir: /home/zhj/project/test_chain/quorum/quorum-examples/examples/7nodes/qdata/dd1
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 raft:1.0 rpc:1.0 txpool:1.0 web3:1.0

> eth.accounts
["0xed9d02e382b34818e88b88a309c7fe71e65f419d"]
> acc0 = eth.accounts[0]
"0xed9d02e382b34818e88b88a309c7fe71e65f419d"
> eth.getBalance(acc0)
1e+27
> eth.blockNumber
0

可以看到有一個賬戶地址,就是複製進去的賬戶,裡面有餘額(在創世檔案裡設定的),現在的區塊數目是0,現在沒有交易,是不產生區塊的,下面進行傳送一筆交易。

連線第7個節點的geth控制檯,檢視賬戶和餘額:

[email protected]:~/project/test_chain/quorum/quorum-examples/examples/7nodes$ geth attach qdata/dd7/geth.ipc 
Welcome to the Geth JavaScript console!

instance: Geth/v1.7.2-stable-fd0e3b9d/linux-amd64/go1.10.1
coinbase: 0xa9e871f88cbeb870d32d88e4221dcfbd36dd635a
at block: 0 (Thu, 01 Jan 1970 08:00:00 CST)
 datadir: /home/zhj/project/test_chain/quorum/quorum-examples/examples/7nodes/qdata/dd7
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 raft:1.0 rpc:1.0 txpool:1.0 web3:1.0

> eth.accounts
["0xa9e871f88cbeb870d32d88e4221dcfbd36dd635a"]
> acc0 =eth.accounts[0]
"0xa9e871f88cbeb870d32d88e4221dcfbd36dd635a"
> eth.getBalance(acc0)
0

然後從節點1的賬戶傳送一筆錢到節點7的賬戶:

> eth.sendTransaction({from:acc0, to:"0xa9e871f88cbeb870d32d88e4221dcfbd36dd635a", value: 100000})
"0xa0da7ef1632361e7b4d37f5258598bce0bbb074935c613c7e565a2c93964807f"

這時檢視賬戶7的餘額:

> eth.getBalance(acc0)
100000

再檢視區塊數目:

> eth.blockNumber
1

可以看到因為這筆交易已經生成了一個區塊。

如果要傳送一筆私有交易,可以用privateFor來指定,例如節點1賬戶傳送一筆私有交易給節點2的賬戶:

>eth.sendTransaction({from: acc0, to: acc1, value: 10000, privateFor:["QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="]})
"0x241c0d71035cce2891e82a883d010bbf0151e79fc3aace8966a626cf0598e7ff"

其中acc0是節點1的賬戶,acc1是節點2的賬戶,privateFor後面的地址是tm2.pub中的內容,返回的交易的地址,下面檢視這筆交易:

> eth.getTransaction("0x241c0d71035cce2891e82a883d010bbf0151e79fc3aace8966a626cf0598e7ff")
{
  blockHash: "0x6e74d807e1951e168489e54b23dd39f367f62312b1463a160c8730ad360f48bf",
  blockNumber: 1,
  from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0x241c0d71035cce2891e82a883d010bbf0151e79fc3aace8966a626cf0598e7ff",
  input: "0x33e358398b2bc693db176718211e9a80b2d2cf58df79e7a760c9b68baa2f41d95a5bec625b3f3eb3b0bb996afb6692e8187164fed745ebd726abd74455895d90",
  nonce: 0,
  r: "0xfa734439ccc3f8d54f22a012703d951a0891c2914b1361967e3f755466f778b6",
  s: "0x79ba2235b3d049e3132a32e4bac3d6023f3c460875d098a61c6c829d3c65af7f",
  to: "0xca843569e3427144cead5e4d5999a3d0ccf92b8e",
  transactionIndex: 0,
  v: "0x25",
  value: 100000
}

其中倒數第二行的v欄位值是0x25,即37。當值為37、 38時代表是一筆私有交易。

(7.13補充:我又試了一下,現在quorum還不支援私有交易傳送以太幣,上面這筆私有交易可以成功傳送並被記錄在區塊裡,但是查一下雙方的餘額時,發現都沒有改變。github上開發者是這麼說的:連結。但是私有合約可以正常執行,下一篇文章中有具體介紹)

本篇到此結束,下一篇文章介紹在quorum上部署和執行智慧合約。