mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-06 01:32:18 +00:00
72c03348b6
Former-commit-id: 6c54cefc17c1855b3fde65de940ab0ae5db072f1 [formerly 70a7100e04ffe3b3d7b558ee05ae4a97fe16403e] Former-commit-id: e5b1d3cc70144cc2fef69ce26b1be89d1721dd7c
284 lines
11 KiB
Solidity
284 lines
11 KiB
Solidity
pragma solidity ^0.4.23;
|
|
|
|
|
|
contract SMC {
|
|
event HeaderAdded(uint indexed shardId, bytes32 chunkRoot, uint period, address proposerAddress);
|
|
event NotaryRegistered(address notary, uint poolIndex);
|
|
event NotaryDeregistered(address notary, uint poolIndex, uint deregisteredPeriod);
|
|
event NotaryReleased(address notary, uint poolIndex);
|
|
event VoteSubmitted(uint indexed shardId, bytes32 chunkRoot, uint period, address notaryAddress);
|
|
|
|
struct Notary {
|
|
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
|
|
}
|
|
|
|
// Notary state variables
|
|
address[] public notaryPool;
|
|
// notaryAddress => notaryStruct
|
|
mapping (address => Notary) public notaryRegistry;
|
|
// number of notaries
|
|
uint public notaryPoolLength;
|
|
// current vote count of each shard
|
|
// first 31 bytes are bitfield of notary'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 notary slot indicies
|
|
uint[] emptySlotsStack;
|
|
// Top index of the stack
|
|
uint emptySlotsStackTop;
|
|
// Notary sample size at current period and next period
|
|
uint currentPeriodNotarySampleSize;
|
|
uint nextPeriodNotarySampleSize;
|
|
uint sampleSizeLastUpdatedPeriod;
|
|
|
|
// Constant values
|
|
// Length of challenge period for notary's proof of custody
|
|
uint public constant CHALLENGE_PERIOD = 25;
|
|
// Number of blocks per period
|
|
uint constant PERIOD_LENGTH = 5;
|
|
// Number of shards
|
|
uint constant SHARD_COUNT = 100;
|
|
// The minimum deposit size for a notary
|
|
uint constant NOTARY_DEPOSIT = 1000 ether;
|
|
// The reward for notary on voting for a collation
|
|
uint constant NOTARY_REWARD = 0.001 ether;
|
|
// Time the ether is locked by notaries
|
|
uint constant NOTARY_LOCKUP_LENGTH = 16128;
|
|
// Number of periods ahead of current period, which the contract
|
|
// is able to return the notary of that period
|
|
uint constant LOOKAHEAD_LENGTH = 4;
|
|
// Number of notaries to select from notary pool for each shard in each period
|
|
uint constant COMMITTEE_SIZE = 135;
|
|
// Threshold(number of notaries in committee) for a proposal to be accepted
|
|
uint constant QUORUM_SIZE = 90;
|
|
|
|
/// Checks if a notary with given shard id and period has been chosen as
|
|
/// a committee member to vote for header added on to the main chain
|
|
function getNotaryInCommittee(uint _shardId) public view returns(address) {
|
|
uint period = block.number / PERIOD_LENGTH;
|
|
|
|
updateNotarySampleSize();
|
|
|
|
// Determine notary pool length based on notary sample size
|
|
uint sampleSize;
|
|
if (period > sampleSizeLastUpdatedPeriod) {
|
|
sampleSize = nextPeriodNotarySampleSize;
|
|
} else {
|
|
sampleSize = currentPeriodNotarySampleSize;
|
|
}
|
|
|
|
// Get the notary pool index to concatenate with shardId and blockHash for random sample
|
|
uint poolIndex = notaryRegistry[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 notaryPool[index];
|
|
}
|
|
|
|
/// Registers notary to notatery registry, locks in the notary deposit,
|
|
/// and returns true on success
|
|
function registerNotary() public payable {
|
|
address notaryAddress = msg.sender;
|
|
require(!notaryRegistry[notaryAddress].deposited);
|
|
require(msg.value == NOTARY_DEPOSIT);
|
|
|
|
updateNotarySampleSize();
|
|
|
|
uint index;
|
|
if (emptyStack()) {
|
|
index = notaryPoolLength;
|
|
notaryPool.push(notaryAddress);
|
|
} else {
|
|
index = stackPop();
|
|
notaryPool[index] = notaryAddress;
|
|
}
|
|
++notaryPoolLength;
|
|
|
|
notaryRegistry[notaryAddress] = Notary({
|
|
deregisteredPeriod: 0,
|
|
poolIndex: index,
|
|
balance: msg.value,
|
|
deposited: true
|
|
});
|
|
|
|
// if current index is greater than notary sample size, increase notary sample size for next period
|
|
if (index >= nextPeriodNotarySampleSize) {
|
|
nextPeriodNotarySampleSize = index + 1;
|
|
}
|
|
|
|
emit NotaryRegistered(notaryAddress, index);
|
|
}
|
|
|
|
/// Deregisters notary from notatery registry, lock up period countdowns down,
|
|
/// notary may call releaseNotary after lock up period finishses to withdraw deposit,
|
|
/// and returns true on success
|
|
function deregisterNotary() public {
|
|
address notaryAddress = msg.sender;
|
|
uint index = notaryRegistry[notaryAddress].poolIndex;
|
|
require(notaryRegistry[notaryAddress].deposited);
|
|
require(notaryPool[index] == notaryAddress);
|
|
|
|
updateNotarySampleSize();
|
|
|
|
uint deregisteredPeriod = block.number / PERIOD_LENGTH;
|
|
notaryRegistry[notaryAddress].deregisteredPeriod = deregisteredPeriod;
|
|
|
|
stackPush(index);
|
|
delete notaryPool[index];
|
|
--notaryPoolLength;
|
|
emit NotaryDeregistered(notaryAddress, index, deregisteredPeriod);
|
|
}
|
|
|
|
/// Removes an entry from notary registry, returns deposit back to the notary,
|
|
/// and returns true on success.
|
|
function releaseNotary() public {
|
|
address notaryAddress = msg.sender;
|
|
uint index = notaryRegistry[notaryAddress].poolIndex;
|
|
require(notaryRegistry[notaryAddress].deposited == true);
|
|
require(notaryRegistry[notaryAddress].deregisteredPeriod != 0);
|
|
require((block.number / PERIOD_LENGTH) > (notaryRegistry[notaryAddress].deregisteredPeriod + NOTARY_LOCKUP_LENGTH));
|
|
|
|
uint balance = notaryRegistry[notaryAddress].balance;
|
|
delete notaryRegistry[notaryAddress];
|
|
notaryAddress.transfer(balance);
|
|
emit NotaryReleased(notaryAddress, 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
|
|
) public {
|
|
require((_shardId >= 0) && (_shardId < SHARD_COUNT));
|
|
require(_period == block.number / PERIOD_LENGTH);
|
|
require(_period > lastSubmittedCollation[_shardId]);
|
|
|
|
updateNotarySampleSize();
|
|
|
|
collationRecords[_shardId][_period] = CollationRecord({
|
|
chunkRoot: _chunkRoot,
|
|
proposer: msg.sender,
|
|
isElected: false
|
|
});
|
|
|
|
lastSubmittedCollation[_shardId] = block.number / PERIOD_LENGTH;
|
|
delete currentVote[_shardId];
|
|
|
|
emit HeaderAdded(_shardId, _chunkRoot, _period, msg.sender);
|
|
}
|
|
|
|
/// Sampled notary 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 < SHARD_COUNT));
|
|
require(_period == block.number / PERIOD_LENGTH);
|
|
require(_period == lastSubmittedCollation[_shardId]);
|
|
require(_index < COMMITTEE_SIZE);
|
|
require(_chunkRoot == collationRecords[_shardId][_period].chunkRoot);
|
|
require(notaryRegistry[msg.sender].deposited);
|
|
require(!hasVoted(_shardId, _index));
|
|
require(getNotaryInCommittee(_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 notary 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 notary to use later
|
|
function stackPush(uint _index) internal {
|
|
if (emptySlotsStack.length == emptySlotsStackTop)
|
|
emptySlotsStack.push(_index);
|
|
else
|
|
emptySlotsStack[emptySlotsStackTop] = _index;
|
|
|
|
++emptySlotsStackTop;
|
|
}
|
|
|
|
/// To keep track of notary size in between periods, we call updateNotarySampleSize
|
|
/// before notary registration/deregistration so correct size can be applied next period
|
|
function updateNotarySampleSize() internal {
|
|
uint currentPeriod = block.number / PERIOD_LENGTH;
|
|
if (currentPeriod < sampleSizeLastUpdatedPeriod) {
|
|
return;
|
|
}
|
|
currentPeriodNotarySampleSize = nextPeriodNotarySampleSize;
|
|
sampleSizeLastUpdatedPeriod = currentPeriod;
|
|
}
|
|
|
|
/// Get one uint out of the empty slots stack for notary index
|
|
function stackPop() internal returns(uint) {
|
|
require(emptySlotsStackTop > 1);
|
|
--emptySlotsStackTop;
|
|
return emptySlotsStack[emptySlotsStackTop];
|
|
}
|
|
|
|
/// Set the index bit to one, notary uses this function to cast its vote,
|
|
/// after the notary 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 notary index to 1
|
|
votes = votes | indexToFlag;
|
|
// Update vote count
|
|
votes++;
|
|
currentVote[_shardId] = bytes32(votes);
|
|
}
|
|
|
|
} |