E2C is a protocol outlined in a paper pending submission that is based on best-case optimality. It is an evolution of the Sync HotStuff protocol. The protocol sacrifices a small performance loss in the case of faulty nodes in order to outperform in the best-case. E2C requires O(1) signatures and O(n) verifications in the best-case as opposed to Sync HotStuff which requires O(n) signatures and O(n2) verifications.
This implementation is used to test its performance in a real system. We leverage the go-quorum so that we can use all features they have implemented. Our results show that E2C is capable of more transactions/second than the included IBFT.
Nodes should be generated by following the first two steps of the guide by Consensys for the IBFT protocol. When following this guide, it is important that you generate member nodes
and not just validator nodes
. member nodes
(also referred to as client nodes) are nodes that do not participate in consensus and only follow the validator nodes
. All Web3
scripts should only attach to member nodes
, as they are guaranteed to have a correct state. A validator node
is not guaranteed to have a correct state, since the node you attach to could be faulty and thus have an incorrect state. Next you must generate a genesis file. An example genesis.json file for an E2C network is as follows:
{
"config": {
"chainId": 10,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"e2c": {
"delta": 200,
"blockSize": 200
},
"txnSizeLimit": 64,
"maxCodeSize": 0,
"qip714Block": 0,
"isMPS": false,
"isQuorum": true
},
"nonce": "0x0",
"timestamp": "0x60e89145",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f899f85494030e71f5bedd0fe4582d1eac9b7e8743b9134e5f943a4390b9590ec4fe25877a2bd8472d66a9e9036c944ea3600670d3e931ad6f34fc91954d181fb91bd7945f324f4842656d640bc620288f4a644d3a12e3a4b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0xe0000000",
"difficulty": "0x1",
"mixHash": "0xd5d31c273f79321e73e40d6b891b153544c63beabb86b1c020578e7e998461f2",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"030e71f5bedd0fe4582d1eac9b7e8743b9134e5f": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
Before using this genesis file, we must make a few changes. First, you will want to change the extra data
field to one that will match the addresses you created by following the Consensys guide. To do this, we must first generate the correct extra data field by copying the address of all the nodes in the validator set and paste them into the array in this go program, which is included in testnet/encode/encode.go
:
package main
import (
"bytes"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
atypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
func Encode(vanity string, validators []common.Address) (string, error) {
newVanity, err := hexutil.Decode(vanity)
if err != nil {
return "", err
}
if len(newVanity) < atypes.E2CExtraVanity {
newVanity = append(newVanity, bytes.Repeat([]byte{0x00}, atypes.E2CExtraVanity-len(newVanity))...)
}
newVanity = newVanity[:atypes.E2CExtraVanity]
ist := &atypes.E2CExtra{
Validators: validators,
Seal: make([]byte, atypes.E2CExtraSeal),
}
payload, err := rlp.EncodeToBytes(&ist)
if err != nil {
return "", err
}
return "0x" + common.Bytes2Hex(append(newVanity, payload...)), nil
}
func main() {
addr := []common.Address{
common.HexToAddress("0396ea1512b97c9f7e90f641a46f48967db064ba"),
common.HexToAddress("17145655faf1fcbbd0473502c504f32ca9ebe144"),
common.HexToAddress("1a80a2887c640c886606e34d9cfc48637a5b4ceb"),
common.HexToAddress("200a7b1d5f2de512c05b0c46eb50e4d2f2922ada")
// Repeat for as many addresses as you have in validator set
}
s, _ := Encode("0x00", addr)
fmt.Println("Extra Data: " + s)
E2CDigest := common.HexToHash(crypto.Keccak256Hash([]byte("E2C practical byzantine fault tolerance")).String())
fmt.Println("Mix Hash: " + E2CDigest.String())
}
An example output of this script is:
Extra Data: 0x0000000000000000000000000000000000000000000000000000000000000000f899f854940396ea1512b97c9f7e90f641a46f48967db064ba9417145655faf1fcbbd0473502c504f32ca9ebe144941a80a2887c640c886606e34d9cfc48637a5b4ceb94200a7b1d5f2de512c05b0c46eb50e4d2f2922adab8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000`
Mix Hash: 0xd5d31c273f79321e73e40d6b891b153544c63beabb86b1c020578e7e998461f2
For the field labeled extra data
, replace the value with the extra data value given from the script.
You can create client accounts by using geth as you would normally. Next, you will likely want to prefund those created accounts. This is done in the genesis file by the alloc
field here:
"alloc": {
"030e71f5bedd0fe4582d1eac9b7e8743b9134e5f": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"address 2": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
// Repeat for all address you want to prefund
},
Finally, you may want to change some settings for the E2C engine. There are two settings available: delta
and blockSize
. delta
is the upper bound on network speed in milliseconds. The E2C engine assumes that it never takes longer than delta
for a message to be delivered. If delta
is too small, the E2C engine will constantly be changing the leader due to nodes believing a leader is faulting. If delta
is too large, the latency will increase. It is likely that you will want to try different values to see what works best, but 200 milliseconds is the default. blockSize
determines how many transactions will be included in each block. An example configuration is given below:
"e2c": {
"delta": 200,
"blockSize": 200
},
To compile the program, you will need to navigate to cmd/geth
and run go install
. We also include a script build.sh
to do the compilation as well. There will be a warning when the program is compiled. This is expected and can be ignored.
To run a validator node, you run it as you would normally run any go-quorum node. It was run with PRIVATE_CONFIG=ignore geth --datadir $DIR --verbosity 4 --nodiscover --syncmode full --mine --networkid 10 --port 30300 --ws --ws.addr 'localhost' --ws.port 8500 --ws.api admin,eth,miner,net,txpool,personal,web3 --allow-insecure-unlock
for our testing.
To run a client node, you use the same command, but without the --mine
tag.
Sending transactions or performing any other Web3 operation is done via Web3 or via Geth Attach.
We provide the networks we used for testing in the testnet
directory. We have networks with 4, 8, 16, and 32 nodes already setup that we used for testing. Included is a test script, run_test.sh
, that will start the network, send numerous transactions, and output the transactions per second. To run a 4 node test, ./run_test.sh 4 "n4" "n4/e2c.json"
. To run an 8, 16, or 32 node test, use the same command but replace all 4's with whatever number of nodes you are running with.