quorum是jp摩根推出的区块链平台。它既可以搭建私人链又可以搭建联盟链,本事的共识算法也包括Raft、Istanbul BFT、PoA三种。我初次学习是利用Istabul BFT搭建联盟链。搭建的过程很快,但在后续增加节点入联盟的时候无法正确加入,研究了一阵后,新加入了一个节点。于是写了这篇文章,希望对后面初学的人,少走一点弯路。
搭建过程我参考了两个链接:
下载这两个软件并安装
mkdir workspace
cd workspace
git clone https://github.com/jpmorganchase/quorum-examples
vagrant up
vagrant ssh
虚拟系统默认位置在 c:user\用户名\VirtualBox VMs目录下,可以用压缩软件打开自行查看内部文件,7nodes节点在home/vagrant文件夹内,但无法更改,更改需要其它软件。还有vagrant up阶段会下载大量东西,最好搭梯子,不然有时速度很慢。
4. 配置、启动7nodes例子
cd quorum-examples/7nodes/
#./<consensu>-init.sh,不同共识算法分别启动不同-init.sh,
#consensu = raft/istanbul/clique,后面也的-start.sh文件一样。
./istanbul-init.sh
./istanbul-start.sh
在开始之前,最好多看看各个文件夹的README。quorum支持三种共识算法,Raft、Istanbul BFT、PoA,可以自行选择不同的共识算法。不同算法,代码不同。默认管理器也有tessera和constellation两种,两个功能类似,用哪个都可以。
我自己选的IBFT算法,IBFT默认选用tessera交易管理器,要更改直接在后面加constellation即可。
./istanbul-init.sh constellation
./cakeshop-start.sh
最后打开http://localhost:8999/,即可查看交易信息等。
在建立好以后,本以为一切大功告成,但是问题来了。我按照网上教程增加新的节点,节点可以增加,但始终无法加入联盟。很多方法用
admin.addPeer("enode:....")
返回都是true,但是始终无法在admin.peers中查看到。
网上方法行不通了,就打算自己查看源代码,了解系统默认节点建立过程。
我们在执行过程中,总共只干了两步
./istanbul-init.sh
./istanbul-start.sh
1.#!/bin/bash
2.set -u
3.set -e
.........
47.echo "[*] Cleaning up temporary data directories"
48.rm -rf qdata
49.mkdir -p qdata/logs
.......
70.genesisFile=./istanbul-genesis.json
71.tempGenesisFile=
.....
80.for i in `seq 1 ${
numNodes}`
81.do
echo "[*] Configuring node ${i}"
mkdir -p qdata/dd${
i}/{
keystore,geth}
if [[ "$istanbulTools" == "true" ]]; then
iMinus1=$(($i - 1))
cp ${
iMinus1}/nodekey qdata/dd${
i}/geth/nodekey
else
cp raft/nodekey${
i} qdata/dd${
i}/geth/nodekey
fi
cp ${
permNodesFile} qdata/dd${
i}/static-nodes.json
if ! [[ -z "${STARTPERMISSION+x}" ]] ; then
cp ${
permNodesFile} qdata/dd${
i}/permissioned-nodes.json
fi
cp keys/key${
i} qdata/dd${
i}/keystore
geth --datadir qdata/dd${
i} init $genesisFile
96.done
97.
98.#Initialise Tessera configuration
99../tessera-init.sh
101.#Initialise Cakeshop configuration
102../cakeshop-init.sh
103.rm -f $tempGenesisFile $permNodesFile
中间没用的就没写了,第49行建立qdata/logs文件夹,第70行将istanbul-genesis.json赋给genesiFile,中间还有一些if语句,但是对我这个模式没影响,就没贴出来了。第82-94行则分别将一些自带的文件复制进去。nodekey后面生成新节点会生成,permissioned-node.json为自带默认文件,可以自己更改,我没改。
到第95步就很关键,利用genesisFile建立第一个块,而这个genesisFile根据条件不同,可以是不同的文件。同一个区块链,必须是同一个创世块,我这里是istanbul-genesis.json,所以我后期新建节点必须基于istanbul-genesis.json这个建立区块链。
完了以后就是执行tessera-init.sh。
2.查看tessera-init.sh
1.#!/usr/bin/env bash
2.# Initialise data for Tessera nodes.
3.# This script will normally perform initialisation for 7 nodes, however
.......
34.numNodes=7
35.if [[ -f qdata/numberOfNodes ]]; then
36. numNodes=`cat qdata/numberOfNodes`
37.fi
.......
67.# Dynamically create the config for peers, depending on numNodes
68.getPeerIPs # get list of IP addresses for peers
69.peerList=
70.for i in `seq 1 ${
numNodes}`
71.do
72. if [[ $i -ne 1 ]]; then
peerList="$peerList,"
fi
portNum=$((9000 + $i))
hostIP=${
peerIPList[$i]}
if [[ "$hostIP" == "" ]]; then
hostIP="127.0.0.1"
echo "[*] WARNING: host IP for node $i not found in permissioned-nodes.json, defaulting to '${hostIP}'"
fi
peerList="${
peerList}
{
\"url\": \"http://${
hostIP}:${
portNum}\"
}"
88.done
89.
90.# Write the config for the Tessera nodes
currentDir=$(pwd)
for i in `seq 1 ${
numNodes}`
do
DDIR="${currentDir}/qdata/c${i}"
mkdir -p ${
DDIR}
mkdir -p qdata/logs
if [ "$encryptorType" == "NACL" ]; then
cp "keys/tm${i}.pub" "${DDIR}/tm.pub"
cp "keys/tm${i}.key" "${DDIR}/tm.key"
fi
rm -f "${DDIR}/tm.ipc"
103. serverPortP2P=$((9000 + ${
i}))
104. serverPortThirdParty=$((9080 + ${
i}))
105. serverPortEnclave=$((9180 + ${
i}))
.....
277.#create a copy of private-contract.js where the public key of the tessera node (privateFor) is replaced with 278.the newly generated key of the last node (numNodes)
279.if [ "$encryptorType" == "EC" ]; then
280. oldKey=$(cat keys/tm7.pub)
281. newKey=$(cat qdata/c${
numNodes}/tm.pub)
282. #replace all / with \/ in the newKey (otherwise sed complains about it)
283. newKey=$(echo $newKey | sed 's/\//\\\//g')
284. echo "OldKey: $oldKey NewKey: $newKey"
285. newFileName=qdata/ec-${
ENCRYPTOR_EC_ELLIPTIC_CURVE:-secp256r1}-private-contract.js
286. sed "s/\"$oldKey\"/\"$newKey\"/g" private-contract.js > $newFileName
287. echo "private-contract sample generated in $newFileName"
288.fi
第34-37行是确定numNode的数字的,默认是7,但是如果qdata文件下的numberOfNodes不是7,则将numberOfNodes中的数字赋值给numNode。这个numNode直接决定后面的一系列配置。
第67-88行是对各个节点端口的配置,默认ip是127.0.0.1,端口从9000依次开始分配。
第90-105是将密钥对复制到qdata文件内,并将一些服务器端口分配,特别serverPortThirdParty,这个是后面的cakeshop需要用到的。
后面都是一些常规操作。Istanbul-init.sh最后一步是./cakeshop-init.sh,,cakeshop-init.sh就是复制一些文件,没得什么看的,而且当时想法就是cakeshop最多影响图形化窗口,对内在联系没得影响。
1.#!/bin/bash
2.set -u
3.set -e
....
52.privacyImpl=tessera
53.tesseraOptions=
54.blockPeriod=5
55.verbosity=3
.....
98.numNodes=7
99.if [[ -f qdata/numberOfNodes ]]; then
100. numNodes=`cat qdata/numberOfNodes`
fi
if [ "$privacyImpl" == "tessera" ]; then
echo "[*] Starting Tessera nodes"
./tessera-start.sh ${
tesseraOptions}
elif [ "$privacyImpl" == "constellation" ]; then
echo "[*] Starting Constellation nodes"
./constellation-start.sh
elif [ "$privacyImpl" == "tessera-remote" ]; then
echo "[*] Starting tessera nodes"
./tessera-start-remote.sh ${
tesseraOptions}
else
113. echo "Unsupported privacy implementation: ${privacyImpl}"
114. usage
115.fi
117.echo "[*] Starting ${numNodes} Ethereum nodes with ChainID and NetworkId of $NETWORK_ID"
QUORUM_GETH_ARGS=${
QUORUM_GETH_ARGS:-}
set -v
ARGS="--nodiscover --verbosity ${verbosity} --istanbul.blockperiod ${blockPeriod} --networkid $NETWORK_ID --syncmode full --mine --minerthreads 1 --rpc --rpccorsdomain=* --rpcvhosts=* --rpcaddr 0.0.0.0 --rpcapi admin,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul,quorumPermission --unlock 0 --password passwords.txt $QUORUM_GETH_ARGS"
basePort=21000
baseRpcPort=22000
for i in `seq 1 ${
numNodes}`
do
port=$(($basePort + ${
i} - 1))
rpcPort=$(($baseRpcPort + ${
i} - 1))
permissioned=
if ! [[ -z "${STARTPERMISSION+x}" ]] ; then
permissioned="--permissioned"
fi
133. PRIVATE_CONFIG=qdata/c${
i}/tm.ipc nohup geth --datadir qdata/dd${
i} ${
ARGS} ${
permissioned} --rpcport ${
rpcPort} --port ${
port} 2>>qdata/logs/${
i}.log &
134.done
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'"
exit 0
第52-55行是后面117-134行节点配置的一些默认参数。
第98-115行则是根据不同的管理器,选择不同的.sh文件启动,我选择的是tessera,则会启动tessera-start.sh文件,同时第98-101行又是对numNodes值的确定。
第117-134则是对节点参数的配置,其中rpcport会影响后面cakeshop的参数。
1.#!/bin/bash
2.# Start Tessera nodes.
3.# This script will normally start up 7 nodes, however
4.# if file qdata/numberOfNodes exists then the script will read number
5.# of nodes from that file.
......
85.echo Config type $TESSERA_CONFIG_TYPE
87.numNodes=7
88.if [[ -f qdata/numberOfNodes ]]; then
89. numNodes=`cat qdata/numberOfNodes`
90.fi
92.echo "[*] Starting $numNodes Tessera node(s)"
93.
currentDir=`pwd`
for i in `seq 1 ${
numNodes}`
do
DDIR="qdata/c$i"
mkdir -p ${
DDIR}
mkdir -p qdata/logs
rm -f "$DDIR/tm.ipc"
DEBUG=""
if [ "$remoteDebug" == "true" ]; then
DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=500$i -Xdebug"
fi
#Only set heap size if not specified on command line
MEMORY=
if [[ ! "$jvmParams" =~ "Xm" ]]; then
MEMORY="-Xms128M -Xmx128M"
fi
CMD="java $jvmParams $DEBUG $MEMORY -jar ${tesseraJar} -configfile $DDIR/tessera-config$TESSERA_CONFIG_TYPE$i.json"
echo "$CMD >> qdata/logs/tessera$i.log 2>&1 &"
${
CMD} >> "qdata/logs/tessera$i.log" 2>&1 &
sleep 1
done
echo "Waiting until all Tessera nodes are running..."
DOWN=true
k=10
while ${
DOWN}; do
sleep 1
DOWN=false
for i in `seq 1 ${
numNodes}`
do
if [ ! -S "qdata/c${i}/tm.ipc" ]; then
128. echo "Node ${i} is not yet listening on tm.ipc"
DOWN=true
fi
set +e
#NOTE: if using https, change the scheme
#NOTE: if using the IP whitelist, change the host to an allowed host
result=$(curl -s http://localhost:900${
i}/upcheck)
set -e
if [ ! "${result}" == "I'm up!" ]; then
echo "Node ${i} is not yet listening on http"
DOWN=true
fi
done
k=$((k - 1))
if [ ${
k} -le 0 ]; then
echo "Tessera is taking a long time to start. Look at the Tessera logs in qdata/logs/ for help diagnosing the problem."
fi
echo "Waiting until all Tessera nodes are running..."
148.
149. sleep 5
150.done
echo "All Tessera nodes started"
exit 0
第87-90行仍然是对numNodes数值的确定,这个数值影响后面的循环次数,而后面都是建立一些系统文件,不需要复制7nodes文件夹内的文件,只与numNodes有关。
至此所以运行文件已经查看完了。基本就与istanbul-init, istanbul-start.sh, tessera-init.sh, tessera-start.sh, istanbul-genesis.json, numberOfNodes这5个文件相关。
原始节点过程:
初始化阶段利用istanbul-genesis.json创世块创建区块链,将permissioned-nodes.json文件放进各个文件夹里面,再将已有的密钥对分别复制到各个的文件夹内部,再根据数量对端口进行分配。
启动阶段:对各个节点进行参数配置,不涉及7nodes内部文件的复制。
总结:要向插入新的节点,必须做到三点:(1)利用istanbul-genesis.json创建节点;(2)生成密钥对;(3)修改numberOfNodes。tessera-start.sh阶段(128行)会对tm.ipc文件进行检测,存在则不会再次启动,而istanbul-start.sh会产生tm.ipc文件,因此在加入节点前,不能启动istanbul-start.sh文件。
(1)利用istanbul-genesis.json创建节点
在进入7nodes文件夹后,先istanbul-init.sh初始化,创建节点
geth --datadir qdata/dd8 init istanbul-genesis.json
(2)生成密钥对
密钥对需要利用tessera生成,系统自带没得tess,但是vagrant有tessera压缩包,位置在home/vagrant/tessera/tessera.jar。
alias tessera="java -jar /home/vagrant/tessera/tessera.jar"
将压缩包解压安装并命名为tessera命令,
tessera -keygen -filename tm8
再利用命令生成名字为tm8的命令对,有两个文件,一个公钥,一个私钥,注意,这个命令加地址没用,必须在home/vagrant/quorum-examples/keys中使用这个命令。
(3)修改numberOfNodes
numberOfNodes在home/vagrant/quorum-examples/qdata目录下,
vi qdata/numberOfNodes
将7改成8。
这样再tessera-init.sh重新初始化,再启动就行了。
./tessera-init.sh
./istanbul-start.sh
./cakeshop-start.sh
(4)cakeshop增加节点
通过http://localhost:8999/进入cakeshop,在add node中,geth rpc填写22007,tessera 3rd party填写9088,即可将新加入的节点写入。再通过不同节点的peers增加节点,即可接入联盟链中,在新节点中,区块与其余7点一致。
虽然很简单,但是花了两三天时间才搞好。这个东西比较偏,网上查找的资料也解决不了,写下来,给和我一样的初学者一点解决思路。
本文解决方法基于vagrant 2020.03.18 ubuntu版本,直接在虚拟机内部操作应该也可以,我自己更新到ubuntu 18.04.4也是可行的,quorum用的也是2020.03.30在Github上JP摩根/quorum-examples的数据。