diff --git a/cmd/smoketest.go b/cmd/smoketest.go index ffc5f5f409..277b73c28a 100644 --- a/cmd/smoketest.go +++ b/cmd/smoketest.go @@ -113,7 +113,7 @@ func SmokeTest(c *cli.Context) error { <-time.NewTimer(time.Second).C chainHandle.ThresholdRelay().SubmitRelayEntry(&event.Entry{ - RequestID: big.NewInt(0), + SigningId: big.NewInt(0), Value: big.NewInt(0), GroupPubKey: big.NewInt(0).Bytes(), Seed: big.NewInt(0), diff --git a/config.toml.SAMPLE b/config.toml.SAMPLE index aaeec1d3d6..0a80184283 100644 --- a/config.toml.SAMPLE +++ b/config.toml.SAMPLE @@ -11,10 +11,8 @@ KeyFile = "/Users/someuser/ethereum/data/keystore/UTC--2018-03-11T01-37-33.202765887Z--AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAA" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" # Hex-encoded address of StakingProxy contract StakingProxy = "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" diff --git a/config/config_test.go b/config/config_test.go index aaca20e397..812af2ae8b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -43,8 +43,7 @@ func TestReadConfig(t *testing.T) { "Ethereum.ContractAddresses": { readValueFunc: func(c *Config) interface{} { return c.Ethereum.ContractAddresses }, expectedValue: map[string]string{ - "KeepRandomBeacon": "0x639deb0dd975af8e4cc91fe9053a37e4faf37649", - "KeepGroup": "0xcf64c2a367341170cb4e09cf8c0ed137d8473ceb", + "KeepRandomBeaconOperator": "0xcf64c2a367341170cb4e09cf8c0ed137d8473ceb", }, }, "Storage.DataDir": { diff --git a/contracts/solidity/contracts/DelayedWithdrawal.sol b/contracts/solidity/contracts/DelayedWithdrawal.sol new file mode 100644 index 0000000000..f9e257b63c --- /dev/null +++ b/contracts/solidity/contracts/DelayedWithdrawal.sol @@ -0,0 +1,35 @@ +pragma solidity ^0.5.4; + +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + + +/** + * @title Delayed Withdrawal + * @dev A base contract to allow delayed funds withdrawal from the contract. + */ +contract DelayedWithdrawal is Ownable { + using SafeMath for uint256; + + uint256 internal _withdrawalDelay; + uint256 internal _pendingWithdrawal; + + /** + * @dev Initiate withdrawal of this contract balance to the owner. + */ + function initiateWithdrawal() public onlyOwner { + _pendingWithdrawal = block.timestamp + _withdrawalDelay; + } + + /** + * @dev Finish withdrawal of this contract balance to the owner. + */ + function finishWithdrawal(address payable payee) public onlyOwner { + require(_pendingWithdrawal > 0, "Pending withdrawal timestamp must be set and be greater than zero."); + require(block.timestamp >= _pendingWithdrawal, "The current time must pass the pending withdrawal timestamp."); + + // Reset pending withdrawal before sending to prevent re-entrancy attacks + _pendingWithdrawal = 0; + payee.transfer(address(this).balance); + } +} diff --git a/contracts/solidity/contracts/KeepGroup.sol b/contracts/solidity/contracts/KeepGroup.sol deleted file mode 100644 index a6a7acf2b7..0000000000 --- a/contracts/solidity/contracts/KeepGroup.sol +++ /dev/null @@ -1,82 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; - - -/** - * @title Keep Group contract - * @dev A proxy contract to provide upgradable Keep Group contract functionality. - * Owner can do upgrades by updating implementation state variable to - * the address of the upgraded contract. All calls to this proxy contract - * are delegated to the implementation contract. - */ -contract KeepGroup is Ownable { - - // Storage position of the address of the current implementation - bytes32 private constant implementationPosition = keccak256("network.keep.group.proxy.implementation"); - - event Upgraded(address implementation); - - constructor(address _implementation) public { - require(_implementation != address(0), "Implementation address can't be zero."); - setImplementation(_implementation); - } - - /** - * @dev Gets the address of the current implementation. - * @return address of the current implementation. - */ - function implementation() public view returns (address _implementation) { - bytes32 position = implementationPosition; - /* solium-disable-next-line */ - assembly { - _implementation := sload(position) - } - } - - /** - * @dev Sets the address of the current implementation. - * @param _implementation address representing the new implementation to be set. - */ - function setImplementation(address _implementation) internal { - bytes32 position = implementationPosition; - /* solium-disable-next-line */ - assembly { - sstore(position, _implementation) - } - } - - /** - * @dev Delegate call to the current implementation contract. - */ - function() external payable { - address _impl = implementation(); - /* solium-disable-next-line */ - assembly { - let ptr := mload(0x40) - calldatacopy(ptr, 0, calldatasize) - let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) - let size := returndatasize - returndatacopy(ptr, 0, size) - - switch result - case 0 { revert(ptr, size) } - default { return(ptr, size) } - } - } - - /** - * @dev Upgrade current implementation. - * @param _implementation Address of the new implementation contract. - */ - function upgradeTo(address _implementation) - public - onlyOwner - { - address currentImplementation = implementation(); - require(_implementation != address(0), "Implementation address can't be zero."); - require(_implementation != currentImplementation, "Implementation address must be different from the current one."); - setImplementation(_implementation); - emit Upgraded(_implementation); - } -} diff --git a/contracts/solidity/contracts/KeepGroupStub.sol b/contracts/solidity/contracts/KeepGroupStub.sol deleted file mode 100644 index 93682b3b84..0000000000 --- a/contracts/solidity/contracts/KeepGroupStub.sol +++ /dev/null @@ -1,38 +0,0 @@ -pragma solidity ^0.5.4; - - -/** - * @title KeepGroupStub - * @dev A simplified Keep Group contract to help local development. - */ -contract KeepGroupStub { - - uint256 internal _randomBeaconValue; - - /** - * @dev Triggers the selection process of a new candidate group. - */ - function runGroupSelection(uint256 newEntry, uint256 requestId, uint256 seed) public { - requestId; - seed; - _randomBeaconValue = newEntry; - } - - /** - * @dev Gets number of active groups. - */ - function numberOfGroups() public pure returns(uint256) { - return 1; - } - - /** - * @dev Returns public key of a group from available groups using modulo operator. - * @param previousEntry Previous random beacon value. - */ - function selectGroup(uint256 previousEntry) public pure returns(bytes memory) { - previousEntry; - // Compressed public key (G2 point) generated with Go client using secret key 123 - return hex"1f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b1b2a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d0"; - } - -} diff --git a/contracts/solidity/contracts/KeepRandomBeaconImplV1.sol b/contracts/solidity/contracts/KeepRandomBeaconImplV1.sol deleted file mode 100644 index eb52db7d3e..0000000000 --- a/contracts/solidity/contracts/KeepRandomBeaconImplV1.sol +++ /dev/null @@ -1,212 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "solidity-bytes-utils/contracts/BytesLib.sol"; -import "./BLS.sol"; - - -interface GroupContract { - function runGroupSelection(uint256 newEntry, uint256 requestId, uint256 seed) external; - function numberOfGroups() external view returns(uint256); - function selectGroup(uint256 previousEntry) external returns(bytes memory); -} - - -/** - * @title KeepRandomBeaconImplV1 - * @dev Initial version of implementation contract that works under Keep Random - * Beacon proxy and allows upgradability. The purpose of the contract is to have - * up-to-date logic for threshold random number generation. Updated contracts - * must inherit from this contract and have to be initialized under updated version name - */ -contract KeepRandomBeaconImplV1 is Ownable { - - using BytesLib for bytes; - - // These are the public events that are used by clients - event RelayEntryRequested(uint256 requestID, uint256 payment, uint256 previousEntry, uint256 seed, bytes groupPublicKey); - event RelayEntryGenerated(uint256 requestID, uint256 requestResponse, bytes requestGroupPubKey, uint256 previousEntry, uint256 seed); - - uint256 internal _requestCounter; - uint256 internal _minPayment; - uint256 internal _withdrawalDelay; - uint256 internal _pendingWithdrawal; - address internal _groupContract; - uint256 internal _previousEntry; - uint256 internal _relayRequestTimeout; - - mapping (string => bool) internal _initialized; - - struct Request { - address sender; - uint256 payment; - bytes groupPubKey; - address callbackContract; - string callbackMethod; - } - - mapping(uint256 => Request) internal _requests; - - /** - * @dev Prevent receiving ether without explicitly calling a function. - */ - function() external payable { - revert("Can not call contract without explicitly calling a function."); - } - - /** - * @dev Initialize Keep Random Beacon implementaion contract. - * @param minPayment Minimum amount of ether (in wei) that allows anyone to request a random number. - * @param withdrawalDelay Delay before the owner can withdraw ether from this contract. - * @param genesisEntry Initial relay entry to create first group. - * @param genesisGroupPubKey Group to respond to the initial relay entry request. - * @param groupContract Group contract linked to this contract. - * @param relayRequestTimeout Timeout in blocks for a relay entry to appear on the chain. - * Blocks are counted from the moment relay request occur. - */ - function initialize( - uint256 minPayment, uint256 withdrawalDelay, uint256 genesisEntry, - bytes memory genesisGroupPubKey, address groupContract, uint256 relayRequestTimeout - ) public onlyOwner { - require(!initialized(), "Contract is already initialized."); - _minPayment = minPayment; - _initialized["KeepRandomBeaconImplV1"] = true; - _withdrawalDelay = withdrawalDelay; - _pendingWithdrawal = 0; - _previousEntry = genesisEntry; - _groupContract = groupContract; - _relayRequestTimeout = relayRequestTimeout; - - // Create initial relay entry request. This will allow relayEntry to be called once - // to trigger the creation of the first group. Requests are removed on successful - // entries so genesis entry can only be called once. - _requestCounter++; - _requests[_requestCounter] = Request(msg.sender, 0, genesisGroupPubKey, address(0), ""); - } - - /** - * @dev Checks if this contract is initialized. - */ - function initialized() public view returns (bool) { - return _initialized["KeepRandomBeaconImplV1"]; - } - - /** - * @dev Creates a request to generate a new relay entry, which will include a - * random number (by signing the previous entry's random number). - * @param seed Initial seed random value from the client. It should be a cryptographically generated random value. - * @return An uint256 representing uniquely generated relay request ID. It is also returned as part of the event. - */ - function requestRelayEntry(uint256 seed) public payable returns (uint256) { - return requestRelayEntry(seed, address(0), ""); - } - - /** - * @dev Creates a request to generate a new relay entry, which will include a - * random number (by signing the previous entry's random number). - * @param seed Initial seed random value from the client. It should be a cryptographically generated random value. - * @param callbackContract Callback contract address. Callback is called once a new relay entry has been generated. - * @param callbackMethod Callback contract method signature. String representation of your method with a single - * uint256 input parameter i.e. "relayEntryCallback(uint256)". - * @return An uint256 representing uniquely generated relay request ID. It is also returned as part of the event. - */ - function requestRelayEntry(uint256 seed, address callbackContract, string memory callbackMethod) public payable returns (uint256) { - require( - msg.value >= _minPayment, - "Payment is less than required minimum." - ); - - require( - GroupContract(_groupContract).numberOfGroups() > 0, - "At least one group needed to serve the request." - ); - - bytes memory groupPubKey = GroupContract(_groupContract).selectGroup(_previousEntry); - - _requestCounter++; - - _requests[_requestCounter] = Request(msg.sender, msg.value, groupPubKey, callbackContract, callbackMethod); - - emit RelayEntryRequested(_requestCounter, msg.value, _previousEntry, seed, groupPubKey); - return _requestCounter; - } - - /** - * @dev Initiate withdrawal of this contract balance to the owner. - */ - function initiateWithdrawal() public onlyOwner { - _pendingWithdrawal = block.timestamp + _withdrawalDelay; - } - - /** - * @dev Finish withdrawal of this contract balance to the owner. - */ - function finishWithdrawal(address payable payee) public onlyOwner { - require(_pendingWithdrawal > 0, "Pending withdrawal timestamp must be set and be greater than zero."); - require(block.timestamp >= _pendingWithdrawal, "The current time must pass the pending withdrawal timestamp."); - - // Reset pending withdrawal before sending to prevent re-entrancy attacks - _pendingWithdrawal = 0; - payee.transfer(address(this).balance); - } - - /** - * @dev Set the minimum payment that is required before a relay entry occurs. - * @param minPayment is the value in wei that is required to be payed for the process to start. - */ - function setMinimumPayment(uint256 minPayment) public onlyOwner { - _minPayment = minPayment; - } - - /** - * @dev Get the minimum payment that is required before a relay entry occurs. - */ - function minimumPayment() public view returns(uint256) { - return _minPayment; - } - - /** - * @dev Creates a new relay entry and stores the associated data on the chain. - * @param requestID The request that started this generation - to tie the results back to the request. - * @param groupSignature The generated random number. - * @param groupPubKey Public key of the group that generated the threshold signature. - */ - function relayEntry(uint256 requestID, uint256 groupSignature, bytes memory groupPubKey, uint256 previousEntry, uint256 seed) public { - - require(_requests[requestID].groupPubKey.equalStorage(groupPubKey), "Provided group was not selected to produce entry for this request."); - require(BLS.verify(groupPubKey, abi.encodePacked(previousEntry, seed), bytes32(groupSignature)), "Group signature failed to pass BLS verification."); - - address callbackContract = _requests[requestID].callbackContract; - - if (callbackContract != address(0)) { - callbackContract.call(abi.encodeWithSignature(_requests[requestID].callbackMethod, groupSignature)); - } - - delete _requests[requestID]; - _previousEntry = groupSignature; - - emit RelayEntryGenerated(requestID, groupSignature, groupPubKey, previousEntry, seed); - GroupContract(_groupContract).runGroupSelection(groupSignature, requestID, seed); - } - - /** - * @dev Gets the previous relay entry value. - */ - function previousEntry() public view returns(uint256) { - return _previousEntry; - } - - /** - * Gets the timeout in blocks for a relay entry to appear on the chain. - */ - function relayRequestTimeout() public view returns(uint256) { - return _relayRequestTimeout; - } - - /** - * @dev Gets version of the current implementation. - */ - function version() public pure returns (string memory) { - return "V1"; - } -} diff --git a/contracts/solidity/contracts/KeepGroupImplV1.sol b/contracts/solidity/contracts/KeepRandomBeaconOperator.sol similarity index 54% rename from contracts/solidity/contracts/KeepGroupImplV1.sol rename to contracts/solidity/contracts/KeepRandomBeaconOperator.sol index ccbbe5ea90..f60cba85f8 100644 --- a/contracts/solidity/contracts/KeepGroupImplV1.sol +++ b/contracts/solidity/contracts/KeepRandomBeaconOperator.sol @@ -8,49 +8,62 @@ import "./TokenStaking.sol"; import "./utils/UintArrayUtils.sol"; import "./utils/AddressArrayUtils.sol"; import "solidity-bytes-utils/contracts/BytesLib.sol"; +import "./cryptography/BLS.sol"; - -interface KeepRandomBeaconContract { - function relayRequestTimeout() external view returns(uint256); +interface ServiceContract { + function entryCreated(uint256 requestId, uint256 entry) external; } -contract KeepGroupImplV1 is Ownable { +/** + * @title KeepRandomBeaconOperator + * @dev Keep client facing contract for random beacon security-critical operations. + * Handles group creation and expiration, BLS signature verification and incentives. + * The contract is not upgradeable. New functionality can be implemented by deploying + * new versions following Keep client update and re-authorization by the stakers. + */ +contract KeepRandomBeaconOperator is Ownable { using SafeMath for uint256; using BytesLib for bytes; using ECDSA for bytes32; + using AddressArrayUtils for address[]; event OnGroupRegistered(bytes groupPubKey); // TODO: Rename to DkgResultSubmittedEvent // TODO: Add memberIndex - event DkgResultPublishedEvent(uint256 requestId, bytes groupPubKey); + event DkgResultPublishedEvent(uint256 signingId, bytes groupPubKey); + + // These are the public events that are used by clients + event SignatureRequested(uint256 signingId, uint256 payment, uint256 previousEntry, uint256 seed, bytes groupPublicKey); + event SignatureSubmitted(uint256 signingId, uint256 requestResponse, bytes requestGroupPubKey, uint256 previousEntry, uint256 seed); - // TODO: Remove requestId once Keep Client DKG is refactored to - // use newEntry as unique id. - event GroupSelectionStarted(uint256 newEntry, uint256 requestId, uint256 seed); + // TODO: Remove signingId once Keep Client DKG is refactored to + // use groupSelectionSeed as unique id. + event GroupSelectionStarted(uint256 groupSelectionSeed, uint256 signingId, uint256 seed); - uint256 internal _groupThreshold; - uint256 internal _groupSize; - uint256 internal _minStake; - address internal _stakingProxy; - address internal _randomBeacon; + uint256 public signingRequestCounter; + uint256 public groupThreshold; + uint256 public groupSize; + uint256 public minimumStake; + address public stakingProxy; + address[] public serviceContracts; - uint256 internal _timeoutInitial; - uint256 internal _timeoutSubmission; - uint256 internal _timeoutChallenge; - uint256 internal _timeDKG; - uint256 internal _resultPublicationBlockStep; - uint256 internal _ticketSubmissionStartBlock; - uint256 internal _randomBeaconValue; + uint256 public ticketInitialSubmissionTimeout; + uint256 public ticketReactiveSubmissionTimeout; + uint256 public ticketChallengeTimeout; + uint256 public timeDKG; + uint256 public resultPublicationBlockStep; + uint256 public ticketSubmissionStartBlock; + uint256 public groupSelectionSeed; - uint256[] internal _tickets; - bytes[] internal _submissions; + uint256[] public tickets; + bytes[] public submissions; - // Store whether DKG result was published for the corresponding requestID. - mapping (uint256 => bool) internal _dkgResultPublished; + // Store whether DKG result was published for the corresponding signingId. + mapping (uint256 => bool) public dkgResultPublished; - bool internal _groupSelectionInProgress; + bool public groupSelectionInProgress; struct Proof { address sender; @@ -58,31 +71,43 @@ contract KeepGroupImplV1 is Ownable { uint256 virtualStakerIndex; } - mapping(uint256 => Proof) internal _proofs; + mapping(uint256 => Proof) public proofs; - // _activeGroupsThreshold is the minimal number of groups that should not + // activeGroupsThreshold is the minimal number of groups that should not // expire to protect the minimal network throughput. // It should be at least 1. - uint256 internal _activeGroupsThreshold; - - // _activeTime is the time in block after which a group expires - uint256 internal _activeTime; + uint256 public activeGroupsThreshold; - // _expiredOffset is pointing to the first active group, it is also the + // groupActiveTime is the time in block after which a group expires + uint256 public groupActiveTime; + + // Timeout in blocks for a relay entry to appear on the chain. Blocks are + // counted from the moment relay request occur. + uint256 public relayRequestTimeout; + + // expiredGroupOffset is pointing to the first active group, it is also the // expired groups counter - uint256 internal _expiredOffset = 0; + uint256 public expiredGroupOffset = 0; struct Group { bytes groupPubKey; uint registrationBlockHeight; } - Group[] internal _groups; - - mapping (bytes => address[]) internal _groupMembers; + Group[] public groups; + + mapping (bytes => address[]) internal groupMembers; - mapping (string => bool) internal _initialized; + bool public initialized; + struct SigningRequest { + uint256 requestId; + uint256 payment; + bytes groupPubKey; + address serviceContract; + } + + mapping(uint256 => SigningRequest) public signingRequests; /** * @dev Checks if submitter is eligible to submit. @@ -91,9 +116,9 @@ contract KeepGroupImplV1 is Ownable { modifier onlyEligibleSubmitter(uint256 submitterMemberIndex) { uint256[] memory selected = selectedTickets(); require(submitterMemberIndex > 0, "Submitter member index must be greater than 0."); - require(_proofs[selected[submitterMemberIndex - 1]].sender == msg.sender, "Submitter member index does not match sender address."); - uint T_init = _ticketSubmissionStartBlock + _timeoutChallenge + _timeDKG; - require(block.number >= (T_init + (submitterMemberIndex-1) * _resultPublicationBlockStep), "Submitter is not eligible to submit at the current block."); + require(proofs[selected[submitterMemberIndex - 1]].sender == msg.sender, "Submitter member index does not match sender address."); + uint T_init = ticketSubmissionStartBlock + ticketChallengeTimeout + timeDKG; + require(block.number >= (T_init + (submitterMemberIndex-1) * resultPublicationBlockStep), "Submitter is not eligible to submit at the current block."); _; } @@ -102,35 +127,99 @@ contract KeepGroupImplV1 is Ownable { */ modifier whenTicketChallengeIsOver() { require( - block.number >= _ticketSubmissionStartBlock + _timeoutChallenge, + block.number >= ticketSubmissionStartBlock + ticketChallengeTimeout, "Ticket submission challenge period must be over." ); _; } /** - * @dev Triggers the selection process of a new candidate group. + * @dev Initialize the contract with a linked Staking proxy contract. + * @param _stakingProxy Address of a staking proxy contract that will be linked to this contract. + * @param _serviceContract Address of a random beacon service contract that will be linked to this contract. + * @param _minimumStake Minimum amount in KEEP that allows KEEP network client to participate in a group. + * @param _groupSize Size of a group in the threshold relay. + * @param _groupThreshold Minimum number of interacting group members needed to produce a relay entry. + * @param _ticketInitialSubmissionTimeout Timeout in blocks after the initial ticket submission is finished. + * @param _ticketReactiveSubmissionTimeout Timeout in blocks after the reactive ticket submission is finished. + * @param _ticketChallengeTimeout Timeout in blocks after the period where tickets can be challenged is finished. + * @param _timeDKG Timeout in blocks after DKG result is complete and ready to be published. + * @param _resultPublicationBlockStep Time in blocks after which member with the given index is eligible + * @param _genesisEntry Initial relay entry to create first group. + * @param _genesisGroupPubKey Group to respond to the initial relay entry request. + * to submit DKG result. + * @param _activeGroupsThreshold is the minimal number of groups that cannot be marked as expired and + * needs to be greater than 0. + * @param _groupActiveTime is the time in block after which a group expires. + * @param _relayRequestTimeout Timeout in blocks for a relay entry to appear on the chain. + * Blocks are counted from the moment relay request occur. */ - function runGroupSelection(uint256 newEntry, uint256 requestId, uint256 seed) public { - require(msg.sender == _randomBeacon); + function initialize( + address _stakingProxy, + address _serviceContract, + uint256 _minimumStake, + uint256 _groupThreshold, + uint256 _groupSize, + uint256 _ticketInitialSubmissionTimeout, + uint256 _ticketReactiveSubmissionTimeout, + uint256 _ticketChallengeTimeout, + uint256 _timeDKG, + uint256 _resultPublicationBlockStep, + uint256 _activeGroupsThreshold, + uint256 _groupActiveTime, + uint256 _relayRequestTimeout, + uint256 _genesisEntry, + bytes memory _genesisGroupPubKey + ) public onlyOwner { + require(!initialized, "Contract is already initialized."); + require(_stakingProxy != address(0x0), "Staking proxy address can't be zero."); + initialized = true; + stakingProxy = _stakingProxy; + serviceContracts.push(_serviceContract); + minimumStake = _minimumStake; + groupSize = _groupSize; + groupThreshold = _groupThreshold; + ticketInitialSubmissionTimeout = _ticketInitialSubmissionTimeout; + ticketReactiveSubmissionTimeout = _ticketReactiveSubmissionTimeout; + ticketChallengeTimeout = _ticketChallengeTimeout; + timeDKG = _timeDKG; + resultPublicationBlockStep = _resultPublicationBlockStep; + activeGroupsThreshold = _activeGroupsThreshold; + groupActiveTime = _groupActiveTime; + relayRequestTimeout = _relayRequestTimeout; + groupSelectionSeed = _genesisEntry; + + // Create initial relay entry request. This will allow relayEntry to be called once + // to trigger the creation of the first group. Requests are removed on successful + // entries so genesis entry can only be called once. + signingRequestCounter++; + signingRequests[signingRequestCounter] = SigningRequest(0, 0, _genesisGroupPubKey, _serviceContract); + } + /** + * @dev Triggers the selection process of a new candidate group. + * @param _groupSelectionSeed Random value that stakers will use to generate their tickets. + * @param _signingId Relay request ID associated with DKG protocol execution. + * @param _seed Random value from the client. It should be a cryptographically generated random value. + */ + function createGroup(uint256 _groupSelectionSeed, uint256 _signingId, uint256 _seed) private { // dkgTimeout is the time after DKG is expected to be complete plus the expected period to submit the result. - uint256 dkgTimeout = _ticketSubmissionStartBlock + _timeoutChallenge + _timeDKG + _groupSize * _resultPublicationBlockStep; + uint256 dkgTimeout = ticketSubmissionStartBlock + ticketChallengeTimeout + timeDKG + groupSize * resultPublicationBlockStep; - if (!_groupSelectionInProgress || block.number > dkgTimeout) { + if (!groupSelectionInProgress || block.number > dkgTimeout) { cleanup(); - _ticketSubmissionStartBlock = block.number; - _randomBeaconValue = newEntry; - _groupSelectionInProgress = true; - emit GroupSelectionStarted(newEntry, requestId, seed); + ticketSubmissionStartBlock = block.number; + groupSelectionSeed = _groupSelectionSeed; + groupSelectionInProgress = true; + emit GroupSelectionStarted(_groupSelectionSeed, _signingId, _seed); } } // TODO: replace with a secure authorization protocol (addressed in RFC 4). - address internal _stakingContract; + address public stakingContract; - function authorizeStakingContract(address stakingContract) public onlyOwner { - _stakingContract = stakingContract; + function authorizeStakingContract(address _stakingContract) public onlyOwner { + stakingContract = _stakingContract; } /** @@ -146,18 +235,18 @@ contract KeepGroupImplV1 is Ownable { uint256 virtualStakerIndex ) public { - if (block.number > _ticketSubmissionStartBlock + _timeoutSubmission) { + if (block.number > ticketSubmissionStartBlock + ticketReactiveSubmissionTimeout) { revert("Ticket submission period is over."); } // Invalid tickets are rejected and their senders penalized. if (!cheapCheck(msg.sender, stakerValue, virtualStakerIndex)) { // TODO: replace with a secure authorization protocol (addressed in RFC 4). - TokenStaking stakingContract = TokenStaking(_stakingContract); - stakingContract.authorizedTransferFrom(msg.sender, address(this), _minStake); + TokenStaking _stakingContract = TokenStaking(stakingContract); + _stakingContract.authorizedTransferFrom(msg.sender, address(this), minimumStake); } else { - _tickets.push(ticketValue); - _proofs[ticketValue] = Proof(msg.sender, stakerValue, virtualStakerIndex); + tickets.push(ticketValue); + proofs[ticketValue] = Proof(msg.sender, stakerValue, virtualStakerIndex); } } @@ -165,7 +254,7 @@ contract KeepGroupImplV1 is Ownable { * @dev Gets submitted tickets in ascending order. */ function orderedTickets() public view returns (uint256[] memory) { - return UintArrayUtils.sort(_tickets); + return UintArrayUtils.sort(tickets); } /** @@ -176,13 +265,13 @@ contract KeepGroupImplV1 is Ownable { uint256[] memory ordered = orderedTickets(); require( - ordered.length >= _groupSize, + ordered.length >= groupSize, "The number of submitted tickets is less than specified group size." ); - uint256[] memory selected = new uint256[](_groupSize); + uint256[] memory selected = new uint256[](groupSize); - for (uint i = 0; i < _groupSize; i++) { + for (uint i = 0; i < groupSize; i++) { selected[i] = ordered[i]; } @@ -198,7 +287,7 @@ contract KeepGroupImplV1 is Ownable { address[] memory participants = new address[](ordered.length); for (uint i = 0; i < ordered.length; i++) { - Proof memory proof = _proofs[ordered[i]]; + Proof memory proof = proofs[ordered[i]]; participants[i] = proof.sender; } @@ -213,14 +302,14 @@ contract KeepGroupImplV1 is Ownable { uint256[] memory ordered = orderedTickets(); require( - ordered.length >= _groupSize, + ordered.length >= groupSize, "The number of submitted tickets is less than specified group size." ); - address[] memory selected = new address[](_groupSize); + address[] memory selected = new address[](groupSize); - for (uint i = 0; i < _groupSize; i++) { - Proof memory proof = _proofs[ordered[i]]; + for (uint i = 0; i < groupSize; i++) { + Proof memory proof = proofs[ordered[i]]; selected[i] = proof.sender; } @@ -232,9 +321,9 @@ contract KeepGroupImplV1 is Ownable { */ function getTicketProof(uint256 ticketValue) public view returns (address sender, uint256 stakerValue, uint256 virtualStakerIndex) { return ( - _proofs[ticketValue].sender, - _proofs[ticketValue].stakerValue, - _proofs[ticketValue].virtualStakerIndex + proofs[ticketValue].sender, + proofs[ticketValue].stakerValue, + proofs[ticketValue].virtualStakerIndex ); } @@ -269,7 +358,7 @@ contract KeepGroupImplV1 is Ownable { uint256 virtualStakerIndex ) public view returns(bool) { bool passedCheapCheck = cheapCheck(staker, stakerValue, virtualStakerIndex); - uint256 expected = uint256(keccak256(abi.encodePacked(_randomBeaconValue, stakerValue, virtualStakerIndex))); + uint256 expected = uint256(keccak256(abi.encodePacked(groupSelectionSeed, stakerValue, virtualStakerIndex))); return passedCheapCheck && ticketValue == expected; } @@ -296,7 +385,7 @@ contract KeepGroupImplV1 is Ownable { /** * @dev Submits result of DKG protocol. It is on-chain part of phase 14 of the protocol. * @param submitterMemberIndex Claimed index of the staker. We pass this for gas efficiency purposes. - * @param requestId Relay request ID associated with DKG protocol execution. + * @param signingId Relay request ID associated with DKG protocol execution. * @param groupPubKey Group public key generated as a result of protocol execution. * @param disqualified bytes representing disqualified group members; 1 at the specific index * means that the member has been disqualified. Indexes reflect positions of members in the @@ -308,7 +397,7 @@ contract KeepGroupImplV1 is Ownable { * @param signingMembersIndexes indices of members corresponding to each signature. */ function submitDkgResult( - uint256 requestId, + uint256 signingId, uint256 submitterMemberIndex, bytes memory groupPubKey, bytes memory disqualified, @@ -318,12 +407,12 @@ contract KeepGroupImplV1 is Ownable { ) public onlyEligibleSubmitter(submitterMemberIndex) { require( - disqualified.length == _groupSize && inactive.length == _groupSize, + disqualified.length == groupSize && inactive.length == groupSize, "Inactive and disqualified bytes arrays don't match the group size." ); require( - !_dkgResultPublished[requestId], + !dkgResultPublished[signingId], "DKG result for this request ID already published." ); @@ -331,19 +420,19 @@ contract KeepGroupImplV1 is Ownable { verifySignatures(signatures, signingMembersIndexes, resultHash); address[] memory members = selectedParticipants(); - for (uint i = 0; i < _groupSize; i++) { + for (uint i = 0; i < groupSize; i++) { if(!_isInactive(inactive, i) && !_isDisqualified(disqualified, i)) { - _groupMembers[groupPubKey].push(members[i]); + groupMembers[groupPubKey].push(members[i]); } } - _groups.push(Group(groupPubKey, block.number)); + groups.push(Group(groupPubKey, block.number)); // TODO: punish/reward logic cleanup(); - _dkgResultPublished[requestId] = true; - emit DkgResultPublishedEvent(requestId, groupPubKey); + dkgResultPublished[signingId] = true; + emit DkgResultPublishedEvent(signingId, groupPubKey); - _groupSelectionInProgress = false; + groupSelectionInProgress = false; } /** @@ -365,7 +454,7 @@ contract KeepGroupImplV1 is Ownable { require(signatures.length >= 65, "Signatures bytes array is too short."); require(signatures.length % 65 == 0, "Signatures in the bytes array should be 65 bytes long."); require(signaturesCount == signingMemberIndices.length, "Number of signatures and indices don't match."); - require(signaturesCount >= _groupThreshold, "Number of signatures is below honest majority threshold."); + require(signaturesCount >= groupThreshold, "Number of signatures is below honest majority threshold."); bytes memory current; // Current signature to be checked. uint256[] memory selected = selectedTickets(); @@ -377,7 +466,7 @@ contract KeepGroupImplV1 is Ownable { address recoveredAddress = resultHash.toEthSignedMessageHash().recover(current); require( - _proofs[selected[signingMemberIndices[i] - 1]].sender == recoveredAddress, + proofs[selected[signingMemberIndices[i] - 1]].sender == recoveredAddress, "Invalid signature. Signer and recovered address at provided index don't match." ); } @@ -389,8 +478,8 @@ contract KeepGroupImplV1 is Ownable { * @dev Checks if DKG protocol result has been already published for the * specific relay request ID associated with the protocol execution. */ - function isDkgResultSubmitted(uint256 requestId) public view returns(bool) { - return _dkgResultPublished[requestId]; + function isDkgResultSubmitted(uint256 signingId) public view returns(bool) { + return dkgResultPublished[signingId]; } @@ -401,62 +490,13 @@ contract KeepGroupImplV1 is Ownable { revert("Can not call contract without explicitly calling a function."); } - /** - * @dev Initialize Keep Group implementation contract with a linked Staking proxy contract. - * @param stakingProxy Address of a staking proxy contract that will be linked to this contract. - * @param randomBeacon Address of a random beacon contract that will be linked to this contract. - * @param minStake Minimum amount in KEEP that allows KEEP network client to participate in a group. - * @param groupSize Size of a group in the threshold relay. - * @param groupThreshold Minimum number of interacting group members needed to produce a relay entry. - * @param timeoutInitial Timeout in blocks after the initial ticket submission is finished. - * @param timeoutSubmission Timeout in blocks after the reactive ticket submission is finished. - * @param timeoutChallenge Timeout in blocks after the period where tickets can be challenged is finished. - * @param timeDKG Timeout in blocks after DKG result is complete and ready to be published. - * @param resultPublicationBlockStep Time in blocks after which member with the given index is eligible - * to submit DKG result. - * @param activeGroupsThreshold is the minimal number of groups that cannot be marked as expired and - * needs to be greater than 0. - * @param activeTime is the time in block after which a group expires. - */ - function initialize( - address stakingProxy, - address randomBeacon, - uint256 minStake, - uint256 groupThreshold, - uint256 groupSize, - uint256 timeoutInitial, - uint256 timeoutSubmission, - uint256 timeoutChallenge, - uint256 timeDKG, - uint256 resultPublicationBlockStep, - uint256 activeGroupsThreshold, - uint256 activeTime - ) public onlyOwner { - require(!initialized(), "Contract is already initialized."); - require(stakingProxy != address(0x0), "Staking proxy address can't be zero."); - require(activeGroupsThreshold > 0, "The minumum number of active groups needs to be more than zero."); - _initialized["KeepGroupImplV1"] = true; - _stakingProxy = stakingProxy; - _randomBeacon = randomBeacon; - _minStake = minStake; - _groupSize = groupSize; - _groupThreshold = groupThreshold; - _timeoutInitial = timeoutInitial; - _timeoutSubmission = timeoutSubmission; - _timeoutChallenge = timeoutChallenge; - _timeDKG = timeDKG; - _resultPublicationBlockStep = resultPublicationBlockStep; - _activeGroupsThreshold = activeGroupsThreshold; - _activeTime = activeTime; - } - /** * @dev Checks that the specified user has enough stake. * @param staker Specifies the identity of the staker. * @return True if staked enough to participate in the group, false otherwise. */ function hasMinimumStake(address staker) public view returns(bool) { - return StakingProxy(_stakingProxy).balanceOf(staker) >= _minStake; + return StakingProxy(stakingProxy).balanceOf(staker) >= minimumStake; } /** @@ -465,72 +505,15 @@ contract KeepGroupImplV1 is Ownable { * @return Number of how many virtual stakers can staker represent. */ function stakingWeight(address staker) public view returns(uint256) { - return StakingProxy(_stakingProxy).balanceOf(staker)/_minStake; + return StakingProxy(stakingProxy).balanceOf(staker)/minimumStake; } /** * @dev Set the minimum amount of KEEP that allows a Keep network client to participate in a group. - * @param minStake Amount in KEEP. - */ - function setMinimumStake(uint256 minStake) public onlyOwner { - _minStake = minStake; - } - - /** - * @dev Get the minimum amount in KEEP that allows KEEP network client to participate in a group. - */ - function minimumStake() public view returns(uint256) { - return _minStake; - } - - /** - * @dev Allows owner to change the groupSize. - */ - function setGroupSize(uint256 groupSize) public onlyOwner { - _groupSize = groupSize; - } - - /** - * @dev ticketInitialSubmissionTimeout is the duration (in blocks) the - * staker has to submit tickets that fall under the natural threshold - * to satisfy the initial ticket timeout (see group selection, phase 2a). - */ - function ticketInitialSubmissionTimeout() public view returns (uint256) { - return _timeoutInitial; - } - - /** - * @dev ticketReactiveSubmissionTimeout is the duration (in blocks) the - * staker has to submit any tickets that did not fall under the natural - * threshold. This final chance to submit tickets is called reactive - * ticket submission (defined in the group selection algorithm, 2b). + * @param _minimumStake Amount in KEEP. */ - function ticketReactiveSubmissionTimeout() public view returns (uint256) { - return _timeoutSubmission; - } - - /** - * @dev ticketChallengeTimeout is the duration (in blocks) the staker - * has to submit any challenges for tickets that fail any checks. - */ - function ticketChallengeTimeout() public view returns (uint256) { - return _timeoutChallenge; - } - - /** - * @dev resultPublicationBlockStep is the duration (in blocks) after which - * member with the given index is eligible to submit DKG result. - */ - function resultPublicationBlockStep() public view returns (uint256) { - return _resultPublicationBlockStep; - } - - /** - * @dev ticketSubmissionStartBlock block number at which current group - * selection started. - */ - function ticketSubmissionStartBlock() public view returns (uint256) { - return _ticketSubmissionStartBlock; + function setMinimumStake(uint256 _minimumStake) public onlyOwner { + minimumStake = _minimumStake; } /** @@ -547,49 +530,14 @@ contract KeepGroupImplV1 is Ownable { */ function naturalThreshold() public view returns (uint256) { uint256 space = 2**256-1; // Space consisting of all possible tickets. - return _groupSize.mul(space.div(tokenSupply().div(_minStake))); - } - - /** - * @dev Checks if this contract is initialized. - */ - function initialized() public view returns (bool) { - return _initialized["KeepGroupImplV1"]; - } - - /** - * @dev Gets size of a group in the threshold relay. - */ - function groupSize() public view returns(uint256) { - return _groupSize; - } - - /** - * @dev Gets number of interacting group members needed to produce a relay entry. - */ - function groupThreshold() public view returns(uint256) { - return _groupThreshold; + return groupSize.mul(space.div(tokenSupply().div(minimumStake))); } /** * @dev Gets number of active groups. */ function numberOfGroups() public view returns(uint256) { - return _groups.length - _expiredOffset; - } - - /** - * @dev Gets the random beacon value for the group selection currently in progress. - */ - function randomBeaconValue() public view returns(uint256) { - return _randomBeaconValue; - } - - /** - * @dev Returns true if the group selection is in progress. - */ - function groupSelectionInProgress() public view returns(bool) { - return _groupSelectionInProgress; + return groups.length - expiredGroupOffset; } /** @@ -601,8 +549,8 @@ contract KeepGroupImplV1 is Ownable { * as active but it does not mean that the group will be immediatelly * considered not as such. */ - function groupActiveTime(Group memory group) internal view returns(uint256) { - return group.registrationBlockHeight + _activeTime; + function groupActiveTimeOf(Group memory group) internal view returns(uint256) { + return group.registrationBlockHeight + groupActiveTime; } /** @@ -611,7 +559,7 @@ contract KeepGroupImplV1 is Ownable { * performing any operations. */ function groupStaleTime(Group memory group) internal view returns(uint256) { - return groupActiveTime(group) + KeepRandomBeaconContract(_randomBeacon).relayRequestTimeout(); + return groupActiveTimeOf(group) + relayRequestTimeout; } /** @@ -624,10 +572,10 @@ contract KeepGroupImplV1 is Ownable { * the past. */ function isStaleGroup(bytes memory groupPubKey) public view returns(bool) { - for (uint i = 0; i < _groups.length; i++) { - if (_groups[i].groupPubKey.equalStorage(groupPubKey)) { - bool isExpired = _expiredOffset > i; - bool isStale = groupStaleTime(_groups[i]) < block.number; + for (uint i = 0; i < groups.length; i++) { + if (groups[i].groupPubKey.equalStorage(groupPubKey)) { + bool isExpired = expiredGroupOffset > i; + bool isStale = groupStaleTime(groups[i]) < block.number; return isExpired && isStale; } } @@ -637,11 +585,11 @@ contract KeepGroupImplV1 is Ownable { /** * @dev Returns public key of a group from active groups using modulo operator. - * @param previousEntry Previous random beacon value. + * @param seed Signing group selection seed. */ - function selectGroup(uint256 previousEntry) public returns(bytes memory) { - uint256 numberOfActiveGroups = _groups.length - _expiredOffset; - uint256 selectedGroup = previousEntry % numberOfActiveGroups; + function selectGroup(uint256 seed) public returns(bytes memory) { + uint256 numberOfActiveGroups = groups.length - expiredGroupOffset; + uint256 selectedGroup = seed % numberOfActiveGroups; /** * We selected a group based on the information about expired groups offset @@ -659,36 +607,36 @@ contract KeepGroupImplV1 is Ownable { * starting from the previous value of expired groups offset since we can * mark expired groups in batches, in a fewer number of steps. */ - if (numberOfActiveGroups > _activeGroupsThreshold) { - while (groupActiveTime(_groups[_expiredOffset + selectedGroup]) < block.number) { + if (numberOfActiveGroups > activeGroupsThreshold) { + while (groupActiveTimeOf(groups[expiredGroupOffset + selectedGroup]) < block.number) { /** * We do -1 to see how many groups are available after the potential removal. * For example: - * _groups = [EEEAAAA] + * groups = [EEEAAAA] * - assuming selectedGroup = 0, then we'll have 4-0-1=3 groups after the removal: [EEEEAAA] * - assuming selectedGroup = 1, then we'll have 4-1-1=2 groups after the removal: [EEEEEAA] * - assuming selectedGroup = 2, then, we'll have 4-2-1=1 groups after the removal: [EEEEEEA] * - assuming selectedGroup = 3, then, we'll have 4-3-1=0 groups after the removal: [EEEEEEE] */ - if (numberOfActiveGroups - selectedGroup - 1 > _activeGroupsThreshold) { + if (numberOfActiveGroups - selectedGroup - 1 > activeGroupsThreshold) { selectedGroup++; - _expiredOffset += selectedGroup; + expiredGroupOffset += selectedGroup; numberOfActiveGroups -= selectedGroup; - selectedGroup = previousEntry % numberOfActiveGroups; + selectedGroup = seed % numberOfActiveGroups; } else { - /* Number of groups that did not expire is less or equal _activeGroupsThreshold - * and we have more groups than _activeGroupsThreshold (including those expired) groups. - * Hence, we maintain the minimum _activeGroupsThreshold of active groups and + /* Number of groups that did not expire is less or equal activeGroupsThreshold + * and we have more groups than activeGroupsThreshold (including those expired) groups. + * Hence, we maintain the minimum activeGroupsThreshold of active groups and * do not let any other groups to expire */ - _expiredOffset = _groups.length - _activeGroupsThreshold; - numberOfActiveGroups = _activeGroupsThreshold; - selectedGroup = previousEntry % numberOfActiveGroups; + expiredGroupOffset = groups.length - activeGroupsThreshold; + numberOfActiveGroups = activeGroupsThreshold; + selectedGroup = seed % numberOfActiveGroups; break; } } } - return _groups[_expiredOffset + selectedGroup].groupPubKey; + return groups[expiredGroupOffset + selectedGroup].groupPubKey; } /** @@ -703,13 +651,61 @@ contract KeepGroupImplV1 is Ownable { */ function cleanup() private { - for (uint i = 0; i < _tickets.length; i++) { - delete _proofs[_tickets[i]]; + for (uint i = 0; i < tickets.length; i++) { + delete proofs[tickets[i]]; } - delete _tickets; + delete tickets; // TODO: cleanup DkgResults } + /** + * @dev Creates a request to generate a new relay entry, which will include a + * random number (by signing the previous entry's random number). + * @param requestId Request Id trackable by service contract. + * @param seed Initial seed random value from the client. It should be a cryptographically generated random value. + * @param previousEntry Previous relay entry that is used to select a signing group for this request. + */ + function sign(uint256 requestId, uint256 seed, uint256 previousEntry) public payable { + + require( + serviceContracts.contains(msg.sender), + "Only authorized service contract can request relay entry." + ); + + require( + numberOfGroups() > 0, + "At least one group needed to serve the request." + ); + + bytes memory groupPubKey = selectGroup(previousEntry); + + signingRequestCounter++; + + signingRequests[signingRequestCounter] = SigningRequest(requestId, msg.value, groupPubKey, msg.sender); + + emit SignatureRequested(signingRequestCounter, msg.value, previousEntry, seed, groupPubKey); + } + + /** + * @dev Creates a new relay entry and stores the associated data on the chain. + * @param _signingId The request that started this generation - to tie the results back to the request. + * @param _groupSignature The generated random number. + * @param _groupPubKey Public key of the group that generated the threshold signature. + */ + function relayEntry(uint256 _signingId, uint256 _groupSignature, bytes memory _groupPubKey, uint256 _previousEntry, uint256 _seed) public { + + require(signingRequests[_signingId].groupPubKey.equalStorage(_groupPubKey), "Provided group was not selected to produce entry for this request."); + require(BLS.verify(_groupPubKey, abi.encodePacked(_previousEntry, _seed), bytes32(_groupSignature)), "Group signature failed to pass BLS verification."); + + address serviceContract = signingRequests[_signingId].serviceContract; + uint256 requestId = signingRequests[_signingId].requestId; + delete signingRequests[_signingId]; + + emit SignatureSubmitted(_signingId, _groupSignature, _groupPubKey, _previousEntry, _seed); + + ServiceContract(serviceContract).entryCreated(requestId, _groupSignature); + createGroup(_groupSignature, _signingId, _seed); + } } diff --git a/contracts/solidity/contracts/KeepGroupImplV1Stub.sol b/contracts/solidity/contracts/KeepRandomBeaconOperatorStub.sol similarity index 54% rename from contracts/solidity/contracts/KeepGroupImplV1Stub.sol rename to contracts/solidity/contracts/KeepRandomBeaconOperatorStub.sol index 825371202f..131af05428 100644 --- a/contracts/solidity/contracts/KeepGroupImplV1Stub.sol +++ b/contracts/solidity/contracts/KeepRandomBeaconOperatorStub.sol @@ -1,19 +1,30 @@ pragma solidity ^0.5.4; -import "./KeepGroupImplV1.sol"; +import "./KeepRandomBeaconOperator.sol"; + +/** + * @title KeepRandomBeaconOperatorStub + * @dev A simplified Random Beacon operator contract to help local development. + */ +contract KeepRandomBeaconOperatorStub is KeepRandomBeaconOperator { + + /** + * @dev Stub method to authorize service contract to help local development. + */ + function authorizeServiceContract(address _serviceContract) public { + serviceContracts.push(_serviceContract); + } -contract KeepGroupImplV1Stub is KeepGroupImplV1 { - /** * @dev Adds a new group based on groupPublicKey. * @param groupPublicKey is the identifier of the newly created group. */ function registerNewGroup(bytes memory groupPublicKey) public { - _groups.push(Group(groupPublicKey, block.number)); + groups.push(Group(groupPublicKey, block.number)); address[] memory members = orderedParticipants(); if (members.length > 0) { - for (uint i = 0; i < _groupSize; i++) { - _groupMembers[groupPublicKey].push(members[i]); + for (uint i = 0; i < groupSize; i++) { + groupMembers[groupPublicKey].push(members[i]); } } } @@ -23,7 +34,7 @@ contract KeepGroupImplV1Stub is KeepGroupImplV1 { * @param groupIndex is the index of the queried group. */ function getGroupRegistrationBlockHeight(uint256 groupIndex) public view returns(uint256) { - return _groups[groupIndex].registrationBlockHeight; + return groups[groupIndex].registrationBlockHeight; } /** @@ -31,13 +42,7 @@ contract KeepGroupImplV1Stub is KeepGroupImplV1 { * @param groupIndex is the index of the queried group. */ function getGroupPublicKey(uint256 groupIndex) public view returns(bytes memory) { - return _groups[groupIndex].groupPubKey; + return groups[groupIndex].groupPubKey; } - /** - * @dev Gets the value of expired offset. - */ - function getExpiredOffset() public view returns(uint256) { - return _expiredOffset; - } -} \ No newline at end of file +} diff --git a/contracts/solidity/contracts/KeepRandomBeacon.sol b/contracts/solidity/contracts/KeepRandomBeaconService.sol similarity index 97% rename from contracts/solidity/contracts/KeepRandomBeacon.sol rename to contracts/solidity/contracts/KeepRandomBeaconService.sol index 851099cfed..22e624b61d 100644 --- a/contracts/solidity/contracts/KeepRandomBeacon.sol +++ b/contracts/solidity/contracts/KeepRandomBeaconService.sol @@ -3,13 +3,13 @@ pragma solidity ^0.5.4; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; /** - * @title Keep Random Beacon + * @title Keep Random Beacon service * @dev A proxy contract to provide upgradable Random Beacon functionality. * Owner can do upgrades by updating implementation state variable to * the address of the upgraded contract. All calls to this proxy contract * are delegated to the implementation contract. */ -contract KeepRandomBeacon is Ownable { +contract KeepRandomBeaconService is Ownable { // Storage position of the address of the current implementation bytes32 private constant implementationPosition = keccak256("network.keep.randombeacon.proxy.implementation"); diff --git a/contracts/solidity/contracts/KeepRandomBeaconServiceImplV1.sol b/contracts/solidity/contracts/KeepRandomBeaconServiceImplV1.sol new file mode 100644 index 0000000000..57471323d3 --- /dev/null +++ b/contracts/solidity/contracts/KeepRandomBeaconServiceImplV1.sol @@ -0,0 +1,177 @@ +pragma solidity ^0.5.4; + +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "./utils/AddressArrayUtils.sol"; +import "./DelayedWithdrawal.sol"; + + +interface OperatorContract { + function sign(uint256 requestId, uint256 seed, uint256 previousEntry) payable external; + // TODO: Add createGroup() when it's implemented on Operator contract. +} + + +/** + * @title KeepRandomBeaconServiceImplV1 + * @dev Initial version of service contract that works under Keep Random + * Beacon proxy and allows upgradability. The purpose of the contract is to have + * up-to-date logic for threshold random number generation. Updated contracts + * must inherit from this contract and have to be initialized under updated version name + */ +contract KeepRandomBeaconServiceImplV1 is Ownable, DelayedWithdrawal { + + using AddressArrayUtils for address[]; + + // These are the public events that are used by clients + event RelayEntryRequested(uint256 requestId); + event RelayEntryGenerated(uint256 requestId, uint256 entry); + + uint256 internal _minPayment; + uint256 internal _previousEntry; + + // Each service contract tracks its own requests and these are independent + // from operator contracts which track signing requests instead. + uint256 internal _requestCounter; + + struct Callback { + address callbackContract; + string callbackMethod; + } + + mapping(uint256 => Callback) internal _callbacks; + + address[] internal _operatorContracts; + + // Mapping to store new implementation versions that inherit from this contract. + mapping (string => bool) internal _initialized; + + /** + * @dev Prevent receiving ether without explicitly calling a function. + */ + function() external payable { + revert("Can not call contract without explicitly calling a function."); + } + + /** + * @dev Initialize Keep Random Beacon service contract implementation. + * @param minPayment Minimum amount of ether (in wei) that allows anyone to request a random number. + * @param withdrawalDelay Delay before the owner can withdraw ether from this contract. + * @param operatorContract Operator contract linked to this contract. + */ + function initialize(uint256 minPayment, uint256 withdrawalDelay, address operatorContract) + public + onlyOwner + { + require(!initialized(), "Contract is already initialized."); + _minPayment = minPayment; + _initialized["KeepRandomBeaconServiceImplV1"] = true; + _withdrawalDelay = withdrawalDelay; + _pendingWithdrawal = 0; + _operatorContracts.push(operatorContract); + } + + /** + * @dev Checks if this contract is initialized. + */ + function initialized() public view returns (bool) { + return _initialized["KeepRandomBeaconServiceImplV1"]; + } + + /** + * @dev Selects an operator contract from the available list using modulo operation + * with previous entry weighted by the number of active groups on each operator contract. + * @return Address of operator contract. + */ + function selectOperatorContract() public view returns (address) { + // TODO: Implement logic + return _operatorContracts[0]; + } + + /** + * @dev Creates a request to generate a new relay entry, which will include a + * random number (by signing the previous entry's random number). + * @param seed Initial seed random value from the client. It should be a cryptographically generated random value. + * @return An uint256 representing uniquely generated entry Id. + */ + function requestRelayEntry(uint256 seed) public payable returns (uint256) { + return requestRelayEntry(seed, address(0), ""); + } + + /** + * @dev Creates a request to generate a new relay entry, which will include a + * random number (by signing the previous entry's random number). + * @param seed Initial seed random value from the client. It should be a cryptographically generated random value. + * @param callbackContract Callback contract address. Callback is called once a new relay entry has been generated. + * @param callbackMethod Callback contract method signature. String representation of your method with a single + * uint256 input parameter i.e. "relayEntryCallback(uint256)". + * @return An uint256 representing uniquely generated relay request ID. It is also returned as part of the event. + */ + function requestRelayEntry(uint256 seed, address callbackContract, string memory callbackMethod) public payable returns (uint256) { + require( + msg.value >= _minPayment, + "Payment is less than required minimum." + ); + + _requestCounter++; + + // TODO: Figure out pricing, if we decide to pass payment to the backed use this instead: + // OperatorContract(selectOperatorContract()).sign.value(msg.value)(_requestCounter, seed, _previousEntry); + OperatorContract(selectOperatorContract()).sign(_requestCounter, seed, _previousEntry); + + if (callbackContract != address(0)) { + _callbacks[_requestCounter] = Callback(callbackContract, callbackMethod); + } + + emit RelayEntryRequested(_requestCounter); + return _requestCounter; + } + + /** + * @dev Store valid entry returned by operator contract and call customer specified callback if required. + * @param requestId Request id tracked internally by this contract. + * @param entry The generated random number. + */ + function entryCreated(uint256 requestId, uint256 entry) public { + require( + _operatorContracts.contains(msg.sender), + "Only authorized operator contract can call relay entry." + ); + + _previousEntry = entry; + emit RelayEntryGenerated(requestId, entry); + + if (_callbacks[requestId].callbackContract != address(0)) { + _callbacks[requestId].callbackContract.call(abi.encodeWithSignature(_callbacks[requestId].callbackMethod, entry)); + delete _callbacks[requestId]; + } + } + + /** + * @dev Set the minimum payment that is required before a relay entry occurs. + * @param minPayment is the value in wei that is required to be payed for the process to start. + */ + function setMinimumPayment(uint256 minPayment) public onlyOwner { + _minPayment = minPayment; + } + + /** + * @dev Get the minimum payment that is required before a relay entry occurs. + */ + function minimumPayment() public view returns(uint256) { + return _minPayment; + } + + /** + * @dev Gets the previous relay entry value. + */ + function previousEntry() public view returns(uint256) { + return _previousEntry; + } + + /** + * @dev Gets version of the current implementation. + */ + function version() public pure returns (string memory) { + return "V1"; + } +} diff --git a/contracts/solidity/contracts/utils/AltBn128.sol b/contracts/solidity/contracts/cryptography/AltBn128.sol similarity index 99% rename from contracts/solidity/contracts/utils/AltBn128.sol rename to contracts/solidity/contracts/cryptography/AltBn128.sol index 54d4598af8..7c32f1206a 100644 --- a/contracts/solidity/contracts/utils/AltBn128.sol +++ b/contracts/solidity/contracts/cryptography/AltBn128.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.4; -import "./ModUtils.sol"; +import "../utils/ModUtils.sol"; /** * @title Operations on alt_bn128 diff --git a/contracts/solidity/contracts/BLS.sol b/contracts/solidity/contracts/cryptography/BLS.sol similarity index 96% rename from contracts/solidity/contracts/BLS.sol rename to contracts/solidity/contracts/cryptography/BLS.sol index 30ede915aa..bf1841d038 100644 --- a/contracts/solidity/contracts/BLS.sol +++ b/contracts/solidity/contracts/cryptography/BLS.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.4; -import "./utils/AltBn128.sol"; +import "./AltBn128.sol"; /** diff --git a/contracts/solidity/contracts/examples/KeepRandomBeaconUpgradeExample.sol b/contracts/solidity/contracts/examples/KeepRandomBeaconServiceUpgradeExample.sol similarity index 68% rename from contracts/solidity/contracts/examples/KeepRandomBeaconUpgradeExample.sol rename to contracts/solidity/contracts/examples/KeepRandomBeaconServiceUpgradeExample.sol index bdc3a5aaa4..9890c6929c 100644 --- a/contracts/solidity/contracts/examples/KeepRandomBeaconUpgradeExample.sol +++ b/contracts/solidity/contracts/examples/KeepRandomBeaconServiceUpgradeExample.sol @@ -1,14 +1,14 @@ pragma solidity ^0.5.4; -import "../KeepRandomBeaconImplV1.sol"; +import "../KeepRandomBeaconServiceImplV1.sol"; /** - * @title KeepRandomBeaconUpgradeExample + * @title KeepRandomBeaconServiceUpgradeExample * @dev Example version of a new implementation contract to test upgradability * under Keep Random Beacon proxy. */ -contract KeepRandomBeaconUpgradeExample is KeepRandomBeaconImplV1 { +contract KeepRandomBeaconServiceUpgradeExample is KeepRandomBeaconServiceImplV1 { uint256 internal _newVar; @@ -18,16 +18,13 @@ contract KeepRandomBeaconUpgradeExample is KeepRandomBeaconImplV1 { * >Functions can be overridden by another function with the same name and the * same number/types of inputs. */ - function initialize( - uint256 _minPayment, uint256 _withdrawalDelay, uint256 _genesisEntry, - bytes memory _genesisGroupPubKey, address _groupContract, uint256 _relayRequestTimeout) + function initialize(uint256 _minPayment, uint256 withdrawalDelay, address _operatorContract) public onlyOwner { - super.initialize(_minPayment, _withdrawalDelay, _genesisEntry, _genesisGroupPubKey, _groupContract, - _relayRequestTimeout); + withdrawalDelay; // Silence unused var + super.initialize(_minPayment, _withdrawalDelay, _operatorContract); _initialized["KeepRandomBeaconImplV2"] = true; - // Example of adding new data to the existing storage. _newVar = 1234; } diff --git a/contracts/solidity/migrations/2_deploy_contracts.js b/contracts/solidity/migrations/2_deploy_contracts.js index c5ecd0e1a9..3684f7866c 100644 --- a/contracts/solidity/migrations/2_deploy_contracts.js +++ b/contracts/solidity/migrations/2_deploy_contracts.js @@ -1,15 +1,14 @@ const KeepToken = artifacts.require("./KeepToken.sol"); const ModUtils = artifacts.require("./utils/ModUtils.sol"); -const AltBn128 = artifacts.require("./AltBn128.sol"); -const BLS = artifacts.require("./BLS.sol"); +const AltBn128 = artifacts.require("./cryptography/AltBn128.sol"); +const BLS = artifacts.require("./cryptography/BLS.sol"); const StakingProxy = artifacts.require("./StakingProxy.sol"); const TokenStaking = artifacts.require("./TokenStaking.sol"); const TokenGrant = artifacts.require("./TokenGrant.sol"); -const KeepRandomBeaconImplV1 = artifacts.require("./KeepRandomBeaconImplV1.sol"); -const KeepRandomBeaconUpgradeExample = artifacts.require("./KeepRandomBeaconUpgradeExample.sol"); -const KeepGroupImplV1 = artifacts.require("./KeepGroupImplV1.sol"); -const KeepGroup = artifacts.require("./KeepGroup.sol"); -const KeepRandomBeacon = artifacts.require("./KeepRandomBeacon.sol"); +const KeepRandomBeaconService = artifacts.require("./KeepRandomBeaconService.sol"); +const KeepRandomBeaconServiceImplV1 = artifacts.require("./KeepRandomBeaconServiceImplV1.sol"); +const KeepRandomBeaconOperator = artifacts.require("./KeepRandomBeaconOperator.sol"); +const KeepRandomBeaconOperatorStub = artifacts.require("./KeepRandomBeaconOperatorStub.sol"); const withdrawalDelay = 86400; // 1 day const minPayment = 1; @@ -43,27 +42,27 @@ module.exports = async function(deployer) { await deployer.deploy(StakingProxy); await deployer.deploy(TokenStaking, KeepToken.address, StakingProxy.address, withdrawalDelay); await deployer.deploy(TokenGrant, KeepToken.address, StakingProxy.address, withdrawalDelay); - await deployer.link(BLS, KeepRandomBeaconImplV1); - await deployer.link(BLS, KeepRandomBeaconUpgradeExample); - await deployer.deploy(KeepRandomBeaconImplV1); - await deployer.deploy(KeepRandomBeacon, KeepRandomBeaconImplV1.address); - await deployer.deploy(KeepGroupImplV1); - await deployer.deploy(KeepGroup, KeepGroupImplV1.address); + await deployer.link(BLS, KeepRandomBeaconOperator); + await deployer.link(BLS, KeepRandomBeaconOperatorStub); + deployer.deploy(KeepRandomBeaconOperator); + await deployer.deploy(KeepRandomBeaconServiceImplV1); + await deployer.deploy(KeepRandomBeaconService, KeepRandomBeaconServiceImplV1.address); + + const keepRandomBeaconService = await KeepRandomBeaconServiceImplV1.at(KeepRandomBeaconService.address); + const keepRandomBeaconOperator = await KeepRandomBeaconOperator.deployed(); - const keepRandomBeacon = await KeepRandomBeaconImplV1.at(KeepRandomBeacon.address); - const keepGroup = await KeepGroupImplV1.at(KeepGroup.address); - await keepGroup.initialize( - StakingProxy.address, KeepRandomBeacon.address, minStake, groupThreshold, - groupSize, timeoutInitial, timeoutSubmission, timeoutChallenge, timeDKG, - resultPublicationBlockStep, activeGroupsThreshold, groupActiveTime - ); // Initialize contract genesis entry value and genesis group defined in Go client submitGenesisRelayEntry() - await keepRandomBeacon.initialize( - minPayment, - withdrawalDelay, + keepRandomBeaconOperator.initialize( + StakingProxy.address, KeepRandomBeaconService.address, minStake, groupThreshold, groupSize, + timeoutInitial, timeoutSubmission, timeoutChallenge, timeDKG, resultPublicationBlockStep, + activeGroupsThreshold, groupActiveTime, relayRequestTimeout, web3.utils.toBN('31415926535897932384626433832795028841971693993751058209749445923078164062862'), "0x1f1954b33144db2b5c90da089e8bde287ec7089d5d6433f3b6becaefdb678b1b2a9de38d14bef2cf9afc3c698a4211fa7ada7b4f036a2dfef0dc122b423259d0", - KeepGroup.address, - relayRequestTimeout + ); + + keepRandomBeaconService.initialize( + minPayment, + withdrawalDelay, + keepRandomBeaconOperator.address ); }; diff --git a/contracts/solidity/package.json b/contracts/solidity/package.json index 0dee8103b4..61b6d350dd 100644 --- a/contracts/solidity/package.json +++ b/contracts/solidity/package.json @@ -8,7 +8,7 @@ }, "scripts": { "truffle": "truffle", - "test": "rm -rf build && truffle test", + "test": "rm -rf build && truffle compile && truffle test", "demo": "truffle migrate --reset && truffle exec ./scripts/demo.js" }, "author": "Matt Luongo ", diff --git a/contracts/solidity/scripts/genesis.js b/contracts/solidity/scripts/genesis.js index a6faa1ce73..91ec3fddb8 100644 --- a/contracts/solidity/scripts/genesis.js +++ b/contracts/solidity/scripts/genesis.js @@ -1,5 +1,4 @@ -const KeepRandomBeaconProxy = artifacts.require('KeepRandomBeacon.sol') -const KeepRandomBeacon = artifacts.require("KeepRandomBeaconImplV1") +const KeepRandomBeaconOperator = artifacts.require('KeepRandomBeaconOperator.sol') // The data below should match genesis relay request data defined on contract // initialization i.e. in 2_deploy_contracts.js. Successful genesis entry will @@ -19,8 +18,7 @@ const groupSignature = web3.utils.toBN('1092010247678959141494937778210470713041 module.exports = async function () { - const keepRandomBeaconProxy = await KeepRandomBeaconProxy.deployed() - let contract = await KeepRandomBeacon.at(keepRandomBeaconProxy.address) + const contract = await KeepRandomBeaconOperator.deployed() try { await contract.relayEntry(1, groupSignature, groupPubKey, previousEntry, seed) console.log('Genesis entry successfully submitted.') diff --git a/contracts/solidity/scripts/initiate-unstake.js b/contracts/solidity/scripts/initiate-unstake.js index 93c7edf096..7c820a9608 100644 --- a/contracts/solidity/scripts/initiate-unstake.js +++ b/contracts/solidity/scripts/initiate-unstake.js @@ -1,6 +1,6 @@ -const TokenStaking = artifacts.require("TokenStaking"); +const TokenStaking = artifacts.require("TokenStaking.sol"); const KeepGroupProxy = artifacts.require('KeepGroup.sol'); -const KeepGroup = artifacts.require("KeepGroupImplV1"); +const KeepGroup = artifacts.require("KeepGroupImplV1.sol"); function getAccounts() { return new Promise((resolve, reject) => { diff --git a/contracts/solidity/scripts/query-beacon.js b/contracts/solidity/scripts/query-beacon.js index 6a7a3e7283..2102c6efa6 100644 --- a/contracts/solidity/scripts/query-beacon.js +++ b/contracts/solidity/scripts/query-beacon.js @@ -1,23 +1,21 @@ -const KeepGroupProxy = artifacts.require('KeepGroup.sol'); -const KeepRandomBeaconProxy = artifacts.require('KeepRandomBeacon.sol'); -const KeepGroup = artifacts.require("KeepGroupImplV1"); -const KeepRandomBeacon = artifacts.require("KeepRandomBeaconImplV1"); +const KeepRandomBeaconService = artifacts.require('KeepRandomBeaconService.sol'); +const KeepRandomBeaconOperator = artifacts.require("KeepRandomBeaconOperator.sol"); +const KeepRandomBeaconServiceImplV1 = artifacts.require("KeepRandomBeaconServiceImplV1.sol"); module.exports = async function () { - const keepRandomBeaconProxy = await KeepRandomBeaconProxy.deployed(); - const keepGroupProxy = await KeepGroupProxy.deployed(); + const keepRandomBeaconServiceProxy = await KeepRandomBeaconService.deployed(); + const keepRandomBeaconOperator = await KeepRandomBeaconOperator.deployed(); async function printLastRelayEntry() { - let contractRef = await KeepRandomBeacon.at(keepRandomBeaconProxy.address); + let contractRef = await KeepRandomBeaconServiceImplV1.at(keepRandomBeaconServiceProxy.address); let lastEntry = await contractRef.previousEntry(); console.log('Last relay entry: ' + lastEntry.toString()); } async function printNumberOfGroups() { - let contractRef = await KeepGroup.at(keepGroupProxy.address); - let groupsCount = await contractRef.numberOfGroups(); + let groupsCount = await keepRandomBeaconOperator.numberOfGroups(); console.log('Number of active groups: ' + groupsCount.toString()); } diff --git a/contracts/solidity/scripts/request-relay-entry-with-callback.js b/contracts/solidity/scripts/request-relay-entry-with-callback.js index 935daafe43..30876a6e3a 100644 --- a/contracts/solidity/scripts/request-relay-entry-with-callback.js +++ b/contracts/solidity/scripts/request-relay-entry-with-callback.js @@ -1,6 +1,6 @@ const crypto = require("crypto") -const KeepRandomBeacon = artifacts.require("KeepRandomBeaconImplV1") -const KeepRandomBeaconProxy = artifacts.require('KeepRandomBeacon.sol') +const KeepRandomBeaconServiceImplV1 = artifacts.require("KeepRandomBeaconServiceImplV1.sol"); +const KeepRandomBeaconService = artifacts.require('KeepRandomBeaconService.sol'); // Example usage: // truffle exec ./scripts/request-relay-entry-with-callback.js yourContractAddress "callbackMethodName" payment @@ -8,12 +8,12 @@ const KeepRandomBeaconProxy = artifacts.require('KeepRandomBeacon.sol') module.exports = async function() { - const keepRandomBeaconProxy = await KeepRandomBeaconProxy.deployed() - const contractInstance = await KeepRandomBeacon.at(keepRandomBeaconProxy.address) + const keepRandomBeaconService = await KeepRandomBeaconService.deployed() + const contractInstance = await KeepRandomBeaconServiceImplV1.at(keepRandomBeaconService.address) try { let tx = await contractInstance.methods['requestRelayEntry(uint256,address,string)'](crypto.randomBytes(32), process.argv[4], process.argv[5], {value: process.argv[6]}) - console.log('Successfully requested relay entry with a callback. RequestId =', tx.logs[0].args.requestID.toString()) + console.log('Successfully requested relay entry with a callback. RequestId =', tx.logs[0].args.requestId.toString()) console.log( '\n---Transaction Summary---' + '\n' + 'From:' + tx.receipt.from + '\n' + diff --git a/contracts/solidity/scripts/request-relay-entry.js b/contracts/solidity/scripts/request-relay-entry.js index dec90e3fab..83818390af 100644 --- a/contracts/solidity/scripts/request-relay-entry.js +++ b/contracts/solidity/scripts/request-relay-entry.js @@ -1,15 +1,15 @@ const crypto = require("crypto") -const KeepRandomBeacon = artifacts.require("KeepRandomBeaconImplV1") -const KeepRandomBeaconProxy = artifacts.require('KeepRandomBeacon.sol') +const KeepRandomBeaconServiceImplV1 = artifacts.require("KeepRandomBeaconServiceImplV1.sol"); +const KeepRandomBeaconService = artifacts.require('KeepRandomBeaconService.sol'); module.exports = async function() { - const keepRandomBeaconProxy = await KeepRandomBeaconProxy.deployed() - const contractInstance = await KeepRandomBeacon.at(keepRandomBeaconProxy.address) + const keepRandomBeaconService = await KeepRandomBeaconService.deployed() + const contractInstance = await KeepRandomBeaconServiceImplV1.at(keepRandomBeaconService.address) try { let tx = await contractInstance.requestRelayEntry(crypto.randomBytes(32), {value: 2}) - console.log('Successfully requested relay entry with RequestId =', tx.logs[0].args.requestID.toString()) + console.log('Successfully requested relay entry with RequestId =', tx.logs[0].args.requestId.toString()) console.log( '\n---Transaction Summary---' + '\n' + 'From:' + tx.receipt.from + '\n' + diff --git a/contracts/solidity/test/TestAltBn128.sol b/contracts/solidity/test/TestAltBn128.sol index abdc29e92d..78a92fc602 100644 --- a/contracts/solidity/test/TestAltBn128.sol +++ b/contracts/solidity/test/TestAltBn128.sol @@ -1,7 +1,8 @@ pragma solidity ^0.5.4; import "truffle/Assert.sol"; -import "../contracts/utils/AltBn128.sol"; +import "../contracts/utils/ModUtils.sol"; +import "../contracts/cryptography/AltBn128.sol"; contract TestAltBn128 { diff --git a/contracts/solidity/test/TestBLS.js b/contracts/solidity/test/TestBLS.js index ab61636c2a..954d97c9d1 100644 --- a/contracts/solidity/test/TestBLS.js +++ b/contracts/solidity/test/TestBLS.js @@ -1,9 +1,9 @@ -const BLS = artifacts.require('./BLS.sol'); +const BLS = artifacts.require('./cryptography/BLS.sol'); contract('TestBLS', function() { let bls; - beforeEach(async () => { + before(async () => { bls = await BLS.new(); }); @@ -15,7 +15,7 @@ contract('TestBLS', function() { "0x48656c6c6f21", "0x884b130ed81751b63d0f5882483d4a24a7640bdf371f23b78dbeb520c84e3a85" ) - assert.equal(result, true, "Should be able to verify valid BLS signature."); + assert.isTrue(result, "Should be able to verify valid BLS signature."); }); it("should be able to verify BLS aggregated signature", async function() { @@ -26,7 +26,7 @@ contract('TestBLS', function() { "0x48656c6c6f21", "0xafd0185522d03e015e2165ad450af72a3b601673e8b41bc7f07014aa80892b24" ) - assert.equal(result, true, "Should be able to verify valid BLS signature."); + assert.isTrue(result, "Should be able to verify valid BLS signature."); }); it("should fail to verify non valid BLS signature", async function() { @@ -36,7 +36,7 @@ contract('TestBLS', function() { "0x48656c6c6f21", "0x884b130ed81751b63d0f5882483d4a24a7640bdf371f23b78dbeb520c84e3a85" ) - assert.equal(result, false, "Should return false for failed verification."); + assert.isFalse(result, "Should return false for failed verification."); }); it("should fail to verify BLS signature without valid message", async function() { @@ -46,7 +46,7 @@ contract('TestBLS', function() { "0x123456789", "0xafd0185522d03e015e2165ad450af72a3b601673e8b41bc7f07014aa80892b24" ) - assert.equal(result, false, "Should return false for failed verification."); + assert.isFalse(result, "Should return false for failed verification."); }); it("should fail to verify BLS signature without valid public key", async function() { @@ -56,6 +56,6 @@ contract('TestBLS', function() { "0x48656c6c6f21", "0xafd0185522d03e015e2165ad450af72a3b601673e8b41bc7f07014aa80892b24" ) - assert.equal(result, false, "Should return false for failed verification."); + assert.isFalse(result, "Should return false for failed verification."); }); }); diff --git a/contracts/solidity/test/TestKeepGroupSelection.js b/contracts/solidity/test/TestKeepGroupSelection.js deleted file mode 100644 index 475536e148..0000000000 --- a/contracts/solidity/test/TestKeepGroupSelection.js +++ /dev/null @@ -1,189 +0,0 @@ -import { duration } from './helpers/increaseTime'; -import exceptThrow from './helpers/expectThrow'; -import mineBlocks from './helpers/mineBlocks'; -import generateTickets from './helpers/generateTickets'; -import {bls} from './helpers/data'; -const KeepToken = artifacts.require('./KeepToken.sol'); -const StakingProxy = artifacts.require('./StakingProxy.sol'); -const TokenStaking = artifacts.require('./TokenStaking.sol'); -const KeepRandomBeaconProxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeaconImplV1 = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const KeepGroupProxy = artifacts.require('./KeepGroup.sol'); -const KeepGroupImplV1 = artifacts.require('./KeepGroupImplV1Stub.sol'); - - -contract('TestKeepGroupSelection', function(accounts) { - - const relayRequestTimeout = 10; - - let token, stakingProxy, stakingContract, minimumStake, groupThreshold, groupSize, - randomBeaconValue, - timeoutInitial, timeoutSubmission, timeoutChallenge, timeDKG, - resultPublicationBlockStep, groupActiveTime, activeGroupsThreshold, - keepRandomBeaconImplV1, keepRandomBeaconProxy, keepRandomBeaconImplViaProxy, - keepGroupImplV1, keepGroupProxy, keepGroupImplViaProxy, - owner = accounts[0], magpie = accounts[1], signature, delegation, - operator1 = accounts[2], tickets1, - operator2 = accounts[3], tickets2, - operator3 = accounts[4], tickets3, - operator4 = accounts[5], tickets4; - - beforeEach(async () => { - token = await KeepToken.new(); - - // Initialize staking contract under proxy - stakingProxy = await StakingProxy.new(); - stakingContract = await TokenStaking.new(token.address, stakingProxy.address, duration.days(30)); - await stakingProxy.authorizeContract(stakingContract.address, {from: owner}) - - // Initialize Keep Random Beacon contract - keepRandomBeaconImplV1 = await KeepRandomBeaconImplV1.new(); - keepRandomBeaconProxy = await KeepRandomBeaconProxy.new(keepRandomBeaconImplV1.address); - keepRandomBeaconImplViaProxy = await KeepRandomBeaconImplV1.at(keepRandomBeaconProxy.address); - - // Initialize Keep Group contract - minimumStake = 200000; - groupThreshold = 15; - groupSize = 20; - timeoutInitial = 20; - timeoutSubmission = 50; - timeoutChallenge = 60; - timeDKG = 20; - resultPublicationBlockStep = 3; - activeGroupsThreshold = 1; - groupActiveTime = 1; - - randomBeaconValue = bls.groupSignature; - - keepGroupImplV1 = await KeepGroupImplV1.new(); - keepGroupProxy = await KeepGroupProxy.new(keepGroupImplV1.address); - keepGroupImplViaProxy = await KeepGroupImplV1.at(keepGroupProxy.address); - await keepGroupImplViaProxy.initialize( - stakingProxy.address, keepRandomBeaconProxy.address, minimumStake, - groupThreshold, groupSize, timeoutInitial, timeoutSubmission, - timeoutChallenge, timeDKG, resultPublicationBlockStep, activeGroupsThreshold, - groupActiveTime - ); - - // Using stub method to add first group to help testing. - await keepGroupImplViaProxy.registerNewGroup(bls.groupPubKey); - - await keepRandomBeaconImplViaProxy.initialize(1,1, randomBeaconValue, bls.groupPubKey, keepGroupProxy.address, - relayRequestTimeout); - await keepRandomBeaconImplViaProxy.relayEntry(1, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); - - // Stake delegate tokens to operator1 - signature = Buffer.from((await web3.eth.sign(web3.utils.soliditySha3(owner), operator1)).substr(2), 'hex'); - delegation = '0x' + Buffer.concat([Buffer.from(magpie.substr(2), 'hex'), signature]).toString('hex'); - await token.approveAndCall(stakingContract.address, minimumStake*2000, delegation, {from: owner}); - tickets1 = generateTickets(randomBeaconValue, operator1, 2000); - - // Stake delegate tokens to operator2 - signature = Buffer.from((await web3.eth.sign(web3.utils.soliditySha3(owner), operator2)).substr(2), 'hex'); - delegation = '0x' + Buffer.concat([Buffer.from(magpie.substr(2), 'hex'), signature]).toString('hex'); - await token.approveAndCall(stakingContract.address, minimumStake*2000, delegation, {from: owner}); - tickets2 = generateTickets(randomBeaconValue, operator2, 2000); - - // Stake delegate tokens to operator3 - signature = Buffer.from((await web3.eth.sign(web3.utils.soliditySha3(owner), operator3)).substr(2), 'hex'); - delegation = '0x' + Buffer.concat([Buffer.from(magpie.substr(2), 'hex'), signature]).toString('hex'); - await token.approveAndCall(stakingContract.address, minimumStake*3000, delegation, {from: owner}); - tickets3 = generateTickets(randomBeaconValue, operator3, 3000); - - }); - - it("should be able to get staking weight", async function() { - assert.equal(web3.utils.toBN(2000).eq(await keepGroupImplViaProxy.stakingWeight(operator1)), true, "Should have expected staking weight."); - assert.equal(web3.utils.toBN(3000).eq(await keepGroupImplViaProxy.stakingWeight(operator3)), true, "Should have expected staking weight."); - }); - - it("should fail to get selected tickets before challenge period is over", async function() { - await exceptThrow(keepGroupImplViaProxy.selectedTickets()); - }); - - it("should fail to get selected participants before challenge period is over", async function() { - await exceptThrow(keepGroupImplViaProxy.selectedParticipants()); - }); - - it("should not trigger group selection while one is in progress", async function() { - let groupSelectionStartBlock = await keepGroupImplViaProxy.ticketSubmissionStartBlock(); - await keepRandomBeaconImplViaProxy.requestRelayEntry(bls.seed, {value: 10}); - await keepRandomBeaconImplViaProxy.relayEntry(2, bls.nextGroupSignature, bls.groupPubKey, bls.groupSignature, bls.seed); - - assert.isTrue((await keepGroupImplViaProxy.ticketSubmissionStartBlock()).eq(groupSelectionStartBlock), "Group selection start block should not be updated."); - assert.isTrue((await keepGroupImplViaProxy.randomBeaconValue()).eq(bls.groupSignature), "Random beacon value for the current group selection should not change."); - }); - - it("should trigger new group selection when the last one is over", async function() { - let groupSelectionStartBlock = await keepGroupImplViaProxy.ticketSubmissionStartBlock(); - mineBlocks(timeoutChallenge + timeDKG + groupSize * resultPublicationBlockStep); - - await keepRandomBeaconImplViaProxy.requestRelayEntry(bls.seed, {value: 10}); - await keepRandomBeaconImplViaProxy.relayEntry(2, bls.nextGroupSignature, bls.groupPubKey, bls.groupSignature, bls.seed); - - assert.isFalse((await keepGroupImplViaProxy.ticketSubmissionStartBlock()).eq(groupSelectionStartBlock), "Group selection start block should be updated."); - assert.isTrue((await keepGroupImplViaProxy.randomBeaconValue()).eq(bls.nextGroupSignature), "Random beacon value for the current group selection should be updated."); - }); - - it("should be able to get selected tickets and participants after challenge period is over", async function() { - - for (let i = 0; i < groupSize*2; i++) { - await keepGroupImplViaProxy.submitTicket(tickets1[i].value, operator1, tickets1[i].virtualStakerIndex, {from: operator1}); - } - - mineBlocks(timeoutChallenge); - let selectedTickets = await keepGroupImplViaProxy.selectedTickets(); - assert.equal(selectedTickets.length, groupSize, "Should be trimmed to groupSize length."); - - let selectedParticipants = await keepGroupImplViaProxy.selectedParticipants(); - assert.equal(selectedParticipants.length, groupSize, "Should be trimmed to groupSize length."); - }); - - it("should be able to output submited tickets in ascending ordered", async function() { - - let tickets = []; - - await keepGroupImplViaProxy.submitTicket(tickets1[0].value, operator1, tickets1[0].virtualStakerIndex, {from: operator1}); - tickets.push(tickets1[0].value); - - await keepGroupImplViaProxy.submitTicket(tickets2[0].value, operator2, tickets2[0].virtualStakerIndex, {from: operator2}); - tickets.push(tickets2[0].value); - - await keepGroupImplViaProxy.submitTicket(tickets3[0].value, operator3, tickets3[0].virtualStakerIndex, {from: operator3}); - tickets.push(tickets3[0].value); - - tickets = tickets.sort(function(a, b){return a-b}); // Sort numbers in ascending order - - // Test tickets ordering - let orderedTickets = await keepGroupImplViaProxy.orderedTickets(); - assert.equal(orderedTickets[0].eq(tickets[0]), true, "Tickets should be in ascending order."); - assert.equal(orderedTickets[1].eq(tickets[1]), true, "Tickets should be in ascending order."); - assert.equal(orderedTickets[2].eq(tickets[2]), true, "Tickets should be in ascending order."); - - }); - - it("should be able to submit a ticket during ticket submission period", async function() { - await keepGroupImplViaProxy.submitTicket(tickets1[0].value, operator1, tickets1[0].virtualStakerIndex, {from: operator1}); - let proof = await keepGroupImplViaProxy.getTicketProof(tickets1[0].value); - assert.equal(proof[1].eq(web3.utils.toBN(operator1)), true , "Should be able to get submitted ticket proof."); - assert.equal(proof[2], tickets1[0].virtualStakerIndex, "Should be able to get submitted ticket proof."); - }); - - it("should be able to verify a ticket", async function() { - - await keepGroupImplViaProxy.submitTicket(tickets1[0].value, operator1, 1, {from: operator1}); - - assert.equal(await keepGroupImplViaProxy.cheapCheck( - operator1, operator1, 1 - ), true, "Should be able to verify a valid ticket."); - - assert.equal(await keepGroupImplViaProxy.costlyCheck( - operator1, tickets1[0].value, operator1, tickets1[0].virtualStakerIndex - ), true, "Should be able to verify a valid ticket."); - - assert.equal(await keepGroupImplViaProxy.costlyCheck( - operator1, 0, operator1, tickets1[0].virtualStakerIndex - ), false, "Should fail verifying invalid ticket."); - - }); -}); diff --git a/contracts/solidity/test/TestKeepGroupViaProxy.js b/contracts/solidity/test/TestKeepGroupViaProxy.js deleted file mode 100644 index afb32763b5..0000000000 --- a/contracts/solidity/test/TestKeepGroupViaProxy.js +++ /dev/null @@ -1,69 +0,0 @@ -import { duration } from './helpers/increaseTime'; -import exceptThrow from './helpers/expectThrow'; -const KeepToken = artifacts.require('./KeepToken.sol'); -const StakingProxy = artifacts.require('./StakingProxy.sol'); -const TokenStaking = artifacts.require('./TokenStaking.sol'); -const KeepRandomBeaconProxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeaconImplV1 = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const KeepGroupProxy = artifacts.require('./KeepGroup.sol'); -const KeepGroupImplV1 = artifacts.require('./KeepGroupImplV1.sol'); - -contract('TestKeepGroupViaProxy', function(accounts) { - - let token, stakingProxy, stakingContract, minimumStake, groupThreshold, groupSize, - timeoutInitial, timeoutSubmission, timeoutChallenge, timeDKG, resultPublicationBlockStep, - activeGroupsThreshold, groupActiveTime, - keepGroupImplV1, keepGroupProxy, keepGroupImplViaProxy, - keepRandomBeaconImplV1, keepRandomBeaconProxy, - account_one = accounts[0], - account_two = accounts[1]; - - beforeEach(async () => { - token = await KeepToken.new(); - - // Initialize staking contract under proxy - stakingProxy = await StakingProxy.new(); - stakingContract = await TokenStaking.new(token.address, stakingProxy.address, duration.days(30)); - await stakingProxy.authorizeContract(stakingContract.address, {from: account_one}) - - // Initialize Keep Random Beacon contract - keepRandomBeaconImplV1 = await KeepRandomBeaconImplV1.new(1,1); - keepRandomBeaconProxy = await KeepRandomBeaconProxy.new(keepRandomBeaconImplV1.address); - - // Initialize Keep Group contract - minimumStake = 200; - groupThreshold = 150; - groupSize = 200; - timeoutInitial = 20; - timeoutSubmission = 100; - timeoutChallenge = 60; - timeDKG = 20; - resultPublicationBlockStep = 3; - activeGroupsThreshold = 1; - groupActiveTime = 1; - - keepGroupImplV1 = await KeepGroupImplV1.new(); - keepGroupProxy = await KeepGroupProxy.new(keepGroupImplV1.address); - keepGroupImplViaProxy = await KeepGroupImplV1.at(keepGroupProxy.address); - await keepGroupImplViaProxy.initialize( - stakingProxy.address, keepRandomBeaconProxy.address, minimumStake, - groupThreshold, groupSize, timeoutInitial, timeoutSubmission, - timeoutChallenge, timeDKG, resultPublicationBlockStep, activeGroupsThreshold, - groupActiveTime - ); - }); - - it("should fail to update minimum stake by non owner", async function() { - await exceptThrow(keepGroupImplViaProxy.setMinimumStake(123, {from: account_two})); - }); - - it("should be able to update minimum stake by the owner", async function() { - await keepGroupImplViaProxy.setMinimumStake(123); - let newMinStake = await keepGroupImplViaProxy.minimumStake(); - assert.equal(newMinStake, 123, "Should be able to get updated minimum stake."); - }); - - it("should be able to check if the implementation contract was initialized", async function() { - assert.equal(await keepGroupImplViaProxy.initialized(), true, "Implementation contract should be initialized."); - }); -}); diff --git a/contracts/solidity/test/TestKeepRandomBeaconCallback.js b/contracts/solidity/test/TestKeepRandomBeaconCallback.js deleted file mode 100644 index baab273be9..0000000000 --- a/contracts/solidity/test/TestKeepRandomBeaconCallback.js +++ /dev/null @@ -1,44 +0,0 @@ -import {bls} from './helpers/data'; -const Proxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeacon = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const KeepGroupStub = artifacts.require('./KeepGroupStub.sol'); -const CallbackContract = artifacts.require('./examples/CallbackContract.sol'); - -contract('TestKeepRandomBeaconCallback', function() { - const relayRequestTimeout = 10; - let impl, proxy, keepRandomBeacon, callbackContract, keepGroupStub; - - before(async () => { - impl = await KeepRandomBeacon.new(); - proxy = await Proxy.new(impl.address); - keepRandomBeacon = await KeepRandomBeacon.at(proxy.address); - - keepGroupStub = await KeepGroupStub.new(); - await keepRandomBeacon.initialize(1, 1, bls.previousEntry, bls.groupPubKey, keepGroupStub.address, - relayRequestTimeout); - callbackContract = await CallbackContract.new(); - }); - - it("should produce entry if callback contract was not provided", async function() { - let tx = await keepRandomBeacon.requestRelayEntry(bls.seed, {value: 10}); - let requestId = tx.logs[0].args.requestID; - await keepRandomBeacon.relayEntry(requestId, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); - - let result = await keepRandomBeacon.previousEntry(); - assert.isTrue(result.eq(bls.groupSignature), "Value should be updated on beacon contract."); - }); - - it("should successfully call method on a callback contract", async function() { - let tx = await keepRandomBeacon.methods['requestRelayEntry(uint256,address,string)'](bls.seed, callbackContract.address, "callback(uint256)", {value: 10}); - let requestId = tx.logs[0].args.requestID; - - let result = await callbackContract.lastEntry(); - assert.isFalse(result.eq(bls.groupSignature), "Entry value on the callback contract should not be the same as next relay entry."); - - await keepRandomBeacon.relayEntry(requestId, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); - - result = await callbackContract.lastEntry(); - assert.isTrue(result.eq(bls.groupSignature), "Value updated by the callback should be the same as relay entry."); - }); - -}); diff --git a/contracts/solidity/test/TestKeepRandomBeaconOperator.js b/contracts/solidity/test/TestKeepRandomBeaconOperator.js new file mode 100644 index 0000000000..2bbf98d226 --- /dev/null +++ b/contracts/solidity/test/TestKeepRandomBeaconOperator.js @@ -0,0 +1,34 @@ +import exceptThrow from './helpers/expectThrow'; +import {initContracts} from './helpers/initContracts'; + +contract('TestKeepRandomBeaconOperator', function(accounts) { + + let operatorContract; + + before(async () => { + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + artifacts.require('./KeepRandomBeaconService.sol'), + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperator.sol') + ); + operatorContract = contracts.operatorContract; + }); + + it("should fail to update minimum stake by non owner", async function() { + await exceptThrow(operatorContract.setMinimumStake(123, {from: accounts[1]})); + }); + + it("should be able to update minimum stake by the owner", async function() { + await operatorContract.setMinimumStake(123); + let newMinStake = await operatorContract.minimumStake(); + assert.equal(newMinStake, 123, "Should be able to get updated minimum stake."); + }); + + it("should be able to check if the contract was initialized", async function() { + assert.isTrue(await operatorContract.initialized(), "Contract should be initialized."); + }); +}); diff --git a/contracts/solidity/test/TestKeepGroupExpiration.js b/contracts/solidity/test/TestKeepRandomBeaconOperatorGroupExpiration.js similarity index 66% rename from contracts/solidity/test/TestKeepGroupExpiration.js rename to contracts/solidity/test/TestKeepRandomBeaconOperatorGroupExpiration.js index b8de4da255..1514bb1234 100644 --- a/contracts/solidity/test/TestKeepGroupExpiration.js +++ b/contracts/solidity/test/TestKeepRandomBeaconOperatorGroupExpiration.js @@ -1,75 +1,36 @@ -import { duration } from './helpers/increaseTime'; import mineBlocks from './helpers/mineBlocks'; -const StakingProxy = artifacts.require('./StakingProxy.sol'); -const TokenStaking = artifacts.require('./TokenStaking.sol'); -const KeepToken = artifacts.require('./KeepToken.sol'); -const KeepRandomBeaconProxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeaconImplV1 = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const KeepGroupProxy = artifacts.require('./KeepGroup.sol'); -const KeepGroupImplV1 = artifacts.require('./KeepGroupImplV1Stub.sol'); - -const minimumStake = 200000; -const groupThreshold = 15; -const groupSize = 20; -const timeoutInitial = 20; -const timeoutSubmission = 50; -const timeoutChallenge = 60; -const timeDKG = 20; -const resultPublicationBlockStep = 3; -const groupActiveTime = 300; -const activeGroupsThreshold = 5; -const testGroupsNumber = 10; - -const minPayment = 1; -const withdrawalDelay = 1; -const genesisEntry = 1; -const genesisGroupPubKey = "0xfff"; -const relayRequestTimeout = 20; - -contract('TestKeepGroupExpiration', function(accounts) { - - let token, stakingProxy, stakingContract, - keepRandomBeaconImplV1, keepRandomBeaconProxy, keepRandomBeaconImplViaProxy, - keepGroupImplV1, keepGroupProxy, keepGroupImplViaProxy, - owner = accounts[0] +import {initContracts} from './helpers/initContracts'; - beforeEach(async () => { - token = await KeepToken.new(); - // Initialize staking contract under proxy - stakingProxy = await StakingProxy.new(); - stakingContract = await TokenStaking.new(token.address, stakingProxy.address, duration.days(30)); - await stakingProxy.authorizeContract(stakingContract.address, {from: owner}) - - keepRandomBeaconImplV1 = await KeepRandomBeaconImplV1.new(); - keepRandomBeaconProxy = await KeepRandomBeaconProxy.new(keepRandomBeaconImplV1.address); - keepRandomBeaconImplViaProxy = await KeepRandomBeaconImplV1.at(keepRandomBeaconProxy.address); - - keepGroupImplV1 = await KeepGroupImplV1.new(); - keepGroupProxy = await KeepGroupProxy.new(keepGroupImplV1.address); - keepGroupImplViaProxy = await KeepGroupImplV1.at(keepGroupProxy.address); - - // Initialize Keep Random Beacon contract - await keepRandomBeaconImplViaProxy.initialize( - minPayment, withdrawalDelay, genesisEntry, genesisGroupPubKey, - keepGroupProxy.address, relayRequestTimeout - ); +contract('TestKeepRandomBeaconOperatorGroupExpiration', function(accounts) { - // Initialize Keep Group contract - await keepGroupImplViaProxy.initialize( - stakingProxy.address, keepRandomBeaconProxy.address, minimumStake, - groupThreshold, groupSize, timeoutInitial, timeoutSubmission, - timeoutChallenge, timeDKG, resultPublicationBlockStep, activeGroupsThreshold, - groupActiveTime + let serviceContract, operatorContract, + groupActiveTime, activeGroupsThreshold, relayRequestTimeout, + testGroupsNumber = 10; + + beforeEach(async () => { + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + artifacts.require('./KeepRandomBeaconService.sol'), + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperatorStub.sol') ); + operatorContract = contracts.operatorContract; + groupActiveTime = (await operatorContract.groupActiveTime()).toNumber(); + activeGroupsThreshold = (await operatorContract.activeGroupsThreshold()).toNumber(); + serviceContract = contracts.serviceContract; + relayRequestTimeout = (await operatorContract.relayRequestTimeout()).toNumber(); }); async function addGroups(numberOfGroups) { for (var i = 1; i <= numberOfGroups; i++) - await keepGroupImplViaProxy.registerNewGroup([i]); + await operatorContract.registerNewGroup([i]); } async function expireGroup(groupIndex) { - let groupRegistrationBlock = await keepGroupImplViaProxy.getGroupRegistrationBlockHeight(groupIndex); + let groupRegistrationBlock = await operatorContract.getGroupRegistrationBlockHeight(groupIndex); let currentBlock = await web3.eth.getBlockNumber(); // If current block is larger than group registration block by group active time then // it is not necessary to mine any blocks cause the group is already expired @@ -81,7 +42,7 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(testGroupsNumber); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(Number(numberOfGroups), testGroupsNumber, "Number of groups is not equal to number of test groups"); }); @@ -96,10 +57,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(testGroupsNumber); await expireGroup(4); - await keepGroupImplViaProxy.selectGroup(4) // 4 % 10 = 4 + await operatorContract.selectGroup(4) // 4 % 10 = 4 - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, activeGroupsThreshold, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), activeGroupsThreshold, "Number of groups is not equal to active groups threshold"); @@ -116,10 +77,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(testGroupsNumber); await expireGroup(5); - await keepGroupImplViaProxy.selectGroup(5) // 5 % 10 = 5 + await operatorContract.selectGroup(5) // 5 % 10 = 5 - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, activeGroupsThreshold, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), activeGroupsThreshold, "Number of groups is not equal to active groups threshold"); @@ -136,10 +97,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(testGroupsNumber); await expireGroup(6); - await keepGroupImplViaProxy.selectGroup(6) // 6 % 10 = 6 + await operatorContract.selectGroup(6) // 6 % 10 = 6 - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, activeGroupsThreshold, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), activeGroupsThreshold, "Number of groups is not equal to active groups threshold"); @@ -156,10 +117,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(testGroupsNumber); await expireGroup(9); - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, activeGroupsThreshold, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), activeGroupsThreshold, "Number of groups is not equal to active groups threshold"); @@ -176,10 +137,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(testGroupsNumber); await expireGroup(9); - await keepGroupImplViaProxy.selectGroup(testGroupsNumber - 1); // 9 + await operatorContract.selectGroup(testGroupsNumber - 1); // 9 - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, activeGroupsThreshold, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), activeGroupsThreshold, "Number of groups is not equal to active groups threshold"); @@ -196,10 +157,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(testGroupsNumber); await expireGroup(9); - await keepGroupImplViaProxy.selectGroup(testGroupsNumber); // 10 + await operatorContract.selectGroup(testGroupsNumber); // 10 - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, activeGroupsThreshold, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), activeGroupsThreshold, "Number of groups is not equal to active groups threshold"); @@ -217,11 +178,11 @@ contract('TestKeepGroupExpiration', function(accounts) { await expireGroup(9); for (var i = 1; i <= testGroupsNumber; i++) - await keepGroupImplViaProxy.registerNewGroup([i]); + await operatorContract.registerNewGroup([i]); - await keepGroupImplViaProxy.selectGroup(1); + await operatorContract.selectGroup(1); - let after = await keepGroupImplViaProxy.numberOfGroups(); + let after = await operatorContract.numberOfGroups(); assert.equal(Number(after), testGroupsNumber, "Number of groups should not fall below the test groups number"); }); @@ -237,10 +198,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(1); await expireGroup(0) // indexed from 0 - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, 0, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), 1, "Unexpected number of groups"); @@ -258,10 +219,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(groupsCount); await expireGroup(groupsCount - 1) // indexed from 0 - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, 0, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), groupsCount, "Unexpected number of groups"); @@ -278,10 +239,10 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(groupsCount); await expireGroup(groupsCount - 1) // indexed from 0 - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); - let expiredOffset = await keepGroupImplViaProxy.getExpiredOffset(); - let numberOfGroups = await keepGroupImplViaProxy.numberOfGroups(); + let expiredOffset = await operatorContract.expiredGroupOffset(); + let numberOfGroups = await operatorContract.numberOfGroups(); assert.equal(expiredOffset, 0, "Unexpected expired offset"); assert.equal(Number(numberOfGroups), groupsCount, "Unexpected number of groups"); @@ -294,9 +255,9 @@ contract('TestKeepGroupExpiration', function(accounts) { let groupsCount = activeGroupsThreshold + 1 await addGroups(groupsCount); - let pubKey = await keepGroupImplViaProxy.getGroupPublicKey(0); + let pubKey = await operatorContract.getGroupPublicKey(0); - let isStale = await keepGroupImplViaProxy.isStaleGroup(pubKey); + let isStale = await operatorContract.isStaleGroup(pubKey); assert.equal(isStale, false, "Group should not be marked as stale"); }); @@ -310,11 +271,11 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(groupsCount); await expireGroup(9); // expire first 10 groups (we index from 0) - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); for (var i = 10; i < groupsCount; i++) { - let pubKey = await keepGroupImplViaProxy.getGroupPublicKey(i); - let isStale = await keepGroupImplViaProxy.isStaleGroup(pubKey); + let pubKey = await operatorContract.getGroupPublicKey(i); + let isStale = await operatorContract.isStaleGroup(pubKey); assert.equal(isStale, false, "Group should not be marked as stale") } @@ -330,13 +291,13 @@ contract('TestKeepGroupExpiration', function(accounts) { await addGroups(groupsCount); await expireGroup(9); // expire first 10 groups (we index from 0) - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); await mineBlocks(relayRequestTimeout); for (var i = 10; i < groupsCount; i++) { - let pubKey = await keepGroupImplViaProxy.getGroupPublicKey(i); - let isStale = await keepGroupImplViaProxy.isStaleGroup(pubKey); + let pubKey = await operatorContract.getGroupPublicKey(i); + let isStale = await operatorContract.isStaleGroup(pubKey); assert.equal(isStale, false, "Group should not be marked as stale") } @@ -354,12 +315,12 @@ contract('TestKeepGroupExpiration', function(accounts) { let groupsCount = activeGroupsThreshold + 1 await addGroups(groupsCount); - let pubKey = await keepGroupImplViaProxy.getGroupPublicKey(0); + let pubKey = await operatorContract.getGroupPublicKey(0); // mine blocks but do not select group so it's not marked as expired await mineBlocks(groupActiveTime + relayRequestTimeout); - let isStale = await keepGroupImplViaProxy.isStaleGroup(pubKey); + let isStale = await operatorContract.isStaleGroup(pubKey); assert.equal(isStale, false, "Group should not be marked as stale"); }); @@ -374,12 +335,12 @@ contract('TestKeepGroupExpiration', function(accounts) { let groupsCount = activeGroupsThreshold + 1 await addGroups(groupsCount); - let pubKey = await keepGroupImplViaProxy.getGroupPublicKey(0); + let pubKey = await operatorContract.getGroupPublicKey(0); await expireGroup(0); - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); - let isStale = await keepGroupImplViaProxy.isStaleGroup(pubKey); + let isStale = await operatorContract.isStaleGroup(pubKey); assert.equal(isStale, false, "Group should not be marked as stale"); }); @@ -395,14 +356,14 @@ contract('TestKeepGroupExpiration', function(accounts) { let groupsCount = activeGroupsThreshold + 1 await addGroups(groupsCount); - let pubKey = await keepGroupImplViaProxy.getGroupPublicKey(0); + let pubKey = await operatorContract.getGroupPublicKey(0); await expireGroup(0); - await keepGroupImplViaProxy.selectGroup(0); + await operatorContract.selectGroup(0); await mineBlocks(relayRequestTimeout); - let isStale = await keepGroupImplViaProxy.isStaleGroup(pubKey); + let isStale = await operatorContract.isStaleGroup(pubKey); assert.equal(isStale, true, "Group should be marked as stale"); }); @@ -416,7 +377,7 @@ contract('TestKeepGroupExpiration', function(accounts) { let pubKey = "0x1337"; // group with such pub key does not exist - let isStale = await keepGroupImplViaProxy.isStaleGroup(pubKey); + let isStale = await operatorContract.isStaleGroup(pubKey); assert.equal(isStale, true, "Group should be marked as stale"); }); diff --git a/contracts/solidity/test/TestKeepRandomBeaconOperatorGroupSelection.js b/contracts/solidity/test/TestKeepRandomBeaconOperatorGroupSelection.js new file mode 100644 index 0000000000..a3503f5bcc --- /dev/null +++ b/contracts/solidity/test/TestKeepRandomBeaconOperatorGroupSelection.js @@ -0,0 +1,150 @@ +import exceptThrow from './helpers/expectThrow'; +import mineBlocks from './helpers/mineBlocks'; +import {bls} from './helpers/data'; +import generateTickets from './helpers/generateTickets'; +import stakeDelegate from './helpers/stakeDelegate'; +import {initContracts} from './helpers/initContracts'; + + +contract('TestKeepRandomBeaconOperatorGroupSelection', function(accounts) { + + let token, serviceContract, operatorContract, + owner = accounts[0], magpie = accounts[1], signature, delegation, + operator1 = accounts[2], tickets1, + operator2 = accounts[3], tickets2, + operator3 = accounts[4], tickets3; + + before(async () => { + + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + artifacts.require('./KeepRandomBeaconService.sol'), + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperatorStub.sol') + ); + token = contracts.token; + serviceContract = contracts.serviceContract; + operatorContract = contracts.operatorContract; + let stakingContract = await operatorContract.stakingContract(); + let minimumStake = await operatorContract.minimumStake(); + + await stakeDelegate(stakingContract, token, owner, operator1, magpie, minimumStake.mul(web3.utils.toBN(2000))) + await stakeDelegate(stakingContract, token, owner, operator2, magpie, minimumStake.mul(web3.utils.toBN(2000))) + await stakeDelegate(stakingContract, token, owner, operator3, magpie, minimumStake.mul(web3.utils.toBN(3000))) + + tickets1 = generateTickets(await operatorContract.groupSelectionSeed(), operator1, 2000); + tickets2 = generateTickets(await operatorContract.groupSelectionSeed(), operator2, 2000); + tickets3 = generateTickets(await operatorContract.groupSelectionSeed(), operator3, 3000); + + // Using stub method to add first group to help testing. + await operatorContract.registerNewGroup(bls.groupPubKey); + + }); + + it("should be able to get staking weight", async function() { + assert.isTrue(web3.utils.toBN(2000).eq(await operatorContract.stakingWeight(operator1)), "Should have expected staking weight."); + assert.isTrue(web3.utils.toBN(3000).eq(await operatorContract.stakingWeight(operator3)), "Should have expected staking weight."); + }); + + it("should fail to get selected tickets before challenge period is over", async function() { + await exceptThrow(operatorContract.selectedTickets()); + }); + + it("should fail to get selected participants before challenge period is over", async function() { + await exceptThrow(operatorContract.selectedParticipants()); + }); + + it("should be able to output submited tickets in ascending ordered", async function() { + + let tickets = []; + + await operatorContract.submitTicket(tickets1[0].value, operator1, tickets1[0].virtualStakerIndex, {from: operator1}); + tickets.push(tickets1[0].value); + + await operatorContract.submitTicket(tickets2[0].value, operator2, tickets2[0].virtualStakerIndex, {from: operator2}); + tickets.push(tickets2[0].value); + + await operatorContract.submitTicket(tickets3[0].value, operator3, tickets3[0].virtualStakerIndex, {from: operator3}); + tickets.push(tickets3[0].value); + + tickets = tickets.sort(function(a, b){return a-b}); // Sort numbers in ascending order + + // Test tickets ordering + let orderedTickets = await operatorContract.orderedTickets(); + assert.isTrue(orderedTickets[0].eq(tickets[0]), "Tickets should be in ascending order."); + assert.isTrue(orderedTickets[1].eq(tickets[1]), "Tickets should be in ascending order."); + assert.isTrue(orderedTickets[2].eq(tickets[2]), "Tickets should be in ascending order."); + + }); + + it("should be able to submit a ticket during ticket submission period", async function() { + await operatorContract.submitTicket(tickets1[0].value, operator1, tickets1[0].virtualStakerIndex, {from: operator1}); + let proof = await operatorContract.getTicketProof(tickets1[0].value); + assert.isTrue(proof[1].eq(web3.utils.toBN(operator1)), "Should be able to get submitted ticket proof."); + assert.equal(proof[2], tickets1[0].virtualStakerIndex, "Should be able to get submitted ticket proof."); + }); + + it("should be able to verify a ticket", async function() { + + await operatorContract.submitTicket(tickets1[0].value, operator1, 1, {from: operator1}); + + assert.isTrue(await operatorContract.cheapCheck( + operator1, operator1, 1 + ), "Should be able to verify a valid ticket."); + + assert.isTrue(await operatorContract.costlyCheck( + operator1, tickets1[0].value, operator1, tickets1[0].virtualStakerIndex + ), "Should be able to verify a valid ticket."); + + assert.isFalse(await operatorContract.costlyCheck( + operator1, 0, operator1, tickets1[0].virtualStakerIndex + ), "Should fail verifying invalid ticket."); + + }); + + it("should not trigger group selection while one is in progress", async function() { + let groupSelectionStartBlock = await operatorContract.ticketSubmissionStartBlock(); + await serviceContract.requestRelayEntry(bls.seed, {value: 10}); + await operatorContract.relayEntry(2, bls.nextGroupSignature, bls.groupPubKey, bls.groupSignature, bls.seed); + + assert.isTrue((await operatorContract.ticketSubmissionStartBlock()).eq(groupSelectionStartBlock), "Group selection start block should not be updated."); + assert.isTrue((await operatorContract.groupSelectionSeed()).eq(bls.groupSignature), "Random beacon value for the current group selection should not change."); + }); + + it("should be able to get selected tickets and participants after challenge period is over", async function() { + + let groupSize = await operatorContract.groupSize(); + + for (let i = 0; i < groupSize*2; i++) { + await operatorContract.submitTicket(tickets1[i].value, operator1, tickets1[i].virtualStakerIndex, {from: operator1}); + } + + mineBlocks(await operatorContract.ticketChallengeTimeout()); + let selectedTickets = await operatorContract.selectedTickets(); + assert.equal(selectedTickets.length, groupSize, "Should be trimmed to groupSize length."); + + let selectedParticipants = await operatorContract.selectedParticipants(); + assert.equal(selectedParticipants.length, groupSize, "Should be trimmed to groupSize length."); + }); + + it("should trigger new group selection when the last one is over", async function() { + let groupSelectionStartBlock = await operatorContract.ticketSubmissionStartBlock(); + + // Calculate the block time when the group selection should be finished + let timeoutChallenge = (await operatorContract.ticketChallengeTimeout()).toNumber(); + let timeDKG = (await operatorContract.timeDKG()).toNumber(); + let groupSize = (await operatorContract.groupSize()).toNumber(); + let resultPublicationBlockStep = (await operatorContract.resultPublicationBlockStep()).toNumber(); + mineBlocks(timeoutChallenge + timeDKG + groupSize * resultPublicationBlockStep); + + await serviceContract.requestRelayEntry(bls.seed, {value: 10}); + await operatorContract.relayEntry(3, bls.nextGroupSignature, bls.groupPubKey, bls.groupSignature, bls.seed); + + assert.isFalse((await operatorContract.ticketSubmissionStartBlock()).eq(groupSelectionStartBlock), "Group selection start block should be updated."); + assert.isTrue((await operatorContract.groupSelectionSeed()).eq(bls.nextGroupSignature), "Random beacon value for the current group selection should be updated."); + }); + +}); diff --git a/contracts/solidity/test/TestKeepGroupInitialize.sol b/contracts/solidity/test/TestKeepRandomBeaconOperatorInitialize.sol similarity index 56% rename from contracts/solidity/test/TestKeepGroupInitialize.sol rename to contracts/solidity/test/TestKeepRandomBeaconOperatorInitialize.sol index c1fea5cf31..9eff75e09f 100644 --- a/contracts/solidity/test/TestKeepGroupInitialize.sol +++ b/contracts/solidity/test/TestKeepRandomBeaconOperatorInitialize.sol @@ -3,7 +3,7 @@ pragma solidity ^0.5.4; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../contracts/utils/ThrowProxy.sol"; -import "../contracts/KeepGroupImplV1.sol"; +import "../contracts/KeepRandomBeaconOperator.sol"; contract StakingProxyMock { @@ -14,20 +14,20 @@ contract StakingProxyMock { } -contract TestKeepGroupInitialize { +contract TestKeepRandomBeaconOperatorInitialize { // Create Staking proxy contract mock StakingProxyMock stakingProxy = new StakingProxyMock(); - // Create KEEP Group Contract - KeepGroupImplV1 keepGroupContract = new KeepGroupImplV1(); + // Create Keep Random Beacon operator contract + KeepRandomBeaconOperator keepRandomBeaconOperator = new KeepRandomBeaconOperator(); function testCannotInitialize() public { // http://truffleframework.com/tutorials/testing-for-throws-in-solidity-tests - ThrowProxy throwProxy = new ThrowProxy(address(keepGroupContract)); + ThrowProxy throwProxy = new ThrowProxy(address(keepRandomBeaconOperator)); // Prime the proxy - KeepGroupImplV1(address(throwProxy)).initialize(address(0), address(0), 200, 150, 200, 1, 1, 1, 1, 1, 1, 1); + KeepRandomBeaconOperator(address(throwProxy)).initialize(address(0), address(0), 200, 150, 200, 1, 1, 1, 1, 1, 1, 1, 1, 1, "0x01"); // Execute the call that is supposed to throw. // r will be false if it threw and true if it didn't. @@ -36,8 +36,8 @@ contract TestKeepGroupInitialize { } function testInitialize() public { - keepGroupContract.initialize(address(stakingProxy), address(0), 200, 150, 200, 1, 1, 1, 1, 1, 1, 1); - Assert.equal(keepGroupContract.initialized(), true, "Should be initialized."); + keepRandomBeaconOperator.initialize(address(stakingProxy), address(0), 200, 150, 200, 1, 1, 1, 1, 1, 1, 1, 1, 1, "0x01"); + Assert.equal(keepRandomBeaconOperator.initialized(), true, "Should be initialized."); } diff --git a/contracts/solidity/test/TestKeepRandomBeaconOperatorPublishDkgResult.js b/contracts/solidity/test/TestKeepRandomBeaconOperatorPublishDkgResult.js new file mode 100644 index 0000000000..f919c6a681 --- /dev/null +++ b/contracts/solidity/test/TestKeepRandomBeaconOperatorPublishDkgResult.js @@ -0,0 +1,225 @@ +import { duration } from './helpers/increaseTime'; +import {bls} from './helpers/data'; +import mineBlocks from './helpers/mineBlocks'; +import generateTickets from './helpers/generateTickets'; +import stakeDelegate from './helpers/stakeDelegate'; +import expectThrow from './helpers/expectThrow'; +import shuffleArray from './helpers/shuffle'; +import {initContracts} from './helpers/initContracts'; + + +contract('TestKeepRandomBeaconOperatorPublishDkgResult', function(accounts) { + + let operatorContract, + owner = accounts[0], magpie = accounts[0], + operator1 = accounts[0], + operator2 = accounts[1], + operator3 = accounts[2], + operator4 = accounts[3], + resultPublicationTime, groupThreshold, + signingId = 0, + selectedParticipants, signatures, signingMemberIndices = [], + disqualified = '0x0000000000000000000000000000000000000000', + inactive = '0x0000000000000000000000000000000000000000', + groupPubKey = "0x1000000000000000000000000000000000000000000000000000000000000000", + resultHash = web3.utils.soliditySha3(groupPubKey, disqualified, inactive); + + beforeEach(async () => { + + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + artifacts.require('./KeepRandomBeaconService.sol'), + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperator.sol') + ); + let token = contracts.token; + operatorContract = contracts.operatorContract; + let stakingContract = await operatorContract.stakingContract(); + let minimumStake = await operatorContract.minimumStake(); + let groupSize = await operatorContract.groupSize(); + groupThreshold = await operatorContract.groupThreshold(); + + await stakeDelegate(stakingContract, token, owner, operator1, magpie, minimumStake.mul(web3.utils.toBN(2000))) + await stakeDelegate(stakingContract, token, owner, operator2, magpie, minimumStake.mul(web3.utils.toBN(2000))) + await stakeDelegate(stakingContract, token, owner, operator3, magpie, minimumStake.mul(web3.utils.toBN(3000))) + + let tickets1 = generateTickets(await operatorContract.groupSelectionSeed(), operator1, 2000); + let tickets2 = generateTickets(await operatorContract.groupSelectionSeed(), operator2, 2000); + let tickets3 = generateTickets(await operatorContract.groupSelectionSeed(), operator3, 3000); + + for(let i = 0; i < groupSize; i++) { + await operatorContract.submitTicket(tickets1[i].value, operator1, tickets1[i].virtualStakerIndex, {from: operator1}); + } + + for(let i = 0; i < groupSize; i++) { + await operatorContract.submitTicket(tickets2[i].value, operator2, tickets2[i].virtualStakerIndex, {from: operator2}); + } + + for(let i = 0; i < groupSize; i++) { + await operatorContract.submitTicket(tickets3[i].value, operator3, tickets3[i].virtualStakerIndex, {from: operator3}); + } + + let ticketSubmissionStartBlock = (await operatorContract.ticketSubmissionStartBlock()).toNumber(); + let timeoutChallenge = (await operatorContract.ticketChallengeTimeout()).toNumber(); + let timeDKG = (await operatorContract.timeDKG()).toNumber(); + resultPublicationTime = ticketSubmissionStartBlock + timeoutChallenge + timeDKG; + + selectedParticipants = await operatorContract.selectedParticipants(); + + for(let i = 0; i < selectedParticipants.length; i++) { + let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); + signingMemberIndices.push(i+1); + if (signatures == undefined) signatures = signature + else signatures += signature.slice(2, signature.length); + } + }); + + it("should be able to submit correct result as first member after DKG finished.", async function() { + + // Jump in time to when submitter becomes eligible to submit + let currentBlock = await web3.eth.getBlockNumber(); + mineBlocks(resultPublicationTime - currentBlock); + + await operatorContract.submitDkgResult(signingId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, {from: selectedParticipants[0]}) + assert.isTrue(await operatorContract.isDkgResultSubmitted.call(signingId), "DkgResult should be submitted"); + }); + + it("should be able to submit correct result with unordered signatures and indexes.", async function() { + + let unorderedSigningMembersIndexes = []; + for (let i = 0; i < selectedParticipants.length; i++) { + unorderedSigningMembersIndexes[i] = i + 1; + } + + unorderedSigningMembersIndexes = shuffleArray(unorderedSigningMembersIndexes); + let unorderedSignatures; + + for(let i = 0; i < selectedParticipants.length; i++) { + let signature = await web3.eth.sign(resultHash, selectedParticipants[unorderedSigningMembersIndexes[i] - 1]); + if (unorderedSignatures == undefined) unorderedSignatures = signature + else unorderedSignatures += signature.slice(2, signature.length); + } + + // Jump in time to when submitter becomes eligible to submit + let currentBlock = await web3.eth.getBlockNumber(); + mineBlocks(resultPublicationTime - currentBlock); + + await operatorContract.submitDkgResult(signingId, 1, groupPubKey, disqualified, inactive, unorderedSignatures, unorderedSigningMembersIndexes, {from: selectedParticipants[0]}) + assert.isTrue(await operatorContract.isDkgResultSubmitted.call(signingId), "DkgResult should should be submitted"); + }); + + it("should only be able to submit result at eligible block time based on member index.", async function() { + + let resultPublicationBlockStep = (await operatorContract.resultPublicationBlockStep()).toNumber(); + let submitter1MemberIndex = 4; + let submitter2MemberIndex = 5; + let submitter2 = selectedParticipants[submitter2MemberIndex - 1]; + let eligibleBlockForSubmitter1 = resultPublicationTime + (submitter1MemberIndex-1)*resultPublicationBlockStep; + let eligibleBlockForSubmitter2 = resultPublicationTime + (submitter2MemberIndex-1)*resultPublicationBlockStep; + + // Jump in time to when submitter 1 becomes eligible to submit + let currentBlock = await web3.eth.getBlockNumber(); + mineBlocks(eligibleBlockForSubmitter1 - currentBlock); + + // Should throw if non eligible submitter 2 tries to submit + await expectThrow(operatorContract.submitDkgResult( + signingId, submitter2MemberIndex, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, + {from: submitter2}) + ); + + // Jump in time to when submitter 2 becomes eligible to submit + currentBlock = await web3.eth.getBlockNumber(); + mineBlocks(eligibleBlockForSubmitter2 - currentBlock); + + await operatorContract.submitDkgResult(signingId, submitter2MemberIndex, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, {from: submitter2}) + assert.isTrue(await operatorContract.isDkgResultSubmitted.call(signingId), "DkgResult should be submitted"); + }); + + it("should not be able to submit if submitter was not selected to be part of the group.", async function() { + await expectThrow(operatorContract.submitDkgResult( + signingId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, + {from: operator4}) + ); + }); + + it("should reject the result with invalid signatures.", async function() { + + signingMemberIndices = []; + signatures = undefined; + let lastParticipantIdx = groupThreshold - 1; + + // Create less than minimum amount of valid signatures + for(let i = 0; i < lastParticipantIdx; i++) { + let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); + signingMemberIndices.push(i+1); + if (signatures == undefined) signatures = signature + else signatures += signature.slice(2, signature.length); + } + + // Add invalid signature as the last one + let nonsenseHash = web3.utils.soliditySha3("ducky duck"); + let invalidSignature = await web3.eth.sign(nonsenseHash, selectedParticipants[lastParticipantIdx]); + signatures += invalidSignature.slice(2, invalidSignature.length); + signingMemberIndices.push(lastParticipantIdx); + + // Jump in time to when first member is eligible to submit + let currentBlock = await web3.eth.getBlockNumber(); + mineBlocks(resultPublicationTime - currentBlock); + + await expectThrow(operatorContract.submitDkgResult( + signingId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, + {from: selectedParticipants[0]}) + ); + }); + + it("should be able to submit the result with minimum number of valid signatures", async function() { + + signingMemberIndices = []; + signatures = undefined; + + // Create minimum amount of valid signatures + for(let i = 0; i < groupThreshold; i++) { + let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); + signingMemberIndices.push(i+1); + if (signatures == undefined) signatures = signature + else signatures += signature.slice(2, signature.length); + } + + // Jump in time to when first member is eligible to submit + let currentBlock = await web3.eth.getBlockNumber(); + mineBlocks(resultPublicationTime - currentBlock); + + await operatorContract.submitDkgResult( + signingId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, + {from: selectedParticipants[0]}) + assert.isTrue(await operatorContract.isDkgResultSubmitted.call(signingId), "DkgResult should should be submitted"); + + }); + + it("should not be able to submit without minimum number of signatures", async function() { + + signingMemberIndices = []; + signatures = undefined; + + // Create less than minimum amount of valid signatures + for(let i = 0; i < groupThreshold - 1; i++) { + let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); + signingMemberIndices.push(i+1); + if (signatures == undefined) signatures = signature + else signatures += signature.slice(2, signature.length); + } + + // Jump in time to when first member is eligible to submit + let currentBlock = await web3.eth.getBlockNumber(); + mineBlocks(resultPublicationTime - currentBlock); + + await expectThrow(operatorContract.submitDkgResult( + signingId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, + {from: selectedParticipants[0]}) + ); + + }); +}) diff --git a/contracts/solidity/test/TestKeepRandomBeaconOperatorRelayEntry.js b/contracts/solidity/test/TestKeepRandomBeaconOperatorRelayEntry.js new file mode 100644 index 0000000000..c889d859fd --- /dev/null +++ b/contracts/solidity/test/TestKeepRandomBeaconOperatorRelayEntry.js @@ -0,0 +1,49 @@ +import exceptThrow from './helpers/expectThrow'; +import {bls} from './helpers/data'; +import {initContracts} from './helpers/initContracts'; + +contract('TestKeepRandomBeaconOperatorRelayEntry', function(accounts) { + let serviceContract, operatorContract; + + before(async () => { + + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + artifacts.require('./KeepRandomBeaconService.sol'), + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperatorStub.sol') + ); + + operatorContract = contracts.operatorContract; + serviceContract = contracts.serviceContract; + // operatorContract.authorizeServiceContract(serviceContract.address); + + // Using stub method to add first group to help testing. + await operatorContract.registerNewGroup(bls.groupPubKey); + await serviceContract.requestRelayEntry(bls.seed, {value: 10}); + }); + + it("should not be able to submit invalid relay entry", async function() { + let signingId = 2; + + // Invalid signature + let groupSignature = web3.utils.toBN('0x0fb34abfa2a9844a58776650e399bca3e08ab134e42595e03e3efc5a0472bcd8'); + + await exceptThrow(operatorContract.relayEntry(signingId, groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed)); + }); + + it("should be able to submit valid relay entry", async function() { + let signingId = 2; + + await operatorContract.relayEntry(signingId, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); + + assert.equal((await serviceContract.getPastEvents())[0].args['entry'].toString(), + bls.groupSignature.toString(), "Should emit event with successfully submitted groupSignature." + ); + + }); + +}); diff --git a/contracts/solidity/test/TestKeepRandomBeaconServiceRelayRequestCallback.js b/contracts/solidity/test/TestKeepRandomBeaconServiceRelayRequestCallback.js new file mode 100644 index 0000000000..23cc8782a7 --- /dev/null +++ b/contracts/solidity/test/TestKeepRandomBeaconServiceRelayRequestCallback.js @@ -0,0 +1,50 @@ +import {bls} from './helpers/data'; +import {initContracts} from './helpers/initContracts'; +const CallbackContract = artifacts.require('./examples/CallbackContract.sol'); + +contract('TestKeepRandomBeaconServiceRelayRequestCallback', function(accounts) { + + let operatorContract, serviceContract, callbackContract; + + before(async () => { + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + artifacts.require('./KeepRandomBeaconService.sol'), + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperatorStub.sol') + ); + + operatorContract = contracts.operatorContract; + serviceContract = contracts.serviceContract; + callbackContract = await CallbackContract.new(); + + // Using stub method to add first group to help testing. + await operatorContract.registerNewGroup(bls.groupPubKey); + }); + + it("should produce entry if callback contract was not provided", async function() { + await serviceContract.requestRelayEntry(bls.seed, {value: 10}); + let signingId = (await operatorContract.getPastEvents())[0].args['signingId'].toNumber(); + await operatorContract.relayEntry(signingId, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); + + let result = await serviceContract.previousEntry(); + assert.isTrue(result.eq(bls.groupSignature), "Value should be updated on beacon contract."); + }); + + it("should successfully call method on a callback contract", async function() { + await serviceContract.methods['requestRelayEntry(uint256,address,string)'](bls.seed, callbackContract.address, "callback(uint256)", {value: 10}); + let signingId = (await operatorContract.getPastEvents())[0].args['signingId'].toNumber(); + + let result = await callbackContract.lastEntry(); + assert.isFalse(result.eq(bls.groupSignature), "Entry value on the callback contract should not be the same as next relay entry."); + + await operatorContract.relayEntry(signingId, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); + + result = await callbackContract.lastEntry(); + assert.isTrue(result.eq(bls.groupSignature), "Value updated by the callback should be the same as relay entry."); + }); + +}); diff --git a/contracts/solidity/test/TestKeepRandomBeaconServiceUpgrade.js b/contracts/solidity/test/TestKeepRandomBeaconServiceUpgrade.js new file mode 100644 index 0000000000..f7e5b5196b --- /dev/null +++ b/contracts/solidity/test/TestKeepRandomBeaconServiceUpgrade.js @@ -0,0 +1,61 @@ +import {bls} from './helpers/data'; +import { duration } from './helpers/increaseTime'; +import exceptThrow from './helpers/expectThrow'; +import {initContracts} from './helpers/initContracts'; +const ServiceContractProxy = artifacts.require('./KeepRandomBeaconService.sol'); +const ServiceContractImplV2 = artifacts.require('./examples/KeepRandomBeaconServiceUpgradeExample.sol'); + + +contract('TestKeepRandomBeaconServiceUpgrade', function(accounts) { + + let operatorContract, serviceContractProxy, serviceContract, serviceContractImplV2, serviceContractV2, + account_two = accounts[1]; + + before(async () => { + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + ServiceContractProxy, + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperatorStub.sol') + ); + + operatorContract = contracts.operatorContract; + serviceContract = contracts.serviceContract; + serviceContractProxy = await ServiceContractProxy.at(serviceContract.address); + + serviceContractImplV2 = await ServiceContractImplV2.new(); + serviceContractV2 = await ServiceContractImplV2.at(serviceContractProxy.address); + + // Using stub method to add first group to help testing. + await operatorContract.registerNewGroup(bls.groupPubKey); + + // Modify state so we can test later that eternal storage works as expected after upgrade + await serviceContract.requestRelayEntry(bls.seed, {value: 10}); + await operatorContract.relayEntry(2, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); + + }); + + it("should be able to check if the implementation contract was initialized", async function() { + assert.isTrue(await serviceContract.initialized(), "Implementation contract should be initialized."); + }); + + it("should fail to upgrade implementation if called by not contract owner", async function() { + await exceptThrow(serviceContractProxy.upgradeTo(serviceContractImplV2.address, {from: account_two})); + }); + + it("should be able to upgrade implementation and initialize it with new data", async function() { + await serviceContractProxy.upgradeTo(serviceContractImplV2.address); + await serviceContractV2.initialize(100, duration.days(0), operatorContract.address); + + assert.isTrue(await serviceContractV2.initialized(), "Implementation contract should be initialized."); + + let newVar = await serviceContractV2.getNewVar(); + assert.equal(newVar, 1234, "Should be able to get new data from upgraded contract."); + + assert.isTrue(bls.groupSignature.eq(await serviceContractV2.previousEntry()), "Should keep previous storage after upgrade."); + }); + +}); diff --git a/contracts/solidity/test/TestKeepRandomBeaconServiceViaProxy.js b/contracts/solidity/test/TestKeepRandomBeaconServiceViaProxy.js new file mode 100644 index 0000000000..5c4832358c --- /dev/null +++ b/contracts/solidity/test/TestKeepRandomBeaconServiceViaProxy.js @@ -0,0 +1,109 @@ +import { duration, increaseTimeTo } from './helpers/increaseTime'; +import {bls} from './helpers/data'; +import latestTime from './helpers/latestTime'; +import exceptThrow from './helpers/expectThrow'; +import encodeCall from './helpers/encodeCall'; +import {initContracts} from './helpers/initContracts'; +const ServiceContractProxy = artifacts.require('./KeepRandomBeaconService.sol') + +contract('TestKeepRandomBeaconServiceViaProxy', function(accounts) { + + let serviceContract, serviceContractProxy, operatorContract, + account_one = accounts[0], + account_two = accounts[1], + account_three = accounts[2]; + + before(async () => { + let contracts = await initContracts( + accounts, + artifacts.require('./KeepToken.sol'), + artifacts.require('./StakingProxy.sol'), + artifacts.require('./TokenStaking.sol'), + ServiceContractProxy, + artifacts.require('./KeepRandomBeaconServiceImplV1.sol'), + artifacts.require('./KeepRandomBeaconOperatorStub.sol') + ); + + operatorContract = contracts.operatorContract; + serviceContract = contracts.serviceContract; + serviceContractProxy = await ServiceContractProxy.at(serviceContract.address); + + // Using stub method to add first group to help testing. + await operatorContract.registerNewGroup(bls.groupPubKey); + }); + + + it("should be able to check if the service contract was initialized", async function() { + assert.isTrue(await serviceContract.initialized(), "Service contract should be initialized."); + }); + + it("should fail to request relay entry with not enough ether", async function() { + await exceptThrow(serviceContract.requestRelayEntry(0, {from: account_two, value: 0})); + }); + + it("should be able to request relay with enough ether", async function() { + await serviceContract.requestRelayEntry(0, {from: account_two, value: 100}) + + assert.equal((await operatorContract.getPastEvents())[0].event, 'SignatureRequested', "SignatureRequested event should occur on operator contract."); + + let contractBalance = await web3.eth.getBalance(serviceContract.address); + assert.equal(contractBalance, 100, "Keep Random Beacon service contract should receive ether."); + + let contractBalanceViaProxy = await web3.eth.getBalance(serviceContractProxy.address); + assert.equal(contractBalanceViaProxy, 100, "Keep Random Beacon service contract new balance should be visible via serviceContractProxy."); + + }); + + it("should be able to request relay entry via serviceContractProxy contract with enough ether", async function() { + await exceptThrow(serviceContractProxy.sendTransaction({from: account_two, value: 1000})); + + await web3.eth.sendTransaction({ + from: account_two, value: 100, gas: 200000, to: serviceContractProxy.address, + data: encodeCall('requestRelayEntry', ['uint256'], [0]) + }); + + assert.equal((await operatorContract.getPastEvents())[0].event, 'SignatureRequested', "SignatureRequested event should occur on the operator contract."); + + let contractBalance = await web3.eth.getBalance(serviceContract.address); + assert.equal(contractBalance, 200, "Keep Random Beacon service contract should receive ether."); + + let contractBalanceServiceContract = await web3.eth.getBalance(serviceContractProxy.address); + assert.equal(contractBalanceServiceContract, 200, "Keep Random Beacon contract new balance should be visible via serviceContractProxy."); + }); + + it("owner should be able to withdraw ether from random beacon service contract", async function() { + + await serviceContract.requestRelayEntry(0, {from: account_one, value: 100}) + + // should fail to withdraw if not owner + await exceptThrow(serviceContract.initiateWithdrawal({from: account_two})); + await exceptThrow(serviceContract.finishWithdrawal(account_two, {from: account_two})); + + await serviceContract.initiateWithdrawal({from: account_one}); + await exceptThrow(serviceContract.finishWithdrawal(account_three, {from: account_one})); + + // jump in time, full withdrawal delay + await increaseTimeTo(await latestTime()+duration.days(30)); + + let receiverStartBalance = await web3.eth.getBalance(account_three); + await serviceContract.finishWithdrawal(account_three, {from: account_one}); + let receiverEndBalance = await web3.eth.getBalance(account_three); + assert.isTrue(receiverEndBalance > receiverStartBalance, "Receiver updated balance should include received ether."); + + let contractEndBalance = await web3.eth.getBalance(serviceContract.address); + assert.equal(contractEndBalance, 0, "Keep Random Beacon contract should send all ether."); + let contractEndBalanceViaProxy = await web3.eth.getBalance(serviceContractProxy.address); + assert.equal(contractEndBalanceViaProxy, 0, "Keep Random Beacon contract updated balance should be visible via serviceContractProxy."); + + }); + + it("should fail to update minimum payment by non owner", async function() { + await exceptThrow(serviceContract.setMinimumPayment(123, {from: account_two})); + }); + + it("should be able to update minimum payment by the owner", async function() { + await serviceContract.setMinimumPayment(123); + let newMinPayment = await serviceContract.minimumPayment(); + assert.equal(newMinPayment, 123, "Should be able to get updated minimum payment."); + }); +}); diff --git a/contracts/solidity/test/TestKeepRandomBeaconUpgrade.js b/contracts/solidity/test/TestKeepRandomBeaconUpgrade.js deleted file mode 100644 index a30b95957c..0000000000 --- a/contracts/solidity/test/TestKeepRandomBeaconUpgrade.js +++ /dev/null @@ -1,61 +0,0 @@ -import {bls} from './helpers/data'; -import increaseTime, { duration } from './helpers/increaseTime'; -import exceptThrow from './helpers/expectThrow'; -const Proxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeaconImplV1 = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const Upgrade = artifacts.require('./examples/KeepRandomBeaconUpgradeExample.sol'); -const KeepGroup = artifacts.require('./KeepGroupStub.sol'); - - -contract('TestKeepRandomBeaconUpgrade', function(accounts) { - const relayRequestTimeout = 10; - - let implV1, implV2, proxy, implViaProxy, impl2ViaProxy, keepGroup, - account_one = accounts[0], - account_two = accounts[1]; - - beforeEach(async () => { - implV1 = await KeepRandomBeaconImplV1.new(); - implV2 = await Upgrade.new(); - proxy = await Proxy.new(implV1.address); - implViaProxy = await KeepRandomBeaconImplV1.at(proxy.address); - keepGroup = await KeepGroup.new(); - await implViaProxy.initialize(100, duration.days(0), bls.previousEntry, bls.groupPubKey, keepGroup.address, - relayRequestTimeout); - - // Add a few calls that modify state so we can test later that eternal storage works as expected after upgrade - await implViaProxy.requestRelayEntry(0, {from: account_two, value: 100}); - await implViaProxy.requestRelayEntry(0, {from: account_two, value: 100}); - await implViaProxy.requestRelayEntry(0, {from: account_two, value: 100}); - - }); - - it("should be able to check if the implementation contract was initialized", async function() { - let result = await implViaProxy.initialized(); - assert.equal(result, true, "Implementation contract should be initialized."); - }); - - it("should fail to upgrade implementation if called by not contract owner", async function() { - await exceptThrow(proxy.upgradeTo(implV2.address, {from: account_two})); - }); - - it("should be able to upgrade implementation and initialize it with new data", async function() { - await proxy.upgradeTo(implV2.address); - - impl2ViaProxy = await Upgrade.at(proxy.address); - await impl2ViaProxy.initialize(100, duration.days(0), bls.previousEntry, bls.groupPubKey, keepGroup.address, - relayRequestTimeout); - - let result = await impl2ViaProxy.initialized(); - assert.equal(result, true, "Implementation contract should be initialized."); - - let newVar = await impl2ViaProxy.getNewVar(); - assert.equal(newVar, 1234, "Should be able to get new data from upgraded contract."); - - await impl2ViaProxy.requestRelayEntry(0, {from: account_two, value: 100}) - - assert.equal((await impl2ViaProxy.getPastEvents())[0].args['requestID'], 6, "requestID should not be reset and should continue to increment where it was left in previous implementation."); - - }); - -}); diff --git a/contracts/solidity/test/TestKeepRandomBeaconViaProxy.js b/contracts/solidity/test/TestKeepRandomBeaconViaProxy.js deleted file mode 100644 index 9233102646..0000000000 --- a/contracts/solidity/test/TestKeepRandomBeaconViaProxy.js +++ /dev/null @@ -1,108 +0,0 @@ -import {bls} from './helpers/data'; -import increaseTime, { duration, increaseTimeTo } from './helpers/increaseTime'; -import latestTime from './helpers/latestTime'; -import exceptThrow from './helpers/expectThrow'; -import encodeCall from './helpers/encodeCall'; -const Proxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeaconImplV1 = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const KeepGroup = artifacts.require('./KeepGroupStub.sol'); - -contract('TestKeepRandomBeaconViaProxy', function(accounts) { - - const relayRequestTimeout = 10; - - let implV1, proxy, implViaProxy, keepGroup, - account_one = accounts[0], - account_two = accounts[1], - account_three = accounts[2]; - - beforeEach(async () => { - implV1 = await KeepRandomBeaconImplV1.new(); - proxy = await Proxy.new(implV1.address); - implViaProxy = await KeepRandomBeaconImplV1.at(proxy.address); - keepGroup = await KeepGroup.new(); - - await implViaProxy.initialize(100, duration.days(30), bls.previousEntry, bls.groupPubKey, keepGroup.address, - relayRequestTimeout); - }); - - it("should be able to check if the implementation contract was initialized", async function() { - let result = await implViaProxy.initialized(); - assert.equal(result, true, "Implementation contract should be initialized."); - }); - - it("should fail to request relay entry with not enough ether", async function() { - await exceptThrow(implViaProxy.requestRelayEntry(0, {from: account_two, value: 99})); - }); - - it("should be able to request relay entry via implementation contract with enough ether", async function() { - await implViaProxy.requestRelayEntry(0, {from: account_two, value: 100}) - - assert.equal((await implViaProxy.getPastEvents())[0].event, 'RelayEntryRequested', "RelayEntryRequested event should occur on the implementation contract."); - - let contractBalance = await web3.eth.getBalance(implViaProxy.address); - assert.equal(contractBalance, 100, "Keep Random Beacon contract should receive ether."); - - let contractBalanceViaProxy = await web3.eth.getBalance(proxy.address); - assert.equal(contractBalanceViaProxy, 100, "Keep Random Beacon contract new balance should be visible via proxy."); - - }); - - it("should be able to request relay entry via proxy contract with enough ether", async function() { - await exceptThrow(proxy.sendTransaction({from: account_two, value: 1000})); - - await web3.eth.sendTransaction({ - from: account_two, value: 100, gas: 200000, to: proxy.address, - data: encodeCall('requestRelayEntry', ['uint256'], [0]) - }); - - assert.equal((await implViaProxy.getPastEvents())[0].event, 'RelayEntryRequested', "RelayEntryRequested event should occur on the proxy contract."); - - let contractBalance = await web3.eth.getBalance(implViaProxy.address); - assert.equal(contractBalance, 100, "Keep Random Beacon contract should receive ether."); - - let contractBalanceViaProxy = await web3.eth.getBalance(proxy.address); - assert.equal(contractBalanceViaProxy, 100, "Keep Random Beacon contract new balance should be visible via proxy."); - }); - - it("owner should be able to withdraw ether from random beacon contract", async function() { - - let amount = web3.utils.toWei('1', 'ether'); - await web3.eth.sendTransaction({ - from: account_two, value: amount, gas: 200000, to: proxy.address, - data: encodeCall('requestRelayEntry', ['uint256'], [0]) - }); - - // should fail to withdraw if not owner - await exceptThrow(implViaProxy.initiateWithdrawal({from: account_two})); - await exceptThrow(implViaProxy.finishWithdrawal(account_two, {from: account_two})); - - await implViaProxy.initiateWithdrawal({from: account_one}); - await exceptThrow(implViaProxy.finishWithdrawal(account_three, {from: account_one})); - - let contractStartBalance = await web3.eth.getBalance(implViaProxy.address); - // jump in time, full withdrawal delay - await increaseTimeTo(await latestTime()+duration.days(30)); - - let receiverStartBalance = web3.utils.fromWei(await web3.eth.getBalance(account_three), 'ether'); - await implViaProxy.finishWithdrawal(account_three, {from: account_one}); - let receiverEndBalance = web3.utils.fromWei(await web3.eth.getBalance(account_three), 'ether'); - assert(Number(receiverEndBalance) > Number(receiverStartBalance), "Receiver updated balance should include received ether."); - - let contractEndBalance = await web3.eth.getBalance(implViaProxy.address); - assert.equal(contractEndBalance, contractStartBalance - amount, "Keep Random Beacon contract should send all ether."); - let contractEndBalanceViaProxy = await web3.eth.getBalance(proxy.address); - assert.equal(contractEndBalanceViaProxy, contractStartBalance - amount, "Keep Random Beacon contract updated balance should be visible via proxy."); - - }); - - it("should fail to update minimum payment by non owner", async function() { - await exceptThrow(implViaProxy.setMinimumPayment(123, {from: account_two})); - }); - - it("should be able to update minimum payment by the owner", async function() { - await implViaProxy.setMinimumPayment(123); - let newMinPayment = await implViaProxy.minimumPayment(); - assert.equal(newMinPayment, 123, "Should be able to get updated minimum payment."); - }); -}); diff --git a/contracts/solidity/test/TestPublishDkgResult.js b/contracts/solidity/test/TestPublishDkgResult.js deleted file mode 100644 index c40c4f15c6..0000000000 --- a/contracts/solidity/test/TestPublishDkgResult.js +++ /dev/null @@ -1,256 +0,0 @@ -import { duration } from './helpers/increaseTime'; -import {bls} from './helpers/data'; -import mineBlocks from './helpers/mineBlocks'; -import generateTickets from './helpers/generateTickets'; -import stakeDelegate from './helpers/stakeDelegate'; -import expectThrow from './helpers/expectThrow'; -import shuffleArray from './helpers/shuffle'; -const KeepToken = artifacts.require('./KeepToken.sol'); -const StakingProxy = artifacts.require('./StakingProxy.sol'); -const TokenStaking = artifacts.require('./TokenStaking.sol'); -const KeepRandomBeaconProxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeaconImplV1 = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const KeepGroupProxy = artifacts.require('./KeepGroup.sol'); -const KeepGroupImplV1 = artifacts.require('./KeepGroupImplV1.sol'); - - -contract('TestPublishDkgResult', function(accounts) { - - const minimumStake = 200000; - const groupThreshold = 15; - const groupSize = 20; - const timeoutInitial = 20; - const timeoutSubmission = 100; - const timeoutChallenge = 60; - const timeDKG = 20; - const resultPublicationBlockStep = 3; - const groupActiveTime = 300; - const activeGroupsThreshold = 5; - const relayRequestTimeout = 10; - - let disqualified, inactive, resultHash, - token, stakingProxy, stakingContract, randomBeaconValue, requestId, - keepRandomBeaconImplV1, keepRandomBeaconProxy, keepRandomBeaconImplViaProxy, - keepGroupImplV1, keepGroupProxy, keepGroupImplViaProxy, groupPubKey, - ticketSubmissionStartBlock, selectedParticipants, signatures, signingMemberIndices = [], - owner = accounts[0], magpie = accounts[0], - operator1 = accounts[0], tickets1, - operator2 = accounts[1], tickets2, - operator3 = accounts[2], tickets3, - operator4 = accounts[3]; - requestId = 0; - disqualified = '0x0000000000000000000000000000000000000000' - inactive = '0x0000000000000000000000000000000000000000' - groupPubKey = "0x1000000000000000000000000000000000000000000000000000000000000000" - - resultHash = web3.utils.soliditySha3(groupPubKey, disqualified, inactive); - - beforeEach(async () => { - token = await KeepToken.new(); - - // Initialize staking contract under proxy - stakingProxy = await StakingProxy.new(); - stakingContract = await TokenStaking.new(token.address, stakingProxy.address, duration.days(30)); - await stakingProxy.authorizeContract(stakingContract.address, {from: owner}) - - // Initialize Keep Random Beacon contract - keepRandomBeaconImplV1 = await KeepRandomBeaconImplV1.new(); - keepRandomBeaconProxy = await KeepRandomBeaconProxy.new(keepRandomBeaconImplV1.address); - keepRandomBeaconImplViaProxy = await KeepRandomBeaconImplV1.at(keepRandomBeaconProxy.address); - - // Initialize Keep Group contract - keepGroupImplV1 = await KeepGroupImplV1.new(); - keepGroupProxy = await KeepGroupProxy.new(keepGroupImplV1.address); - keepGroupImplViaProxy = await KeepGroupImplV1.at(keepGroupProxy.address); - await keepGroupImplViaProxy.initialize( - stakingProxy.address, keepRandomBeaconProxy.address, minimumStake, groupThreshold, - groupSize, timeoutInitial, timeoutSubmission, timeoutChallenge, timeDKG, resultPublicationBlockStep, - activeGroupsThreshold, groupActiveTime - ); - - randomBeaconValue = bls.groupSignature; - - await keepRandomBeaconImplViaProxy.initialize(1,1, randomBeaconValue, bls.groupPubKey, keepGroupProxy.address, - relayRequestTimeout); - await keepRandomBeaconImplViaProxy.relayEntry(1, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); - - await stakeDelegate(stakingContract, token, owner, operator1, magpie, minimumStake*2000) - await stakeDelegate(stakingContract, token, owner, operator2, magpie, minimumStake*2000) - await stakeDelegate(stakingContract, token, owner, operator3, magpie, minimumStake*3000) - - tickets1 = generateTickets(randomBeaconValue, operator1, 2000); - tickets2 = generateTickets(randomBeaconValue, operator2, 2000); - tickets3 = generateTickets(randomBeaconValue, operator3, 3000); - - for(let i = 0; i < groupSize; i++) { - await keepGroupImplViaProxy.submitTicket(tickets1[i].value, operator1, tickets1[i].virtualStakerIndex, {from: operator1}); - } - - for(let i = 0; i < groupSize; i++) { - await keepGroupImplViaProxy.submitTicket(tickets2[i].value, operator2, tickets2[i].virtualStakerIndex, {from: operator2}); - } - - for(let i = 0; i < groupSize; i++) { - await keepGroupImplViaProxy.submitTicket(tickets3[i].value, operator3, tickets3[i].virtualStakerIndex, {from: operator3}); - } - - ticketSubmissionStartBlock = await keepGroupImplViaProxy.ticketSubmissionStartBlock(); - selectedParticipants = await keepGroupImplViaProxy.selectedParticipants(); - - for(let i = 0; i < selectedParticipants.length; i++) { - let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); - signingMemberIndices.push(i+1); - if (signatures == undefined) signatures = signature - else signatures += signature.slice(2, signature.length); - } - }); - - it("should be able to submit correct result as first member after DKG finished.", async function() { - - // Jump in time to when submitter becomes eligible to submit - let currentBlock = await web3.eth.getBlockNumber(); - mineBlocks(ticketSubmissionStartBlock.toNumber() + timeoutChallenge + timeDKG - currentBlock); - - await keepGroupImplViaProxy.submitDkgResult(requestId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, {from: selectedParticipants[0]}) - let submitted = await keepGroupImplViaProxy.isDkgResultSubmitted.call(requestId); - assert.equal(submitted, true, "DkgResult should should be submitted"); - }); - - it("should be able to submit correct result with unordered signatures and indexes.", async function() { - - let unorderedSigningMembersIndexes = []; - for (let i = 0; i < selectedParticipants.length; i++) { - unorderedSigningMembersIndexes[i] = i + 1; - } - - unorderedSigningMembersIndexes = shuffleArray(unorderedSigningMembersIndexes); - let unorderedSignatures; - - for(let i = 0; i < selectedParticipants.length; i++) { - let signature = await web3.eth.sign(resultHash, selectedParticipants[unorderedSigningMembersIndexes[i] - 1]); - if (unorderedSignatures == undefined) unorderedSignatures = signature - else unorderedSignatures += signature.slice(2, signature.length); - } - - // Jump in time to when submitter becomes eligible to submit - let currentBlock = await web3.eth.getBlockNumber(); - mineBlocks(ticketSubmissionStartBlock.toNumber() + timeoutChallenge + timeDKG - currentBlock); - - await keepGroupImplViaProxy.submitDkgResult(requestId, 1, groupPubKey, disqualified, inactive, unorderedSignatures, unorderedSigningMembersIndexes, {from: selectedParticipants[0]}) - let submitted = await keepGroupImplViaProxy.isDkgResultSubmitted.call(requestId); - assert.equal(submitted, true, "DkgResult should should be submitted"); - }); - - it("should only be able to submit result at eligible block time based on member index.", async function() { - - let submitter1MemberIndex = 4; - let submitter2MemberIndex = 5; - let submitter2 = selectedParticipants[submitter2MemberIndex - 1]; - let eligibleBlockForSubmitter1 = ticketSubmissionStartBlock.toNumber() + timeoutChallenge + timeDKG + (submitter1MemberIndex-1)*resultPublicationBlockStep; - let eligibleBlockForSubmitter2 = ticketSubmissionStartBlock.toNumber() + timeoutChallenge + timeDKG + (submitter2MemberIndex-1)*resultPublicationBlockStep; - - // Jump in time to when submitter 1 becomes eligible to submit - let currentBlock = await web3.eth.getBlockNumber(); - mineBlocks(eligibleBlockForSubmitter1 - currentBlock); - - // Should throw if non eligible submitter 2 tries to submit - await expectThrow(keepGroupImplViaProxy.submitDkgResult( - requestId, submitter2MemberIndex, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, - {from: submitter2}) - ); - - // Jump in time to when submitter 2 becomes eligible to submit - currentBlock = await web3.eth.getBlockNumber(); - mineBlocks(eligibleBlockForSubmitter2 - currentBlock); - - await keepGroupImplViaProxy.submitDkgResult(requestId, submitter2MemberIndex, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, {from: submitter2}) - let submitted = await keepGroupImplViaProxy.isDkgResultSubmitted.call(requestId); - assert.equal(submitted, true, "DkgResult should be submitted"); - }); - - it("should not be able to submit if submitter was not selected to be part of the group.", async function() { - await expectThrow(keepGroupImplViaProxy.submitDkgResult( - requestId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, - {from: operator4}) - ); - }); - - it("should reject the result with invalid signatures.", async function() { - - signingMemberIndices = []; - signatures = undefined; - let lastParticipantIdx = groupThreshold - 1; - - // Create less than minimum amount of valid signatures - for(let i = 0; i < lastParticipantIdx; i++) { - let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); - signingMemberIndices.push(i+1); - if (signatures == undefined) signatures = signature - else signatures += signature.slice(2, signature.length); - } - - // Add invalid signature as the last one - let nonsenseHash = web3.utils.soliditySha3("ducky duck"); - let invalidSignature = await web3.eth.sign(nonsenseHash, selectedParticipants[lastParticipantIdx]); - signatures += invalidSignature.slice(2, invalidSignature.length); - signingMemberIndices.push(lastParticipantIdx); - - // Jump in time to when first member is eligible to submit - let currentBlock = await web3.eth.getBlockNumber(); - mineBlocks(ticketSubmissionStartBlock.toNumber() + timeoutChallenge + timeDKG - currentBlock); - - await expectThrow(keepGroupImplViaProxy.submitDkgResult( - requestId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, - {from: selectedParticipants[0]}) - ); - }); - - it("should be able to submit the result with minimum number of valid signatures", async function() { - - signingMemberIndices = []; - signatures = undefined; - - // Create minimum amount of valid signatures - for(let i = 0; i < groupThreshold; i++) { - let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); - signingMemberIndices.push(i+1); - if (signatures == undefined) signatures = signature - else signatures += signature.slice(2, signature.length); - } - - // Jump in time to when first member is eligible to submit - let currentBlock = await web3.eth.getBlockNumber(); - mineBlocks(ticketSubmissionStartBlock.toNumber() + timeoutChallenge + timeDKG - currentBlock); - - await keepGroupImplViaProxy.submitDkgResult( - requestId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, - {from: selectedParticipants[0]}) - let submitted = await keepGroupImplViaProxy.isDkgResultSubmitted.call(requestId); - assert.equal(submitted, true, "DkgResult should should be submitted"); - - }); - - it("should not be able to submit without minimum number of signatures", async function() { - - signingMemberIndices = []; - signatures = undefined; - - // Create less than minimum amount of valid signatures - for(let i = 0; i < groupThreshold - 1; i++) { - let signature = await web3.eth.sign(resultHash, selectedParticipants[i]); - signingMemberIndices.push(i+1); - if (signatures == undefined) signatures = signature - else signatures += signature.slice(2, signature.length); - } - - // Jump in time to when first member is eligible to submit - let currentBlock = await web3.eth.getBlockNumber(); - mineBlocks(ticketSubmissionStartBlock.toNumber() + timeoutChallenge + timeDKG - currentBlock); - - await expectThrow(keepGroupImplViaProxy.submitDkgResult( - requestId, 1, groupPubKey, disqualified, inactive, signatures, signingMemberIndices, - {from: selectedParticipants[0]}) - ); - - }); -}) diff --git a/contracts/solidity/test/TestRelayEntry.js b/contracts/solidity/test/TestRelayEntry.js deleted file mode 100644 index fc2d32c123..0000000000 --- a/contracts/solidity/test/TestRelayEntry.js +++ /dev/null @@ -1,46 +0,0 @@ -import exceptThrow from './helpers/expectThrow'; -import {bls} from './helpers/data'; -const KeepRandomBeaconProxy = artifacts.require('./KeepRandomBeacon.sol'); -const KeepRandomBeaconImplV1 = artifacts.require('./KeepRandomBeaconImplV1.sol'); -const KeepGroupStub = artifacts.require('./KeepGroupStub.sol'); - - -contract('TestRelayEntry', function() { - const relayRequestTimeout = 10; - - let keepRandomBeaconImplV1, keepRandomBeaconProxy, keepRandomBeaconImplViaProxy, keepGroupStub; - - beforeEach(async () => { - - // Initialize Keep Random Beacon contract - keepRandomBeaconImplV1 = await KeepRandomBeaconImplV1.new(); - keepRandomBeaconProxy = await KeepRandomBeaconProxy.new(keepRandomBeaconImplV1.address); - keepRandomBeaconImplViaProxy = await KeepRandomBeaconImplV1.at(keepRandomBeaconProxy.address); - - keepGroupStub = await KeepGroupStub.new(); - await keepRandomBeaconImplViaProxy.initialize(1,1, bls.previousEntry, bls.groupPubKey, keepGroupStub.address, - relayRequestTimeout); - await keepRandomBeaconImplViaProxy.requestRelayEntry(bls.seed, {value: 10}); - }); - - it("should not be able to submit invalid relay entry", async function() { - let requestID = 1; - - // Invalid signature - let groupSignature = web3.utils.toBN('0x0fb34abfa2a9844a58776650e399bca3e08ab134e42595e03e3efc5a0472bcd8'); - - await exceptThrow(keepRandomBeaconImplViaProxy.relayEntry(requestID, groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed)); - }); - - it("should be able to submit valid relay entry", async function() { - let requestID = 1; - - await keepRandomBeaconImplViaProxy.relayEntry(requestID, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); - - assert.equal((await keepRandomBeaconImplViaProxy.getPastEvents())[0].args['requestResponse'].toString(), - bls.groupSignature.toString(), "Should emit event with successfully submitted groupSignature." - ); - - }); - -}); diff --git a/contracts/solidity/test/TestToken.js b/contracts/solidity/test/TestToken.js new file mode 100644 index 0000000000..db7ec007d8 --- /dev/null +++ b/contracts/solidity/test/TestToken.js @@ -0,0 +1,31 @@ +const KeepToken = artifacts.require('./KeepToken.sol'); + +contract('TestToken', function(accounts) { + + let token, + account_one = accounts[0], + account_two = accounts[1]; + + before(async () => { + token = await KeepToken.new(); + }); + + it("should send tokens correctly", async function() { + let amount = web3.utils.toBN(1000000000); + + // Starting balances + let account_one_starting_balance = await token.balanceOf.call(account_one); + let account_two_starting_balance = await token.balanceOf.call(account_two); + + // Send tokens + await token.transfer(account_two, amount, {from: account_one}); + + // Ending balances + let account_one_ending_balance = await token.balanceOf.call(account_one); + let account_two_ending_balance = await token.balanceOf.call(account_two); + + assert.equal(account_one_ending_balance.eq(account_one_starting_balance.sub(amount)), true, "Amount wasn't correctly taken from the sender"); + assert.equal(account_two_ending_balance.eq(account_two_starting_balance.add(amount)), true, "Amount wasn't correctly sent to the receiver"); + + }); +}); diff --git a/contracts/solidity/test/TestTokenGrant.js b/contracts/solidity/test/TestTokenGrant.js index c0aef953d5..20e0192e16 100644 --- a/contracts/solidity/test/TestTokenGrant.js +++ b/contracts/solidity/test/TestTokenGrant.js @@ -5,7 +5,7 @@ const KeepToken = artifacts.require('./KeepToken.sol'); const TokenGrant = artifacts.require('./TokenGrant.sol'); const StakingProxy = artifacts.require('./StakingProxy.sol'); -contract('TestTokenGrants', function(accounts) { +contract('TestTokenGrant', function(accounts) { let token, grantContract, stakingProxy, amount, vestingDuration, start, cliff, @@ -13,7 +13,7 @@ contract('TestTokenGrants', function(accounts) { account_two = accounts[1], beneficiary = accounts[2]; - beforeEach(async () => { + before(async () => { token = await KeepToken.new(); stakingProxy = await StakingProxy.new(); grantContract = await TokenGrant.new(token.address, stakingProxy.address, duration.days(30)); @@ -124,6 +124,7 @@ contract('TestTokenGrants', function(accounts) { it("should be able to revoke revocable token grant as grant owner.", async function() { let account_one_starting_balance = await token.balanceOf.call(account_one); + let beneficiary_starting_balance = await grantContract.balanceOf.call(beneficiary); // Create revocable token grant. await token.approve(grantContract.address, amount, {from: account_one}); @@ -133,17 +134,22 @@ contract('TestTokenGrants', function(accounts) { let account_one_ending_balance = await token.balanceOf.call(account_one); assert.equal(account_one_ending_balance.eq(account_one_starting_balance.sub(amount)), true, "Amount should be taken out from grant creator main balance."); - assert.equal((await grantContract.balanceOf.call(beneficiary)).eq(amount), true, "Amount should be added to beneficiary's granted balance."); + assert.equal((await grantContract.balanceOf.call(beneficiary)).eq(beneficiary_starting_balance.add(amount)), true, "Amount should be added to beneficiary's granted balance."); await grantContract.revoke(id); - assert.equal((await token.balanceOf.call(account_one)).eq(account_one_starting_balance), true, "Amount should be added to grant creator main balance."); - assert.equal((await grantContract.balanceOf.call(beneficiary)).isZero(), true, "Amount should be taken out from beneficiary's granted balance."); + beneficiary_starting_balance = await grantContract.balanceOf.call(beneficiary); + account_one_starting_balance = await token.balanceOf.call(account_one); + + let unreleasedAmount = await grantContract.unreleasedAmount(id) + assert.equal((await token.balanceOf.call(account_one)).eq(account_one_starting_balance.add(amount).sub(unreleasedAmount)), true, "Amount should be added to grant creator main balance."); + assert.equal((await grantContract.balanceOf.call(beneficiary)).eq(beneficiary_starting_balance.sub(amount).add(unreleasedAmount)), true, "Amount should be taken out from beneficiary's granted balance."); }); it("should be able to revoke the grant but no amount is refunded since duration of the vesting is over.", async function() { let account_one_starting_balance = await token.balanceOf.call(account_one); + let beneficiary_starting_balance = await grantContract.balanceOf.call(beneficiary); // Create revocable token grant with 0 duration. await token.approve(grantContract.address, amount, {from: account_one}); @@ -153,12 +159,13 @@ contract('TestTokenGrants', function(accounts) { let account_one_ending_balance = await token.balanceOf.call(account_one); assert.equal(account_one_ending_balance.eq(account_one_starting_balance.sub(amount)), true, "Amount should be taken out from grant creator main balance."); - assert.equal((await grantContract.balanceOf.call(beneficiary)).eq(amount), true, "Amount should be added to beneficiary's granted balance."); + assert.equal((await grantContract.balanceOf.call(beneficiary)).eq(beneficiary_starting_balance.add(amount)), true, "Amount should be added to beneficiary's granted balance."); + beneficiary_starting_balance = await grantContract.balanceOf.call(beneficiary); await grantContract.revoke(id); assert.equal((await token.balanceOf.call(account_one)).eq(account_one_starting_balance.sub(amount)), true, "No amount to be returned to grant creator since vesting duration is over."); - assert.equal((await grantContract.balanceOf.call(beneficiary)).eq(amount), true, "Amount should stay at beneficiary's grant balance."); + assert.equal((await grantContract.balanceOf.call(beneficiary)).eq(beneficiary_starting_balance), true, "Amount should stay at beneficiary's grant balance."); }); }); diff --git a/contracts/solidity/test/TestStakeTokenGrantsViaProxy.js b/contracts/solidity/test/TestTokenGrantStakeViaProxy.js similarity index 88% rename from contracts/solidity/test/TestStakeTokenGrantsViaProxy.js rename to contracts/solidity/test/TestTokenGrantStakeViaProxy.js index 4140b0e41e..0035fe180d 100644 --- a/contracts/solidity/test/TestStakeTokenGrantsViaProxy.js +++ b/contracts/solidity/test/TestTokenGrantStakeViaProxy.js @@ -5,14 +5,14 @@ const KeepToken = artifacts.require('./KeepToken.sol'); const StakingProxy = artifacts.require('./StakingProxy.sol'); const TokenGrant = artifacts.require('./TokenGrant.sol'); -contract('TestStakeTokenGrantsViaProxy', function(accounts) { +contract('TestTokenGrantStakeViaProxy', function(accounts) { let token, stakingProxy, grantContract, account_one = accounts[0], account_two = accounts[1], account_two_operator = accounts[2]; - beforeEach(async () => { + before(async () => { token = await KeepToken.new(); }); @@ -53,7 +53,7 @@ contract('TestStakeTokenGrantsViaProxy', function(accounts) { // Owner of stakingProxy should be able to authorize a token grant contract await stakingProxy.authorizeContract(grantContract.address, {from: account_one}) - assert.equal(await stakingProxy.isAuthorized(grantContract.address), true, "StakingProxy owner should be able to authorize a token grant contract."); + assert.isTrue(await stakingProxy.isAuthorized(grantContract.address), "StakingProxy owner should be able to authorize a token grant contract."); // Stake granted tokens await grantContract.stake(id, delegation, {from: account_two}) @@ -65,7 +65,7 @@ contract('TestStakeTokenGrantsViaProxy', function(accounts) { // Owner of stakingProxy should be able to deauthorize a token grant contract await stakingProxy.deauthorizeContract(grantContract.address, {from: account_one}) - assert.equal(await stakingProxy.isAuthorized(grantContract.address), false, "StakingProxy owner should be able to deauthorize a token grant contract."); + assert.isFalse(await stakingProxy.isAuthorized(grantContract.address), "StakingProxy owner should be able to deauthorize a token grant contract."); }); }); diff --git a/contracts/solidity/test/keeptoken.js b/contracts/solidity/test/TestTokenStake.js similarity index 98% rename from contracts/solidity/test/keeptoken.js rename to contracts/solidity/test/TestTokenStake.js index 561fc81394..2a7f99f2b9 100644 --- a/contracts/solidity/test/keeptoken.js +++ b/contracts/solidity/test/TestTokenStake.js @@ -6,7 +6,7 @@ const TokenStaking = artifacts.require('./TokenStaking.sol'); const TokenGrant = artifacts.require('./TokenGrant.sol'); const StakingProxy = artifacts.require('./StakingProxy.sol'); -contract('KeepToken', function(accounts) { +contract('TestTokenStake', function(accounts) { let token, grantContract, stakingContract, stakingProxy, account_one = accounts[0], @@ -16,7 +16,7 @@ contract('KeepToken', function(accounts) { account_two_operator = accounts[4], account_two_magpie = accounts[5]; - beforeEach(async () => { + before(async () => { token = await KeepToken.new(); stakingProxy = await StakingProxy.new(); stakingContract = await TokenStaking.new(token.address, stakingProxy.address, duration.days(30)); diff --git a/contracts/solidity/test/TestStakeViaProxy.js b/contracts/solidity/test/TestTokenStakeViaProxy.js similarity index 90% rename from contracts/solidity/test/TestStakeViaProxy.js rename to contracts/solidity/test/TestTokenStakeViaProxy.js index a9919394c7..611d729921 100644 --- a/contracts/solidity/test/TestStakeViaProxy.js +++ b/contracts/solidity/test/TestTokenStakeViaProxy.js @@ -4,13 +4,13 @@ const KeepToken = artifacts.require('./KeepToken.sol'); const StakingProxy = artifacts.require('./StakingProxy.sol'); const TokenStaking = artifacts.require('./TokenStaking.sol'); -contract('TestStakeViaProxy', function(accounts) { +contract('TestTokenStakeViaProxy', function(accounts) { let token, stakingProxy, stakingContract, owner = accounts[0], operator = accounts[1]; - beforeEach(async () => { + before(async () => { token = await KeepToken.new(); }); @@ -31,7 +31,7 @@ contract('TestStakeViaProxy', function(accounts) { // Owner of stakingProxy should be able to authorize a staking contract await stakingProxy.authorizeContract(stakingContract.address, {from: owner}) - assert.equal(await stakingProxy.isAuthorized(stakingContract.address), true, "StakingProxy owner should be able to authorize a staking contract."); + assert.isTrue(await stakingProxy.isAuthorized(stakingContract.address), "StakingProxy owner should be able to authorize a staking contract."); // Stake tokens using approveAndCall pattern await token.approveAndCall(stakingContract.address, stakingAmount, delegation, {from: owner}); diff --git a/contracts/solidity/test/helpers/initContracts.js b/contracts/solidity/test/helpers/initContracts.js new file mode 100644 index 0000000000..59ec8d3187 --- /dev/null +++ b/contracts/solidity/test/helpers/initContracts.js @@ -0,0 +1,60 @@ +import { duration } from './increaseTime'; +import { bls } from './data'; + +async function initContracts(accounts, KeepToken, StakingProxy, TokenStaking, KeepRandomBeaconService, + KeepRandomBeaconServiceImplV1, KeepRandomBeaconOperator) { + + let token, stakingProxy, stakingContract, + serviceContractImplV1, serviceContractProxy, serviceContract, + operatorContract; + + let minimumStake = 200000, + groupThreshold = 15, + groupSize = 20, + timeoutInitial = 20, + timeoutSubmission = 100, + timeoutChallenge = 60, + timeDKG = 20, + resultPublicationBlockStep = 3, + groupActiveTime = 300, + activeGroupsThreshold = 5, + minPayment = 1, + withdrawalDelay = 1, + relayRequestTimeout = 10; + + // Initialize Keep token contract + token = await KeepToken.new(); + + // Initialize staking contract under proxy + stakingProxy = await StakingProxy.new(); + stakingContract = await TokenStaking.new(token.address, stakingProxy.address, duration.days(30)); + await stakingProxy.authorizeContract(stakingContract.address, {from: accounts[0]}) + + // Initialize Keep Random Beacon service contract + serviceContractImplV1 = await KeepRandomBeaconServiceImplV1.new(); + serviceContractProxy = await KeepRandomBeaconService.new(serviceContractImplV1.address); + serviceContract = await KeepRandomBeaconServiceImplV1.at(serviceContractProxy.address) + + // Initialize Keep Random Beacon operator contract + operatorContract = await KeepRandomBeaconOperator.new(); + await operatorContract.initialize( + stakingProxy.address, serviceContract.address, minimumStake, groupThreshold, + groupSize, timeoutInitial, timeoutSubmission, timeoutChallenge, timeDKG, resultPublicationBlockStep, + activeGroupsThreshold, groupActiveTime, relayRequestTimeout, + bls.groupSignature, bls.groupPubKey + ); + + await serviceContract.initialize(minPayment, withdrawalDelay, operatorContract.address); + + // TODO: replace with a secure authorization protocol (addressed in RFC 4). + await operatorContract.authorizeStakingContract(stakingContract.address); + await operatorContract.relayEntry(1, bls.groupSignature, bls.groupPubKey, bls.previousEntry, bls.seed); + + return { + token: token, + serviceContract: serviceContract, + operatorContract: operatorContract + }; +}; + +module.exports.initContracts = initContracts; diff --git a/contracts/solidity/test/helpers/stakeDelegate.js b/contracts/solidity/test/helpers/stakeDelegate.js index a908306cb2..d0453c89e3 100644 --- a/contracts/solidity/test/helpers/stakeDelegate.js +++ b/contracts/solidity/test/helpers/stakeDelegate.js @@ -1,5 +1,5 @@ -export default async function stakeDelegate(stakingContract, token, owner, operator, magpie,stake) { - let signature = Buffer.from((await web3.eth.sign(web3.utils.soliditySha3(owner), operator)).substr(2), 'hex'); - let delegation = '0x' + Buffer.concat([Buffer.from(magpie.substr(2), 'hex'), signature]).toString('hex'); - token.approveAndCall(stakingContract.address, stake, delegation, {from: owner}); +export default async function stakeDelegate(stakingContract, token, owner, operator, magpie, stake) { + let signature = Buffer.from((await web3.eth.sign(web3.utils.soliditySha3(owner), operator)).substr(2), 'hex'); + let delegation = '0x' + Buffer.concat([Buffer.from(magpie.substr(2), 'hex'), signature]).toString('hex'); + token.approveAndCall(stakingContract, stake, delegation, {from: owner}); } \ No newline at end of file diff --git a/docs/development/local-keep-network.adoc b/docs/development/local-keep-network.adoc index 39f001f5aa..3857d85e58 100644 --- a/docs/development/local-keep-network.adoc +++ b/docs/development/local-keep-network.adoc @@ -270,20 +270,15 @@ Running migration: 2_deploy_contracts.js Replacing TokenGrant... ... 0xf3d4a78110ddd2003bd4c47bb59a0165e8468b3f38777131731d0829a5a3cfbf TokenGrant: 0x24e006907b85482b86c335c0c8e15c9ca49e6800 - Replacing KeepRandomBeaconImplV1... + Replacing KeepRandomBeaconServiceImplV1... ... 0x03a560288292005f2181fe561461aa70b521741349641c31525f64c1482caf25 - KeepRandomBeaconImplV1: 0x3179d9c794e597d6316736189bf040b74a2f1dd7 - Replacing KeepRandomBeacon... + KeepRandomBeaconServiceImplV1: 0x3179d9c794e597d6316736189bf040b74a2f1dd7 + Replacing KeepRandomBeaconService... ... 0x9facb5fe566862e67e50d6ad0fc622f717ee5cb795c7044ba9ad2ff32f9faa70 - KeepRandomBeacon: 0x15045ff30d6327345cc052cc4b8c28dbe974a74b - Replacing KeepGroupImplV1... + KeepRandomBeaconService: 0x15045ff30d6327345cc052cc4b8c28dbe974a74b + Replacing KeepRandomBeaconOperator... ... 0x9e49a94de6dfbc6496c89bb3edff8201ad407ba906893029185f72be2c4e9528 - KeepGroupImplV1: 0x9da7876f5404dde662bf5cbc6ca1462e777571ff - Replacing KeepGroup... - ... 0xf4de680e9775a788680403e79b4682947b37295263ec7f14b772b5378bd6ad49 - KeepGroup: 0xdff3075ca23fe28697d5c4f171cf04abd79bd837 - ... 0xe6d7bd7377a3e83ba521e4df55604a0dec63fc8011820e8f128a2bf3fe36aab8 - ... 0x7efce8f03761ca2526c37ed6076260b6de2c6b7535f739bc0db1565e3824afbe + KeepRandomBeaconOperator: 0x9da7876f5404dde662bf5cbc6ca1462e777571ff Saving successful migration to network... ... 0xcb9a9ab4d9a0c153a7a24786d4aa1b61feb2b0278fd6fa2d91222e7324cce187 Saving artifacts... @@ -329,12 +324,12 @@ point to the secound account (the first one is used by Ethereum client): ``` Update `[ethereum.ContractAddresses]` section to point to the previously -deployed contract instances. Please use addresses of `KeepGroup` and +deployed contract instances. Please use addresses of `KeepRandomBeaconOperator` and `KeepRandomBeacon` contracts: ``` [ethereum.ContractAddresses] - KeepRandomBeacon = "0x15045ff30d6327345cc052cc4b8c28dbe974a74b" - KeepGroup = "0xdff3075ca23fe28697d5c4f171cf04abd79bd837" + KeepRandomBeaconService = "0x15045ff30d6327345cc052cc4b8c28dbe974a74b" + KeepRandomBeaconOperator = "0xdff3075ca23fe28697d5c4f171cf04abd79bd837" ``` Next, create configuration files for non-bootstrap peers: diff --git a/docs/random-beacon/signing/sign.py b/docs/random-beacon/signing/sign.py index 21c493b273..c39c585fa3 100644 --- a/docs/random-beacon/signing/sign.py +++ b/docs/random-beacon/signing/sign.py @@ -6,8 +6,8 @@ class Request(NamedTuple): openTimeout: Blockheight previousOutput: BlsSignature -def openCommitment(requestID, seed_i): - request_i = Requests[requestID] # abort if no such request +def openCommitment(signingId, seed_i): + request_i = Requests[signingId] # abort if no such request senderPubkey = getSenderPubkey() T_open = getCurrentBlockheight() @@ -36,12 +36,12 @@ def openCommitment(requestID, seed_i): signingInput = beaconInput ) - OutputInProgress[requestID] = outputWaiting + OutputInProgress[signingId] = outputWaiting -def receiveOutput(requestID, outputSignature): - outputWaiting = OutputInProgress[requestID] # abort if not found - request_i = Requests[requestID] +def receiveOutput(signingId, outputSignature): + outputWaiting = OutputInProgress[signingId] # abort if not found + request_i = Requests[signingId] pubkey_Group_i = outputWaiting.signingGroup.groupPubkey input_i = outputWaiting.signingInput diff --git a/docs/rfc/rfc-13-continuous-delivery.adoc b/docs/rfc/rfc-13-continuous-delivery.adoc index 7509c74816..0ead86763b 100644 --- a/docs/rfc/rfc-13-continuous-delivery.adoc +++ b/docs/rfc/rfc-13-continuous-delivery.adoc @@ -75,10 +75,8 @@ contracts before clients due to client startup behavior. KeyFile = "path/to/in-use/keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0x6299496199d9922193fdd2d717ef585f431er01" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0x6299496449d11141193fdd2d717ef585f431er02" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0x6299496449d11141193fdd2d717ef585f431er02" # StakingProxy Staking = "0x6297776199d99942293fdd2d717ef585f431er03" @@ -104,10 +102,8 @@ contracts before clients due to client startup behavior. KeyFile = "path/to/in-use/keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0x6299496199d9922193fdd2d717ef585f431er01" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0x6299496449d11141193fdd2d717ef585f431er02" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0x6299496449d11141193fdd2d717ef585f431er02" # Hex-encoded address of StakingProxy contract Staking = "0x6297776199d99942293fdd2d717ef585f431er03" diff --git a/infrastructure/kube/keep-dev/kube-setup.org b/infrastructure/kube/keep-dev/kube-setup.org index b7232a0f1b..0ff485ee7c 100644 --- a/infrastructure/kube/keep-dev/kube-setup.org +++ b/infrastructure/kube/keep-dev/kube-setup.org @@ -451,10 +451,8 @@ keep-client-config.toml: KeyFile = "/mnt/eth-data/eth-keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0x9d28652e7777946a5Cf1E738E38024cb14993be9" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0xc9e8CA4765A797C2d614a87de000b35240b86610" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0xc9e8CA4765A797C2d614a87de000b35240b86610" # StakingProxy Staking = "0xC3920F936b251DFE4B4b62C3b62FB530D032a090" @@ -539,10 +537,8 @@ keep-client-config.toml: KeyFile = "/mnt/eth-data/eth-keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0x9d28652e7777946a5Cf1E738E38024cb14993be9" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0xc9e8CA4765A797C2d614a87de000b35240b86610" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0xc9e8CA4765A797C2d614a87de000b35240b86610" # StakingProxy Staking = "0xC3920F936b251DFE4B4b62C3b62FB530D032a090" @@ -626,10 +622,8 @@ keep-client-config.toml: KeyFile = "/mnt/eth-data/eth-keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0x9d28652e7777946a5Cf1E738E38024cb14993be9" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0xc9e8CA4765A797C2d614a87de000b35240b86610" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0xc9e8CA4765A797C2d614a87de000b35240b86610" # StakingProxy Staking = "0xC3920F936b251DFE4B4b62C3b62FB530D032a090" @@ -713,10 +707,8 @@ keep-client-config.toml: KeyFile = "/mnt/eth-data/eth-keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0x9d28652e7777946a5Cf1E738E38024cb14993be9" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0xc9e8CA4765A797C2d614a87de000b35240b86610" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0xc9e8CA4765A797C2d614a87de000b35240b86610" # StakingProxy Staking = "0xC3920F936b251DFE4B4b62C3b62FB530D032a090" @@ -799,10 +791,8 @@ keep-client-config.toml: KeyFile = "/mnt/eth-data/eth-keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "0x9d28652e7777946a5Cf1E738E38024cb14993be9" - # Hex-encoded address of KeepGroup contract - KeepGroup = "0xc9e8CA4765A797C2d614a87de000b35240b86610" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "0xc9e8CA4765A797C2d614a87de000b35240b86610" # StakingProxy Staking = "0xC3920F936b251DFE4B4b62C3b62FB530D032a090" diff --git a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/Dockerfile b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/Dockerfile index 87b9f3bdff..9677ef1181 100644 --- a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/Dockerfile +++ b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/Dockerfile @@ -25,8 +25,8 @@ ENV ETH_NETWORK_ID=$ETH_NETWORK_ID COPY ./StakingProxy.json /tmp/StakingProxy.json COPY ./TokenStaking.json /tmp/TokenStaking.json COPY ./KeepToken.json /tmp/KeepToken.json -COPY ./KeepRandomBeacon.json /tmp/KeepRandomBeacon.json -COPY ./KeepGroup.json /tmp/KeepGroup.json +COPY ./KeepRandomBeaconService.json /tmp/KeepRandomBeaconService.json +COPY ./KeepRandomBeaconOperator.json /tmp/KeepRandomBeaconOperator.json COPY ./keep-client-bootstrap-peer-template.toml /tmp/keep-client-bootstrap-peer-template.toml COPY ./keep-client-standard-peer-template.toml /tmp/keep-client-standard-peer-template.toml diff --git a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-bootstrap-peer-template.toml b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-bootstrap-peer-template.toml index 80f5410649..a50e8d5edf 100644 --- a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-bootstrap-peer-template.toml +++ b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-bootstrap-peer-template.toml @@ -9,10 +9,8 @@ KeyFile = "/mnt/eth-data/eth-keyfile" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "Set by Kube" - # Hex-encoded address of KeepGroup contract - KeepGroup = "Set by Kube" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "Set by Kube" # StakingProxy StakingProxy = "Set by Kube" diff --git a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-standard-peer-template.toml b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-standard-peer-template.toml index eb054d4999..c2347060d9 100644 --- a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-standard-peer-template.toml +++ b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/keep-client-standard-peer-template.toml @@ -9,10 +9,8 @@ KeyFile = "Set by Kube" [ethereum.ContractAddresses] - # Hex-encoded address of KeepRandomBeacon contract - KeepRandomBeacon = "Set by Kube" - # Hex-encoded address of KeepGroup contract - KeepGroup = "Set by Kube" + # Hex-encoded address of KeepRandomBeaconOperator contract + KeepRandomBeaconOperator = "Set by Kube" # StakingProxy StakingProxy = "Set by Kube" diff --git a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/provision-keep-client.js b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/provision-keep-client.js index c4e6c86f67..c7725f6573 100755 --- a/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/provision-keep-client.js +++ b/infrastructure/kube/templates/keep-client/initcontainer/provision-keep-client/provision-keep-client.js @@ -51,15 +51,15 @@ const keepTokenContractAbi = keepTokenContractParsed.abi; const keepTokenContractAddress = keepTokenContractParsed.networks[ethNetworkId].address; const keepTokenContract = new web3.eth.Contract(keepTokenContractAbi, keepTokenContractAddress); -// keepRandomBeacon, only contract address for config file create -const keepRandomBeaconJsonFile = '/tmp/KeepRandomBeacon.json'; -const keepRandomBeaconParsed = JSON.parse(fs.readFileSync(keepRandomBeaconJsonFile)); -const keepRandomBeaconContractAddress = keepRandomBeaconParsed.networks[ethNetworkId].address; +// keepRandomBeaconService, only contract address for config file create +const keepRandomBeaconServiceJsonFile = '/tmp/KeepRandomBeaconService.json'; +const keepRandomBeaconServiceParsed = JSON.parse(fs.readFileSync(keepRandomBeaconServiceJsonFile)); +const keepRandomBeaconServiceContractAddress = keepRandomBeaconServiceParsed.networks[ethNetworkId].address; -// KeepGroup, only contract address for config file create -const keepGroupJsonFile = '/tmp/KeepGroup.json'; -const keepGroupParsed = JSON.parse(fs.readFileSync(keepGroupJsonFile)); -const keepGroupContractAddress = keepGroupParsed.networks[ethNetworkId].address; +// KeepRandomBeaconOperator, only contract address for config file create +const keepRandomBeaconOperatorJsonFile = '/tmp/KeepRandomBeaconOperator.json'; +const keepRandomBeaconOperatorParsed = JSON.parse(fs.readFileSync(keepRandomBeaconOperatorJsonFile)); +const keepRandomBeaconOperatorContractAddress = keepRandomBeaconOperatorParsed.networks[ethNetworkId].address; // Stake a target eth account async function provisionKeepClient() { @@ -233,8 +233,7 @@ async function createKeepClientConfig(operator) { parsedConfigFile.ethereum.URL = ethHost.replace('http://', 'ws://') + ':' + ethWsPort; parsedConfigFile.ethereum.URLRPC = ethHost + ':' + ethRpcPort; - parsedConfigFile.ethereum.ContractAddresses.KeepRandomBeacon = keepRandomBeaconContractAddress; - parsedConfigFile.ethereum.ContractAddresses.KeepGroup = keepGroupContractAddress; + parsedConfigFile.ethereum.ContractAddresses.KeepRandomBeaconOperator = keepRandomBeaconOperatorContractAddress; parsedConfigFile.ethereum.ContractAddresses.StakingProxy = stakingProxyContractAddress; parsedConfigFile.LibP2P.Seed = 2; parsedConfigFile.LibP2P.Port = 3919; @@ -260,8 +259,7 @@ async function createKeepClientConfig(operator) { parsedConfigFile.ethereum.URLRPC = ethHost + ':' + ethRpcPort; parsedConfigFile.ethereum.account.Address = operator; parsedConfigFile.ethereum.account.KeyFile = '/mnt/keep-client/config/eth_account_keyfile'; - parsedConfigFile.ethereum.ContractAddresses.KeepRandomBeacon = keepRandomBeaconContractAddress; - parsedConfigFile.ethereum.ContractAddresses.KeepGroup = keepGroupContractAddress; + parsedConfigFile.ethereum.ContractAddresses.KeepRandomBeaconOperator = keepRandomBeaconOperatorContractAddress; parsedConfigFile.ethereum.ContractAddresses.StakingProxy = stakingProxyContractAddress; parsedConfigFile.LibP2P.Port = 3919; diff --git a/pkg/beacon/beacon.go b/pkg/beacon/beacon.go index a26c759df6..4b1e514a51 100644 --- a/pkg/beacon/beacon.go +++ b/pkg/beacon/beacon.go @@ -48,11 +48,11 @@ func Initialize( groupRegistry, ) - relayChain.OnRelayEntryRequested(func(request *event.Request) { + relayChain.OnSignatureRequested(func(request *event.Request) { fmt.Printf("New relay entry requested [%+v]\n", request) go node.GenerateRelayEntryIfEligible( - request.RequestID, + request.SigningId, request.PreviousEntry, request.Seed, relayChain, @@ -69,7 +69,7 @@ func Initialize( relayChain, blockCounter, event.NewEntry.Bytes(), - event.RequestID, + event.SigningId, event.Seed, event.BlockNumber, ) diff --git a/pkg/beacon/relay/chain/chain.go b/pkg/beacon/relay/chain/chain.go index 869c95326a..d62cca6272 100644 --- a/pkg/beacon/relay/chain/chain.go +++ b/pkg/beacon/relay/chain/chain.go @@ -23,14 +23,14 @@ type RelayEntryInterface interface { // the entry as seen on-chain, or failed if there is an error submitting // the entry. SubmitRelayEntry(entry *event.Entry) *async.RelayEntryPromise - // OnRelayEntryGenerated is a callback that is invoked when an on-chain + // OnSignatureSubmitted is a callback that is invoked when an on-chain // notification of a new, valid relay entry is seen. - OnRelayEntryGenerated( + OnSignatureSubmitted( func(entry *event.Entry), ) (subscription.EventSubscription, error) - // OnRelayEntryRequested is a callback that is invoked when an on-chain + // OnSignatureRequested is a callback that is invoked when an on-chain // notification of a new, valid relay request is seen. - OnRelayEntryRequested( + OnSignatureRequested( func(request *event.Request), ) (subscription.EventSubscription, error) } @@ -85,7 +85,7 @@ type DistributedKeyGenerationInterface interface { // Signatures over DKG result hash are collected in a map keyed by signer's // member index. SubmitDKGResult( - requestID *big.Int, + signingId *big.Int, participantIndex group.MemberIndex, dkgResult *DKGResult, signatures map[group.MemberIndex]operator.Signature, @@ -98,7 +98,7 @@ type DistributedKeyGenerationInterface interface { // IsDKGResultSubmitted checks if a DKG result hash has already been // submitted to the chain for the given request ID. IsDKGResultSubmitted( - requestID *big.Int, + signingId *big.Int, ) (bool, error) // CalculateDKGResultHash calculates 256-bit hash of DKG result in standard // specific for the chain. Operation is performed off-chain. diff --git a/pkg/beacon/relay/dkg/dkg.go b/pkg/beacon/relay/dkg/dkg.go index b4e7ba20ef..9ff8d620fa 100644 --- a/pkg/beacon/relay/dkg/dkg.go +++ b/pkg/beacon/relay/dkg/dkg.go @@ -14,7 +14,7 @@ import ( // ExecuteDKG runs the full distributed key generation lifecycle. func ExecuteDKG( - requestID *big.Int, + signingId *big.Int, seed *big.Int, index int, // starts with 0 groupSize int, @@ -53,7 +53,7 @@ func ExecuteDKG( err = dkgResult.Publish( playerIndex, - requestID, + signingId, gjkrResult.Group, gjkrResult, channel, diff --git a/pkg/beacon/relay/dkg/dkg_test.go b/pkg/beacon/relay/dkg/dkg_test.go index e5866c4b60..58de68f6bf 100644 --- a/pkg/beacon/relay/dkg/dkg_test.go +++ b/pkg/beacon/relay/dkg/dkg_test.go @@ -20,7 +20,7 @@ func TestExecuteDKGLocal(t *testing.T) { groupSize := 5 threshold := 3 - requestID := big.NewInt(13) + signingId := big.NewInt(13) seed := big.NewInt(8) startBlockHeight := uint64(2) @@ -40,7 +40,7 @@ func TestExecuteDKGLocal(t *testing.T) { } signer, err := ExecuteDKG( - requestID, + signingId, seed, playerIndex, groupSize, diff --git a/pkg/beacon/relay/dkg/result/publish.go b/pkg/beacon/relay/dkg/result/publish.go index dc6cca6b04..8bfbcbea61 100644 --- a/pkg/beacon/relay/dkg/result/publish.go +++ b/pkg/beacon/relay/dkg/result/publish.go @@ -19,7 +19,7 @@ import ( // along with everyone's votes. func Publish( playerIndex group.MemberIndex, - requestID *big.Int, + signingId *big.Int, dkgGroup *group.Group, result *gjkr.Result, channel net.BroadcastChannel, @@ -33,7 +33,7 @@ func Publish( relayChain: relayChain, blockCounter: blockCounter, member: NewSigningMember(playerIndex, dkgGroup, privateKey), - requestID: requestID, + signingId: signingId, result: convertResult(result, dkgGroup.GroupSize()), signatureMessages: make([]*DKGResultHashSignatureMessage, 0), signingStartBlockHeight: startBlockHeight, diff --git a/pkg/beacon/relay/dkg/result/states.go b/pkg/beacon/relay/dkg/result/states.go index 0ef803cd1b..1291fcdfc6 100644 --- a/pkg/beacon/relay/dkg/result/states.go +++ b/pkg/beacon/relay/dkg/result/states.go @@ -27,7 +27,7 @@ type resultSigningState struct { member *SigningMember - requestID *big.Int + signingId *big.Int result *relayChain.DKGResult signatureMessages []*DKGResultHashSignatureMessage @@ -96,7 +96,7 @@ func (rss *resultSigningState) Next() signingState { relayChain: rss.relayChain, blockCounter: rss.blockCounter, member: rss.member, - requestID: rss.requestID, + signingId: rss.signingId, result: rss.result, signatureMessages: rss.signatureMessages, validSignatures: make(map[group.MemberIndex]operator.Signature), @@ -123,7 +123,7 @@ type signaturesVerificationState struct { member *SigningMember - requestID *big.Int + signingId *big.Int result *relayChain.DKGResult signatureMessages []*DKGResultHashSignatureMessage @@ -160,7 +160,7 @@ func (svs *signaturesVerificationState) Next() signingState { relayChain: svs.relayChain, blockCounter: svs.blockCounter, member: NewSubmittingMember(svs.member.index), - requestID: svs.requestID, + signingId: svs.signingId, result: svs.result, signatures: svs.validSignatures, submissionStartBlockHeight: svs.verificationStartBlockHeight + @@ -185,7 +185,7 @@ type resultSubmissionState struct { member *SubmittingMember - requestID *big.Int + signingId *big.Int result *relayChain.DKGResult signatures map[group.MemberIndex]operator.Signature @@ -206,7 +206,7 @@ func (rss *resultSubmissionState) ActiveBlocks() uint64 { func (rss *resultSubmissionState) Initiate() error { return rss.member.SubmitDKGResult( - rss.requestID, + rss.signingId, rss.result, rss.signatures, rss.relayChain, diff --git a/pkg/beacon/relay/dkg/result/submission.go b/pkg/beacon/relay/dkg/result/submission.go index 5f18fa9cfe..194a950b58 100644 --- a/pkg/beacon/relay/dkg/result/submission.go +++ b/pkg/beacon/relay/dkg/result/submission.go @@ -49,7 +49,7 @@ func NewSubmittingMember( // // See Phase 14 of the protocol specification. func (sm *SubmittingMember) SubmitDKGResult( - requestID *big.Int, + signingId *big.Int, result *relayChain.DKGResult, signatures map[group.MemberIndex]operator.Signature, chainRelay relayChain.Interface, @@ -87,7 +87,7 @@ func (sm *SubmittingMember) SubmitDKGResult( // Check if any result has already been submitted to the chain with current // request ID. - alreadySubmitted, err := chainRelay.IsDKGResultSubmitted(requestID) + alreadySubmitted, err := chainRelay.IsDKGResultSubmitted(signingId) if err != nil { return returnWithError( fmt.Errorf( @@ -126,7 +126,7 @@ func (sm *SubmittingMember) SubmitDKGResult( fmt.Printf("[member:%v] Submitting DKG result...\n", sm.index) chainRelay.SubmitDKGResult( - requestID, + signingId, sm.index, result, signatures, @@ -139,7 +139,7 @@ func (sm *SubmittingMember) SubmitDKGResult( }) return <-errorChannel case submittedResultEvent := <-onSubmittedResultChan: - if submittedResultEvent.RequestID.Cmp(requestID) == 0 { + if submittedResultEvent.SigningId.Cmp(signingId) == 0 { fmt.Printf( "[member:%v] DKG result submitted by other member, leaving.\n", sm.index, diff --git a/pkg/beacon/relay/dkg/result/submission_test.go b/pkg/beacon/relay/dkg/result/submission_test.go index 1303615062..9d02268bbb 100644 --- a/pkg/beacon/relay/dkg/result/submission_test.go +++ b/pkg/beacon/relay/dkg/result/submission_test.go @@ -56,7 +56,7 @@ func TestSubmitDKGResult(t *testing.T) { } for testName, test := range tests { t.Run(testName, func(t *testing.T) { - requestID := big.NewInt(101) + signingId := big.NewInt(101) member := &SubmittingMember{ index: group.MemberIndex(test.memberIndex), @@ -70,7 +70,7 @@ func TestSubmitDKGResult(t *testing.T) { relayChain := chainHandle.ThresholdRelay() - isSubmitted, err := relayChain.IsDKGResultSubmitted(requestID) + isSubmitted, err := relayChain.IsDKGResultSubmitted(signingId) if err != nil { t.Fatal(err) } @@ -82,7 +82,7 @@ func TestSubmitDKGResult(t *testing.T) { blockCounter, _ := chainHandle.BlockCounter() err = member.SubmitDKGResult( - requestID, + signingId, result, signatures, relayChain, @@ -101,7 +101,7 @@ func TestSubmitDKGResult(t *testing.T) { currentBlock, ) } - isSubmitted, err = relayChain.IsDKGResultSubmitted(requestID) + isSubmitted, err = relayChain.IsDKGResultSubmitted(signingId) if err != nil { t.Fatal(err) } @@ -136,8 +136,8 @@ func TestConcurrentPublishResult(t *testing.T) { var tests = map[string]struct { resultToPublish1 *relayChain.DKGResult resultToPublish2 *relayChain.DKGResult - requestID1 *big.Int - requestID2 *big.Int + signingId1 *big.Int + signingId2 *big.Int expectedDuration1 func(tStep uint64) uint64 // index * t_step expectedDuration2 func(tStep uint64) uint64 // index * t_step }{ @@ -148,8 +148,8 @@ func TestConcurrentPublishResult(t *testing.T) { resultToPublish2: &relayChain.DKGResult{ GroupPublicKey: []byte{101}, }, - requestID1: big.NewInt(11), - requestID2: big.NewInt(11), + signingId1: big.NewInt(11), + signingId2: big.NewInt(11), expectedDuration1: func(tStep uint64) uint64 { return 0 }, // (P1-1) * t_step expectedDuration2: func(tStep uint64) uint64 { return 0 }, // result already published by member 1 -1 }, @@ -160,8 +160,8 @@ func TestConcurrentPublishResult(t *testing.T) { resultToPublish2: &relayChain.DKGResult{ GroupPublicKey: []byte{202}, }, - requestID1: big.NewInt(11), - requestID2: big.NewInt(11), + signingId1: big.NewInt(11), + signingId2: big.NewInt(11), expectedDuration1: func(tStep uint64) uint64 { return 0 }, // (P1-1) * t_step expectedDuration2: func(tStep uint64) uint64 { return 0 }, // result already published by member 1 -1 }, @@ -172,8 +172,8 @@ func TestConcurrentPublishResult(t *testing.T) { resultToPublish2: &relayChain.DKGResult{ GroupPublicKey: []byte{101}, }, - requestID1: big.NewInt(12), - requestID2: big.NewInt(13), + signingId1: big.NewInt(12), + signingId2: big.NewInt(13), expectedDuration1: func(tStep uint64) uint64 { return 0 }, // (P1-1) * t_step expectedDuration2: func(tStep uint64) uint64 { return (uint64(member2.index) - 1) * tStep // (P4-1) * t_step @@ -207,7 +207,7 @@ func TestConcurrentPublishResult(t *testing.T) { blockCounter, _ := chainHandle.BlockCounter() err := member1.SubmitDKGResult( - test.requestID1, + test.signingId1, test.resultToPublish1, signatures, chainHandle.ThresholdRelay(), @@ -226,7 +226,7 @@ func TestConcurrentPublishResult(t *testing.T) { blockCounter, _ := chainHandle.BlockCounter() err := member2.SubmitDKGResult( - test.requestID2, + test.signingId2, test.resultToPublish2, signatures, chainHandle.ThresholdRelay(), diff --git a/pkg/beacon/relay/entry/entry.go b/pkg/beacon/relay/entry/entry.go index 54fb5393c3..168377f61c 100644 --- a/pkg/beacon/relay/entry/entry.go +++ b/pkg/beacon/relay/entry/entry.go @@ -28,7 +28,7 @@ func SignAndSubmit( blockCounter chain.BlockCounter, channel net.BroadcastChannel, relayChain relayChain.Interface, - requestID *big.Int, + signingId *big.Int, previousEntry *big.Int, seed *big.Int, threshold int, @@ -43,7 +43,7 @@ func SignAndSubmit( relayChain: relayChain, blockCounter: blockCounter, signer: signer, - requestID: requestID, + signingId: signingId, previousEntry: previousEntry, seed: seed, threshold: threshold, diff --git a/pkg/beacon/relay/entry/gen/pb/message.proto b/pkg/beacon/relay/entry/gen/pb/message.proto index 978b1e8892..0ceabc6c01 100644 --- a/pkg/beacon/relay/entry/gen/pb/message.proto +++ b/pkg/beacon/relay/entry/gen/pb/message.proto @@ -6,5 +6,5 @@ package entry; message SignatureShare { uint32 senderID = 1; bytes share = 2; - string requestID = 3; + string signingId = 3; } diff --git a/pkg/beacon/relay/entry/marshaling.go b/pkg/beacon/relay/entry/marshaling.go index 193f98c1c3..b95fce36b5 100644 --- a/pkg/beacon/relay/entry/marshaling.go +++ b/pkg/beacon/relay/entry/marshaling.go @@ -19,7 +19,7 @@ func (ssm *SignatureShareMessage) Marshal() ([]byte, error) { pbSignatureShare := pb.SignatureShare{ SenderID: uint32(ssm.senderID), Share: ssm.shareBytes, - RequestID: ssm.requestID.String(), + SigningId: ssm.signingId.String(), } return pbSignatureShare.Marshal() @@ -34,15 +34,15 @@ func (ssm *SignatureShareMessage) Unmarshal(bytes []byte) error { return err } - requestID := new(big.Int) - requestID, ok := requestID.SetString(pbSignatureShare.RequestID, 10) + signingId := new(big.Int) + signingId, ok := signingId.SetString(pbSignatureShare.SigningId, 10) if !ok { return fmt.Errorf("could not unmarshal request ID") } ssm.senderID = group.MemberIndex(pbSignatureShare.SenderID) ssm.shareBytes = pbSignatureShare.Share - ssm.requestID = requestID + ssm.signingId = signingId return nil } diff --git a/pkg/beacon/relay/entry/marshaling_test.go b/pkg/beacon/relay/entry/marshaling_test.go index c52d46f03a..b19cb0ba50 100644 --- a/pkg/beacon/relay/entry/marshaling_test.go +++ b/pkg/beacon/relay/entry/marshaling_test.go @@ -27,11 +27,11 @@ func TestSignatureShareMessageRoundTrip(t *testing.T) { testutils.AssertBytesEqual(t, msg.shareBytes, unmarshaled.shareBytes) - if msg.requestID.Cmp(unmarshaled.requestID) != 0 { + if msg.signingId.Cmp(unmarshaled.signingId) != 0 { t.Errorf( "unexpected request ID\nexpected: [%v]\nactual: [%v]", - msg.requestID, - unmarshaled.requestID, + msg.signingId, + unmarshaled.signingId, ) } } diff --git a/pkg/beacon/relay/entry/message.go b/pkg/beacon/relay/entry/message.go index 4a7ae0e131..5c3007cd4b 100644 --- a/pkg/beacon/relay/entry/message.go +++ b/pkg/beacon/relay/entry/message.go @@ -11,7 +11,7 @@ import ( type SignatureShareMessage struct { senderID group.MemberIndex shareBytes []byte - requestID *big.Int + signingId *big.Int } // SenderID returns protocol-level identifier of the message sender. diff --git a/pkg/beacon/relay/entry/states.go b/pkg/beacon/relay/entry/states.go index a91395469b..515c2e35b6 100644 --- a/pkg/beacon/relay/entry/states.go +++ b/pkg/beacon/relay/entry/states.go @@ -25,7 +25,7 @@ type signingStateBase struct { signer *dkg.ThresholdSigner - requestID *big.Int + signingId *big.Int previousEntry *big.Int seed *big.Int @@ -56,7 +56,7 @@ func (sss *signatureShareState) Initiate() error { message := &SignatureShareMessage{ sss.MemberIndex(), sss.selfSignatureShare.Marshal(), - sss.requestID, + sss.signingId, } if err := sss.channel.Send(message); err != nil { return err @@ -70,7 +70,7 @@ func (sss *signatureShareState) Receive(msg net.Message) error { if !group.IsMessageFromSelf( sss.MemberIndex(), signatureShareMessage, - ) && sss.isForTheCurrentRequestID(signatureShareMessage) { + ) && sss.isForTheCurrentSigningId(signatureShareMessage) { sss.signatureShareMessages = append( sss.signatureShareMessages, signatureShareMessage, @@ -81,8 +81,8 @@ func (sss *signatureShareState) Receive(msg net.Message) error { return nil } -func (sss *signatureShareState) isForTheCurrentRequestID(msg *SignatureShareMessage) bool { - return sss.requestID.Cmp(msg.requestID) == 0 +func (sss *signatureShareState) isForTheCurrentSigningId(msg *SignatureShareMessage) bool { + return sss.signingId.Cmp(msg.signingId) == 0 } func (sss *signatureShareState) Next() signingState { @@ -205,7 +205,7 @@ func (ess *entrySubmissionState) Initiate() error { } return submitter.submitRelayEntry( - ess.requestID, + ess.signingId, new(big.Int).SetBytes(ess.signature), ess.previousEntry, ess.seed, diff --git a/pkg/beacon/relay/entry/submission.go b/pkg/beacon/relay/entry/submission.go index e0e045ff38..2a1e60ad51 100644 --- a/pkg/beacon/relay/entry/submission.go +++ b/pkg/beacon/relay/entry/submission.go @@ -25,7 +25,7 @@ type relayEntrySubmitter struct { // Relay entry submit process starts at block height defined by startBlockheight // parameter. func (res *relayEntrySubmitter) submitRelayEntry( - requestID *big.Int, + signingId *big.Int, newEntry *big.Int, previousEntry *big.Int, seed *big.Int, @@ -42,7 +42,7 @@ func (res *relayEntrySubmitter) submitRelayEntry( onSubmittedResultChan := make(chan *event.Entry) - subscription, err := res.chain.OnRelayEntryGenerated( + subscription, err := res.chain.OnSignatureSubmitted( func(event *event.Entry) { onSubmittedResultChan <- event }, @@ -84,7 +84,7 @@ func (res *relayEntrySubmitter) submitRelayEntry( fmt.Printf("[member:%v] Submitting relay entry..\n", res.index) entry := &event.Entry{ - RequestID: requestID, + SigningId: signingId, Value: newEntry, PreviousEntry: previousEntry, Timestamp: time.Now().UTC(), @@ -98,7 +98,7 @@ func (res *relayEntrySubmitter) submitRelayEntry( fmt.Printf( "[member:%v] Relay entry for request [%v] successfully submitted at block [%v]\n", res.index, - requestID, + signingId, entry.BlockNumber, ) } @@ -106,7 +106,7 @@ func (res *relayEntrySubmitter) submitRelayEntry( }) return <-errorChannel case submittedEntryEvent := <-onSubmittedResultChan: - if submittedEntryEvent.RequestID.Cmp(requestID) == 0 { + if submittedEntryEvent.SigningId.Cmp(signingId) == 0 { fmt.Printf( "[member:%v] Relay entry submitted by other member, leaving.\n", res.index, diff --git a/pkg/beacon/relay/event/event.go b/pkg/beacon/relay/event/event.go index 5f90bd49dd..2de863be04 100644 --- a/pkg/beacon/relay/event/event.go +++ b/pkg/beacon/relay/event/event.go @@ -10,7 +10,7 @@ import ( // Entry represents one entry in the threshold relay. type Entry struct { - RequestID *big.Int + SigningId *big.Int Value *big.Int GroupPubKey []byte PreviousEntry *big.Int @@ -22,7 +22,7 @@ type Entry struct { // Request represents a request for an entry in the threshold relay. type Request struct { - RequestID *big.Int + SigningId *big.Int Payment *big.Int PreviousEntry *big.Int Seed *big.Int @@ -33,7 +33,7 @@ type Request struct { // GroupSelectionStart represents a group selection start event. type GroupSelectionStart struct { NewEntry *big.Int - RequestID *big.Int + SigningId *big.Int Seed *big.Int BlockNumber uint64 } @@ -47,10 +47,10 @@ type GroupTicketSubmission struct { // GroupRegistration represents a registered group in the threshold relay with a // public key, that is considered active at ActivationBlockHeight, and was -// spawned by the relay request with id, RequestID. +// spawned by the relay request with id, SigningId. type GroupRegistration struct { GroupPublicKey []byte - RequestID *big.Int + SigningId *big.Int BlockNumber uint64 } @@ -60,7 +60,7 @@ type GroupRegistration struct { // the index of the member who submitted the result and a final public key of // the group. type DKGResultSubmission struct { - RequestID *big.Int + SigningId *big.Int MemberIndex uint32 GroupPublicKey []byte diff --git a/pkg/beacon/relay/genesis.go b/pkg/beacon/relay/genesis.go index a4c2a5f78c..5c00dfe15a 100644 --- a/pkg/beacon/relay/genesis.go +++ b/pkg/beacon/relay/genesis.go @@ -54,7 +54,7 @@ func GenesisRelayEntry() *event.Entry { }.Compress() return &event.Entry{ - RequestID: big.NewInt(int64(1)), + SigningId: big.NewInt(int64(1)), Value: new(big.Int).SetBytes(genesisGroupSignature), GroupPubKey: genesisGroupPubKey, PreviousEntry: genesisEntryValue, diff --git a/pkg/beacon/relay/group_selection.go b/pkg/beacon/relay/group_selection.go index 4bd35009ed..bc9423bd6b 100644 --- a/pkg/beacon/relay/group_selection.go +++ b/pkg/beacon/relay/group_selection.go @@ -28,7 +28,7 @@ func (n *Node) SubmitTicketsForGroupSelection( relayChain relaychain.Interface, blockCounter chain.BlockCounter, beaconValue []byte, - entryRequestID *big.Int, + entrySigningId *big.Int, entrySeed *big.Int, startBlockHeight uint64, ) error { @@ -102,7 +102,7 @@ func (n *Node) SubmitTicketsForGroupSelection( go n.JoinGroupIfEligible( relayChain, &groupselection.Result{SelectedStakers: selectedStakers}, - entryRequestID, + entrySigningId, entrySeed, challengeEndBlockHeight, ) diff --git a/pkg/beacon/relay/node.go b/pkg/beacon/relay/node.go index 6fbb0d855e..8d8937846b 100644 --- a/pkg/beacon/relay/node.go +++ b/pkg/beacon/relay/node.go @@ -49,7 +49,7 @@ type Node struct { func (n *Node) JoinGroupIfEligible( relayChain relaychain.Interface, groupSelectionResult *groupselection.Result, - entryRequestID *big.Int, + entrySigningId *big.Int, entrySeed *big.Int, dkgStartBlockHeight uint64, ) { @@ -82,7 +82,7 @@ func (n *Node) JoinGroupIfEligible( go func() { signer, err := dkg.ExecuteDKG( - entryRequestID, + entrySigningId, entrySeed, playerIndex, n.chainConfig.GroupSize, diff --git a/pkg/beacon/relay/relay.go b/pkg/beacon/relay/relay.go index 28bf51cd71..495d2370f7 100644 --- a/pkg/beacon/relay/relay.go +++ b/pkg/beacon/relay/relay.go @@ -41,7 +41,7 @@ func NewNode( // determining whether the node is or is not is a member of the requested group, and // signature creation and submission is performed in a background goroutine. func (n *Node) GenerateRelayEntryIfEligible( - requestID *big.Int, + signingId *big.Int, previousEntry *big.Int, seed *big.Int, relayChain relayChain.Interface, @@ -71,7 +71,7 @@ func (n *Node) GenerateRelayEntryIfEligible( n.blockCounter, channel, relayChain, - requestID, + signingId, previousEntry, seed, n.chainConfig.HonestThreshold(), @@ -82,7 +82,7 @@ func (n *Node) GenerateRelayEntryIfEligible( fmt.Fprintf( os.Stderr, "error creating threshold signature for request [%s]: [%v]\n", - requestID, + signingId, err, ) return diff --git a/pkg/chain/ethereum/connect.go b/pkg/chain/ethereum/connect.go index 439f8c4c5f..19ffc9f024 100644 --- a/pkg/chain/ethereum/connect.go +++ b/pkg/chain/ethereum/connect.go @@ -15,15 +15,14 @@ import ( ) type ethereumChain struct { - config Config - client *ethclient.Client - clientRPC *rpc.Client - clientWS *rpc.Client - requestID *big.Int - keepGroupContract *contract.KeepGroup - keepRandomBeaconContract *contract.KeepRandomBeacon - stakingContract *contract.StakingProxy - accountKey *keystore.Key + config Config + client *ethclient.Client + clientRPC *rpc.Client + clientWS *rpc.Client + signingId *big.Int + keepRandomBeaconOperatorContract *contract.KeepRandomBeaconOperator + stakingContract *contract.StakingProxy + accountKey *keystore.Key // transactionMutex allows interested parties to forcibly serialize // transaction submission. @@ -76,42 +75,22 @@ func Connect(config Config) (chain.Handle, error) { pv.accountKey = key } - address, err := addressForContract(config, "KeepRandomBeacon") + address, err := addressForContract(config, "KeepRandomBeaconOperator") if err != nil { - return nil, fmt.Errorf("error resolving KeepRandomBeacon contract: [%v]", err) + return nil, fmt.Errorf("error resolving KeepRandomBeaconOperator contract: [%v]", err) } - keepRandomBeaconContract, err := - contract.NewKeepRandomBeacon( + keepRandomBeaconOperatorContract, err := + contract.NewKeepRandomBeaconOperator( *address, pv.accountKey, pv.client, pv.transactionMutex, ) if err != nil { - return nil, fmt.Errorf( - "error attaching to KeepRandomBeacon contract: [%v]", - err, - ) - } - pv.keepRandomBeaconContract = keepRandomBeaconContract - - address, err = addressForContract(config, "KeepGroup") - if err != nil { - return nil, fmt.Errorf("error resolving KeepGroup contract: [%v]", err) - } - - keepGroupContract, err := - contract.NewKeepGroup( - *address, - pv.accountKey, - pv.client, - pv.transactionMutex, - ) - if err != nil { - return nil, fmt.Errorf("error attaching to KeepGroup contract: [%v]", err) + return nil, fmt.Errorf("error attaching to KeepRandomBeaconOperator contract: [%v]", err) } - pv.keepGroupContract = keepGroupContract + pv.keepRandomBeaconOperatorContract = keepRandomBeaconOperatorContract address, err = addressForContract(config, "StakingProxy") if err != nil { diff --git a/pkg/chain/ethereum/ethereum.go b/pkg/chain/ethereum/ethereum.go index d8a9f53e22..c996b3dfec 100644 --- a/pkg/chain/ethereum/ethereum.go +++ b/pkg/chain/ethereum/ethereum.go @@ -28,18 +28,18 @@ func (ec *ethereumChain) GetKeys() (*operator.PrivateKey, *operator.PublicKey) { } func (ec *ethereumChain) GetConfig() (*relayconfig.Chain, error) { - groupSize, err := ec.keepGroupContract.GroupSize() + groupSize, err := ec.keepRandomBeaconOperatorContract.GroupSize() if err != nil { return nil, fmt.Errorf("error calling GroupSize: [%v]", err) } - threshold, err := ec.keepGroupContract.GroupThreshold() + threshold, err := ec.keepRandomBeaconOperatorContract.GroupThreshold() if err != nil { return nil, fmt.Errorf("error calling GroupThreshold: [%v]", err) } ticketInitialSubmissionTimeout, err := - ec.keepGroupContract.TicketInitialSubmissionTimeout() + ec.keepRandomBeaconOperatorContract.TicketInitialSubmissionTimeout() if err != nil { return nil, fmt.Errorf( "error calling TicketInitialSubmissionTimeout: [%v]", @@ -48,7 +48,7 @@ func (ec *ethereumChain) GetConfig() (*relayconfig.Chain, error) { } ticketReactiveSubmissionTimeout, err := - ec.keepGroupContract.TicketReactiveSubmissionTimeout() + ec.keepRandomBeaconOperatorContract.TicketReactiveSubmissionTimeout() if err != nil { return nil, fmt.Errorf( "error calling TicketReactiveSubmissionTimeout: [%v]", @@ -57,7 +57,7 @@ func (ec *ethereumChain) GetConfig() (*relayconfig.Chain, error) { } ticketChallengeTimeout, err := - ec.keepGroupContract.TicketChallengeTimeout() + ec.keepRandomBeaconOperatorContract.TicketChallengeTimeout() if err != nil { return nil, fmt.Errorf( "error calling TicketChallengeTimeout: [%v]", @@ -65,7 +65,7 @@ func (ec *ethereumChain) GetConfig() (*relayconfig.Chain, error) { ) } - resultPublicationBlockStep, err := ec.keepGroupContract.ResultPublicationBlockStep() + resultPublicationBlockStep, err := ec.keepRandomBeaconOperatorContract.ResultPublicationBlockStep() if err != nil { return nil, fmt.Errorf( "error calling ResultPublicationBlockStep: [%v]", @@ -73,17 +73,17 @@ func (ec *ethereumChain) GetConfig() (*relayconfig.Chain, error) { ) } - minimumStake, err := ec.keepGroupContract.MinimumStake() + minimumStake, err := ec.keepRandomBeaconOperatorContract.MinimumStake() if err != nil { return nil, fmt.Errorf("error calling MinimumStake: [%v]", err) } - tokenSupply, err := ec.keepGroupContract.TokenSupply() + tokenSupply, err := ec.keepRandomBeaconOperatorContract.TokenSupply() if err != nil { return nil, fmt.Errorf("error calling TokenSupply: [%v]", err) } - naturalThreshold, err := ec.keepGroupContract.NaturalThreshold() + naturalThreshold, err := ec.keepRandomBeaconOperatorContract.NaturalThreshold() if err != nil { return nil, fmt.Errorf("error calling NaturalThreshold: [%v]", err) } @@ -105,7 +105,7 @@ func (ec *ethereumChain) GetConfig() (*relayconfig.Chain, error) { // be returned if not staked. If err != nil then it was not possible to determine // if the address is staked or not. func (ec *ethereumChain) HasMinimumStake(address common.Address) (bool, error) { - return ec.keepGroupContract.HasMinimumStake(address) + return ec.keepRandomBeaconOperatorContract.HasMinimumStake(address) } func (ec *ethereumChain) SubmitTicket(ticket *chain.Ticket) *async.GroupTicketPromise { @@ -123,7 +123,7 @@ func (ec *ethereumChain) SubmitTicket(ticket *chain.Ticket) *async.GroupTicketPr } } - _, err := ec.keepGroupContract.SubmitTicket( + _, err := ec.keepRandomBeaconOperatorContract.SubmitTicket( ticket.Value, ticket.Proof.StakerValue, ticket.Proof.VirtualStakerIndex, @@ -141,7 +141,7 @@ func (ec *ethereumChain) GetSelectedParticipants() ( []chain.StakerAddress, error, ) { - selectedParticipants, err := ec.keepGroupContract.SelectedParticipants() + selectedParticipants, err := ec.keepRandomBeaconOperatorContract.SelectedParticipants() if err != nil { return nil, err } @@ -173,7 +173,7 @@ func (ec *ethereumChain) SubmitRelayEntry( generatedEntry := make(chan *event.Entry) - subscription, err := ec.OnRelayEntryGenerated( + subscription, err := ec.OnSignatureSubmitted( func(onChainEvent *event.Entry) { generatedEntry <- onChainEvent }, @@ -194,7 +194,7 @@ func (ec *ethereumChain) SubmitRelayEntry( return } - if event.RequestID.Cmp(newEntry.RequestID) == 0 { + if event.SigningId.Cmp(newEntry.SigningId) == 0 { subscription.Unsubscribe() close(generatedEntry) @@ -213,8 +213,8 @@ func (ec *ethereumChain) SubmitRelayEntry( } }() - _, err = ec.keepRandomBeaconContract.RelayEntry( - newEntry.RequestID, + _, err = ec.keepRandomBeaconOperatorContract.RelayEntry( + newEntry.SigningId, newEntry.Value, newEntry.GroupPubKey, newEntry.PreviousEntry, @@ -229,12 +229,12 @@ func (ec *ethereumChain) SubmitRelayEntry( return relayEntryPromise } -func (ec *ethereumChain) OnRelayEntryGenerated( +func (ec *ethereumChain) OnSignatureSubmitted( handle func(entry *event.Entry), ) (subscription.EventSubscription, error) { - return ec.keepRandomBeaconContract.WatchRelayEntryGenerated( + return ec.keepRandomBeaconOperatorContract.WatchSignatureSubmitted( func( - requestID *big.Int, + signingId *big.Int, requestResponse *big.Int, requestGroupPubKey []byte, previousEntry *big.Int, @@ -242,7 +242,7 @@ func (ec *ethereumChain) OnRelayEntryGenerated( blockNumber uint64, ) { handle(&event.Entry{ - RequestID: requestID, + SigningId: signingId, Value: requestResponse, GroupPubKey: requestGroupPubKey, PreviousEntry: previousEntry, @@ -260,12 +260,12 @@ func (ec *ethereumChain) OnRelayEntryGenerated( ) } -func (ec *ethereumChain) OnRelayEntryRequested( +func (ec *ethereumChain) OnSignatureRequested( handle func(request *event.Request), ) (subscription.EventSubscription, error) { - return ec.keepRandomBeaconContract.WatchRelayEntryRequested( + return ec.keepRandomBeaconOperatorContract.WatchSignatureRequested( func( - requestID *big.Int, + signingId *big.Int, payment *big.Int, previousEntry *big.Int, seed *big.Int, @@ -273,7 +273,7 @@ func (ec *ethereumChain) OnRelayEntryRequested( blockNumber uint64, ) { handle(&event.Request{ - RequestID: requestID, + SigningId: signingId, Payment: payment, PreviousEntry: previousEntry, Seed: seed, @@ -293,16 +293,16 @@ func (ec *ethereumChain) OnRelayEntryRequested( func (ec *ethereumChain) OnGroupSelectionStarted( handle func(groupSelectionStart *event.GroupSelectionStart), ) (subscription.EventSubscription, error) { - return ec.keepGroupContract.WatchGroupSelectionStarted( + return ec.keepRandomBeaconOperatorContract.WatchGroupSelectionStarted( func( newEntry *big.Int, - requestID *big.Int, + signingId *big.Int, seed *big.Int, blockNumber uint64, ) { handle(&event.GroupSelectionStart{ NewEntry: newEntry, - RequestID: requestID, + SigningId: signingId, Seed: seed, BlockNumber: blockNumber, }) @@ -319,15 +319,15 @@ func (ec *ethereumChain) OnGroupSelectionStarted( func (ec *ethereumChain) OnGroupRegistered( handle func(groupRegistration *event.GroupRegistration), ) (subscription.EventSubscription, error) { - return ec.keepGroupContract.WatchDkgResultPublishedEvent( + return ec.keepRandomBeaconOperatorContract.WatchDkgResultPublishedEvent( func( - requestID *big.Int, + signingId *big.Int, groupPublicKey []byte, blockNumber uint64, ) { handle(&event.GroupRegistration{ GroupPublicKey: groupPublicKey, - RequestID: requestID, + SigningId: signingId, BlockNumber: blockNumber, }) }, @@ -337,21 +337,21 @@ func (ec *ethereumChain) OnGroupRegistered( ) } -func (ec *ethereumChain) IsDKGResultSubmitted(requestID *big.Int) (bool, error) { - return ec.keepGroupContract.IsDkgResultSubmitted(requestID) +func (ec *ethereumChain) IsDKGResultSubmitted(signingId *big.Int) (bool, error) { + return ec.keepRandomBeaconOperatorContract.IsDkgResultSubmitted(signingId) } func (ec *ethereumChain) IsStaleGroup(groupPublicKey []byte) (bool, error) { - return ec.keepGroupContract.IsStaleGroup(groupPublicKey) + return ec.keepRandomBeaconOperatorContract.IsStaleGroup(groupPublicKey) } func (ec *ethereumChain) OnDKGResultSubmitted( handler func(dkgResultPublication *event.DKGResultSubmission), ) (subscription.EventSubscription, error) { - return ec.keepGroupContract.WatchDkgResultPublishedEvent( - func(requestID *big.Int, groupPubKey []byte, blockNumber uint64) { + return ec.keepRandomBeaconOperatorContract.WatchDkgResultPublishedEvent( + func(signingId *big.Int, groupPubKey []byte, blockNumber uint64) { handler(&event.DKGResultSubmission{ - RequestID: requestID, + SigningId: signingId, GroupPublicKey: groupPubKey, BlockNumber: blockNumber, }) @@ -366,7 +366,7 @@ func (ec *ethereumChain) OnDKGResultSubmitted( } func (ec *ethereumChain) SubmitDKGResult( - requestID *big.Int, + signingId *big.Int, participantIndex group.MemberIndex, result *relaychain.DKGResult, signatures map[group.MemberIndex]operator.Signature, @@ -408,7 +408,7 @@ func (ec *ethereumChain) SubmitDKGResult( return } - if event.RequestID.Cmp(requestID) == 0 { + if event.SigningId.Cmp(signingId) == 0 { subscription.Unsubscribe() close(publishedResult) @@ -435,8 +435,8 @@ func (ec *ethereumChain) SubmitDKGResult( return resultPublicationPromise } - if _, err = ec.keepGroupContract.SubmitDkgResult( - requestID, + if _, err = ec.keepRandomBeaconOperatorContract.SubmitDkgResult( + signingId, participantIndex.Int(), result.GroupPublicKey, result.Disqualified, diff --git a/pkg/chain/gen/Makefile b/pkg/chain/gen/Makefile index ed46be188a..f4660f2466 100644 --- a/pkg/chain/gen/Makefile +++ b/pkg/chain/gen/Makefile @@ -13,7 +13,7 @@ abigen_files := $(addprefix abi/,$(addsuffix .go,$(contract_stems))) # *ImplV1.go files will get generated into clean Keep contract bindings, as will # the Staking*.go contract(s). The corresponding contract filenames will drop # the ImplV1, if it exists, and live in the contract/ directory. -clean_contract_stems := $(filter %ImplV1,$(contract_stems)) $(filter Staking%,$(contract_stems)) $(filter TokenGrant, $(contract_stems)) +clean_contract_stems := $(filter %ImplV1,$(contract_stems)) $(filter %Operator,$(contract_stems)) $(filter Staking%,$(contract_stems)) $(filter TokenGrant, $(contract_stems)) contract_files := $(addprefix contract/,$(addsuffix .go,$(subst ImplV1,,$(clean_contract_stems)))) all: gen_contract_go gen_abi_go @@ -21,6 +21,9 @@ all: gen_contract_go gen_abi_go clean: rm -r abi/* rm -r contract/* + mkdir tmp && mv cmd/cmd*.go tmp + rm -r cmd/* + mv tmp/* cmd && rm -r tmp gen_abi_go: $(abigen_files) @@ -40,6 +43,9 @@ abi/%.go: abi/%.abi contract/%.go cmd/%.go: abi/%ImplV1.abi abi/%ImplV1.go abi/%.go *.tmpl *.go go run ./ $< contract/$*.go cmd/$*.go +contract/%Operator.go cmd/%Operator.go: abi/%Operator.abi abi/%Operator.go *.tmpl *.go + go run ./ $< contract/$*Operator.go cmd/$*Operator.go + contract/Staking%.go cmd/Staking%.go: abi/Staking%.abi abi/Staking%.go *.tmpl *.go go run ./ $< contract/Staking$*.go cmd/Staking$*.go diff --git a/pkg/chain/local/local.go b/pkg/chain/local/local.go index 73420766e6..d21cb7812e 100644 --- a/pkg/chain/local/local.go +++ b/pkg/chain/local/local.go @@ -36,7 +36,7 @@ type localChain struct { groupRelayEntries map[string]*big.Int submittedResultsMutex sync.Mutex - // Map of submitted DKG Results. Key is a RequestID of the specific DKG + // Map of submitted DKG Results. Key is a SigningId of the specific DKG // execution. submittedResults map[string]*relaychain.DKGResult @@ -49,7 +49,7 @@ type localChain struct { groupRegisteredHandlers map[int]func(groupRegistration *event.GroupRegistration) resultSubmissionHandlers map[int]func(submission *event.DKGResultSubmission) - requestID int64 + signingId int64 latestValue *big.Int simulatedHeight uint64 @@ -133,7 +133,7 @@ func (c *localChain) SubmitRelayEntry(entry *event.Entry) *async.RelayEntryPromi c.groupRelayEntriesMutex.Lock() defer c.groupRelayEntriesMutex.Unlock() - existing, exists := c.groupRelayEntries[string(entry.GroupPubKey)+entry.RequestID.String()] + existing, exists := c.groupRelayEntries[string(entry.GroupPubKey)+entry.SigningId.String()] if exists { if existing.Cmp(entry.Value) != 0 { err := fmt.Errorf( @@ -151,7 +151,7 @@ func (c *localChain) SubmitRelayEntry(entry *event.Entry) *async.RelayEntryPromi return relayEntryPromise } - c.groupRelayEntries[string(entry.GroupPubKey)+entry.RequestID.String()] = entry.Value + c.groupRelayEntries[string(entry.GroupPubKey)+entry.SigningId.String()] = entry.Value c.handlerMutex.Lock() for _, handler := range c.relayEntryHandlers { @@ -167,7 +167,7 @@ func (c *localChain) SubmitRelayEntry(entry *event.Entry) *async.RelayEntryPromi return relayEntryPromise } -func (c *localChain) OnRelayEntryGenerated( +func (c *localChain) OnSignatureSubmitted( handler func(entry *event.Entry), ) (subscription.EventSubscription, error) { c.handlerMutex.Lock() @@ -184,7 +184,7 @@ func (c *localChain) OnRelayEntryGenerated( }), nil } -func (c *localChain) OnRelayEntryRequested( +func (c *localChain) OnSignatureRequested( handler func(request *event.Request), ) (subscription.EventSubscription, error) { c.handlerMutex.Lock() @@ -338,16 +338,16 @@ func (c *localChain) IsStaleGroup(groupPublicKey []byte) (bool, error) { // IsDKGResultPublished simulates check if the result was already submitted to a // chain. -func (c *localChain) IsDKGResultSubmitted(requestID *big.Int) (bool, error) { +func (c *localChain) IsDKGResultSubmitted(signingId *big.Int) (bool, error) { c.submittedResultsMutex.Lock() defer c.submittedResultsMutex.Unlock() - return c.submittedResults[requestID.String()] != nil, nil + return c.submittedResults[signingId.String()] != nil, nil } // SubmitDKGResult submits the result to a chain. func (c *localChain) SubmitDKGResult( - requestID *big.Int, + signingId *big.Int, participantIndex group.MemberIndex, resultToPublish *relaychain.DKGResult, signatures map[group.MemberIndex]operator.Signature, @@ -357,16 +357,16 @@ func (c *localChain) SubmitDKGResult( dkgResultPublicationPromise := &async.DKGResultSubmissionPromise{} - _, ok := c.submittedResults[requestID.String()] + _, ok := c.submittedResults[signingId.String()] if ok { dkgResultPublicationPromise.Fail(fmt.Errorf( "result for request ID [%v] is already submitted", - requestID, + signingId, )) return dkgResultPublicationPromise } - c.submittedResults[requestID.String()] = resultToPublish + c.submittedResults[signingId.String()] = resultToPublish currentBlock, err := c.blockCounter.CurrentBlock() if err != nil { @@ -375,7 +375,7 @@ func (c *localChain) SubmitDKGResult( } dkgResultPublicationEvent := &event.DKGResultSubmission{ - RequestID: requestID, + SigningId: signingId, MemberIndex: uint32(participantIndex), GroupPublicKey: resultToPublish.GroupPublicKey[:], BlockNumber: currentBlock, @@ -389,7 +389,7 @@ func (c *localChain) SubmitDKGResult( groupRegistrationEvent := &event.GroupRegistration{ GroupPublicKey: resultToPublish.GroupPublicKey[:], - RequestID: requestID, + SigningId: signingId, BlockNumber: currentBlock, } diff --git a/pkg/chain/local/local_test.go b/pkg/chain/local/local_test.go index fd193cc53e..e6ae115b8f 100644 --- a/pkg/chain/local/local_test.go +++ b/pkg/chain/local/local_test.go @@ -112,10 +112,10 @@ func TestLocalSubmitRelayEntry(t *testing.T) { defer cancel() chainHandle := Connect(10, 4, big.NewInt(200)).ThresholdRelay() - requestID := int64(19) + signingId := int64(19) relayEntryPromise := chainHandle.SubmitRelayEntry( &event.Entry{ - RequestID: big.NewInt(requestID), + SigningId: big.NewInt(signingId), GroupPubKey: []byte("1"), }, ) @@ -131,11 +131,11 @@ func TestLocalSubmitRelayEntry(t *testing.T) { select { case entry := <-done: - if entry.RequestID.Int64() != requestID { + if entry.SigningId.Int64() != signingId { t.Fatalf( "Unexpected relay entry request id\nExpected: [%v]\nActual: [%v]", - requestID, - entry.RequestID.Int64(), + signingId, + entry.SigningId.Int64(), ) } case <-ctx.Done(): @@ -144,7 +144,7 @@ func TestLocalSubmitRelayEntry(t *testing.T) { } -func TestLocalOnRelayEntryGenerated(t *testing.T) { +func TestLocalOnSignatureSubmitted(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() @@ -152,7 +152,7 @@ func TestLocalOnRelayEntryGenerated(t *testing.T) { eventFired := make(chan *event.Entry) - subscription, err := chainHandle.OnRelayEntryGenerated( + subscription, err := chainHandle.OnSignatureSubmitted( func(entry *event.Entry) { eventFired <- entry }, @@ -164,7 +164,7 @@ func TestLocalOnRelayEntryGenerated(t *testing.T) { defer subscription.Unsubscribe() expectedEntry := &event.Entry{ - RequestID: big.NewInt(42), + SigningId: big.NewInt(42), Value: big.NewInt(19), GroupPubKey: []byte("1"), Seed: big.NewInt(30), @@ -187,7 +187,7 @@ func TestLocalOnRelayEntryGenerated(t *testing.T) { } } -func TestLocalOnRelayEntryGeneratedUnsubscribed(t *testing.T) { +func TestLocalOnSignatureSubmittedUnsubscribed(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() @@ -195,7 +195,7 @@ func TestLocalOnRelayEntryGeneratedUnsubscribed(t *testing.T) { eventFired := make(chan *event.Entry) - subscription, err := chainHandle.OnRelayEntryGenerated( + subscription, err := chainHandle.OnSignatureSubmitted( func(entry *event.Entry) { eventFired <- entry }, @@ -238,16 +238,16 @@ func TestLocalOnGroupRegistered(t *testing.T) { defer subscription.Unsubscribe() groupPublicKey := []byte("1") - requestID := big.NewInt(42) + signingId := big.NewInt(42) memberIndex := group.MemberIndex(1) dkgResult := &relaychain.DKGResult{GroupPublicKey: groupPublicKey} signatures := make(map[group.MemberIndex]operator.Signature) - chainHandle.SubmitDKGResult(requestID, memberIndex, dkgResult, signatures) + chainHandle.SubmitDKGResult(signingId, memberIndex, dkgResult, signatures) expectedGroupRegistrationEvent := &event.GroupRegistration{ GroupPublicKey: groupPublicKey, - RequestID: requestID, + SigningId: signingId, } select { @@ -284,12 +284,12 @@ func TestLocalOnGroupRegisteredUnsubscribed(t *testing.T) { subscription.Unsubscribe() groupPublicKey := []byte("1") - requestID := big.NewInt(42) + signingId := big.NewInt(42) memberIndex := group.MemberIndex(1) dkgResult := &relaychain.DKGResult{GroupPublicKey: groupPublicKey} signatures := make(map[group.MemberIndex]operator.Signature) - chainHandle.SubmitDKGResult(requestID, memberIndex, dkgResult, signatures) + chainHandle.SubmitDKGResult(signingId, memberIndex, dkgResult, signatures) select { case event := <-eventFired: @@ -319,15 +319,15 @@ func TestLocalOnDKGResultSubmitted(t *testing.T) { defer subscription.Unsubscribe() groupPublicKey := []byte("1") - requestID := big.NewInt(42) + signingId := big.NewInt(42) memberIndex := group.MemberIndex(1) dkgResult := &relaychain.DKGResult{GroupPublicKey: groupPublicKey} signatures := make(map[group.MemberIndex]operator.Signature) - chainHandle.SubmitDKGResult(requestID, memberIndex, dkgResult, signatures) + chainHandle.SubmitDKGResult(signingId, memberIndex, dkgResult, signatures) expectedResultSubmissionEvent := &event.DKGResultSubmission{ - RequestID: requestID, + SigningId: signingId, MemberIndex: uint32(memberIndex), GroupPublicKey: groupPublicKey, } @@ -366,12 +366,12 @@ func TestLocalOnDKGResultSubmittedUnsubscribed(t *testing.T) { subscription.Unsubscribe() groupPublicKey := []byte("1") - requestID := big.NewInt(42) + signingId := big.NewInt(42) memberIndex := group.MemberIndex(1) dkgResult := &relaychain.DKGResult{GroupPublicKey: groupPublicKey} signatures := make(map[group.MemberIndex]operator.Signature) - chainHandle.SubmitDKGResult(requestID, memberIndex, dkgResult, signatures) + chainHandle.SubmitDKGResult(signingId, memberIndex, dkgResult, signatures) select { case event := <-eventFired: @@ -536,42 +536,42 @@ func TestLocalIsGroupStale(t *testing.T) { func TestLocalIsDKGResultSubmitted(t *testing.T) { submittedResults := make(map[*big.Int][]*relaychain.DKGResult) - submittedRequestID := big.NewInt(1) + submittedSigningId := big.NewInt(1) submittedResult := &relaychain.DKGResult{ GroupPublicKey: []byte{11}, } - submittedResults[submittedRequestID] = append( - submittedResults[submittedRequestID], + submittedResults[submittedSigningId] = append( + submittedResults[submittedSigningId], submittedResult, ) chainHandle := Connect(10, 4, big.NewInt(100)).ThresholdRelay() chainHandle.SubmitDKGResult( - submittedRequestID, + submittedSigningId, group.MemberIndex(1), submittedResult, make(map[group.MemberIndex]operator.Signature), ) var tests = map[string]struct { - requestID *big.Int + signingId *big.Int expectedResult bool }{ "result for the request ID submitted": { - requestID: submittedRequestID, + signingId: submittedSigningId, expectedResult: true, }, "result for the given request ID not yet submitted": { - requestID: big.NewInt(3), + signingId: big.NewInt(3), expectedResult: false, }, } for testName, test := range tests { t.Run(testName, func(t *testing.T) { - actualResult, err := chainHandle.IsDKGResultSubmitted(test.requestID) + actualResult, err := chainHandle.IsDKGResultSubmitted(test.signingId) if err != nil { t.Fatal(err) } @@ -605,13 +605,13 @@ func TestLocalSubmitDKGResult(t *testing.T) { } // Submit new result for request ID 1 - requestID1 := big.NewInt(1) + signingId1 := big.NewInt(1) memberIndex := uint32(1) submittedResult11 := &relaychain.DKGResult{ GroupPublicKey: []byte{11}, } expectedEvent1 := &event.DKGResultSubmission{ - RequestID: requestID1, + SigningId: signingId1, MemberIndex: memberIndex, GroupPublicKey: submittedResult11.GroupPublicKey[:], BlockNumber: 0, @@ -623,15 +623,15 @@ func TestLocalSubmitDKGResult(t *testing.T) { 3: operator.Signature{103}, } - chainHandle.SubmitDKGResult(requestID1, 1, submittedResult11, signatures) + chainHandle.SubmitDKGResult(signingId1, 1, submittedResult11, signatures) if !reflect.DeepEqual( - localChain.submittedResults[requestID1.String()], + localChain.submittedResults[signingId1.String()], submittedResult11, ) { t.Fatalf("invalid submitted results for request ID %v\nexpected: %v\nactual: %v\n", - requestID1, + signingId1, []*relaychain.DKGResult{submittedResult11}, - localChain.submittedResults[requestID1.String()], + localChain.submittedResults[signingId1.String()], ) } select { @@ -647,22 +647,22 @@ func TestLocalSubmitDKGResult(t *testing.T) { } // Submit the same result for request ID 2 - requestID2 := big.NewInt(2) + signingId2 := big.NewInt(2) expectedEvent2 := &event.DKGResultSubmission{ - RequestID: requestID2, + SigningId: signingId2, MemberIndex: memberIndex, GroupPublicKey: submittedResult11.GroupPublicKey[:], } - chainHandle.SubmitDKGResult(requestID2, 1, submittedResult11, signatures) + chainHandle.SubmitDKGResult(signingId2, 1, submittedResult11, signatures) if !reflect.DeepEqual( - localChain.submittedResults[requestID2.String()], + localChain.submittedResults[signingId2.String()], submittedResult11, ) { t.Fatalf("invalid submitted results for request ID %v\nexpected: %v\nactual: %v\n", - requestID2, + signingId2, []*relaychain.DKGResult{submittedResult11}, - localChain.submittedResults[requestID2.String()], + localChain.submittedResults[signingId2.String()], ) } select { @@ -678,7 +678,7 @@ func TestLocalSubmitDKGResult(t *testing.T) { } // Submit already submitted result for request ID 1 - promise := chainHandle.SubmitDKGResult(requestID1, 1, submittedResult11, signatures) + promise := chainHandle.SubmitDKGResult(signingId1, 1, submittedResult11, signatures) promise.OnSuccess(func(result *event.DKGResultSubmission) { t.Fatalf("Should not be able to submit result for the given ID more than once") }) diff --git a/test/config.bootstrap.toml b/test/config.bootstrap.toml index bc90828e17..0eb98891d9 100644 --- a/test/config.bootstrap.toml +++ b/test/config.bootstrap.toml @@ -11,8 +11,7 @@ KeyFile = "/tmp/UTC--2018-03-11T01-37-33.202765887Z--c2a56884538778bacd91aa5bf343bf882c5fb18b" [ethereum.ContractAddresses] - KeepRandomBeacon = "0x639deb0dd975af8e4cc91fe9053a37e4faf37649" - KeepGroup = "0xcf64c2a367341170cb4e09cf8c0ed137d8473ceb" + KeepRandomBeaconOperator = "0xcf64c2a367341170cb4e09cf8c0ed137d8473ceb" [libp2p] Port = 27001 diff --git a/test/config.toml b/test/config.toml index cf3e091457..5e6ed7f10d 100644 --- a/test/config.toml +++ b/test/config.toml @@ -11,8 +11,7 @@ KeyFile = "/tmp/UTC--2018-03-11T01-37-33.202765887Z--c2a56884538778bacd91aa5bf343bf882c5fb18b" [ethereum.ContractAddresses] - KeepRandomBeacon = "0x639deb0dd975af8e4cc91fe9053a37e4faf37649" - KeepGroup = "0xcf64c2a367341170cb4e09cf8c0ed137d8473ceb" + KeepRandomBeaconOperator = "0xcf64c2a367341170cb4e09cf8c0ed137d8473ceb" [libp2p] Port = 27001