Skip to content

Commit

Permalink
Streamlined IBFT contract to optimise persisted data. Modified tests …
Browse files Browse the repository at this point in the history
…to reflect changes.
  • Loading branch information
Shirikatsu committed Feb 12, 2019
1 parent b582602 commit bee238c
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 65 deletions.
89 changes: 33 additions & 56 deletions contracts/validation/IBFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,9 @@ contract IBFT is IonCompatible {
uint256 blockNumber;
bytes32 blockHash;
bytes32 prevBlockHash;
bytes32 txRootHash;
bytes32 receiptRootHash;
}

struct Metadata {
address[] validators;
mapping (address => bool) m_validators;
uint256 threshold;
}
}

event GenesisCreated(bytes32 chainId, bytes32 blockHash);
event BlockSubmitted(bytes32 chainId, bytes32 blockHash);
Expand All @@ -50,10 +44,8 @@ contract IBFT is IonCompatible {
}

mapping (bytes32 => bool) public chains;
mapping (bytes32 => mapping (bytes32 => bool)) public m_blockhashes;
mapping (bytes32 => bytes32) public m_chainHeads;
mapping (bytes32 => mapping (bytes32 => BlockHeader)) public m_blockheaders;
mapping (bytes32 => mapping (bytes32 => Metadata)) public m_blockmetadata;
mapping (bytes32 => bytes32[]) public heads;

constructor (address _ionAddr) IonCompatible(_ionAddr) public {}

