Steps to set up the EKO network and dynamically manage network authorities:¶
- Use the validator set contracts available at https://github.com/parity-contracts/kovan-validator-set
- We just need these contracts:
- OwnedSet.sol
- BaseOwnedSet.sol
- ValidatorSet.sol
- Owned.sol
- Setup the basic chain genesis saved under genesis.json with all required fields for Authority Round consensus engine
{
"name": "DemoPoA",
"engine": {
"authorityRound": {
"params": {
"stepDuration": "1",
"validators" : {
"list": []
}
}
}
},
"params": {
"gasLimitBoundDivisor": "0x400",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2323",
"eip155Transition": 0,
"validateChainIdTransition": 0,
"eip140Transition": 0,
"eip211Transition": 0,
"eip214Transition": 0,
"eip658Transition": 0
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"gasLimit": "0x5B8D80"
},
"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 } } } }
}
}
- Setup a node by creating a node config file saved under node.toml
[parity]
chain = "genesis.json"
base_path = "./eko_node_data"
[network]
port = 30300
[rpc]
cors = ["*"]
interface ="0.0.0.0"
port = 8545
apis = ["web3", "eth", "net", "personal", "parity", "eko_set", "traces", "rpc", "eko_accounts"]
[ui]
interface = "0.0.0.0"
port = 8181
[websockets]
port = 8451
- Start the node using
./eko --config node.toml --nat none - Attach the geth to the exposed RPC port of the node using
geth attach http://localhost:8545. - Create a new account using
personal.newAccount()and an account password. - Create a file with the node passwords saved as node.pwds and add the password ( assuming the password was “eko” ) as a list
> eko
- Update the node.toml as follows ( assuming the generated address address is
0x00f3b949bb87ae90574c22f986c34207157b66b2)
[parity]
chain = "genesis.json"
base_path = "./eko_node_data"
[network]
port = 30300
[rpc]
cors = ["*"]
interface ="0.0.0.0"
port = 8545
apis = ["web3", "eth", "net", "personal", "parity", "eko_set", "traces", "rpc", "eko_accounts"]
[ui]
interface = "0.0.0.0"
port = 8181
[websockets]
port = 8451
[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x00f3b949bb87ae90574c22f986c34207157b66b2"
reseal_on_txs = "none"
- Restart the node using
./eko --config node.toml --nat none - Update the validator section of the genesis.json as follows
"validators": {
"safeContract": "0x0000000000000000000000000000000000000005"
}
Such that the final genesis file becomes
{
"name": "EKOPoA",
"engine": {
"authorityRound": {
"params": {
"gasLimitBoundDivisor": "0x400",
"stepDuration": "1",
"validators": {
"safeContract": "0x0000000000000000000000000000000000000005"
}
}
}
},
"params": {
"gasLimitBoundDivisor": "0x400",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID": "0x2323",
"eip155Transition": 0,
"validateChainIdTransition": 0,
"eip140Transition": 0,
"eip211Transition": 0,
"eip214Transition": 0,
"eip658Transition": 0
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"gasLimit": "0x56691B7"
},
"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 } } } }
}
}
The address specified in the safeContract address will be the deployed address of the validator set contract.
- Refer to the validator set contracts at https://github.com/parity-contracts/kovan-validator-set
git clone https://github.com/parity-contracts/kovan-validator-set.git
cd kovan-validator-set
remixd -s contracts/ --remix-ide "https://remix.ethereum.org"
Go to http://remix.ethereum.org/
- Open the localhost connection from the top left corner
- Select OwnedSet.sol from the list
- Update the OwnedSet contract constructor as follows
constructor(address[] _initial, address _owner) BaseOwnedSet(_initial)
public
{
owner = _owner;
systemAddress = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE;
}
- Update the Owned.sol contract owner variable declaration as follows
address public owner;
- Update the BaseOwnedSet.sol as follows
- Declare
stakeAmountto store the current stake amount for the authorities in the network andvalidatorStaketo store the stakes for each authority mapped to the address asuint public stakeAmount; mapping(address => uint) public validatorStake;
- Define a function
setStakeAmountto provide the functionality to update the stake amountfunction setStakeAmount(uint _stakeAmount) external onlyOwner { stakeAmount = _stakeAmount; }
- Declare events
event ValidatorAdded(address indexed validatorAddress, uint stake); event ValidatorRemoved(address indexed validatorAddress, uint stake); event CorruptValidatorRemoved(address indexed validatorAddress, uint stake);
- Update the
addValidatorfunction as followsfunction addValidator(address _validator) external onlyOwner isNotValidator(_validator) payable { require(msg.value == stakeAmount); status[_validator].isIn = true; status[_validator].index = pending.length; pending.push(_validator); validatorStake[_validator] = stakeAmount; triggerChange(); emit ValidatorAdded(_validator, stakeAmount); }
- Define a function
removeCorruptValidatorto add the functionality to remove a corrupt validator from the network and transfer the locked stake amount to the admin. The final function should be as follows,function removeCorruptValidator(address _validator) external onlyOwner isValidator(_validator) { // Remove validator from pending by moving the // last element to its slot require(validatorStake[_validator] > 0); uint index = status[_validator].index; uint _stakeAmount = validatorStake[_validator]; pending[index] = pending[pending.length - 1]; status[pending[index]].index = index; delete pending[pending.length - 1]; pending.length--; msg.sender.transfer(_stakeAmount); validatorStake[_validator] = 0; // Reset address status delete status[_validator]; triggerChange(); emit CorruptValidatorRemoved(_validator, _stakeAmount); }
- Update the function
removeValidatorto add the functionality to remove a corrupt validator from the network and transfer the locked stake amount to the admin. The final function should be as follows,function removeValidator(address _validator) external onlyOwner isValidator(_validator) { require(validatorStake[_validator] > 0); // Remove validator from pending by moving the // last element to its slot uint index = status[_validator].index; uint _stakeAmount = validatorStake[_validator]; pending[index] = pending[pending.length - 1]; status[pending[index]].index = index; delete pending[pending.length - 1]; pending.length--; msg.sender.transfer(_stakeAmount); validatorStake[_validator] = 0; // Reset address status delete status[_validator]; triggerChange(); emit ValidatorRemoved(_validator, _stakeAmount); }
- Select OwnedSet contract from the list of contracts to deploy.
- In the arguments section,
_initialwould contain the list of initial validators. Here you need to place the array of addresses of your validator accounts. We will use[“0x00f3b949bb87ae90574c22f986c34207157b66b2”]as we had assigned earlier to our 1st node config file._ownershould contain the address of the owner address(for this example we will be using0x00f3b949bb87ae90574c22f986c34207157b66b2) to which you would like to give authority to manage authorities in the network. Also, set the stake amount for all the new authorities in the network. For now we will be setting is to be equal to 200.
- Copy the bytecode of contract along with the encoded values of input fields by clicking the briefcase button 💼.
- Update the account section for the genesis.json as follows,
"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 } } } },
"<owner_address_holding_premined_ethers>": {"balance": "<provide_initial_premined_ether_balance_here>"},
"0x0000000000000000000000000000000000000005": {"balance": "<provide_initial_balance_here>", "constructor": "<paste_byte_code_here>"}
}
- Restart the Eko nodes with the keys of the
0x00f3b949bb87ae90574c22f986c34207157b66b2account. - Connect the metamask to the exposed RPC port of the nodes.
- Import the account
0x00f3b949bb87ae90574c22f986c34207157b66b2using its private key to the metamask accounts list. - Select Injected Web3 as the preferred environment in remix solidity browser.
- Access and interact with the validator set contracts using
At Addressfunctionality of remix solidity browser, the contracts are predeployed at0x0000000000000000000000000000000000000005
- Check the current validator by calling
getValidatorsand the owner by callingownerconstant functions
- To add a new authority in the network, Copy the genesis file and start the second node with the node config saved under node.toml (on 2nd nodes system)
[parity]
chain = "../genesis.json"
base_path = "./eko_node_data1"
[network]
port = 30301
[rpc]
cors = ["*"]
interface ="0.0.0.0"
port = 8541
apis = ["web3", "eth", "net", "personal", "parity", "eko_set", "traces", "rpc", "eko_accounts"]
[ui]
interface = "0.0.0.0"
port = 8181
[websockets]
port = 8451
- Connect the nodes, Here we will simply use curl. Obtain 1st node’s enode:
curl --data '{"jsonrpc":"2.0","method":"parity_enode","params":[],"id":0}' -H "Content-Type: application/json" -X POST localhost:8545
- Add the
resultto node 1 (replace enode://RESULT in the command):
curl --data '{"jsonrpc":"2.0","method":"parity_addReservedPeer","params":["enode://RESULT"],"id":0}' -H "Content-Type: application/json" -X POST localhost:8541
Now the nodes should indicate 1/25 peers in the console, which means they are connected to each other.
- Geth attach to the node’s RPC exposed port using
geth attach http://localhost:8541 - Generate a new account using
personal.newAccount(). Choose a password for the account. - Update the node config file for the second node by assigning the generated account value ( assuming the generated account is
0x8c7cfb7f40b7a6c4d34c7619c6075d0402112811) to the engine_signer such that the node config file looks like,
[parity]
chain = "../genesis.json"
base_path = "./eko_node_data1"
[network]
port = 30301
[rpc]
cors = ["*"]
interface ="0.0.0.0"
port = 8541
apis = ["web3", "eth", "net", "personal", "parity", "eko_set", "traces", "rpc", "eko_accounts"]
[ui]
interface = "0.0.0.0"
port = 8181
[websockets]
port = 8451
[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x8c7cfb7f40b7a6c4d34c7619c6075d0402112811"
reseal_on_txs = "none"
- Restart the second node
- Sign in with the owner account using the metamask and select the
Injected Web3from the environment dropdown in remix solidity browser. - Now, access the OwnedSet.sol contract again as before using the predeployed contract address and perform the transaction
addValidatorwith address parameter0x8c7cfb7f40b7a6c4d34c7619c6075d0402112811to add the second node’s owner address as a new authority.
Note: the stake amount as msg.value needs to be supplied with this transaction
- The node logs should look like,
- We can check the stakes for the validator using the constant function
validatorStakeas,
- Now, if we call
getValidatorwe should get
This gives a confirmation that the new authority has been added to the network.
- To remove an authority the owner should perform the transaction
removeValidatorwith address parameter0x8c7cfb7f40b7a6c4d34c7619c6075d0402112811. The stakes will be transferred to the authority’s address from the contract and the validator will be removed from the network.
The node logs should look like,
Now, if we call getValidator we should get
This gives a confirmation that the mentioned authority has been removed from the network.
When we check the stake balance now, we should get
- To remove a corrupt authority the owner should perform the transaction
removeCorruptValidatorwith the address parameter0x8c7cfb7f40b7a6c4d34c7619c6075d0402112811. In this case, the stake will not be transferred to the authority’s address but these will be transferred to the owner’s address and the validator will be removed from the network.