diff --git a/README.md b/README.md index a0782d8d50f977dea99a3e00b38ff3752e809d24..e3ccd343d0390618587ec358ee6b45e2db5a6a35 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,14 @@ request: **geth** Connects you to your running local geth. -## create.contract +## compile.and.deploy.contract requires: **geth**, **solc**, **jq** Compiles and deploys a contract to the blockchain. A local "node1" must already be running and synchronized. Argument 1 is the filename of the contract to compile. -Example: `create.contract src/TimestampAuthority.sol` +Example: `compile.and.deploy.contract src/TimestampAuthority.sol` ## tsa-insert.sh requires: **geth** diff --git a/bin/create.contract b/bin/compile.and.deploy.contract similarity index 100% rename from bin/create.contract rename to bin/compile.and.deploy.contract diff --git a/src/Sealers.sol b/src/Sealers.sol index 47ae489fe24a48ddaea4b76ad6316d1e142cac45..0d5391880c70287c2a6e8447c4413c79e1afbc72 100644 --- a/src/Sealers.sol +++ b/src/Sealers.sol @@ -1,89 +1,190 @@ -pragma solidity ^0.4.22; +// vim:syntax:filetype=javascript:ai:sm + +pragma solidity ^0.4.24; // This contract is supposed to maintain the list of authorized sealers. // For this to work, a sealer must deploy the contract and then all sealers -// must occasionally (every 5 minutes) run the related SealerSync.js +// must, say, every 5 minutes, run the related SealerSync.js contract Sealers { // This struct contains a list of votes and who has voted for each victim/beneficiary. // Votes taking longer than 30K blocks, will have to restart. struct Proposal { - address victim; // Whom are we voting about. - uint256 votestart; // In which block did this vote begin? - bool promotion; // true=promotion, false=demotion - address[] voters; // List of voters. + address victim; // Whom are we voting about. + uint votestart; // In which block did this vote begin? + bool promotion; // true=promotion, false=demotion + address[] voters; // List of voters. } address[] public sealers; - Proposal[] public sealerproposals; + Proposal[] public sealerproposals; - event vote( address voter, address victim, bool promotionOrDemotion ); + event voteCast( address voter, address victim, bool promotionOrDemotion ); event adminChange( address admin, bool promotionOrDemotion ); - constructor() public { - sealers[0] = msg.sender; + constructor() public + { + sealers.push( msg.sender ); } - // Public utility, works on any list needle and haystack given. - // - // Returns an index to the position of the needle inside haystack. - // 0 is first position. -1 means not found. - function findAddressInList( address[] haystack, address needle ) public pure returns (int256) { - uint256 i = haystack.length; - while ( i-- > 0 ) - if ( needle == haystack[i] ) - return int256(i & 0x7fffffffffffffffffffffffffffffff); - return -1; - } - function sealerIdx( address needle ) public view returns (int256) { - return findAddressInList( sealers, needle ); - } - // This function is to be used to know how many sealers are in the sealers list. - function sealerLength() public view returns (uint256) { + // This function is used to know how many sealers are in the sealers list. + function sealerLength() public view returns (uint) + { return sealers.length; } + // This function is to be used to get the address from a specific position in the sealers list. // Remember first position is 0. // Last position is sealers.length-1. - function sealerPosition( uint256 idx ) public view returns (address) { + function sealerPosition( uint idx ) public view returns (address) + { require( idx < sealers.length, "There are not that many sealers registered in the list." ); return sealers[idx]; } - function isSealer( address subject ) public view returns (bool) { - return ( sealerIdx(subject) > -1 ); + + function isSealer( address subject ) public view returns (bool) + { + return ( _findAddressInList( sealers, subject ) > 0 ); } - function requireSealer( address subject ) public view { + + function requireSealer( address subject ) public view + { require( isSealer(subject), "Not sealer" ); } - function _remove_entry( uint256 idx, list alist ) private returns (list) { - uint256 pos = idx; + // Returns an index to the position of the needle inside haystack. + // Beware that: + // 0 = not found. + // 1 = first position. + function _findAddressInList( address[] haystack, address needle ) private pure returns (uint) + { + uint i = haystack.length; + while ( i-- > 0 ) + if ( needle == haystack[i] ) + return i+1; + return 0; + } + + function _remove_proposal( uint idx ) private + { // Move all items in the list (after idx) one step closer to the // front of the list. - while ( ++pos < list.length ) - alist[pos-1] = alist[pos]; + uint max = sealerproposals.length; + while ( ++idx < max ) + sealerproposals[idx-1] = sealerproposals[idx]; // "pop" the end of the list, making the list shorter. - alist.length--; - return alist; + sealerproposals.length--; + } + + function _del_votes( address victim ) private + { + uint i_max = sealerproposals.length; + uint i = i_max; + // check all proposals + while ( i-- > 0 ) + { + // check all voters for every proposal + uint j_max = sealerproposals[i].voters.length; + uint j = j_max; + while ( j-- > 0 ) + if ( sealerproposals[i].voters[j] == victim ) + { + // Found the victim as voter, but since he is about + // to be deleted, we will remove him from the list. + uint k = j; + while ( ++k < j_max ) + sealerproposals[i].voters[k-1] = sealerproposals[i].voters[k]; + sealerproposals[i].voters.length--; + j_max--; + if ( sealerproposals[i].voters.length == 0 ) + { + _remove_proposal( i ); + i_max--; + } + } + } + } + + function _remove_sealer( address victim ) private + { + // Remove votes that the victim has already cast. + _del_votes( victim ); + // Move all items in the list (after match) one step closer to the + // front of the list. + uint max = sealers.length; + uint i = max; + while ( i-- > 0 ) + if ( sealers[i] == victim ) + { + // We could have recycled 'i' here, but for clarity, we don't. + uint j = i; + while ( ++j < max ) + sealers[j-1] = sealers[j]; + // "pop" the end of the list, making the list shorter. + sealers.length--; + return; + } } // This function sees if any proposals have overstayed their welcome and thus // needs to be removed. - function _trimProposals() private { - for ( uint256 i = sealerproposals.length-1; i>=0; i-- ) + function _trimProposals() private + { + uint i = sealerproposals.length; + while ( i-- > 0 ) { // If a proposal is more than 30K blocks old, then remove it from the list. if ( sealerproposals[i].votestart + 30000 <= block.number ) - sealerproposals = _remove_entry( i, sealerproposals ); + _remove_proposal( i ); } } - function promotionIdx( address victim, bool promotion ) public view return (int256) { - uint256 idx = sealerproposals.length; - while ( idx-- > 0) - if ( sealerproposals[idx].victim == victim && sealerproposals[idx].promotion == promotion ) - return idx; - return -1; + // We run through the entire list of proposals, checking if they fulfill the + // requirements. Why the whole list? Because if a sealer is removed, whom has + // not yet voted for a proposal, that proposal may now have achieved majority. + function _promotedemote() private + { + uint prevlength = 0; + // Keep looping over the list until the number of proposals stops changing. + while ( prevlength != sealerproposals.length ) + { + uint i = sealerproposals.length; + prevlength = i; + uint majority = sealers.length / 2 + 1; + // Loop over all proposals + while ( i-- > 0 ) + { + // If we have enough votes to perform the actual promotion/demotion + if ( sealerproposals[i].voters.length >= majority ) + { + // Is it a promotion or a demotion? + if ( sealerproposals[i].promotion ) + // Add victim to sealer list + sealers.push( sealerproposals[i].victim ); + else + // Remove victim from sealer list + _remove_sealer( sealerproposals[i].victim ); + + // Send notification + emit adminChange( sealerproposals[i].victim, + sealerproposals[i].promotion ); + // Remove the proposal because the voting is complete. + _remove_proposal( i ); + } + } + } + } + // Returns an index to the position of the proposal inside matching the [victim,promotion] tuple + // Beware that: + // 0 = not found. + // 1 = first position. + function promotionIdx( address victim, bool promotion ) public view returns (uint) + { + uint i = sealerproposals.length; + while ( i-- > 0) + if ( sealerproposals[i].victim == victim && sealerproposals[i].promotion == promotion ) + return i+1; + return 0; } // You can call this for free and know if your promote call @@ -104,9 +205,9 @@ contract Sealers { if ( !isSealer(victim) && !promotion ) return false; - // See if the voter is already in the list of voters for this [victim,promotion] tupple - int256 proppos = promotionIdx( victim, promotion ); - if ( proppos > -1 && findAddressInList( sealerproposals[proppos].voters, voter ) > -1 ) + // See if the voter is already in the list of voters for this [victim,promotion] tuple + uint proppos = promotionIdx( victim, promotion ); + if ( proppos > 0 && _findAddressInList( sealerproposals[proppos-1].voters, voter ) > 0 ) return false; return true; } @@ -116,48 +217,33 @@ contract Sealers { // As per usual, this requires n/2+1 votes. // The boolean must be true if you want to add a sealer // and false if you want to remove one. - function promote( address victim, bool promotion ) public { + function vote( address victim, bool promotion ) public + { if ( ! mayVote(msg.sender, victim, promotion)) - revert("That seems redundant."); + revert("That seems redundant or is otherwise not allowed."); _trimProposals(); // Send notification of the vote - emit vote( msg.sender, victim, promotion ); + emit voteCast( msg.sender, victim, promotion ); - // signed as opposed to unsigned (int) - int256 signedretval = promotionIdx( victim, promotion ); - uint proppos; - if ( signedretval == -1 ) + uint proppos = promotionIdx( victim, promotion ); + if ( proppos == 0 ) { // This is a new proposal, so we will add it, so we can vote on it. address[] memory emptyAddressList; proppos = sealerproposals.push( Proposal(victim, block.number, promotion, emptyAddressList ) ); } - else - proppos = uint256( signedretval & 0x7fffffffffffffffffffffffffffffff ); - - // Make things a bit more easy to read by using a shorter variable name - Proposal memory prop = sealerproposals[proppos]; + proppos--; // Add our vote - prop.voters.push( msg.sender ); - - // Stop here if we do not have enough votes to perform the actual promotion/demotion - if ( prop.voters.length < sealers.length/2+1 ) - return; + sealerproposals[proppos].voters.push( msg.sender ); - // Remove the proposal because the voting is complete. - sealerproposals = _remove_entry( proppos, sealerproposals ); + // See if we're ready to promote/demote anyone, based on the proposals. + _promotedemote(); - // Is it a promotion or a demotion? - if ( promotion ) - // Add victim to sealer list - sealers.push( victim ); - else - // Remove victim from sealer list - sealers = _remove_entry( sealerIdx(victim), sealers ); - - // Send notification - emit adminChange( victim, promotion ); + // If we have no more sealers, we have no reason to live. + if ( sealers.length == 0 ) + selfdestruct( msg.sender ); } } +