Expand Down Expand Up @@ -83,7 +75,7 @@ contract IBFT is IonCompatible {
require(_chainId != ion.chainId(), "Cannot add this chain id to chain register");

if (chains[_chainId]) {
require(!m_blockhashes[_chainId][_genesisBlockHash], "Chain already exists with identical genesis");
require(m_chainHeads[_chainId] == bytes32(0x0), "Chain already exists");
} else {
chains[_chainId] = true;
ion.addChain(_storeAddr, _chainId);
Expand All @@ -107,19 +99,17 @@ contract IBFT is IonCompatible {

// Check the parent hash is the same as the previous block submitted
bytes32 parentBlockHash = SolUtils.BytesToBytes32(header[0].toBytes(), 1);
require(m_blockhashes[_chainId][parentBlockHash], "Not child of previous block!");
require(m_chainHeads[_chainId] == parentBlockHash, "Not child of previous block!");

// Verify that validator and sealers are correct
require(checkSignature(_chainId, header[12].toData(), keccak256(_rlpUnsignedBlockHeader), parentBlockHash), "Signer is not validator");
require(checkSeals(_chainId, _commitSeals, _rlpSignedBlockHeader, parentBlockHash), "Sealer(s) not valid");

// Append new block to the struct
addValidators(_chainId, header[12].toData(), keccak256(_rlpSignedBlockHeader), parentBlockHash);
storeBlock(_chainId, keccak256(_rlpSignedBlockHeader), parentBlockHash, SolUtils.BytesToBytes32(header[4].toBytes(), 1), SolUtils.BytesToBytes32(header[5].toBytes(), 1), header[8].toUint(), _rlpSignedBlockHeader, _storageAddr);
addValidators(_chainId, header[12].toData(), keccak256(_rlpSignedBlockHeader));
storeBlock(_chainId, keccak256(_rlpSignedBlockHeader), parentBlockHash, header[8].toUint(), _rlpSignedBlockHeader, _storageAddr);

emit BlockSubmitted(_chainId, keccak256(_rlpSignedBlockHeader));


}


Expand All @@ -142,18 +132,10 @@ contract IBFT is IonCompatible {
BlockHeader storage header = m_blockheaders[_chainId][_genesisBlockHash];
header.blockNumber = 0;
header.blockHash = _genesisBlockHash;
header.validators = _validators;
header.threshold = 2*(_validators.length/3) + 1;

Metadata storage metadata = m_blockmetadata[_chainId][_genesisBlockHash];
metadata.validators = _validators;

// Append validators and vote threshold
for (uint256 i = 0; i < _validators.length; i++) {
metadata.m_validators[_validators[i]] = true;
}
metadata.threshold = 2*(_validators.length/3) + 1;

m_blockhashes[_chainId][_genesisBlockHash] = true;

m_chainHeads[_chainId] = _genesisBlockHash;
emit GenesisCreated(_chainId, _genesisBlockHash);
}

Expand All @@ -168,7 +150,7 @@ contract IBFT is IonCompatible {
* parent block
*/
function checkSignature(bytes32 _chainId, bytes _extraData, bytes32 _hash, bytes32 _parentBlockHash) internal view returns (bool) {
// Retrieve Istanbul Extra
// Retrieve Istanbul Extra Data
bytes memory istanbulExtra = new bytes(_extraData.length - 32);
SolUtils.BytesToBytes(istanbulExtra, _extraData, 32);

Expand All @@ -179,10 +161,10 @@ contract IBFT is IonCompatible {

// Recover the signature
address sigAddr = ECVerify.ecrecovery(keccak256(_hash), extraDataSig);
Metadata storage parentMetadata = m_blockmetadata[_chainId][_parentBlockHash];
BlockHeader storage parentBlock = m_blockheaders[_chainId][_parentBlockHash];

// Check if signature is a validator that exists in previous block
return parentMetadata.m_validators[sigAddr];
return isValidator(parentBlock.validators, sigAddr);
}

/*
Expand All @@ -196,28 +178,33 @@ contract IBFT is IonCompatible {
*/
function checkSeals(bytes32 _chainId, bytes _seals, bytes _rlpBlock, bytes32 _parentBlockHash) internal view returns (bool) {
bytes32 signedHash = keccak256(abi.encodePacked(keccak256(_rlpBlock), 0x02));
Metadata storage parentMetadata = m_blockmetadata[_chainId][_parentBlockHash];
BlockHeader storage parentBlock = m_blockheaders[_chainId][_parentBlockHash];
uint256 validSeals = 0;

// Check if signature is a validator that exists in previous block
RLP.RLPItem[] memory seals = _seals.toRLPItem().toList();
for (uint i = 0; i < seals.length; i++) {

// Recover the signature
address sigAddr = ECVerify.ecrecovery(signedHash, seals[i].toData());
if (!parentMetadata.m_validators[sigAddr]) {
if (!isValidator(parentBlock.validators, sigAddr))
return false;
}
validSeals++;
}

if (validSeals < parentMetadata.threshold) {
if (validSeals < parentBlock.threshold)
return false;
}

return true;
}

function isValidator(address[] _validators, address _validator) internal pure returns (bool) {
for (uint i = 0; i < _validators.length; i++) {
if (_validator == _validators[i])
return true;
}
return false;
}

/*
* addValidators
* param: _chainId (bytes32) Unique id of interoperating chain
Expand All @@ -227,13 +214,10 @@ contract IBFT is IonCompatible {
*
* Updates the validators from the RLP encoded extradata
*/
function addValidators(bytes32 _chainId, bytes _extraData, bytes32 _blockHash, bytes32 _parentBlockHash) internal {
// Metadata storage parentMetadata = m_blockmetadata[_chainId][_parentBlockHash];
Metadata storage metadata = m_blockmetadata[_chainId][_blockHash];

address[] storage newValidators = metadata.validators;
function addValidators(bytes32 _chainId, bytes _extraData, bytes32 _blockHash) internal {
BlockHeader storage newBlock = m_blockheaders[_chainId][_blockHash];

// Retrieve Istanbul Extra
// Retrieve Istanbul Extra Data
bytes memory rlpIstanbulExtra = new bytes(_extraData.length - 32);
SolUtils.BytesToBytes(rlpIstanbulExtra, _extraData, 32);

Expand All @@ -242,22 +226,17 @@ contract IBFT is IonCompatible {

for (uint i = 0; i < decodedExtra.length; i++) {
address validator = decodedExtra[i].toAddress();
newValidators.push(validator);
metadata.m_validators[validator] = true;
newBlock.validators.push(validator);
}

metadata.validators = newValidators;
metadata.threshold = 2*(newValidators.length/3) + 1;

newBlock.threshold = 2*(newBlock.validators.length/3) + 1;
}

/*
* storeBlock
* param: _chainId (bytes32) Unique id of interoperating chain
* param: _hash (address) Byte array of the extra data containing signature
* param: _parentHash (bytes32) Current block hash being checked
* param: _txRootHash (bytes32) Parent block hash of current block being checked
* param: _receiptRootHash (bytes32) Parent block hash of current block being checked
* param: _height (bytes32) Parent block hash of current block being checked
* param: _rlpBlockHeader (bytes32) Parent block hash of current block being checked
* param: _storageAddr (bytes32) Parent block hash of current block being checked
Expand All @@ -268,27 +247,25 @@ contract IBFT is IonCompatible {
bytes32 _chainId,
bytes32 _hash,
bytes32 _parentHash,
bytes32 _txRootHash,
bytes32 _receiptRootHash,
uint256 _height,
bytes _rlpBlockHeader,
address _storageAddr
) internal {
m_blockhashes[_chainId][_hash] = true;
m_chainHeads[_chainId] = _hash;

BlockHeader storage header = m_blockheaders[_chainId][_hash];
header.blockNumber = _height;
header.blockHash = _hash;
header.prevBlockHash = _parentHash;
header.txRootHash = _txRootHash;
header.receiptRootHash = _receiptRootHash;

delete m_blockheaders[_chainId][_parentHash];

// Add block to Ion
ion.storeBlock(_storageAddr, _chainId, _rlpBlockHeader);
}

function getValidators(bytes32 _chainId, bytes32 _blockHash) public view returns (address[]) {
return m_blockmetadata[_chainId][_blockHash].validators;
function getValidators(bytes32 _chainId) public view returns (address[]) {
return m_blockheaders[_chainId][m_chainHeads[_chainId]].validators;
}

}
27 changes: 18 additions & 9 deletions test/ibft.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,18 @@ contract('Ibft.js', (accounts) => {
let chainExists = await ibft.chains(TESTCHAINID);

assert(chainExists);
})

it('Fail Register Chain Twice', async () => {
// Successfully add id of another chain
let tx = await ibft.RegisterChain(TESTCHAINID, VALIDATORS_BEFORE, GENESIS_HASH, storage.address);
console.log("\tGas used to register chain = " + tx.receipt.gasUsed.toString() + " gas");
let chainExists = await ibft.chains(TESTCHAINID);

assert(chainExists);

// Fail adding id of this chain
await ibft.RegisterChain(storage.address, DEPLOYEDCHAINID, VALIDATORS_BEFORE, GENESIS_HASH).should.be.rejected;
await ibft.RegisterChain(DEPLOYEDCHAINID, VALIDATORS_BEFORE, GENESIS_HASH, storage.address).should.be.rejected;

// Fail adding id of chain already initialised
await ibft.RegisterChain(TESTCHAINID, VALIDATORS_BEFORE, GENESIS_HASH, storage.address).should.be.rejected;
Expand All @@ -183,7 +192,7 @@ contract('Ibft.js', (accounts) => {
// Successfully add id of another chain
await ibft.RegisterChain(TESTCHAINID, VALIDATORS_BEFORE, GENESIS_HASH, storage.address);

let registeredValidators = await ibft.getValidators.call(TESTCHAINID, GENESIS_HASH);
let registeredValidators = await ibft.getValidators.call(TESTCHAINID);

for (let i = 0; i < VALIDATORS_BEFORE.length; i++) {
let validatorExists = registeredValidators.some(v => { return v == VALIDATORS_BEFORE[i] });;
Expand Down Expand Up @@ -218,8 +227,8 @@ contract('Ibft.js', (accounts) => {
const submittedEvent = validationReceipt.logs.find(l => { return l.event == 'BlockSubmitted' });
assert.equal(Web3Utils.sha3(rlpHeader.signed), submittedEvent.args.blockHash);

let blockHashExists = await ibft.m_blockhashes(TESTCHAINID, block.hash);
assert(blockHashExists);
let addedBlockHash = await ibft.m_chainHeads.call(TESTCHAINID);
assert.equal(addedBlockHash, block.hash);

let header = await ibft.m_blockheaders(TESTCHAINID, block.hash);

Expand Down Expand Up @@ -250,8 +259,8 @@ contract('Ibft.js', (accounts) => {
const submittedEvent = validationReceipt.logs.find(l => { return l.event == 'BlockSubmitted' });
assert.equal(Web3Utils.sha3(rlpHeader.signed), submittedEvent.args.blockHash);

let blockHashExists = await ibft.m_blockhashes(TESTCHAINID, block_add.hash);
assert(blockHashExists);
let addedBlockHash = await ibft.m_chainHeads.call(TESTCHAINID);
assert.equal(addedBlockHash, block_add.hash);

let header = await ibft.m_blockheaders(TESTCHAINID, block_add.hash);

Expand All @@ -262,14 +271,14 @@ contract('Ibft.js', (accounts) => {
assert.equal(parentHash, block_add.parentHash);

// Check new validators
let registeredValidators = await ibft.getValidators.call(TESTCHAINID, block_add.hash);
let registeredValidators = await ibft.getValidators.call(TESTCHAINID);
for (let i = 0; i < VALIDATORS_AFTER.length; i++) {
let validatorExists = registeredValidators.some(v => { return v == VALIDATORS_AFTER[i] });;
assert(validatorExists);
}
})

it('Submit Block Unknown Validator', async () => {
it('Fail Submit Block with Unknown Validator', async () => {
await ibft.RegisterChain(TESTCHAINID, VALIDATORS_BEFORE, GENESIS_HASH, storage.address);

block.extraData = "0xdc83010000886175746f6e69747988676f312e31302e34856c696e7578000000f90164f854941cb62855cd70774634c85c9acb7c3070ce692936946b2f468af3d0ba2f3a09712faea4d379c2e891a194a667ea98809a69724c6672018bd7db799cd3fefc94c2054df3acfdbe5b221866b25e09026734ca5572b841012edd2e5936deaf4c0ee17698dc0fda832bb51a81d929ae3156d73e5475123c19d162cf1e434637c16811d63d1d3b587906933d75e25cedf7bef59e8fa8375d01f8c9b841719c5bc521721e71ff7fafff09fdff4037e678a77a816b08d45b89d55f35edc94b5c51cc3eeba79d3de291c3c46fbf04faec4952e7d0836be9ad5d855f525c9301b841a7c9eed0337f92a5d4caf6f57b3b59ba10a14ea615c6264fc82fcf5b2e4b626f701fd3596cd1f8639b37a41cb4f3a7582bb530790441de73e6e3449284127b4d00b841210db6ef89906ef1c77538426d29b8440a1c987d508e396776e63515df2a345767c195dc540cfabdf86d696c73b4a24632445565d322d8e45fa2668ec5e6c0e000";
Expand All @@ -281,7 +290,7 @@ contract('Ibft.js', (accounts) => {

})

it('Submit Block Insufficient Seals', async () => {
it('Fail Submit Block with Insufficient Seals', async () => {
await ibft.RegisterChain(TESTCHAINID, VALIDATORS_BEFORE, GENESIS_HASH, storage.address);

let badExtraData = "0xf90164f854944335d75841d8b85187cf651ed130774143927c799461d7d88dbc76259fcf1f26bc0b5763aebd67aead94955425273ef777d6430d910f9a8b10adbe95fff694f00d3c728929e42000c8d92d1a7e6a666f12e6edb8410c11022a97fcb2248a2d757a845b4804755702125f8b7ec6c06503ae0277ad996dc22f81431e8036b6cf9ef7d3c1ff1b65a255c9cb70dd2f4925951503a6fdbf01f8c9b8412d3849c86c8ba3ed9a79cdd71b1684364c4c4efb1f01e83ca8cf663f3c95f7ac64b711cd297527d42fb3111b8f78d5227182f38ccc442be5ac4dcb52efede89a01b84135de3661d0191247c7f835c8eb6d7939052c0da8ae234baf8bd208c00225e706112df9bad5bf773120ba4bbc55f6d18e478de43712c0cd3de7a3e2bfd65abb7c01b841735f482a051e6ad7fb76a815907e68d903b73eff4e472006e56fdeca8155cb575f4c1d3e98cf3a4b013331c1bd171d0d500243ac0e073a5fd382294c4fe996f000";
Expand Down

0 comments on commit bee238c

Please sign in to comment.