From 3a0acc56b72b3978d5432f0e2db4c8db85df4cc7 Mon Sep 17 00:00:00 2001 From: Marcin Chrzanowski Date: Thu, 21 Nov 2019 10:42:56 +0100 Subject: [PATCH 01/11] Freeze rewards distribution --- .../protocol/contracts/governance/EpochRewards.sol | 11 +++++++++-- packages/protocol/migrations/14_epoch_rewards.ts | 5 ++++- packages/protocol/test/governance/epochrewards.ts | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index 6642ee226d6..4b91c56d34e 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -3,6 +3,7 @@ pragma solidity ^0.5.3; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "../baklava/Freezable.sol"; import "../common/FixidityLib.sol"; import "../common/Initializable.sol"; import "../common/UsingRegistry.sol"; @@ -11,7 +12,7 @@ import "../common/UsingPrecompiles.sol"; /** * @title Contract for calculating epoch rewards. */ -contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry { +contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry, Freezable { using FixidityLib for FixidityLib.Fraction; using SafeMath for uint256; @@ -82,6 +83,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry */ function initialize( address registryAddress, + address _freezer, uint256 targetVotingYieldInitial, uint256 targetVotingYieldMax, uint256 targetVotingYieldAdjustmentFactor, @@ -92,6 +94,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry uint256 _targetValidatorEpochPayment ) external initializer { _transferOwnership(msg.sender); + setFreezer(_freezer); setRegistry(registryAddress); setTargetVotingYieldParameters(targetVotingYieldMax, targetVotingYieldAdjustmentFactor); setRewardsMultiplierParameters( @@ -127,6 +130,10 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry ); } + function setFreezer(address freezer) public onlyOwner { + _setFreezer(freezer); + } + /** * @notice Sets the target voting Gold fraction. * @param value The percentage of floating Gold voting to target. @@ -362,7 +369,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry * voting Gold fraction. * @dev Only called directly by the protocol. */ - function updateTargetVotingYield() external { + function updateTargetVotingYield() external onlyWhenNotFrozen { require(msg.sender == address(0)); _updateTargetVotingYield(); } diff --git a/packages/protocol/migrations/14_epoch_rewards.ts b/packages/protocol/migrations/14_epoch_rewards.ts index 43dfa88fc49..7c8756cead1 100644 --- a/packages/protocol/migrations/14_epoch_rewards.ts +++ b/packages/protocol/migrations/14_epoch_rewards.ts @@ -3,10 +3,13 @@ import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils' import { config } from '@celo/protocol/migrationsConfig' import { toFixed } from '@celo/utils/lib/fixidity' import { EpochRewardsInstance } from 'types' +const truffle = require('@celo/protocol/truffle-config.js') -const initializeArgs = async (): Promise => { +const initializeArgs = async (networkName: string): Promise => { + const network: any = truffle.networks[networkName] return [ config.registry.predeployedProxyAddress, + network.from, toFixed(config.epochRewards.targetVotingYieldParameters.initial).toFixed(), toFixed(config.epochRewards.targetVotingYieldParameters.max).toFixed(), toFixed(config.epochRewards.targetVotingYieldParameters.adjustmentFactor).toFixed(), diff --git a/packages/protocol/test/governance/epochrewards.ts b/packages/protocol/test/governance/epochrewards.ts index b0605142ad1..9d5d74e365c 100644 --- a/packages/protocol/test/governance/epochrewards.ts +++ b/packages/protocol/test/governance/epochrewards.ts @@ -84,6 +84,7 @@ contract('EpochRewards', (accounts: string[]) => { await epochRewards.initialize( registry.address, + accounts[0], targetVotingYieldParams.initial, targetVotingYieldParams.max, targetVotingYieldParams.adjustmentFactor, @@ -123,6 +124,7 @@ contract('EpochRewards', (accounts: string[]) => { await assertRevert( epochRewards.initialize( registry.address, + account[0], targetVotingYieldParams.initial, targetVotingYieldParams.max, targetVotingYieldParams.adjustmentFactor, From 1af99f0bd3f4ac6df2aa8ee274acaa11244402f7 Mon Sep 17 00:00:00 2001 From: Marcin Chrzanowski Date: Thu, 21 Nov 2019 17:27:56 +0100 Subject: [PATCH 02/11] Test rewards are not distributed after freezing --- .../src/e2e-tests/governance_tests.ts | 137 ++++++++++++------ 1 file changed, 96 insertions(+), 41 deletions(-) diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts index bbb13416cb5..03d0b0f6160 100644 --- a/packages/celotool/src/e2e-tests/governance_tests.ts +++ b/packages/celotool/src/e2e-tests/governance_tests.ts @@ -227,6 +227,65 @@ describe('governance tests', () => { assertAlmostEqual(currentBalance.minus(previousBalance), expected) } + const waitForBlock = async (blockNumber: number) => { + // const epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber() + let currentBlock: number + do { + currentBlock = await web3.eth.getBlockNumber() + await sleep(0.1) + } while (currentBlock < blockNumber) + } + + const waitForEpochTransition = async (epoch: number) => { + // const epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber() + let blockNumber: number + do { + blockNumber = await web3.eth.getBlockNumber() + await sleep(0.1) + } while (blockNumber % epoch !== 1) + } + + const assertTargetVotingYieldChanged = async (blockNumber: number, expected: BigNumber) => { + const currentTarget = new BigNumber( + (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[0] + ) + const previousTarget = new BigNumber( + (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber - 1))[0] + ) + const difference = currentTarget.minus(previousTarget) + + // Assert equal to 9 decimal places due to rounding errors. + assert.equal( + fromFixed(difference) + .dp(9) + .toFixed(), + fromFixed(expected) + .dp(9) + .toFixed() + ) + } + + const assertTargetVotingYieldUnchanged = async (blockNumber: number) => { + await assertTargetVotingYieldChanged(blockNumber, new BigNumber(0)) + } + + const getLastEpochBlock = (blockNumber: number, epoch: number) => { + const epochNumber = Math.floor((blockNumber - 1) / epoch) + return epochNumber * epoch + } + + const assertGoldTokenTotalSupplyUnchanged = async (blockNumber: number) => { + await assertGoldTokenTotalSupplyChanged(blockNumber, new BigNumber(0)) + } + + const assertGoldTokenTotalSupplyChanged = async (blockNumber: number, expected: BigNumber) => { + const currentSupply = new BigNumber(await goldToken.methods.totalSupply().call({}, blockNumber)) + const previousSupply = new BigNumber( + await goldToken.methods.totalSupply().call({}, blockNumber - 1) + ) + assertAlmostEqual(currentSupply.minus(previousSupply), expected) + } + describe('when the validator set is changing', () => { let epoch: number const blockNumbers: number[] = [] @@ -292,16 +351,10 @@ describe('governance tests', () => { assert.equal(epoch, 10) // Wait for an epoch transition so we can activate our vote. - let blockNumber: number - do { - blockNumber = await web3.eth.getBlockNumber() - await sleep(0.1) - } while (blockNumber % epoch !== 1) + await waitForEpochTransition(epoch) // Wait for an extra epoch transition to ensure everyone is connected to one another. - do { - blockNumber = await web3.eth.getBlockNumber() - await sleep(0.1) - } while (blockNumber % epoch !== 1) + await waitForEpochTransition(epoch) + await activate(validatorAccounts[0]) // Prepare for member swapping. @@ -363,14 +416,9 @@ describe('governance tests', () => { ) } - const getLastEpochBlock = (blockNumber: number) => { - const epochNumber = Math.floor((blockNumber - 1) / epoch) - return epochNumber * epoch - } - it('should always return a validator set size equal to the number of group members at the end of the last epoch', async () => { for (const blockNumber of blockNumbers) { - const lastEpochBlock = getLastEpochBlock(blockNumber) + const lastEpochBlock = getLastEpochBlock(blockNumber, epoch) const validatorSetSize = await election.methods .numberValidatorsInCurrentSet() .call({}, blockNumber) @@ -382,7 +430,7 @@ describe('governance tests', () => { it('should always return a validator set equal to the signing keys of the group members at the end of the last epoch', async function(this: any) { this.timeout(0) for (const blockNumber of blockNumbers) { - const lastEpochBlock = getLastEpochBlock(blockNumber) + const lastEpochBlock = getLastEpochBlock(blockNumber, epoch) const memberAccounts = await getValidatorGroupMembers(lastEpochBlock) const memberSigners = await Promise.all( memberAccounts.map((v: string) => getValidatorSigner(v, lastEpochBlock)) @@ -397,7 +445,7 @@ describe('governance tests', () => { it('should block propose in a round robin fashion', async () => { let roundRobinOrder: string[] = [] for (const blockNumber of blockNumbers) { - const lastEpochBlock = getLastEpochBlock(blockNumber) + const lastEpochBlock = getLastEpochBlock(blockNumber, epoch) // Fetch the round robin order if it hasn't already been set for this epoch. if (roundRobinOrder.length === 0 || blockNumber === lastEpochBlock + 1) { const validatorSet = await getValidatorSetSignersAtBlock(blockNumber) @@ -646,30 +694,6 @@ describe('governance tests', () => { }) it('should update the target voting yield', async () => { - const assertTargetVotingYieldChanged = async (blockNumber: number, expected: BigNumber) => { - const currentTarget = new BigNumber( - (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[0] - ) - const previousTarget = new BigNumber( - (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber - 1))[0] - ) - const difference = currentTarget.minus(previousTarget) - - // Assert equal to 9 decimal places due to rounding errors. - assert.equal( - fromFixed(difference) - .dp(9) - .toFixed(), - fromFixed(expected) - .dp(9) - .toFixed() - ) - } - - const assertTargetVotingYieldUnchanged = async (blockNumber: number) => { - await assertTargetVotingYieldChanged(blockNumber, new BigNumber(0)) - } - for (const blockNumber of blockNumbers) { if (isLastBlockOfEpoch(blockNumber, epoch)) { // We use the voting gold fraction from before the rewards are granted. @@ -694,6 +718,37 @@ describe('governance tests', () => { }) }) + describe('when rewards distribution is frozen', () => { + before(restart) + + let epoch: number + let blockFrozen: number + let latestBlock: number + + beforeEach(async function(this: any) { + this.timeout(0) + const validator = (await kit.web3.eth.getAccounts())[0] + await kit.web3.eth.personal.unlockAccount(validator, '', 1000000) + await epochRewards.methods.freeze().send({ from: validator }) + blockFrozen = await web3.eth.getBlockNumber() + epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber() + await waitForBlock(blockFrozen + epoch * 2) + latestBlock = await web3.eth.getBlockNumber() + }) + + it('should not update the target voing yield', async () => { + for (let blockNumber = blockFrozen; blockNumber < latestBlock; blockNumber++) { + await assertTargetVotingYieldUnchanged(blockNumber) + } + }) + + it('should not mint new Celo Gold', async () => { + for (let blockNumber = blockFrozen; blockNumber < latestBlock; blockNumber++) { + await assertGoldTokenTotalSupplyUnchanged(blockNumber) + } + }) + }) + describe('after the gold token smart contract is registered', () => { let goldGenesisSupply = new BigNumber(0) beforeEach(async function(this: any) { From ac428daaa8475a77a51c2ca4357e3ec0a0ba525b Mon Sep 17 00:00:00 2001 From: Marcin Chrzanowski Date: Sat, 23 Nov 2019 22:34:59 +0100 Subject: [PATCH 03/11] Refactor * Appease the linter * beforeEach -> before --- .../src/e2e-tests/governance_tests.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts index 03d0b0f6160..e19a2ce41a4 100644 --- a/packages/celotool/src/e2e-tests/governance_tests.ts +++ b/packages/celotool/src/e2e-tests/governance_tests.ts @@ -602,19 +602,6 @@ describe('governance tests', () => { return new BigNumber(gpm).times(new BigNumber(gas)) } - const assertGoldTokenTotalSupplyChanged = async ( - blockNumber: number, - expected: BigNumber - ) => { - const currentSupply = new BigNumber( - await goldToken.methods.totalSupply().call({}, blockNumber) - ) - const previousSupply = new BigNumber( - await goldToken.methods.totalSupply().call({}, blockNumber - 1) - ) - assertAlmostEqual(currentSupply.minus(previousSupply), expected) - } - const assertLockedGoldBalanceChanged = async (blockNumber: number, expected: BigNumber) => { await assertBalanceChanged(lockedGold.options.address, blockNumber, expected, goldToken) } @@ -627,10 +614,6 @@ describe('governance tests', () => { await assertVotesChanged(blockNumber, new BigNumber(0)) } - const assertGoldTokenTotalSupplyUnchanged = async (blockNumber: number) => { - await assertGoldTokenTotalSupplyChanged(blockNumber, new BigNumber(0)) - } - const assertLockedGoldBalanceUnchanged = async (blockNumber: number) => { await assertLockedGoldBalanceChanged(blockNumber, new BigNumber(0)) } @@ -725,7 +708,7 @@ describe('governance tests', () => { let blockFrozen: number let latestBlock: number - beforeEach(async function(this: any) { + before(async function(this: any) { this.timeout(0) const validator = (await kit.web3.eth.getAccounts())[0] await kit.web3.eth.personal.unlockAccount(validator, '', 1000000) From e9639a1120a5fca3b51138c92f3af011775f00b2 Mon Sep 17 00:00:00 2001 From: Marcin Chrzanowski Date: Sun, 24 Nov 2019 18:21:27 +0100 Subject: [PATCH 04/11] Fix typo --- packages/protocol/test/governance/epochrewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test/governance/epochrewards.ts b/packages/protocol/test/governance/epochrewards.ts index d1ab5373e9a..c8463eec3c1 100644 --- a/packages/protocol/test/governance/epochrewards.ts +++ b/packages/protocol/test/governance/epochrewards.ts @@ -131,7 +131,7 @@ contract('EpochRewards', (accounts: string[]) => { await assertRevert( epochRewards.initialize( registry.address, - account[0], + accounts[0], targetVotingYieldParams.initial, targetVotingYieldParams.max, targetVotingYieldParams.adjustmentFactor, From 644441e520d2417f40f7738cc20e62e6cccb6457 Mon Sep 17 00:00:00 2001 From: Marcin Chrzanowski Date: Mon, 25 Nov 2019 11:32:28 +0100 Subject: [PATCH 05/11] Return 0s from rewards calculations when frozen --- .../contracts/governance/EpochRewards.sol | 4 ++++ packages/protocol/test/governance/epochrewards.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index dbad334d271..994887df029 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -379,6 +379,10 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry * @return The per validator epoch payment and the total rewards to voters. */ function calculateTargetEpochPaymentAndRewards() external view returns (uint256, uint256) { + if (frozen) { + return (0, 0); + } + uint256 targetEpochRewards = getTargetEpochRewards(); uint256 targetTotalEpochPaymentsInGold = getTargetTotalEpochPaymentsInGold(); uint256 targetGoldSupplyIncrease = targetEpochRewards.add(targetTotalEpochPaymentsInGold); diff --git a/packages/protocol/test/governance/epochrewards.ts b/packages/protocol/test/governance/epochrewards.ts index c8463eec3c1..3106f052aab 100644 --- a/packages/protocol/test/governance/epochrewards.ts +++ b/packages/protocol/test/governance/epochrewards.ts @@ -580,4 +580,19 @@ contract('EpochRewards', (accounts: string[]) => { }) }) }) + + describe('when the contract is frozen', () => { + beforeEach(async () => { + await epochRewards.freeze() + }) + + it('should make calculateTargetEpochPaymentAndRewards return zeroes', async () => { + const [ + validatorPayment, + voterRewards, + ] = await epochRewards.calculateTargetEpochPaymentAndRewards() + assertEqualBN(validatorPayment, 0) + assertEqualBN(voterRewards, 0) + }) + }) }) From a90e69292d0bdabb5869680a0bf6a00fd34ece0a Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Sun, 24 Nov 2019 17:09:29 -0800 Subject: [PATCH 06/11] Fix elect validators migration, deploy integration (#1847) --- .env | 6 +- .env.integration | 23 +++--- .../src/e2e-tests/governance_tests.ts | 15 +--- packages/celotool/src/e2e-tests/utils.ts | 3 - packages/celotool/src/lib/ethstats.ts | 4 +- packages/celotool/src/lib/generate_utils.ts | 8 +- .../contracts/governance/Election.sol | 18 +---- packages/protocol/migrations/19_governance.ts | 2 + .../migrations/20_elect_validators.ts | 73 +++++++++++-------- packages/protocol/migrationsConfig.js | 4 +- packages/walletkit/package.json | 2 +- 11 files changed, 74 insertions(+), 84 deletions(-) diff --git a/.env b/.env index 97d3ecca66a..24243c5006f 100644 --- a/.env +++ b/.env @@ -12,14 +12,14 @@ CLUSTER_DOMAIN_NAME="celo-networks-dev" TESTNET_PROJECT_NAME="celo-testnet" BLOCKSCOUT_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/blockscout" -BLOCKSCOUT_DOCKER_IMAGE_TAG="8be64adeccbb8f67dea3b6653600021577e544c5" +BLOCKSCOUT_DOCKER_IMAGE_TAG="909682b7435fc3e05849211d96fb1dfbf76306f2" BLOCKSCOUT_WEB_REPLICAS=3 BLOCKSCOUT_DB_SUFFIX= ETHSTATS_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/ethstats" ETHSTATS_DOCKER_IMAGE_TAG="cd037ea1e18848466452ba9890c1f1bcd3f61009" -ETHSTATS_TRUSTED_ADDRESSES="0x480b0e0A641AE45521377d4984d085a003934561,0xF523976B9FB2160e9a43D8Aee016b98ea8f57837" -ETHSTATS_BANNED_ADDRESSES="0xFd24A0699288E141A855bC8c0dd0400C5E89D5e5" +ETHSTATS_TRUSTED_ADDRESSES="" +ETHSTATS_BANNED_ADDRESSES="" FAUCET_GENESIS_ACCOUNTS=2 diff --git a/.env.integration b/.env.integration index 31f7b4abca4..86243ff42ca 100644 --- a/.env.integration +++ b/.env.integration @@ -11,31 +11,36 @@ CLUSTER_DOMAIN_NAME="celo-testnet" TESTNET_PROJECT_NAME="celo-testnet" BLOCKSCOUT_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/blockscout" -BLOCKSCOUT_DOCKER_IMAGE_TAG="ad86714d629c01272e0651dec1fb6a968c3cec71" +BLOCKSCOUT_DOCKER_IMAGE_TAG="909682b7435fc3e05849211d96fb1dfbf76306f2" BLOCKSCOUT_WEB_REPLICAS=3 -BLOCKSCOUT_DB_SUFFIX="25" +BLOCKSCOUT_DB_SUFFIX="29" BLOCKSCOUT_SUBNETWORK_NAME="Integration" +ETHSTATS_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/ethstats" +ETHSTATS_DOCKER_IMAGE_TAG="cd037ea1e18848466452ba9890c1f1bcd3f61009" +ETHSTATS_TRUSTED_ADDRESSES="" +ETHSTATS_BANNED_ADDRESSES="" + FAUCET_GENESIS_ACCOUNTS=2 GETH_NODE_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-testnet/geth" # When upgrading change this to latest commit hash from the master of the geth repo # `geth $ git show | head -n 1` -GETH_NODE_DOCKER_IMAGE_TAG="c1ae452c707f8bee91a9a0bf49193e78e9c8512e" +GETH_NODE_DOCKER_IMAGE_TAG="09a217ff58a95214cbc5189c933359707f4fdaf2" GETH_BOOTNODE_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/geth-all" # When upgrading change this to latest commit hash from the master of the geth repo # `geth $ git show | head -n 1` -GETH_BOOTNODE_DOCKER_IMAGE_TAG="c1ae452c707f8bee91a9a0bf49193e78e9c8512e" +GETH_BOOTNODE_DOCKER_IMAGE_TAG="09a217ff58a95214cbc5189c933359707f4fdaf2" CELOTOOL_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" -CELOTOOL_DOCKER_IMAGE_TAG="celotool-2cb725c36b69e7ae608875610af080f4f3fa79bd" +CELOTOOL_DOCKER_IMAGE_TAG="celotool-5bea6d30cbe6aa4272b32a4d2cfed5567f422ea9" TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" -TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_TAG="transaction-metrics-exporter-2cb725c36b69e7ae608875610af080f4f3fa79bd" +TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_TAG="transaction-metrics-exporter-5bea6d30cbe6aa4272b32a4d2cfed5567f422ea9" -ATTESTATION_SERVICE_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" -ATTESTATION_SERVICE_DOCKER_IMAGE_TAG="attestation-service-2cb725c36b69e7ae608875610af080f4f3fa79bd" +ATTESTATION_SERVICE_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-testnet/celo-monorepo" +ATTESTATION_SERVICE_DOCKER_IMAGE_TAG="attestation-service-c8e3392aa2ca44ff83b4035700ece5fd12ed2b84" GETH_EXPORTER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet-production/geth-exporter" GETH_EXPORTER_DOCKER_IMAGE_TAG="ed7d21bd50592709173368cd697ef73c1774a261" @@ -47,7 +52,6 @@ BLOCK_TIME=3 EPOCH=1000 ISTANBUL_REQUEST_TIMEOUT_MS=3000 -# "og" -> our original 4 tx nodes, "${n}" -> for deriving n tx nodes from the MNEMONIC # NOTE: we only create static IPs when TX_NODES is set to "og" VALIDATORS=20 TX_NODES=2 @@ -61,7 +65,6 @@ GETH_NODES_BACKUP_CRONJOB_ENABLED=true CONTRACT_CRONJOBS_ENABLED=true CLUSTER_CREATION_FLAGS="--enable-autoscaling --min-nodes 3 --max-nodes 8 --machine-type=n1-standard-4" - GETH_NODE_CPU_REQUEST=400m GETH_NODE_MEMORY_REQUEST=2.5G diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts index e19a2ce41a4..7820c6a463b 100644 --- a/packages/celotool/src/e2e-tests/governance_tests.ts +++ b/packages/celotool/src/e2e-tests/governance_tests.ts @@ -196,16 +196,6 @@ describe('governance tests', () => { return decryptedKeystore.privateKey } - const activate = async (account: string, txOptions: any = {}) => { - const [group] = await validators.methods.getRegisteredValidatorGroups().call() - const tx = election.methods.activate(group) - let gas = txOptions.gas - if (!gas) { - gas = (await tx.estimateGas({ ...txOptions })) * 2 - } - return tx.send({ from: account, ...txOptions, gas }) - } - const isLastBlockOfEpoch = (blockNumber: number, epochSize: number) => { return blockNumber % epochSize === 0 } @@ -355,12 +345,13 @@ describe('governance tests', () => { // Wait for an extra epoch transition to ensure everyone is connected to one another. await waitForEpochTransition(epoch) - await activate(validatorAccounts[0]) - // Prepare for member swapping. const groupWeb3 = new Web3('ws://localhost:8555') await waitToFinishSyncing(groupWeb3) const groupKit = newKitFromWeb3(groupWeb3) + const group: string = (await groupWeb3.eth.getAccounts())[0] + await (await groupKit.contracts.getElection()).activate(group) + validators = await groupKit._web3Contracts.getValidators() const membersToSwap = [validatorAccounts[0], validatorAccounts[1]] const memberSwapper = await newMemberSwapper(groupKit, membersToSwap) diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts index c1a9421b0d7..05efcd8e954 100644 --- a/packages/celotool/src/e2e-tests/utils.ts +++ b/packages/celotool/src/e2e-tests/utils.ts @@ -362,9 +362,6 @@ export async function migrateContracts( election: { minElectableValidators: '1', }, - reserve: { - goldBalance: 100000, - }, stableToken: { initialBalances: { addresses: validators.map(ensure0x), diff --git a/packages/celotool/src/lib/ethstats.ts b/packages/celotool/src/lib/ethstats.ts index c512501cf84..21fd416b298 100644 --- a/packages/celotool/src/lib/ethstats.ts +++ b/packages/celotool/src/lib/ethstats.ts @@ -1,6 +1,6 @@ import { installGenericHelmChart, removeGenericHelmChart } from 'src/lib/helm_deploy' import { execCmdWithExitOnFailure } from 'src/lib/utils' -import { envVar, fetchEnv } from './env-utils' +import { envVar, fetchEnv, fetchEnvOrFallback } from './env-utils' import { AccountType, getAddressesFor } from './generate_utils' const helmChartPath = '../helm-charts/ethstats' @@ -50,6 +50,6 @@ function generateAuthorizedAddresses() { publicKeys.push(getAddressesFor(AccountType.TX_NODE, mnemonic, txNodes)) publicKeys.push(getAddressesFor(AccountType.VALIDATOR, mnemonic, validatorNodes)) - publicKeys.push(fetchEnv(envVar.ETHSTATS_TRUSTED_ADDRESSES).split(',')) + publicKeys.push(fetchEnvOrFallback(envVar.ETHSTATS_TRUSTED_ADDRESSES, '').split(',')) return publicKeys.reduce((accumulator, value) => accumulator.concat(value), []) } diff --git a/packages/celotool/src/lib/generate_utils.ts b/packages/celotool/src/lib/generate_utils.ts index dbd7f1f93d4..2633757b842 100644 --- a/packages/celotool/src/lib/generate_utils.ts +++ b/packages/celotool/src/lib/generate_utils.ts @@ -91,8 +91,8 @@ export const privateKeyToStrippedAddress = (privateKey: string) => const validatorZeroBalance = fetchEnvOrFallback( envVar.VALIDATOR_ZERO_GENESIS_BALANCE, - '100010011000000000000000000' -) // 100,010,011 CG + '103010030000000000000000000' +) // 103,010,030 CG const validatorBalance = fetchEnvOrFallback( envVar.VALIDATOR_GENESIS_BALANCE, '10011000000000000000000' @@ -109,12 +109,12 @@ export const getStrippedAddressesFor = (accountType: AccountType, mnemonic: stri getAddressesFor(accountType, mnemonic, n).map(strip0x) export const getValidators = (mnemonic: string, n: number) => { - return getPrivateKeysFor(AccountType.VALIDATOR, mnemonic, n).map((key) => { + return getPrivateKeysFor(AccountType.VALIDATOR, mnemonic, n).map((key, i) => { const blsKeyBytes = blsPrivateKeyToProcessedPrivateKey(key) return { address: strip0x(privateKeyToAddress(key)), blsPublicKey: bls12377js.BLS.privateToPublicBytes(blsKeyBytes).toString('hex'), - balance: n === 0 ? validatorZeroBalance : validatorBalance, + balance: i === 0 ? validatorZeroBalance : validatorBalance, } }) } diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index c3d90b1fa8b..72403b0206e 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -752,8 +752,7 @@ contract Election is totalNumMembersElected = totalNumMembersElected.add(1); } } - // Shuffle the validator set using validator-supplied entropy - return shuffleArray(electedValidators); + return electedValidators; } /** @@ -789,21 +788,6 @@ contract Election is return (groupIndex, memberElected); } - /** - * @notice Randomly permutes an array of addresses. - * @param array The array to permute. - * @return The permuted array. - */ - function shuffleArray(address[] memory array) private view returns (address[] memory) { - bytes32 r = getRandom().getBlockRandomness(block.number); - for (uint256 i = array.length - 1; i > 0; i = i.sub(1)) { - uint256 j = uint256(r) % (i + 1); - (array[i], array[j]) = (array[j], array[i]); - r = keccak256(abi.encodePacked(r)); - } - return array; - } - /** * @notice Returns get current validator signers using the precompiles. * @return List of current validator signers. diff --git a/packages/protocol/migrations/19_governance.ts b/packages/protocol/migrations/19_governance.ts index 187a99e676c..d38fd786329 100644 --- a/packages/protocol/migrations/19_governance.ts +++ b/packages/protocol/migrations/19_governance.ts @@ -52,9 +52,11 @@ module.exports = deploymentForCoreContract( ) const proxyAndImplementationOwnedByGovernance = [ + 'Accounts', 'Attestations', 'BlockchainParameters', 'Election', + 'EpochRewards', 'Escrow', 'Exchange', 'FeeCurrencyWhitelist', diff --git a/packages/protocol/migrations/20_elect_validators.ts b/packages/protocol/migrations/20_elect_validators.ts index c4db15d6a0a..c1e72566fa0 100644 --- a/packages/protocol/migrations/20_elect_validators.ts +++ b/packages/protocol/migrations/20_elect_validators.ts @@ -64,19 +64,26 @@ async function registerValidatorGroup( numMembers ) + // Add a premium to cover tx fees + const v = lockedGoldValue.times(1.01).toFixed() + + console.info(` - send funds ${v} to group address ${account.address}`) await sendTransactionWithPrivateKey(web3, null, privateKey, { to: account.address, - value: lockedGoldValue.times(1.01).toFixed(), // Add a premium to cover tx fees + value: v, }) + console.info(` - lock gold`) await lockGold(accounts, lockedGold, lockedGoldValue, account.privateKey) + console.info(` - setName`) // @ts-ignore const setNameTx = accounts.contract.methods.setName(`${name} ${encodedKey}`) await sendTransactionWithPrivateKey(web3, setNameTx, account.privateKey, { to: accounts.address, }) + console.info(` - registerValidatorGroup`) // @ts-ignore const tx = validators.contract.methods.registerValidatorGroup( toFixed(config.validators.commission).toString() @@ -86,7 +93,7 @@ async function registerValidatorGroup( to: validators.address, }) - return account + return [account, lockedGoldValue] } async function registerValidator( @@ -99,6 +106,9 @@ async function registerValidator( index: number, networkName: string ) { + const valName = `CLabs Validator #${index} on ${networkName}` + + console.info(` - lockGold ${valName}`) await lockGold( accounts, lockedGold, @@ -106,12 +116,15 @@ async function registerValidator( validatorPrivateKey ) + console.info(` - setName ${valName}`) + // @ts-ignore - const setNameTx = accounts.contract.methods.setName(`CLabs Validator #${index} on ${networkName}`) + const setNameTx = accounts.contract.methods.setName(valName) await sendTransactionWithPrivateKey(web3, setNameTx, validatorPrivateKey, { to: accounts.address, }) + console.info(` - registerValidator ${valName}`) const publicKey = privateKeyToPublicKey(validatorPrivateKey) const blsPublicKey = getBlsPublicKey(validatorPrivateKey) const blsPoP = getBlsPoP(privateKeyToAddress(validatorPrivateKey), validatorPrivateKey) @@ -123,6 +136,8 @@ async function registerValidator( to: validators.address, }) + console.info(` - affiliate ${valName}`) + // @ts-ignore const affiliateTx = validators.contract.methods.affiliate(groupAddress) @@ -130,6 +145,8 @@ async function registerValidator( to: validators.address, }) + console.info(` - setAccountDataEncryptionKey ${valName}`) + // @ts-ignore const registerDataEncryptionKeyTx = accounts.contract.methods.setAccountDataEncryptionKey( privateKeyToPublicKey(validatorPrivateKey) @@ -141,6 +158,7 @@ async function registerValidator( // Authorize the attestation signer const attestationKeyAddress = privateKeyToAddress(attestationKey) + console.info(` - authorizeAttestationSigner ${valName}->${attestationKeyAddress}`) const message = web3.utils.soliditySha3({ type: 'address', value: privateKeyToAddress(validatorPrivateKey), @@ -159,6 +177,7 @@ async function registerValidator( to: accounts.address, }) + console.info(` - done ${valName}`) return } @@ -195,6 +214,11 @@ module.exports = async (_deployer: any, networkName: string) => { return } + // Assumptions about where funds are located: + // * Validator 0 holds funds for all groups' stakes + // * Validator 1-n holds funds needed for their own stake + const validator0Key = valKeys[0] + if (valKeys.length < config.validators.minElectableValidators) { console.warn( ` Warning: Have ${valKeys.length} Validator keys but require a minimum of ${ @@ -219,32 +243,32 @@ module.exports = async (_deployer: any, networkName: string) => { } console.info(` Registering validator group: ${groupName} ...`) - const account = await registerValidatorGroup( + const [groupAccount, groupLockedGoldValue] = await registerValidatorGroup( groupName, accounts, lockedGold, validators, - groupKeys[0], + validator0Key, groupKeys.length ) - console.info(` * Registering ${groupKeys.length} validators ...`) await Promise.all( - groupKeys.map((key, index) => - registerValidator( + groupKeys.map((key, i) => { + const index = idx * config.validators.maxGroupSize + i + return registerValidator( accounts, lockedGold, validators, key, attestationKeys[index], - account.address, + groupAccount.address, index, networkName ) - ) + }) ) - console.info(' * Adding Validators to Validator Group ...') + console.info(` * Adding Validators to ${groupName} ...`) for (const [i, key] of groupKeys.entries()) { const address = privateKeyToAddress(key) console.info(` - Adding ${address} ...`) @@ -255,41 +279,30 @@ module.exports = async (_deployer: any, networkName: string) => { NULL_ADDRESS, prevGroupAddress ) - await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, { + await sendTransactionWithPrivateKey(web3, addTx, groupAccount.privateKey, { to: validators.address, }) } else { // @ts-ignore const addTx = validators.contract.methods.addMember(address) - await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, { + await sendTransactionWithPrivateKey(web3, addTx, groupAccount.privateKey, { to: validators.address, }) } } - console.info(' * Voting for the group ...') - // Make another deposit so our vote has more weight. - const minLockedGoldVotePerValidator = 10000 - const value = new BigNumber(groupKeys.length) - .times(minLockedGoldVotePerValidator) - .times(web3.utils.toWei('1')) - // @ts-ignore - const bondTx = lockedGold.contract.methods.lock() - await sendTransactionWithPrivateKey(web3, bondTx, groupKeys[0], { - to: lockedGold.address, - value, - }) - + console.info(' * Group voting for itself ...') // @ts-ignore const voteTx = election.contract.methods.vote( - account.address, - '0x' + value.toString(16), + groupAccount.address, + '0x' + groupLockedGoldValue.toString(16), NULL_ADDRESS, prevGroupAddress ) - await sendTransactionWithPrivateKey(web3, voteTx, groupKeys[0], { + await sendTransactionWithPrivateKey(web3, voteTx, groupAccount.privateKey, { to: election.address, }) - prevGroupAddress = account.address + + prevGroupAddress = groupAccount.address } } diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js index 7318345d041..0bf5c32e95a 100644 --- a/packages/protocol/migrationsConfig.js +++ b/packages/protocol/migrationsConfig.js @@ -98,11 +98,11 @@ const DefaultConfig = { }, validators: { groupLockedGoldRequirements: { - value: '10000000000000000000000', // 10k gold + value: '10000000000000000000000', // 10k gold per validator duration: 60 * 24 * 60 * 60, // 60 days }, validatorLockedGoldRequirements: { - value: '10000000000000000000000', // 1 gold + value: '10000000000000000000000', // 10k gold duration: 60 * 24 * 60 * 60, // 60 days }, validatorScoreParameters: { diff --git a/packages/walletkit/package.json b/packages/walletkit/package.json index 88bc280334c..4c5deaba9de 100644 --- a/packages/walletkit/package.json +++ b/packages/walletkit/package.json @@ -12,7 +12,7 @@ "clean": "rm -rf lib .artifacts contracts types lib", "lint": "tslint -c tslint.json --project tsconfig.json", "prebuild": "yarn clean", - "build": "yarn run build:for-env integration", + "build": "yarn run build:for-env pilotstaging", "prepublishOnly": "yarn run build", "test": "export TZ=UTC && jest --ci --silent --coverage --runInBand test/attestations.test.ts test/erc20-utils.test.ts test/google-storage-utils.test.ts test/start_geth.sh test/transaction-utils.test.ts", "test:verbose": "export TZ=UTC && jest --ci --verbose --runInBand test/attestations.test.ts test/erc20-utils.test.ts test/google-storage-utils.test.ts test/start_geth.sh test/transaction-utils.test.ts" From 8ba374a9deb293f608e0b3109487c18be0775595 Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Sun, 24 Nov 2019 17:33:20 -0800 Subject: [PATCH 07/11] Update faucet to pull from the reserve when possible (#1844) --- packages/celotool/src/cmds/account/faucet.ts | 63 +++++++++++++------- packages/contractkit/src/wrappers/Reserve.ts | 5 +- packages/protocol/migrations/07_reserve.ts | 2 + 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/packages/celotool/src/cmds/account/faucet.ts b/packages/celotool/src/cmds/account/faucet.ts index 8827a2e2f51..bd0b54639a3 100644 --- a/packages/celotool/src/cmds/account/faucet.ts +++ b/packages/celotool/src/cmds/account/faucet.ts @@ -9,24 +9,37 @@ import { AccountArgv } from '../account' export const command = 'faucet' -export const describe = 'command for fauceting an address with gold and dollars' +export const describe = 'command for fauceting an address with gold and/or dollars' interface FaucetArgv extends AccountArgv { account: string + gold: number + dollars: number } export const builder = (argv: yargs.Argv) => { - return argv.option('account', { - type: 'string', - description: 'Account to faucet', - demand: 'Please specify account to faucet', - coerce: (address) => { - if (!validateAccountAddress(address)) { - throw Error(`Receiver Address is invalid: "${address}"`) - } - return address - }, - }) + return argv + .option('account', { + type: 'string', + description: 'Account to faucet', + demand: 'Please specify account to faucet', + coerce: (address) => { + if (!validateAccountAddress(address)) { + throw Error(`Receiver Address is invalid: "${address}"`) + } + return address + }, + }) + .option('dollars', { + type: 'number', + description: 'Number of dollars to faucet', + default: 0, + }) + .option('gold', { + type: 'number', + description: 'Amount of gold to faucet', + default: 0, + }) } export const handler = async (argv: FaucetArgv) => { @@ -40,18 +53,26 @@ export const handler = async (argv: FaucetArgv) => { console.log(`Using account: ${account}`) kit.defaultAccount = account - const [goldToken, stableToken] = await Promise.all([ + const [goldToken, stableToken, reserve] = await Promise.all([ kit.contracts.getGoldToken(), kit.contracts.getStableToken(), + kit.contracts.getReserve(), ]) - const goldAmount = (await convertToContractDecimals(1, goldToken)).toString() - const stableTokenAmount = (await convertToContractDecimals(10, stableToken)).toString() - - console.log(`Fauceting ${goldAmount} Gold and ${stableTokenAmount} StableToken to ${address}`) - await Promise.all([ - goldToken.transfer(address, goldAmount).sendAndWaitForReceipt(), - stableToken.transfer(address, stableTokenAmount).sendAndWaitForReceipt(), - ]) + const goldAmount = await convertToContractDecimals(argv.gold, goldToken) + const stableTokenAmount = await convertToContractDecimals(argv.dollars, stableToken) + console.log( + `Fauceting ${goldAmount.toFixed()} Gold and ${stableTokenAmount.toFixed()} StableToken to ${address}` + ) + if (!goldAmount.isZero()) { + if (await reserve.isSpender(account)) { + await reserve.transferGold(address, goldAmount.toFixed()).sendAndWaitForReceipt() + } else { + await goldToken.transfer(address, goldAmount.toFixed()).sendAndWaitForReceipt() + } + } + if (stableTokenAmount.isZero()) { + await stableToken.transfer(address, stableTokenAmount.toFixed()).sendAndWaitForReceipt() + } } try { diff --git a/packages/contractkit/src/wrappers/Reserve.ts b/packages/contractkit/src/wrappers/Reserve.ts index d0513fd570e..dfb6d39bb66 100644 --- a/packages/contractkit/src/wrappers/Reserve.ts +++ b/packages/contractkit/src/wrappers/Reserve.ts @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js' import { Reserve } from '../generated/types/Reserve' -import { BaseWrapper, proxyCall, toBigNumber } from './BaseWrapper' +import { BaseWrapper, proxyCall, proxySend, toBigNumber } from './BaseWrapper' export interface ReserveConfig { tobinTaxStalenessThreshold: BigNumber @@ -19,6 +19,9 @@ export class ReserveWrapper extends BaseWrapper { undefined, toBigNumber ) + isSpender: (account: string) => Promise = proxyCall(this.contract.methods.isSpender) + transferGold = proxySend(this.kit, this.contract.methods.transferGold) + /** * Returns current configuration parameters. */ diff --git a/packages/protocol/migrations/07_reserve.ts b/packages/protocol/migrations/07_reserve.ts index b13ebb75c66..47a423cbb5c 100644 --- a/packages/protocol/migrations/07_reserve.ts +++ b/packages/protocol/migrations/07_reserve.ts @@ -31,5 +31,7 @@ module.exports = deploymentForCoreContract( to: reserve.address, value: web3.utils.toWei(config.reserve.goldBalance.toString(), 'ether') as string, }) + console.info(`Marking ${network.from} as a reserve spender`) + await reserve.addSpender(network.from) } ) From 51f61b60ee94e5741a5d6967f77ed366c70ae3a2 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Sun, 24 Nov 2019 20:43:21 -0800 Subject: [PATCH 08/11] Serialize to hex string (#1848) --- .../contractkit/src/providers/celo-private-keys-subprovider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contractkit/src/providers/celo-private-keys-subprovider.ts b/packages/contractkit/src/providers/celo-private-keys-subprovider.ts index 4c718c65fd8..6f42c9c1518 100644 --- a/packages/contractkit/src/providers/celo-private-keys-subprovider.ts +++ b/packages/contractkit/src/providers/celo-private-keys-subprovider.ts @@ -124,7 +124,7 @@ export class CeloPrivateKeysWalletProvider extends PrivateKeyWalletSubprovider { txParams.gatewayFeeRecipient = await this.getCoinbase() } if (!isEmpty(txParams.gatewayFeeRecipient) && isEmpty(txParams.gatewayFee)) { - txParams.gatewayFee = DefaultGatewayFee.toString() + txParams.gatewayFee = DefaultGatewayFee.toString(16) } debug( 'Gateway fee for the transaction is %s paid to %s', From a3fdf21ed1dc03f06de3e5d2f29955d59c185531 Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Sun, 24 Nov 2019 22:12:01 -0800 Subject: [PATCH 09/11] Various improvements to the CLI, allow voters to revote for a group (#1840) --- .circleci/config.yml | 4 +- packages/cli/package.json | 2 +- .../src/commands/account/transferdollar.ts | 34 +---- .../cli/src/commands/account/transfergold.ts | 26 +--- .../cli/src/commands/election/activate.ts | 51 +++++++ packages/cli/src/commands/election/list.ts | 4 +- packages/cli/src/commands/election/revoke.ts | 41 ++++++ packages/cli/src/commands/election/show.ts | 38 ++++-- packages/cli/src/commands/election/vote.ts | 8 +- packages/cli/src/commands/exchange/list.ts | 4 +- .../cli/src/commands/exchange/selldollar.ts | 4 +- .../cli/src/commands/exchange/sellgold.ts | 4 +- packages/cli/src/commands/lockedgold/lock.ts | 12 +- .../cli/src/commands/lockedgold/withdraw.ts | 6 +- packages/cli/src/commands/validator/list.ts | 2 +- .../cli/src/commands/validator/register.ts | 17 +-- .../cli/src/commands/validatorgroup/member.ts | 6 +- packages/cli/src/utils/command.ts | 10 +- packages/cli/src/utils/helpers.ts | 1 + packages/contractkit/package.json | 3 +- .../contractkit/src/wrappers/Accounts.test.ts | 10 +- packages/contractkit/src/wrappers/Accounts.ts | 9 ++ .../contractkit/src/wrappers/Attestations.ts | 2 +- .../contractkit/src/wrappers/BaseWrapper.ts | 2 +- packages/contractkit/src/wrappers/Election.ts | 125 ++++++++++++++++- .../src/wrappers/Validators.test.ts | 9 +- .../contractkit/src/wrappers/Validators.ts | 23 ++-- .../docs/command-line-interface/account.md | 10 +- .../docs/command-line-interface/election.md | 51 ++++++- .../docs/command-line-interface/lockedgold.md | 2 +- .../docs/command-line-interface/validator.md | 5 +- .../command-line-interface/validatorgroup.md | 6 +- .../contracts/governance/Election.sol | 23 +++- packages/protocol/test/governance/election.ts | 126 +++++++++++++----- 34 files changed, 494 insertions(+), 186 deletions(-) create mode 100644 packages/cli/src/commands/election/activate.ts create mode 100644 packages/cli/src/commands/election/revoke.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index d4406607d0f..2f5fe2d130e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -255,7 +255,7 @@ jobs: name: Run test itself command: | - cd ~/src/packages/mobile + cd ~/src/packages/mobile # detox sometimes without releasing the terminal and thus making the CI timout # 480s = 8 minutes timeout 480 yarn test:detox || echo "failed, try again" @@ -402,7 +402,7 @@ jobs: - run: name: Generate DevChain command: | - (cd packages/contractkit && yarn test:prepare) + (cd packages/contractkit && yarn test:reset) - run: name: Run Tests command: yarn --cwd=packages/contractkit test diff --git a/packages/cli/package.json b/packages/cli/package.json index b7a071af5cd..0dfd83dceea 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -26,7 +26,7 @@ "docs": "yarn oclif-dev readme --multi --dir=../docs/command-line-interface && yarn prettier ../docs/command-line-interface/*.md --write", "lint": "tslint -c tslint.json --project tsconfig.json", "prepack": "yarn run build && oclif-dev manifest && oclif-dev readme", - "test:reset": "yarn --cwd ../protocol devchain generate .devchain", + "test:reset": "yarn --cwd ../protocol devchain generate .devchain --migration_override ../dev-utils/src/migration-override.json", "test:livechain": "yarn --cwd ../protocol devchain run .devchain", "test": "TZ=UTC jest --runInBand" }, diff --git a/packages/cli/src/commands/account/transferdollar.ts b/packages/cli/src/commands/account/transferdollar.ts index 164e8d9f392..75fcd38dd6a 100644 --- a/packages/cli/src/commands/account/transferdollar.ts +++ b/packages/cli/src/commands/account/transferdollar.ts @@ -11,11 +11,11 @@ export default class DollarTransfer extends BaseCommand { ...BaseCommand.flags, from: Flags.address({ required: true, description: 'Address of the sender' }), to: Flags.address({ required: true, description: 'Address of the receiver' }), - amountInWei: flags.string({ required: true, description: 'Amount to transfer (in wei)' }), + value: flags.string({ required: true, description: 'Amount to transfer (in wei)' }), } static examples = [ - 'transferdollar --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --amountInWei 1', + 'transferdollar --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 1', ] async run() { @@ -23,38 +23,12 @@ export default class DollarTransfer extends BaseCommand { const from: string = res.flags.from const to: string = res.flags.to - const amountInWei = new BigNumber(res.flags.amountInWei) + const value = new BigNumber(res.flags.value) this.kit.defaultAccount = from - const goldToken = await this.kit.contracts.getGoldToken() const stableToken = await this.kit.contracts.getStableToken() - // Units of all balances are in wei, unless specified. - // Check the balance before - const goldBalanceFromBefore = await goldToken.balanceOf(from) - const dollarBalanceFromBefore = await stableToken.balanceOf(from) - // Perform the transfer - await displaySendTx('dollar.Transfer', stableToken.transfer(to, amountInWei.toString())) - - // Check the balance after - const goldBalanceFromAfter = await goldToken.balanceOf(from) - const dollarBalanceFromAfter = await stableToken.balanceOf(from) - - // Get gas cost - const goldDifference = goldBalanceFromBefore.minus(goldBalanceFromAfter) - const dollarDifference = dollarBalanceFromBefore.minus(dollarBalanceFromAfter) - const gasCostInWei = goldDifference - this.log( - `Transferred ${amountInWei} from ${from} to ${to}, gas cost: ${gasCostInWei.toString()}` - ) - this.log( - `Dollar Balance of sender ${from} went down by ${dollarDifference.toString()} wei,` + - `final balance: ${dollarBalanceFromAfter.toString()} Celo Dollars wei` - ) - this.log( - `Gold Balance of sender ${from} went down by ${goldDifference.toString()} wei, ` + - `final balance: ${goldBalanceFromAfter.toString()} Celo Gold wei` - ) + await displaySendTx('dollar.Transfer', stableToken.transfer(to, value.toFixed())) } } diff --git a/packages/cli/src/commands/account/transfergold.ts b/packages/cli/src/commands/account/transfergold.ts index 768fab2c83c..96e04a39f8d 100644 --- a/packages/cli/src/commands/account/transfergold.ts +++ b/packages/cli/src/commands/account/transfergold.ts @@ -11,11 +11,11 @@ export default class GoldTransfer extends BaseCommand { ...BaseCommand.flags, from: Flags.address({ required: true, description: 'Address of the sender' }), to: Flags.address({ required: true, description: 'Address of the receiver' }), - amountInWei: flags.string({ required: true, description: 'Amount to transfer (in wei)' }), + value: flags.string({ required: true, description: 'Amount to transfer (in wei)' }), } static examples = [ - 'transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --amountInWei 1', + 'transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 1', ] async run() { @@ -23,30 +23,12 @@ export default class GoldTransfer extends BaseCommand { const from: string = res.flags.from const to: string = res.flags.to - const amountInWei = new BigNumber(res.flags.amountInWei) + const value = new BigNumber(res.flags.value) this.kit.defaultAccount = from - // Units of all balances are in wei, unless specified. - // Check the balance before const goldToken = await this.kit.contracts.getGoldToken() - // Check the balance before - const balanceFromBeforeInWei = await goldToken.balanceOf(from) - // Perform the transfer - await displaySendTx('gold.Transfer', goldToken.transfer(to, amountInWei.toString())) - - // Check the balance after - const balanceFromAfterInWei = await goldToken.balanceOf(from) - - // Get gas cost - const differenceInWei = balanceFromBeforeInWei.minus(balanceFromAfterInWei) - const gasCostInWei = differenceInWei.minus(amountInWei) - this.log( - `Transferred ${amountInWei} from ${from} to ${to}, gas cost: ${gasCostInWei.toString()} wei` - ) - this.log( - `Balance of sender ${from} went down by ${differenceInWei.toString()} wei, final balance: ${balanceFromAfterInWei} Celo Gold wei` - ) + await displaySendTx('gold.Transfer', goldToken.transfer(to, value.toFixed())) } } diff --git a/packages/cli/src/commands/election/activate.ts b/packages/cli/src/commands/election/activate.ts new file mode 100644 index 00000000000..26b8d51ea56 --- /dev/null +++ b/packages/cli/src/commands/election/activate.ts @@ -0,0 +1,51 @@ +import { sleep } from '@celo/utils/lib/async' +import { flags } from '@oclif/command' +import { BaseCommand } from '../../base' +import { newCheckBuilder } from '../../utils/checks' +import { displaySendTx } from '../../utils/cli' +import { Flags } from '../../utils/command' + +export default class ElectionVote extends BaseCommand { + static description = 'Activate pending votes in validator elections to begin earning rewards' + + static flags = { + ...BaseCommand.flags, + from: Flags.address({ required: true, description: "Voter's address" }), + wait: flags.boolean({ description: 'Wait until all pending votes become activatable' }), + } + + static examples = [ + 'activate --from 0x4443d0349e8b3075cba511a0a87796597602a0f1', + 'activate --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --wait', + ] + async run() { + const res = this.parse(ElectionVote) + + this.kit.defaultAccount = res.flags.from + await newCheckBuilder(this, res.flags.from) + .isSignerOrAccount() + .runChecks() + + const election = await this.kit.contracts.getElection() + const accounts = await this.kit.contracts.getAccounts() + const account = await accounts.voteSignerToAccount(res.flags.from) + const hasPendingVotes = await election.hasPendingVotes(account) + if (hasPendingVotes) { + if (res.flags.wait) { + // Spin until pending votes become activatable. + while (!(await election.hasActivatablePendingVotes(account))) { + await sleep(1000) + } + } + const txos = await election.activate(account) + for (const txo of txos) { + await displaySendTx('activate', txo, { from: res.flags.from }) + } + if (txos.length === 0) { + this.log(`Pending votes not yet activatable. Consider using the --wait flag.`) + } + } else { + this.log(`No pending votes to activate`) + } + } +} diff --git a/packages/cli/src/commands/election/list.ts b/packages/cli/src/commands/election/list.ts index 80bc4acd2c6..1079dea038f 100644 --- a/packages/cli/src/commands/election/list.ts +++ b/packages/cli/src/commands/election/list.ts @@ -17,8 +17,8 @@ export default class List extends BaseCommand { cli.action.stop() cli.table(groupVotes, { address: {}, - votes: {}, - capacity: {}, + votes: { get: (g) => g.votes.toFixed() }, + capacity: { get: (g) => g.capacity.toFixed() }, eligible: {}, }) } diff --git a/packages/cli/src/commands/election/revoke.ts b/packages/cli/src/commands/election/revoke.ts new file mode 100644 index 00000000000..db009ea9b75 --- /dev/null +++ b/packages/cli/src/commands/election/revoke.ts @@ -0,0 +1,41 @@ +import { flags } from '@oclif/command' +import BigNumber from 'bignumber.js' +import { BaseCommand } from '../../base' +import { newCheckBuilder } from '../../utils/checks' +import { displaySendTx } from '../../utils/cli' +import { Flags } from '../../utils/command' + +export default class ElectionRevoke extends BaseCommand { + static description = 'Revoke votes for a Validator Group in validator elections.' + + static flags = { + ...BaseCommand.flags, + from: Flags.address({ required: true, description: "Voter's address" }), + for: Flags.address({ + description: "ValidatorGroup's address", + required: true, + }), + value: flags.string({ description: 'Value of votes to revoke', required: true }), + } + + static examples = [ + 'revoke --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --for 0x932fee04521f5fcb21949041bf161917da3f588b, --value 1000000', + ] + async run() { + const res = this.parse(ElectionRevoke) + + this.kit.defaultAccount = res.flags.from + await newCheckBuilder(this, res.flags.from) + .isSignerOrAccount() + .isValidatorGroup(res.flags.for) + .runChecks() + + const election = await this.kit.contracts.getElection() + const accounts = await this.kit.contracts.getAccounts() + const account = await accounts.voteSignerToAccount(res.flags.from) + const txos = await election.revoke(account, res.flags.for, new BigNumber(res.flags.value)) + for (const txo of txos) { + await displaySendTx('revoke', txo, { from: res.flags.from }) + } + } +} diff --git a/packages/cli/src/commands/election/show.ts b/packages/cli/src/commands/election/show.ts index 0d60baecf87..c73247810d2 100644 --- a/packages/cli/src/commands/election/show.ts +++ b/packages/cli/src/commands/election/show.ts @@ -1,32 +1,48 @@ +import { flags } from '@oclif/command' import { IArg } from '@oclif/parser/lib/args' import { BaseCommand } from '../../base' import { newCheckBuilder } from '../../utils/checks' -import { printValueMap } from '../../utils/cli' +import { printValueMapRecursive } from '../../utils/cli' import { Args } from '../../utils/command' export default class ElectionShow extends BaseCommand { - static description = 'Show election information about an existing Validator Group' + static description = 'Show election information about a voter or Validator Group' static flags = { ...BaseCommand.flags, + voter: flags.boolean({ + exclusive: ['group'], + description: 'Show information about an account voting in Validator elections', + }), + group: flags.boolean({ + exclusive: ['voter'], + description: 'Show information about a group running in Validator elections', + }), } static args: IArg[] = [ - Args.address('groupAddress', { description: "Validator Groups's address" }), + Args.address('address', { description: "Voter or Validator Groups's address" }), ] static examples = ['show 0x97f7333c51897469E8D98E7af8653aAb468050a3'] async run() { - const { args } = this.parse(ElectionShow) - const address = args.groupAddress + const res = this.parse(ElectionShow) + const address = res.args.address const election = await this.kit.contracts.getElection() - await newCheckBuilder(this) - .isValidatorGroup(address) - .runChecks() - - const groupVotes = await election.getValidatorGroupVotes(address) - printValueMap(groupVotes) + if (res.flags.group) { + await newCheckBuilder(this) + .isValidatorGroup(address) + .runChecks() + const groupVotes = await election.getValidatorGroupVotes(address) + printValueMapRecursive(groupVotes) + } else if (res.flags.voter) { + await newCheckBuilder(this) + .isAccount(address) + .runChecks() + const voter = await election.getVoter(address) + printValueMapRecursive(voter) + } } } diff --git a/packages/cli/src/commands/election/vote.ts b/packages/cli/src/commands/election/vote.ts index cf957d74755..0132f5a93c4 100644 --- a/packages/cli/src/commands/election/vote.ts +++ b/packages/cli/src/commands/election/vote.ts @@ -1,6 +1,7 @@ import { flags } from '@oclif/command' import BigNumber from 'bignumber.js' import { BaseCommand } from '../../base' +import { newCheckBuilder } from '../../utils/checks' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' @@ -11,7 +12,7 @@ export default class ElectionVote extends BaseCommand { ...BaseCommand.flags, from: Flags.address({ required: true, description: "Voter's address" }), for: Flags.address({ - description: "Set vote for ValidatorGroup's address", + description: "ValidatorGroup's address", required: true, }), value: flags.string({ description: 'Amount of Gold used to vote for group', required: true }), @@ -24,6 +25,11 @@ export default class ElectionVote extends BaseCommand { const res = this.parse(ElectionVote) this.kit.defaultAccount = res.flags.from + await newCheckBuilder(this, res.flags.from) + .isSignerOrAccount() + .isValidatorGroup(res.flags.for) + .runChecks() + const election = await this.kit.contracts.getElection() const tx = await election.vote(res.flags.for, new BigNumber(res.flags.value)) await displaySendTx('vote', tx) diff --git a/packages/cli/src/commands/exchange/list.ts b/packages/cli/src/commands/exchange/list.ts index 18e06e52b7e..1d38232cf06 100644 --- a/packages/cli/src/commands/exchange/list.ts +++ b/packages/cli/src/commands/exchange/list.ts @@ -26,7 +26,7 @@ export default class List extends BaseCommand { const goldForDollar = await exchange.getBuyTokenAmount(parsedFlags.amount as string, false) cli.action.stop() - this.log(`${parsedFlags.amount} cGLD => ${dollarForGold.toString()} cUSD`) - this.log(`${parsedFlags.amount} cUSD => ${goldForDollar.toString()} cGLD`) + this.log(`${parsedFlags.amount} cGLD => ${dollarForGold.toFixed()} cUSD`) + this.log(`${parsedFlags.amount} cUSD => ${goldForDollar.toFixed()} cGLD`) } } diff --git a/packages/cli/src/commands/exchange/selldollar.ts b/packages/cli/src/commands/exchange/selldollar.ts index b407bbe5b76..60ad39a9c39 100644 --- a/packages/cli/src/commands/exchange/selldollar.ts +++ b/packages/cli/src/commands/exchange/selldollar.ts @@ -19,9 +19,9 @@ export default class SellDollar extends BaseCommand { const stableToken = await this.kit.contracts.getStableToken() const exchange = await this.kit.contracts.getExchange() - await displaySendTx('approve', stableToken.approve(exchange.address, sellAmount.toString())) + await displaySendTx('approve', stableToken.approve(exchange.address, sellAmount.toFixed())) - const exchangeTx = exchange.exchange(sellAmount.toString(), minBuyAmount.toString(), false) + const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount.toFixed(), false) await displaySendTx('exchange', exchangeTx) } } diff --git a/packages/cli/src/commands/exchange/sellgold.ts b/packages/cli/src/commands/exchange/sellgold.ts index 3423eedca70..977db9839a5 100644 --- a/packages/cli/src/commands/exchange/sellgold.ts +++ b/packages/cli/src/commands/exchange/sellgold.ts @@ -19,9 +19,9 @@ export default class SellGold extends BaseCommand { const goldToken = await this.kit.contracts.getGoldToken() const exchange = await this.kit.contracts.getExchange() - await displaySendTx('approve', goldToken.approve(exchange.address, sellAmount.toString())) + await displaySendTx('approve', goldToken.approve(exchange.address, sellAmount.toFixed())) - const exchangeTx = exchange.exchange(sellAmount.toString(), minBuyAmount.toString(), true) + const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount.toFixed(), true) await displaySendTx('exchange', exchangeTx) } } diff --git a/packages/cli/src/commands/lockedgold/lock.ts b/packages/cli/src/commands/lockedgold/lock.ts index 6e64e3c0ec7..3cf093e41cd 100644 --- a/packages/cli/src/commands/lockedgold/lock.ts +++ b/packages/cli/src/commands/lockedgold/lock.ts @@ -19,7 +19,7 @@ export default class Lock extends BaseCommand { static args = [] static examples = [ - 'lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 1000000000000000000', + 'lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 10000000000000000000000', ] async run() { @@ -28,14 +28,18 @@ export default class Lock extends BaseCommand { this.kit.defaultAccount = address const value = new BigNumber(res.flags.value) + + await newCheckBuilder(this) + .addCheck(`Value [${value.toFixed()}] is not > 0`, () => value.gt(0)) + .isAccount(address) + .runChecks() + const lockedGold = await this.kit.contracts.getLockedGold() const pendingWithdrawalsValue = await lockedGold.getPendingWithdrawalsTotalValue(address) const relockValue = BigNumber.minimum(pendingWithdrawalsValue, value) const lockValue = value.minus(relockValue) await newCheckBuilder(this) - .addCheck(`Value [${value.toString()}] is >= 0`, () => value.gt(0)) - .isAccount(address) .hasEnoughGold(address, lockValue) .runChecks() @@ -44,6 +48,6 @@ export default class Lock extends BaseCommand { await displaySendTx('relock', txo, { from: address }) } const tx = lockedGold.lock() - await displaySendTx('lock', tx, { value: lockValue.toString() }) + await displaySendTx('lock', tx, { value: lockValue.toFixed() }) } } diff --git a/packages/cli/src/commands/lockedgold/withdraw.ts b/packages/cli/src/commands/lockedgold/withdraw.ts index 6ad636ce8cf..d3432e6516c 100644 --- a/packages/cli/src/commands/lockedgold/withdraw.ts +++ b/packages/cli/src/commands/lockedgold/withdraw.ts @@ -31,7 +31,7 @@ export default class Withdraw extends BaseCommand { const pendingWithdrawal = pendingWithdrawals[i] if (pendingWithdrawal.time.isLessThan(currentTime)) { console.log( - `Found available pending withdrawal of value ${pendingWithdrawal.value.toString()}, withdrawing` + `Found available pending withdrawal of value ${pendingWithdrawal.value.toFixed()}, withdrawing` ) await displaySendTx('withdraw', lockedgold.withdraw(i)) madeWithdrawal = true @@ -45,9 +45,9 @@ export default class Withdraw extends BaseCommand { const remainingPendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from) for (const pendingWithdrawal of remainingPendingWithdrawals) { console.log( - `Pending withdrawal of value ${pendingWithdrawal.value.toString()} available for withdrawal in ${pendingWithdrawal.time + `Pending withdrawal of value ${pendingWithdrawal.value.toFixed()} available for withdrawal in ${pendingWithdrawal.time .minus(currentTime) - .toString()} seconds.` + .toFixed()} seconds.` ) } } diff --git a/packages/cli/src/commands/validator/list.ts b/packages/cli/src/commands/validator/list.ts index f15d60d7799..c1faa4b5de1 100644 --- a/packages/cli/src/commands/validator/list.ts +++ b/packages/cli/src/commands/validator/list.ts @@ -22,7 +22,7 @@ export default class ValidatorList extends BaseCommand { address: {}, name: {}, affiliation: {}, - score: {}, + score: { get: (v) => v.score.toFixed() }, ecdsaPublicKey: {}, blsPublicKey: {}, }) diff --git a/packages/cli/src/commands/validator/register.ts b/packages/cli/src/commands/validator/register.ts index 8812e48901a..033778e308c 100644 --- a/packages/cli/src/commands/validator/register.ts +++ b/packages/cli/src/commands/validator/register.ts @@ -10,13 +10,12 @@ export default class ValidatorRegister extends BaseCommand { static flags = { ...BaseCommand.flags, from: Flags.address({ required: true, description: 'Address for the Validator' }), - ecdsaKey: Flags.ecdsaPublicKey({ required: true }), blsKey: Flags.blsPublicKey({ required: true }), blsPop: Flags.blsProofOfPossession({ required: true }), } static examples = [ - 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --ecdsaKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae1 --blsKey 0x4fa3f67fc913878b068d1fa1cdddc54913d3bf988dbe5a36a20fa888f20d4894c408a6773f3d7bde11154f2a3076b700d345a42fd25a0e5e83f4db5586ac7979ac2053cd95d8f2efd3e959571ceccaa743e02cf4be3f5d7aaddb0b06fc9aff00 --blsPop 0xcdb77255037eb68897cd487fdd85388cbda448f617f874449d4b11588b0b7ad8ddc20d9bb450b513bb35664ea3923900', + 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --blsKey 0x4fa3f67fc913878b068d1fa1cdddc54913d3bf988dbe5a36a20fa888f20d4894c408a6773f3d7bde11154f2a3076b700d345a42fd25a0e5e83f4db5586ac7979ac2053cd95d8f2efd3e959571ceccaa743e02cf4be3f5d7aaddb0b06fc9aff00 --blsPop 0xcdb77255037eb68897cd487fdd85388cbda448f617f874449d4b11588b0b7ad8ddc20d9bb450b513bb35664ea3923900', ] async run() { @@ -31,16 +30,14 @@ export default class ValidatorRegister extends BaseCommand { .canSignValidatorTxs() .signerMeetsValidatorBalanceRequirements() .runChecks() - - await displaySendTx( - 'registerValidator', - validators.registerValidator( - res.flags.ecdsaKey as any, - res.flags.blsKey as any, - res.flags.blsPop as any - ) + const tx = await validators.registerValidator( + res.flags.from, + res.flags.blsKey, + res.flags.blsPop ) + await displaySendTx('registerValidator', tx) + // register encryption key on accounts contract // TODO: Use a different key data encryption const pubKey = await addressToPublicKey(res.flags.from, this.web3.eth.sign) diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts index c4130c66dae..7be1af0d60b 100644 --- a/packages/cli/src/commands/validatorgroup/member.ts +++ b/packages/cli/src/commands/validatorgroup/member.ts @@ -28,9 +28,9 @@ export default class ValidatorGroupMembers extends BaseCommand { static args: IArg[] = [Args.address('validatorAddress', { description: "Validator's address" })] static examples = [ - 'member --accept 0x97f7333c51897469e8d98e7af8653aab468050a3 ', - 'member --remove 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95', - 'member --reorder 3 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95', + 'member --from 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 --accept 0x97f7333c51897469e8d98e7af8653aab468050a3', + 'member --from 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 --remove 0x97f7333c51897469e8d98e7af8653aab468050a3', + 'member --from 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 --reorder 3 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95', ] async run() { diff --git a/packages/cli/src/utils/command.ts b/packages/cli/src/utils/command.ts index 7268010b70c..36f48889364 100644 --- a/packages/cli/src/utils/command.ts +++ b/packages/cli/src/utils/command.ts @@ -8,16 +8,13 @@ import Web3 from 'web3' const parseBytes = (input: string, length: number, msg: string) => { // Check that the string starts with 0x and has byte length of `length`. - if (Web3.utils.isHex(input) && input.length === length && input.startsWith('0x')) { + if (Web3.utils.isHex(input) && input.length === length * 2 + 2 && input.startsWith('0x')) { return input } else { throw new CLIError(msg) } } -const parseEcdsaPublicKey: ParseFn = (input) => { - return parseBytes(input, 64, `${input} is not an ECDSA public key`) -} const parseBlsPublicKey: ParseFn = (input) => { return parseBytes(input, BLS_PUBLIC_KEY_SIZE, `${input} is not a BLS public key`) } @@ -65,11 +62,6 @@ export const Flags = { description: 'Account Address', helpValue: '0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d', }), - ecdsaPublicKey: flags.build({ - parse: parseEcdsaPublicKey, - description: 'ECDSA Public Key', - helpValue: '0x', - }), blsPublicKey: flags.build({ parse: parseBlsPublicKey, description: 'BLS Public Key', diff --git a/packages/cli/src/utils/helpers.ts b/packages/cli/src/utils/helpers.ts index 4d8c7c0f768..ae0400c9df3 100644 --- a/packages/cli/src/utils/helpers.ts +++ b/packages/cli/src/utils/helpers.ts @@ -3,6 +3,7 @@ import { Block } from 'web3/eth/types' import { failWith } from './cli' export async function nodeIsSynced(web3: Web3): Promise { + return true if (process.env.NO_SYNCCHECK) { return true } diff --git a/packages/contractkit/package.json b/packages/contractkit/package.json index 290e70aa4c7..2b9f0a8a09c 100644 --- a/packages/contractkit/package.json +++ b/packages/contractkit/package.json @@ -20,7 +20,8 @@ "clean:all": "yarn clean && rm -rf src/generated", "build:gen": "yarn --cwd ../protocol build", "prepublishOnly": "yarn build:gen && yarn build", - "test:prepare": "yarn --cwd ../protocol devchain generate .devchain --migration_override ../dev-utils/src/migration-override.json", + "test:reset": "yarn --cwd ../protocol devchain generate .devchain --migration_override ../dev-utils/src/migration-override.json", + "test:livechain": "yarn --cwd ../protocol devchain run .devchain", "test": "jest --runInBand", "lint": "tslint -c tslint.json --project ." }, diff --git a/packages/contractkit/src/wrappers/Accounts.test.ts b/packages/contractkit/src/wrappers/Accounts.test.ts index 193a5f8abe5..5063015339b 100644 --- a/packages/contractkit/src/wrappers/Accounts.test.ts +++ b/packages/contractkit/src/wrappers/Accounts.test.ts @@ -1,5 +1,5 @@ import { testWithGanache } from '@celo/dev-utils/lib/ganache-test' -import { addressToPublicKey, parseSignature } from '@celo/utils/lib/signatureUtils' +import { parseSignature } from '@celo/utils/lib/signatureUtils' import Web3 from 'web3' import { newKitFromWeb3 } from '../kit' import { AccountsWrapper } from './Accounts' @@ -47,12 +47,12 @@ testWithGanache('Accounts Wrapper', (web3) => { }) const setupValidator = async (validatorAccount: string) => { - const publicKey = await addressToPublicKey(validatorAccount, web3.eth.sign) await registerAccountWithLockedGold(validatorAccount) - await validators + await (await validators // @ts-ignore - .registerValidator(publicKey, blsPublicKey, blsPoP) - .sendAndWaitForReceipt({ from: validatorAccount }) + .registerValidator(validatorAccount, blsPublicKey, blsPoP)).sendAndWaitForReceipt({ + from: validatorAccount, + }) } test('SBAT authorize validator key when not a validator', async () => { diff --git a/packages/contractkit/src/wrappers/Accounts.ts b/packages/contractkit/src/wrappers/Accounts.ts index bb5bb9b3c86..7cb87638588 100644 --- a/packages/contractkit/src/wrappers/Accounts.ts +++ b/packages/contractkit/src/wrappers/Accounts.ts @@ -49,6 +49,15 @@ export class AccountsWrapper extends BaseWrapper { this.contract.methods.getValidatorSigner ) + /** + * Returns the account address given the signer for voting + * @param signer Address that is authorized to sign the tx as voter + * @return The Account address + */ + voteSignerToAccount: (signer: Address) => Promise
= proxyCall( + this.contract.methods.voteSignerToAccount + ) + /** * Returns the account address given the signer for validating * @param signer Address that is authorized to sign the tx as validator diff --git a/packages/contractkit/src/wrappers/Attestations.ts b/packages/contractkit/src/wrappers/Attestations.ts index 2e14944dd70..a78df0510bd 100644 --- a/packages/contractkit/src/wrappers/Attestations.ts +++ b/packages/contractkit/src/wrappers/Attestations.ts @@ -211,7 +211,7 @@ export class AttestationsWrapper extends BaseWrapper { async approveAttestationFee(attestationsRequested: number) { const tokenContract = await this.kit.contracts.getContract(CeloContract.StableToken) const fee = await this.attestationFeeRequired(attestationsRequested) - return tokenContract.approve(this.address, fee.toString()) + return tokenContract.approve(this.address, fee.toFixed()) } /** diff --git a/packages/contractkit/src/wrappers/BaseWrapper.ts b/packages/contractkit/src/wrappers/BaseWrapper.ts index e51fc651118..852171391d3 100644 --- a/packages/contractkit/src/wrappers/BaseWrapper.ts +++ b/packages/contractkit/src/wrappers/BaseWrapper.ts @@ -33,7 +33,7 @@ export function toNumber(input: string) { } export function parseNumber(input: NumberLike) { - return new BigNumber(input).toString(10) + return new BigNumber(input).toFixed() } export function parseBytes(input: string): Array { diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts index b0f1816357a..72d29476b94 100644 --- a/packages/contractkit/src/wrappers/Election.ts +++ b/packages/contractkit/src/wrappers/Election.ts @@ -23,6 +23,17 @@ export interface ValidatorGroupVote { eligible: boolean } +export interface Voter { + address: Address + votes: GroupVote[] +} + +export interface GroupVote { + group: Address + pending: BigNumber + active: BigNumber +} + export interface ElectableValidators { min: BigNumber max: BigNumber @@ -38,7 +49,6 @@ export interface ElectionConfig { * Contract for voting for validators and managing validator groups. */ export class ElectionWrapper extends BaseWrapper { - activate = proxySend(this.kit, this.contract.methods.activate) /** * Returns the minimum and maximum number of validators that can be elected. * @returns The minimum and maximum number of validators that can be elected. @@ -100,6 +110,51 @@ export class ElectionWrapper extends BaseWrapper { this.contract.methods.getGroupsVotedForByAccount ) + async getVotesForGroupByAccount(account: Address, group: Address): Promise { + const pending = await this.contract.methods + .getPendingVotesForGroupByAccount(group, account) + .call() + const active = await this.contract.methods + .getActiveVotesForGroupByAccount(group, account) + .call() + return { + group, + pending: toBigNumber(pending), + active: toBigNumber(active), + } + } + + async getVoter(account: Address): Promise { + const groups = await this.contract.methods.getGroupsVotedForByAccount(account).call() + const votes = await Promise.all(groups.map((g) => this.getVotesForGroupByAccount(account, g))) + return { address: account, votes } + } + + /** + * Returns whether or not the account has any pending votes. + * @param account The address of the account casting votes. + * @return The groups that `account` has voted for. + */ + async hasPendingVotes(account: Address): Promise { + const groups: string[] = await this.contract.methods.getGroupsVotedForByAccount(account).call() + const isNotPending = await Promise.all( + groups.map(async (g) => + toBigNumber( + await this.contract.methods.getPendingVotesForGroupByAccount(account, g).call() + ).isZero() + ) + ) + return !isNotPending.every((a: boolean) => a) + } + + async hasActivatablePendingVotes(account: Address): Promise { + const groups = await this.contract.methods.getGroupsVotedForByAccount(account).call() + const isActivatable = await Promise.all( + groups.map((g: string) => this.contract.methods.hasActivatablePendingVotes(account, g).call()) + ) + return isActivatable.some((a: boolean) => a) + } + /** * Returns current configuration parameters. */ @@ -136,6 +191,72 @@ export class ElectionWrapper extends BaseWrapper { return concurrentMap(5, groups, (g) => this.getValidatorGroupVotes(g)) } + _activate = proxySend(this.kit, this.contract.methods.activate) + + /** + * Activates any activatable pending votes. + * @param account The account with pending votes to activate. + */ + async activate(account: Address): Promise>> { + const groups = await this.contract.methods.getGroupsVotedForByAccount(account).call() + const isActivatable = await Promise.all( + groups.map((g) => this.contract.methods.hasActivatablePendingVotes(account, g).call()) + ) + const groupsActivatable = groups.filter((_, i) => isActivatable[i]) + return groupsActivatable.map((g) => this._activate(g)) + } + + async revokePending( + account: Address, + group: Address, + value: BigNumber + ): Promise> { + const groups = await this.contract.methods.getGroupsVotedForByAccount(account).call() + const index = groups.indexOf(group) + const { lesser, greater } = await this.findLesserAndGreaterAfterVote(group, value.times(-1)) + + return toTransactionObject( + this.kit, + this.contract.methods.revokePending(group, value.toFixed(), lesser, greater, index) + ) + } + + async revokeActive( + account: Address, + group: Address, + value: BigNumber + ): Promise> { + const groups = await this.contract.methods.getGroupsVotedForByAccount(account).call() + const index = groups.indexOf(group) + const { lesser, greater } = await this.findLesserAndGreaterAfterVote(group, value.times(-1)) + + return toTransactionObject( + this.kit, + this.contract.methods.revokeActive(group, value.toFixed(), lesser, greater, index) + ) + } + + async revoke( + account: Address, + group: Address, + value: BigNumber + ): Promise>> { + const vote = await this.getVotesForGroupByAccount(account, group) + if (value.gt(vote.pending.plus(vote.active))) { + throw new Error(`can't revoke more votes for ${group} than have been made by ${account}`) + } + const txos = [] + const pendingValue = BigNumber.minimum(vote.pending, value) + if (!pendingValue.isZero()) { + txos.push(await this.revokePending(account, group, pendingValue)) + } + if (pendingValue.lt(value)) { + const activeValue = value.minus(pendingValue) + txos.push(await this.revokeActive(account, group, activeValue)) + } + return txos + } + /** * Increments the number of total and pending votes for `group`. * @param validatorGroup The validator group to vote for. @@ -150,7 +271,7 @@ export class ElectionWrapper extends BaseWrapper { return toTransactionObject( this.kit, - this.contract.methods.vote(validatorGroup, value.toString(), lesser, greater) + this.contract.methods.vote(validatorGroup, value.toFixed(), lesser, greater) ) } diff --git a/packages/contractkit/src/wrappers/Validators.test.ts b/packages/contractkit/src/wrappers/Validators.test.ts index 5ae5444d0f8..55bccca0fe6 100644 --- a/packages/contractkit/src/wrappers/Validators.test.ts +++ b/packages/contractkit/src/wrappers/Validators.test.ts @@ -1,5 +1,4 @@ import { testWithGanache } from '@celo/dev-utils/lib/ganache-test' -import { addressToPublicKey } from '@celo/utils/lib/signatureUtils' import BigNumber from 'bignumber.js' import Web3 from 'web3' import { newKitFromWeb3 } from '../kit' @@ -48,13 +47,13 @@ testWithGanache('Validators Wrapper', (web3) => { } const setupValidator = async (validatorAccount: string) => { - const publicKey = await addressToPublicKey(validatorAccount, web3.eth.sign) await registerAccountWithLockedGold(validatorAccount) // set account1 as the validator - await validators + await (await validators // @ts-ignore - .registerValidator(publicKey, blsPublicKey, blsPoP) - .sendAndWaitForReceipt({ from: validatorAccount }) + .registerValidator(validatorAccount, blsPublicKey, blsPoP)).sendAndWaitForReceipt({ + from: validatorAccount, + }) } test('SBAT registerValidatorGroup', async () => { diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts index cda4aa7bedc..33cc4b27466 100644 --- a/packages/contractkit/src/wrappers/Validators.ts +++ b/packages/contractkit/src/wrappers/Validators.ts @@ -1,6 +1,7 @@ import { eqAddress } from '@celo/utils/lib/address' import { zip } from '@celo/utils/lib/collections' import { fromFixed, toFixed } from '@celo/utils/lib/fixidity' +import { addressToPublicKey } from '@celo/utils/lib/signatureUtils' import BigNumber from 'bignumber.js' import { Address, NULL_ADDRESS } from '../base' import { Validators } from '../generated/types/Validators' @@ -257,23 +258,21 @@ export class ValidatorsWrapper extends BaseWrapper { * * Fails if the account is already a validator or validator group. * - * @param ecdsaPublicKey The ECDSA public key that the validator is using for consensus, should match - * the validator signer. 64 bytes. + * @param validatorAddress The address that the validator is using for consensus, should match + * the validator signer. * @param blsPublicKey The BLS public key that the validator is using for consensus, should pass proof * of possession. 48 bytes. * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the * account address. 96 bytes. */ - - registerValidator: ( - ecdsaPublicKey: string, - blsPublicKey: string, - blsPop: string - ) => CeloTransactionObject = proxySend( - this.kit, - this.contract.methods.registerValidator, - tupleParser(parseBytes, parseBytes, parseBytes) - ) + async registerValidator(validatorAddress: Address, blsPublicKey: string, blsPop: string) { + const ecdsaPublicKey = await addressToPublicKey(validatorAddress, this.kit.web3.eth.sign) + return toTransactionObject( + this.kit, + // @ts-ignore incorrect typing for bytes type + this.contract.methods.registerValidator(ecdsaPublicKey, blsPublicKey, blsPop) + ) + } /** * De-registers a validator, removing it from the group for which it is a member. diff --git a/packages/docs/command-line-interface/account.md b/packages/docs/command-line-interface/account.md index ac4aca29214..938b4ee9623 100644 --- a/packages/docs/command-line-interface/account.md +++ b/packages/docs/command-line-interface/account.md @@ -296,13 +296,13 @@ USAGE $ celocli account:transferdollar OPTIONS - --amountInWei=amountInWei (required) Amount to transfer (in wei) --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the sender --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the receiver + --value=value (required) Amount to transfer (in wei) EXAMPLE transferdollar --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 - --amountInWei 1 + --value 1 ``` _See code: [packages/cli/src/commands/account/transferdollar.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/transferdollar.ts)_ @@ -316,13 +316,13 @@ USAGE $ celocli account:transfergold OPTIONS - --amountInWei=amountInWei (required) Amount to transfer (in wei) --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the sender --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the receiver + --value=value (required) Amount to transfer (in wei) EXAMPLE - transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 - --amountInWei 1 + transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value + 1 ``` _See code: [packages/cli/src/commands/account/transfergold.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/transfergold.ts)_ diff --git a/packages/docs/command-line-interface/election.md b/packages/docs/command-line-interface/election.md index 6cd348d462d..4629de45e8a 100644 --- a/packages/docs/command-line-interface/election.md +++ b/packages/docs/command-line-interface/election.md @@ -4,6 +4,25 @@ description: View and manage validator elections ## Commands +### Activate + +Activate pending votes in validator elections to begin earning rewards + +``` +USAGE + $ celocli election:activate + +OPTIONS + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Voter's address + --wait Wait until all pending votes become activatable + +EXAMPLES + activate --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 + activate --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --wait +``` + +_See code: [packages/cli/src/commands/election/activate.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/election/activate.ts)_ + ### Current Outputs the currently elected validator set @@ -32,6 +51,26 @@ EXAMPLE _See code: [packages/cli/src/commands/election/list.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/election/list.ts)_ +### Revoke + +Revoke votes for a Validator Group in validator elections. + +``` +USAGE + $ celocli election:revoke + +OPTIONS + --for=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) ValidatorGroup's address + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Voter's address + --value=value (required) Value of votes to revoke + +EXAMPLE + revoke --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --for 0x932fee04521f5fcb21949041bf161917da3f588b, --value + 1000000 +``` + +_See code: [packages/cli/src/commands/election/revoke.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/election/revoke.ts)_ + ### Run Runs an mock election and outputs the validators that were elected @@ -48,14 +87,18 @@ _See code: [packages/cli/src/commands/election/run.ts](https://github.com/celo-o ### Show -Show election information about an existing Validator Group +Show election information about a voter or Validator Group ``` USAGE - $ celocli election:show GROUPADDRESS + $ celocli election:show ADDRESS ARGUMENTS - GROUPADDRESS Validator Groups's address + ADDRESS Voter or Validator Groups's address + +OPTIONS + --group Show information about a group running in Validator elections + --voter Show information about an account voting in Validator elections EXAMPLE show 0x97f7333c51897469E8D98E7af8653aAb468050a3 @@ -72,7 +115,7 @@ USAGE $ celocli election:vote OPTIONS - --for=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Set vote for ValidatorGroup's address + --for=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) ValidatorGroup's address --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Voter's address --value=value (required) Amount of Gold used to vote for group diff --git a/packages/docs/command-line-interface/lockedgold.md b/packages/docs/command-line-interface/lockedgold.md index 949255f3f15..172013b51f0 100644 --- a/packages/docs/command-line-interface/lockedgold.md +++ b/packages/docs/command-line-interface/lockedgold.md @@ -17,7 +17,7 @@ OPTIONS --value=value (required) unit amount of Celo Gold (cGLD) EXAMPLE - lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 1000000000000000000 + lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 10000000000000000000000 ``` _See code: [packages/cli/src/commands/lockedgold/lock.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/lock.ts)_ diff --git a/packages/docs/command-line-interface/validator.md b/packages/docs/command-line-interface/validator.md index 35e0ac1e1bd..1b389d6bd5f 100644 --- a/packages/docs/command-line-interface/validator.md +++ b/packages/docs/command-line-interface/validator.md @@ -83,13 +83,10 @@ USAGE OPTIONS --blsKey=0x (required) BLS Public Key --blsPop=0x (required) BLS Proof-of-Possession - --ecdsaKey=0x (required) ECDSA Public Key --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address for the Validator EXAMPLE - register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --ecdsaKey - 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf - 997eda082ae1 --blsKey + register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --blsKey 0x4fa3f67fc913878b068d1fa1cdddc54913d3bf988dbe5a36a20fa888f20d4894c408a6773f3d7bde11154f2a3076b700d345a42fd25a0e5e83f4 db5586ac7979ac2053cd95d8f2efd3e959571ceccaa743e02cf4be3f5d7aaddb0b06fc9aff00 --blsPop 0xcdb77255037eb68897cd487fdd85388cbda448f617f874449d4b11588b0b7ad8ddc20d9bb450b513bb35664ea3923900 diff --git a/packages/docs/command-line-interface/validatorgroup.md b/packages/docs/command-line-interface/validatorgroup.md index ab77089ffc9..10ee77e5c3a 100644 --- a/packages/docs/command-line-interface/validatorgroup.md +++ b/packages/docs/command-line-interface/validatorgroup.md @@ -71,9 +71,9 @@ OPTIONS --reorder=reorder Reorder a validator within the members list EXAMPLES - member --accept 0x97f7333c51897469e8d98e7af8653aab468050a3 - member --remove 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 - member --reorder 3 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 + member --from 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 --accept 0x97f7333c51897469e8d98e7af8653aab468050a3 + member --from 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 --remove 0x97f7333c51897469e8d98e7af8653aab468050a3 + member --from 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 --reorder 3 0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 ``` _See code: [packages/cli/src/commands/validatorgroup/member.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validatorgroup/member.ts)_ diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 72403b0206e..13e44b0b3d7 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -208,17 +208,20 @@ contract Election is { require(votes.total.eligible.contains(group)); require(0 < value); - require(canReceiveVotes(group, value)); + require(canReceiveVotes(group, value), "Unable to receive votes"); address account = getAccounts().voteSignerToAccount(msg.sender); // Add group to the groups voted for by the account. + bool alreadyVotedForGroup = false; address[] storage groups = votes.groupsVotedFor[account]; - require(groups.length < maxNumGroupsVotedFor); for (uint256 i = 0; i < groups.length; i = i.add(1)) { - require(groups[i] != group); + alreadyVotedForGroup = alreadyVotedForGroup || groups[i] == group; + } + if (!alreadyVotedForGroup) { + require(groups.length < maxNumGroupsVotedFor); + groups.push(group); } - groups.push(group); incrementPendingVotes(group, account, value); incrementTotalVotes(group, value, lesser, greater); getLockedGold().decrementNonvotingAccountBalance(account, value); @@ -244,6 +247,18 @@ contract Election is return true; } + /** + * @notice Returns whether or not an account's votes for the specified group can be activated. + * @param account The account with pending votes. + * @param group The validator group that `account` has pending votes for. + * @return Whether or not `account` has activatable votes for `group`. + * @dev Pending votes cannot be activated until an election has been held. + */ + function hasActivatablePendingVotes(address account, address group) external view returns (bool) { + PendingVote storage pendingVote = votes.pending.forGroup[group].byAccount[account]; + return pendingVote.epoch < getEpochNumber() && pendingVote.value > 0; + } + /** * @notice Revokes `value` pending votes for `group` * @param group The validator group to revoke votes from. diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts index c0b98704a9b..7c0725e9a0b 100644 --- a/packages/protocol/test/governance/election.ts +++ b/packages/protocol/test/governance/election.ts @@ -323,50 +323,110 @@ contract('Election', (accounts: string[]) => { describe('when the voter can vote for an additional group', () => { describe('when the voter has sufficient non-voting balance', () => { - let resp: any beforeEach(async () => { await mockLockedGold.incrementNonvotingAccountBalance(voter, value) - resp = await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) }) - it('should add the group to the list of groups the account has voted for', async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [group]) - }) + describe('when the voter has not already voted for this group', () => { + let resp: any + beforeEach(async () => { + resp = await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) + }) - it("should increment the account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), value) - }) + it('should add the group to the list of groups the account has voted for', async () => { + assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [group]) + }) - it("should increment the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value) - }) + it("should increment the account's pending votes for the group", async () => { + assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), value) + }) - it("should increment the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value) - }) + it("should increment the account's total votes for the group", async () => { + assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value) + }) - it('should increment the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), value) - }) + it("should increment the account's total votes", async () => { + assertEqualBN(await election.getTotalVotesByAccount(voter), value) + }) - it('should increment the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), value) - }) + it('should increment the total votes for the group', async () => { + assertEqualBN(await election.getTotalVotesForGroup(group), value) + }) - it("should decrement the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), 0) - }) + it('should increment the total votes', async () => { + assertEqualBN(await election.getTotalVotes(), value) + }) + + it("should decrement the account's nonvoting locked gold balance", async () => { + assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), 0) + }) + + it('should emit the ValidatorGroupVoteCast event', async () => { + assert.equal(resp.logs.length, 1) + const log = resp.logs[0] + assertContainSubset(log, { + event: 'ValidatorGroupVoteCast', + args: { + account: voter, + group, + value: new BigNumber(value), + }, + }) + }) - it('should emit the ValidatorGroupVoteCast event', async () => { - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupVoteCast', - args: { - account: voter, - group, - value: new BigNumber(value), - }, + describe('when the voter has already voted for this group', () => { + let resp: any + beforeEach(async () => { + await mockLockedGold.incrementNonvotingAccountBalance(voter, value) + resp = await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) + }) + + it('should not change the list of groups the account has voted for', async () => { + assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [group]) + }) + + it("should increment the account's pending votes for the group", async () => { + assertEqualBN( + await election.getPendingVotesForGroupByAccount(group, voter), + value.times(2) + ) + }) + + it("should increment the account's total votes for the group", async () => { + assertEqualBN( + await election.getTotalVotesForGroupByAccount(group, voter), + value.times(2) + ) + }) + + it("should increment the account's total votes", async () => { + assertEqualBN(await election.getTotalVotesByAccount(voter), value.times(2)) + }) + + it('should increment the total votes for the group', async () => { + assertEqualBN(await election.getTotalVotesForGroup(group), value.times(2)) + }) + + it('should increment the total votes', async () => { + assertEqualBN(await election.getTotalVotes(), value.times(2)) + }) + + it("should decrement the account's nonvoting locked gold balance", async () => { + assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), 0) + }) + + it('should emit the ValidatorGroupVoteCast event', async () => { + assert.equal(resp.logs.length, 1) + const log = resp.logs[0] + assertContainSubset(log, { + event: 'ValidatorGroupVoteCast', + args: { + account: voter, + group, + value: new BigNumber(value), + }, + }) + }) }) }) }) From d71a75b541bfefe3144750de6e596c38a123dea2 Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Mon, 25 Nov 2019 00:21:35 -0800 Subject: [PATCH 10/11] Validator UX docs for baklava network (#1688) (#1849) --- packages/cli/package.json | 15 +- .../cli/src/commands/account/authorize.ts | 3 +- packages/cli/src/commands/account/balance.ts | 8 +- .../commands/account/proof-of-possession.ts | 38 +++- packages/cli/src/commands/account/register.ts | 3 +- packages/cli/src/commands/election/current.ts | 3 +- packages/cli/src/commands/election/list.ts | 3 +- packages/cli/src/commands/election/run.ts | 3 +- packages/cli/src/commands/election/show.ts | 2 +- packages/cli/src/commands/exchange/dollars.ts | 47 ++++ packages/cli/src/commands/exchange/gold.ts | 44 ++++ .../cli/src/commands/exchange/selldollar.ts | 27 --- .../cli/src/commands/exchange/sellgold.ts | 27 --- .../commands/exchange/{list.ts => show.ts} | 8 +- packages/cli/src/commands/lockedgold/show.ts | 3 +- .../cli/src/commands/lockedgold/unlock.ts | 3 +- .../cli/src/commands/lockedgold/withdraw.ts | 3 +- .../cli/src/commands/network/parameters.ts | 3 +- packages/cli/src/commands/node/accounts.ts | 2 +- .../transferdollar.ts => transfer/dollars.ts} | 8 +- .../transfergold.ts => transfer/gold.ts} | 8 +- .../cli/src/commands/validator/affiliate.ts | 3 +- .../cli/src/commands/validator/deaffiliate.ts | 3 +- .../cli/src/commands/validator/deregister.ts | 3 +- packages/cli/src/commands/validator/list.ts | 3 +- .../src/commands/validator/requirements.ts | 3 +- packages/cli/src/commands/validator/show.ts | 2 +- .../validator/update-bls-public-key.ts | 3 +- .../src/commands/validatorgroup/commission.ts | 3 +- .../src/commands/validatorgroup/deregister.ts | 3 +- .../cli/src/commands/validatorgroup/list.ts | 3 +- .../src/commands/validatorgroup/register.ts | 6 +- packages/cli/src/utils/lockedgold.ts | 6 +- packages/contractkit/src/wrappers/Accounts.ts | 21 +- .../docs/command-line-interface/account.md | 61 ++---- .../docs/command-line-interface/election.md | 10 +- .../docs/command-line-interface/exchange.md | 52 ++--- .../command-line-interface/introduction.md | 4 + .../docs/command-line-interface/lockedgold.md | 10 +- .../docs/command-line-interface/network.md | 4 +- packages/docs/command-line-interface/node.md | 2 +- .../docs/command-line-interface/transfer.md | 45 ++++ .../docs/command-line-interface/validator.md | 16 +- .../command-line-interface/validatorgroup.md | 12 +- .../getting-started/running-a-full-node.md | 61 ++++-- .../running-a-validator-alfajores.md | 114 ++++++++++ .../running-a-validator-baklava.md | 180 ++++++++++++++++ .../getting-started/running-a-validator.md | 174 +++++++-------- .../running-attestation-service.md | 138 ++++++++++++ .../docs/getting-started/using-the-cli.md | 23 -- scripts/run-docker-validator-network.sh | 200 ++++++++++++++++++ 51 files changed, 1078 insertions(+), 351 deletions(-) create mode 100644 packages/cli/src/commands/exchange/dollars.ts create mode 100644 packages/cli/src/commands/exchange/gold.ts delete mode 100644 packages/cli/src/commands/exchange/selldollar.ts delete mode 100644 packages/cli/src/commands/exchange/sellgold.ts rename packages/cli/src/commands/exchange/{list.ts => show.ts} (74%) rename packages/cli/src/commands/{account/transferdollar.ts => transfer/dollars.ts} (74%) rename packages/cli/src/commands/{account/transfergold.ts => transfer/gold.ts} (79%) create mode 100644 packages/docs/command-line-interface/transfer.md create mode 100644 packages/docs/getting-started/running-a-validator-alfajores.md create mode 100644 packages/docs/getting-started/running-a-validator-baklava.md create mode 100644 packages/docs/getting-started/running-attestation-service.md delete mode 100644 packages/docs/getting-started/using-the-cli.md create mode 100755 scripts/run-docker-validator-network.sh diff --git a/packages/cli/package.json b/packages/cli/package.json index 0dfd83dceea..2c589cb8782 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@celo/celocli", "description": "CLI Tool for transacting with the Celo protocol", - "version": "0.0.29", + "version": "0.0.30-beta", "author": "Celo", "license": "Apache-2.0", "repository": "celo-org/celo-monorepo", @@ -79,16 +79,16 @@ "commands": "./lib/commands", "topics": { "account": { - "description": "Manage your account, send and receive Celo Gold and Celo Dollars" + "description": "Manage your account, keys, and metadata" }, "config": { "description": "Configure CLI options which persist across commands" }, "election": { - "description": "View and manage validator elections" + "description": "Participate in and view the state of Validator Elections" }, "exchange": { - "description": "Commands for interacting with the Exchange" + "description": "Exchange Celo Dollars and Celo Gold via the stability mechanism" }, "lockedgold": { "description": "View and manage locked Celo Gold" @@ -96,11 +96,14 @@ "node": { "description": "Manage your full node" }, + "transfer": { + "description": "Transfer Celo Gold and Celo Dollars" + }, "validator": { - "description": "View and manage validators" + "description": "View and manage Validators" }, "validatorgroup": { - "description": "View and manage validator groups" + "description": "View and manage Validator Groups" } }, "bin": "celocli", diff --git a/packages/cli/src/commands/account/authorize.ts b/packages/cli/src/commands/account/authorize.ts index 4878f7e4013..55d63e24034 100644 --- a/packages/cli/src/commands/account/authorize.ts +++ b/packages/cli/src/commands/account/authorize.ts @@ -5,7 +5,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class Authorize extends BaseCommand { - static description = 'Authorize an attestation, validator, or vote signer' + static description = + 'Keep your locked Gold more secure by authorizing alternative keys to be used for signing attestations, voting, or validating. By doing so, you can continue to participate in the protocol why keeping the key with access to your locked Gold in cold storage. You must include a "proof-of-possession" of the key being authorized, which can be generated with the "account:proof-of-possession" command.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/account/balance.ts b/packages/cli/src/commands/account/balance.ts index d5f44440467..d7800d91d03 100644 --- a/packages/cli/src/commands/account/balance.ts +++ b/packages/cli/src/commands/account/balance.ts @@ -3,13 +3,13 @@ import { printValueMap } from '../../utils/cli' import { Args } from '../../utils/command' export default class Balance extends BaseCommand { - static description = 'View Celo Dollar and Gold balances given account address' + static description = 'View Celo Dollar and Gold balances for an address' static flags = { ...BaseCommand.flags, } - static args = [Args.address('account')] + static args = [Args.address('address')] static examples = ['balance 0x5409ed021d9299bf6814279a6a1411a7e866a631'] @@ -19,8 +19,8 @@ export default class Balance extends BaseCommand { const goldToken = await this.kit.contracts.getGoldToken() const stableToken = await this.kit.contracts.getStableToken() const balances = { - goldBalance: await goldToken.balanceOf(args.account), - dollarBalance: await stableToken.balanceOf(args.account), + goldBalance: await goldToken.balanceOf(args.address), + dollarBalance: await stableToken.balanceOf(args.address), } printValueMap(balances) } diff --git a/packages/cli/src/commands/account/proof-of-possession.ts b/packages/cli/src/commands/account/proof-of-possession.ts index 24b0ab206ba..7c5b0f90cb6 100644 --- a/packages/cli/src/commands/account/proof-of-possession.ts +++ b/packages/cli/src/commands/account/proof-of-possession.ts @@ -1,15 +1,26 @@ import { serializeSignature } from '@celo/utils/lib/signatureUtils' +import { flags } from '@oclif/command' import { BaseCommand } from '../../base' import { printValueMap } from '../../utils/cli' import { Flags } from '../../utils/command' - export default class ProofOfPossession extends BaseCommand { - static description = 'Generate proof-of-possession to be used to authorize a signer' + static description = + 'Generate proof-of-possession to be used to authorize a signer. See the "account:authorize" command for more details.' static flags = { ...BaseCommand.flags, - signer: Flags.address({ required: true }), - account: Flags.address({ required: true }), + signer: Flags.address({ + required: true, + description: 'Address of the signer key to prove possession of.', + }), + account: Flags.address({ + required: true, + description: 'Address of the account that needs to proove possession of the signer key.', + }), + privateKey: flags.string({ + description: + 'Optional. The signer private key, only necessary if the key is not being managed by a locally running node.', + }), } static examples = [ @@ -19,10 +30,19 @@ export default class ProofOfPossession extends BaseCommand { async run() { const res = this.parse(ProofOfPossession) const accounts = await this.kit.contracts.getAccounts() - const pop = await accounts.generateProofOfSigningKeyPossession( - res.flags.account, - res.flags.signer - ) - printValueMap({ signature: serializeSignature(pop) }) + if (res.flags.privateKey) { + const pop = await accounts.generateProofOfSigningKeyPossessionLocally( + res.flags.account, + res.flags.signer, + res.flags.privateKey + ) + printValueMap({ signature: serializeSignature(pop) }) + } else { + const pop = await accounts.generateProofOfSigningKeyPossession( + res.flags.account, + res.flags.signer + ) + printValueMap({ signature: serializeSignature(pop) }) + } } } diff --git a/packages/cli/src/commands/account/register.ts b/packages/cli/src/commands/account/register.ts index 9dd445c5838..c7c4b4791fd 100644 --- a/packages/cli/src/commands/account/register.ts +++ b/packages/cli/src/commands/account/register.ts @@ -5,7 +5,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class Register extends BaseCommand { - static description = 'Register an account' + static description = + 'Register an account on-chain. This allows you to lock Gold, which is a pre-requisite for registering a Validator or Group, participating in Validator elections and on-chain Governance, and earning epoch rewards.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/election/current.ts b/packages/cli/src/commands/election/current.ts index 26fda2f61da..5eba5fe88d0 100644 --- a/packages/cli/src/commands/election/current.ts +++ b/packages/cli/src/commands/election/current.ts @@ -2,7 +2,8 @@ import { cli } from 'cli-ux' import { BaseCommand } from '../../base' export default class ElectionCurrent extends BaseCommand { - static description = 'Outputs the currently elected validator set' + static description = + 'Outputs the set of validators currently participating in BFT to create blocks. The validator set is re-elected at the end of every epoch.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/election/list.ts b/packages/cli/src/commands/election/list.ts index 1079dea038f..b0195723394 100644 --- a/packages/cli/src/commands/election/list.ts +++ b/packages/cli/src/commands/election/list.ts @@ -2,7 +2,8 @@ import { cli } from 'cli-ux' import { BaseCommand } from '../../base' export default class List extends BaseCommand { - static description = 'Outputs the validator groups and their vote totals' + static description = + 'Prints the list of validator groups, the number of votes they have received, the number of additional votes they are able to receive, and whether or not they are eleigible to elect validators.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/election/run.ts b/packages/cli/src/commands/election/run.ts index ee1ea8089f8..3026f7b3e27 100644 --- a/packages/cli/src/commands/election/run.ts +++ b/packages/cli/src/commands/election/run.ts @@ -2,7 +2,8 @@ import { cli } from 'cli-ux' import { BaseCommand } from '../../base' export default class ElectionRun extends BaseCommand { - static description = 'Runs an mock election and outputs the validators that were elected' + static description = + 'Runs a "mock" election and prints out the validators that would be elected if the epoch ended right now.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/election/show.ts b/packages/cli/src/commands/election/show.ts index c73247810d2..345e6337bf1 100644 --- a/packages/cli/src/commands/election/show.ts +++ b/packages/cli/src/commands/election/show.ts @@ -6,7 +6,7 @@ import { printValueMapRecursive } from '../../utils/cli' import { Args } from '../../utils/command' export default class ElectionShow extends BaseCommand { - static description = 'Show election information about a voter or Validator Group' + static description = 'Show election information about a voter or registered Validator Group' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/exchange/dollars.ts b/packages/cli/src/commands/exchange/dollars.ts new file mode 100644 index 00000000000..69c335ca533 --- /dev/null +++ b/packages/cli/src/commands/exchange/dollars.ts @@ -0,0 +1,47 @@ +import { flags } from '@oclif/command' +import BigNumber from 'bignumber.js' +import { BaseCommand } from '../../base' +import { displaySendTx } from '../../utils/cli' +import { Flags } from '../../utils/command' + +export default class ExchangeDollars extends BaseCommand { + static description = 'Exchange Celo Dollars for Celo Gold via the stability mechanism' + + static flags = { + ...BaseCommand.flags, + from: Flags.address({ + required: true, + description: 'The address with Celo Dollars to exchange', + }), + value: Flags.address({ + required: true, + description: 'The value of Celo Dollars to exchange for Celo Gold', + }), + for: Flags.address({ + required: true, + description: 'The minimum value of Celo Gold to receive in return', + }), + commission: flags.string({ required: true }), + } + + static args = [] + + static examples = [ + 'dollars --value 10000000000000 --for 50000000000000 --from 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d', + ] + + async run() { + const res = this.parse(ExchangeDollars) + const sellAmount = new BigNumber(res.flags.value) + const minBuyAmount = new BigNumber(res.flags.for) + + this.kit.defaultAccount = res.flags.from + const stableToken = await this.kit.contracts.getStableToken() + const exchange = await this.kit.contracts.getExchange() + + await displaySendTx('approve', stableToken.approve(exchange.address, sellAmount.toFixed())) + + const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount.toFixed(), false) + await displaySendTx('exchange', exchangeTx) + } +} diff --git a/packages/cli/src/commands/exchange/gold.ts b/packages/cli/src/commands/exchange/gold.ts new file mode 100644 index 00000000000..68e0fa9fa04 --- /dev/null +++ b/packages/cli/src/commands/exchange/gold.ts @@ -0,0 +1,44 @@ +import { flags } from '@oclif/command' +import BigNumber from 'bignumber.js' +import { BaseCommand } from '../../base' +import { displaySendTx } from '../../utils/cli' +import { Flags } from '../../utils/command' + +export default class ExchangeGold extends BaseCommand { + static description = 'Exchange Celo Gold for Celo Dollars via the stability mechanism' + + static flags = { + ...BaseCommand.flags, + from: Flags.address({ required: true, description: 'The address with Celo Gold to exchange' }), + value: Flags.address({ + required: true, + description: 'The value of Celo Gold to exchange for Celo Dollars', + }), + for: Flags.address({ + required: true, + description: 'The minimum value of Celo Dollars to receive in return', + }), + commission: flags.string({ required: true }), + } + + static args = [] + + static examples = [ + 'gold --value 5000000000000 --for 100000000000000 --from 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d', + ] + + async run() { + const res = this.parse(ExchangeGold) + const sellAmount = new BigNumber(res.flags.value) + const minBuyAmount = new BigNumber(res.flags.for) + + this.kit.defaultAccount = res.flags.from + const goldToken = await this.kit.contracts.getGoldToken() + const exchange = await this.kit.contracts.getExchange() + + await displaySendTx('approve', goldToken.approve(exchange.address, sellAmount.toFixed())) + + const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount.toFixed(), true) + await displaySendTx('exchange', exchangeTx) + } +} diff --git a/packages/cli/src/commands/exchange/selldollar.ts b/packages/cli/src/commands/exchange/selldollar.ts deleted file mode 100644 index 60ad39a9c39..00000000000 --- a/packages/cli/src/commands/exchange/selldollar.ts +++ /dev/null @@ -1,27 +0,0 @@ -import BigNumber from 'bignumber.js' -import { BaseCommand } from '../../base' -import { displaySendTx } from '../../utils/cli' -import { swapArguments } from '../../utils/exchange' - -export default class SellDollar extends BaseCommand { - static description = 'Sell Celo dollars for Celo gold on the exchange' - - static args = swapArguments - - static examples = ['selldollar 100 300 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d'] - - async run() { - const { args } = this.parse(SellDollar) - const sellAmount = new BigNumber(args.sellAmount) - const minBuyAmount = new BigNumber(args.minBuyAmount) - - this.kit.defaultAccount = args.from - const stableToken = await this.kit.contracts.getStableToken() - const exchange = await this.kit.contracts.getExchange() - - await displaySendTx('approve', stableToken.approve(exchange.address, sellAmount.toFixed())) - - const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount.toFixed(), false) - await displaySendTx('exchange', exchangeTx) - } -} diff --git a/packages/cli/src/commands/exchange/sellgold.ts b/packages/cli/src/commands/exchange/sellgold.ts deleted file mode 100644 index 977db9839a5..00000000000 --- a/packages/cli/src/commands/exchange/sellgold.ts +++ /dev/null @@ -1,27 +0,0 @@ -import BigNumber from 'bignumber.js' -import { BaseCommand } from '../../base' -import { displaySendTx } from '../../utils/cli' -import { swapArguments } from '../../utils/exchange' - -export default class SellGold extends BaseCommand { - static description = 'Sell Celo gold for Celo dollars on the exchange' - - static args = swapArguments - - static examples = ['sellgold 100 300 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d'] - - async run() { - const { args } = this.parse(SellGold) - const sellAmount = new BigNumber(args.sellAmount) - const minBuyAmount = new BigNumber(args.minBuyAmount) - - this.kit.defaultAccount = args.from - const goldToken = await this.kit.contracts.getGoldToken() - const exchange = await this.kit.contracts.getExchange() - - await displaySendTx('approve', goldToken.approve(exchange.address, sellAmount.toFixed())) - - const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount.toFixed(), true) - await displaySendTx('exchange', exchangeTx) - } -} diff --git a/packages/cli/src/commands/exchange/list.ts b/packages/cli/src/commands/exchange/show.ts similarity index 74% rename from packages/cli/src/commands/exchange/list.ts rename to packages/cli/src/commands/exchange/show.ts index 1d38232cf06..97139cd7afa 100644 --- a/packages/cli/src/commands/exchange/list.ts +++ b/packages/cli/src/commands/exchange/show.ts @@ -2,13 +2,13 @@ import { flags } from '@oclif/command' import { cli } from 'cli-ux' import { BaseCommand } from '../../base' -export default class List extends BaseCommand { - static description = 'List information about tokens on the exchange (all amounts in wei)' +export default class ExchangeShow extends BaseCommand { + static description = 'Show the current exchange rates offered by the Exchange' static flags = { ...BaseCommand.flags, amount: flags.string({ - description: 'Amount of sellToken (in wei) to report rates for', + description: 'Amount of the token being exchanged to report rates for', default: '1000000000000000000', }), } @@ -18,7 +18,7 @@ export default class List extends BaseCommand { static examples = ['list'] async run() { - const { flags: parsedFlags } = this.parse(List) + const { flags: parsedFlags } = this.parse(ExchangeShow) cli.action.start('Fetching exchange rates...') const exchange = await this.kit.contracts.getExchange() diff --git a/packages/cli/src/commands/lockedgold/show.ts b/packages/cli/src/commands/lockedgold/show.ts index b959faa599a..f6f21de0902 100644 --- a/packages/cli/src/commands/lockedgold/show.ts +++ b/packages/cli/src/commands/lockedgold/show.ts @@ -4,7 +4,8 @@ import { printValueMapRecursive } from '../../utils/cli' import { Args } from '../../utils/command' export default class Show extends BaseCommand { - static description = 'Show Locked Gold information for a given account' + static description = + 'Show Locked Gold information for a given account. This includes the total amount of locked gold, the amount being used for voting in Validator Elections, and any pending withdrawals that have been initiated via "lockedgold:unlock".' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/lockedgold/unlock.ts b/packages/cli/src/commands/lockedgold/unlock.ts index 2438a474aac..6eff5df54fa 100644 --- a/packages/cli/src/commands/lockedgold/unlock.ts +++ b/packages/cli/src/commands/lockedgold/unlock.ts @@ -6,7 +6,8 @@ import { Flags } from '../../utils/command' import { LockedGoldArgs } from '../../utils/lockedgold' export default class Unlock extends BaseCommand { - static description = 'Unlocks Celo Gold, which can be withdrawn after the unlocking period.' + static description = + 'Unlocks Celo Gold, which can be withdrawn after the unlocking period. Unlocked gold will appear as a "pending withdrawal" until the unlocking period is over, after which it can be withdrawn via "lockedgold:withdraw".' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/lockedgold/withdraw.ts b/packages/cli/src/commands/lockedgold/withdraw.ts index d3432e6516c..2738495c5fe 100644 --- a/packages/cli/src/commands/lockedgold/withdraw.ts +++ b/packages/cli/src/commands/lockedgold/withdraw.ts @@ -4,7 +4,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class Withdraw extends BaseCommand { - static description = 'Withdraw unlocked gold whose unlocking period has passed.' + static description = + 'Withdraw any pending withdrawals created via "lockedgold:unlock" that have become available.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/network/parameters.ts b/packages/cli/src/commands/network/parameters.ts index a87439ae45b..27369aa1edf 100644 --- a/packages/cli/src/commands/network/parameters.ts +++ b/packages/cli/src/commands/network/parameters.ts @@ -2,7 +2,8 @@ import { BaseCommand } from '../../base' import { printValueMapRecursive } from '../../utils/cli' export default class Parameters extends BaseCommand { - static description = 'View network parameters' + static description = + 'View parameters of the network, including but not limited to configuration for the various Celo core smart contracts.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/node/accounts.ts b/packages/cli/src/commands/node/accounts.ts index 10a9299d826..541eebb7efe 100644 --- a/packages/cli/src/commands/node/accounts.ts +++ b/packages/cli/src/commands/node/accounts.ts @@ -1,7 +1,7 @@ import { BaseCommand } from '../../base' export default class NodeAccounts extends BaseCommand { - static description = 'List node accounts' + static description = 'List the addresses that this node has the private keys for.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/account/transferdollar.ts b/packages/cli/src/commands/transfer/dollars.ts similarity index 74% rename from packages/cli/src/commands/account/transferdollar.ts rename to packages/cli/src/commands/transfer/dollars.ts index 75fcd38dd6a..4c7d26b0c0e 100644 --- a/packages/cli/src/commands/account/transferdollar.ts +++ b/packages/cli/src/commands/transfer/dollars.ts @@ -4,8 +4,8 @@ import { BaseCommand } from '../../base' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' -export default class DollarTransfer extends BaseCommand { - static description = 'Transfer Celo Dollars' +export default class TransferDollars extends BaseCommand { + static description = 'Transfer Celo Dollars to a specified address.' static flags = { ...BaseCommand.flags, @@ -15,11 +15,11 @@ export default class DollarTransfer extends BaseCommand { } static examples = [ - 'transferdollar --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 1', + 'dollars --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 1000000000000000000', ] async run() { - const res = this.parse(DollarTransfer) + const res = this.parse(TransferDollars) const from: string = res.flags.from const to: string = res.flags.to diff --git a/packages/cli/src/commands/account/transfergold.ts b/packages/cli/src/commands/transfer/gold.ts similarity index 79% rename from packages/cli/src/commands/account/transfergold.ts rename to packages/cli/src/commands/transfer/gold.ts index 96e04a39f8d..d56ae4ef7d5 100644 --- a/packages/cli/src/commands/account/transfergold.ts +++ b/packages/cli/src/commands/transfer/gold.ts @@ -4,8 +4,8 @@ import { BaseCommand } from '../../base' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' -export default class GoldTransfer extends BaseCommand { - static description = 'Transfer gold' +export default class TransferGold extends BaseCommand { + static description = 'Transfer Celo Gold to a specified address.' static flags = { ...BaseCommand.flags, @@ -15,11 +15,11 @@ export default class GoldTransfer extends BaseCommand { } static examples = [ - 'transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 1', + 'transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 10000000000000000000', ] async run() { - const res = this.parse(GoldTransfer) + const res = this.parse(TransferGold) const from: string = res.flags.from const to: string = res.flags.to diff --git a/packages/cli/src/commands/validator/affiliate.ts b/packages/cli/src/commands/validator/affiliate.ts index 26dda68f8a4..13f0bc5b16a 100644 --- a/packages/cli/src/commands/validator/affiliate.ts +++ b/packages/cli/src/commands/validator/affiliate.ts @@ -5,7 +5,8 @@ import { displaySendTx } from '../../utils/cli' import { Args, Flags } from '../../utils/command' export default class ValidatorAffiliate extends BaseCommand { - static description = 'Affiliate to a ValidatorGroup' + static description = + "Affiliate a Validator with a Validator Group. This allows the Validator Group to add that Validator as a member. If the Validator is already a member of a Validator Group, affiliating with a different Group will remove the Validator from the first group's members." static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validator/deaffiliate.ts b/packages/cli/src/commands/validator/deaffiliate.ts index 54846cc13c4..6488364b012 100644 --- a/packages/cli/src/commands/validator/deaffiliate.ts +++ b/packages/cli/src/commands/validator/deaffiliate.ts @@ -4,7 +4,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class ValidatorDeAffiliate extends BaseCommand { - static description = 'DeAffiliate to a ValidatorGroup' + static description = + 'Deaffiliate a Validator from a Validator Group, and remove it from the Group if it is also a member.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validator/deregister.ts b/packages/cli/src/commands/validator/deregister.ts index 9c6184c73f1..133d3ea2380 100644 --- a/packages/cli/src/commands/validator/deregister.ts +++ b/packages/cli/src/commands/validator/deregister.ts @@ -4,7 +4,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class ValidatorDeregister extends BaseCommand { - static description = 'Deregister a Validator' + static description = + 'Deregister a Validator. Approximately 60 days after deregistration, the 10,000 Gold locked up to register the Validator will become possible to unlock. Note that deregistering a Validator will also deaffiliate and remove the Validator from any Group it may be an affiliate or member of.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validator/list.ts b/packages/cli/src/commands/validator/list.ts index c1faa4b5de1..fd3530fb1c3 100644 --- a/packages/cli/src/commands/validator/list.ts +++ b/packages/cli/src/commands/validator/list.ts @@ -2,7 +2,8 @@ import { cli } from 'cli-ux' import { BaseCommand } from '../../base' export default class ValidatorList extends BaseCommand { - static description = 'List registered Validators' + static description = + 'List registered Validators, their name (if provided), affiliation, uptime score, and public keys used for validating.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validator/requirements.ts b/packages/cli/src/commands/validator/requirements.ts index 0cb871b268f..69aa4ac16f0 100644 --- a/packages/cli/src/commands/validator/requirements.ts +++ b/packages/cli/src/commands/validator/requirements.ts @@ -2,7 +2,8 @@ import { BaseCommand } from '../../base' import { printValueMap } from '../../utils/cli' export default class ValidatorRequirements extends BaseCommand { - static description = 'Get Requirements for Validators' + static description = + 'List the Locked Gold requirements for registering a Validator. This consists of a value, which is the amount of Celo Gold that needs to be locked in order to register, and a duration, which is the amount of time that Gold must stay locked following the deregistration of the Validator.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validator/show.ts b/packages/cli/src/commands/validator/show.ts index 17ed3bdcf29..1cde4b17516 100644 --- a/packages/cli/src/commands/validator/show.ts +++ b/packages/cli/src/commands/validator/show.ts @@ -5,7 +5,7 @@ import { printValueMap } from '../../utils/cli' import { Args } from '../../utils/command' export default class ValidatorShow extends BaseCommand { - static description = 'Show information about an existing Validator' + static description = 'Show information about a registered Validator.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validator/update-bls-public-key.ts b/packages/cli/src/commands/validator/update-bls-public-key.ts index e4511839fbf..3c31ca102a8 100644 --- a/packages/cli/src/commands/validator/update-bls-public-key.ts +++ b/packages/cli/src/commands/validator/update-bls-public-key.ts @@ -4,7 +4,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class ValidatorUpdateBlsPublicKey extends BaseCommand { - static description = 'Update BLS key for a validator' + static description = + 'Update the BLS public key for a Validator to be used in consensus. Regular (ECDSA and BLS) key rotation is recommended for Validator operational security.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validatorgroup/commission.ts b/packages/cli/src/commands/validatorgroup/commission.ts index 19c7263bda5..fae157fb5e0 100644 --- a/packages/cli/src/commands/validatorgroup/commission.ts +++ b/packages/cli/src/commands/validatorgroup/commission.ts @@ -6,7 +6,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class ValidatorGroupCommission extends BaseCommand { - static description = 'Update the commission for an existing validator group' + static description = + 'Update the commission for a registered Validator Group. This represents the share of the epoch rewards given to elected Validators that goes to the group they are a member of.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validatorgroup/deregister.ts b/packages/cli/src/commands/validatorgroup/deregister.ts index 57c0aeefdbb..bafb6cd95b6 100644 --- a/packages/cli/src/commands/validatorgroup/deregister.ts +++ b/packages/cli/src/commands/validatorgroup/deregister.ts @@ -4,7 +4,8 @@ import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' export default class ValidatorGroupDeRegister extends BaseCommand { - static description = 'Deregister a ValidatorGroup' + static description = + 'Deregister a Validator Group. Approximately 60 days after deregistration, the 10,000 Gold locked up to register the Validator Group will become possible to unlock. Note that the Group must be empty (i.e. no members) before deregistering.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validatorgroup/list.ts b/packages/cli/src/commands/validatorgroup/list.ts index 096520b8ff9..a7e6482e449 100644 --- a/packages/cli/src/commands/validatorgroup/list.ts +++ b/packages/cli/src/commands/validatorgroup/list.ts @@ -2,7 +2,8 @@ import { cli } from 'cli-ux' import { BaseCommand } from '../../base' export default class ValidatorGroupList extends BaseCommand { - static description = 'List existing Validator Groups' + static description = + 'List registered Validator Groups, their names (if provided), commission, and members.' static flags = { ...BaseCommand.flags, diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts index 4c9975a1a87..ef3366b71db 100644 --- a/packages/cli/src/commands/validatorgroup/register.ts +++ b/packages/cli/src/commands/validatorgroup/register.ts @@ -11,7 +11,11 @@ export default class ValidatorGroupRegister extends BaseCommand { static flags = { ...BaseCommand.flags, from: Flags.address({ required: true, description: 'Address for the Validator Group' }), - commission: flags.string({ required: true }), + commission: flags.string({ + required: true, + description: + 'The share of the epoch rewards given to elected Validators that goes to the group.', + }), } static examples = ['register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --commission 0.1'] diff --git a/packages/cli/src/utils/lockedgold.ts b/packages/cli/src/utils/lockedgold.ts index 9e9aee7645e..f37c8976e69 100644 --- a/packages/cli/src/utils/lockedgold.ts +++ b/packages/cli/src/utils/lockedgold.ts @@ -1,10 +1,6 @@ export const LockedGoldArgs = { - pendingWithdrawalIndexArg: { - name: 'pendingWithdrawalIndex', - description: 'index of pending withdrawal whose unlocking period has passed', - }, valueArg: { name: 'value', - description: 'unit amount of Celo Gold (cGLD)', + description: 'The unit amount of Celo Gold (cGLD)', }, } diff --git a/packages/contractkit/src/wrappers/Accounts.ts b/packages/contractkit/src/wrappers/Accounts.ts index 7cb87638588..bf369c9818a 100644 --- a/packages/contractkit/src/wrappers/Accounts.ts +++ b/packages/contractkit/src/wrappers/Accounts.ts @@ -1,8 +1,11 @@ import { hashMessageWithPrefix, + LocalSigner, + NativeSigner, parseSignature, Signature, signedMessageToPublicKey, + Signer, } from '@celo/utils/lib/signatureUtils' import Web3 from 'web3' import { Address } from '../base' @@ -180,7 +183,19 @@ export class AccountsWrapper extends BaseWrapper { } async generateProofOfSigningKeyPossession(account: Address, signer: Address) { - return this.getParsedSignatureOfAddress(account, signer) + return this.getParsedSignatureOfAddress( + account, + signer, + NativeSigner(this.kit.web3.eth.sign, signer) + ) + } + + async generateProofOfSigningKeyPossessionLocally( + account: Address, + signer: Address, + privateKey: string + ) { + return this.getParsedSignatureOfAddress(account, signer, LocalSigner(privateKey)) } /** @@ -247,9 +262,9 @@ export class AccountsWrapper extends BaseWrapper { return parseSignature(hash, signature, signer) } - private async getParsedSignatureOfAddress(address: Address, signer: string) { + private async getParsedSignatureOfAddress(address: Address, signer: string, signerFn: Signer) { const hash = Web3.utils.soliditySha3({ type: 'address', value: address }) - const signature = await this.kit.web3.eth.sign(hash, signer) + const signature = await signerFn.sign(hash) return parseSignature(hash, signature, signer) } } diff --git a/packages/docs/command-line-interface/account.md b/packages/docs/command-line-interface/account.md index 938b4ee9623..b1829761ead 100644 --- a/packages/docs/command-line-interface/account.md +++ b/packages/docs/command-line-interface/account.md @@ -1,12 +1,12 @@ --- -description: Manage your account, send and receive Celo Gold and Celo Dollars +description: Manage your account, keys, and metadata --- ## Commands ### Authorize -Authorize an attestation, validator, or vote signer +Keep your locked Gold more secure by authorizing alternative keys to be used for signing attestations, voting, or validating. By doing so, you can continue to participate in the protocol why keeping the key with access to your locked Gold in cold storage. You must include a "proof-of-possession" of the key being authorized, which can be generated with the "account:proof-of-possession" command. ``` USAGE @@ -29,11 +29,11 @@ _See code: [packages/cli/src/commands/account/authorize.ts](https://github.com/c ### Balance -View Celo Dollar and Gold balances given account address +View Celo Dollar and Gold balances for an address ``` USAGE - $ celocli account:balance ACCOUNT + $ celocli account:balance ADDRESS EXAMPLE balance 0x5409ed021d9299bf6814279a6a1411a7e866a631 @@ -216,15 +216,20 @@ _See code: [packages/cli/src/commands/account/new.ts](https://github.com/celo-or ### Proof-of-possession -Generate proof-of-possession to be used to authorize a signer +Generate proof-of-possession to be used to authorize a signer. See the "account:authorize" command for more details. ``` USAGE $ celocli account:proof-of-possession OPTIONS - --account=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address - --signer=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address + --account=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the account that needs to proove + possession of the signer key. + + --privateKey=privateKey Optional. The signer private key, only necessary if the key is + not being managed by a locally running node. + + --signer=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the signer key to prove possession of. EXAMPLE proof-of-possession --account 0x5409ed021d9299bf6814279a6a1411a7e866a631 --signer @@ -235,7 +240,7 @@ _See code: [packages/cli/src/commands/account/proof-of-possession.ts](https://gi ### Register -Register an account +Register an account on-chain. This allows you to lock Gold, which is a pre-requisite for registering a Validator or Group, participating in Validator elections and on-chain Governance, and earning epoch rewards. ``` USAGE @@ -287,46 +292,6 @@ EXAMPLE _See code: [packages/cli/src/commands/account/show-metadata.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/show-metadata.ts)_ -### Transferdollar - -Transfer Celo Dollars - -``` -USAGE - $ celocli account:transferdollar - -OPTIONS - --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the sender - --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the receiver - --value=value (required) Amount to transfer (in wei) - -EXAMPLE - transferdollar --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 - --value 1 -``` - -_See code: [packages/cli/src/commands/account/transferdollar.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/transferdollar.ts)_ - -### Transfergold - -Transfer gold - -``` -USAGE - $ celocli account:transfergold - -OPTIONS - --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the sender - --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the receiver - --value=value (required) Amount to transfer (in wei) - -EXAMPLE - transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value - 1 -``` - -_See code: [packages/cli/src/commands/account/transfergold.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/transfergold.ts)_ - ### Unlock Unlock an account address to send transactions or validate blocks diff --git a/packages/docs/command-line-interface/election.md b/packages/docs/command-line-interface/election.md index 4629de45e8a..b7fb0140bd1 100644 --- a/packages/docs/command-line-interface/election.md +++ b/packages/docs/command-line-interface/election.md @@ -1,5 +1,5 @@ --- -description: View and manage validator elections +description: Participate in and view the state of Validator Elections --- ## Commands @@ -25,7 +25,7 @@ _See code: [packages/cli/src/commands/election/activate.ts](https://github.com/c ### Current -Outputs the currently elected validator set +Outputs the set of validators currently participating in BFT to create blocks. The validator set is re-elected at the end of every epoch. ``` USAGE @@ -39,7 +39,7 @@ _See code: [packages/cli/src/commands/election/current.ts](https://github.com/ce ### List -Outputs the validator groups and their vote totals +Prints the list of validator groups, the number of votes they have received, the number of additional votes they are able to receive, and whether or not they are eleigible to elect validators. ``` USAGE @@ -73,7 +73,7 @@ _See code: [packages/cli/src/commands/election/revoke.ts](https://github.com/cel ### Run -Runs an mock election and outputs the validators that were elected +Runs a "mock" election and prints out the validators that would be elected if the epoch ended right now. ``` USAGE @@ -87,7 +87,7 @@ _See code: [packages/cli/src/commands/election/run.ts](https://github.com/celo-o ### Show -Show election information about a voter or Validator Group +Show election information about a voter or registered Validator Group ``` USAGE diff --git a/packages/docs/command-line-interface/exchange.md b/packages/docs/command-line-interface/exchange.md index 16fc35f9776..9dad11f6ce0 100644 --- a/packages/docs/command-line-interface/exchange.md +++ b/packages/docs/command-line-interface/exchange.md @@ -1,60 +1,62 @@ --- -description: Commands for interacting with the Exchange +description: Exchange Celo Dollars and Celo Gold via the stability mechanism --- ## Commands -### List +### Dollars -List information about tokens on the exchange (all amounts in wei) +Exchange Celo Dollars for Celo Gold via the stability mechanism ``` USAGE - $ celocli exchange:list + $ celocli exchange:dollars OPTIONS - --amount=amount [default: 1000000000000000000] Amount of sellToken (in wei) to report rates for + --commission=commission (required) + --for=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) The minimum value of Celo Gold to receive in return + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) The address with Celo Dollars to exchange + --value=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) The value of Celo Dollars to exchange for Celo Gold EXAMPLE - list + dollars --value 10000000000000 --for 50000000000000 --from 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d ``` -_See code: [packages/cli/src/commands/exchange/list.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/exchange/list.ts)_ +_See code: [packages/cli/src/commands/exchange/dollars.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/exchange/dollars.ts)_ -### Selldollar +### Gold -Sell Celo dollars for Celo gold on the exchange +Exchange Celo Gold for Celo Dollars via the stability mechanism ``` USAGE - $ celocli exchange:selldollar SELLAMOUNT MINBUYAMOUNT FROM + $ celocli exchange:gold -ARGUMENTS - SELLAMOUNT the amount of sellToken (in wei) to sell - MINBUYAMOUNT the minimum amount of buyToken (in wei) expected - FROM +OPTIONS + --commission=commission (required) + --for=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) The minimum value of Celo Dollars to receive in return + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) The address with Celo Gold to exchange + --value=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) The value of Celo Gold to exchange for Celo Dollars EXAMPLE - selldollar 100 300 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d + gold --value 5000000000000 --for 100000000000000 --from 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d ``` -_See code: [packages/cli/src/commands/exchange/selldollar.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/exchange/selldollar.ts)_ +_See code: [packages/cli/src/commands/exchange/gold.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/exchange/gold.ts)_ -### Sellgold +### Show -Sell Celo gold for Celo dollars on the exchange +Show the current exchange rates offered by the Exchange ``` USAGE - $ celocli exchange:sellgold SELLAMOUNT MINBUYAMOUNT FROM + $ celocli exchange:show -ARGUMENTS - SELLAMOUNT the amount of sellToken (in wei) to sell - MINBUYAMOUNT the minimum amount of buyToken (in wei) expected - FROM +OPTIONS + --amount=amount [default: 1000000000000000000] Amount of the token being exchanged to report rates for EXAMPLE - sellgold 100 300 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d + list ``` -_See code: [packages/cli/src/commands/exchange/sellgold.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/exchange/sellgold.ts)_ +_See code: [packages/cli/src/commands/exchange/show.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/exchange/show.ts)_ diff --git a/packages/docs/command-line-interface/introduction.md b/packages/docs/command-line-interface/introduction.md index 25c74201f36..e28f7fa398b 100644 --- a/packages/docs/command-line-interface/introduction.md +++ b/packages/docs/command-line-interface/introduction.md @@ -40,6 +40,10 @@ Make sure to kill the container when you are done. `$ docker kill celo_cli_container` +### **Prerequisites** + +- **You have a full node running.** See the [Running a Full Node](running-a-full-node.md) instructions for more details on running a full node. + ### Overview The tool is broken down into modules and commands with the following pattern: diff --git a/packages/docs/command-line-interface/lockedgold.md b/packages/docs/command-line-interface/lockedgold.md index 172013b51f0..c792e3f056b 100644 --- a/packages/docs/command-line-interface/lockedgold.md +++ b/packages/docs/command-line-interface/lockedgold.md @@ -14,7 +14,7 @@ USAGE OPTIONS --from=from (required) - --value=value (required) unit amount of Celo Gold (cGLD) + --value=value (required) The unit amount of Celo Gold (cGLD) EXAMPLE lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 10000000000000000000000 @@ -24,7 +24,7 @@ _See code: [packages/cli/src/commands/lockedgold/lock.ts](https://github.com/cel ### Show -Show Locked Gold information for a given account +Show Locked Gold information for a given account. This includes the total amount of locked gold, the amount being used for voting in Validator Elections, and any pending withdrawals that have been initiated via "lockedgold:unlock". ``` USAGE @@ -38,7 +38,7 @@ _See code: [packages/cli/src/commands/lockedgold/show.ts](https://github.com/cel ### Unlock -Unlocks Celo Gold, which can be withdrawn after the unlocking period. +Unlocks Celo Gold, which can be withdrawn after the unlocking period. Unlocked gold will appear as a "pending withdrawal" until the unlocking period is over, after which it can be withdrawn via "lockedgold:withdraw". ``` USAGE @@ -46,7 +46,7 @@ USAGE OPTIONS --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address - --value=value (required) unit amount of Celo Gold (cGLD) + --value=value (required) The unit amount of Celo Gold (cGLD) EXAMPLE unlock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 500000000 @@ -56,7 +56,7 @@ _See code: [packages/cli/src/commands/lockedgold/unlock.ts](https://github.com/c ### Withdraw -Withdraw unlocked gold whose unlocking period has passed. +Withdraw any pending withdrawals created via "lockedgold:unlock" that have become available. ``` USAGE diff --git a/packages/docs/command-line-interface/network.md b/packages/docs/command-line-interface/network.md index 094ec8a2738..b9b193e1645 100644 --- a/packages/docs/command-line-interface/network.md +++ b/packages/docs/command-line-interface/network.md @@ -1,12 +1,12 @@ --- -description: View network parameters +description: View parameters of the network, including but not limited to configuration for the various Celo core smart contracts. --- ## Commands ### Parameters -View network parameters +View parameters of the network, including but not limited to configuration for the various Celo core smart contracts. ``` USAGE diff --git a/packages/docs/command-line-interface/node.md b/packages/docs/command-line-interface/node.md index 63a938881ca..031ad811a85 100644 --- a/packages/docs/command-line-interface/node.md +++ b/packages/docs/command-line-interface/node.md @@ -6,7 +6,7 @@ description: Manage your full node ### Accounts -List node accounts +List the addresses that this node has the private keys for. ``` USAGE diff --git a/packages/docs/command-line-interface/transfer.md b/packages/docs/command-line-interface/transfer.md new file mode 100644 index 00000000000..640ae7f409a --- /dev/null +++ b/packages/docs/command-line-interface/transfer.md @@ -0,0 +1,45 @@ +--- +description: Transfer Celo Gold and Celo Dollars +--- + +## Commands + +### Dollars + +Transfer Celo Dollars to a specified address. + +``` +USAGE + $ celocli transfer:dollars + +OPTIONS + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the sender + --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the receiver + --value=value (required) Amount to transfer (in wei) + +EXAMPLE + dollars --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value + 1000000000000000000 +``` + +_See code: [packages/cli/src/commands/transfer/dollars.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/transfer/dollars.ts)_ + +### Gold + +Transfer Celo Gold to a specified address. + +``` +USAGE + $ celocli transfer:gold + +OPTIONS + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the sender + --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the receiver + --value=value (required) Amount to transfer (in wei) + +EXAMPLE + transfergold --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value + 10000000000000000000 +``` + +_See code: [packages/cli/src/commands/transfer/gold.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/transfer/gold.ts)_ diff --git a/packages/docs/command-line-interface/validator.md b/packages/docs/command-line-interface/validator.md index 1b389d6bd5f..fc7e1f015c2 100644 --- a/packages/docs/command-line-interface/validator.md +++ b/packages/docs/command-line-interface/validator.md @@ -1,12 +1,12 @@ --- -description: View and manage validators +description: View and manage Validators --- ## Commands ### Affiliate -Affiliate to a ValidatorGroup +Affiliate a Validator with a Validator Group. This allows the Validator Group to add that Validator as a member. If the Validator is already a member of a Validator Group, affiliating with a different Group will remove the Validator from the first group's members. ``` USAGE @@ -26,7 +26,7 @@ _See code: [packages/cli/src/commands/validator/affiliate.ts](https://github.com ### Deaffiliate -DeAffiliate to a ValidatorGroup +Deaffiliate a Validator from a Validator Group, and remove it from the Group if it is also a member. ``` USAGE @@ -43,7 +43,7 @@ _See code: [packages/cli/src/commands/validator/deaffiliate.ts](https://github.c ### Deregister -Deregister a Validator +Deregister a Validator. Approximately 60 days after deregistration, the 10,000 Gold locked up to register the Validator will become possible to unlock. Note that deregistering a Validator will also deaffiliate and remove the Validator from any Group it may be an affiliate or member of. ``` USAGE @@ -60,7 +60,7 @@ _See code: [packages/cli/src/commands/validator/deregister.ts](https://github.co ### List -List registered Validators +List registered Validators, their name (if provided), affiliation, uptime score, and public keys used for validating. ``` USAGE @@ -96,7 +96,7 @@ _See code: [packages/cli/src/commands/validator/register.ts](https://github.com/ ### Requirements -Get Requirements for Validators +List the Locked Gold requirements for registering a Validator. This consists of a value, which is the amount of Celo Gold that needs to be locked in order to register, and a duration, which is the amount of time that Gold must stay locked following the deregistration of the Validator. ``` USAGE @@ -110,7 +110,7 @@ _See code: [packages/cli/src/commands/validator/requirements.ts](https://github. ### Show -Show information about an existing Validator +Show information about a registered Validator. ``` USAGE @@ -127,7 +127,7 @@ _See code: [packages/cli/src/commands/validator/show.ts](https://github.com/celo ### Update-bls-public-key -Update BLS key for a validator +Update the BLS public key for a Validator to be used in consensus. Regular (ECDSA and BLS) key rotation is recommended for Validator operational security. ``` USAGE diff --git a/packages/docs/command-line-interface/validatorgroup.md b/packages/docs/command-line-interface/validatorgroup.md index 10ee77e5c3a..638b396b59e 100644 --- a/packages/docs/command-line-interface/validatorgroup.md +++ b/packages/docs/command-line-interface/validatorgroup.md @@ -1,12 +1,12 @@ --- -description: View and manage validator groups +description: View and manage Validator Groups --- ## Commands ### Commission -Update the commission for an existing validator group +Update the commission for a registered Validator Group. This represents the share of the epoch rewards given to elected Validators that goes to the group they are a member of. ``` USAGE @@ -24,7 +24,7 @@ _See code: [packages/cli/src/commands/validatorgroup/commission.ts](https://gith ### Deregister -Deregister a ValidatorGroup +Deregister a Validator Group. Approximately 60 days after deregistration, the 10,000 Gold locked up to register the Validator Group will become possible to unlock. Note that the Group must be empty (i.e. no members) before deregistering. ``` USAGE @@ -41,7 +41,7 @@ _See code: [packages/cli/src/commands/validatorgroup/deregister.ts](https://gith ### List -List existing Validator Groups +List registered Validator Groups, their names (if provided), commission, and members. ``` USAGE @@ -87,7 +87,9 @@ USAGE $ celocli validatorgroup:register OPTIONS - --commission=commission (required) + --commission=commission (required) The share of the epoch rewards given to elected + Validators that goes to the group. + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address for the Validator Group EXAMPLE diff --git a/packages/docs/getting-started/running-a-full-node.md b/packages/docs/getting-started/running-a-full-node.md index 95aae11f3b2..1367dddea64 100644 --- a/packages/docs/getting-started/running-a-full-node.md +++ b/packages/docs/getting-started/running-a-full-node.md @@ -1,6 +1,15 @@ # Running a Full Node -This section explains how to get a full node running on the [Alfajores Testnet](alfajores-testnet.md), using a Docker image that was built for this purpose. +- [Running a Full Node](#running-a-full-node) + - [Prerequisites](#prerequisites) + - [Celo Networks](#celo-networks) + - [Pull the Celo Docker image](#pull-the-celo-docker-image) + - [Set up a data directory](#set-up-a-data-directory) + - [Create an account and get its address](#create-an-account-and-get-its-address) + - [Configure the node](#configure-the-node) + - [Start the node](#start-the-node) + +This section explains how to get a full node running on the [Alfajores Testnet](alfajores-testnet.md) and Baklava Beta Network, using a Docker image that was built for this purpose. Full nodes play a special purpose in the Celo ecosystem, acting as a bridge between the mobile wallets \(running as light clients\) and the validator nodes. To make sure that full nodes are rewarded for this service, the Celo protocol includes full node incentives. Every time a light client sends a new transaction, a portion of the transaction fees will go to the full node that gossips the transaction to other full nodes and validators. @@ -14,11 +23,25 @@ For this reason, despite the fact that Celo uses a proof-of-stake protocol, user A note about conventions: The code you'll see on this page is bash commands and their output. -A $ signifies the bash prompt. Everything following it is the command you should run in a terminal. The $ isn't part of the command, so don't copy it. - When you see text in angle brackets <>, replace them and the text inside with your own value of what it refers to. Don't include the <> in the command. {% endhint %} +## **Celo Networks** + +First we are going to setup the environment depending on the network we want to use (`Baklava` or `Alfajores`). Run: + +```bash +# If you want to connect to Baklava: +export CELO_NETWORK=baklava +export CELO_IMAGE=us.gcr.io/celo-testnet/celo-node +export NETWORK_ID=1101 + +# If you want to connect to Alfajores: +export CELO_NETWORK=alfajores +export CELO_IMAGE=us.gcr.io/celo-testnet/celo-node +export NETWORK_ID=44785 +``` + ## **Pull the Celo Docker image** We're going to use a Docker image containing the Celo node software in this tutorial. @@ -27,15 +50,17 @@ If you are re-running these instructions, the Celo Docker image may have been up Run: -`$ docker pull us.gcr.io/celo-testnet/celo-node:alfajores` +```bash +docker pull $CELO_IMAGE:$CELO_NETWORK +``` ## **Set up a data directory** First, create the directory that will store your node's configuration and its copy of the blockchain. This directory can be named anything you'd like, but here's a default you can use. The commands below create a directory and then navigate into it. The rest of the steps assume you are running the commands from inside this directory. -``` -$ mkdir celo-data-dir -$ cd celo-data-dir +```bash +mkdir celo-data-dir +cd celo-data-dir ``` ## **Create an account and get its address** @@ -44,13 +69,17 @@ In this step, you'll create an account on the network. If you've already done th Run the command to create a new account: -`` $ docker run -v `pwd`:/root/.celo --entrypoint /bin/sh -it us.gcr.io/celo-testnet/celo-node:alfajores -c "geth account new" `` +```bash +docker run -v $PWD:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE:$CELO_NETWORK -c "geth account new" +``` It will prompt you for a passphrase, ask you to confirm it, and then will output your account address: `Address: {}` Save this address to an environment variables, so that you can reference it below (don't include the braces): -`$ export CELO_ACCOUNT_ADDRESS=` +```bash +export CELO_ACCOUNT_ADDRESS= +``` _Note: this environment variable will only persist while you have this terminal window open. If you want this environment variable to be available in the future, you can add it to your `~/.bash_profile_ @@ -58,17 +87,23 @@ _Note: this environment variable will only persist while you have this terminal The genesis block is the first block in the chain, and is specific to each network. This command gets the `genesis.json` file for alfajores and uses it to initialize your nodes' data directory. -`` $ docker run -v `pwd`:/root/.celo us.gcr.io/celo-testnet/celo-node:alfajores init /celo/genesis.json `` +```bash +docker run -v $PWD:/root/.celo $CELO_IMAGE:$CELO_NETWORK init /celo/genesis.json +``` In order to allow the node to sync with the network, give it the address of existing nodes in the network: -`` $ docker run -v `pwd`:/root/.celo --entrypoint cp us.gcr.io/celo-testnet/celo-node:alfajores /celo/static-nodes.json /root/.celo/ `` +```bash +docker run -v $PWD:/root/.celo --entrypoint cp $CELO_IMAGE:$CELO_NETWORK /celo/static-nodes.json /root/.celo/ +``` ## **Start the node** This command specifies the settings needed to run the node, and gets it started. -`` $ docker run -p 127.0.0.1:8545:8545 -p 127.0.0.1:8546:8546 -p 30303:30303 -p 30303:30303/udp -v `pwd`:/root/.celo us.gcr.io/celo-testnet/celo-node:alfajores --verbosity 3 --networkid 44785 --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug,admin,personal --lightserv 90 --lightpeers 1000 --maxpeers 1100 --etherbase $CELO_ACCOUNT_ADDRESS `` +```bash +docker run -p 127.0.0.1:8545:8545 -p 127.0.0.1:8546:8546 -p 30303:30303 -p 30303:30303/udp -v $PWD:/root/.celo $CELO_IMAGE:$CELO_NETWORK --verbosity 3 --networkid $NETWORK_ID --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug,admin,personal --lightserv 90 --lightpeers 1000 --maxpeers 1100 --etherbase $CELO_ACCOUNT_ADDRESS +``` You'll start seeing some output. There may be some errors or warnings that are ignorable. After a few minutes, you should see lines that look like this. This means your node has synced with the network and is receiving blocks. @@ -80,7 +115,7 @@ INFO [07-16|14:04:48.941] Imported new chain segment blocks=335 t INFO [07-16|14:04:56.944] Imported new chain segment blocks=472 txs=0 mgas=0.000 elapsed=8.003s mgasps=0.000 number=1927 hash=4f1010…1414c1 age=4h52m31s cache=2.34mB ``` -You will have fully synced with the network once you have pulled the latest block number, which you can lookup by visiting at the [Alfajores Testnet Stats](https://alfajores-ethstats.celo-testnet.org/) page. +You will have fully synced with the network once you have pulled the latest block number, which you can lookup by visiting at the [Baklava Testnet Stats](https://baklava-ethstats.celo-testnet.org/) or [Alfajores Testnet Stats](https://alfajores-ethstats.celo-testnet.org/) pages. {% hint style="danger" %} **Security**: The command line above includes the parameter `--rpcaddr 0.0.0.0` which makes the Celo Blockchain software listen for incoming RPC requests on all network adaptors. Exercise extreme caution in doing this when running outside Docker, as it means that any unlocked accounts and their funds may be accessed from other machines on the Internet. In the context of running a Docker container on your local machine, this together with the `docker -p` flags allows you to make RPC calls from outside the container, i.e from your local host, but not from outside your machine. Read more about [Docker Networking](https://docs.docker.com/network/network-tutorial-standalone/#use-user-defined-bridge-networks) here. diff --git a/packages/docs/getting-started/running-a-validator-alfajores.md b/packages/docs/getting-started/running-a-validator-alfajores.md new file mode 100644 index 00000000000..18e7c920bac --- /dev/null +++ b/packages/docs/getting-started/running-a-validator-alfajores.md @@ -0,0 +1,114 @@ +# Running a Validator in Alfajores Network + +- [Running a Validator in Alfajores Network](#running-a-validator-in-alfajores-network) + - [Instructions](#instructions) + - [Pull the Celo Docker image](#pull-the-celo-docker-image) + - [Create accounts](#create-accounts) + - [Deploy the Validator node](#deploy-the-validator-node) + - [Running the Attestation Service](#running-the-attestation-service) + +This section explains how to get a Validator node running on the Alfajores network, using a Docker image that was built for this purpose. Most of this process is the same as running a full node, but with a few additional steps. + +This section is specific for Alfajores Network. You can find more details about running a Validator in different networks at [Running a Validator page](running-a-validator.md). + +## Instructions + +First we are going to setup the main environment variables related with the `Alfajores` network. Run: + +```bash +export CELO_IMAGE=us.gcr.io/celo-testnet/celo-node:alfajores +export NETWORK_ID=44785 +export URL_VERIFICATION_POOL=https://us-central1-celo-testnet-production.cloudfunctions.net/handleVerificationRequestalfajores/v0.1/sms/ +``` + +### Pull the Celo Docker image + +In all the commands we are going to see the `CELO_IMAGE` variable to refer to the right Docker image to use. Now we can get the Docker image: + +```bash +docker pull $CELO_IMAGE +``` + +### Create accounts + +Create and cd into the directory where you want to store the data and any other files needed to run your node. You can name this whatever you’d like, but here’s a default you can use: + +```bash +mkdir celo-alfajores-dir +cd celo-alfajores-dir +``` + +Create two accounts, one for the Validator and one for Validator Group, and get their addresses if you don’t already have them. If you already have your accounts, you can skip this step. + +To create your two accounts, run this command twice: + +```bash +docker run -v $PWD:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account new" +``` + +It will prompt you for a passphrase, ask you to confirm it, and then will output your account address: `Address: {}` + +{% hint style="danger" %} +**Warning**: There is a known issue running geth inside Docker that happens eventually. So if that command fails, please check [this page](https://forum.celo.org/t/setting-up-a-validator-faq/90). +{% endhint %} + +Let's save these addresses to environment variables, so that you can reference it later (don't include the braces): + +```bash +export CELO_VALIDATOR_GROUP_ADDRESS= +export CELO_VALIDATOR_ADDRESS= +``` + +In order to register the Validator later on, generate a "proof of possession" - a signature proving you know your Validator's BLS private key. Run this command: + +```bash +docker run -v $PWD:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account proof-of-possession $CELO_VALIDATOR_ADDRESS" +``` + +It will prompt you for the passphrase you've chosen for the Validator account. Let's save the resulting proof-of-possession to an environment variable: + +```bash +export CELO_VALIDATOR_POP= +``` + +### Deploy the Validator node + +Initialize the docker container, building from an image for the network and initializing Celo with the genesis block found inside the Docker image: + +```bash +docker run -v $PWD:/root/.celo $CELO_IMAGE init /celo/genesis.json +``` + +To participate in consensus, we need to set up our nodekey for our account. We can do so via the following command \(it will prompt you for your passphrase\): + +```bash +docker run -v $PWD:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account set-node-key $CELO_VALIDATOR_ADDRESS" +``` + +In order to allow the node to sync with the network, give it the address of existing nodes in the network: + +```bash +docker run -v $PWD:/root/.celo --entrypoint cp $CELO_IMAGE /celo/static-nodes.json /root/.celo/ +``` + +Start up the node: + +```bash +docker run --name celo-validator --restart always -p 127.0.0.1:8545:8545 -p 127.0.0.1:8546:8546 -p 30303:30303 -p 30303:30303/udp -v $PWD:/root/.celo $CELO_IMAGE --verbosity 3 --networkid 44785 --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug,admin,personal --maxpeers 1100 --mine --miner.verificationpool=$URL_VERIFICATION_POOL --etherbase $CELO_VALIDATOR_ADDRESS +``` + +{% hint style="danger" %} +**Security**: The command line above includes the parameter `--rpcaddr 0.0.0.0` which makes the Celo Blockchain software listen for incoming RPC requests on all the interfaces of the Docker container. Exercise extreme caution in doing this when running outside Docker, as it means that any unlocked accounts and their funds may be accessed from other machines on the Internet. In the context of running a Docker container on your local machine, this together with the `docker -p` flags allows you to make RPC calls from outside the container, i.e from your local host, but not from outside your machine. Read more about [Docker Networking](https://docs.docker.com/network/network-tutorial-standalone/#use-user-defined-bridge-networks) here. +{% endhint %} + +The `mine` flag will tell geth to try participating in the BFT consensus protocol, which is analogous to mining on the Ethereum PoW network. It will not be allowed to validate until it gets elected -- so next we need to stand for election. + +The `networkid` parameter value of `44785` indicates we are connecting the Alfajores Testnet. + +### Running the Attestation Service + +As part of the [lightweight identity protocol](/celo-codebase/protocol/identity), Validators are expected to run an [Attestation Service](https://github.com/celo-org/celo-monorepo/tree/master/packages/attestation-service) to provide attestations that allow users to map their phone number to an account on Celo. + +You can find the complete instructions about how to run the [Attestation Service at the documentation page](running-attestation-service.md). + +Now you may need to wait for your node to complete a full sync. You can check on the sync status with `celocli node:synced`. Your node will be fully synced when it has downloaded and processed the latest block, which you can see on the [Alfajores Testnet Stats](https://alfajores-ethstats.celo-testnet.org/) page. diff --git a/packages/docs/getting-started/running-a-validator-baklava.md b/packages/docs/getting-started/running-a-validator-baklava.md new file mode 100644 index 00000000000..a0e3f6cdf3e --- /dev/null +++ b/packages/docs/getting-started/running-a-validator-baklava.md @@ -0,0 +1,180 @@ +# Running a Validator in Baklava Network + +- [Running a Validator in Baklava Network](#running-a-validator-in-baklava-network) + - [Instructions](#instructions) + - [Environment variables](#environment-variables) + - [Pull the Celo Docker image](#pull-the-celo-docker-image) + - [Create accounts](#create-accounts) + - [Deploy the Validator and Proxy nodes](#deploy-the-validator-and-proxy-nodes) + - [Running the Attestation Service](#running-the-attestation-service) + - [Reference Script](#reference-script) + +This section explains how to get a Validator node running on the Baklava network, using a Docker image that was built for this purpose. Most of this process is the same as running a full node, but with a few additional steps. + +This section is specific for Baklava Network. You can find more details about running a Validator in different networks at [Running a Validator page](running-a-validator.md). + +## Instructions + +If you are re-running these instructions, the Celo Docker image may have been updated, and it's important to get the latest version. + +To run a complete Validator it's necessary to execute the following components: + +- The Validator software +- A Proxy that acts as an intermediary for the Validator requests +- The Attestation Service + +The Proxy is not mandatory but highly recommended. It allows to protect the Validator node from outside connections and hide the Validator behind that Proxy from other nodes of the network. + +### Environment variables + +| Variable | Explanation | +| ----------------------------- | ---------------------------------------------------------------- | +| CELO_IMAGE | The Docker image used for the Validator and Proxy containers | | +| NETWORK_ID | The Celo network chain ID | | +| URL_VERIFICATION_POOL | URL for the Verification pool for the attestation process | | +| CELO_VALIDATOR_GROUP_ADDRESS | The public address for the validation group | | +| CELO_VALIDATOR_ADDRESS | The public address for the Validator instance | | +| CELO_PROXY_ADDRESS | The public address for the Proxy instance | | +| CELO_VALIDATOR_BLS_PUBLIC_KEY | The BLS public key for the Validator instance | | +| CELO_VALIDATOR_BLS_SIGNATURE | A proof-of-possession of the BLS public key | | +| PROXY_ENODE | The enode address for the Validator proxy | | +| PROXY_IP | The Proxy container internal IP address from docker pool address | | +| ATTESTATION_KEY | The private key for the account used in the Attestation Service | | +| ATTESTATION_SERVICE_URL | The URL to access the Attestation Service deployed | | +| METADATA_URL | The URL to access the metadata file for your Attestation Service | | + +First we are going to setup the main environment variables related with the `Baklava` network. Run: + +```bash +export CELO_IMAGE=us.gcr.io/celo-testnet/celo-node:baklava +export NETWORK_ID=1101 +``` + +### Pull the Celo Docker image + +In all the commands we are going to see the `CELO_IMAGE` variable to refer to the right Docker image to use. Now we can get the Docker image: + +```bash +docker pull $CELO_IMAGE +``` + +### Create accounts + +At this point we need to create the accounts that will be used by the Validator and the Proxy. We create and cd into the directory where you want to store the data and any other files needed to run your node. You can name this whatever you’d like, but here’s a default you can use: + +```bash +mkdir -p celo-data-dir/proxy celo-data-dir/validator +cd celo-data-dir +``` + +We are going to need to create 3 accounts, 2 for the Validator and 1 for the Proxy. + +First we create three accounts, one for the Validator, one for the Validator Group and the last one for the Proxy. You can generate their addresses using the below commands if you don’t already have them. If you already have some accounts, you can skip this step. + +To create the accounts needed, run the following commands. The first two create the accounts for the Validator, the third one for the Proxy: + +```bash +docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account new" +docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account new" +docker run -v $PWD/proxy:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account new" +``` + +Those commands will prompt you for a passphrase, ask you to confirm it, and then will output your account address: `Address: {}` + +{% hint style="danger" %} +**Warning**: There is a known issue running geth inside Docker that happens eventually. So if that command fails, please check [this page](https://forum.celo.org/t/setting-up-a-validator-faq/90). +{% endhint %} + +Let's save these addresses to environment variables, so that you can reference it later (don't include the braces): + +```bash +export CELO_VALIDATOR_GROUP_ADDRESS= +export CELO_VALIDATOR_ADDRESS= +export CELO_PROXY_ADDRESS= +``` + +In order to register the Validator later on, generate a "proof of possession" - a signature proving you know your Validator's BLS private key. Run this command to generate this "proof-of-possession", which consists of a the BLS public key and a signature: + +```bash +docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account proof-of-possession $CELO_VALIDATOR_ADDRESS" +``` + +It will prompt you for the passphrase you've chosen for the Validator account. Let's save the resulting proof-of-possession to two environment variables: + +```bash +export CELO_VALIDATOR_BLS_PUBLIC_KEY= +export CELO_VALIDATOR_BLS_SIGNATURE= +``` + +### Deploy the Validator and Proxy nodes + +We initialize the Docker containers for the Validator and the Proxy, building from an image for the network and initializing Celo with the genesis block found inside the Docker image: + +```bash +docker run -v $PWD/proxy:/root/.celo $CELO_IMAGE init /celo/genesis.json +docker run -v $PWD/validator:/root/.celo $CELO_IMAGE init /celo/genesis.json +``` + +To participate in consensus, we need to set up our nodekey for our accounts. We can do so via the following commands \(it will prompt you for your passphrase\): + +```bash +docker run -v $PWD/proxy:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account set-node-key $CELO_PROXY_ADDRESS" +docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "geth account set-node-key $CELO_VALIDATOR_ADDRESS" +``` + +{% hint style="danger" %} +**Warning**: There is a known issue running geth inside Docker that happens eventually. So if that command fails, please check [this page](https://forum.celo.org/t/setting-up-a-validator-faq/90). +{% endhint %} + +In order to allow the node to sync with the network, give it the address of existing nodes in the network: + +```bash +docker run -v $PWD/proxy:/root/.celo --entrypoint cp $CELO_IMAGE /celo/static-nodes.json /root/.celo/ +docker run -v $PWD/validator:/root/.celo --entrypoint cp $CELO_IMAGE /celo/static-nodes.json /root/.celo/ +``` + +At this point we are ready to start up the Proxy: + +```bash +docker run --name celo-proxy --restart always -p 8545:8545 -p 8546:8546 -p 30303:30303 -p 30303:30303/udp -p 30503:30503 -p 30503:30503/udp -v $PWD/proxy:/root/.celo $CELO_IMAGE --verbosity 3 --networkid $NETWORK_ID --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug --maxpeers 1100 --etherbase=$CELO_PROXY_ADDRESS --proxy.proxy --proxy.proxiedvalidatoraddress $CELO_VALIDATOR_ADDRESS --proxy.internalendpoint :30503 +``` + +Now we need to obtain the Proxy enode and ip addresses, running the following commands: + +```bash +export PROXY_ENODE=$(docker exec celo-proxy geth --exec "admin.nodeInfo['enode'].split('//')[1].split('@')[0]" attach | tr -d '"') +export PROXY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' celo-proxy) +``` + +Now we can start up the Validator node: + +```bash +docker run -v $PWD/validator:/root/.celo --entrypoint sh --rm $CELO_IMAGE -c "echo $DEFAULT_PASSWORD > /root/.celo/.password" +docker run --name celo-validator --restart always -p 127.0.0.1:8547:8545 -p 127.0.0.1:8548:8546 -p 30304:30303 -p 30304:30303/udp -v $PWD/validator:/root/.celo $CELO_IMAGE --verbosity 3 --networkid $NETWORK_ID --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug --maxpeers 125 --mine --istanbul.blockperiod=5 --istanbul.requesttimeout=3000 --etherbase $CELO_VALIDATOR_ADDRESS --nodiscover --proxy.proxied --proxy.proxyenodeurlpair=enode://$PROXY_ENODE@$PROXY_IP:30503\;enode://$PROXY_ENODE@$PROXY_IP:30503 --unlock=$CELO_VALIDATOR_ADDRESS --password /root/.celo/.password +``` + +{% hint style="danger" %} +**Security**: The command line above includes the parameter `--rpcaddr 0.0.0.0` which makes the Celo Blockchain software listen for incoming RPC requests on all the interfaces of the Docker container. Exercise extreme caution in doing this when running outside Docker, as it means that any unlocked accounts and their funds may be accessed from other machines on the Internet. In the context of running a Docker container on your local machine, this together with the `docker -p` flags allows you to make RPC calls from outside the container, i.e from your local host, but not from outside your machine. Read more about [Docker Networking](https://docs.docker.com/network/network-tutorial-standalone/#use-user-defined-bridge-networks) here. +{% endhint %} + +The `mine` flag does not mean the node starts mining blocks, but rather starts trying to participate in the BFT consensus protocol. It cannot do this until it gets elected -- so next we need to stand for election. + +The `networkid` parameter value of `44785` indicates we are connecting the Baklava Beta network. + +### Running the Attestation Service + +As part of the [lightweight identity protocol](/celo-codebase/protocol/identity), Validators are expected to run an [Attestation Service](https://github.com/celo-org/celo-monorepo/tree/master/packages/attestation-service) to provide attestations that allow users to map their phone number to an account on Celo. + +You can find the complete instructions about how to run the [Attestation Service at the documentation page](running-attestation-service.md). + +You’re all set! Note that elections are finalized at the end of each epoch, roughly once an hour in the Baklava Testnet. After that hour, if you get elected, your node will start participating BFT consensus and validating blocks. Users requesting attestations will hit your registered Attestation Service. + +### Reference Script + +You can use (and modify if you want) this [reference bash script](../../../scripts/run-docker-validator-network.sh) automating all the above steps. It requires Docker and screen. + +You can see all the options using the following command: + +```bash +./run-docker-validator-network.sh help +``` diff --git a/packages/docs/getting-started/running-a-validator.md b/packages/docs/getting-started/running-a-validator.md index e073bc3bf06..adca40b2f4b 100644 --- a/packages/docs/getting-started/running-a-validator.md +++ b/packages/docs/getting-started/running-a-validator.md @@ -1,29 +1,42 @@ # Running a Validator -This section explains how to get a validator node running on the network, using a Docker image that was built for this purpose. Most of this process is the same as running a full node, but with a few additional steps. +- [Running a Validator](#running-a-validator) + - [Prerequisites](#prerequisites) + - [Hardware requirements](#hardware-requirements) + - [Software requirements](#software-requirements) + - [Celo Networks](#celo-networks) + - [Obtain and lock up some Celo Gold for staking](#obtain-and-lock-up-some-celo-gold-for-staking) + - [Baklava](#baklava) + - [Alfajores](#alfajores) + - [Lock up Celo Gold](#lock-up-celo-gold) + - [Run for election](#run-for-election) -Validators help secure the Celo network by participating in Celo’s Proof of Stake protocol. Validators are organized into Validator Groups, analogous to parties in representative democracies. A validator group is essentially an ordered list of validators, along with metadata like name and URL. +This section explains how to get a Validator node running on the network, using a Docker image that was built for this purpose. Most of this process is the same as running a full node, but with a few additional steps. -Just as anyone in a democracy can create their own political party, or seek to get selected to represent a party in an election, any Celo user can create a validator group and add themselves to it, or set up a potential validator and work to get an existing validator group to include them. +Validators help secure the Celo network by participating in Celo’s Proof of Stake protocol. Validators are organized into Validator Groups, analogous to parties in representative democracies. A Validator Group is essentially an ordered list of Validators, along with metadata like name and URL. -While other Validator Groups will exist on the Alfajores Testnet, the fastest way to get up and running with a validator will be to register a Validator Group, register a Validator, and add that Validator to your Validator Group. The addresses used to register Validator Groups and Validators must be unique, which will require that you create two accounts in the step-by-step guide below. +Just as anyone in a democracy can create their own political party, or seek to get selected to represent a party in an election, any Celo user can create a Validator group and add themselves to it, or set up a potential Validator and work to get an existing Validator group to include them. + +While other Validator Groups will exist on the Celo Networks, the fastest way to get up and running with a Validator will be to register a Validator Group, register a Validator, and add that Validator to your Validator Group. The addresses used to register Validator Groups and Validators must be unique, which will require that you create two accounts in the step-by-step guide below. + +You can find more details about Celo mission and why becoming a Validator [at the following page](https://medium.com/celohq/calling-all-chefs-become-a-celo-validator-c75d1c2909aa). {% hint style="info" %} -If you are starting up a validator, please consider leaving it running for a few weeks to support the network. +If you are starting up a Validator, please consider leaving it running for a few weeks to support the network. {% endhint %} -## **Prerequisites** +## Prerequisites ### Hardware requirements -Because Celo network is based in Proof of Stake, the hardware requirements are not very high. Proof of Stake consensus is not so CPU intensive as Proof of Work but has a higher requirements of network connectivity and lantency. Here you have a list of the standard requirements for running a validator node: +Because Celo network is based in Proof of Stake, the hardware requirements are not very high. Proof of Stake consensus is not so CPU intensive as Proof of Work but has a higher requirements of network connectivity and lantency. Here you have a list of the standard requirements for running a Validator node: - Memory: 8 GB RAM - CPU: Quad core 3GHz (64-bit) - Disk: 256 GB of SSD storage - Network: At least 1 GB input/output dual Ethernet -It is recommended to run the validator node in an environment that facilitates a 24/7 execution. Deployments in a top-tier datacenter facilitates the security and better uptimes. +It is recommended to run the Validator node in an environment that facilitates a 24/7 execution. Deployments in a top-tier datacenter facilitates the security and better uptimes. ### Software requirements @@ -46,167 +59,126 @@ The code you'll see on this page is bash commands and their output. When you see text in angle brackets <>, replace them and the text inside with your own value of what it refers to. Don't include the <> in the command. {% endhint %} -## **Pull the Celo Docker image** - -We're going to use a Docker image containing the Celo node software in this tutorial. - -If you are re-running these instructions, the Celo Docker image may have been updated, and it's important to get the latest version. - -Run: - -```bash -docker pull us.gcr.io/celo-testnet/celo-node:alfajores` -``` +## Celo Networks -## **Create accounts** +Celo provides different networks for different purposes. You can find the specifics about how to run a Validator in the Celo networks in the following documentation pages: -Create and cd into the directory where you want to store the data and any other files needed to run your node. You can name this whatever you’d like, but here’s a default you can use: +- [Running a Validator in Baklava Network](running-a-validator-baklava.md) +- [Running a Validator in Alfajores Network](running-a-validator-alfajores.md) -```bash -mkdir celo-data-dir -cd celo-data-dir -``` +In this documentation pages we're going to use a Docker image containing the Celo node software. -Create two accounts, one for the Validator and one for Validator Group, and get their addresses if you don’t already have them. If you already have your accounts, you can skip this step. +You can use also this [reference script](../../../scripts/run-docker-validator-network.sh) for running the Celo validator stack using Docker containers. -To create your two accounts, run this command twice: +### Obtain and lock up some Celo Gold for staking -```bash -docker run -v $PWD:/root/.celo --entrypoint /bin/sh -it us.gcr.io/celo-testnet/celo-node:alfajores -c "geth account new" -``` +#### Baklava -It will prompt you for a passphrase, ask you to confirm it, and then will output your account address: `Address: {}` +To participate in The Great Celo Stake Off (aka TGCSO) and get fauceted it's necessary to register online via an [online form](https://docs.google.com/forms/d/e/1FAIpQLSfbn5hTJ4UIWpN92-o2qMTUB0UnrFsL0fm97XqGe4VhhN_r5A/viewform). -Let's save these addresses to environment variables, so that you can reference it later (don't include the braces): +#### Alfajores -```bash -export CELO_VALIDATOR_GROUP_ADDRESS= -export CELO_VALIDATOR_ADDRESS= -``` +Visit the [Alfajores Celo Faucet](https://celo.org/build/faucet) to send **both** of your accounts some funds. -In order to register the validator later on, generate a "proof of possession" - a signature proving you know your validator's BLS private key. Run this command: +In a new tab, unlock your accounts so that you can send transactions. This only unlocks the accounts for the lifetime of the Validator that's running, so be sure to unlock `$CELO_VALIDATOR_ADDRESS` again if your node gets restarted: ```bash -docker run -v $PWD:/root/.celo --entrypoint /bin/sh -it us.gcr.io/celo-testnet/celo-node:alfajores -c "geth account proof-of-possession $CELO_VALIDATOR_ADDRESS" +# You will be prompted for your password. +celocli account:unlock --account $CELO_VALIDATOR_GROUP_ADDRESS +celocli account:unlock --account $CELO_VALIDATOR_ADDRESS ``` -It will prompt you for the passphrase you've chosen for the validator account. Let's save the resulting proof-of-possession to an environment variable: +In a new tab, make a locked Gold account for both of your addresses by running the Celo CLI. This will allow you to stake Celo Gold, which is required to register a Validator and Validator Groups: ```bash -export CELO_VALIDATOR_POP= +celocli account:register --from $CELO_VALIDATOR_GROUP_ADDRESS --name +celocli account:register --from $CELO_VALIDATOR_ADDRESS --name ``` -## Deploy the validator node +#### Lock up Celo Gold -Initialize the docker container, building from an image for the network and initializing Celo with the genesis block: +Lock up Celo Gold for both accounts in order to secure the right to register a Validator and Validator Group. The current requirement is 10k Celo Gold to register a validator, and 10k Celo Gold _per member validator_ to register a Validator Group. For Validators, this gold remains locked for approximately 60 days following deregistration. For groups, this gold remains locked for approximately 60 days following the removal of the Nth validator from the group. ```bash -docker run -v $PWD:/root/.celo us.gcr.io/celo-testnet/celo-node:alfajores init /celo/genesis.json +celocli lockedgold:lock --from $CELO_VALIDATOR_GROUP_ADDRESS --value 10000000000000000000000 +celocli lockedgold:lock --from $CELO_VALIDATOR_ADDRESS --value 10000000000000000000000 ``` -To participate in consensus, we need to set up our nodekey for our account. We can do so via the following command \(it will prompt you for your passphrase\): +### Run for election -```bash -docker run -v $PWD:/root/.celo --entrypoint /bin/sh -it us.gcr.io/celo-testnet/celo-node:alfajores -c "geth account set-node-key $CELO_VALIDATOR_ADDRESS" -``` +In order to be elected as a Validator, you will first need to register your group and Validator. Note that when registering a Validator Group, you need to specify a commission, which is the fraction of epoch rewards paid to the group by its members. -In order to allow the node to sync with the network, give it the address of existing nodes in the network: +Register your Validator Group: ```bash -docker run -v $PWD:/root/.celo --entrypoint cp us.gcr.io/celo-testnet/celo-node:alfajores /celo/static-nodes.json /root/.celo/ +celocli validatorgroup:register --from $CELO_VALIDATOR_GROUP_ADDRESS --commission 0.1 ``` -Start up the node: +Register your Validator: ```bash -docker run -p 127.0.0.1:8545:8545 -p 127.0.0.1:8546:8546 -p 30303:30303 -p 30303:30303/udp -v $PWD:/root/.celo us.gcr.io/celo-testnet/celo-node:alfajores --verbosity 3 --networkid 44785 --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug,admin,personal --maxpeers 1100 --mine --etherbase $CELO_VALIDATOR_ADDRESS +celocli validator:register --from $CELO_VALIDATOR_ADDRESS --blsKey $CELO_VALIDATOR_BLS_PUBLIC_KEY --blsPop $CELO_VALIDATOR_BLS_SIGNATURE ``` -{% hint style="danger" %} -**Security**: The command line above includes the parameter `--rpcaddr 0.0.0.0` which makes the Celo Blockchain software listen for incoming RPC requests on all network adaptors. Exercise extreme caution in doing this when running outside Docker, as it means that any unlocked accounts and their funds may be accessed from other machines on the Internet. In the context of running a Docker container on your local machine, this together with the `docker -p` flags allows you to make RPC calls from outside the container, i.e from your local host, but not from outside your machine. Read more about [Docker Networking](https://docs.docker.com/network/network-tutorial-standalone/#use-user-defined-bridge-networks) here. -{% endhint %} - -The `mine` flag will tell geth to try participating in the BFT consensus protocol, which is analogous to mining on the Ethereum PoW network. It will not be allowed to validate until it gets elected -- so next we need to stand for election. - -The `networkid` parameter value of `44785` indicates we are connecting the Alfajores Testnet. - -Now you may need to wait for your node to complete a full sync. You can check on the sync status with `celocli node:synced`. Your node will be fully synced when it has downloaded and processed the latest block, which you can see on the [Alfajores Testnet Stats](https://alfajores-ethstats.celo-testnet.org/) page. - -## Obtain and lock up some Celo Gold for staking - -Visit the [Alfajores Faucet](https://celo.org/build/faucet) to send **both** of your accounts some funds. - -In a new tab, unlock your accounts so that you can send transactions. This only unlocks the accounts for the lifetime of the validator that's running, so be sure to unlock `$CELO_VALIDATOR_ADDRESS` again if your node gets restarted: +Affiliate your Validator with your Validator Group. Note that you will not be a member of this group until the Validator Group accepts you: ```bash -# You will be prompted for your password. -celocli account:unlock --account $CELO_VALIDATOR_GROUP_ADDRESS -celocli account:unlock --account $CELO_VALIDATOR_ADDRESS +celocli validator:affiliate $CELO_VALIDATOR_GROUP_ADDRESS --from $CELO_VALIDATOR_ADDRESS ``` -In a new tab, make a locked Gold account for both of your addresses by running the Celo CLI. This will allow you to stake Celo Gold, which is required to register a validator and validator groups: +Accept the affiliation: ```bash -celocli account:register --from $CELO_VALIDATOR_GROUP_ADDRESS --name -celocli account:register --from $CELO_VALIDATOR_ADDRESS --name +celocli validatorgroup:member --accept $CELO_VALIDATOR_ADDRESS --from $CELO_VALIDATOR_GROUP_ADDRESS ``` -Make a locked Gold commitment for both accounts in order to secure the right to register a validator and validator group. The current requirement is 1 Celo Gold with a notice period of 60 days. If you choose to stake more gold, or a longer notice period, be sure to use those values below: +Use both accounts to vote for your Validator Group: ```bash -celocli lockedgold:lockup --from $CELO_VALIDATOR_GROUP_ADDRESS --goldAmount 1000000000000000000 --noticePeriod 5184000 -celocli lockedgold:lockup --from $CELO_VALIDATOR_ADDRESS --goldAmount 1000000000000000000 --noticePeriod 5184000 +celocli election:vote --from $CELO_VALIDATOR_ADDRESS --for $CELO_VALIDATOR_GROUP_ADDRESS --value 10000000000000000000000 +celocli election:vote --from $CELO_VALIDATOR_GROUP_ADDRESS --for $CELO_VALIDATOR_GROUP_ADDRESS --value 10000000000000000000000 ``` -## Run for election - -In order to be elected as a validator, you will first need to register your group and validator and give them each an an ID, which people will know them by (e.g. `Awesome Validators Inc.` and `Alice's Awesome Validator`). +You’re all set! Note that elections are finalized at the end of each epoch, roughly once an hour in the Alfajores or Baklava Testnets. After that hour, if you get elected, your node will start participating BFT consensus and validating blocks. -Register your validator group: +You can inspect the current state of voting by running: ```bash -celocli validatorgroup:register --id --from $CELO_VALIDATOR_GROUP_ADDRESS --noticePeriod 5184000 +celocli election:list ``` -Register your validator: +If you find your Validator still not getting elected you may need to faucet yourself more funds and lock more gold in order to be able to cast more votes for your Validator Group! + +At any moment you can check the currently elected validators by running the following command: ```bash -celocli validator:register --id --from $CELO_VALIDATOR_ADDRESS --noticePeriod 5184000 --publicKey 0x`openssl rand -hex 64`$CELO_VALIDATOR_POP +celocli election:current ``` -{% hint style="info" %} -**Roadmap**: Note that the “publicKey” first part of the public key field is currently ignored, and thus can be set to any 128 character hex value. The rest is used for the BLS public key and proof-of-possession. -{% endhint %} - -Affiliate your validator with your validator group. Note that you will not be a member of this group until the validator group accepts you: +### Stop Validating -```bash -celocli validator:affiliation --set $CELO_VALIDATOR_GROUP_ADDRESS --from $CELO_VALIDATOR_ADDRESS -``` +If for some reason you need to stop running your Validator, please do one or all of the following so that it will no longer be chosen as a participant in BFT: -Accept the affiliation: +- Deregister your validator: ```bash -celocli validatorgroup:member --accept $CELO_VALIDATOR_ADDRESS --from $CELO_VALIDATOR_GROUP_ADDRESS +celocli validator:deaffiliate --from $CELO_VALIDATOR_ADDRESS +celocli validator:deregister --from $CELO_VALIDATOR_ADDRESS ``` -Use both accounts to vote for your validator group: +- Stop voting for your validator group: ```bash -celocli validatorgroup:vote --from $CELO_VALIDATOR_ADDRESS --for $CELO_VALIDATOR_GROUP_ADDRESS -celocli validatorgroup:vote --from $CELO_VALIDATOR_GROUP_ADDRESS --for $CELO_VALIDATOR_GROUP_ADDRESS +celocli election:revoke --from $CELO_VALIDATOR_ADDRESS --for $CELO_VALIDATOR_GROUP_ADDRESS --value 10000000000000000000000 +celocli election:revoke --from $CELO_VALIDATOR_GROUP_ADDRESS --for $CELO_VALIDATOR_GROUP_ADDRESS --value 10000000000000000000000 ``` -You’re all set! Note that elections are finalized at the end of each epoch, roughly once an hour in the Alfajores Testnet. After that hour, if you get elected, your node will start participating BFT consensus and validating blocks. - -You can inspect the current state of voting by running: +- Deregister your validator group: ```bash -celocli validatorgroup:list +celocli validatorgroup:deregister --from $CELO_VALIDATOR_GORUP_ADDRESS ``` -If you find your validator still not getting elected you may need to faucet yourself more funds and bond a greater deposit to command more voting weight! - {% hint style="info" %} **Roadmap**: Different parameters will govern elections in a Celo production network. Epochs are likely to be daily, rather than hourly. Running a Validator will also include setting up proxy nodes to protect against DDoS attacks, and using hardware wallets to secure the key used to sign blocks. We plan to update these instructions with more details soon. {% endhint %} diff --git a/packages/docs/getting-started/running-attestation-service.md b/packages/docs/getting-started/running-attestation-service.md new file mode 100644 index 00000000000..739e019bba9 --- /dev/null +++ b/packages/docs/getting-started/running-attestation-service.md @@ -0,0 +1,138 @@ +# Running the Attestation Service + +- [Running the Attestation Service](#running-the-attestation-service) + - [Environment variables](#environment-variables) + - [Sms Providers](#sms-providers) + - [Nexmo](#nexmo) + - [Twilio](#twilio) + - [Accounts Configuration](#accounts-configuration) \* [Database Configuration](#database-configuration) + - [Executing the Attestation Service](#executing-the-attestation-service) + +As part of the [lightweight identity protocol](/celo-codebase/protocol/identity), validators are expected to run an Attestation Service to provide attestations that allow users to map their phone number to an account on Celo. The Attestation Service is a simple Node.js application that can be run with a Docker image. + +## Environment variables + +The service needs the following environment variables: + +| Variable | Explanation | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DATABASE_URL | The URL under which your database is accessible, currently supported are `postgres://`, `mysql://` and `sqlite://` | | +| CELO_PROVIDER | The URL under which a celo blockchain node is reachable, i.e. something like `https://integration-forno.celo-testnet.org` | | +| ACCOUNT_ADDRESS | The address of the validator account | | +| ATTESTATION_PRIVATE_KEY | The private key with which attestations should be signed. You could use your account key for attestations, but really you should authorize a dedicated attestation key | | +| APP_SIGNATURE | The hash with which clients can auto-read SMS messages on android | | +| SMS_PROVIDERS | A comma-separated list of providers you want to configure, we currently support `nexmo` & `twilio` | | + +## Sms Providers + +Currently the Sms providers supported are Nexmo & Twilio. You can create your user account in the provider of your election using the [Nexmo Sign Up form](https://dashboard.nexmo.com/sign-up) or the [Twilio Sign Up form](https://www.twilio.com/try-twilio). + +### Nexmo + +Here is the list of the enviromnet variables needed to use the Nexmo SMS broker: + +| Variable | Explanation | +| --------------- | --------------------------------------------------------------- | +| NEXMO_KEY | The API key to the Nexmo API | +| NEXMO_SECRET | The API secret to the Nexmo API | +| NEXMO_BLACKLIST | A comma-sperated list of country codes you do not want to serve | + +### Twilio + +If you prefer using Twilio, this is list of the variables to use: + +| Variable | Explanation | +| ---------------------------- | --------------------------------------------------------------- | +| TWILIO_ACCOUNT_SID | The Twilio account ID | +| TWILIO_MESSAGING_SERVICE_SID | The Twilio Message Service ID. Starts by `MG` | +| TWILIO_AUTH_TOKEN | The API authentication token | +| TWILIO_BLACKLIST | A comma-sperated list of country codes you do not want to serve | + +## Accounts Configuration + +First we need to create an account for getting the attestation key needed to sign the attestations. Run: + +```bash +celocli account:new +``` + +We copy the account details and assign the Private Key to the `ATTESTATION_PRIVATE_KEY` environment variable: + +```bash +export ATTESTATION_PRIVATE_KEY=0x +export ATTESTATION_ADDRESS= +``` + +You can create a proof of posession of this attestation key by running the CLI commands (remember to prefix the key with 0x) + +```bash +celocli account:proof-of-possession --signer $ATTESTATION_ADDRESS --account $CELO_VALIDATOR_ADDRESS --privateKey $ATTESTATION_KEY +``` + +That will give you a signature that you can then use to authorize the key: + +```bash +celocli account:authorize --from $CELO_VALIDATOR_ADDRESS -r attestation --pop SIGNATURE --signer $ATTESTATION_ADDRESS +``` + +The Attestation Service needs to connect to a Web3 Provider. This is going to depend on the network you want to connect. So depending on which network you are making available the service, you need to configure the `CELO_PROVIDER` variable pointing to that. + +For example: + +```bash +# Web3 provider for Alfajores network +export CELO_PROVIDER="https://alfajores-forno.celo-testnet.org/" +``` + +#### Database Configuration + +For storing and retrieving the attestation requests the service needs a database to persist that information. Currently `sqlite`, `postgres` and `mysql` are supported. For testing purposes you can use `sqlite` but it's recommended to run a stand-alone database server using `mysql` or `postgres` if your intention is running the Attestation Service in a production environment. + +So for specifying the database url you need to setup the `DATABASE_URL` variable: + +```bash +export DATABASE_URL="sqlite://db/dev.db" +export DATABASE_URL="mysql://user:password@mysql.example.com:3306/attestation-service" +export DATABASE_URL="postgres://user:password@postgres.example.com:5432/attestation-service" +``` + +You can find the migration scripts for creating the schema at the `celo-monorepo`, `packages/attestation-service` folder. From there, after setting up the `DATABASE_URL` env variable you can run the following commands: + +```bash +yarn run db:create +yarn run db:migrate +``` + +## Executing the Attestation Service + +The following command for running the Attestation Service is using Nexmo, but you can adapt for using Twilio easily: + +```bash +docker run -e ATTESTATION_KEY=$ATTESTATION_KEY -e ACCOUNT_ADDRESS=$CELO_VALIDATOR_ADDRESS -e CELO_PROVIDER=$CELO_PROVIDER -e DATABASE_URL=$DATABASE_URL -e SMS_PROVIDERS=nexmo -e NEXMO_KEY=$NEXMO_KEY -e NEXMO_SECRET=$NEXMO_SECRET -e NEXMO_BLACKLIST=$NEXMO_BLACKLIST -p 3000:80 us.gcr.io/celo-testnet/attestation-service:$CELO_NETWORK +``` + +In order for users to request attestations from your service, you need to register the endpoint under which your service is reachable in your [metadata](/celo-codebase/protocol/identity/metadata). + +```bash +celocli identity:create-metadata ./metadata.json +``` + +The `ATTESTATION_SERVICE_URL` variable stores the URL to access the Attestation Service deployed. In the following command we specify the URL where this Attestation Service is: + +```bash +celocli identity:change-attestation-service-url ./metadata.json --url $ATTESTATION_SERVICE_URL +``` + +And then host your metadata somewhere reachable via HTTP. You can register your metadata URL with: + +```bash +celocli identity:register-metadata --url --from $CELO_VALIDATOR_ADDRESS +``` + +You can use for testing a gist url (i.e: `https://gist.github.com/john.doe/a29f83d478c9daa2ac52596ba9778391`) or similar where you have publicly available your metadata. + +If everything goes well users should see that you are ready for attestations by running: + +```bash +celocli identity:get-metadata $CELO_VALIDATOR_ADDRESS +``` diff --git a/packages/docs/getting-started/using-the-cli.md b/packages/docs/getting-started/using-the-cli.md deleted file mode 100644 index 7185cb2452e..00000000000 --- a/packages/docs/getting-started/using-the-cli.md +++ /dev/null @@ -1,23 +0,0 @@ -# Using the CLI - -This section describes how to make a transaction using the Celo CLI. Doing so is easy and quick once you have fauceted yourself some funds and have a full node running. - -### **Prerequisites** - -- **You have Docker installed.** If you don’t have it already, follow the instructions here: [Get Started with Docker](https://www.docker.com/get-started). It will involve creating or signing in with a Docker account, downloading a desktop app, and then launching the app to be able to use the Docker CLI. If you are running on a Linux server, follow the instructions for your distro [here](https://docs.docker.com/install/#server). You may be required to run Docker with sudo depending on your installation environment. -- **You have celocli installed.** - - See to [Command Line Interface \(CLI\)](../command-line-interface/introduction.md) for instructions on how to get set up. - -- **You have a full node running.** See the [Running a Full Node](running-a-full-node.md) instructions for more details on running a full node. -- **You have fauceted yourself.** See the [Faucet](faucet.md) instructions for help funding your account with testnet tokens. - -### **Sending a payment** - -Unlock your accounts so that you can send transactions: - -`$ celocli account:unlock --account $YOUR_ADDRESS --password ` - -Send a payment to another account: - -`$ celocli account:transferdollar --from $YOUR_ADDRESS --amountInWei $AMOUNT --to $DESTINATION_ADDRESS` diff --git a/scripts/run-docker-validator-network.sh b/scripts/run-docker-validator-network.sh new file mode 100755 index 00000000000..7aaba350b8b --- /dev/null +++ b/scripts/run-docker-validator-network.sh @@ -0,0 +1,200 @@ +#!/usr/bin/bash +set -euo pipefail + +export LC_ALL=en_US.UTF-8 + +# Usage: run-network.sh +COMMAND=${1:-"pull,accounts,deploy,run-validator,status"} +DATA_DIR=${2:-"/tmp/celo/network"} +export CELO_IMAGE=${3:-"us.gcr.io/celo-testnet/geth@sha256:4bc97381db0bb81b7a3e473bb61d447c90be165834316d3f75bc34d7db718b39"} +export NETWORK_ID=${4:-"1101"} +export NETWORK_NAME=${5:-"integration"} +export DEFAULT_PASSWORD=${6:-"1234"} +export CELO_IMAGE_ATTESTATION=${7:-"us.gcr.io/celo-testnet/celo-monorepo@sha256:3e958851e4a89e39eeefcc56e324d9ee0d09286a36cb63c285195183fe4dc4ee"} +export CELO_PROVIDER=${8:-"https://integration-forno.celo-testnet.org/"} # https://berlintestnet001-forno.celo-networks-dev.org/ +export DATABASE_URL=${9:-"sqlite://db/dev.db"} + +export NEXMO_KEY="xx" +export NEXMO_SECRET="xx" +export NEXMO_BLACKLIST="" + + +VALIDATOR_DIR="${DATA_DIR}/validator" +PROXY_DIR="${DATA_DIR}/proxy" +mkdir -p $DATA_DIR +mkdir -p $VALIDATOR_DIR +mkdir -p $PROXY_DIR +__PWD=$PWD + + +#### Internal functions +remove_containers () { + echo -e "\tRemoving previous celo-proxy and celo-validator containers" + docker rm -f celo-proxy celo-validator celo-attestation-service || echo -e "Containers removed" +} + +download_genesis () { + echo -e "\tDownload genesis.json and static-nodes.json to the container" + docker run -v $PWD/proxy:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "wget https://www.googleapis.com/storage/v1/b/static_nodes/o/$NETWORK_NAME?alt=media -O /root/.celo/static-nodes.json" + docker run -v $PWD/proxy:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "wget https://www.googleapis.com/storage/v1/b/genesis_blocks/o/$NETWORK_NAME?alt=media -O /root/.celo/genesis.json" + + docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "wget https://www.googleapis.com/storage/v1/b/static_nodes/o/$NETWORK_NAME?alt=media -O /root/.celo/static-nodes.json" + docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "wget https://www.googleapis.com/storage/v1/b/genesis_blocks/o/$NETWORK_NAME?alt=media -O /root/.celo/genesis.json" + +} + +make_status_requests () { + echo -e "Checking Proxy and Validator state:" + + echo -n "* Proxy eth_blockNumber:" + curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' -H "Content-Type: application/json" localhost:8545 + + echo -n "* Validator net_peerCount:" + curl -X POST --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":74}' -H "Content-Type: application/json" localhost:8547 + + echo -n "* Validator eth_mining:" + curl -X POST --data '{"jsonrpc":"2.0","method":"eth_mining","params":[],"id":1}' -H "Content-Type: application/json" localhost:8547 + echo -e "" + +} + +#### Main + +if [[ $COMMAND == *"help"* ]]; then + + echo -e "Script for running a local validator network using docker containers. This script runs:" + echo -e "\t - A Validator node" + echo -e "\t - A Proxy node" + echo -e "\t - An attestation service\n" + + echo -e "Options:" + echo -e "$0 " + echo -e "\t - Command; comma separated list of actions to execute. Options are: help, pull, clean, accounts, deploy, run-validator, run-attestation, status. Default: pull,accounts,deploy,run-validator,status" + echo -e "\t - Data Dir; Local folder where will be created the data dir for the nodes. Default: /tmp/celo/network" + echo -e "\t - Celo Image; Image to download" + echo -e "\t - Celo Network; Docker image network to use (typically alfajores or baklava, but you can use a commit). " + echo -e "\t - Network Id; 1101 for integration, 44785 for alfajores, etc." + echo -e "\t - Network Name; integration by default" + echo -e "\t - Password; Password to use during the creation of accounts" + + echo -e "\nExamples:" + echo -e "$0 pull,clean,deploy,run-validator " + echo -e "$0 deploy,run-validator /tmp/celo/network" + + echo -e "\n" + exit 0 +fi + +if [[ $COMMAND == *"pull"* ]]; then + + echo -e "* Downloading docker image: $CELO_IMAGE" + docker pull $CELO_IMAGE + +fi + + +if [[ $COMMAND == *"clean"* ]]; then + + echo -e "* Removing data dir $DATA_DIR" + rm -rf $DATA_DIR + mkdir -p $DATA_DIR + mkdir -p $VALIDATOR_DIR + mkdir -p $PROXY_DIR +fi + + +if [[ $COMMAND == *"accounts"* ]]; then + + echo -e "* Creating addresses ..." + cd $DATA_DIR + + export CELO_VALIDATOR_ADDRESS=$(docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c " printf '%s\n' $DEFAULT_PASSWORD $DEFAULT_PASSWORD | geth account new " |tail -1| cut -d'{' -f 2| tr -cd "[:alnum:]\n" ) + export CELO_VALIDATOR_GROUP_ADDRESS=$(docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c " printf '%s\n' $DEFAULT_PASSWORD $DEFAULT_PASSWORD | geth account new " |tail -1| cut -d'{' -f 2| tr -cd "[:alnum:]\n" ) + export CELO_PROXY_ADDRESS=$(docker run -v $PWD/proxy:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c " printf '%s\n' $DEFAULT_PASSWORD $DEFAULT_PASSWORD | geth account new " |tail -1| cut -d'{' -f 2| tr -cd "[:alnum:]\n" ) + + echo -e "\tCELO_VALIDATOR_ADDRESS=$CELO_VALIDATOR_ADDRESS" + echo -e "\tCELO_VALIDATOR_GROUP_ADDRESS=$CELO_VALIDATOR_ADDRESS" + echo -e "\tCELO_PROXY_ADDRESS=$CELO_VALIDATOR_ADDRESS" + + export CELO_VALIDATOR_POP=$(docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c " printf '%s\n' $DEFAULT_PASSWORD | geth account proof-of-possession $CELO_VALIDATOR_ADDRESS "| tail -1| cut -d' ' -f 3| tr -cd "[:alnum:]\n" ) + + echo -e "\tCELO_VALIDATOR_POP=$CELO_VALIDATOR_POP" + +fi + +if [[ $COMMAND == *"deploy"* ]]; then + + echo -e "* Deploying ..." + cd $DATA_DIR + + download_genesis + echo -e "\tInitializing using genesis" + docker run -v $PWD/proxy:/root/.celo $CELO_IMAGE init /root/.celo/genesis.json + docker run -v $PWD/validator:/root/.celo $CELO_IMAGE init /root/.celo/genesis.json + + echo -e "\tSetting up nodekey" + docker run -v $PWD/proxy:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "printf '%s\n' $DEFAULT_PASSWORD | geth account set-node-key $CELO_PROXY_ADDRESS" + docker run -v $PWD/validator:/root/.celo --entrypoint /bin/sh -it $CELO_IMAGE -c "printf '%s\n' $DEFAULT_PASSWORD | geth account set-node-key $CELO_VALIDATOR_ADDRESS" + +fi + + +if [[ $COMMAND == *"run-validator"* ]]; then + + echo -e "* Let's run the validator network ..." + cd $DATA_DIR + + remove_containers + echo -e "\tStarting the Proxy" + screen -S celo-proxy -d -m docker run --name celo-proxy --restart always -p 8545:8545 -p 8546:8546 -p 30303:30303 -p 30303:30303/udp -p 30503:30503 -p 30503:30503/udp -v $PWD/proxy:/root/.celo $CELO_IMAGE --verbosity 3 --networkid $NETWORK_ID --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug --maxpeers 1100 --etherbase=$CELO_PROXY_ADDRESS --proxy.proxy --proxy.proxiedvalidatoraddress $CELO_VALIDATOR_ADDRESS --proxy.internalendpoint :30503 + + sleep 10s + + export PROXY_ENODE=$(docker exec celo-proxy geth --exec "admin.nodeInfo['enode'].split('//')[1].split('@')[0]" attach | tr -d '"') + export PROXY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' celo-proxy) + echo -e "\tProxy running: enode://$PROXY_ENODE@$PROXY_IP" + + echo -e "\tStarting Validator node" + docker run -v $PWD/validator:/root/.celo --entrypoint sh --rm $CELO_IMAGE -c "echo $DEFAULT_PASSWORD > /root/.celo/.password" + screen -S celo-validator -d -m docker run --name celo-validator --restart always -p 127.0.0.1:8547:8545 -p 127.0.0.1:8548:8546 -p 30304:30303 -p 30304:30303/udp -v $PWD/validator:/root/.celo $CELO_IMAGE --verbosity 3 --networkid $NETWORK_ID --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug --maxpeers 125 --mine --istanbul.blockperiod=5 --istanbul.requesttimeout=3000 --etherbase $CELO_VALIDATOR_ADDRESS --nodiscover --proxy.proxied --proxy.proxyenodeurlpair=enode://$PROXY_ENODE@$PROXY_IP:30503\;enode://$PROXY_ENODE@$PROXY_IP:30503 --unlock=$CELO_VALIDATOR_ADDRESS --password /root/.celo/.password + + sleep 5s + + echo -e "\tEverything should be running, you can check running 'screen -ls'" + screen -ls + + echo -e "\tYou can re-attach to the proxy or the validator running:" + echo -e "\t 'screen -r -S celo-proxy' or 'screen -r -S celo-validator'\n" + +fi + +if [[ $COMMAND == *"run-attestation"* ]]; then + + echo -e "* Let's run the attestation service ..." + + echo -e "\tPulling docker image: $CELO_IMAGE_ATTESTATION" + docker pull $CELO_IMAGE_ATTESTATION + + export ATTESTATION_KEY=$(celocli account:new| tail -3| head -1| cut -d' ' -f 2| tr -cd "[:alnum:]\n") + echo -e "\tATTESTATION_KEY=$ATTESTATION_KEY" + + screen -S attestation-service -d -m docker run --name celo-attestation-service --restart always -e ATTESTATION_KEY=$ATTESTATION_KEY -e ACCOUNT_ADDRESS=$CELO_VALIDATOR_ADDRESS -e CELO_PROVIDER=$CELO_PROVIDER -e DATABASE_URL=$DATABASE_URL -e SMS_PROVIDERS=nexmo -e NEXMO_KEY=$NEXMO_KEY -e NEXMO_SECRET=$NEXMO_SECRET -e NEXMO_BLACKLIST=$NEXMO_BLACKLIST -p 3000:80 $CELO_IMAGE_ATTESTATION + + echo -e "\tAttestation service should be running, you can check running 'screen -ls'" + echo -e "\tYou can re-attach to the attestation-service running:" + echo -e "\t 'screen -r -S celo-attestation-service'\n" + +fi + +if [[ $COMMAND == *"status"* ]]; then + + make_status_requests + +fi + +cd $__PWD + + + + + From a6d0b691765603aa3580b4911501c6b988e0e055 Mon Sep 17 00:00:00 2001 From: Jean Regisser Date: Mon, 25 Nov 2019 09:26:37 +0100 Subject: [PATCH 11/11] [Docs] Correct typo --- packages/docs/getting-started/running-a-validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/getting-started/running-a-validator.md b/packages/docs/getting-started/running-a-validator.md index adca40b2f4b..59477ba0d77 100644 --- a/packages/docs/getting-started/running-a-validator.md +++ b/packages/docs/getting-started/running-a-validator.md @@ -29,7 +29,7 @@ If you are starting up a Validator, please consider leaving it running for a few ### Hardware requirements -Because Celo network is based in Proof of Stake, the hardware requirements are not very high. Proof of Stake consensus is not so CPU intensive as Proof of Work but has a higher requirements of network connectivity and lantency. Here you have a list of the standard requirements for running a Validator node: +Because Celo network is based in Proof of Stake, the hardware requirements are not very high. Proof of Stake consensus is not so CPU intensive as Proof of Work but has a higher requirements of network connectivity and latency. Here you have a list of the standard requirements for running a Validator node: - Memory: 8 GB RAM - CPU: Quad core 3GHz (64-bit)