Istanbul BFT (IBFT)

Currently, we can choose PoW and PoA consensus in Go Ethereum (Geth), but in those consensus algorithms, either the performance is not that good or forks can still happen, which are major issues for financial usages. Therefore, we implemented Istanbul BFT (IBFT) in Geth, which achieves instant finality, manageable validator set and higher throughput. The details can be found in EIP 650. In this post, we will walk through how to:

  • Run IBFT with 4 validators and 1 monitoring node on the network status.
  • Add/Remove validators dynamically.
  • Run IBFT in Quorum.

Run IBFT geth

Let’s try our istanbul-tools! It will generate nodekeys, genesis.json, static-nodes.json, and then a docker-compose file automatically. This example requires a Go (version 1.7 or later) compiler. You can install them using your favorite package manager.

# install istanbul tool
$
go get github.com/getamis/istanbul-tools/cmd/istanbul
# create a docker-compose file with 4 validators
$ istanbul setup --num 4 --docker-compose --save
# run docker-compose 
$ docker-compose up -d
# open ethstats website (http://${docker_machine}:3000)
$ open http://192.168.99.100:3000

Then, if you can see below website, and new blocks keep generating, it works!

Add/Remove validator

In IBFT, we support RPC API to access IBFT. The details can be found in https://github.com/getamis/go-ethereum/wiki/RPC-API. Below is an example of removing a validator. If the number of voting on removing a particular validator is more than 1/2 out of all validators, the candidate will be removed from the validator set. Therefore, in our demo, we attached to 3 geth and proposed removing a validator:

# attach to validator-0 geth (http://${docker_machine}:8545)
$ geth attach http://192.168.99.100:8545
> istanbul.getValidators()
["0x23971dab0b29c27fa0de9226c45bef04d9f39156", "0x740f27259843279c22a45440ab0cb1c42b9c492f", "0xee9f5b9b89e69f7d3dde461a479fd1bb6ba8e07e", "0xf92d3fe9942a42e4522a578789f9aec6f7a033e1"]
> istanbul.propose("0x23971dab0b29c27fa0de9226c45bef04d9f39156", false)
> exit
# attach to validator-1 geth
$ geth attach http://192.168.99.100:8546
> istanbul.propose("0x23971dab0b29c27fa0de9226c45bef04d9f39156", false)
> exit
# attach to validator-2 geth
$ geth attach http://192.168.99.100:8547
> istanbul.propose("0x23971dab0b29c27fa0de9226c45bef04d9f39156", false)
> istanbul.getValidators()
["0x740f27259843279c22a45440ab0cb1c42b9c492f", "0xee9f5b9b89e69f7d3dde461a479fd1bb6ba8e07e", "0xf92d3fe9942a42e4522a578789f9aec6f7a033e1"]

If we want to add a validator, we can do similar procedure, and propose adding a validator like below.

istanbul.propose("0x23971dab0b29c27fa0de9226c45bef04d9f39156", true)

Quorum

IBFT has already been merged in Quorum! Let’s try IBFT in quorum. We can use Istanbul-tool with quorum flag.

# create a docker-compose file with 4 validators
$ istanbul setup --num 4 --docker-compose --quorum --save
# run docker-compose
$ docker-compose up -d
# list all constellation public keys
$ ls tm*
tm0.pub tm1.pub tm2.pub tm3.pub

Private transactions in Quorum

We can get all validators’ constellation public keys after running docker compose. To send private transactions, simply use the recipient’s constellation public key in privateFor field while running send transaction commands. Below is an example of creating a private contract between validator-0 and validator-3:

# get validator-3’s public key
$ cat tm3.pub
GLEUU2vMkZNBcE6tn3cbME75ppK8r74ty1ZjHQswIn0=
# attach to validator-0 geth
$ build/bin/geth attach http://192.168.99.100:8545
# create an account to send transaction
> personal.newAccount()
# unlock this account
> personal.unlockAccount(eth.accounts[0], "", 300)
# create a private contract for validator-3
> 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);
# put validator3’s public key into privateFor
> var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["GLEUU2vMkZNBcE6tn3cbME75ppK8r74ty1ZjHQswIn0="]}, 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 mined! Address: 0x938c3fe6d57254da05d669a66cd986c711913baf
# get the value in the private contract
> var address = "0x938c3fe6d57254da05d669a66cd986c711913baf"
> 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"}],"type":"constructor"}];
> var private = eth.contract(abi).at(address)
> private.get()
42

After that you can try to get the value from both validator-1 (not the privateFor recipient) and validator-3 (the privateFor recipient), you will get 0 and 42, respectively.

validator-1:

# attach to validator-1 geth
$ build/bin/geth attach http://192.168.99.100:8546
# get the value in the private contract, but fail.
> var address = "0x938c3fe6d57254da05d669a66cd986c711913baf"
> 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"}],"type":"constructor"}];
> var private = eth.contract(abi).at(address)
> private.get()
0

validator-3:

# attach to validator-3 geth
$ build/bin/geth attach http://192.168.99.100:8548
# get the value in the private contract, but fail.
> var address = "0x938c3fe6d57254da05d669a66cd986c711913baf"
> 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"}],"type":"constructor"}];
> var private = eth.contract(abi).at(address)
> private.get()
42

References

  1. https://github.com/ethereum/EIPs/issues/650
  2. https://github.com/getamis/go-ethereum
  3. https://github.com/getamis/istanbul-tools
  4. https://github.com/jpmorganchase/quorum
Like what you read? Give Zih-Ci Lin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.