Simple Consensus is implemented and covered with tests
This commit is contained in:
parent
06859fd2b5
commit
5a1eccfbb9
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
build
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
var Migrations = artifacts.require("./Migrations.sol");
|
||||
|
||||
module.exports = function(deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
// See <http://truffleframework.com/docs/advanced/configuration>
|
||||
// to customize your Truffle configuration!
|
||||
};
|
|
@ -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
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue