最近在研究Quorum, 虽然Quorum官方有7nodesample可以快速创建一个quorum区块链网络,但它隐藏了众多配置细节和步骤,目的是为了新手快速体验其功能。所以如果希望深入了解其机理并进行定制的话,手动搭建必不可少,以下将详细展示搭建步骤。
Quorum是基于以太坊的,最突出的改进在于支持IBFT(Istanbul Byzantine Fault Tolerant)共识协议以及支持私密交易(private transaction),本教程将搭建支持IBFT和私密交易的拥有三个node和三个transaction manager的Quorum区块链网络。
一、规划目录结构及端口
根据Quorum的架构,我们创建三个node和三个transaction manager的网络,每个node和每个transaction manager都需要有自己的data目录,因此我们的目录结构规划如下:
/opt/dev/testnet/data --数据文件根目录
|_node1
| |_dd --node1数据目录
| | |_geth
| | |_keystore
| |_tm --transaction manager1数据目录
|_node2
| |_dd --node2数据目录
| | |_geth
| | |_keystore
| |_tm --transaction manager2数据目录
|_node3
|_dd --node3数据目录
| |_geth
| |_keystore
|_tm --transaction manager3数据目录
创建脚本:
mkdir -p /opt/dev/quorum/testnet/data
cd /opt/dev/quorum/testnet/data
mkdir -p node1/dd/geth/
mkdir -p node1/dd/keystore/
mkdir -p node1/tm
mkdir -p node2/dd/geth/
mkdir -p node2/dd/keystore/
mkdir -p node2/tm
mkdir -p node3/dd/geth/
mkdir -p node3/dd/keystore/
mkdir -p node3/tm
端口的规划如下
#node端口
32001~32003 --rpc服务端口
31001~31003 --node间peer通信端口
50401~50403 --raft协议端口
#transaction manager 端口
9101~9104 --tx manager间通信端口
9181~9184 --服务端口
二、创建账户Account
不同于Hyperledger Fabric拥有MSP组件可以创建管理组织以及证书,以太坊是完全基于公链设计的,并不具备这种PKI的集中管理的机制,Quorum对此并无任何改造和增强,因此account的生成完全是可以离线操作的,而账户的本质其实就是一个椭圆曲线的私钥。本文之所以要预先准备账户是为了在之后的node console中方便的使用web3的api, 因为有些api是要求节点中配置账户信息的。
本文使用的Quorum版本是V2.2.3
),请自行下载解压出geth可执行文件,创建账户的命令如下, 提示输入密码的时候可以直接回车不进行设置:
\>geth --datadir=/opt/dev/quorum/testnet/data/node1/dd/ account new
\>geth --datadir=/opt/dev/quorum/testnet/data/node2/dd/ account new
\>geth --datadir=/opt/dev/quorum/testnet/data/node3/dd/ account new
在datadir路径keystore下会生成用户私钥文件,打开其中一个查看:
\>ll /opt/dev/quorum/testnet/data/node1/dd/keystore
-rw------- 1 root root 491 Apr 24 16:19 UTC--2019-04-24T08-19-29.095420057Z--9affedff10f7229c680819d5eeb12c3624f6baeb
\>more /opt/dev/quorum/testnet/data/node1/dd/keystore/UTC--2019-04-24T08-19-29.095420057Z--9affedff10f7229c680819d5eeb12c3624f6baeb
{"address":"9affedff10f7229c680819d5eeb12c3624f6baeb","crypto":{"cipher":"aes-128-ctr","ciphertext":"18e7b1d213c913524e0ba16025e3bd99bcff2e9dd572874b2ffb4d55bc4cb2dd"
,"cipherparams":{"iv":"b8f2a0e7347f47d21c8181d49f639a67"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"c0933ccaa243e91789696127f0bc9c4268aed0
5a0dc2c8da8cc053faac516b28"},"mac":"119ce5ab369c2dc5e1ee07f814ebd6d83f9eeb04a94336e8da873edb9d904ae8"},"id":"40458a80-fb2e-47cd-84ee-b8d42c37ca1b","version":3}
json中的address即为账户地址:9affedff10f7229c680819d5eeb12c3624f6baeb.刚才的命令生成的三个账户地址如下:
账户1: 9affedff10f7229c680819d5eeb12c3624f6baeb
账户2: 5c8822ab6af840f8c3e52fe9a71c43f90672728e
账户3: 915779c113cffecac6583310d80a8def09546272
三、创建nodekey
nodekey是node的唯一标识,需要配置在node数据文件路径中,以太坊nodekey生成的标准命令是使用bootnode --genkey=nodekey命令,但若使用IBFT共识的话,对nodekey是有严格要求的, validator id是根据nodekey生成的,所以如果选用IBFT共识我们就需要使用istanbul-tool来生成nodekey。istanbul-tool需要从git
)上clone,自行构建出istanbul命令,然后在一个临时目录/opt/dev/quorum/testnet/tmp中执行:
\>mkdir -p /opt/dev/quorum/testnet/tmp/
\>cd /opt/dev/quorum/testnet/tmp/
\>istanbul setup --num 3 --nodes --quorum --save --verbose
\>ll
drwxr-xr-x 2 root root 4096 Apr 25 16:22 0/
drwxr-xr-x 2 root root 4096 Apr 25 16:22 1/
drwxr-xr-x 2 root root 4096 Apr 25 16:22 2/
-rwxr-xr-x 1 root root 1524 Apr 25 16:22 genesis.json*
-rwxr-xr-x 1 root root 500 Apr 25 16:22 static-nodes.json*
根据我们的参数--num 3,命令将创建3个nodekey, 打开其中一个可以看到:
\>more 0/nodekey
c3828ef20a55925c60587f63359798249fb8c20992ef68ce9d0ff10abfd8c858
手动将三个nodekey文件分别拷贝到相应node的数据路径
cp 0/nodekey /opt/dev/quorum/testnet/data/node1/dd/geth/nodekey
cp 1/nodekey /opt/dev/quorum/testnet/data/node2/dd/geth/nodekey
cp 2/nodekey /opt/dev/quorum/testnet/data/node3/dd/geth/nodekey
四、static-nodes.json, permissioned-nodes.json文件
Quorum 联盟链的node是有准入限制的,体现在permissioned-nodes.json文件中,permissioned-nodes.json同时可以用作静态节点配置,所以在本例中static-nodes.json, permissioned-nodes.json两个文件是相同的:
首先打开我们使用istanbul命令创建的static-nodes.json文件
\>vim /opt/dev/quorum/testnet/tmp/static-nodes.json
根据我们之前的端口规划,修改端口如下:
[
"enode://f3c5520a8bea82dcbf28412e61c0f225fc8c5dbd9e729529b91d3755def5583b761bf94d11c434659dd4f8ffba696a1258cb8a8ce8e5ba25c2d3f25a965375a2@127.0.0.1:31001?discport=0&raftport=50401",
"enode://926c705394776e3fe25d79f9e290444a424ffcd3855fb212af049abb80d658a59f3f5843876d587f33419e262b96d60a14c914c6076312e7e9af8c93e36f9c3b@127.0.0.1:31002?discport=0&raftport=50402",
"enode://8a6690de0099da4fbba4eb741f875b15b1adc823149d85302cddeaf5c0c77e504b78fe23e93ce199fdb1c0959954c95031d0959c861fb2ce6ecfac50a49768d0@127.0.0.1:31003?discport=0&raftport=50403"
]
然后将文件配置到相应的node路径中:
cp /opt/dev/quorum/testnet/tmp/static-nodes.json /opt/dev/quorum/testnet/data/node1/dd/static-nodes.json
cp /opt/dev/quorum/testnet/tmp/static-nodes.json /opt/dev/quorum/testnet/data/node1/dd/permissioned-nodes.json
cp /opt/dev/quorum/testnet/tmp/static-nodes.json /opt/dev/quorum/testnet/data/node2/dd/static-nodes.json
cp /opt/dev/quorum/testnet/tmp/static-nodes.json /opt/dev/quorum/testnet/data/node2/dd/permissioned-nodes.json
cp /opt/dev/quorum/testnet/tmp/static-nodes.json /opt/dev/quorum/testnet/data/node3/dd/static-nodes.json
cp /opt/dev/quorum/testnet/tmp/static-nodes.json /opt/dev/quorum/testnet/data/node3/dd/permissioned-nodes.json
五、创建创世文件genesis.json并初始化区块链
可以使用之前istanbul命令生成的genesis.json
\>vim /opt/dev/quorum/testnet/tmp/genesis.json
内容修改如下:
{
"alloc": {
"0x9affedff10f7229c680819d5eeb12c3624f6baeb": {
"balance": "1000000000000000000000000000"
},
"0x5c8822ab6af840f8c3e52fe9a71c43f90672728e": {
"balance": "1000000000000000000000000000"
},
"0x915779c113cffecac6583310d80a8def09546272": {
"balance": "1000000000000000000000000000"
}
},
"coinbase": "0x0000000000000000000000000000000000000000",
"config": {
"homesteadBlock": 0,
"byzantiumBlock": 0,
"chainId": 10,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"isQuorum": true,
"istanbul": {
"epoch": 30000,
"policy": 0
}
},
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f885f83f9490ccaba53ed0c2979d4659692ca3b0ecc385fd7094a02f4bb093989222608d25c6c57a5b40526f679694f0fa966e6efa633080d1603e7daea5176de87d82b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0",
"gasLimit": "0xE0000000",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"nonce": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00"
}
解释一下修改了哪些地方,其实就是把alloc部分的account换成了我们刚才生成的account, 并修改了我们希望的预置balance余额,对于quorum来说balance没什么用处,初始化一个较大的值即可。istanbul命令已经帮我们配置了"isQuorum"以及"istanbul"的属性,不必我们再去设置:
{
"isQuorum": true,
"istanbul": {
"epoch": 30000,
"policy": 0
}
}
另外着重说一下"extraData"这个属性:
{
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f885f83f9490ccaba53ed0c2979d4659692ca3b0ecc385fd7094a02f4bb093989222608d25c6c57a5b40526f679694f0fa966e6efa633080d1603e7daea5176de87d82b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0"
}
这个属性的值是istanbul工具将合法的validator编码而成的二进制字段,我们可以使用istanbul命令来解码看一下:
\>istanbul extra decode --extradata 0x0000000000000000000000000000000000000000000000000000000000000000f885f83f9490ccaba53ed0c2979d4659692ca3b0ecc385fd7094a02f4bb093989222608d25c6c57a5b40526f679694f0fa966e6efa633080d1603e7daea5176de87d82b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0
vanity: 0x0000000000000000000000000000000000000000000000000000000000000000
validator: 0x90ccABA53ed0C2979D4659692CA3B0EcC385FD70
validator: 0xA02f4bB093989222608D25C6C57a5B40526F6796
validator: 0xF0FA966E6EfA633080d1603E7dAea5176De87d82
seal: 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
可以看到解码出来三个validator,这里需要特别注意的是,这三个validator并不是account,而是从三个nodekey生成的id,也就是说三个node在进行共识投票的时候,跟node上配置的account是无关的。很多人把这个validator理解成了account,将account id用istanbul工具编码后配置到了genesis.json的extraData字段中,这样做的话,实际的共识发起者是根据nodekey生成的validator, 区块打包共识的时候发现发起者并不在extraData编码的validators列表中,则为非法发起者,导致无法成功达成共识。
然后使用genesis.json初始化三个node节点
\>geth --datadir /opt/dev/quorum/testnet/data/node1/dd init /opt/dev/quorum/testnet/tmp/genesis.json
\>geth --datadir /opt/dev/quorum/testnet/data/node2/dd init /opt/dev/quorum/testnet/tmp/genesis.json
\>geth --datadir /opt/dev/quorum/testnet/data/node3/dd init /opt/dev/quorum/testnet/tmp/genesis.json
六、配置transaction manager
1、创建秘钥对
首先下载或自行编译, 图省事可以docker pull quorumengineering/tessera:0.9, 然后启动一个容器,从容器的/tessera/tessera-app.jar 位置拷贝到宿主机/opt/dev/quorum/testnet/tmp/tessera-app-0.9.jar,然后执行如下命令,遇到弹出输入密码时同样可以直接回车:
\>cd /opt/dev/quorum/testnet/tmp
\>alias tessera="java -jar /opt/dev/quorum/testnet/tmp/tessera-app-0.9.jar"
\>tessera -keygen -filename 1
\>tessera -keygen -filename 2
\>tessera -keygen -filename 3
\>ll
-rw-r--r-- 1 root root 109 Apr 25 11:38 1.key
-rw-r--r-- 1 root root 44 Apr 25 11:38 1.pub
-rw-r--r-- 1 root root 109 Apr 25 11:38 2.key
-rw-r--r-- 1 root root 44 Apr 25 11:38 2.pub
-rw-r--r-- 1 root root 109 Apr 25 11:38 3.key
-rw-r--r-- 1 root root 44 Apr 25 11:38 3.pub
会生成三对秘钥对:
1.key
{
"type" : "unlocked",
"data" : {
"bytes" : "oBQw7B/TivaynIT9SQTx5Ni1jNV1M5s/J6+1r7KlCJ8="
}
}
1.pub
NPaOkPjlF3WFgA1WaqtANE0tqX/M8Rdr1h4SzQX0ghQ=
2.key
{
"type" : "unlocked",
"data" : {
"bytes" : "/FAEF3msNOcWNkLkzUdSdNLFvFSJgddjwV2HOWTV/Rk="
}
}
2.pub
T8olcFvm2JojQd616k1MIx/Gm2IEZPkyV4GutVvrPgM=
3.key
{
"type" : "unlocked",
"data" : {
"bytes" : "ZR00hvY4nCiG8sWQFasKvwGtOBi0b2oxNriVdMN++MY="
}
}
3.pub
Uc952L7QFuk8R5sGtjpfHb9EM+X6pTFRixVa+XgPBjc=
创建三个tessera.json配置文件,将下面文件中的keyData改为你自己的:
tessera-1.json
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:/opt/dev/quorum/testnet/data/node1/tm/db;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0",
"autoCreateTables": true
},
"serverConfigs":[
{
"app":"ThirdParty",
"enabled": true,
"serverAddress": "http://127.0.0.1:9181",
"communicationType" : "REST"
},
{
"app":"Q2T",
"enabled": true,
"serverAddress": "unix:/opt/dev/quorum/testnet/data/node1/tm/tm.ipc",
"communicationType" : "REST"
},
{
"app":"P2P",
"enabled": true,
"serverAddress": "http://127.0.0.1:9101",
"sslConfig": {
"tls": "OFF",
"generateKeyStoreIfNotExisted": true,
"serverKeyStore": "/opt/dev/quorum/testnet/data/node1/tm/server-keystore",
"serverKeyStorePassword": "quorum",
"serverTrustStore": "/opt/dev/quorum/testnet/data/node1/tm/server-truststore",
"serverTrustStorePassword": "quorum",
"serverTrustMode": "TOFU",
"knownClientsFile": "/opt/dev/quorum/testnet/data/node1/tm/knownClients",
"clientKeyStore": "/opt/dev/quorum/testnet/data/node1/tm/client-keystore",
"clientKeyStorePassword": "quorum",
"clientTrustStore": "/opt/dev/quorum/testnet/data/node1/tm/client-truststore",
"clientTrustStorePassword": "quorum",
"clientTrustMode": "TOFU",
"knownServersFile": "/opt/dev/quorum/testnet/data/node1/tm/knownServers"
},
"communicationType" : "REST"
}
],
"peer": [
{
"url": "http://127.0.0.1:9101"
},
{
"url": "http://127.0.0.1:9102"
},
{
"url": "http://127.0.0.1:9103"
}
],
"keys": {
"passwords": [],
"keyData": [
{
"config": {"data":{"bytes":"oBQw7B/TivaynIT9SQTx5Ni1jNV1M5s/J6+1r7KlCJ8="},"type":"unlocked"},
"publicKey": "NPaOkPjlF3WFgA1WaqtANE0tqX/M8Rdr1h4SzQX0ghQ="
}
]
},
"alwaysSendTo": []
}
tessera-2.json
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:/opt/dev/quorum/testnet/data/node2/tm/db;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0",
"autoCreateTables": true
},
"serverConfigs":[
{
"app":"ThirdParty",
"enabled": true,
"serverAddress": "http://127.0.0.1:9182",
"communicationType" : "REST"
},
{
"app":"Q2T",
"enabled": true,
"serverAddress": "unix:/opt/dev/quorum/testnet/data/node2/tm/tm.ipc",
"communicationType" : "REST"
},
{
"app":"P2P",
"enabled": true,
"serverAddress": "http://127.0.0.1:9102",
"sslConfig": {
"tls": "OFF",
"generateKeyStoreIfNotExisted": true,
"serverKeyStore": "/opt/dev/quorum/testnet/data/node2/tm/server-keystore",
"serverKeyStorePassword": "quorum",
"serverTrustStore": "/opt/dev/quorum/testnet/data/node2/tm/server-truststore",
"serverTrustStorePassword": "quorum",
"serverTrustMode": "TOFU",
"knownClientsFile": "/opt/dev/quorum/testnet/data/node2/tm/knownClients",
"clientKeyStore": "/opt/dev/quorum/testnet/data/node2/tm/client-keystore",
"clientKeyStorePassword": "quorum",
"clientTrustStore": "/opt/dev/quorum/testnet/data/node2/tm/client-truststore",
"clientTrustStorePassword": "quorum",
"clientTrustMode": "TOFU",
"knownServersFile": "/opt/dev/quorum/testnet/data/node2/tm/knownServers"
},
"communicationType" : "REST"
}
],
"peer": [
{
"url": "http://127.0.0.1:9101"
},
{
"url": "http://127.0.0.1:9102"
},
{
"url": "http://127.0.0.1:9103"
}
],
"keys": {
"passwords": [],
"keyData": [
{
"config": {"data":{"bytes":"/FAEF3msNOcWNkLkzUdSdNLFvFSJgddjwV2HOWTV/Rk="},"type":"unlocked"},
"publicKey": "T8olcFvm2JojQd616k1MIx/Gm2IEZPkyV4GutVvrPgM="
}
]
},
"alwaysSendTo": []
}
tessera-3.json
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:/opt/dev/quorum/testnet/data/node3/tm/db;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0",
"autoCreateTables": true
},
"serverConfigs":[
{
"app":"ThirdParty",
"enabled": true,
"serverAddress": "http://127.0.0.1:9183",
"communicationType" : "REST"
},
{
"app":"Q2T",
"enabled": true,
"serverAddress": "unix:/opt/dev/quorum/testnet/data/node3/tm/tm.ipc",
"communicationType" : "REST"
},
{
"app":"P2P",
"enabled": true,
"serverAddress": "http://127.0.0.1:9103",
"sslConfig": {
"tls": "OFF",
"generateKeyStoreIfNotExisted": true,
"serverKeyStore": "/opt/dev/quorum/testnet/data/node3/tm/server-keystore",
"serverKeyStorePassword": "quorum",
"serverTrustStore": "/opt/dev/quorum/testnet/data/node3/tm/server-truststore",
"serverTrustStorePassword": "quorum",
"serverTrustMode": "TOFU",
"knownClientsFile": "/opt/dev/quorum/testnet/data/node3/tm/knownClients",
"clientKeyStore": "/opt/dev/quorum/testnet/data/node3/tm/client-keystore",
"clientKeyStorePassword": "quorum",
"clientTrustStore": "/opt/dev/quorum/testnet/data/node3/tm/client-truststore",
"clientTrustStorePassword": "quorum",
"clientTrustMode": "TOFU",
"knownServersFile": "/opt/dev/quorum/testnet/data/node3/tm/knownServers"
},
"communicationType" : "REST"
}
],
"peer": [
{
"url": "http://127.0.0.1:9101"
},
{
"url": "http://127.0.0.1:9102"
},
{
"url": "http://127.0.0.1:9103"
}
],
"keys": {
"passwords": [],
"keyData": [
{
"config": {"data":{"bytes":"ZR00hvY4nCiG8sWQFasKvwGtOBi0b2oxNriVdMN++MY="},"type":"unlocked"},
"publicKey": "Uc952L7QFuk8R5sGtjpfHb9EM+X6pTFRixVa+XgPBjc="
}
]
},
"alwaysSendTo": []
}
七、启动transaction manager - tessera
执行如下命令后台启动三个tessera transaction manager
\>nohup java -Xms128M -Xmx128M -jar /opt/dev/quorum/testnet/tmp/tessera-app-0.9.jar -configfile /opt/dev/quorum/testnet/tmp/tessera-1.json >> /opt/dev/quorum/testnet/data/node1/tm.log &
\>nohup java -Xms128M -Xmx128M -jar /opt/dev/quorum/testnet/tmp/tessera-app-0.9.jar -configfile /opt/dev/quorum/testnet/tmp/tessera-2.json >> /opt/dev/quorum/testnet/data/node2/tm.log &
\>nohup java -Xms128M -Xmx128M -jar /opt/dev/quorum/testnet/tmp/tessera-app-0.9.jar -configfile /opt/dev/quorum/testnet/tmp/tessera-3.json >> /opt/dev/quorum/testnet/data/node3/tm.log &
tessera transaction manager启动成功后,会在相应的路径下生成ipc文件
/opt/dev/quorum/testnet/data/node1/tm/tm.ipc
/opt/dev/quorum/testnet/data/node2/tm/tm.ipc
/opt/dev/quorum/testnet/data/node3/tm/tm.ipc
八、启动quorum node
因为前面账户创建时候我们没有设置密码,所以建立三个空的password.txt文件
touch /opt/dev/quorum/testnet/data/node1/passwords.txt
touch /opt/dev/quorum/testnet/data/node2/passwords.txt
touch /opt/dev/quorum/testnet/data/node3/passwords.txt
执行如下命令后台启动三个quorum node, quorum node和tessera transaction manager通过ipc进行通信
\>export PRIVATE_CONFIG=/opt/dev/quorum/testnet/data/node1/tm/tm.ipc
\>nohup geth --identity node1-istanbul --datadir /opt/dev/quorum/testnet/data/node1/dd --permissioned --nodiscover --verbosity 5 --networkid 10 --rpc --rpcaddr 0.0.0.0 --rpcport 32001 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul --port 31001 --unlock 0 --password /opt/dev/quorum/testnet/data/node1/passwords.txt --emitcheckpoints --istanbul.blockperiod 1 --mine --minerthreads 1 --syncmode full >> /opt/dev/quorum/testnet/data/node1/dd.log &
\>export PRIVATE_CONFIG=/opt/dev/quorum/testnet/data/node2/tm/tm.ipc
\>nohup geth --identity node2-istanbul --datadir /opt/dev/quorum/testnet/data/node2/dd --permissioned --nodiscover --verbosity 5 --networkid 10 --rpc --rpcaddr 0.0.0.0 --rpcport 32002 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul --port 31002 --unlock 0 --password /opt/dev/quorum/testnet/data/node2/passwords.txt --emitcheckpoints --istanbul.blockperiod 1 --mine --minerthreads 1 --syncmode full >> /opt/dev/quorum/testnet/data/node2/dd.log &
\>export PRIVATE_CONFIG=/opt/dev/quorum/testnet/data/node3/tm/tm.ipc
\>nohup geth --identity node3-istanbul --datadir /opt/dev/quorum/testnet/data/node3/dd --permissioned --nodiscover --verbosity 5 --networkid 10 --rpc --rpcaddr 0.0.0.0 --rpcport 32003 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul --port 31003 --unlock 0 --password /opt/dev/quorum/testnet/data/node3/passwords.txt --emitcheckpoints --istanbul.blockperiod 1 --mine --minerthreads 1 --syncmode full >> /opt/dev/quorum/testnet/data/node3/dd.log &
至此我们的Quorum区块链网络就已经启动成功了。
九、验证
我们部署一个私有合约,以此来验证我们的区块链网络
首先连接到node1的console
\>geth attach /opt/dev/quorum/testnet/data/node1/dd/geth.ipc
zmm: cfgPath is PRIVATE_CONFIG
Welcome to the Geth JavaScript console!
instance: Geth/node1-istanbul/v1.8.18-stable-f681cbf3(quorum-v2.2.3)/linux-amd64/go1.12.1
coinbase: 0x90ccaba53ed0c2979d4659692ca3b0ecc385fd70
at block: 4 (Sat, 27 Apr 2019 15:56:13 CST)
datadir: /opt/dev/quorum/testnet/data/node1/dd
modules: admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>
贴入以下内容部署一个私有合约, 例子来自官方的7nodesample, 注意修改其中的"privateFor"字段为tessera秘钥对中3.pub的内容,表明由node1发起的这个合约只同node3进行私有
\>more 3.pub
Uc952L7QFuk8R5sGtjpfHb9EM+X6pTFRixVa+XgPBjc=
a = eth.accounts[0]
web3.eth.defaultAccount = a;
// abi and bytecode generated from simplestorage.sol:
// > solcjs --bin --abi simplestorage.sol
var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"payable":false,"type":"constructor"}];
var bytecode = "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029";
var simpleContract = web3.eth.contract(abi);
var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["Uc952L7QFuk8R5sGtjpfHb9EM+X6pTFRixVa+XgPBjc="]}, function(e, contract) {
if (e) {
console.log("err creating contract", e);
} else {
if (!contract.address) {
console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
} else {
console.log("Contract mined! Address: " + contract.address);
console.log(contract);
}
}
});
修改"privateFor"字段之后,将其贴入console:
instance: Geth/node1-istanbul/v1.8.18-stable-f681cbf3(quorum-v2.2.3)/linux-amd64/go1.12.1
coinbase: 0x90ccaba53ed0c2979d4659692ca3b0ecc385fd70
at block: 109 (Sat, 27 Apr 2019 16:17:03 CST)
datadir: /opt/dev/quorum/testnet/data/node1/dd
modules: admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> a = eth.accounts[0]
:false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type"0x9affedff10f7229c680819d5eeb12c3624f6baeb"
> web3.eth.defaultAccount = a;
"0x9affedff10f7229c680819d5eeb12c3624f6baeb"
>
> // abi and bytecode generated from simplestorage.sol:
undefined
> // > solcjs --bin --abi simplestorage.sol
undefined
> var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"payable":false,"type":"constructor"}];
undefined
>
> var bytecode = "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029";
undefined
>
> var simpleContract = web3.eth.contract(abi);
undefined
> var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["Uc952L7QFuk8R5sGtjpfHb9EM+X6pTFRixVa+XgPBjc="]}, function(e, contract) {
...... if (e) {
......... console.log("err creating contract", e);
......... } else {
......... if (!contract.address) {
............ console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
............ } else {
............ console.log("Contract mined! Address: " + contract.address);
............ console.log(contract);
............ }
......... }
...... });
Contract transaction send: TransactionHash: 0x74efa531fa5e8c1962a0b7cd2c1f3c34e5f19890cec27a1f90438edb074bda51 waiting to be mined...
undefined
> Contract mined! Address: 0xc48ba0d7ea03ab25a5f264845b848d847d391fc4
[object Object]
>
看到下方的Contract mined! Address: 0x5f71775e74bc96902c31df3205aca9a968811a42则说明IBFT工作正常,成功出块,接下来我们验证private隐私性,因为我们使用了privateFor,只允许node3持有私有数据,因此我们对node1、node2和node3分别调用智能合约,看看结果如何:
对于node1,我们继续使用刚才的console
> simple.get()
42
结果是我们创建合约时候赋值的42,接着打开node2终端
\>geth attach /opt/dev/quorum/testnet/data/node2/dd/geth.ipc
zmm: cfgPath is PRIVATE_CONFIG
Welcome to the Geth JavaScript console!
instance: Geth/node2-istanbul/v1.8.18-stable-f681cbf3(quorum-v2.2.3)/linux-amd64/go1.12.1
coinbase: 0xa02f4bb093989222608d25c6c57a5b40526f6796
at block: 445 (Sat, 27 Apr 2019 16:22:39 CST)
datadir: /opt/dev/quorum/testnet/data/node2/dd
modules: admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>
为了调用我们刚才创建的智能合约,simple.at处应该使用地址0xc48ba0d7ea03ab25a5f264845b848d847d391fc4,注意你应该把地址修改成你自己刚刚部署的contract地址,在node2的console贴入如下代码:
a = eth.accounts[0]
web3.eth.defaultAccount = a;
// abi and bytecode generated from simplestorage.sol:
// > solcjs --bin --abi simplestorage.sol
var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"payable":false,"type":"constructor"}];
var simpleContract = web3.eth.contract(abi);
var simple = simpleContract.at("0xc48ba0d7ea03ab25a5f264845b848d847d391fc4")
结果:
> a = eth.accounts[0]
:false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type"0x5c8822ab6af840f8c3e52fe9a71c43f90672728e"
> web3.eth.defaultAccount = a;
"0x5c8822ab6af840f8c3e52fe9a71c43f90672728e"
>
> // abi and bytecode generated from simplestorage.sol:
undefined
> // > solcjs --bin --abi simplestorage.sol
undefined
> var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"payable":false,"type":"constructor"}];
undefined
>
> var simpleContract = web3.eth.contract(abi);
undefined
> var simple = simpleContract.at("0xc48ba0d7ea03ab25a5f264845b848d847d391fc4")
undefined
接着我们调用合约:
> simple.get()
0
结果符合预期,node2应该看不到node1和node3的私有合约
接下来以同样的方式打开node3的console
\>geth attach /opt/dev/quorum/testnet/data/node3/dd/geth.ipc
zmm: cfgPath is PRIVATE_CONFIG
Welcome to the Geth JavaScript console!
instance: Geth/node3-istanbul/v1.8.18-stable-f681cbf3(quorum-v2.2.3)/linux-amd64/go1.12.1
coinbase: 0xf0fa966e6efa633080d1603e7daea5176de87d82
at block: 754 (Sat, 27 Apr 2019 16:27:48 CST)
datadir: /opt/dev/quorum/testnet/data/node3/dd
modules: admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> a = eth.accounts[0]
:false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type"0x915779c113cffecac6583310d80a8def09546272"
> web3.eth.defaultAccount = a;
"0x915779c113cffecac6583310d80a8def09546272"
>
> // abi and bytecode generated from simplestorage.sol:
undefined
> // > solcjs --bin --abi simplestorage.sol
undefined
> var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"payable":false,"type":"constructor"}];
undefined
>
> var simpleContract = web3.eth.contract(abi);
undefined
> var simple = simpleContract.at("0xc48ba0d7ea03ab25a5f264845b848d847d391fc4")
undefined
> simple.get()
42
>
可以看到结果符合预期,node3读出了私有合约的值42。
至此我们已经成功搭建了基于Quorum的使用IBFT共识的支持私有合约的区块链网络,下一篇文章将讲述在搭建后进行动态增加节点以及增加IBFT投票validator的操作。