From 23993bdcfc06b8217223c02f8678de9f7ccb780f Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Wed, 2 Jun 2021 14:07:44 -0600 Subject: [PATCH 1/8] Switch e2e transfer test to use mycelo and fix its flakiness. The flakiness was due to validator rewards being added to the account being used to check tx fees being received. This was solved by using separate addresses for validator rewards and tx fees (possible since Donut). The relevant geth startup code was updated to make this possible using the corresponding flags. --- .../celotool/src/e2e-tests/transfer_tests.ts | 38 +++++++++---------- packages/celotool/src/lib/geth.ts | 11 +----- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/packages/celotool/src/e2e-tests/transfer_tests.ts b/packages/celotool/src/e2e-tests/transfer_tests.ts index f6425cd84cf..a2acc2645b0 100644 --- a/packages/celotool/src/e2e-tests/transfer_tests.ts +++ b/packages/celotool/src/e2e-tests/transfer_tests.ts @@ -157,22 +157,20 @@ describe('Transfer tests', function (this: any) { const TransferAmount: BigNumber = new BigNumber(Web3.utils.toWei('1', 'ether')) let currentGethInstance: GethInstanceConfig - const expectedProposerBlockReward: string = new BigNumber( - Web3.utils.toWei('1', 'ether') - ).toString() const validatorAddress = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95' const DEF_FROM_PK = 'f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d' const FromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631' // Arbitrary addresses. + const txFeeRecipientAddress = '0x5555555555555555555555555555555555555555' const governanceAddress = '0x00000000000000000000000000000000DeaDBeef' const ToAddress = '0xbBae99F0E1EE565404465638d40827b54D343638' - const FeeRecipientAddress = '0x4f5f8a3f45d179553e7b95119ce296010f50f6f1' + const gatewayFeeRecipientAddress = '0x4f5f8a3f45d179553e7b95119ce296010f50f6f1' const syncModes = ['full', 'fast', 'light', 'lightest'] const gethConfig: GethRunConfig = { - migrateTo: 20, + useMycelo: true, networkId: 1101, network: 'local', runPath: TMP_PATH, @@ -184,6 +182,9 @@ describe('Transfer tests', function (this: any) { { name: 'validator', validating: true, + minerValidator: validatorAddress, + // Separate address for tx fees, so that we can easy identify balance changes due to them + txFeeRecipient: txFeeRecipientAddress, syncmode: 'full', port: 30303, rpcport: 8545, @@ -214,8 +215,8 @@ describe('Transfer tests', function (this: any) { rpcport: 8547, // We need to set an etherbase here so that the full node will accept transactions from // light clients. - minerValidator: FeeRecipientAddress, - txFeeRecipient: FeeRecipientAddress, + minerValidator: gatewayFeeRecipientAddress, + txFeeRecipient: gatewayFeeRecipientAddress, } const restartWithCleanNodes = async () => { @@ -490,8 +491,8 @@ describe('Transfer tests', function (this: any) { const accounts = [ fromAddress, toAddress, - validatorAddress, - FeeRecipientAddress, + txFeeRecipientAddress, + gatewayFeeRecipientAddress, governanceAddress, ] balances = await newBalanceWatcher(kit, accounts) @@ -570,19 +571,15 @@ describe('Transfer tests', function (this: any) { } it(`should increment the gateway fee recipient's ${feeToken} balance by the gateway fee`, () => - assertEqualBN(balances.delta(FeeRecipientAddress, feeToken), txRes.fees.gateway)) + assertEqualBN(balances.delta(gatewayFeeRecipientAddress, feeToken), txRes.fees.gateway)) it(`should increment the infrastructure fund's ${feeToken} balance by the base portion of the gas fee`, () => assertEqualBN(balances.delta(governanceAddress, feeToken), txRes.fees.base)) - it(`should increment the proposers's ${feeToken} balance by the rest of the gas fee`, () => { - assertEqualBN( - balances.delta(validatorAddress, feeToken).mod(expectedProposerBlockReward), - txRes.fees.tip - ) + it(`should increment the tx fee recipient's ${feeToken} balance by the rest of the gas fee`, () => { + assertEqualBN(balances.delta(txFeeRecipientAddress, feeToken), txRes.fees.tip) }) } - describe('Normal Transfer >', () => { before(restartWithCleanNodes) @@ -597,7 +594,7 @@ describe('Transfer tests', function (this: any) { const recipient = (choice: string) => { switch (choice) { case 'peer': - return FeeRecipientAddress + return gatewayFeeRecipientAddress case 'random': return Web3.utils.randomHex(20) default: @@ -800,8 +797,7 @@ describe('Transfer tests', function (this: any) { balances = await newBalanceWatcher(kit, [ FromAddress, ToAddress, - validatorAddress, - FeeRecipientAddress, + gatewayFeeRecipientAddress, governanceAddress, ]) @@ -845,8 +841,8 @@ describe('Transfer tests', function (this: any) { it("should halve the gateway fee recipient's Celo Dollar balance then increase it by the gateway fee", () => { assertEqualBN( balances - .current(FeeRecipientAddress, StableToken.cUSD) - .minus(balances.initial(FeeRecipientAddress, StableToken.cUSD).idiv(2)), + .current(gatewayFeeRecipientAddress, StableToken.cUSD) + .minus(balances.initial(gatewayFeeRecipientAddress, StableToken.cUSD).idiv(2)), expectedFees.gateway ) }) diff --git a/packages/celotool/src/lib/geth.ts b/packages/celotool/src/lib/geth.ts index 1c79ed7aca6..01d7174be58 100644 --- a/packages/celotool/src/lib/geth.ts +++ b/packages/celotool/src/lib/geth.ts @@ -895,8 +895,6 @@ export async function startGeth( if (instance.validating && !minerValidator) { throw new Error('miner.validator address from the instance is required') } - // TODO(ponti): add flag after Donut fork - // const txFeeRecipient = instance.txFeeRecipient || minerValidator const verbosity = gethConfig.verbosity ? gethConfig.verbosity : '3' instance.args = [ @@ -920,13 +918,8 @@ export async function startGeth( ] if (minerValidator) { - instance.args.push( - '--etherbase', // TODO(ponti): change to '--miner.validator' after deprecating the 'etherbase' flag - minerValidator - ) - // TODO(ponti): add flag after Donut fork - // '--tx-fee-recipient', - // txFeeRecipient + const txFeeRecipient = instance.txFeeRecipient || minerValidator + instance.args.push('--miner.validator', minerValidator, '--tx-fee-recipient', txFeeRecipient) } if (rpcport) { From fcbbe6af29adf7885d63b9888b11151bacf8b3e1 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Wed, 2 Jun 2021 15:20:21 -0600 Subject: [PATCH 2/8] Use mycelo in e2e replica test --- packages/celotool/src/e2e-tests/replica_tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/celotool/src/e2e-tests/replica_tests.ts b/packages/celotool/src/e2e-tests/replica_tests.ts index 87d2889c104..a168a9faadd 100644 --- a/packages/celotool/src/e2e-tests/replica_tests.ts +++ b/packages/celotool/src/e2e-tests/replica_tests.ts @@ -35,7 +35,7 @@ const verbose = false describe('replica swap tests', () => { const gethConfig: GethRunConfig = { - migrate: false, + useMycelo: true, runPath: TMP_PATH, verbosity: 4, networkId: 1101, From 1a42ff7ee1b56256bc812e8b3db6aa4dd97d7676 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Wed, 2 Jun 2021 15:27:17 -0600 Subject: [PATCH 3/8] Switch validator order e2e test to use mycelo --- packages/celotool/src/e2e-tests/validator_order_tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/celotool/src/e2e-tests/validator_order_tests.ts b/packages/celotool/src/e2e-tests/validator_order_tests.ts index bac1288dff8..2e1e9097a76 100644 --- a/packages/celotool/src/e2e-tests/validator_order_tests.ts +++ b/packages/celotool/src/e2e-tests/validator_order_tests.ts @@ -16,7 +16,7 @@ describe('governance tests', () => { networkId: 1101, network: 'local', runPath: TMP_PATH, - migrateTo: 19, + useMycelo: true, instances: _.range(VALIDATORS).map((i) => ({ name: `validator${i}`, validating: true, From fe1987b6e6cad55b19373047f223730a448e2ce1 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Thu, 3 Jun 2021 10:38:57 -0600 Subject: [PATCH 4/8] geth initialize hook: don't start network unless needed for migrations. --- packages/celotool/src/e2e-tests/utils.ts | 36 +++++++++++++++--------- packages/celotool/src/lib/geth.ts | 26 +++++++---------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts index eee1d798ae9..bf58351baa6 100644 --- a/packages/celotool/src/e2e-tests/utils.ts +++ b/packages/celotool/src/e2e-tests/utils.ts @@ -22,6 +22,7 @@ import { getEnodeAddress, getLogFilename, initAndStartGeth, + initGeth, migrateContracts, resetDataDir, restoreDatadir, @@ -301,6 +302,15 @@ export function getContext(gethConfig: GethRunConfig, verbose: boolean = verbose } } + if (gethConfig.useMycelo || !(gethConfig.migrate || gethConfig.migrateTo)) { + // Just need to initialize the nodes in this case. No need to actually start the network + // since we don't need to run the migrations against it. + for (const instance of gethConfig.instances) { + await initGeth(gethConfig, gethBinaryPath, instance, verbose) + } + return + } + // Start all the instances for (const instance of gethConfig.instances) { await initAndStartGeth(gethConfig, gethBinaryPath, instance, verbose) @@ -309,20 +319,18 @@ export function getContext(gethConfig: GethRunConfig, verbose: boolean = verbose // Directly connect validator peers that are not using a bootnode or proxy. await connectValidatorPeers(gethConfig.instances) - if (!gethConfig.useMycelo && (gethConfig.migrate || gethConfig.migrateTo)) { - await Promise.all( - gethConfig.instances.filter((i) => i.validating).map((i) => waitToFinishInstanceSyncing(i)) - ) - - await migrateContracts( - MonorepoRoot, - validatorPrivateKeys, - attestationKeys, - validators.map((x) => x.address), - gethConfig.migrateTo, - gethConfig.migrationOverrides - ) - } + await Promise.all( + gethConfig.instances.filter((i) => i.validating).map((i) => waitToFinishInstanceSyncing(i)) + ) + + await migrateContracts( + MonorepoRoot, + validatorPrivateKeys, + attestationKeys, + validators.map((x) => x.address), + gethConfig.migrateTo, + gethConfig.migrationOverrides + ) } const before = async () => { diff --git a/packages/celotool/src/lib/geth.ts b/packages/celotool/src/lib/geth.ts index 01d7174be58..700bad62f38 100644 --- a/packages/celotool/src/lib/geth.ts +++ b/packages/celotool/src/lib/geth.ts @@ -736,29 +736,20 @@ export async function initAndStartGeth( instance: GethInstanceConfig, verbose: boolean ) { - const datadir = getDatadir(gethConfig.runPath, instance) - - if (verbose) { - console.info(`geth:${instance.name}: init datadir ${datadir}`) - } - - const genesisPath = path.join(gethConfig.runPath, 'genesis.json') - await init(gethBinaryPath, datadir, genesisPath, verbose) - - if (instance.privateKey) { - await importPrivateKey(gethConfig, gethBinaryPath, instance, verbose) - } - + await initGeth(gethConfig, gethBinaryPath, instance, verbose) return startGeth(gethConfig, gethBinaryPath, instance, verbose) } -export async function init( +export async function initGeth( + gethConfig: GethRunConfig, gethBinaryPath: string, - datadir: string, - genesisPath: string, + instance: GethInstanceConfig, verbose: boolean ) { + const datadir = getDatadir(gethConfig.runPath, instance) + const genesisPath = path.join(gethConfig.runPath, 'genesis.json') if (verbose) { + console.info(`geth:${instance.name}: init datadir ${datadir}`) console.log(`init geth with genesis at ${genesisPath}`) } @@ -766,6 +757,9 @@ export async function init( await spawnCmdWithExitOnFailure(gethBinaryPath, ['--datadir', datadir, 'init', genesisPath], { silent: !verbose, }) + if (instance.privateKey) { + await importPrivateKey(gethConfig, gethBinaryPath, instance, verbose) + } } export async function importPrivateKey( From 83e8dd6623eec7b4f7485c7f316b043f94c008c9 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Tue, 8 Jun 2021 08:33:26 -0600 Subject: [PATCH 5/8] Update e2e slashing tests to use mycelo --- .../celotool/src/e2e-tests/slashing_tests.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/celotool/src/e2e-tests/slashing_tests.ts b/packages/celotool/src/e2e-tests/slashing_tests.ts index 07540a5c1b5..afd075c11ad 100644 --- a/packages/celotool/src/e2e-tests/slashing_tests.ts +++ b/packages/celotool/src/e2e-tests/slashing_tests.ts @@ -4,6 +4,7 @@ import { ContractKit, newKitFromWeb3 } from '@celo/contractkit' import { ensureLeading0x } from '@celo/utils/lib/address' import BigNumber from 'bignumber.js' import { assert } from 'chai' +import * as _ from 'lodash' import * as rlp from 'rlp' import Web3 from 'web3' import { GethRunConfig } from '../lib/interfaces/geth-run-config' @@ -92,11 +93,11 @@ async function generateValidIntervalArrays( } describe('slashing tests', function (this: any) { - const gethConfigDown: GethRunConfig = { + const gethConfig: GethRunConfig = { network: 'local', networkId: 1101, runPath: TMP_PATH, - migrate: true, + useMycelo: true, genesisConfig: { churritoBlock: 0, donutBlock: 0, @@ -130,16 +131,6 @@ describe('slashing tests', function (this: any) { port: 30309, rpcport: 8551, }, - ], - } - - const gethConfig: GethRunConfig = { - network: 'local', - networkId: 1101, - runPath: TMP_PATH, - migrate: true, - instances: gethConfigDown.instances.concat([ - // Validator 4 will be down in the downtime test { name: 'validator4', validating: true, @@ -147,9 +138,14 @@ describe('slashing tests', function (this: any) { port: 30311, rpcport: 8553, }, - ]), + ], } + // Do a shallow copy so that the instance objects are the the same (even after the init step fills private keys, etc.) + const gethConfigDown = _.clone(gethConfig) + // Exclude the last validator to simulate it being down + gethConfigDown.instances = gethConfig.instances.slice(0, gethConfig.instances.length - 1) + const hooks: any = getHooks(gethConfig) const hooksDown: any = getHooks(gethConfigDown) let web3: Web3 @@ -157,7 +153,6 @@ describe('slashing tests', function (this: any) { before(async function (this: any) { this.timeout(0) - // Comment out the following line after a test run for a quick rerun. await hooks.before() }) @@ -230,6 +225,7 @@ describe('slashing tests', function (this: any) { this.timeout(0) // Disable test timeout const slasher = await kit._web3Contracts.getDowntimeSlasher() const slashableDowntime = new BigNumber(await slasher.methods.slashableDowntime().call()) + await waitForBlock(web3, 1) const blockNumber = await kit.connection.getBlockNumber() await waitForBlock(web3, blockNumber + slashableDowntime.toNumber() + 2 * safeMarginBlocks) @@ -237,7 +233,6 @@ describe('slashing tests', function (this: any) { doubleSigningBlock = await kit.connection.getBlock(blockNumber + 2 * safeMarginBlocks) const signer = await slasher.methods.validatorSignerAddressFromSet(4, blockNumber).call() - const validator = (await kit.connection.getAccounts())[0] await kit.connection.web3.eth.personal.unlockAccount(validator, '', 1000000) const lockedGold = await kit.contracts.getLockedGold() From 290b79b6dce8695a1dc91cece188056fa8f49fc3 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Tue, 15 Jun 2021 13:54:37 -0600 Subject: [PATCH 6/8] Convert e2e governance tests to use mycelo --- .../src/e2e-tests/governance_tests.ts | 89 ++++++++++--------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts index 96a80a87377..252c851f10e 100644 --- a/packages/celotool/src/e2e-tests/governance_tests.ts +++ b/packages/celotool/src/e2e-tests/governance_tests.ts @@ -7,14 +7,15 @@ import { fromFixed, toFixed } from '@celo/utils/lib/fixidity' import { bitIsSet, parseBlockExtraData } from '@celo/utils/lib/istanbul' import BigNumber from 'bignumber.js' import { assert } from 'chai' -import path from 'path' import Web3 from 'web3' -import { connectBipartiteClique, connectPeers, importGenesis, initAndStartGeth } from '../lib/geth' +import { AccountType, generateAddress, generatePrivateKey } from '../lib/generate_utils' +import { connectBipartiteClique, connectPeers, initAndStartGeth } from '../lib/geth' import { GethInstanceConfig } from '../lib/interfaces/geth-instance-config' import { GethRunConfig } from '../lib/interfaces/geth-run-config' import { assertAlmostEqual, getHooks, + mnemonic, sleep, waitForBlock, waitForEpochTransition, @@ -28,6 +29,14 @@ interface MemberSwapper { const TMP_PATH = '/tmp/e2e' const verbose = false const carbonOffsettingPartnerAddress = '0x1234567812345678123456781234567812345678' +const validatorAddress = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95' +// The tests calculate some expected values based on the rewards multiplier from the block +// before an epoch block. However, the actual rewards multiplier used for epoch rewards is +// calculated inside the epoch block. Since the multiplier depends on the timestamp, this means +// the expected values will never exactly match the actual values, so we need some tolerance. +// This constant defines the tolerance as a fraction of the expected value. +// values. We use 10^-6, so they have to be match to (nearly) 6 significant figures +const tolerance = new BigNumber(10).pow(new BigNumber(-6)) async function newMemberSwapper(kit: ContractKit, members: string[]): Promise { let index = 0 @@ -165,15 +174,15 @@ async function calculateUptime( // TODO(asa): Test independent rotation of ecdsa, bls keys. describe('governance tests', () => { const gethConfig: GethRunConfig = { - migrate: true, runPath: TMP_PATH, - verbosity: 4, - migrateTo: 25, + verbosity: 3, + useMycelo: true, networkId: 1101, network: 'local', genesisConfig: { churritoBlock: 0, donutBlock: 0, + epoch: 10, }, instances: [ // Validators 0 and 1 are swapped in and out of the group. @@ -214,11 +223,6 @@ describe('governance tests', () => { rpcport: 8553, }, ], - migrationOverrides: { - epochRewards: { - carbonOffsettingPartner: carbonOffsettingPartnerAddress, - }, - }, } const hooks: any = getHooks(gethConfig) @@ -229,7 +233,6 @@ describe('governance tests', () => { let sortedOracles: any let epochRewards: any let goldToken: any - let registry: any let reserve: any let validators: any let accounts: any @@ -250,16 +253,27 @@ describe('governance tests', () => { await hooks.restart() web3 = new Web3('http://localhost:8545') kit = newKitFromWeb3(web3) + // TODO(mcortesi): magic sleep. without it unlockAccount sometimes fails + await sleep(2) + // Assuming empty password + await kit.connection.web3.eth.personal.unlockAccount(validatorAddress, '', 1000000) goldToken = await kit._web3Contracts.getGoldToken() stableToken = await kit._web3Contracts.getStableToken() sortedOracles = await kit._web3Contracts.getSortedOracles() validators = await kit._web3Contracts.getValidators() - registry = await kit._web3Contracts.getRegistry() reserve = await kit._web3Contracts.getReserve() election = await kit._web3Contracts.getElection() epochRewards = await kit._web3Contracts.getEpochRewards() accounts = await kit._web3Contracts.getAccounts() + + await waitForBlock(web3, 1) + + const er = await kit._web3Contracts.getEpochRewards() + const fraction = await er.methods.getCarbonOffsettingFraction().call() + await er.methods + .setCarbonOffsettingFund(carbonOffsettingPartnerAddress, fraction) + .send({ from: validatorAddress }) } const getValidatorGroupMembers = async (blockNumber?: number) => { @@ -288,6 +302,12 @@ describe('governance tests', () => { const getValidatorGroupPrivateKey = async () => { const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call() + // If we're using mycelo, we can just generate the validator group key directly + const myceloAddress = generateAddress(mnemonic, AccountType.VALIDATOR_GROUP, 0) + if (myceloAddress === groupAddress) { + return '0x' + generatePrivateKey(mnemonic, AccountType.VALIDATOR_GROUP, 0) + } + // Otherwise, the validator group key is encoded in its name (see 25_elect_validators.ts) const name = await accounts.methods.getName(groupAddress).call() const encryptedKeystore64 = name.split(' ')[1] const encryptedKeystore = JSON.parse(Buffer.from(encryptedKeystore64, 'base64').toString()) @@ -315,7 +335,8 @@ describe('governance tests', () => { ) assert.isFalse(currentBalance.isNaN()) assert.isFalse(previousBalance.isNaN()) - assertAlmostEqual(currentBalance.minus(previousBalance), expected) + const margin = expected.times(tolerance) + assertAlmostEqual(currentBalance.minus(previousBalance), expected, margin) } const assertTargetVotingYieldChanged = async (blockNumber: number, expected: BigNumber) => { @@ -407,7 +428,18 @@ describe('governance tests', () => { const groupKit = newKitFromWeb3(groupWeb3) const group: string = (await groupWeb3.eth.getAccounts())[0] + // Send some funds to the group, so it can afford fees + await ( + await kit.sendTransaction({ + from: validatorAddress, + to: group, + value: Web3.utils.toWei('1', 'ether'), + }) + ).waitReceipt() + // groupKit uses a different node than kit does, so wait a second in case kit's node + // got the new block before groupKit's node did. + await sleep(1) const txos = await (await groupKit.contracts.getElection()).activate(group) for (const txo of txos) { await txo.sendAndWaitForReceipt({ from: group }) @@ -416,6 +448,8 @@ describe('governance tests', () => { validators = await groupKit._web3Contracts.getValidators() const membersToSwap = [validatorAccounts[0], validatorAccounts[1]] const memberSwapper = await newMemberSwapper(groupKit, membersToSwap) + // The memberSwapper makes a change when it's created, so we wait for epoch change so it takes effect + await waitForEpochTransition(web3, epoch) const handled: any = {} @@ -659,7 +693,8 @@ describe('governance tests', () => { const previousVotes = new BigNumber( await election.methods.getTotalVotesForGroup(group).call({}, blockNumber - 1) ) - assertAlmostEqual(currentVotes.minus(previousVotes), expected) + const margin = expected.times(tolerance) + assertAlmostEqual(currentVotes.minus(previousVotes), expected, margin) } // Returns the gas fee base for a given block, which is distributed to the governance contract. @@ -1073,30 +1108,4 @@ describe('governance tests', () => { } }) }) - - describe('after the gold token smart contract is registered', () => { - let goldGenesisSupply = new BigNumber(0) - beforeEach(async function (this: any) { - this.timeout(0) // Disable test timeout - await restart() - const genesis = await importGenesis(path.join(gethConfig.runPath, 'genesis.json')) - Object.keys(genesis.alloc).forEach((address) => { - goldGenesisSupply = goldGenesisSupply.plus(genesis.alloc[address].balance) - }) - }) - - it('should initialize the Celo Gold total supply correctly', async function (this: any) { - const events = await registry.getPastEvents('RegistryUpdated', { fromBlock: 0 }) - let blockNumber = 0 - for (const e of events) { - if (e.returnValues.identifier === 'GoldToken') { - blockNumber = e.blockNumber - break - } - } - assert.isAtLeast(blockNumber, 1) - const goldTotalSupply = await goldToken.methods.totalSupply().call({}, blockNumber) - assert.equal(goldTotalSupply, goldGenesisSupply.toFixed()) - }) - }) }) From 0e2d34e3cb46ed871f07e6993466a6bd27c27316 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Tue, 15 Jun 2021 16:12:25 -0600 Subject: [PATCH 7/8] e2e governance test: workaround for slow blockchain announce protocol startup. celo-blockchain's announce protocol initially sends out enode queries every minute initially. This is too long an interval, and it means that validator 0 (which generally sends out the first query before the other nodes are up) can go up to a minute without the other validators considering it a validator, leading to missed signatures. This commit works around that by waiting a minute after the network starts. It should be possible to remove it once the announce protocol is tweaked to fix this (e.g. the enode query frequency should be made configurable via a flag and then in the e2e tests we can set it to 1 second). --- packages/celotool/src/e2e-tests/governance_tests.ts | 2 ++ packages/celotool/src/e2e-tests/utils.ts | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts index 252c851f10e..95c74c55063 100644 --- a/packages/celotool/src/e2e-tests/governance_tests.ts +++ b/packages/celotool/src/e2e-tests/governance_tests.ts @@ -17,6 +17,7 @@ import { getHooks, mnemonic, sleep, + waitForAnnounceToStabilize, waitForBlock, waitForEpochTransition, waitToFinishInstanceSyncing, @@ -268,6 +269,7 @@ describe('governance tests', () => { accounts = await kit._web3Contracts.getAccounts() await waitForBlock(web3, 1) + await waitForAnnounceToStabilize(web3) const er = await kit._web3Contracts.getEpochRewards() const fraction = await er.methods.getCarbonOffsettingFraction().call() diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts index bf58351baa6..02db2ece41c 100644 --- a/packages/celotool/src/e2e-tests/utils.ts +++ b/packages/celotool/src/e2e-tests/utils.ts @@ -103,6 +103,16 @@ export async function waitForEpochTransition(web3: Web3, epoch: number) { } while (blockNumber % epoch !== 1) } +export async function waitForAnnounceToStabilize(web3: Web3) { + // Due to a problem in the announce protocol's settings, it can take a minute for all the validators + // to be aware of each other even though they are connected. This can lead to the first validator missing + // block signatures initially. So we wait for that to pass. + // Before we used mycelo, this wasn't noticeable because the migrations meant that the network would have + // been running for close to 10 minutes already, which was more than enough time. + // TODO: This function and its uses can be removed after the announce startup behavior has been resolved. + await waitForBlock(web3, 70) +} + export function assertAlmostEqual( actual: BigNumber, expected: BigNumber, From 9a5d3d2b4e31d6bf935c62736f2b2b7545104cca Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Wed, 16 Jun 2021 13:43:03 -0600 Subject: [PATCH 8/8] Fix e2e transfer test flakiness due to epoch rewards --- .../celotool/src/e2e-tests/transfer_tests.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/celotool/src/e2e-tests/transfer_tests.ts b/packages/celotool/src/e2e-tests/transfer_tests.ts index a2acc2645b0..ce1133f6254 100644 --- a/packages/celotool/src/e2e-tests/transfer_tests.ts +++ b/packages/celotool/src/e2e-tests/transfer_tests.ts @@ -1,6 +1,6 @@ // tslint:disable-next-line: no-reference (Required to make this work w/ ts-node) import { CeloTxPending, CeloTxReceipt, TransactionResult } from '@celo/connect' -import { CeloContract, ContractKit, newKitFromWeb3 } from '@celo/contractkit' +import { ContractKit, newKitFromWeb3 } from '@celo/contractkit' import { CeloTokenType, EachCeloToken, StableToken, Token } from '@celo/contractkit/lib/celo-tokens' import { eqAddress } from '@celo/utils/lib/address' import { toFixed } from '@celo/utils/lib/fixidity' @@ -158,13 +158,13 @@ describe('Transfer tests', function (this: any) { let currentGethInstance: GethInstanceConfig + let governanceAddress: string // set later on using the contract itself const validatorAddress = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95' const DEF_FROM_PK = 'f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d' const FromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631' // Arbitrary addresses. const txFeeRecipientAddress = '0x5555555555555555555555555555555555555555' - const governanceAddress = '0x00000000000000000000000000000000DeaDBeef' const ToAddress = '0xbBae99F0E1EE565404465638d40827b54D343638' const gatewayFeeRecipientAddress = '0x4f5f8a3f45d179553e7b95119ce296010f50f6f1' @@ -239,13 +239,17 @@ describe('Transfer tests', function (this: any) { 3 ) - // Install an arbitrary address as the goverance address to act as the infrastructure fund. - // This is chosen instead of full migration for speed and to avoid the need for a governance - // proposal, as all contracts are owned by governance once the migration is complete. - const registry = await kit._web3Contracts.getRegistry() - const tx = registry.methods.setAddressFor(CeloContract.Governance, governanceAddress) - const gas = await tx.estimateGas({ from: validatorAddress }) - await tx.send({ from: validatorAddress, gas }) + governanceAddress = (await kit._web3Contracts.getGovernance()).options.address + // The tests below check the balance of the governance contract (i.e. the community fund) + // before and after transactions to verify the correct amount has been received from the fees. + // This causes flakiness due to the fund also receiving epoch rewards (if the epoch change is + // between the blocks the balance checker uses as its before and after the test will fail due + // to the unexpected change from the epoch rewards). + // To avoid this, we set the community fund's fraction of epoch rewards to zero. + // Another option would have been to make the epoch size large enough so no epoch changes happen + // during the test. + const epochRewards = await kit._web3Contracts.getEpochRewards() + await epochRewards.methods.setCommunityRewardFraction(0).send({ from: validatorAddress }) // Give the account we will send transfers as sufficient gold and dollars. const startBalance = TransferAmount.times(500)