Skip to content

Commit

Permalink
Storing previous randomness values (#1197)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrsmkl authored and celo-ci-bot-user committed Oct 25, 2019
1 parent 947e237 commit e153a29
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 35 deletions.
122 changes: 113 additions & 9 deletions packages/protocol/contracts/identity/Random.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
pragma solidity ^0.5.3;

import "./interfaces/IRandom.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";

import "../common/Initializable.sol";

/**
* @title Provides randomness for verifier selection
*/
contract Random is IRandom {
contract Random is IRandom, Ownable, Initializable {

using SafeMath for uint256;

/* Stores most recent commitment per address */
mapping(address => bytes32) public commitments;

bytes32 public _random;
uint256 public randomnessBlockRetentionWindow = 256;

mapping (uint256 => bytes32) private history;
uint256 private historyFirst;
uint256 private historySize;

event RandomnessBlockRetentionWindowSet(uint256 value);

function initialize() external {
/**
* @notice Initializes the contract with initial parameters.
* @param _randomnessBlockRetentionWindow Number of old random blocks whose randomness
* values can be queried.
*/
function initialize(uint256 _randomnessBlockRetentionWindow) external initializer {
_transferOwnership(msg.sender);
setRandomnessBlockRetentionWindow(_randomnessBlockRetentionWindow);
}

/**
* @notice Sets the number of old random blocks whose randomness values can be queried.
* @param value Number of old random blocks whose randomness values can be queried.
*/
function setRandomnessBlockRetentionWindow(uint256 value) public onlyOwner {
require(value > 0, "randomnessBlockRetetionWindow cannot be zero");
randomnessBlockRetentionWindow = value;
emit RandomnessBlockRetentionWindowSet(value);
}

/**
Expand All @@ -31,28 +59,104 @@ contract Random is IRandom {
bytes32 newCommitment,
address proposer
) external {
require(msg.sender == address(0));
require(msg.sender == address(0), "only VM can call");
_revealAndCommit(randomness, newCommitment, proposer);
}

/**
* @notice Implements step of the randomness protocol.
* @param randomness Bytes that will be added to the entropy pool.
* @param newCommitment The hash of randomness that will be revealed in the future.
* @param proposer Address of the block proposer.
*/
function _revealAndCommit(
bytes32 randomness,
bytes32 newCommitment,
address proposer
) internal {
// ensure revealed randomness matches previous commitment
if (commitments[proposer] != 0) {
require(randomness != 0);
require(randomness != 0, "randomness cannot be zero if there is a previous commitment");
bytes32 expectedCommitment = computeCommitment(randomness);
require(expectedCommitment == commitments[proposer]);
require(
expectedCommitment == commitments[proposer],
"commitment didn't match the posted randomness"
);
} else {
require(randomness == 0);
require(randomness == 0, "randomness should be zero if there is no previous commitment");
}

// add entropy
_random = keccak256(abi.encodePacked(_random, randomness));
uint256 blockNumber = block.number == 0 ? 0 : block.number.sub(1);
addRandomness(block.number, keccak256(abi.encodePacked(history[blockNumber], randomness)));

commitments[proposer] = newCommitment;
}

/**
* @notice Add a value to the randomness history.
* @param blockNumber Current block number.
* @param randomness The new randomness added to history.
* @dev The calls to this function should be made so that on the next call, blockNumber will
* be the previous one, incremented by one.
*/
function addRandomness(uint256 blockNumber, bytes32 randomness) internal {
history[blockNumber] = randomness;
if (historySize == 0) {
historyFirst = block.number;
historySize = 1;
} else if (historySize > randomnessBlockRetentionWindow) {
delete history[historyFirst];
delete history[historyFirst+1];
historyFirst += 2;
historySize--;
} else if (historySize == randomnessBlockRetentionWindow) {
delete history[historyFirst];
historyFirst++;
} else /* historySize < randomnessBlockRetentionWindow */ {
historySize++;
}
}

/**
* @notice Compute the commitment hash for a given randomness value.
* @param randomness The value for which the commitment hash is computed.
* @return Commitment parameter.
*/
function computeCommitment(bytes32 randomness) public pure returns (bytes32) {
return keccak256(abi.encodePacked(randomness));
}

/**
* @notice Querying the current randomness value.
* @return Returns the current randomness value.
*/
function random() external view returns (bytes32) {
return _random;
return _getBlockRandomness(block.number, block.number);
}

/**
* @notice Get randomness values of previous blocks.
* @param blockNumber The number of block whose randomness value we want to know.
* @return The associated randomness value.
*/
function getBlockRandomness(uint256 blockNumber) external view returns (bytes32) {
return _getBlockRandomness(blockNumber, block.number);
}

/**
* @notice Get randomness values of previous blocks.
* @param blockNumber The number of block whose randomness value we want to know.
* @param cur Number of the current block.
* @return The associated randomness value.
*/
function _getBlockRandomness(uint256 blockNumber, uint256 cur) internal view returns (bytes32) {
require(blockNumber <= cur, "Cannot query randomness of future blocks");
require(
blockNumber > cur.sub(historySize) &&
(randomnessBlockRetentionWindow >= cur ||
blockNumber > cur.sub(randomnessBlockRetentionWindow)),
"Cannot query randomness older than the stored history");
return history[blockNumber];
}
}
13 changes: 13 additions & 0 deletions packages/protocol/contracts/identity/test/TestRandom.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pragma solidity ^0.5.3;

import "../Random.sol";

contract TestRandom is Random {
function addTestRandomness(uint256 blockNumber, bytes32 randomness) external {
addRandomness(blockNumber, randomness);
}
function getTestRandomness(uint256 blockNumber, uint256 cur) external view returns (bytes32) {
return _getBlockRandomness(blockNumber, cur);
}
}

12 changes: 11 additions & 1 deletion packages/protocol/migrations/13_random.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
import { RandomInstance } from 'types'

module.exports = deploymentForCoreContract<RandomInstance>(web3, artifacts, CeloContractName.Random)
const initializeArgs = async (_: string): Promise<any[]> => {
return [config.random.randomnessBlockRetentionWindow]
}

module.exports = deploymentForCoreContract<RandomInstance>(
web3,
artifacts,
CeloContractName.Random,
initializeArgs
)
3 changes: 2 additions & 1 deletion packages/protocol/migrations/16_governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ module.exports = deploymentForCoreContract<GovernanceInstance>(
)
await reserve.addSpender(governance.address)

const proxyOwnedByGovernance = ['GoldToken', 'Random']
const proxyOwnedByGovernance = ['GoldToken']
await Promise.all(
proxyOwnedByGovernance.map((contractName) =>
transferOwnershipOfProxy(contractName, governance.address, artifacts)
Expand All @@ -61,6 +61,7 @@ module.exports = deploymentForCoreContract<GovernanceInstance>(
'GasPriceMinimum',
'Governance',
'LockedGold',
'Random',
'Registry',
'Reserve',
'SortedOracles',
Expand Down
37 changes: 20 additions & 17 deletions packages/protocol/migrationsConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ const DefaultConfig = {
attestationExpirySeconds: 60 * 60, // 1 hour,
attestationRequestFeeInDollars: 0.05,
},
lockedGold: {
unlockingPeriod: 60 * 60 * 24 * 3, // 3 days
},
oracles: {
reportExpiry: 60 * 60, // 1 hour
blockchainParameters: {
minimumClientVersion: {
major: 1,
minor: 8,
patch: 23,
},
},
election: {
minElectableValidators: '22',
Expand All @@ -29,6 +30,12 @@ const DefaultConfig = {
updateFrequency: 3600,
minimumReports: 1,
},
gasPriceMinimum: {
initialMinimum: 10000,
targetDensity: 1 / 2,
adjustmentSpeed: 1 / 2,
proposerFraction: 1 / 2,
},
governance: {
approvalStageDuration: 15 * 60, // 15 minutes
concurrentProposals: 10,
Expand All @@ -42,11 +49,14 @@ const DefaultConfig = {
participationBaselineUpdateFactor: 1 / 5,
participationBaselineQuorumFactor: 1,
},
gasPriceMinimum: {
initialMinimum: 10000,
targetDensity: 1 / 2,
adjustmentSpeed: 1 / 2,
proposerFraction: 1 / 2,
lockedGold: {
unlockingPeriod: 60 * 60 * 24 * 3, // 3 days
},
oracles: {
reportExpiry: 60 * 60, // 1 hour
},
random: {
randomnessBlockRetentionWindow: 256,
},
registry: {
predeployedProxyAddress: '0x000000000000000000000000000000000000ce10',
Expand Down Expand Up @@ -91,13 +101,6 @@ const DefaultConfig = {
groupName: 'C-Labs',
commission: 0.1,
},
blockchainParameters: {
minimumClientVersion: {
major: 1,
minor: 8,
patch: 23,
},
},
}

const linkedLibraries = {
Expand Down
11 changes: 6 additions & 5 deletions packages/protocol/test/identity/attestations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import {
MockLockedGoldInstance,
MockStableTokenContract,
MockStableTokenInstance,
MockElectionContract,
MockElectionInstance,
RandomContract,
RandomInstance,
TestRandomContract,
TestRandomInstance,
MockElectionContract,
RegistryContract,
RegistryInstance,
} from 'types'
Expand All @@ -37,7 +37,7 @@ const Attestations: TestAttestationsContract = artifacts.require('TestAttestatio
const MockStableToken: MockStableTokenContract = artifacts.require('MockStableToken')
const MockElection: MockElectionContract = artifacts.require('MockElection')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
const Random: RandomContract = artifacts.require('Random')
const Random: TestRandomContract = artifacts.require('TestRandom')
const Registry: RegistryContract = artifacts.require('Registry')

const dataEncryptionKey = '0x02f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e01611111111'
Expand All @@ -49,7 +49,7 @@ contract('Attestations', (accounts: string[]) => {
let attestations: TestAttestationsInstance
let mockStableToken: MockStableTokenInstance
let otherMockStableToken: MockStableTokenInstance
let random: RandomInstance
let random: TestRandomInstance
let mockElection: MockElectionInstance
let mockLockedGold: MockLockedGoldInstance
let registry: RegistryInstance
Expand Down Expand Up @@ -142,6 +142,7 @@ contract('Attestations', (accounts: string[]) => {
otherMockStableToken = await MockStableToken.new()
attestations = await Attestations.new()
random = await Random.new()
random.addTestRandomness(0, '0x00')
mockLockedGold = await MockLockedGold.new()
await Promise.all(
accounts.map((account) =>
Expand Down
Loading

0 comments on commit e153a29

Please sign in to comment.