Simple Consensus is implemented and covered with tests

This commit is contained in:
Roman Storm 2017-11-27 22:49:24 -08:00
parent 06859fd2b5
commit 5a1eccfbb9
11 changed files with 4034 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
build

24
Makefile Normal file
View File

@ -0,0 +1,24 @@
.PHONY: merge testrpc test solhint travis_test
merge:
node_modules/.bin/sol-merger "src/*.sol" build
testrpc:
node_modules/.bin/testrpc -p 8544 \
--account="0x7e9a1de56cce758c544ba5dea3a6347a4a01c453d81edc32c2385e9767f29505, 1000000000000000000000000000" \
--account="0xf2029a2f20a9f57cd1a9a2a44c63d0c875f906c646f333b028cb6f1c38ef7db5, 1000000000000000000000000000" \
--account="0x84f24b0dddc8262675927168bbbf8688f846bcaedc2618ae576d34c043401719, 1000000000000000000000000000" \
--account="0x1fdc76364db4a4bcfad8f2c010995a96fcb98a165e34858665a234ba5471520b, 1000000000000000000000000000" \
--account="0x1fdc76364db4a4bcfad8f2c010995a96fcb98a165e34858665a234ba5471520c, 1000000000000000000000000000" \
--account="0x1fdc76364db4a4bcfad8f2c010995a96fcb98a165e34858665a234ba54715123, 1000000000000000000000000000" \
--account="0x1fdc76364db4a4bcfad8f2c010995a96fcb98a165e34858665a234ba54715104, 1000000000000000000000000000" \
test:
node_modules/.bin/truffle test
solhint:
solhint contracts/*.sol contracts/util/*.sol
travis_test:
nohup make testrpc &
make test

23
contracts/Migrations.sol Normal file
View File

@ -0,0 +1,23 @@
pragma solidity ^0.4.17;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
function Migrations() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

View File

@ -0,0 +1,130 @@
contract SimpleConsensus {
/// Issue this log event to signal a desired change in validator set.
/// This will not lead to a change in active validator set until
/// finalizeChange is called.
///
/// Only the last log event of any block can take effect.
/// If a signal is issued while another is being finalized it may never
/// take effect.
///
/// _parent_hash here should be the parent block hash, or the
/// signal will not be recognized.
struct ValidatorState {
// Is this a validator.
bool isValidator;
// Index in the currentValidators.
uint256 index;
}
bool public finalized = false;
address public SYSTEM_ADDRESS = 0xfffffffffffffffffffffffffffffffffffffffe;
address[] public currentValidators;
address[] public pendingList;
address public keysManager;
address public ballotsManager;
uint256 public currentValidatorsLength;
mapping(address => ValidatorState) public validatorsState;
modifier only_system_and_not_finalized() {
require(msg.sender == SYSTEM_ADDRESS && !finalized);
_;
}
modifier only_ballots_manager_or_keys_manager() {
require(msg.sender == ballotsManager || msg.sender == keysManager);
_;
}
modifier only_ballots_manager() {
require(msg.sender == ballotsManager);
_;
}
modifier is_validator(address _someone) {
require(validatorsState[_someone].isValidator);
_;
}
modifier is_not_validator(address _someone) {
require(!validatorsState[_someone].isValidator);
_;
}
event InitiateChange(bytes32 indexed _parent_hash, address[] _new_set);
event ChangeFinalized(address[] _new_set);
function SimpleConsensus() {
// TODO: When you deploy this contract, make sure you hardcode items below
// Make sure you have those addresses defined in spec.json
currentValidators = [0x0039F22efB07A647557C7C5d17854CFD6D489eF3];
for(uint256 i = 0; i < currentValidators.length; i++){
validatorsState[currentValidators[i]] = ValidatorState({
isValidator: true,
index: i
});
}
currentValidatorsLength = currentValidators.length;
pendingList = currentValidators;
keysManager = 0x0039F22efB07A647557C7C5d17854CFD6D489eF3;
ballotsManager = 0x0039F22efB07A647557C7C5d17854CFD6D489eF3;
}
/// Get current validator set (last enacted or initial if no changes ever made)
function getValidators() public view returns (address[]) {
return currentValidators;
}
/// Called when an initiated change reaches finality and is activated.
/// Only valid when msg.sender == SUPER_USER (EIP96, 2**160 - 2)
///
/// Also called when the contract is first enabled for consensus. In this case,
/// the "change" finalized is the activation of the initial set.
function finalizeChange() public only_system_and_not_finalized {
finalized = true;
currentValidators = pendingList;
currentValidatorsLength = currentValidators.length;
ChangeFinalized(getValidators());
}
function addValidator(address _validator) public only_ballots_manager_or_keys_manager is_not_validator(_validator) {
require(_validator != address(0));
pendingList = currentValidators;
pendingList.push(_validator);
validatorsState[_validator] = ValidatorState({
isValidator: true,
index: currentValidators.length
});
finalized = false;
InitiateChange(block.blockhash(block.number - 1), pendingList);
}
function removeValidator(address _validator) public only_ballots_manager_or_keys_manager is_validator(_validator) {
uint removedIndex = validatorsState[_validator].index;
// Can not remove the last validator.
uint lastIndex = pendingList.length-1;
address lastValidator = pendingList[lastIndex];
// Override the removed validator with the last one.
pendingList[removedIndex] = lastValidator;
// Update the index of the last validator.
validatorsState[lastValidator].index = removedIndex;
delete pendingList[lastIndex];
pendingList.length--;
validatorsState[_validator].index = 0;
validatorsState[_validator].isValidator = false;
finalized = false;
InitiateChange(block.blockhash(block.number - 1), pendingList);
}
function setKeysManager(address _newAddress) public only_ballots_manager {
require(_newAddress != ballotsManager);
keysManager = _newAddress;
}
function setBallotsManager(address _newAddress) public only_ballots_manager {
ballotsManager = _newAddress;
}
}

View File

@ -0,0 +1,5 @@
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

3598
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "oracles-poa-network-contracts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chai-bignumber": "^2.0.2",
"ethereumjs-testrpc": "^6.0.3",
"truffle": "^4.0.1"
}
}

View File

@ -0,0 +1,17 @@
import '../contracts/SimpleConsensus.sol';
contract SimpleConsensusMock is SimpleConsensus {
//For testing
// address public SYSTEM_ADDRESS = 0xfffffffffffffffffffffffffffffffffffffffe;
function setSystemAddress(address _newAddress) public {
SYSTEM_ADDRESS = _newAddress;
}
function setKeysManager(address _newAddress) public {
keysManager = _newAddress;
}
function setBallotsManager(address _newAddress) public {
ballotsManager = _newAddress;
}
}

View File

@ -0,0 +1,201 @@
let SimpleConsensus = artifacts.require('./SimpleConsensusMock');
const ERROR_MSG = 'VM Exception while processing transaction: revert';
require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(web3.BigNumber))
.should();
contract('SimpleConsensus [all features]', function (accounts) {
let simpleConsensus;
beforeEach(async () => {
simpleConsensus = await SimpleConsensus.new();
});
describe('default values', async () => {
it('finalized should be false', async () => {
let validators = await simpleConsensus.getValidators();
let finalized = await simpleConsensus.finalized();
finalized.should.be.false;
});
it('checks SYSTEM_ADDRESS', async () => {
let SYSTEM_ADDRESS = await simpleConsensus.SYSTEM_ADDRESS();
SYSTEM_ADDRESS.should.be.equal('0xfffffffffffffffffffffffffffffffffffffffe');
})
})
describe('#finalizeChange', async () => {
it('should only be called by SYSTEM_ADDRESS', async () => {
await simpleConsensus.finalizeChange().should.be.rejectedWith(ERROR_MSG);
await simpleConsensus.setSystemAddress(accounts[0]);
await simpleConsensus.finalizeChange().should.be.fulfilled;
})
it('should set finalized to true', async () => {
let finalized = await simpleConsensus.finalized();
finalized.should.be.false;
await simpleConsensus.setSystemAddress(accounts[0]);
await simpleConsensus.finalizeChange().should.be.fulfilled;
finalized = await simpleConsensus.finalized();
finalized.should.be.true;
})
it('should set currentValidators to pendingList', async () => {
await simpleConsensus.setSystemAddress(accounts[0]);
const { logs } = await simpleConsensus.finalizeChange().should.be.fulfilled;
let currentValidatorsLength = await simpleConsensus.currentValidatorsLength();
let currentValidators = [];
let pendingList = [];
for (let i = 0; i < currentValidatorsLength.toNumber(10); i++) {
let validator = await simpleConsensus.currentValidators(i);
currentValidators.push(validator);
let pending = await simpleConsensus.pendingList(i);
pendingList.push(pending);
}
currentValidators.should.be.deep.equal(pendingList);
const event = logs.find(e => e.event === 'ChangeFinalized')
event.should.exist
})
it('set currentValidators to pendingList after addValidator call', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]);
await simpleConsensus.setSystemAddress(accounts[0]);
const { logs } = await simpleConsensus.finalizeChange().should.be.fulfilled;
let currentValidatorsLength = await simpleConsensus.currentValidatorsLength();
let currentValidators = [];
let pendingList = [];
for (let i = 0; i < currentValidatorsLength.toNumber(10); i++) {
let validator = await simpleConsensus.currentValidators(i);
currentValidators.push(validator);
let pending = await simpleConsensus.pendingList(i);
pendingList.push(pending);
}
currentValidators.should.be.deep.equal(pendingList);
})
})
describe('#addValidator', async () => {
it('should add validator', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
})
it('should be called only either from ballot manager or keys manager', async () => {
await simpleConsensus.addValidator(accounts[1]).should.be.rejectedWith(ERROR_MSG);
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.setBallotsManager(accounts[0]);
await simpleConsensus.addValidator(accounts[2]).should.be.fulfilled;
})
it('should not allow to add already existing validator', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.addValidator(accounts[1]).should.be.rejectedWith(ERROR_MSG);
})
it('should not allow 0x0 addresses', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator('0x0').should.be.rejectedWith(ERROR_MSG);
await simpleConsensus.addValidator('0x0000000000000000000000000000000000000000').should.be.rejectedWith(ERROR_MSG);
})
it('should set validatorsState for new validator', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
let state = await simpleConsensus.validatorsState(accounts[1]);
let currentValidatorsLength = await simpleConsensus.currentValidatorsLength();
state[0].should.be.true;
state[1].should.be.bignumber.equal(currentValidatorsLength)
})
it('should set finalized to false', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
let finalized = await simpleConsensus.finalized();
finalized.should.be.false;
})
it('should emit InitiateChange with blockhash and pendingList as params', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
const {logs} = await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
let currentValidatorsLength = await simpleConsensus.currentValidatorsLength();
let currentValidators = [];
for (let i = 0; i < currentValidatorsLength.toNumber(10); i++) {
let validator = await simpleConsensus.currentValidators(i);
currentValidators.push(validator);
}
currentValidators.push(accounts[1]);
logs[0].args['_new_set'].should.deep.equal(currentValidators);
logs[0].event.should.be.equal('InitiateChange');
})
})
describe('#removeValidator', async () => {
it('should remove validator', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.removeValidator(accounts[1]).should.be.fulfilled;
})
it('should be called only either from ballot manager or keys manager', async () => {
await simpleConsensus.removeValidator(accounts[1]).should.be.rejectedWith(ERROR_MSG);
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.removeValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.setBallotsManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.removeValidator(accounts[1]).should.be.fulfilled;
})
it('should only be allowed to remove from existing set of validators', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.removeValidator(accounts[1]).should.be.rejectedWith(ERROR_MSG);
})
it('should decrease length of pendingList', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.setSystemAddress(accounts[0]);
await simpleConsensus.finalizeChange().should.be.fulfilled;
await simpleConsensus.addValidator(accounts[2]).should.be.fulfilled;
await simpleConsensus.setSystemAddress(accounts[0]);
await simpleConsensus.finalizeChange().should.be.fulfilled;
let currentValidatorsLength = await simpleConsensus.currentValidatorsLength();
let pendingList = [];
for(let i = 0; i < currentValidatorsLength; i++){
let pending = await simpleConsensus.pendingList(i);
pendingList.push(pending);
}
const indexOfRemovedElement = pendingList.indexOf(accounts[1]);
pendingList.splice(indexOfRemovedElement, 1);
const { logs } = await simpleConsensus.removeValidator(accounts[1]).should.be.fulfilled;
let pendingListFromContract = logs[0].args['_new_set'];
pendingListFromContract.length.should.be.equal(currentValidatorsLength.toNumber(10) - 1);
pendingList.should.be.deep.equal(pendingListFromContract);
logs[0].event.should.be.equal('InitiateChange');
})
it('should change validatorsState', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.removeValidator(accounts[1]).should.be.fulfilled;
const state = await simpleConsensus.validatorsState(accounts[1]);
state[0].should.be.false;
state[1].should.be.bignumber.equal(0);
})
it('should set finalized to false', async () => {
await simpleConsensus.setKeysManager(accounts[0]);
await simpleConsensus.addValidator(accounts[1]).should.be.fulfilled;
await simpleConsensus.removeValidator(accounts[1]).should.be.fulfilled;
const finalized = await simpleConsensus.finalized();
finalized.should.be.false;
})
})
});

4
truffle-config.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
};

12
truffle.js Normal file
View File

@ -0,0 +1,12 @@
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
networks: {
test: {
host: "localhost",
port: 8544,
gas: 4600000,
network_id: "*" // Match any network id
}
}
};