Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose and test validator set precompiles #874

Merged
merged 19 commits into from
Sep 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 182 additions & 7 deletions packages/celotool/src/e2e-tests/governance_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { assert } from 'chai'
import Web3 from 'web3'
import { strip0x } from '../lib/utils'
import {
assertRevert,
erc20Abi,
getContext,
getContractAddress,
getEnode,
getHooks,
importGenesis,
initAndStartGeth,
sleep,
Expand Down Expand Up @@ -167,6 +168,39 @@ const validatorsAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'index',
type: 'uint256',
},
],
name: 'validatorAddressFromCurrentSet',
outputs: [
{
name: '',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'numberValidatorsInCurrentSet',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
]

describe('governance tests', () => {
Expand All @@ -181,21 +215,21 @@ describe('governance tests', () => {
],
}

const hooks: any = getHooks(gethConfig)
const context: any = getContext(gethConfig)
let web3: any
let lockedGold: any
let validators: any
let goldToken: any

before(async function(this: any) {
this.timeout(0)
await hooks.before()
await context.hooks.before()
})

after(hooks.after)
after(context.hooks.after)

const restart = async () => {
await hooks.restart()
await context.hooks.restart()
web3 = new Web3('http://localhost:8545')
lockedGold = new web3.eth.Contract(lockedGoldAbi, await getContractAddress('LockedGoldProxy'))
goldToken = new web3.eth.Contract(erc20Abi, await getContractAddress('GoldTokenProxy'))
Expand Down Expand Up @@ -289,6 +323,147 @@ describe('governance tests', () => {
return tx.send({ from: account, ...txOptions, gas })
}

describe('Validators.numberValidatorsInCurrentSet()', () => {
before(async function() {
this.timeout(0)
await restart()
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
})

it('should return the validator set size', async () => {
const numberValidators = await validators.methods.numberValidatorsInCurrentSet().call()

assert.equal(numberValidators, 5)
})

describe('after the validator set changes', () => {
before(async function() {
this.timeout(0)
await restart()
const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
const epoch = 10

const groupInstance = {
name: 'validatorGroup',
validating: false,
syncmode: 'full',
port: 30325,
wsport: 8567,
privateKey: groupPrivateKey.slice(2),
peers: [await getEnode(8545)],
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
const groupWeb3 = new Web3('ws://localhost:8567')
validators = new groupWeb3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
)
// Give the node time to sync.
await sleep(15)
const members = await getValidatorGroupMembers()
await removeMember(groupWeb3, groupAddress, members[0])
await sleep(epoch * 2)
})

it('should return the reduced validator set size', async () => {
const numberValidators = await validators.methods.numberValidatorsInCurrentSet().call()

assert.equal(numberValidators, 4)
})
})
})

describe('Validators.validatorAddressFromCurrentSet()', () => {
before(async function() {
this.timeout(0)
await restart()
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
})

it('should return the first validator', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(0).call()

assert.equal(strip0x(resultAddress), context.validators[0].address)
})

it('should return the third validator', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(2).call()

assert.equal(strip0x(resultAddress), context.validators[2].address)
})

it('should return the fifth validator', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(4).call()

assert.equal(strip0x(resultAddress), context.validators[4].address)
})

it('should revert when asked for an out of bounds validator', async function(this: any) {
this.timeout(0) // Disable test timeout
await assertRevert(
validators.methods.validatorAddressFromCurrentSet(5).send({
from: `0x${context.validators[0].address}`,
})
)
})

describe('after the validator set changes', () => {
before(async function() {
this.timeout(0)
await restart()
const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
const epoch = 10

const groupInstance = {
name: 'validatorGroup',
validating: false,
syncmode: 'full',
port: 30325,
wsport: 8567,
privateKey: groupPrivateKey.slice(2),
peers: [await getEnode(8545)],
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
const groupWeb3 = new Web3('ws://localhost:8567')
validators = new groupWeb3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
)
// Give the node time to sync.
await sleep(15)
const members = await getValidatorGroupMembers()
await removeMember(groupWeb3, groupAddress, members[0])
await sleep(epoch * 2)

validators = new web3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
)
})

it('should return the second validator in the first place', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(0).call()

assert.equal(strip0x(resultAddress), context.validators[1].address)
})

it('should return the last validator in the fourth place', async () => {
const resultAddress = await validators.methods.validatorAddressFromCurrentSet(3).call()

assert.equal(strip0x(resultAddress), context.validators[4].address)
})

it('should revert when asked for an out of bounds validator', async function(this: any) {
this.timeout(0)
await assertRevert(
validators.methods.validatorAddressFromCurrentSet(4).send({
from: `0x${context.validators[0].address}`,
})
)
})
})
})

