Quorum在ethereum的基础上把PoW共识机制替换成了QuorumChain(以及raft,不过本篇不涉及raft共识),其共识方式以及搭建私链的方法也就和ethereum有很大差别了。
QuorumChain简介
QuorumChain是一个基于时间的多数表决算法:
- 用智能合约去管理共识以及能参与共识的人
- 用以太坊事务通过网络传递“投票(
votes
)” - 用以太坊签名验证去验证从
Maker
以及Voter
节点发送来的签名
Quorum网络中的节点能被赋予Voter
权限,该权限可以Vote
给区块去决定哪个块是链上的下一个块。收到投票最多的最近的块会被上链。
区块只允许拥有Maker
权限的节点来创建。一个节点创建出区块并且签名,然后将签名存放在块的ExtraData
中。在区块上链的时候,作为区块头验证的一部分,节点会验证区块的签名者地址是否是一个有效的Maker
。
私链构成
- Block Maker节点:负责创建区块
- Voter节点:负责投票
- Observer节点:无特别职责,只接收以及验证区块
genesis.json
区块链总是从创世节点开始的,这就需要一个genesis.json文件。Quorum的genesis.json在ethereum的genesis.json基础上加上了很多自定义信息。
{
"alloc": {
"0x0000000000000000000000000000000000000020": {
"code": "606060405236156100e35763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416631290948581146100e5578063284d163c146100fa57806342169e4814610118578063488099a61461013a5780634fe437d51461016a578063559c390c1461018c57806368bb8bb6146101b157806372a571fc146101c957806386c1ff68146101e757806398ba676d14610205578063a7771ee31461022d578063adfaa72e1461025d578063cf5289851461028d578063de8fa431146102af578063e814d1c7146102d1578063f4ab9adf14610301575bfe5b34156100ed57fe5b6100f860043561031f565b005b341561010257fe5b6100f8600160a060020a0360043516610355565b005b341561012057fe5b610128610418565b60408051918252519081900360200190f35b341561014257fe5b610156600160a060020a036004351661041e565b604080519115158252519081900360200190f35b341561017257fe5b610128610433565b60408051918252519081900360200190f35b341561019457fe5b610128600435610439565b60408051918252519081900360200190f35b34156101b957fe5b6100f860043560243561053f565b005b34156101d157fe5b6100f8600160a060020a036004351661064f565b005b34156101ef57fe5b6100f8600160a060020a0360043516610707565b005b341561020d57fe5b6101286004356024356107ca565b60408051918252519081900360200190f35b341561023557fe5b610156600160a060020a036004351661081f565b604080519115158252519081900360200190f35b341561026557fe5b610156600160a060020a0360043516610841565b604080519115158252519081900360200190f35b341561029557fe5b610128610856565b60408051918252519081900360200190f35b34156102b757fe5b61012861085c565b60408051918252519081900360200190f35b34156102d957fe5b610156600160a060020a0360043516610863565b604080519115158252519081900360200190f35b341561030957fe5b6100f8600160a060020a0360043516610885565b005b600160a060020a03331660009081526003602052604090205460ff161561034b5760018190555b610351565b60006000fd5b5b50565b600160a060020a03331660009081526005602052604090205460ff161561034b57600454600114156103875760006000fd5b600160a060020a03811660009081526005602052604090205460ff161561034657600160a060020a038116600081815260056020908152604091829020805460ff1916905560048054600019019055815192835290517f8cee3054364d6799f1c8962580ad61273d9d38ca1ff26516bd1ad23c099a60229281900390910190a15b5b610351565b60006000fd5b5b50565b60025481565b60056020526000908152604090205460ff1681565b60015481565b600060006000600060006001860381548110151561045357fe5b906000526020600020906002020160005b509250600090505b60018301548110156105335760018301805484916000918490811061048d57fe5b906000526020600020900160005b505481526020808201929092526040908101600090812054858252928690522054108015610502575060015483600001600085600101848154811015156104de57fe5b906000526020600020900160005b5054815260208101919091526040016000205410155b1561052a576001830180548290811061051757fe5b906000526020600020900160005b505491505b5b60010161046c565b8193505b505050919050565b600160a060020a03331660009081526003602052604081205460ff161561034b57600054839010156105805760008054808503019061057e908261093d565b505b60008054600019850190811061059257fe5b906000526020600020906002020160005b5060008381526020829052604090205490915015156105e6578060010180548060010182816105d2919061096f565b916000526020600020900160005b50839055505b600082815260208281526040918290208054600101905581514381529081018490528151600160a060020a033316927f3d03ba7f4b5227cdb385f2610906e5bcee147171603ec40005b30915ad20e258928290030190a25b610649565b60006000fd5b5b505050565b600160a060020a03331660009081526005602052604090205460ff161561034b57600160a060020a03811660009081526005602052604090205460ff16151561034657600160a060020a038116600081815260056020908152604091829020805460ff19166001908117909155600480549091019055815192835290517f1a4ce6942f7aa91856332e618fc90159f13a340611a308f5d7327ba0707e56859281900390910190a15b5b610351565b60006000fd5b5b50565b600160a060020a03331660009081526003602052604090205460ff161561034b57600254600114156107395760006000fd5b600160a060020a03811660009081526003602052604090205460ff161561034657600160a060020a038116600081815260036020908152604091829020805460ff1916905560028054600019019055815192835290517f183393fc5cffbfc7d03d623966b85f76b9430f42d3aada2ac3f3deabc78899e89281900390910190a15b5b610351565b60006000fd5b5b50565b600060006000600185038154811015156107e057fe5b906000526020600020906002020160005b509050806001018381548110151561080557fe5b906000526020600020900160005b505491505b5092915050565b600160a060020a03811660009081526003602052604090205460ff165b919050565b60036020526000908152604090205460ff1681565b60045481565b6000545b90565b600160a060020a03811660009081526005602052604090205460ff165b919050565b600160a060020a03331660009081526003602052604090205460ff161561034b57600160a060020a03811660009081526003602052604090205460ff16151561034657600160a060020a038116600081815260036020908152604091829020805460ff19166001908117909155600280549091019055815192835290517f0ad2eca75347acd5160276fe4b5dad46987e4ff4af9e574195e3e9bc15d7e0ff9281900390910190a15b5b610351565b60006000fd5b5b50565b815481835581811511610649576002028160020283600052602060002091820191016106499190610999565b5b505050565b815481835581811511610649576000838152602090206106499181019083016109c6565b5b505050565b61086091905b808211156109bf5760006109b660018301826109e7565b5060020161099f565b5090565b90565b61086091905b808211156109bf57600081556001016109cc565b5090565b90565b508054600082559060005260206000209081019061035191906109c6565b5b505600a165627a7a72305820216e6fcc048c1a646961a7229a6bdb83e7f57881831fe5f6b5d460cd63b39a1e0029",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x02",
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x04",
"0x938506c58fdd0a60c2fdd4eaf9df138401f9c7adc4a568ad0b79f3f0d9578d92": "0x01",
"0x86b5b4e8e5ebbd5e26b7c2c653fa66dc2fc1c5f9a68fc119a5540e7db9b525d3": "0x01",
"0x4faa4c520d1ecfc9d44f6d4965a6669e4837b27542b7a215dfac121c8f55881a": "0x01",
"0x77a85380fcb135101de0456d520642341df891279860d6a928d5cdb3151eb866": "0x01",
"0x1df2cecb8a3f5afae0dee78976fa2f5286703fbea23d090c10f571c4b75dee77": "0x01",
"0x0000000000000000000000000000000000000000000000000000000000000004": "0x02",
"0xaca3b76ed4968740c3180dd7fa37f4aa229a2c758a848f53920e9ccb4c4bb74e": "0x01",
"0xd188ba2dc293670542c1befaf7678b0859e5354a0727d1188b2afb6f47fe24d1": "0x01"
}
},
"0x9526680c11c2701d5d0176f0d853576929c476ad": {
"balance": "1000000000000000000000000000"
},
"0x8a06392b5b522a34a9ff817bb2c517f1d64a7fa7": {
"balance": "1000000000000000000000000000"
},
"0x31913ae2926808eff6b2162937e2f4388ab555d4": {
"balance": "1000000000000000000000000000"
},
"0x871538639bc387a67f8550a0fb1715672d5fe87e": {
"balance": "1000000000000000000000000000"
},
"0x27d114ebfbc88116a98c416f2e687757d91308be": {
"balance": "1000000000000000000000000000"
}
},
"coinbase": "0x0000000000000000000000000000000000000000",
"config": {
"homesteadBlock": 0
},
"difficulty": "0x0",
"extraData": "0x",
"gasLimit": "0x2FEFD800",
"mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
"nonce": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00"
}
"0x0000000000000000000000000000000000000020"
为投票合约的地址,该地址是硬编码到代码中的,code
的值为block_voting.sol文件编译后的字节码。storage
里存放的是和投票合约相关的配置项目。
Voter
账户和Block Maker
账户必须在genesis.json
预先定义好,以上面的为例,配置分别是:
"0x938506c58fdd0a60c2fdd4eaf9df138401f9c7adc4a568ad0b79f3f0d9578d92": "0x01",
"0x86b5b4e8e5ebbd5e26b7c2c653fa66dc2fc1c5f9a68fc119a5540e7db9b525d3": "0x01",
"0x4faa4c520d1ecfc9d44f6d4965a6669e4837b27542b7a215dfac121c8f55881a": "0x01",
"0x77a85380fcb135101de0456d520642341df891279860d6a928d5cdb3151eb866": "0x01",
"0x1df2cecb8a3f5afae0dee78976fa2f5286703fbea23d090c10f571c4b75dee77": "0x01",
与
"0x9526680c11c2701d5d0176f0d853576929c476ad": {
"balance": "1000000000000000000000000000"
},
"0x8a06392b5b522a34a9ff817bb2c517f1d64a7fa7": {
"balance": "1000000000000000000000000000"
},
"0x31913ae2926808eff6b2162937e2f4388ab555d4": {
"balance": "1000000000000000000000000000"
},
"0x871538639bc387a67f8550a0fb1715672d5fe87e": {
"balance": "1000000000000000000000000000"
},
"0x27d114ebfbc88116a98c416f2e687757d91308be": {
"balance": "1000000000000000000000000000"
}
例子中的0x938506c58fdd0a60c2fdd4eaf9df138401f9c7adc4a568ad0b79f3f0d9578d92
是账号地址0x9526680c11c2701d5d0176f0d853576929c476ad
通过计算后获得的stroge key
,后面对应的0x01
是true
的意思,计算方法是:web3.sha3(<256 bit aligned key value> + <256 bit variable index>)
。Voter
对应的variable index
是3
,Block Maker
对应的variable index
是5
。
以0x9526680c11c2701d5d0176f0d853576929c476ad
为例,设置为Voter
:
启动web3:geth --datadir $PWD --dev console 2>>dev.log
,执行代码:
function lp64(str) {
var len = str.length
var plen = 64 - len
var pstr = ""
if (plen > 0) {
for (var i = 0; i < plen; i++) {
pstr += "0"
}
return pstr + str
}
return str
}
vidx = lp64("3")
addr = "9526680c11c2701d5d0176f0d853576929c476ad"
web3.sha3(lp64(addr) + vidx, {"encoding": "hex"})
如果是设置为Block Maker
,则只需要将vidx = lp64("3")
改成vidx = lp64("5")
即可。
私链部署示例
本示例以官方的 7nodes example 为样本修改而成。部署一个拥有2个Maker
节点、2个Voter
节点、2个Observer
节点的网络。
创建workspace:
mkdir workspace
cd workspace
创建账号以及genesis.json:
用命令geth --datadir $PWD account new
创建5个账号,然后进入keysotre
目录,讲账号的key分别命名为maker1
、maker2
、voter1
、voter2
、voter3
,然后根据上述genesis.json
部分生成对应的权限key以及genesis.json。
使用下面的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 as block maker"
mkdir -p qdata/dd1/keystore
cp keysotre/maker1 qdata/dd1/keystore
geth --datadir qdata/dd1 init genesis.json
echo "[*] Configuring node 2 as block maker and voter"
mkdir -p qdata/dd2/keystore
cp keysotre/maker2 qdata/dd2/keystore
cp keysotre/voter1 qdata/dd2/keystore
geth --datadir qdata/dd2 init genesis.json
echo "[*] Configuring node 3"
mkdir -p qdata/dd3/keystore
geth --datadir qdata/dd3 init genesis.json
echo "[*] Configuring node 4 as voter"
mkdir -p qdata/dd4/keystore
cp keysotre/voter2 qdata/dd4/keystore
geth --datadir qdata/dd4 init genesis.json
echo "[*] Configuring node 5 as voter"
mkdir -p qdata/dd5/keystore
cp keysotre/voter3 qdata/dd5/keystore
geth --datadir qdata/dd5 init genesis.json
echo "[*] Configuring node 6"
mkdir -p qdata/dd6/keystore
geth --datadir qdata/dd6 init genesis.json
echo "[*] Configuring node 7"
mkdir -p qdata/dd7/keystore
geth --datadir qdata/dd7 init genesis.json
生成bootnode:
mkdir boot
cd boot
bootnode -genkey node.key
回到workspace目录,生成7个节点对应的constellation-node的key:
mkdir keys
cd keys
使用下面的ruby脚本生成key:
(1..7).each do |i|
`constellation-node --generatekeys=tm#{i}`
`constellation-node --generatekeys=tm#{i}a`
end
在workspace目录下,根据7nodes example的tm1.conf
、tm2.conf
等配置,创建自己的tm1~7等7个配置文件。
参考下面的start.sh
:
#!/bin/bash
set -u
set -e
NETID=419231
BOOTNODE_ENODE=enode://1b9002c04a4a41ceb491dcc1d803f4a4cb864684c077204003f0cf5d69819f5c8e6993c1c4c8c57f34358fb7b12c71f6f6c27709428953cd9ba918b9a9c9dc1e@127.0.0.1:40301
GLOBAL_ARGS="--bootnodes $BOOTNODE_ENODE --networkid $NETID --rpc --rpcaddr 0.0.0.0 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum"
echo "[*] Starting Constellation nodes"
nohup constellation-node tm1.conf 2>> qdata/logs/constellation1.log &
sleep 1
nohup constellation-node tm2.conf 2>> qdata/logs/constellation2.log &
nohup constellation-node tm3.conf 2>> qdata/logs/constellation3.log &
nohup constellation-node tm4.conf 2>> qdata/logs/constellation4.log &
nohup constellation-node tm5.conf 2>> qdata/logs/constellation5.log &
nohup constellation-node tm6.conf 2>> qdata/logs/constellation6.log &
nohup constellation-node tm7.conf 2>> qdata/logs/constellation7.log &
echo "[*] Starting bootnode"
nohup bootnode -nodekey boot/node.key --addr="127.0.0.1:40301" 2>>qdata/logs/bootnode.log &
echo "wait for bootnode to start..."
sleep 6
echo "[*] Starting node 1"
PRIVATE_CONFIG=tm1.conf nohup geth --datadir qdata/dd1 $GLOBAL_ARGS --rpcport 42000 --port 41000 --blockmakeraccount "0x871538639bc387a67f8550a0fb1715672d5fe87e" --blockmakerpassword "" --minblocktime 2 --maxblocktime 5 2>>qdata/logs/1.log &
PRIVATE_CONFIG=tm1.conf nohup geth --datadir qdata/dd1 $GLOBAL_ARGS --rpcport 42000 --port 41000 --unlock 0 --password passwords.txt 2>>qdata/logs/1.log &
echo "[*] Starting node 2"
PRIVATE_CONFIG=tm2.conf nohup geth --datadir qdata/dd2 $GLOBAL_ARGS --rpcport 42001 --port 41001 --voteaccount "0x9526680c11c2701d5d0176f0d853576929c476ad" --votepassword "" --blockmakeraccount "0x27d114ebfbc88116a98c416f2e687757d91308be" --blockmakerpassword "" --minblocktime 2 --maxblocktime 5 2>>qdata/logs/2.log &
PRIVATE_CONFIG=tm2.conf nohup geth --datadir qdata/dd2 $GLOBAL_ARGS --rpcport 42001 --port 41001 --voteaccount "0x9526680c11c2701d5d0176f0d853576929c476ad" --votepassword "" --blockmakeraccount "0x27d114ebfbc88116a98c416f2e687757d91308be" --blockmakerpassword "" --singleblockmaker --minblocktime 2 --maxblocktime 5 2>>qdata/logs/2.log &
echo "[*] Starting node 3"
PRIVATE_CONFIG=tm3.conf nohup geth --datadir qdata/dd3 $GLOBAL_ARGS --rpcport 42002 --port 41002 2>>qdata/logs/3.log &
echo "[*] Starting node 4"
PRIVATE_CONFIG=tm4.conf nohup geth --datadir qdata/dd4 $GLOBAL_ARGS --rpcport 42003 --port 41003 --voteaccount "0x8a06392b5b522a34a9ff817bb2c517f1d64a7fa7" --votepassword "" 2>>qdata/logs/4.log &
echo "[*] Starting node 5"
PRIVATE_CONFIG=tm5.conf nohup geth --datadir qdata/dd5 $GLOBAL_ARGS --rpcport 42004 --port 41004 --voteaccount "0x31913ae2926808eff6b2162937e2f4388ab555d4" --votepassword "" 2>>qdata/logs/5.log &
echo "[*] Starting node 6"
PRIVATE_CONFIG=tm6.conf nohup geth --datadir qdata/dd6 $GLOBAL_ARGS --rpcport 42005 --port 41005 2>>qdata/logs/6.log &
echo "[*] Starting node 7"
PRIVATE_CONFIG=tm7.conf nohup geth --datadir qdata/dd7 $GLOBAL_ARGS --rpcport 42006 --port 41006 2>>qdata/logs/7.log &
echo "[*] Waiting for nodes to start"
sleep 10
echo "[*] Sending first transaction"
PRIVATE_CONFIG=tm1.conf geth --exec 'loadScript("script1.js")' attach ipc:qdata/dd1/geth.ipc
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"
将其中的BOOTNODE_ENODE
、端口号
、账号地址
改成自己生成的相应数据。脚本中用于测试的script1.js
,可以用下面的hello world 测试脚本
。
hello world 测试脚本:
var greeterSource = 'contract mortal { address owner; function mortal() { owner = msg.sender; } function kill() { if (msg.sender == owner) selfdestruct(owner); } } contract greeter is mortal { string greeting; function greeter(string _greeting) public { greeting = _greeting; } function greet() constant returns (string) { return greeting; } }'
var greeterCompiled = web3.eth.compile.solidity(greeterSource)
var greeterRoot = Object.keys(greeterCompiled)[0];
var _greeting = "Hello World!"
var greeterContract = web3.eth.contract(greeterCompiled[greeterRoot].info.abiDefinition);
var greeter = greeterContract.new(_greeting,{from:web3.eth.accounts[0], data: greeterCompiled[greeterRoot].code, gas: 300000}, function(e, contract){
if(!e) {
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);
}
}
})
参考文档
- 7nodes example
- Using-Quorum
- QuorumChain-Consensus
- constellation
- quorum running
- Issue: How is the Storage map populated in the genesis.json?
- Issue: Blockmaker isn't allowed to create blocks