From 4c2180be1d87303a4d88519bbb440a3465a580a4 Mon Sep 17 00:00:00 2001 From: Fynn Date: Mon, 15 Jan 2018 23:01:24 +0100 Subject: [PATCH] WIP: Validator manager contract in solidity Former-commit-id: 98483e7425feed46ae2a946e7d86461cbcf28d21 [formerly 334390368a91c34c1d66b0e262fcaef293cd6409] Former-commit-id: 7f5f7dc5385c8cef6ecea39040f915554472a736 --- sharding/contracts/validator_manager.sol | 230 +++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 sharding/contracts/validator_manager.sol diff --git a/sharding/contracts/validator_manager.sol b/sharding/contracts/validator_manager.sol new file mode 100644 index 000000000..50a1e5694 --- /dev/null +++ b/sharding/contracts/validator_manager.sol @@ -0,0 +1,230 @@ +pragma solidity ^0.4.19; + +contract VMC { + struct Validator { + // Amount of wei the validator holds + uint deposit; + // The address which the validator's signatures must verify to (to be later replaced with validation code) + address validationCodeAddr; + // 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; + bytes12 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; + + // The exact deposit size which you have to deposit to become a validator + uint depositSize; + // Any given validator randomly gets allocated to some number of shards every SHUFFLING_CYCLE + int shufflingCycleLength; + // Gas limit of the signature validation code + uint sigGasLimit; + // Is a valcode addr deposited now? + mapping (address => bool) isValcodeDeposited; + uint periodLength; + int numValidatorsPerCycle; + int shardCount; + bytes32 addHeaderLogTopic; + address sighasherAddr; + // Log the latest period number of the shard + mapping (int => int) periodHead; + + function VMC() public { + numValidators = 0; + emptySlotsStackTop = 0; + depositSize = 100 ether; + shufflingCycleLength = 5; // FIXME: just modified temporarily for test; + sigGasLimit = 400000; + periodLength = 5; + numValidatorsPerCycle = 100; + shardCount = 100; + addHeaderLogTopic = keccak256("add_header()"); + sighasherAddr = 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) { + address zeroAddr = 0x0; + 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].validationCodeAddr != zeroAddr) && + (validators[i].cycle <= currentCycle)) + activateValidatorNum += 1; + } + return activateValidatorNum + emptySlotsStackTop; + } + + function deposit(address _validationCodeAddr, address _returnAddr) public payable returns(int) { + require(!isValcodeDeposited[_validationCodeAddr]); + 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, + validationCodeAddr: _validationCodeAddr, + returnAddr: _returnAddr, + cycle: nextCycle + }); + } + ++numValidators; + isValcodeDeposited[_validationCodeAddr] = true; + + log2(keccak256("deposit()"), bytes32(_validationCodeAddr), bytes32(index)); + return index; + } + + function withdraw(uint _validatorIndex, bytes10 _sig) public returns(bool) { + // TODO + } + + 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)].validationCodeAddr; + } + + // Get all possible shard ids that the given valcode_addr + // may be sampled in the current cycle + function getShardList(address _valcodeAddr) public constant returns(bool[100]) { + bool[100] 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 < 100; ++shardId) { + shardList[shardId] = false; + for (int possibleIndexInSubset = 0; possibleIndexInSubset < 100; ++possibleIndexInSubset) { + uint validatorIndex = uint(keccak256(cycleSeed, bytes32(shardId), bytes32(possibleIndexInSubset))) + % uint(validatorsMaxIndex); + if (_valcodeAddr == validators[int(validatorIndex)].validationCodeAddr) { + shardList[shardId] = true; + break; + } + } + } + } + return shardList; + } + + + function addHeader(bytes12 header) public returns(bool) { + // TODO + } + + 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; + } +}