describe('when the validator set is changing', () => {
const epoch = 10
const expectedEpochMembership = new Map()
Expand All @@ -306,7 +481,7 @@ describe('governance tests', () => {
privateKey: groupPrivateKey.slice(2),
peers: [await getEnode(8545)],
}
await initAndStartGeth(hooks.gethBinaryPath, groupInstance)
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
const groupWeb3 = new Web3('ws://localhost:8567')
validators = new groupWeb3.eth.Contract(
validatorsAbi,
Expand Down Expand Up @@ -377,7 +552,7 @@ describe('governance tests', () => {
rpcport: 8567,
privateKey: 'f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d',
}
await initAndStartGeth(hooks.gethBinaryPath, delegateInstance)
await initAndStartGeth(context.hooks.gethBinaryPath, delegateInstance)
// Note that we don't need to create an account or make a commitment as this has already been
// done in the migration.
await delegateRewards(account, delegate)
Expand Down
11 changes: 9 additions & 2 deletions packages/celotool/src/e2e-tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export async function initAndStartGeth(gethBinaryPath: string, instance: GethIns
return startGeth(gethBinaryPath, instance)
}

export function getHooks(gethConfig: GethTestConfig) {
export function getContext(gethConfig: GethTestConfig) {
const mnemonic =
'jazz ripple brown cloth door bridge pen danger deer thumb cable prepare negative library vast'
const validatorInstances = gethConfig.instances.filter((x: any) => x.validating)
Expand Down Expand Up @@ -449,7 +449,14 @@ export function getHooks(gethConfig: GethTestConfig) {

const after = () => killGeth()

return { before, after, restart, gethBinaryPath }
return {
validators,
hooks: { before, after, restart, gethBinaryPath },
}
}

export function getHooks(gethConfig: GethTestConfig) {
return getContext(gethConfig).hooks
}

export async function assertRevert(promise: any, errorMessage: string = '') {
Expand Down
42 changes: 42 additions & 0 deletions packages/protocol/contracts/governance/Validators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
using SafeMath for uint256;
using BytesLib for bytes;

// Address of the getValidator precompiled contract
address constant public GET_VALIDATOR_ADDRESS = address(0xfa);

// TODO(asa): These strings should be modifiable
struct ValidatorGroup {
string identifier;
Expand Down Expand Up @@ -543,6 +546,45 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}

function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
address validatorAddress;
assembly {
let newCallDataPosition := mload(0x40)
mstore(newCallDataPosition, index)
let success := staticcall(
5000,
0xfa,
newCallDataPosition,
32,
0,
0
)
returndatacopy(add(newCallDataPosition, 64), 0, 32)
validatorAddress := mload(add(newCallDataPosition, 64))
}

return validatorAddress;
}

function numberValidatorsInCurrentSet() external view returns (uint256) {
uint256 numberValidators;
assembly {
let success := staticcall(
5000,
0xf9,
0,
0,
0,
0
)
let returnData := mload(0x40)
returndatacopy(returnData, 0, 32)
numberValidators := mload(returnData)
}

return numberValidators;
}

/**
* @notice Returns validator information.
* @param account The account that registered the validator.
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/truffle.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const networks = {
...defaultConfig,
from: DEVELOPMENT_FROM,
gasPrice: 0,
gas: 7000000,
defaultBalance: 1000000,
mnemonic: 'concert load couple harbor equip island argue ramp clarify fence smart topic',
},
Expand Down