与前文使用Geth搭建Ethereum以太坊私链不同,这次搭建以太坊私链用的是Parity.
CentOS系统可能会出现GLIBCXX版本不匹配的问题,建议使用Ubuntu18.04。
步骤
1. Parity下载
于Parity Github Releases下载Parity,本文使用的是 2.7.2版本。
下载完成后
chmod +x parity
# 查看版本号
parity --version
2. spec配置
创建chain spec demo-spec.json
{
"name": "DemoPoA",
"engine": {
"authorityRound": {
"params": {
"stepDuration": "2",
"validators" : {
"list": []
}
}
}
},
"params": {
"gasLimitBoundDivisor": "0x400",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2323"
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x10000",
"gasLimit": "0x12a05f200"
},
"accounts": {
"0x0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0x0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0x0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0x0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }
}
}
stepDuration 设定成5秒产生一个区块。
validators 设定Authority的地方,目前先空著,后面创建account之后再回来填入。
3. 节点配置
node0使用 node0.toml
[parity]
chain = "demo-spec.json"
base_path = "parity0"
[network]
port = 30300
[rpc]
port = 8540
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8180
[websockets]
port = 8450
node1 使用如下配置文件 node1.toml:
[parity]
chain = "demo-spec.json"
base_path = "parity1"
[network]
port = 30301
[rpc]
port = 8541
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8181
[websockets]
port = 8451
[ipc]
disable = true
4. 账户创建
我们将创建三个帐户:两个权限和一个用户帐户。这里只记录两种:
方法:使用RPC
使用启动节点0 parity --config node0.toml
RPC可以通过访问web3,parity.js或简单地使用curl。
这将创建第一个权限地址:
curl --data '{"jsonrpc":"2.0","method":"parity_newAccountFromPhrase","params":["node0", "node0"],"id":0}' -H "Content-Type: application/json" -X POST localhost:8540
返回的地址应该是0x00bd138abd70e2f00903268f3db08f2d25677c9e。
用户地址:
curl --data '{"jsonrpc":"2.0","method":"parity_newAccountFromPhrase","params":["user", "user"],"id":0}' -H "Content-Type: application/json" -X POST localhost:8540
返回的地址应该是0x004ec07d2329997267ec62b4166639513386f32e。
现在启动另一个节点 parity --config node1.toml
并创建第二个权限帐户:
curl --data '{"jsonrpc":"2.0","method":"parity_newAccountFromPhrase","params":["node1", "node1"],"id":0}' -H "Content-Type: application/json" -X POST localhost:8541
返回的地址应该是0x00aa39d30f0d20ff03a22ccfc30b7efbfca597c2。
现在可以将帐户添加到spec文件中。打开demo-spec.json备份并将我们刚创建的权限添加到"validators"数组中:
"validators" : {
"list": [
"0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e",
"0x00Aa39d30F0D20FF03a22cCfc30B7EfbFca597C2"
]
}
然后将我们的用户帐户添加到spec的accounts
中,以便我们有一些余额可以发送:
"0x004ec07d2329997267ec62b4166639513386f32e": { "balance": "10000000000000000000000" }
5. 运行授权节点
现在规范已经完成,我们可以启动将运行链的两个节点。要将节点作为权限运行,我们需要启用它来签署共识消息。
首先,让我们用节点密码创建一个文件node.pwds。每行将包含我们在创建权限帐户时使用的密码,要存储在名为node.pwds的文件中的内容为:
node0
node1
现在我们可以添加engine-signer到配置文件中。
node0.toml:
[parity]
chain = "demo-spec.json"
base_path = "parity0"
[network]
port = 30300
[rpc]
port = 8540
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8180
[websockets]
port = 8450
[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e"
reseal_on_txs = "none"
并且node1.toml:
[parity]
chain = "demo-spec.json"
base_path = "parity1"
[network]
port = 30301
[rpc]
port = 8541
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8181
[websockets]
port = 8451
[ipc]
disable = true
[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x00Aa39d30F0D20FF03a22cCfc30B7EfbFca597C2"
reseal_on_txs = "none"
重新启动两个节点
6. 添加节点
首选获取节点0信息
- 通过控制台输出
- RPC
curl --data '{"jsonrpc":"2.0","method":"parity_enode","params":[],"id":0}' -H "Content-Type: application/json" -X POST localhost:8540
将“结果”添加到节点1(enode://RESULT在命令中替换):
curl --data '{"jsonrpc":"2.0","method":"parity_addReservedPeer","params":["enode://RESULT"],"id":0}' -H "Content-Type: application/json" -X POST localhost:8541
7. 静态节点
Parity也可实现类似Geth的静态节点。
把节点添加到一个文件,比如 myPrivateNetwork.txt
, 一行一条:
enode://0000..0001@192.168.0.101:36541
enode://0000..0002@192.168.0.102:36542
enode://0000..0003@192.168.0.103:36543
enode://0000..0004@192.168.0.104:36544
enode://0000..0005@192.168.0.105:36545
接下来,运行Parity带上以下参数 --reserved-peers myPrivateNetwork.txt --reserved-only
. 或者把它添加到toml文件
[parity]
chain = "myGenesis.json"
[network]
id = 13337
reserved_only = true
reserved_peers = "./myPrivateNetwork.txt"
7. 交易测试
curl --data '{"jsonrpc":"2.0","method":"personal_sendTransaction","params":[{"from":"0x004ec07d2329997267Ec62b4166639513386F32E","to":"0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e","value":"0xde0b6b3a7640000"}, "user"],"id":0}' -H "Content-Type: application/json" -X POST localhost:8540
过几秒就会被挖出。
8. 一般性节点
现在可以创建更多帐户,发送价值,编写合同并进行部署。用于开发和使用以太坊网络的所有工具也可用于此网络。
要在多台计算机上部署Parity,您可能会发现docker构建很有用。
要添加非授权节点,可以使用以下更简单的配置:
[parity]
chain = "demo-spec.json"
base_path = "/tmp/parity2"
[network]
port = 30302
[rpc]
port = 8542
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8182
[websockets]
port = 8452
[ipc]
disable = true
然后,帐户和连接节点可以与权限节点相同。为了确保接受事务,权限也可以在usd_per_tx = "0"
字段下运行[mining]。任何提交交易的节点都可以免费提供。
9. 动态管理验证节点
使用合约动态管理验证节点,合约代码
pragma solidity ^0.4.21;
contract ValidatorSet {
event InitiateChange(bytes32 indexed _parent_hash, address[] _new_set);
function getValidators() public constant returns (address[] _validators);
function finalizeChange() public;
}
contract MajorityList is ValidatorSet {
event ChangeFinalized(address[] current_set);
struct ValidatorStatus {
bool isValidator;
uint index;
}
address SYSTEM_ADDRESS = 0x0000000000000000000000000000000000000000;
address[] public validatorsList;
address[] pendingList;
bool finalized;
mapping(address => ValidatorStatus) validatorsStatus;
bool private initialized;
function MajorityList() public {
pendingList = [0x00bd138abd70e2f00903268f3db08f2d25677c9e];
initializeValidators();
}
modifier uninitialized() {
if (initialized) { revert();}
_;
}
modifier when_finalized() {
if (!finalized) { revert();}
_;
}
modifier only_system_and_not_finalized() {
if (msg.sender != SYSTEM_ADDRESS || finalized) { revert(); }
_;
}
modifier is_validator(address someone) {
if (validatorsStatus[someone].isValidator) { _; }
}
modifier is_not_validator(address someone) {
if (!validatorsStatus[someone].isValidator) { _; }
}
function initializeValidators() public uninitialized {
for (uint j = 0; j < pendingList.length; j++) {
address validator = pendingList[j];
validatorsStatus[validator] = ValidatorStatus({
isValidator: true,
index: j
});
}
initialized = true;
validatorsList = pendingList;
finalized = false;
}
function initiateChange() private when_finalized {
finalized = false;
emit InitiateChange(block.blockhash(block.number - 1), pendingList);
}
function finalizeChange() public only_system_and_not_finalized {
validatorsList = pendingList;
finalized = true;
emit ChangeFinalized(validatorsList);
}
function addValidator(address validator) public is_not_validator(validator){
validatorsStatus[validator].index = pendingList.length;
pendingList.push(validator);
validatorsStatus[validator].isValidator = true;
initiateChange();
}
function removeValidator(address validator) public is_validator(validator){
uint removedIndex = validatorsStatus[validator].index;
uint lastIndex = pendingList.length-1;
address lastValidator = pendingList[lastIndex];
pendingList[removedIndex] = lastValidator;
validatorsStatus[lastValidator].index = removedIndex;
delete pendingList[lastIndex];
pendingList.length--;
validatorsStatus[validator].index = 0;
validatorsStatus[validator].isValidator = false;
initiateChange();
}
function getValidators() public constant returns (address[]) {
return validatorsList;
}
}
通过Remix编译成功后,查看编译详情,Bytecode中的object即是合约的二进制代码,实际使用时要加上 0x
。
spec修改如下:
"validators": {
"safeContract":"0x0000000000000000000000000000000000000005"
}
accounts处
添加
"0x0000000000000000000000000000000000000005":{
"balance": "1",
"constructor":"0x..."
}
constructor处添加的是合约的二进制代码。
注意点
- 子节点必须使用和主节点相同的 spec.json,不然连不上主节点!
- validators可以动态配置也可静态配置。静态配置最为简单,动态配置还存在一些问题。