mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-31 23:41:22 +00:00
621e769692
Former-commit-id: c36b63e78bc0bb6d05763605f443d99fc6370eb4 [formerly 5ed2b7fa9a7640ea93fc05b7351da95f10a5b6b9] Former-commit-id: ccd55ddcc878e4d1bcd3e71102f88de23982a427
328 lines
11 KiB
Solidity
328 lines
11 KiB
Solidity
pragma solidity ^0.4.19;
|
|
|
|
import "RLP.sol";
|
|
|
|
interface SigHasherContract {
|
|
// function () returns(bytes32);
|
|
}
|
|
|
|
contract VMC {
|
|
using RLP for RLP.RLPItem;
|
|
using RLP for RLP.Iterator;
|
|
using RLP for bytes;
|
|
|
|
struct Validator {
|
|
// Amount of wei the validator holds
|
|
uint deposit;
|
|
// The validator's address
|
|
address addr;
|
|
// Addess to withdraw to
|
|
address returnAddr;
|
|
// The cycle number which the validator would be included after
|
|
int cycle;
|
|
}
|
|
|
|
struct CollationHeader {
|
|
bytes32 parentCollationHash;
|
|
int score;
|
|
}
|
|
|
|
struct Receipt {
|
|
int shardId;
|
|
uint txStartgas;
|
|
uint txGasprice;
|
|
uint value;
|
|
address sender;
|
|
address to;
|
|
bytes32 data;
|
|
}
|
|
|
|
mapping (int => Validator) validators;
|
|
mapping (int => mapping (bytes32 => CollationHeader)) collationHeaders;
|
|
mapping (int => Receipt) receipts;
|
|
|
|
mapping (int => bytes32) shardHead;
|
|
int numValidators;
|
|
int numReceipts;
|
|
// Indexs of empty slots caused by the function `withdraw`
|
|
mapping (int => int) emptySlotsStack;
|
|
// The top index of the stack in empty_slots_stack
|
|
int emptySlotsStackTop;
|
|
|
|
// Is a valcode addr deposited now?
|
|
mapping (address => bool) isValcodeDeposited;
|
|
|
|
// Constant values
|
|
uint constant periodLength = 5;
|
|
int constant numValidatorsPerCycle = 100;
|
|
int constant shardCount = 100;
|
|
// The exact deposit size which you have to deposit to become a validator
|
|
uint constant depositSize = 100 ether;
|
|
// Any given validator randomly gets allocated to some number of shards every SHUFFLING_CYCLE
|
|
int constant shufflingCycleLength = 5; // FIXME: just modified temporarily for test;
|
|
|
|
bytes32 addHeaderLogTopic;
|
|
SigHasherContract sighasher;
|
|
// Log the latest period number of the shard
|
|
mapping (int => int) periodHead;
|
|
|
|
function VMC() public {
|
|
numValidators = 0;
|
|
emptySlotsStackTop = 0;
|
|
addHeaderLogTopic = keccak256("add_header()");
|
|
sighasher = SigHasherContract(0xDFFD41E18F04Ad8810c83B14FD1426a82E625A7D);
|
|
}
|
|
|
|
function isStackEmpty() internal view returns(bool) {
|
|
return emptySlotsStackTop == 0;
|
|
}
|
|
function stackPush(int index) internal {
|
|
emptySlotsStack[emptySlotsStackTop] = index;
|
|
++emptySlotsStackTop;
|
|
}
|
|
function stackPop() internal returns(int) {
|
|
if (isStackEmpty())
|
|
return -1;
|
|
--emptySlotsStackTop;
|
|
return emptySlotsStack[emptySlotsStackTop];
|
|
}
|
|
|
|
function getValidatorsMaxIndex() public view returns(int) {
|
|
int activateValidatorNum = 0;
|
|
int currentCycle = int(block.number) / shufflingCycleLength;
|
|
int allValidatorSlotsNum = numValidators + emptySlotsStackTop;
|
|
|
|
// TODO: any better way to iterate the mapping?
|
|
for (int i = 0; i < 1024; ++i) {
|
|
if (i >= allValidatorSlotsNum)
|
|
break;
|
|
if (validators[i].cycle <= currentCycle)
|
|
activateValidatorNum += 1;
|
|
}
|
|
return activateValidatorNum + emptySlotsStackTop;
|
|
}
|
|
|
|
function deposit(address _returnAddr) public payable returns(int) {
|
|
require(!isValcodeDeposited[msg.sender]);
|
|
require(msg.value == depositSize);
|
|
// Find the empty slot index in validators set
|
|
int index;
|
|
int nextCycle = 0;
|
|
if (!isStackEmpty())
|
|
index = stackPop();
|
|
else {
|
|
index = int(numValidators);
|
|
nextCycle = (int(block.number) / shufflingCycleLength) + 1;
|
|
validators[index] = Validator({
|
|
deposit: msg.value,
|
|
addr: msg.sender,
|
|
returnAddr: _returnAddr,
|
|
cycle: nextCycle
|
|
});
|
|
}
|
|
++numValidators;
|
|
isValcodeDeposited[msg.sender] = true;
|
|
|
|
log2(keccak256("deposit()"), bytes32(msg.sender), bytes32(index));
|
|
return index;
|
|
}
|
|
|
|
function withdraw(int _validatorIndex) public {
|
|
var msgHash = keccak256("withdraw");
|
|
require(msg.sender == validators[_validatorIndex].addr);
|
|
validators[_validatorIndex].returnAddr.transfer(validators[_validatorIndex].deposit);
|
|
isValcodeDeposited[validators[_validatorIndex].addr] = false;
|
|
delete validators[_validatorIndex];
|
|
stackPush(_validatorIndex);
|
|
--numValidators;
|
|
log1(msgHash, bytes32(_validatorIndex));
|
|
}
|
|
|
|
function sample(int _shardId) public constant returns(address) {
|
|
require(block.number >= periodLength);
|
|
var cycle = int(block.number) / shufflingCycleLength;
|
|
int cycleStartBlockNumber = cycle * shufflingCycleLength - 1;
|
|
if (cycleStartBlockNumber < 0)
|
|
cycleStartBlockNumber = 0;
|
|
int cycleSeed = int(block.blockhash(uint(cycleStartBlockNumber)));
|
|
// originally, error occurs when block.number <= 4 because
|
|
// `seed_block_number` becomes negative in these cases.
|
|
int seed = int(block.blockhash(block.number - (block.number % uint(periodLength)) - 1));
|
|
|
|
uint indexInSubset = uint(keccak256(seed, bytes32(_shardId))) % uint(numValidatorsPerCycle);
|
|
uint validatorIndex = uint(keccak256(cycleSeed, bytes32(_shardId), bytes32(indexInSubset))) % uint(getValidatorsMaxIndex());
|
|
|
|
if (validators[int(validatorIndex)].cycle > cycle)
|
|
return 0x0;
|
|
else
|
|
return validators[int(validatorIndex)].addr;
|
|
}
|
|
|
|
// Get all possible shard ids that the given _valcodeAddr
|
|
// may be sampled in the current cycle
|
|
function getShardList(address _validatorAddr) public constant returns(bool[shardCount]) {
|
|
bool[shardCount] memory shardList;
|
|
int cycle = int(block.number) / shufflingCycleLength;
|
|
int cycleStartBlockNumber = cycle * shufflingCycleLength - 1;
|
|
if (cycleStartBlockNumber < 0)
|
|
cycleStartBlockNumber = 0;
|
|
|
|
var cycleSeed = block.blockhash(uint(cycleStartBlockNumber));
|
|
int validatorsMaxIndex = getValidatorsMaxIndex();
|
|
if (numValidators != 0) {
|
|
for (uint8 shardId = 0; shardId < shardCount; ++shardId) {
|
|
shardList[shardId] = false;
|
|
for (uint8 possibleIndexInSubset = 0; possibleIndexInSubset < shardCount; ++possibleIndexInSubset) {
|
|
uint validatorIndex = uint(keccak256(cycleSeed, bytes32(shardId), bytes32(possibleIndexInSubset)))
|
|
% uint(validatorsMaxIndex);
|
|
if (_validatorAddr == validators[int(validatorIndex)].addr) {
|
|
shardList[shardId] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return shardList;
|
|
}
|
|
|
|
struct Header {
|
|
int shardId;
|
|
uint expectedPeriodNumber;
|
|
bytes32 periodStartPrevhash;
|
|
bytes32 parentCollationHash;
|
|
bytes32 txListRoot;
|
|
address collationCoinbase;
|
|
bytes32 postStateRoot;
|
|
bytes32 receiptRoot;
|
|
int collationNumber;
|
|
bytes sig;
|
|
}
|
|
|
|
function addHeader(bytes _header) public returns(bool) {
|
|
// require(_header.length <= 4096);
|
|
// TODO
|
|
// values = RLPList(header, [num, num, bytes32, bytes32, bytes32, address, bytes32, bytes32, num, bytes])
|
|
// return True
|
|
bytes memory mHeader = _header;
|
|
var RLPList = mHeader.toRLPItem(true).iterator();
|
|
var header = Header({
|
|
shardId: RLPList.next().toInt(),
|
|
expectedPeriodNumber: RLPList.next().toUint(),
|
|
periodStartPrevhash: RLPList.next().toBytes32(),
|
|
parentCollationHash: RLPList.next().toBytes32(),
|
|
txListRoot: RLPList.next().toBytes32(),
|
|
collationCoinbase: RLPList.next().toAddress(),
|
|
postStateRoot: RLPList.next().toBytes32(),
|
|
receiptRoot: RLPList.next().toBytes32(),
|
|
collationNumber: RLPList.next().toInt(),
|
|
sig: RLPList.next().toBytes()
|
|
});
|
|
|
|
// Check if the header is valid
|
|
require((header.shardId >= 0) && (header.shardId < shardCount));
|
|
require(block.number >= periodLength);
|
|
require(header.expectedPeriodNumber == (block.number / periodLength));
|
|
require(header.periodStartPrevhash == block.blockhash(header.expectedPeriodNumber * periodLength - 1));
|
|
|
|
|
|
// Check if this header already exists
|
|
var entireHeaderHash = keccak256(header);
|
|
assert(entireHeaderHash != 0x0);
|
|
assert(collationHeaders[header.shardId][entireHeaderHash].score == 0);
|
|
// Check whether the parent exists.
|
|
// if (parent_collation_hash == 0), i.e., is the genesis,
|
|
// then there is no need to check.
|
|
if (header.parentCollationHash != 0x0)
|
|
assert ((header.parentCollationHash == 0x0) || (collationHeaders[header.shardId][header.parentCollationHash].score > 0));
|
|
// Check if only one colllation in one period
|
|
assert (periodHead[header.shardId] < int(header.expectedPeriodNumber));
|
|
|
|
// Check the signature with validation_code_addr
|
|
var collatorValcodeAddr = sample(header.shardId);
|
|
if (collatorValcodeAddr == 0x0)
|
|
return false;
|
|
|
|
// assembly {
|
|
// TODO next block
|
|
// }
|
|
// sighash = extract32(raw_call(self.sighasher_addr, header, gas=200000, outsize=32), 0)
|
|
// assert extract32(raw_call(collator_valcode_addr, concat(sighash, sig), gas=self.sig_gas_limit, outsize=32), 0) == as_bytes32(1)
|
|
|
|
// Check score == collation_number
|
|
var _score = collationHeaders[header.shardId][header.parentCollationHash].score + 1;
|
|
assert(header.collationNumber == _score);
|
|
|
|
|
|
// Add the header
|
|
collationHeaders[header.shardId][entireHeaderHash] = CollationHeader({
|
|
parentCollationHash: header.parentCollationHash,
|
|
score: _score
|
|
});
|
|
|
|
// Update the latest period number
|
|
periodHead[header.shardId] = int(header.expectedPeriodNumber);
|
|
|
|
// Determine the head
|
|
if (_score > collationHeaders[header.shardId][shardHead[header.shardId]].score) {
|
|
var previousHeadHash = shardHead[header.shardId];
|
|
shardHead[header.shardId] = entireHeaderHash;
|
|
// Logs only when `change_head` happens due to the fork
|
|
// TODO LOG
|
|
// log1([addHeaderLogTopic, keccak256("change_head"), entireHeaderHash], previousHeadHash);
|
|
}
|
|
// Emit log
|
|
// TODO LOG
|
|
// log1(addHeaderLogTopic, _header);
|
|
|
|
return true;
|
|
}
|
|
|
|
function getPeriodStartPrevhash(uint _expectedPeriodNumber) public constant returns(bytes32) {
|
|
uint blockNumber = _expectedPeriodNumber * periodLength - 1;
|
|
require(block.number > blockNumber);
|
|
return block.blockhash(blockNumber);
|
|
}
|
|
|
|
|
|
|
|
// Returns the difference between the block number of this hash and the block
|
|
// number of the 10000th ancestor of this hash.
|
|
function getAncestorDistance(bytes32 /*_hash*/) public pure returns(bytes32) {
|
|
// TODO
|
|
}
|
|
|
|
// Returns the gas limit that collations can currently have (by default make
|
|
// this function always answer 10 million).
|
|
function getCollationGasLimit() public pure returns(uint) {
|
|
return 10000000;
|
|
}
|
|
|
|
|
|
// Records a request to deposit msg.value ETH to address to in shard shard_id
|
|
// during a future collation. Saves a `receipt ID` for this request,
|
|
// also saving `msg.sender`, `msg.value`, `to`, `shard_id`, `startgas`,
|
|
// `gasprice`, and `data`.
|
|
function txToShard(address _to, int _shardId, uint _txStartgas, uint _txGasprice, bytes12 _data) public payable returns(int) {
|
|
receipts[numReceipts] = Receipt({
|
|
shardId: _shardId,
|
|
txStartgas: _txStartgas,
|
|
txGasprice: _txGasprice,
|
|
value: msg.value,
|
|
sender: msg.sender,
|
|
to: _to,
|
|
data: _data
|
|
});
|
|
var receiptId = numReceipts;
|
|
++numReceipts;
|
|
|
|
log3(keccak256("tx_to_shard()"), bytes32(_to), bytes32(_shardId), bytes32(receiptId));
|
|
return receiptId;
|
|
}
|
|
|
|
function updataGasPrice(int _receiptId, uint _txGasprice) public payable returns(bool) {
|
|
require(receipts[_receiptId].sender == msg.sender);
|
|
receipts[_receiptId].txGasprice = _txGasprice;
|
|
return true;
|
|
}
|
|
}
|