prysm-pulse/contracts/sharding-manager-contract/sharding_manager.sol
2018-08-12 15:50:42 -07:00

287 lines
11 KiB
Solidity

pragma solidity ^0.4.23;
contract SMC {
event HeaderAdded(uint indexed shardId, bytes32 chunkRoot, uint period, address proposerAddress);
event AttesterRegistered(address attester, uint poolIndex);
event AttesterDeregistered(address attester, uint poolIndex, uint deregisteredPeriod);
event AttesterReleased(address attester, uint poolIndex);
event VoteSubmitted(uint indexed shardId, bytes32 chunkRoot, uint period, address attesterAddress);
struct Attester {
uint deregisteredPeriod;
uint poolIndex;
uint balance;
bool deposited;
}
struct CollationRecord {
bytes32 chunkRoot; // Root hash of the collation body
address proposer; // Address of the proposer
bool isElected; // True if the collation has reached quorum size
bytes32 signature; // Signature of the collation header after proposer signs
}
// Attester state variables
address[] public attesterPool;
// attesterAddress => attesterStruct
mapping (address => Attester) public attesterRegistry;
// number of attesters
uint public attesterPoolLength;
// current vote count of each shard
// first 31 bytes are bitfield of attester's vote
// last 1 byte is the number of the total votes
mapping (uint => bytes32) public currentVote;
// Collation state variables
// shardId => (period => CollationHeader), collation records been appended by proposer
mapping (uint => mapping (uint => CollationRecord)) public collationRecords;
// shardId => period, last period of the submitted collation header
mapping (uint => uint) public lastSubmittedCollation;
// shardId => period, last period of the approved collation header
mapping (uint => uint) public lastApprovedCollation;
// Internal help functions variables
// Stack of empty attester slot indicies
uint[] emptySlotsStack;
// Top index of the stack
uint emptySlotsStackTop;
// Attester sample size at current period and next period
uint currentPeriodAttesterSampleSize;
uint nextPeriodAttesterSampleSize;
uint sampleSizeLastUpdatedPeriod;
// Number of shards
// TODO: Setting default as 100. This will be a dynamic when we introduce random beacon
uint public shardCount = 100;
// Constant values
// Length of challenge period for attester's proof of custody
uint public constant CHALLENGE_PERIOD = 25;
// Number of blocks per period
uint constant PERIOD_LENGTH = 5;
// The minimum deposit size for a attester
uint constant ATTESTER_DEPOSIT = 1000 ether;
// Time the ether is locked by attesters
uint constant ATTESTER_LOCKUP_LENGTH = 16128;
// Number of attesters to select from attester pool for each shard in each period
uint constant COMMITTEE_SIZE = 135;
// Threshold(number of attesters in committee) for a proposal to be accepted
uint constant QUORUM_SIZE = 90;
// Number of periods ahead of current period, which the contract
// is able to return the attester of that period
uint constant LOOKAHEAD_LENGTH = 4;
/// Checks if a attester with given shard id and period has been chosen as
/// a committee member to vote for header added on to the main chain
function getAttesterInCommittee(uint _shardId) public view returns(address) {
uint period = block.number / PERIOD_LENGTH;
updateAttesterSampleSize();
// Determine attester pool length based on attester sample size
uint sampleSize;
if (period > sampleSizeLastUpdatedPeriod) {
sampleSize = nextPeriodAttesterSampleSize;
} else {
sampleSize = currentPeriodAttesterSampleSize;
}
// Get the attester pool index to concatenate with shardId and blockHash for random sample
uint poolIndex = attesterRegistry[msg.sender].poolIndex;
// Get the most recent block number before the period started
uint latestBlock = period * PERIOD_LENGTH - 1;
uint latestBlockHash = uint(block.blockhash(latestBlock));
uint index = uint(keccak256(latestBlockHash, poolIndex, _shardId)) % sampleSize;
return attesterPool[index];
}
/// Registers attester to notatery registry, locks in the attester deposit,
/// and returns true on success
function registerAttester() public payable {
address attesterAddress = msg.sender;
require(!attesterRegistry[attesterAddress].deposited);
require(msg.value == ATTESTER_DEPOSIT);
updateAttesterSampleSize();
uint index;
if (emptyStack()) {
index = attesterPoolLength;
attesterPool.push(attesterAddress);
} else {
index = stackPop();
attesterPool[index] = attesterAddress;
}
++attesterPoolLength;
attesterRegistry[attesterAddress] = Attester({
deregisteredPeriod: 0,
poolIndex: index,
balance: msg.value,
deposited: true
});
// if current index is greater than attester sample size, increase attester sample size for next period
if (index >= nextPeriodAttesterSampleSize) {
nextPeriodAttesterSampleSize = index + 1;
}
emit AttesterRegistered(attesterAddress, index);
}
/// Deregisters attester from notatery registry, lock up period countdowns down,
/// attester may call releaseAttester after lock up period finishses to withdraw deposit,
/// and returns true on success
function deregisterAttester() public {
address attesterAddress = msg.sender;
uint index = attesterRegistry[attesterAddress].poolIndex;
require(attesterRegistry[attesterAddress].deposited);
require(attesterPool[index] == attesterAddress);
updateAttesterSampleSize();
uint deregisteredPeriod = block.number / PERIOD_LENGTH;
attesterRegistry[attesterAddress].deregisteredPeriod = deregisteredPeriod;
stackPush(index);
delete attesterPool[index];
--attesterPoolLength;
emit AttesterDeregistered(attesterAddress, index, deregisteredPeriod);
}
/// Removes an entry from attester registry, returns deposit back to the attester,
/// and returns true on success.
function releaseAttester() public {
address attesterAddress = msg.sender;
uint index = attesterRegistry[attesterAddress].poolIndex;
require(attesterRegistry[attesterAddress].deposited == true);
require(attesterRegistry[attesterAddress].deregisteredPeriod != 0);
require((block.number / PERIOD_LENGTH) > (attesterRegistry[attesterAddress].deregisteredPeriod + ATTESTER_LOCKUP_LENGTH));
uint balance = attesterRegistry[attesterAddress].balance;
delete attesterRegistry[attesterAddress];
attesterAddress.transfer(balance);
emit AttesterReleased(attesterAddress, index);
}
/// Add collation header to the main chain, anyone can call this function. It emits a log
function addHeader(
uint _shardId,
uint _period,
bytes32 _chunkRoot,
bytes32 _signature
) public {
require((_shardId >= 0) && (_shardId < shardCount));
require(_period == block.number / PERIOD_LENGTH);
require(_period > lastSubmittedCollation[_shardId]);
updateAttesterSampleSize();
collationRecords[_shardId][_period] = CollationRecord({
chunkRoot: _chunkRoot,
proposer: msg.sender,
isElected: false,
signature: _signature
});
lastSubmittedCollation[_shardId] = block.number / PERIOD_LENGTH;
delete currentVote[_shardId];
emit HeaderAdded(_shardId, _chunkRoot, _period, msg.sender);
}
/// Sampled attester can call the following funtion to submit vote,
/// a vote log will be emitted for client to monitor
function submitVote(
uint _shardId,
uint _period,
uint _index,
bytes32 _chunkRoot
) public {
require((_shardId >= 0) && (_shardId < shardCount));
require(_period == block.number / PERIOD_LENGTH);
require(_period == lastSubmittedCollation[_shardId]);
require(_index < COMMITTEE_SIZE);
require(_chunkRoot == collationRecords[_shardId][_period].chunkRoot);
require(attesterRegistry[msg.sender].deposited);
require(!hasVoted(_shardId, _index));
require(getAttesterInCommittee(_shardId) == msg.sender);
castVote(_shardId, _index);
uint voteCount = getVoteCount(_shardId);
if (voteCount >= QUORUM_SIZE) {
lastApprovedCollation[_shardId] = _period;
collationRecords[_shardId][_period].isElected = true;
}
emit VoteSubmitted(_shardId, _chunkRoot, _period, msg.sender);
}
/// Returns total vote count of currentVote
/// the vote count is stored in the last byte of currentVote
function getVoteCount(uint _shardId) public view returns (uint) {
uint votes = uint(currentVote[_shardId]);
// Extra the last byte of currentVote
return votes % 256;
}
/// Check if a bit is set, this function is used to check
/// if a attester has casted the vote. Right shift currentVote by index
/// and AND with 1, return true if voted, false if not
function hasVoted(uint _shardId, uint _index) public view returns (bool) {
uint votes = uint(currentVote[_shardId]);
// Shift currentVote to right by given index
votes = votes >> (255 - _index);
// AND 1 to neglect everything but bit 0, then compare to 1
return votes & 1 == 1;
}
/// Check if the empty slots stack is empty
function emptyStack() internal view returns(bool) {
return emptySlotsStackTop == 0;
}
/// Save one uint into the empty slots stack for attester to use later
function stackPush(uint _index) internal {
if (emptySlotsStack.length == emptySlotsStackTop)
emptySlotsStack.push(_index);
else
emptySlotsStack[emptySlotsStackTop] = _index;
++emptySlotsStackTop;
}
/// To keep track of attester size in between periods, we call updateAttesterSampleSize
/// before attester registration/deregistration so correct size can be applied next period
function updateAttesterSampleSize() internal {
uint currentPeriod = block.number / PERIOD_LENGTH;
if (currentPeriod < sampleSizeLastUpdatedPeriod) {
return;
}
currentPeriodAttesterSampleSize = nextPeriodAttesterSampleSize;
sampleSizeLastUpdatedPeriod = currentPeriod;
}
/// Get one uint out of the empty slots stack for attester index
function stackPop() internal returns(uint) {
require(emptySlotsStackTop > 1);
--emptySlotsStackTop;
return emptySlotsStack[emptySlotsStackTop];
}
/// Set the index bit to one, attester uses this function to cast its vote,
/// after the attester casts its vote, we increase currentVote's count by 1
function castVote(uint _shardId, uint _index) internal {
uint votes = uint(currentVote[_shardId]);
// Get the bitfield by shifting 1 to the index
uint indexToFlag = 2 ** (255 - _index);
// OR with currentVote to cast attester index to 1
votes = votes | indexToFlag;
// Update vote count
votes++;
currentVote[_shardId] = bytes32(votes);
}
}