diff --git a/.circleci/config.yml b/.circleci/config.yml index e6046f541ab..d3ce211a131 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,6 +93,42 @@ jobs: - run: yarn run ci-lint + build-all-packages: + <<: *defaults + steps: + - attach_workspace: + at: ~/app + + - run: + name: Build SDK + command: | + set -e + yarn --cwd=packages/contractkit build alfajores + + - run: + name: Build CLI + command: | + yarn --cwd=packages/cli setup:environment alfajores + yarn --cwd=packages/cli build + + - run: + name: Build mobile + command: yarn --cwd=packages/mobile build + + - run: + name: Build Verification Pool + command: yarn --cwd=packages/verification-pool-api compile-typescript + + - run: + name: Build Verifier + command: yarn --cwd=packages/verifier build:typescript + + - run: + name: Build Web + command: | + set -e + yarn --cwd=packages/web build + general-test: <<: *defaults steps: @@ -128,6 +164,12 @@ jobs: - attach_workspace: at: ~/app + - run: + name: Ensure translations are not missing + command: | + set -euo pipefail + diff <(cat packages/mobile/locales/en-US/*.json | jq keys | sort) <(cat packages/mobile/locales/es-AR/*.json | jq keys | sort) + - run: name: jest tests command: | @@ -182,6 +224,27 @@ jobs: # Flaky tests - run them twice command: yarn --cwd packages/protocol test || yarn --cwd packages/protocol test + contractkit-test: + <<: *defaults + steps: + - attach_workspace: + at: ~/app + - run: + name: test alphanet + command: | + # Test alphanet + set -euo pipefail + yarn --cwd=packages/contractkit build alfajores + yarn --cwd=packages/contractkit test + + - run: + name: test alphanet staging + command: | + # Test alphanet + set -euo pipefail + yarn --cwd=packages/contractkit build alfajoresstaging + yarn --cwd=packages/contractkit test + cli-test: <<: *defaults steps: @@ -282,6 +345,35 @@ jobs: mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config ./ci_test_sync.sh checkout master + end-to-end-geth-integration-sync-test: + <<: *defaults + steps: + - attach_workspace: + at: ~/app + - run: + name: Check if the test should run + command: | + DIRS_TO_CHECK="${PWD}/packages/celotool,${PWD}/packages/protocol" + ./scripts/ci_check_if_test_should_run_v2.sh ${DIRS_TO_CHECK} + - run: + name: Setup Go language + command: | + set -e + set -v + wget https://dl.google.com/go/go1.11.5.linux-amd64.tar.gz + tar xf go1.11.5.linux-amd64.tar.gz -C /tmp + ls /tmp/go/bin/go + /tmp/go/bin/go version + - run: + name: Run test + command: | + set -e + export PATH=${PATH}:/tmp/go/bin + go version + cd packages/celotool + mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config + ./ci_test_integration_sync.sh checkout master + web: working_directory: ~/app docker: @@ -301,10 +393,22 @@ workflows: - lint-checks: requires: - install_dependencies + - build-all-packages: + requires: + - install_dependencies + - lint-checks - general-test: requires: - install_dependencies - lint-checks + - contractkit-test: + requires: + - install_dependencies + - lint-checks + - cli-test: + requires: + - install_dependencies + - lint-checks - mobile-test: requires: - install_dependencies @@ -320,12 +424,20 @@ workflows: - protocol-test: requires: - install_dependencies + - lint-checks - end-to-end-geth-transfer-test: requires: - install_dependencies + - lint-checks - end-to-end-geth-governance-test: requires: - install_dependencies + - lint-checks - end-to-end-geth-sync-test: requires: - install_dependencies + - lint-checks + - end-to-end-geth-integration-sync-test: + requires: + - install_dependencies + - lint-checks diff --git a/.env b/.env index 2190c71422d..631fc2f4b26 100644 --- a/.env +++ b/.env @@ -40,9 +40,6 @@ EPOCH=30000 VALIDATORS="3" STATIC_IPS_FOR_GETH_NODES=false -# requires STATIC_IPS_FOR_GETH_NODES to be false -INTERNAL_BOOTNODE=true - GETHTX1_NODE_ID="1182aa8c9dbb96cd1aa71b74e2b6b481085971e08b210bab3b64c39d54876d4b1370f3f2c3cc3c0f52806a0e5772aa3fe937b4ceda8b97c5bf647a34170555e4" GETHTX2_NODE_ID="b1d8deee4e5f4faf9b7e8e7fbd5e19545632d9023ff10de55e2e7a37464c52d1d6fb3ac8cb011757558b37309b83d915de19ef86eb27fe13209cc02d0098ee1f" GETHTX3_NODE_ID="7f8b950b57ef0189637375e1aab3c6cc089501063089242ccdfb3982045025feeb64fdf343b08f2534372f5d636fa6804150c14e40bc2d395057a834c6be3932" diff --git a/.env.alfajores b/.env.alfajores index 3a14140d2c9..3222aa1a6c7 100644 --- a/.env.alfajores +++ b/.env.alfajores @@ -13,6 +13,7 @@ BLOCKSCOUT_WEB_DOCKER_IMAGE_TAG="web-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" BLOCKSCOUT_INDEXER_DOCKER_IMAGE_TAG="indexer-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" BLOCKSCOUT_WEB_REPLICAS=3 BLOCKSCOUT_DB_SUFFIX="6" +BLOCKSCOUT_SUBNETWORK_NAME="Alfajores" 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 @@ -46,9 +47,6 @@ VALIDATORS=70 TX_NODES=10 STATIC_IPS_FOR_GETH_NODES=true -# requires STATIC_IPS_FOR_GETH_NODES to be false -INTERNAL_BOOTNODE=false - ADMIN_RPC_ENABLED=false # Testnet vars diff --git a/.env.alfajoresstaging b/.env.alfajoresstaging index aec487af41b..871816589e2 100644 --- a/.env.alfajoresstaging +++ b/.env.alfajoresstaging @@ -13,6 +13,7 @@ BLOCKSCOUT_WEB_DOCKER_IMAGE_TAG="web-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" BLOCKSCOUT_INDEXER_DOCKER_IMAGE_TAG="indexer-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" BLOCKSCOUT_WEB_REPLICAS=3 BLOCKSCOUT_DB_SUFFIX="8" +BLOCKSCOUT_SUBNETWORK_NAME="Alfajores Staging" GETH_NODE_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/geth" # When upgrading change this to latest commit hash from the master of the geth repo @@ -43,9 +44,6 @@ VALIDATORS=70 TX_NODES=10 STATIC_IPS_FOR_GETH_NODES=true -# requires STATIC_IPS_FOR_GETH_NODES to be false -INTERNAL_BOOTNODE=false - ADMIN_RPC_ENABLED=false # Testnet vars diff --git a/.env.appintegration b/.env.appintegration index 73304782e39..966c17620d0 100644 --- a/.env.appintegration +++ b/.env.appintegration @@ -13,6 +13,7 @@ BLOCKSCOUT_WEB_DOCKER_IMAGE_TAG="web-af8cefc512035b8fea9fd61e2828b9b9d6f2ae96" BLOCKSCOUT_INDEXER_DOCKER_IMAGE_TAG="indexer-af8cefc512035b8fea9fd61e2828b9b9d6f2ae96" BLOCKSCOUT_WEB_REPLICAS=3 BLOCKSCOUT_DB_SUFFIX="5" +BLOCKSCOUT_SUBNETWORK_NAME="App Integration" GETH_NODE_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/geth" GETH_NODE_DOCKER_IMAGE_TAG="94c7a784fe0e44737c4f2637b52ead0f6d98a26c" @@ -39,9 +40,6 @@ VALIDATORS=10 TX_NODES=4 STATIC_IPS_FOR_GETH_NODES=false -# requires STATIC_IPS_FOR_GETH_NODES to be false -INTERNAL_BOOTNODE=false - ADMIN_RPC_ENABLED=false # Testnet vars diff --git a/.env.integration b/.env.integration index a095f1d508b..4db170aa9ad 100644 --- a/.env.integration +++ b/.env.integration @@ -13,6 +13,7 @@ BLOCKSCOUT_WEB_DOCKER_IMAGE_TAG="web-fb9a5bd46a0968865ef30cc568a260f01cb7fdaf" BLOCKSCOUT_INDEXER_DOCKER_IMAGE_TAG="indexer-fb9a5bd46a0968865ef30cc568a260f01cb7fdaf" BLOCKSCOUT_WEB_REPLICAS=3 BLOCKSCOUT_DB_SUFFIX="15" +BLOCKSCOUT_SUBNETWORK_NAME="Integration" 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 @@ -46,9 +47,6 @@ VALIDATORS=1 TX_NODES=1 STATIC_IPS_FOR_GETH_NODES=false -# requires STATIC_IPS_FOR_GETH_NODES to be false -INTERNAL_BOOTNODE=false - ADMIN_RPC_ENABLED=false # Testnet vars diff --git a/.env.integrationtesting b/.env.integrationtesting index 847eeebc5cf..0b7f3e1a6a1 100644 --- a/.env.integrationtesting +++ b/.env.integrationtesting @@ -13,6 +13,7 @@ BLOCKSCOUT_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/blockscout" BLOCKSCOUT_WEB_DOCKER_IMAGE_TAG="web-ea15cfd04caedc7dfb8b23342ece8a9e61963cc1" BLOCKSCOUT_INDEXER_DOCKER_IMAGE_TAG="indexer-6448af74c2032ef02e45bf3534b15ceb4c1622ad" BLOCKSCOUT_WEB_REPLICAS=3 +BLOCKSCOUT_SUBNETWORK_NAME="Integration Testing" GETH_NODE_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/geth" GETH_NODE_DOCKER_IMAGE_TAG="eb90e01af7b9f2b336279374471a00e16ff66614" @@ -37,9 +38,6 @@ BLOCK_TIME=5 VALIDATORS=1 TX_NODES=1 -# requires STATIC_IPS_FOR_GETH_NODES to be false -INTERNAL_BOOTNODE=false - # Testnet vars GETH_NODES_BACKUP_CRONJOB_ENABLED=true CONTRACT_CRONJOBS_ENABLED=true diff --git a/.env.pilot b/.env.pilot new file mode 100644 index 00000000000..ced0e8c70b6 --- /dev/null +++ b/.env.pilot @@ -0,0 +1,75 @@ +ENV_TYPE="production" + +GETH_VERBOSITY=2 + +KUBERNETES_CLUSTER_NAME="pilot" +KUBERNETES_CLUSTER_ZONE="us-west1-a" +CLUSTER_DOMAIN_NAME="celo-testnet" + +TESTNET_PROJECT_NAME="celo-testnet-production" + +BLOCKSCOUT_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/blockscout" +BLOCKSCOUT_WEB_DOCKER_IMAGE_TAG="web-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" +BLOCKSCOUT_INDEXER_DOCKER_IMAGE_TAG="indexer-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" +BLOCKSCOUT_WEB_REPLICAS=3 +BLOCKSCOUT_DB_SUFFIX="1" +BLOCKSCOUT_SUBNETWORK_NAME="Pilot" + +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="58a4453c22504e3367a4d95177320acda4c5b061" + +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="58a4453c22504e3367a4d95177320acda4c5b061" + +CELOTOOL_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" +CELOTOOL_DOCKER_IMAGE_TAG="celotool-2616309a839a30e53faecfafb9b68ab51a5fcdcf" + +TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" +TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_TAG="transaction-metrics-exporter-2616309a839a30e53faecfafb9b68ab51a5fcdcf" + +GETH_EXPORTER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet-production/geth-exporter" +GETH_EXPORTER_DOCKER_IMAGE_TAG="ed7d21bd50592709173368cd697ef73c1774a261" + +# Genesis Vars +NETWORK_ID=1101 +CONSENSUS_TYPE="istanbul" +PREDEPLOYED_CONTRACTS="REGISTRY" +BLOCK_TIME=5 +EPOCH=17280 // Minimum epoch length is 1 day + +# "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=10 +TX_NODES=2 +STATIC_IPS_FOR_GETH_NODES=false + +ADMIN_RPC_ENABLED=false + +# Testnet vars +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 + +VERIFICATION_POOL_URL="https://us-central1-celo-testnet-production.cloudfunctions.net/handleVerificationRequestpilot/v0.1/sms/" +VERIFICATION_REWARDS_URL="https://us-central1-celo-testnet-production.cloudfunctions.net/handleVerificationRequestpilot/v0.1/rewards/" + +STACKDRIVER_MONITORING_DASHBOARD="https://app.google.stackdriver.com/dashboards/18188447122939770939?project=celo-testnet-production" +STACKDRIVER_NOTIFICATION_CHANNEL="11334674278687022983" +MOBILE_WALLET_PLAYSTORE_LINK="https://play.google.com/apps/internaltest/4700990475000634666" + +NOTIFICATION_SERVICE_FIREBASE_DB="https://console.firebase.google.com/u/0/project/celo-org-mobile/database/celo-org-mobile-int/data" + +PROMTOSD_SCRAPE_INTERVAL="5m" +PROMTOSD_EXPORT_INTERVAL="5m" + +AUCTION_CRON_SPEC="*/5 * * * *" + +SMS_RETRIEVER_HASH_CODE=l5k6LvdPDXS diff --git a/.env.pilotstaging b/.env.pilotstaging new file mode 100644 index 00000000000..7b983bfcc62 --- /dev/null +++ b/.env.pilotstaging @@ -0,0 +1,72 @@ +ENV_TYPE="staging" + +GETH_VERBOSITY=2 + +KUBERNETES_CLUSTER_NAME="pilotstaging" +KUBERNETES_CLUSTER_ZONE="us-west1-a" +CLUSTER_DOMAIN_NAME="celo-testnet" + +TESTNET_PROJECT_NAME="celo-testnet" + +BLOCKSCOUT_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/blockscout" +BLOCKSCOUT_WEB_DOCKER_IMAGE_TAG="web-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" +BLOCKSCOUT_INDEXER_DOCKER_IMAGE_TAG="indexer-f6c3e0888d1d0ef72dc8bf870808702b7fd13730" +BLOCKSCOUT_WEB_REPLICAS=3 +BLOCKSCOUT_DB_SUFFIX="16" +BLOCKSCOUT_SUBNETWORK_NAME="Pilot Staging" + +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="58a4453c22504e3367a4d95177320acda4c5b061" + +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="58a4453c22504e3367a4d95177320acda4c5b061" + +CELOTOOL_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" +CELOTOOL_DOCKER_IMAGE_TAG="celotool-2616309a839a30e53faecfafb9b68ab51a5fcdcf" + +GETH_EXPORTER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet-production/geth-exporter" +GETH_EXPORTER_DOCKER_IMAGE_TAG="ed7d21bd50592709173368cd697ef73c1774a261" + +# Genesis Vars +NETWORK_ID=1101 +CONSENSUS_TYPE="istanbul" +PREDEPLOYED_CONTRACTS="REGISTRY" +BLOCK_TIME=5 +EPOCH=17280 // Minimum epoch length is 1 day + +# "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=10 +TX_NODES=2 +STATIC_IPS_FOR_GETH_NODES=false + +ADMIN_RPC_ENABLED=false + +# Testnet vars +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 + +VERIFICATION_POOL_URL="https://us-central1-celo-testnet.cloudfunctions.net/handleVerificationRequestpilotstaging/v0.1/sms/" +VERIFICATION_REWARDS_URL="https://us-central1-celo-testnet.cloudfunctions.net/handleVerificationRequestpilotstaging/v0.1/rewards/" + +STACKDRIVER_MONITORING_DASHBOARD="https://app.google.stackdriver.com/dashboards/17701013576385040071?project=celo-testnet" +STACKDRIVER_NOTIFICATION_CHANNEL="12047595356119796119" +MOBILE_WALLET_PLAYSTORE_LINK="https://play.google.com/apps/internaltest/4700990475000634666" + +NOTIFICATION_SERVICE_FIREBASE_DB="https://console.firebase.google.com/u/0/project/celo-org-mobile/database/celo-org-mobile-int/data" + +PROMTOSD_SCRAPE_INTERVAL="5m" +PROMTOSD_EXPORT_INTERVAL="5m" + +AUCTION_CRON_SPEC="*/5 * * * *" + +SMS_RETRIEVER_HASH_CODE=l5k6LvdPDXS diff --git a/packages/blockchain-api/tslint.json b/packages/blockchain-api/tslint.json index 1d79f0e2dc3..18f73fefbbd 100644 --- a/packages/blockchain-api/tslint.json +++ b/packages/blockchain-api/tslint.json @@ -1,3 +1,6 @@ { - "extends": ["@celo/typescript/tslint.json"] + "extends": ["@celo/typescript/tslint.json"], + "rules": { + "no-floating-promises": true + } } diff --git a/packages/celotool/geth_tests/governance_tests.ts b/packages/celotool/geth_tests/governance_tests.ts index 8161e0fefef..b38eccc3a52 100644 --- a/packages/celotool/geth_tests/governance_tests.ts +++ b/packages/celotool/geth_tests/governance_tests.ts @@ -4,9 +4,9 @@ import { getHooks, importGenesis, initAndStartGeth, - sleep, } from '@celo/celotool/geth_tests/src/lib/utils' import BigNumber from 'bignumber.js' +import { strip0x } from '../src/lib/utils' const assert = require('chai').assert const Web3 = require('web3') @@ -119,7 +119,7 @@ describe('governance tests', () => { const getParsedSignatureOfAddress = async (address: string, signer: string, signerWeb3: any) => { // @ts-ignore const hash = signerWeb3.utils.soliditySha3({ type: 'address', value: address }) - const signature = (await signerWeb3.eth.sign(hash, signer)).slice(2) + const signature = strip0x(await signerWeb3.eth.sign(hash, signer)) return { r: `0x${signature.slice(0, 64)}`, s: `0x${signature.slice(64, 128)}`, @@ -141,17 +141,17 @@ describe('governance tests', () => { return await tx.send({ from: account, ...txOptions, gasPrice, gas }) } - const redeemRewards = async (account: string, txOptions: any = {}) => { - await unlockAccount(account, web3) - const tx = bondedDeposits.methods.redeemRewards() - let gas = txOptions.gas - // We overestimate to account for variations in the fraction reduction necessary to redeem - // rewards. - if (!gas) { - gas = 2 * (await tx.estimateGas({ ...txOptions })) - } - return await tx.send({ from: account, ...txOptions, gasPrice, gas }) - } + // const redeemRewards = async (account: string, txOptions: any = {}) => { + // await unlockAccount(account, web3) + // const tx = bondedDeposits.methods.redeemRewards() + // let gas = txOptions.gas + // // We overestimate to account for variations in the fraction reduction necessary to redeem + // // rewards. + // if (!gas) { + // gas = 2 * (await tx.estimateGas({ ...txOptions })) + // } + // return await tx.send({ from: account, ...txOptions, gasPrice, gas }) + // } describe('when a bonded deposit account with weight exists', () => { const account = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95' @@ -174,12 +174,12 @@ describe('governance tests', () => { await delegateRewards(account, delegate) }) - it('should be able to redeem block rewards', async function(this: any) { - this.timeout(0) // Disable test timeout - await sleep(1) - await redeemRewards(account) - assert.isAtLeast(await web3.eth.getBalance(delegate), 1) - }) + // it('should be able to redeem block rewards', async function(this: any) { + // this.timeout(0) // Disable test timeout + // await sleep(1) + // await redeemRewards(account) + // assert.isAtLeast(await web3.eth.getBalance(delegate), 1) + // }) }) describe('when adding any block', () => { diff --git a/packages/celotool/geth_tests/src/lib/utils.ts b/packages/celotool/geth_tests/src/lib/utils.ts index 8ff9c5672bb..fd0717393f8 100644 --- a/packages/celotool/geth_tests/src/lib/utils.ts +++ b/packages/celotool/geth_tests/src/lib/utils.ts @@ -1,16 +1,17 @@ -const assert = require('chai').assert -const fs = require('fs') import { AccountType, ConsensusType, generateGenesis, - generatePrivateKey, - generatePublicKeyFromPrivateKey, - getValidators, + getPrivateKeysFor, + privateKeyToPublicKey, + privateKeyToStrippedAddress, } from '@celo/celotool/src/lib/generate_utils' import { getEnodeAddress } from '@celo/celotool/src/lib/geth' -import { spawn } from 'child_process' -import path from 'path' +import { ensure0x } from '@celo/celotool/src/lib/utils' +import { assert } from 'chai' +import { spawn, SpawnOptions } from 'child_process' +import fs from 'fs' +import { join as joinPath, resolve as resolvePath } from 'path' import { Admin } from 'web3-eth-admin' interface GethInstanceConfig { @@ -31,25 +32,34 @@ interface GethTestConfig { instances: GethInstanceConfig[] } -const testDir = '/tmp/e2e' -const genesisPath = `${testDir}/genesis.json` +const TEST_DIR = '/tmp/e2e' +const GENESIS_PATH = `${TEST_DIR}/genesis.json` const networkid = 1101 -export function execCmd(cmd: string, args: string[], options: any = {}, logsFilepath: string = '') { - return new Promise(async (resolve, reject) => { - console.debug('$ ' + [cmd].concat(args).join(' ')) - let process - if (!logsFilepath) { - process = spawn(cmd, args, { ...options, stdio: 'inherit' }) - } else { - try { - fs.unlinkSync(logsFilepath) - } catch (error) {} - const logStream = fs.createWriteStream(logsFilepath, { flags: 'a' }) - process = spawn(cmd, args, { ...options }) - process.stdout.pipe(logStream) - process.stderr.pipe(logStream) +export function spawnWithLog(cmd: string, args: string[], logsFilepath: string) { + try { + fs.unlinkSync(logsFilepath) + } catch (error) { + // nothing to do + } + const logStream = fs.createWriteStream(logsFilepath, { flags: 'a' }) + const process = spawn(cmd, args) + process.stdout.pipe(logStream) + process.stderr.pipe(logStream) + return process +} + +export function execCmd( + cmd: string, + args: string[], + options?: SpawnOptions & { silent?: boolean } +) { + return new Promise(async (resolve, reject) => { + const { silent, ...spawnOptions } = options || { silent: false } + if (!silent) { + console.debug('$ ' + [cmd].concat(args).join(' ')) } + const process = spawn(cmd, args, { ...spawnOptions, stdio: silent ? 'ignore' : 'inherit' }) process.on('close', (code) => { try { resolve(code) @@ -64,18 +74,12 @@ export function execCmd(cmd: string, args: string[], options: any = {}, logsFile export async function execCmdWithExitOnFailure( cmd: string, args: string[], - options: any = {}, - logsFilepath: string = '' + options?: SpawnOptions & { silent?: boolean } ) { - return new Promise(async (resolve, reject) => { - const code = await execCmd(cmd, args, options, logsFilepath) - if (code == 0) { - resolve() - } else { - process.exit(1) - reject() - } - }) + const code = await execCmd(cmd, args, options) + if (code !== 0) { + process.exit(1) + } } // TODO(asa): Use the contract kit here instead @@ -141,9 +145,9 @@ export const erc20Abi = [ }, ] -export const monorepoRoot = path.resolve(process.cwd(), './../..') +export const monorepoRoot = resolvePath(process.cwd(), './../..') -export async function checkoutGethRepo(branch: string, path: string) { +async function checkoutGethRepo(branch: string, path: string) { await execCmdWithExitOnFailure('rm', ['-rf', path]) await execCmdWithExitOnFailure('git', [ 'clone', @@ -157,16 +161,16 @@ export async function checkoutGethRepo(branch: string, path: string) { await execCmdWithExitOnFailure('git', ['checkout', branch], { cwd: path }) } -export async function buildGeth(path: string) { +async function buildGeth(path: string) { await execCmdWithExitOnFailure('make', ['geth'], { cwd: path }) } -export async function setupTestDir(testDir: string) { +async function setupTestDir(testDir: string) { await execCmd('rm', ['-rf', testDir]) await execCmd('mkdir', [testDir]) } -export async function writeGenesis(validators: string[], path: string) { +function writeGenesis(validators: string[], path: string) { const blockTime = 0 const epochLength = 10 const genesis = generateGenesis( @@ -180,40 +184,58 @@ export async function writeGenesis(validators: string[], path: string) { fs.writeFileSync(path, genesis) } -export async function importGenesis() { - return JSON.parse(fs.readFileSync(genesisPath)) +export function importGenesis() { + return JSON.parse(fs.readFileSync(GENESIS_PATH).toString()) } export async function init(gethBinaryPath: string, datadir: string, genesisPath: string) { - await execCmdWithExitOnFailure('rm', ['-rf', datadir]) - await execCmdWithExitOnFailure(gethBinaryPath, ['--datadir', datadir, 'init', genesisPath]) + await execCmdWithExitOnFailure('rm', ['-rf', datadir], { silent: true }) + await execCmdWithExitOnFailure(gethBinaryPath, ['--datadir', datadir, 'init', genesisPath], { + silent: true, + }) } export async function importPrivateKey(gethBinaryPath: string, instance: GethInstanceConfig) { const keyFile = '/tmp/key.txt' fs.writeFileSync(keyFile, instance.privateKey) - await execCmdWithExitOnFailure(gethBinaryPath, [ - 'account', - 'import', - '--datadir', - getDatadir(instance), - '--password', - '/dev/null', - keyFile, - ]) + console.info(`geth:${instance.name}: import account`) + await execCmdWithExitOnFailure( + gethBinaryPath, + ['account', 'import', '--datadir', getDatadir(instance), '--password', '/dev/null', keyFile], + { silent: true } + ) +} + +export async function killPid(pid: number) { + await execCmd('kill', ['-9', pid.toString()]) } export async function killGeth() { - await execCmd('pkill', ['-9', 'geth']) + console.info(`Killing ALL geth instances`) + await execCmd('pkill', ['-9', 'geth'], { silent: true }) } -export async function addStaticPeers(datadir: string, enodes: string[]) { +function addStaticPeers(datadir: string, enodes: string[]) { fs.writeFileSync(`${datadir}/static-nodes.json`, JSON.stringify(enodes)) } -// TODO(asa): Use sleep-promise -export async function sleep(seconds: number) { - await execCmd('sleep', [seconds.toString()]) +async function isPortOpen(host: string, port: number) { + return (await execCmd('nc', ['-z', host, port.toString()], { silent: true })) === 0 +} + +async function waitForPortOpen(host: string, port: number, seconds: number) { + while (seconds > 0) { + if (await isPortOpen(host, port)) { + return true + } + seconds -= 1 + await sleep(1) + } + return false +} + +export function sleep(seconds: number) { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)) } export async function getEnode(rpcPort: number) { @@ -223,11 +245,8 @@ export async function getEnode(rpcPort: number) { export async function startGeth(gethBinaryPath: string, instance: GethInstanceConfig) { const datadir = getDatadir(instance) - const syncmode = instance.syncmode - const port = instance.port - const rpcport = instance.rpcport + const { syncmode, port, rpcport, validating: mine } = instance const privateKey = instance.privateKey || '' - const mine = instance.validating const lightserv = instance.lightserv || false const unlock = instance.validating const etherbase = instance.etherbase || '' @@ -271,99 +290,105 @@ export async function startGeth(gethBinaryPath: string, instance: GethInstanceCo if (mine) { gethArgs.push('--mine', '--minerthreads=10', `--nodekeyhex=${privateKey}`) } - execCmd(gethBinaryPath, gethArgs, {}, `${datadir}/logs.txt`) + const gethProcess = spawnWithLog(gethBinaryPath, gethArgs, `${datadir}/logs.txt`) + // Give some time for geth to come up - await sleep(1) -} + const isOpen = await waitForPortOpen('localhost', rpcport, 5) + if (!isOpen) { + console.error(`geth:${instance.name}: jsonRPC didn't open after 5 seconds`) + process.exit(1) + } else { + console.info(`geth:${instance.name}: jsonRPC port open ${rpcport}`) + } -function add0x(str: string) { - return '0x' + str + return gethProcess.pid } export async function migrateContracts(validatorPrivateKeys: string[], to: number = 1000) { - let args = [ + const args = [ '--cwd', `${monorepoRoot}/packages/protocol`, 'init-network', '-n', 'testing', '-k', - validatorPrivateKeys.map(add0x).join(','), + validatorPrivateKeys.map(ensure0x).join(','), '-t', to.toString(), ] await execCmdWithExitOnFailure('yarn', args) } -export async function getContractAddress(contractName: string) { +export function getContractAddress(contractName: string) { const filePath = `${monorepoRoot}/packages/protocol/build/testing/contracts/${contractName}.json` - let contractData = JSON.parse(await fs.readFileSync(filePath, 'utf8')) + const contractData = JSON.parse(fs.readFileSync(filePath, 'utf8')) return contractData.networks[networkid].address } export async function snapshotDatadir(instance: GethInstanceConfig) { // Sometimes the socket is still present, preventing us from snapshotting. - await execCmd('rm', [`${getDatadir(instance)}/geth.ipc`]) + await execCmd('rm', [`${getDatadir(instance)}/geth.ipc`], { silent: true }) await execCmdWithExitOnFailure('cp', ['-r', getDatadir(instance), getSnapshotdir(instance)]) } export async function restoreDatadir(instance: GethInstanceConfig) { const datadir = getDatadir(instance) const snapshotdir = getSnapshotdir(instance) - await execCmdWithExitOnFailure('rm', ['-rf', datadir]) - await execCmdWithExitOnFailure('cp', ['-r', snapshotdir, datadir]) + console.info(`geth:${instance.name}: restore datadir: ${datadir}`) + await execCmdWithExitOnFailure('rm', ['-rf', datadir], { silent: true }) + await execCmdWithExitOnFailure('cp', ['-r', snapshotdir, datadir], { silent: true }) } -export function getInstanceDir(instance: GethInstanceConfig) { - return `${testDir}/${instance.name}` +function getInstanceDir(instance: GethInstanceConfig) { + return joinPath(TEST_DIR, instance.name) } -export function getDatadir(instance: GethInstanceConfig) { - const instanceDir = `${testDir}/${instance.name}` - return `${instanceDir}/datadir` +function getDatadir(instance: GethInstanceConfig) { + return joinPath(getInstanceDir(instance), 'datadir') } -export function getSnapshotdir(instance: GethInstanceConfig) { - const instanceDir = `${testDir}/${instance.name}` - return `${instanceDir}/snapshot` +function getSnapshotdir(instance: GethInstanceConfig) { + return joinPath(getInstanceDir(instance), 'snapshot') } +/** + * @returns Promise the geth pid number + */ export async function initAndStartGeth(gethBinaryPath: string, instance: GethInstanceConfig) { const datadir = getDatadir(instance) - await init(gethBinaryPath, datadir, genesisPath) + console.info(`geth:${instance.name}: init datadir ${datadir}`) + await init(gethBinaryPath, datadir, GENESIS_PATH) if (instance.privateKey) { await importPrivateKey(gethBinaryPath, instance) } if (instance.peers) { await addStaticPeers(datadir, instance.peers) } - await startGeth(gethBinaryPath, instance) + return startGeth(gethBinaryPath, instance) } export function getHooks(gethConfig: GethTestConfig) { const mnemonic = 'jazz ripple brown cloth door bridge pen danger deer thumb cable prepare negative library vast' - const validatorInstances = gethConfig.instances.filter((x: any) => x.validating == true) + const validatorInstances = gethConfig.instances.filter((x: any) => x.validating) const numValidators = validatorInstances.length - const validators = getValidators(mnemonic, numValidators) - const validatorPrivateKeys = validators.map((_: any, i: number) => - generatePrivateKey(mnemonic, AccountType.VALIDATOR, i) - ) + const validatorPrivateKeys = getPrivateKeysFor(AccountType.VALIDATOR, mnemonic, numValidators) + const validators = validatorPrivateKeys.map(privateKeyToStrippedAddress) const validatorEnodes = validatorPrivateKeys.map((x: any, i: number) => - getEnodeAddress(generatePublicKeyFromPrivateKey(x), '127.0.0.1', validatorInstances[i].port) + getEnodeAddress(privateKeyToPublicKey(x), '127.0.0.1', validatorInstances[i].port) ) const argv = require('minimist')(process.argv.slice(2)) const branch = argv.branch || 'master' const gethRepoPath = argv.localgeth || '/tmp/geth' const gethBinaryPath = `${gethRepoPath}/build/bin/geth` - const before = async function(this: any) { + const before = async () => { if (!argv.localgeth) { await checkoutGethRepo(branch, gethRepoPath) } await buildGeth(gethRepoPath) - await setupTestDir(testDir) - await writeGenesis(validators, genesisPath) + await setupTestDir(TEST_DIR) + await writeGenesis(validators, GENESIS_PATH) let validatorIndex = 0 for (const instance of gethConfig.instances) { if (instance.validating) { @@ -372,7 +397,7 @@ export function getHooks(gethConfig: GethTestConfig) { } // Automatically connect validator nodes to eachother. instance.peers = instance.peers.concat( - validatorEnodes.filter((_: string, i: number) => i != validatorIndex) + validatorEnodes.filter((_: string, i: number) => i !== validatorIndex) ) if (!instance.privateKey) { instance.privateKey = validatorPrivateKeys[validatorIndex] @@ -408,9 +433,7 @@ export function getHooks(gethConfig: GethTestConfig) { } } - const after = async () => { - await killGeth() - } + const after = () => killGeth() return { before, after, restart, gethBinaryPath } } diff --git a/packages/celotool/geth_tests/sync_tests.ts b/packages/celotool/geth_tests/sync_tests.ts index 31381dba5cd..48e96444b60 100644 --- a/packages/celotool/geth_tests/sync_tests.ts +++ b/packages/celotool/geth_tests/sync_tests.ts @@ -1,13 +1,16 @@ -const assert = require('chai').assert -const Web3 = require('web3') import { - getHooks, getEnode, + getHooks, initAndStartGeth, + killPid, sleep, } from '@celo/celotool/geth_tests/src/lib/utils' +import { assert } from 'chai' +import Web3 from 'web3' + +describe('sync tests', function(this: any) { + this.timeout(0) -describe('sync tests', () => { const gethConfig = { migrate: true, instances: [ @@ -19,8 +22,7 @@ describe('sync tests', () => { } const hooks = getHooks(gethConfig) - before(async function(this: any) { - this.timeout(0) + before(async () => { // Start validator nodes and migrate contracts. await hooks.before() // Restart validator nodes. @@ -42,45 +44,43 @@ describe('sync tests', () => { after(hooks.after) - const beforeEachHook = async (test: any, syncmode: string) => { - test.timeout(0) - const syncInstance = { - name: syncmode, - validating: false, - syncmode: syncmode, - port: 30313, - rpcport: 8555, - lightserv: syncmode == 'light' || syncmode == 'ultralight' ? false : true, - peers: [await getEnode(8553)], - } - await initAndStartGeth(hooks.gethBinaryPath, syncInstance) - } - - const syncTest = async (test: any) => { - test.timeout(0) - const validatingWeb3 = new Web3(`http://localhost:8545`) - const validatingFirstBlock = await validatingWeb3.eth.getBlock('latest') - await sleep(20) - const validatingLatestBlock = await validatingWeb3.eth.getBlock('latest') - await sleep(3) - const syncWeb3 = new Web3(`http://localhost:8555`) - const syncLatestBlock = await syncWeb3.eth.getBlock('latest') - assert.isAbove(validatingLatestBlock.number, 1) - // Assert that the validator is still producing blocks. - assert.isAbove(validatingLatestBlock.number, validatingFirstBlock.number) - // Assert that the syncing node has synced with the validator. - assert.isAtLeast(syncLatestBlock.number, validatingLatestBlock.number) - } - const syncModes = ['full', 'fast', 'light', 'ultralight'] - for (const syncMode of syncModes) { - describe(`when syncing with a ${syncMode} node`, () => { - beforeEach(async function(this: any) { - await beforeEachHook(this, syncMode) + for (const syncmode of syncModes) { + describe(`when syncing with a ${syncmode} node`, () => { + let gethPid: number | null = null + beforeEach(async () => { + const syncInstance = { + name: syncmode, + validating: false, + syncmode, + port: 30313, + rpcport: 8555, + lightserv: syncmode !== 'light' && syncmode !== 'ultralight', + peers: [await getEnode(8553)], + } + gethPid = await initAndStartGeth(hooks.gethBinaryPath, syncInstance) + }) + + afterEach(() => { + if (gethPid) { + killPid(gethPid) + gethPid = null + } }) - it('should sync the latest block', async function(this: any) { - await syncTest(this) + it('should sync the latest block', async () => { + const validatingWeb3 = new Web3(`http://localhost:8545`) + const validatingFirstBlock = await validatingWeb3.eth.getBlock('latest') + await sleep(20) + const validatingLatestBlock = await validatingWeb3.eth.getBlock('latest') + await sleep(3) + const syncWeb3 = new Web3(`http://localhost:8555`) + const syncLatestBlock = await syncWeb3.eth.getBlock('latest') + assert.isAbove(validatingLatestBlock.number, 1) + // Assert that the validator is still producing blocks. + assert.isAbove(validatingLatestBlock.number, validatingFirstBlock.number) + // Assert that the syncing node has synced with the validator. + assert.isAtLeast(syncLatestBlock.number, validatingLatestBlock.number) }) }) } diff --git a/packages/celotool/geth_tests/transfer_tests.ts b/packages/celotool/geth_tests/transfer_tests.ts index 47511965103..3cc314b7cfd 100644 --- a/packages/celotool/geth_tests/transfer_tests.ts +++ b/packages/celotool/geth_tests/transfer_tests.ts @@ -1,16 +1,16 @@ import { + erc20Abi, getContractAddress, getEnode, getHooks, initAndStartGeth, - erc20Abi, sleep, } from '@celo/celotool/geth_tests/src/lib/utils' import { CURRENCY_ENUM } from '@celo/utils' import BigNumber from 'bignumber.js' +import { assert } from 'chai' import Web3 from 'web3' import { Tx } from 'web3/eth/types' -const assert = require('chai').assert const stableTokenAbi = erc20Abi.concat([ { @@ -128,7 +128,9 @@ const registryAbi = [ }, ] -describe('transfer tests', () => { +describe('transfer tests', function(this: any) { + this.timeout(0) + const gethConfig = { migrateTo: 8, migrateGovernance: false, @@ -137,16 +139,11 @@ describe('transfer tests', () => { ], } const hooks = getHooks(gethConfig) - - before(async function(this: any) { - this.timeout(0) - await hooks.before() - }) - + before(hooks.before) after(hooks.after) - let web3: any - let amount: BigNumber + let web3: Web3 + const DEF_AMOUNT: BigNumber = new BigNumber(Web3.utils.toWei('1', 'ether')) let stableToken: any let gasPriceMinimum: any let initialBalances: any @@ -155,14 +152,16 @@ describe('transfer tests', () => { let txSuccess: boolean let stableTokenAddress: string let gasPriceMinimumAddress: string - let expectedInfrastructureBlockReward: string + const expectedInfrastructureBlockReward: string = new BigNumber( + Web3.utils.toWei('1', 'ether') + ).toString() const validatorAddress = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95' - const fromPrivateKey = 'f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d' - const fromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631' + const DEF_FROM_PK = 'f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d' + const DEF_FROM_ADDR = '0x5409ed021d9299bf6814279a6a1411a7e866a631' // Arbitrary addresses. - const toAddress = '0xbBae99F0E1EE565404465638d40827b54D343638' + const DEF_TO_ADDR = '0xbBae99F0E1EE565404465638d40827b54D343638' const feeRecipientAddress = '0x4f5f8a3f45d179553e7b95119ce296010f50f6f1' const governanceAddress = '0x1a748f924e5b346d68b2202e85ba6a2c72570b26' @@ -170,6 +169,8 @@ describe('transfer tests', () => { // Restart the validator node await hooks.restart() + // TODO(mcortesi): magic sleep. without it unlockAccount sometimes fails + await sleep(2) web3 = new Web3('http://localhost:8545') await unlockAccount(validatorAddress) // We do not deploy the governance contract so that we can set inflation parameters on @@ -185,13 +186,11 @@ describe('transfer tests', () => { // TODO(asa): Move this to the `before` // Give the account we will send transfers as sufficient gold and dollars. stableTokenAddress = await getContractAddress('StableTokenProxy') - amount = new BigNumber(web3.utils.toWei('1', 'ether')) - expectedInfrastructureBlockReward = new BigNumber(web3.utils.toWei('1', 'ether')).toString() - const startBalance = amount.times(10) + const startBalance = DEF_AMOUNT.times(10) stableToken = new web3.eth.Contract(stableTokenAbi, stableTokenAddress) - await transferCeloDollars(validatorAddress, fromAddress, startBalance) - await transferCeloGold(validatorAddress, fromAddress, startBalance) + await transferCeloDollars(validatorAddress, DEF_FROM_ADDR, startBalance) + await transferCeloGold(validatorAddress, DEF_FROM_ADDR, startBalance) // Spin up a node that we can sync with. const fullInstance = { @@ -212,10 +211,10 @@ describe('transfer tests', () => { const syncInstance = { name: syncmode, validating: false, - syncmode: syncmode, + syncmode, port: 30307, rpcport: 8549, - privateKey: fromPrivateKey, + privateKey: DEF_FROM_PK, peers: [await getEnode(8547)], } await initAndStartGeth(hooks.gethBinaryPath, syncInstance) @@ -242,22 +241,22 @@ describe('transfer tests', () => { amount: BigNumber, txOptions: any = {} ) => { + await unlockAccount(fromAddress) + // Hack to get the node to suggest a price for us. + // Otherwise, web3 will suggest the default gold price. + if (txOptions.gasCurrency) { + txOptions.gasPrice = '0' + } + const tx: Tx = { + from: fromAddress, + to: toAddress, + value: amount.toString(), + ...txOptions, + } + if (!tx.gas) { + tx.gas = await web3.eth.estimateGas(tx) + } return new Promise(async (resolve, reject) => { - await unlockAccount(fromAddress) - // Hack to get the node to suggest a price for us. - // Otherwise, web3 will suggest the default gold price. - if (txOptions.gasCurrency) { - txOptions.gasPrice = '0' - } - const tx: Tx = { - from: fromAddress, - to: toAddress, - value: amount.toString(), - ...txOptions, - } - if (!tx.gas) { - tx.gas = await web3.eth.estimateGas(tx) - } try { await web3.eth .sendTransaction(tx) @@ -274,19 +273,19 @@ describe('transfer tests', () => { amount: BigNumber, txOptions: any = {} ) => { - return new Promise(async (resolve, reject) => { - await unlockAccount(fromAddress) - // Hack to get the node to suggest a price for us. - // Otherwise, web3 will suggest the default gold price. - if (txOptions.gasCurrency) { - txOptions.gasPrice = '0' - } - const tx = stableToken.methods.transfer(toAddress, amount.toString()) - let gas = txOptions.gas - if (!gas) { - gas = await tx.estimateGas({ ...txOptions }) - } + await unlockAccount(fromAddress) + // Hack to get the node to suggest a price for us. + // Otherwise, web3 will suggest the default gold price. + if (txOptions.gasCurrency) { + txOptions.gasPrice = '0' + } + const tx = stableToken.methods.transfer(toAddress, amount.toString()) + let gas = txOptions.gas + if (!gas) { + gas = await tx.estimateGas({ ...txOptions }) + } + return new Promise(async (resolve, reject) => { try { await tx .send({ from: fromAddress, ...txOptions, gas }) @@ -312,18 +311,18 @@ describe('transfer tests', () => { updatePeriod ) const gas = await tx.estimateGas({ from: validatorAddress }) - return await tx.send({ from: validatorAddress, gas }) + return tx.send({ from: validatorAddress, gas }) } const getBalances = async () => { - const accounts = [fromAddress, toAddress, governanceAddress, feeRecipientAddress] + const accounts = [DEF_FROM_ADDR, DEF_TO_ADDR, governanceAddress, feeRecipientAddress] const goldBalances: any = {} const dollarBalances: any = {} for (const a of accounts) { goldBalances[a] = new BigNumber(await web3.eth.getBalance(a)) dollarBalances[a] = new BigNumber(await stableToken.methods.balanceOf(a).call()) } - let balances: any = {} + const balances: any = {} balances[CURRENCY_ENUM.GOLD] = goldBalances balances[CURRENCY_ENUM.DOLLAR] = dollarBalances return balances @@ -331,9 +330,9 @@ describe('transfer tests', () => { const getGasPriceMinimum = async (gasCurrency: string | undefined) => { if (gasCurrency) { - return await gasPriceMinimum.methods.getGasPriceMinimum(gasCurrency).call() + return gasPriceMinimum.methods.getGasPriceMinimum(gasCurrency).call() } else { - return await gasPriceMinimum.methods.gasPriceMinimum().call() + return gasPriceMinimum.methods.gasPriceMinimum().call() } } @@ -342,17 +341,17 @@ describe('transfer tests', () => { expectedGasUsed: number, gasCurrency?: string ): Promise<[boolean, any, any]> => { - const gasPriceMinimum = await getGasPriceMinimum(gasCurrency) - assert.isAbove(gasPriceMinimum, 0) + const minGasPrice = await getGasPriceMinimum(gasCurrency) + assert.isAbove(minGasPrice, 0) const receipt = await txPromise - const newBalances = await getBalances() + const balances = await getBalances() const tx = await web3.eth.getTransaction(receipt.transactionHash) const gasPrice = tx.gasPrice - assert.isAbove(gasPrice, 0) + assert.isAbove(parseInt(gasPrice, 10), 0) const expectedTransactionFee = new BigNumber(expectedGasUsed).times(gasPrice) const expectedInfrastructureFeeFraction = 0.5 const expectedTransactionFeeToInfrastructure = new BigNumber(expectedGasUsed) - .times(gasPriceMinimum) + .times(minGasPrice) .times(expectedInfrastructureFeeFraction) const expectedTransactionFeeToRecipient = expectedTransactionFee.minus( expectedTransactionFeeToInfrastructure @@ -362,7 +361,7 @@ describe('transfer tests', () => { infrastructure: expectedTransactionFeeToInfrastructure, recipient: expectedTransactionFeeToRecipient, } - return [receipt.status, newBalances, fees] + return [receipt.status, balances, fees] } const assertBalances = ( @@ -380,22 +379,22 @@ describe('transfer tests', () => { }) } if (expectSuccess) { - if (transferToken != feeToken) { + if (transferToken !== feeToken) { it(`should decrement the sender's ${transferToken} balance by the transfer amount`, () => { assert.equal( - initialBalances[transferToken][fromAddress] - .minus(newBalances[transferToken][fromAddress]) - .toString(), - amount.toString() + initialBalances[transferToken][DEF_FROM_ADDR].minus( + newBalances[transferToken][DEF_FROM_ADDR] + ).toString(), + DEF_AMOUNT.toString() ) }) } else { it(`should decrement the sender's ${transferToken} balance by the transfer amount plus the gas fee`, () => { - const expectedBalanceChange = expectedFees.total.plus(amount) + const expectedBalanceChange = expectedFees.total.plus(DEF_AMOUNT) assert.equal( - initialBalances[transferToken][fromAddress] - .minus(newBalances[transferToken][fromAddress]) - .toString(), + initialBalances[transferToken][DEF_FROM_ADDR].minus( + newBalances[transferToken][DEF_FROM_ADDR] + ).toString(), expectedBalanceChange.toString() ) }) @@ -403,34 +402,34 @@ describe('transfer tests', () => { it(`should increment the receiver's ${transferToken} balance by the transfer amount`, () => { assert.equal( - newBalances[transferToken][toAddress] - .minus(initialBalances[transferToken][toAddress]) - .toString(), - amount.toString() + newBalances[transferToken][DEF_TO_ADDR].minus( + initialBalances[transferToken][DEF_TO_ADDR] + ).toString(), + DEF_AMOUNT.toString() ) }) - } else if (transferToken != feeToken) { + } else if (transferToken !== feeToken) { it(`should not change the sender's ${transferToken} balance`, () => { assert.equal( - initialBalances[transferToken][fromAddress].toString(), - newBalances[transferToken][fromAddress].toString() + initialBalances[transferToken][DEF_FROM_ADDR].toString(), + newBalances[transferToken][DEF_FROM_ADDR].toString() ) }) it(`should not change the receiver's ${transferToken} balance`, () => { assert.equal( - initialBalances[transferToken][toAddress].toString(), - newBalances[transferToken][toAddress].toString() + initialBalances[transferToken][DEF_TO_ADDR].toString(), + newBalances[transferToken][DEF_TO_ADDR].toString() ) }) } - if (!expectSuccess || transferToken != feeToken) { + if (!expectSuccess || transferToken !== feeToken) { it(`should decrement the sender's ${transferToken} balance by the gas fee`, () => { assert.equal( - initialBalances[feeToken][fromAddress] - .minus(newBalances[feeToken][fromAddress]) - .toString(), + initialBalances[feeToken][DEF_FROM_ADDR].minus( + newBalances[feeToken][DEF_FROM_ADDR] + ).toString(), expectedFees.total.toString() ) }) @@ -462,14 +461,13 @@ describe('transfer tests', () => { describe(`when running ${syncMode} sync`, () => { describe('when transferring Celo Gold', () => { describe('when paying for gas in Celo Gold', () => { - if (syncMode == 'light' || syncMode == 'ultralight') { + if (syncMode === 'light' || syncMode === 'ultralight') { describe('when running in light/ultralight sync mode', () => { describe('when not explicitly specifying a gas fee recipient', () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloGold(fromAddress, toAddress, amount), + transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT), GOLD_TRANSACTION_GAS_COST ) }) @@ -480,10 +478,9 @@ describe('transfer tests', () => { describe('when explicitly specifying the gas fee recipient', () => { describe("when using a peer's etherbase", () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloGold(fromAddress, toAddress, amount, { + transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gasFeeRecipient: feeRecipientAddress, }), GOLD_TRANSACTION_GAS_COST @@ -495,10 +492,9 @@ describe('transfer tests', () => { describe('when setting to an arbitrary address', () => { it('should get rejected by the sending node before being added to the tx pool', async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) try { - await transferCeloGold(fromAddress, toAddress, amount, { + await transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gasFeeRecipient: web3.utils.randomHex(20), }) } catch (error) { @@ -513,10 +509,9 @@ describe('transfer tests', () => { }) } else { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloGold(fromAddress, toAddress, amount, { + transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gasFeeRecipient: feeRecipientAddress, }), GOLD_TRANSACTION_GAS_COST @@ -532,12 +527,11 @@ describe('transfer tests', () => { describe('when there is no demurrage', () => { describe('when setting a gas amount greater than the amount of gas necessary', () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) const expectedGasUsed = 158511 ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloGold(fromAddress, toAddress, amount, { + transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gasCurrency: stableTokenAddress, gasFeeRecipient: feeRecipientAddress, }), @@ -550,11 +544,10 @@ describe('transfer tests', () => { describe('when setting a gas amount less than the amount of gas necessary but more than the intrinsic gas amount', () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) const gas = intrinsicGas + 1000 ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloGold(fromAddress, toAddress, amount, { + transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gas, gasCurrency: stableTokenAddress, gasFeeRecipient: feeRecipientAddress, @@ -569,11 +562,10 @@ describe('transfer tests', () => { describe('when setting a gas amount less than the intrinsic gas amount', () => { it('should not add the transaction to the pool', async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) const gas = intrinsicGas - 1 try { - await transferCeloGold(fromAddress, toAddress, amount, { + await transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gas, gasCurrency: stableTokenAddress, }) @@ -587,7 +579,6 @@ describe('transfer tests', () => { describe('when there is demurrage of 50% applied', () => { describe('when setting a gas amount greater than the amount of gas necessary', () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) // To avoid a scenario where large numbers of retroactive updates occur, @@ -605,7 +596,7 @@ describe('transfer tests', () => { const expectedGasUsed = 158511 ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloGold(fromAddress, toAddress, amount, { + transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gasCurrency: stableTokenAddress, gasFeeRecipient: feeRecipientAddress, }), @@ -617,27 +608,26 @@ describe('transfer tests', () => { it("should decrement the sender's Celo Gold balance by the transfer amount", () => { assert.equal( - initialBalances[CURRENCY_ENUM.GOLD][fromAddress] - .minus(newBalances[CURRENCY_ENUM.GOLD][fromAddress]) - .toString(), - amount.toString() + initialBalances[CURRENCY_ENUM.GOLD][DEF_FROM_ADDR].minus( + newBalances[CURRENCY_ENUM.GOLD][DEF_FROM_ADDR] + ).toString(), + DEF_AMOUNT.toString() ) }) it("should increment the receiver's Celo Gold balance by the transfer amount", () => { assert.equal( - newBalances[CURRENCY_ENUM.GOLD][toAddress] - .minus(initialBalances[CURRENCY_ENUM.GOLD][toAddress]) - .toString(), - amount.toString() + newBalances[CURRENCY_ENUM.GOLD][DEF_TO_ADDR].minus( + initialBalances[CURRENCY_ENUM.GOLD][DEF_TO_ADDR] + ).toString(), + DEF_AMOUNT.toString() ) }) it("should halve the sender's Celo Dollar balance due to demurrage and decrement it by the gas fee", () => { assert.equal( - initialBalances[CURRENCY_ENUM.DOLLAR][fromAddress] - .div(2) - .minus(newBalances[CURRENCY_ENUM.DOLLAR][fromAddress]) + initialBalances[CURRENCY_ENUM.DOLLAR][DEF_FROM_ADDR].div(2) + .minus(newBalances[CURRENCY_ENUM.DOLLAR][DEF_FROM_ADDR]) .toString(), expectedFees.total.toString() ) @@ -664,7 +654,6 @@ describe('transfer tests', () => { describe('when setting a gas amount less than the amount of gas necessary but more than the intrinsic gas amount', () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) // To avoid a scenario where large numbers of retroactive updates occur, // set updatePeriod so that the exponent is limited to 1 by fetching when the @@ -681,7 +670,7 @@ describe('transfer tests', () => { const gas = intrinsicGas + 1000 ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloGold(fromAddress, toAddress, amount, { + transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gas, gasCurrency: stableTokenAddress, gasFeeRecipient: feeRecipientAddress, @@ -694,23 +683,22 @@ describe('transfer tests', () => { it("should not change the sender's Celo Gold balance", () => { assert.equal( - initialBalances[CURRENCY_ENUM.GOLD][fromAddress].toString(), - newBalances[CURRENCY_ENUM.GOLD][fromAddress].toString() + initialBalances[CURRENCY_ENUM.GOLD][DEF_FROM_ADDR].toString(), + newBalances[CURRENCY_ENUM.GOLD][DEF_FROM_ADDR].toString() ) }) it("should not change the receiver's Celo Gold balance", () => { assert.equal( - initialBalances[CURRENCY_ENUM.GOLD][toAddress].toString(), - newBalances[CURRENCY_ENUM.GOLD][toAddress].toString() + initialBalances[CURRENCY_ENUM.GOLD][DEF_TO_ADDR].toString(), + newBalances[CURRENCY_ENUM.GOLD][DEF_TO_ADDR].toString() ) }) it("should halve the sender's Celo Dollar balance due to demurrage and decrement it by the gas fee", () => { assert.equal( - initialBalances[CURRENCY_ENUM.DOLLAR][fromAddress] - .div(2) - .minus(newBalances[CURRENCY_ENUM.DOLLAR][fromAddress]) + initialBalances[CURRENCY_ENUM.DOLLAR][DEF_FROM_ADDR].div(2) + .minus(newBalances[CURRENCY_ENUM.DOLLAR][DEF_FROM_ADDR]) .toString(), expectedFees.total.toString() ) @@ -737,7 +725,6 @@ describe('transfer tests', () => { describe('when setting a gas amount less than the intrinsic gas amount', () => { it('should not add the transaction to the pool', async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) const gas = intrinsicGas - 1 @@ -754,7 +741,7 @@ describe('transfer tests', () => { await setInflationParams(2, 1, timeSinceLastUpdated.toNumber()) try { - await transferCeloGold(fromAddress, toAddress, amount, { + await transferCeloGold(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gas, gasCurrency: stableTokenAddress, }) @@ -770,12 +757,11 @@ describe('transfer tests', () => { describe('when transferring Celo Dollars', () => { describe('when paying for gas in Celo Dollars', () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) const expectedGasUsed = 190740 ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloDollars(fromAddress, toAddress, amount, { + transferCeloDollars(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gasCurrency: stableTokenAddress, gasFeeRecipient: feeRecipientAddress, }), @@ -788,12 +774,11 @@ describe('transfer tests', () => { describe('when paying for gas in Celo Gold', () => { before(async function(this: any) { - this.timeout(0) // Disable test timeout await restartGeth(syncMode) const expectedGasUsed = 55740 ;[txSuccess, newBalances, expectedFees] = await runTestTransaction( - transferCeloDollars(fromAddress, toAddress, amount, { + transferCeloDollars(DEF_FROM_ADDR, DEF_TO_ADDR, DEF_AMOUNT, { gasFeeRecipient: feeRecipientAddress, }), expectedGasUsed diff --git a/packages/celotool/src/cmds/account/list.ts b/packages/celotool/src/cmds/account/list.ts index 63c452fe47a..6c36d7414b4 100644 --- a/packages/celotool/src/cmds/account/list.ts +++ b/packages/celotool/src/cmds/account/list.ts @@ -113,7 +113,7 @@ async function getNonverificationTransactions(transactionUrl: string) { const resp = await fetch(transactionUrl) jsonResp = await resp.json() } catch (e) { - sleep(Math.random() * 5000) + await sleep(Math.random() * 5000) } } diff --git a/packages/celotool/src/cmds/deploy/initial/contracts.ts b/packages/celotool/src/cmds/deploy/initial/contracts.ts index b0ec0e3e538..90f533fa656 100644 --- a/packages/celotool/src/cmds/deploy/initial/contracts.ts +++ b/packages/celotool/src/cmds/deploy/initial/contracts.ts @@ -2,13 +2,13 @@ import { InitialArgv } from '@celo/celotool/src/cmds/deploy/initial' import { uploadArtifacts } from '@celo/celotool/src/lib/artifacts' import { portForwardAnd } from '@celo/celotool/src/lib/port_forward' -import { envVar, execCmd, fetchEnv } from '@celo/celotool/src/lib/utils' +import { ensure0x, envVar, execCmd, fetchEnv } from '@celo/celotool/src/lib/utils' import { switchToClusterFromEnv } from 'src/lib/cluster' import { AccountType, - generateAccountAddressFromPrivateKey, generatePrivateKey, - getValidatorsPrivateKeys, + getPrivateKeysFor, + privateKeyToAddress, } from 'src/lib/generate_utils' import { OG_ACCOUNTS } from 'src/lib/genesis_constants' @@ -20,25 +20,23 @@ export const builder = {} function minerForEnv() { if (fetchEnv(envVar.VALIDATORS) === 'og') { - return '0x' + OG_ACCOUNTS[0].address + return ensure0x(OG_ACCOUNTS[0].address) } else { - return generateAccountAddressFromPrivateKey( + return privateKeyToAddress( generatePrivateKey(fetchEnv(envVar.MNEMONIC), AccountType.VALIDATOR, 0) ) } } -function add0x(msg: string) { - return '0x' + msg -} function getValidatorKeys() { if (fetchEnv(envVar.VALIDATORS) === 'og') { - return OG_ACCOUNTS.map((account) => account.privateKey).map(add0x) + return OG_ACCOUNTS.map((account) => account.privateKey).map(ensure0x) } else { - return getValidatorsPrivateKeys( + return getPrivateKeysFor( + AccountType.VALIDATOR, fetchEnv(envVar.MNEMONIC), parseInt(fetchEnv(envVar.VALIDATORS), 10) - ).map(add0x) + ).map(ensure0x) } } diff --git a/packages/celotool/src/cmds/deploy/upgrade/testnet.ts b/packages/celotool/src/cmds/deploy/upgrade/testnet.ts index 6fcd527cd46..5d33b552228 100644 --- a/packages/celotool/src/cmds/deploy/upgrade/testnet.ts +++ b/packages/celotool/src/cmds/deploy/upgrade/testnet.ts @@ -19,7 +19,7 @@ export const builder = {} export const handler = async (argv: TestnetArgv) => { await switchToClusterFromEnv() - upgradeStaticIPs(argv.celoEnv) + await upgradeStaticIPs(argv.celoEnv) if (argv.reset) { await resetAndUpgradeHelmChart(argv.celoEnv) diff --git a/packages/celotool/src/cmds/gcp.ts b/packages/celotool/src/cmds/gcp.ts new file mode 100644 index 00000000000..d31afbc5fc1 --- /dev/null +++ b/packages/celotool/src/cmds/gcp.ts @@ -0,0 +1,7 @@ +import * as yargs from 'yargs' + +export const command = 'gcp ' + +export const describe = 'commands for interacting with GCP' + +export const builder = (argv: yargs.Argv) => argv.commandDir('gcp', { extensions: ['ts'] }) diff --git a/packages/celotool/src/cmds/gcp/remove-leaked-forwarding-rules.ts b/packages/celotool/src/cmds/gcp/remove-leaked-forwarding-rules.ts new file mode 100644 index 00000000000..ff99649d1d3 --- /dev/null +++ b/packages/celotool/src/cmds/gcp/remove-leaked-forwarding-rules.ts @@ -0,0 +1,124 @@ +import { execCmd, execCmdWithExitOnFailure } from '@celo/celotool/src/lib/utils' +import { zip } from 'lodash' +import * as yargs from 'yargs' + +export const command = 'remove-leaked-forwarding-rules' + +export const describe = 'Removes leaked forwarding rules that Kubernetes did not garbage collect' + +interface Argv extends yargs.Argv { + keywords: string + project: string +} + +export const builder = (argv: yargs.Argv) => { + return argv + .option('keywords', { + required: false, + default: '', + type: 'string', + description: 'comma-separated list of keywords when matched with the rule, should be deleted', + }) + .option('project', { + alias: 'p', + required: true, + type: 'string', + description: 'GCP project within which to run this command', + }) +} + +export const handler = async (argv: Argv) => { + console.info('Fetching forwarding-rules') + let rules: any[] = await execCmdWithExitOnFailure( + `gcloud compute forwarding-rules list --format=json --project=${argv.project}` + ).then(([body]) => JSON.parse(body)) + + const candidates = rules.filter((rule) => rule.target.includes('targetPools')) + + console.info('Determining health of rules') + const shouldDelete = await Promise.all( + candidates.map(async (rule) => { + const targetComponents = rule.target.split('/') + const zone = targetComponents[8] + const target = targetComponents[10] + + try { + await execCmd( + `gcloud compute target-pools get-health ${target} --region=${zone} --format=json --project=${ + argv.project + }`, + {}, + true + ) + return false + } catch ([error, stdout, stderr]) { + const healthyInstances = JSON.parse(stdout).length + return healthyInstances === 0 + } + }) + ) + + const candidatesToDelete = zip(candidates, shouldDelete).filter(([, x]) => x) + console.info( + `Should delete ${candidatesToDelete.length} forwarding-rules that don't have any targets` + ) + + await Promise.all( + candidatesToDelete.map(async ([candidate]) => { + const targetComponents = candidate.target.split('/') + const zone = targetComponents[8] + const target = targetComponents[10] + + console.info(`Deleting forwarding-rule ${candidate.name}`) + await execCmdWithExitOnFailure( + `gcloud compute forwarding-rules delete ${candidate.name} ${getRegionFlag( + candidate.selfLink + )} -q --project=${argv.project}` + ) + console.info(`Deleted forwarding-rule ${candidate.name}`) + + console.info(`Deleting target-pool ${target}`) + await execCmdWithExitOnFailure( + `gcloud compute target-pools delete ${target} --region=${zone} -q --project=${argv.project}` + ) + console.info(`Deleted target-pool ${target}`) + }) + ) + + if (argv.keywords.length === 0) { + console.info(`No keywords given`) + return + } + + const keywordsToMatch = argv.keywords.split(',') + + rules = await execCmdWithExitOnFailure( + `gcloud compute forwarding-rules list --format=json --project=${argv.project}` + ).then(([body]) => JSON.parse(body)) + + const matchingRules = rules.filter((lb) => + keywordsToMatch.some( + (keyword) => lb.description.includes(keyword) || lb.target.includes(keyword) + ) + ) + + await Promise.all( + matchingRules.map(async (rule) => { + console.info(`Deleting forwarding-rule ${rule.name}`) + await execCmdWithExitOnFailure( + `gcloud compute forwarding-rules delete ${rule.name} ${getRegionFlag( + rule.selfLink + )} -q --project=${argv.project}` + ) + console.info(`Deleted forwarding-rule ${rule.name}`) + }) + ) + + return +} + +function getRegionFlag(name: string) { + const parts = name.split('/') + const regionIndicator = parts[7] + return regionIndicator === 'global' ? '--global' : `--region=${parts[8]}` +} diff --git a/packages/celotool/src/cmds/generate/account-address.ts b/packages/celotool/src/cmds/generate/account-address.ts index 97be2fea427..66434bd2f03 100644 --- a/packages/celotool/src/cmds/generate/account-address.ts +++ b/packages/celotool/src/cmds/generate/account-address.ts @@ -1,5 +1,5 @@ /* tslint:disable no-console */ -import { generateAccountAddressFromPrivateKey } from 'src/lib/generate_utils' +import { privateKeyToAddress } from 'src/lib/generate_utils' import * as yargs from 'yargs' interface AccountAddressArgv { @@ -19,5 +19,5 @@ export const builder = (argv: yargs.Argv) => { } export const handler = async (argv: AccountAddressArgv) => { - console.log(generateAccountAddressFromPrivateKey(argv.privateKey)) + console.log(privateKeyToAddress(argv.privateKey)) } diff --git a/packages/celotool/src/cmds/generate/public-key.ts b/packages/celotool/src/cmds/generate/public-key.ts index 8d51f4d0d2a..c4bdd4aef6c 100644 --- a/packages/celotool/src/cmds/generate/public-key.ts +++ b/packages/celotool/src/cmds/generate/public-key.ts @@ -2,8 +2,8 @@ import { coerceMnemonicAccountType, generatePrivateKey, - generatePublicKeyFromPrivateKey, MNEMONIC_ACCOUNT_TYPE_CHOICES, + privateKeyToPublicKey, } from 'src/lib/generate_utils' import * as yargs from 'yargs' @@ -47,7 +47,7 @@ export const builder = (argv: yargs.Argv) => { */ export const handler = async (argv: Bip32Argv) => { console.log( - generatePublicKeyFromPrivateKey( + privateKeyToPublicKey( generatePrivateKey(argv.mnemonic, coerceMnemonicAccountType(argv.accountType), argv.index) ) ) diff --git a/packages/celotool/src/cmds/geth/get_gold_balance.ts b/packages/celotool/src/cmds/geth/get_gold_balance.ts index ff5831686d4..6a908a74b1b 100644 --- a/packages/celotool/src/cmds/geth/get_gold_balance.ts +++ b/packages/celotool/src/cmds/geth/get_gold_balance.ts @@ -2,6 +2,7 @@ import { GethArgv } from '@celo/celotool/src/cmds/geth' import { addCeloEnvMiddleware, addCeloGethMiddleware, + ensure0x, execCmdWithExitOnFailure, } from '@celo/celotool/src/lib/utils' import * as yargs from 'yargs' @@ -37,9 +38,7 @@ export const handler = async (argv: GetGoldBalanceArgv) => { // This return is required to prevent false lint errors in the code following this line return } - if (!account.startsWith('0x')) { - account = '0x' + account - } + account = ensure0x(account) if (account.length !== 42) { invalidArgumentExit(account, 'Account must be 40 hex-chars') } diff --git a/packages/celotool/src/cmds/geth/init.ts b/packages/celotool/src/cmds/geth/init.ts index 240fa94e2ea..e59a3a0217a 100644 --- a/packages/celotool/src/cmds/geth/init.ts +++ b/packages/celotool/src/cmds/geth/init.ts @@ -73,7 +73,7 @@ export const handler = async (argv: InitArgv) => { if (argv.fetchStaticNodesFromNetwork) { await switchToClusterFromEnv(false) - getEnodesAddresses(namespace).then((enodes) => { + await getEnodesAddresses(namespace).then((enodes) => { writeStaticNodes(enodes, datadir, STATIC_NODES_FILE_NAME) console.info(`Geth has been initialized successfully! 😎`) }) diff --git a/packages/celotool/src/cmds/geth/run.ts b/packages/celotool/src/cmds/geth/run.ts index ca1fb4865ef..9eb3cedd6ee 100644 --- a/packages/celotool/src/cmds/geth/run.ts +++ b/packages/celotool/src/cmds/geth/run.ts @@ -2,7 +2,7 @@ import { GethArgv } from '@celo/celotool/src/cmds/geth' import { spawnSync } from 'child_process' import fs from 'fs' import path from 'path' -import { addCeloGethMiddleware, validateAccountAddress } from 'src/lib/utils' +import { addCeloGethMiddleware, ensure0x, validateAccountAddress } from 'src/lib/utils' import * as yargs from 'yargs' const STATIC_NODES_FILE_NAME = 'static-nodes.json' @@ -75,12 +75,10 @@ export const builder = (argv: yargs.Argv) => { description: 'Verbosity level', default: 5, }) - .coerce('miner-address', (minerAddress: string) => { - if (minerAddress !== null && !minerAddress.startsWith('0x')) { - return '0x' + minerAddress - } - return minerAddress - }) + .coerce( + 'miner-address', + (minerAddress: string) => (minerAddress === null ? null : ensure0x(minerAddress)) + ) } export const handler = async (argv: RunArgv) => { diff --git a/packages/celotool/src/cmds/geth/simulate_client.ts b/packages/celotool/src/cmds/geth/simulate_client.ts index d6cac71cae2..522aed0c058 100644 --- a/packages/celotool/src/cmds/geth/simulate_client.ts +++ b/packages/celotool/src/cmds/geth/simulate_client.ts @@ -1,7 +1,7 @@ import { GethArgv } from '@celo/celotool/src/cmds/geth' import fs from 'fs' import { getBlockscoutClusterInternalUrl } from 'src/lib/endpoints' -import { generateAccountAddressFromPrivateKey } from 'src/lib/generate_utils' +import { privateKeyToAddress } from 'src/lib/generate_utils' import { checkGethStarted, getWeb3AndTokensContracts, simulateClient, sleep } from 'src/lib/geth' import { addCeloEnvMiddleware, CeloEnvArgv } from 'src/lib/utils' import * as yargs from 'yargs' @@ -64,7 +64,7 @@ export const handler = async (argv: SimulateClientArgv) => { const blockscoutProbability = argv.blockscout const loadTestID = argv.loadTestId - const address = generateAccountAddressFromPrivateKey(privateKey).toLowerCase() + const address = privateKeyToAddress(privateKey).toLowerCase() checkGethStarted(dataDir) diff --git a/packages/celotool/src/cmds/geth/static_nodes.ts b/packages/celotool/src/cmds/geth/static_nodes.ts index 5ff31396baa..303ecea9b80 100644 --- a/packages/celotool/src/cmds/geth/static_nodes.ts +++ b/packages/celotool/src/cmds/geth/static_nodes.ts @@ -36,7 +36,7 @@ export const handler = async (argv: StaticNodesArgv) => { const outputDirPath = argv.outputDir const outputFileName = argv.outputFileName - getEnodesWithExternalIPAddresses(namespace).then((enodes) => { + await getEnodesWithExternalIPAddresses(namespace).then((enodes) => { writeStaticNodes( enodes, outputDirPath, diff --git a/packages/celotool/src/lib/blockscout.ts b/packages/celotool/src/lib/blockscout.ts index 7fff44334b6..5a4bd220702 100644 --- a/packages/celotool/src/lib/blockscout.ts +++ b/packages/celotool/src/lib/blockscout.ts @@ -1,5 +1,5 @@ import { installGenericHelmChart, removeGenericHelmChart } from 'src/lib/helm_deploy' -import { execCmdWithExitOnFailure, fetchEnv } from 'src/lib/utils' +import { execCmdWithExitOnFailure, fetchEnv, fetchEnvOrFallback } from 'src/lib/utils' export async function installHelmChart( celoEnv: string, @@ -11,12 +11,12 @@ export async function installHelmChart( celoEnv, celoEnv + '-blockscout', '../helm-charts/blockscout', - helmParameters(blockscoutDBUsername, blockscoutDBPassword, blockscoutDBConnectionName) + helmParameters(celoEnv, blockscoutDBUsername, blockscoutDBPassword, blockscoutDBConnectionName) ) } export async function removeHelmRelease(celoEnv: string) { - removeGenericHelmChart(celoEnv + '-blockscout') + await removeGenericHelmChart(celoEnv + '-blockscout') } export async function upgradeHelmChart( @@ -29,6 +29,7 @@ export async function upgradeHelmChart( if (process.env.CELOTOOL_VERBOSE === 'true') { await execCmdWithExitOnFailure( `helm upgrade --debug --dry-run ${celoEnv}-blockscout ../helm-charts/blockscout --namespace ${celoEnv} ${helmParameters( + celoEnv, blockscoutDBUsername, blockscoutDBPassword, blockscoutDBConnectionName @@ -37,6 +38,7 @@ export async function upgradeHelmChart( } await execCmdWithExitOnFailure( `helm upgrade ${celoEnv}-blockscout ../helm-charts/blockscout --namespace ${celoEnv} ${helmParameters( + celoEnv, blockscoutDBUsername, blockscoutDBPassword, blockscoutDBConnectionName @@ -46,6 +48,7 @@ export async function upgradeHelmChart( } function helmParameters( + celoEnv: string, blockscoutDBUsername: string, blockscoutDBPassword: string, blockscoutDBConnectionName: string @@ -59,6 +62,7 @@ function helmParameters( `--set blockscout.db.password=${blockscoutDBPassword}`, `--set blockscout.db.connection_name=${blockscoutDBConnectionName.trim()}`, `--set blockscout.replicas=${fetchEnv('BLOCKSCOUT_WEB_REPLICAS')}`, + `--set blockscout.subnetwork=${fetchEnvOrFallback('BLOCKSCOUT_SUBNETWORK_NAME', celoEnv)}`, `--set promtosd.scrape_interval=${fetchEnv('PROMTOSD_SCRAPE_INTERVAL')}`, `--set promtosd.export_interval=${fetchEnv('PROMTOSD_EXPORT_INTERVAL')}`, ] diff --git a/packages/celotool/src/lib/generate_utils.ts b/packages/celotool/src/lib/generate_utils.ts index 9b6ef5b036b..e0f73d5e8c0 100644 --- a/packages/celotool/src/lib/generate_utils.ts +++ b/packages/celotool/src/lib/generate_utils.ts @@ -7,7 +7,13 @@ import { PROXY_CONTRACT_CODE, TEMPLATE, } from '@celo/celotool/src/lib/genesis_constants' -import { envVar, fetchEnv, fetchEnvOrFallback } from '@celo/celotool/src/lib/utils' +import { + ensure0x, + envVar, + fetchEnv, + fetchEnvOrFallback, + strip0x, +} from '@celo/celotool/src/lib/utils' import { ec as EC } from 'elliptic' import { range, repeat } from 'lodash' import rlp from 'rlp' @@ -54,40 +60,42 @@ export const generatePrivateKey = (mnemonic: string, accountType: AccountType, i return newNode.privateKey.toString('hex') } -export const generatePublicKeyFromPrivateKey = (privateKey: string) => { +export const privateKeyToPublicKey = (privateKey: string) => { const ecPrivateKey = ec.keyFromPrivate(Buffer.from(privateKey, 'hex')) const ecPublicKey: string = ecPrivateKey.getPublic('hex') return ecPublicKey.slice(2) } -export const generateAccountAddressFromPrivateKey = (privateKey: string) => { - if (!privateKey.startsWith('0x')) { - privateKey = '0x' + privateKey - } - // @ts-ignore-next-line - return new Web3.modules.Eth().accounts.privateKeyToAccount(privateKey).address +export const privateKeyToAddress = (privateKey: string) => { + // @ts-ignore + return new Web3.modules.Eth().accounts.privateKeyToAccount(ensure0x(privateKey)).address } +export const privateKeyToStrippedAddress = (privateKey: string) => + strip0x(privateKeyToAddress(privateKey)) + const DEFAULT_BALANCE = '1000000000000000000000000' const VALIDATOR_OG_SOURCE = 'og' -export const getValidatorsPrivateKeys = (mnemonic: string, n: number) => { - return range(0, n).map((i) => generatePrivateKey(mnemonic, AccountType.VALIDATOR, i)) -} +export const getPrivateKeysFor = (accountType: AccountType, mnemonic: string, n: number) => + range(0, n).map((i) => generatePrivateKey(mnemonic, accountType, i)) -export const getValidators = (mnemonic: string, n: number) => { - return range(0, n) - .map((i) => generatePrivateKey(mnemonic, AccountType.VALIDATOR, i)) - .map(generateAccountAddressFromPrivateKey) - .map((address) => address.slice(2)) -} +export const getAddressesFor = (accountType: AccountType, mnemonic: string, n: number) => + getPrivateKeysFor(accountType, mnemonic, n).map(privateKeyToAddress) + +export const getStrippedAddressesFor = (accountType: AccountType, mnemonic: string, n: number) => + getAddressesFor(accountType, mnemonic, n).map(strip0x) export const generateGenesisFromEnv = (enablePetersburg: boolean = true) => { const validatorEnv = fetchEnv(envVar.VALIDATORS) const validators = validatorEnv === VALIDATOR_OG_SOURCE ? OG_ACCOUNTS.map((account) => account.address) - : getValidators(fetchEnv(envVar.MNEMONIC), parseInt(validatorEnv, 10)) + : getStrippedAddressesFor( + AccountType.VALIDATOR, + fetchEnv(envVar.MNEMONIC), + parseInt(validatorEnv, 10) + ) // @ts-ignore if (![ConsensusType.CLIQUE, ConsensusType.ISTANBUL].includes(fetchEnv(envVar.CONSENSUS_TYPE))) { diff --git a/packages/celotool/src/lib/geth.ts b/packages/celotool/src/lib/geth.ts index cb095c36591..be96da4d57c 100644 --- a/packages/celotool/src/lib/geth.ts +++ b/packages/celotool/src/lib/geth.ts @@ -2,7 +2,7 @@ import { AccountType, generatePrivateKey, - generatePublicKeyFromPrivateKey, + privateKeyToPublicKey, } from '@celo/celotool/src/lib/generate_utils' import { retrieveIPAddress } from '@celo/celotool/src/lib/helm_deploy' import { envVar, execCmd, execCmdWithExitOnFailure, fetchEnv } from '@celo/celotool/src/lib/utils' @@ -85,7 +85,7 @@ export const getBootnodeEnode = async (namespace: string) => { AccountType.LOAD_TESTING_ACCOUNT, 0 ) - const nodeId = generatePublicKeyFromPrivateKey(privateKey) + const nodeId = privateKeyToPublicKey(privateKey) return [getEnodeAddress(nodeId, ip, DISCOVERY_PORT)] } @@ -98,7 +98,7 @@ const getEnodesWithIpAddresses = async (namespace: string, getExternalIP: boolea const enodes = Promise.all( txNodesRange.map(async (index) => { const privateKey = generatePrivateKey(fetchEnv(envVar.MNEMONIC), AccountType.TX_NODE, index) - const nodeId = generatePublicKeyFromPrivateKey(privateKey) + const nodeId = privateKeyToPublicKey(privateKey) let address: string if (getExternalIP) { address = txAddresses[index] @@ -337,8 +337,8 @@ const transferAndTrace = async ( logMessage.gasCurrency = gasCurrencySymbol } - const transferToken = new Promise((resolve) => { - transferERC20Token( + const transferToken = new Promise(async (resolve) => { + await transferERC20Token( web3, token, from, @@ -497,8 +497,8 @@ export const simulateClient = async ( const sendTransactionTime = Date.now() - const transferToken = new Promise((resolve: (data: any) => void) => { - transferERC20Token( + const transferToken = new Promise(async (resolve: (data: any) => void) => { + await transferERC20Token( web3, token, senderAddress, @@ -553,7 +553,7 @@ export const simulateClient = async ( }) if (getRandomInt(0, 99) < blockscoutProbability) { - measureBlockscout( + await measureBlockscout( blockscoutUrl, receipt.transactionHash, senderAddress, diff --git a/packages/celotool/src/lib/helm_deploy.ts b/packages/celotool/src/lib/helm_deploy.ts index 56601071998..62b716ce6aa 100644 --- a/packages/celotool/src/lib/helm_deploy.ts +++ b/packages/celotool/src/lib/helm_deploy.ts @@ -57,7 +57,7 @@ export async function createCloudSQLInstance(celoEnv: string, instanceName: stri await ensureAuthenticatedGcloudAccount() console.info('Creating Cloud SQL database, this might take a minute or two ...') - failIfSecretMissing(CLOUDSQL_SECRET_NAME, 'default') + await failIfSecretMissing(CLOUDSQL_SECRET_NAME, 'default') try { await execCmd(`gcloud sql instances describe ${instanceName}`) @@ -120,7 +120,7 @@ export async function createCloudSQLInstance(celoEnv: string, instanceName: stri await execCmdWithExitOnFailure(`gcloud sql databases create blockscout -i ${instanceName}`) console.info('Copying blockscout service account secret to namespace') - copySecret(CLOUDSQL_SECRET_NAME, 'default', celoEnv) + await copySecret(CLOUDSQL_SECRET_NAME, 'default', celoEnv) const [blockscoutDBConnectionName] = await execCmdWithExitOnFailure( `gcloud sql instances describe ${instanceName} --format="value(connectionName)"` @@ -360,12 +360,17 @@ export async function createStaticIPs(celoEnv: string) { export async function upgradeStaticIPs(celoEnv: string) { const prevTxNodeCount = await getStatefulSetReplicas(celoEnv, `${celoEnv}-tx-nodes`) const newTxNodeCount = parseInt(fetchEnv(envVar.TX_NODES), 10) - upgradeNodeTypeStaticIPs(celoEnv, 'tx-nodes', prevTxNodeCount, newTxNodeCount) + await upgradeNodeTypeStaticIPs(celoEnv, 'tx-nodes', prevTxNodeCount, newTxNodeCount) if (useStaticIPsForGethNodes()) { const prevValidatorNodeCount = await getStatefulSetReplicas(celoEnv, `${celoEnv}-validators`) const newValidatorNodeCount = parseInt(fetchEnv(envVar.VALIDATORS), 10) - upgradeNodeTypeStaticIPs(celoEnv, 'validators', prevValidatorNodeCount, newValidatorNodeCount) + await upgradeNodeTypeStaticIPs( + celoEnv, + 'validators', + prevValidatorNodeCount, + newValidatorNodeCount + ) } } @@ -530,7 +535,6 @@ async function helmParameters(celoEnv: string) { `--set geth.backup.enabled=${fetchEnv(envVar.GETH_NODES_BACKUP_CRONJOB_ENABLED)}`, `--set bootnode.image.repository=${fetchEnv('GETH_BOOTNODE_DOCKER_IMAGE_REPOSITORY')}`, `--set bootnode.image.tag=${fetchEnv('GETH_BOOTNODE_DOCKER_IMAGE_TAG')}`, - `--set bootnode.internal=${fetchEnvOrFallback(envVar.INTERNAL_BOOTNODE, 'true')}`, `--set cluster.zone=${fetchEnv('KUBERNETES_CLUSTER_ZONE')}`, `--set cluster.name=${fetchEnv('KUBERNETES_CLUSTER_NAME')}`, `--set bucket=${bucketName}`, @@ -608,8 +612,8 @@ export async function removeGenericHelmChart(releaseName: string) { } export async function installHelmChart(celoEnv: string) { - failIfSecretMissing(BACKUP_GCS_SECRET_NAME, 'default') - copySecret(BACKUP_GCS_SECRET_NAME, 'default', celoEnv) + await failIfSecretMissing(BACKUP_GCS_SECRET_NAME, 'default') + await copySecret(BACKUP_GCS_SECRET_NAME, 'default', celoEnv) return installGenericHelmChart( celoEnv, celoEnv, diff --git a/packages/celotool/src/lib/utils.ts b/packages/celotool/src/lib/utils.ts index a6cf1cf42ce..a06e937da78 100644 --- a/packages/celotool/src/lib/utils.ts +++ b/packages/celotool/src/lib/utils.ts @@ -36,7 +36,6 @@ export enum envVar { GETHTX3_NODE_ID = 'GETHTX3_NODE_ID', GETHTX4_NODE_ID = 'GETHTX4_NODE_ID', GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS', - INTERNAL_BOOTNODE = 'INTERNAL_BOOTNODE', KUBERNETES_CLUSTER_NAME = 'KUBERNETES_CLUSTER_NAME', KUBERNETES_CLUSTER_ZONE = 'KUBERNETES_CLUSTER_ZONE', MNEMONIC = 'MNEMONIC', @@ -62,13 +61,17 @@ export enum EnvTypes { PRODUCTION = 'production', } -export function execCmd(cmd: string, options: any = {}): Promise<[string, string]> { +export function execCmd( + cmd: string, + execOptions: any = {}, + rejectWithOutput = false +): Promise<[string, string]> { return new Promise((resolve, reject) => { if (process.env.CELOTOOL_VERBOSE === 'true') { console.debug('$ ' + cmd) } - exec(cmd, { maxBuffer: 1024 * 1000, ...options }, (err, stdout, stderr) => { + exec(cmd, { maxBuffer: 1024 * 1000, ...execOptions }, (err, stdout, stderr) => { if (process.env.CELOTOOL_VERBOSE === 'true') { console.debug(stdout.toString()) } @@ -76,7 +79,11 @@ export function execCmd(cmd: string, options: any = {}): Promise<[string, string console.error(stderr.toString()) } if (err) { - reject(err) + if (rejectWithOutput) { + reject([err, stdout.toString(), stderr.toString()]) + } else { + reject(err) + } } else { resolve([stdout.toString(), stderr.toString()]) } @@ -300,3 +307,6 @@ export function addCeloGethMiddleware(argv: yargs.Argv) { export const validateAccountAddress = (address: string) => { return address !== null && address.toLowerCase().startsWith('0x') && address.length === 42 // 0x followed by 40 hex-chars } + +export const ensure0x = (hexstr: string) => (hexstr.startsWith('0x') ? hexstr : '0x' + hexstr) +export const strip0x = (hexstr: string) => (hexstr.startsWith('0x') ? hexstr.slice(2) : hexstr) diff --git a/packages/celotool/tslint.json b/packages/celotool/tslint.json index 242151051dd..1730e39dc29 100644 --- a/packages/celotool/tslint.json +++ b/packages/celotool/tslint.json @@ -6,6 +6,7 @@ "rules": { "no-relative-imports": true, "max-classes-per-file": [true, 2], - "no-global-arrow-functions": false + "no-global-arrow-functions": false, + "no-floating-promises": true } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 20e9f3b8350..cfa97b3811a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -21,7 +21,7 @@ "node": ">=8.0.0" }, "scripts": { - "setup:environment": "./import_contract_types_from_sdk.sh", + "setup:environment": "yarn --cwd=packages/contractkit build-sdk", "build": "rm -rf lib && yarn run prepack", "lint": "tslint -c tslint.json --project tsconfig.json", "lint-checks": "yarn run lint", @@ -32,6 +32,7 @@ "version": "oclif-dev readme && git add README.md" }, "dependencies": { + "@celo/contractkit": "^0.0.1", "@oclif/command": "^1", "@oclif/config": "^1", "@oclif/plugin-help": "^2", diff --git a/packages/cli/src/adapters/bonded-deposit.ts b/packages/cli/src/adapters/bonded-deposit.ts index 6a5e1e8203e..0653f52e285 100644 --- a/packages/cli/src/adapters/bonded-deposit.ts +++ b/packages/cli/src/adapters/bonded-deposit.ts @@ -1,10 +1,11 @@ -import Web3 from 'web3' -import { BondedDeposits } from '../generated/contracts' -import { Address, zip } from '../utils/helpers' - import BN from 'bn.js' +import Web3 from 'web3' import { TransactionObject } from 'web3/eth/types' +import { BondedDeposits } from '@celo/contractkit' + +import { Address, zip } from '../utils/helpers' + export interface VotingDetails { accountAddress: Address voterAddress: Address diff --git a/packages/cli/src/adapters/validators.ts b/packages/cli/src/adapters/validators.ts index 0025015fc11..9d315542deb 100644 --- a/packages/cli/src/adapters/validators.ts +++ b/packages/cli/src/adapters/validators.ts @@ -1,6 +1,8 @@ import Web3 from 'web3' import { TransactionObject } from 'web3/eth/types' -import { Validators } from '../generated/contracts' + +import { Validators } from '@celo/contractkit' + import { Address, compareBN, eqAddress, NULL_ADDRESS, zip } from '../utils/helpers' import { BondedDepositAdapter } from './bonded-deposit' diff --git a/packages/cli/src/commands/account/balance.ts b/packages/cli/src/commands/account/balance.ts index f51f43505f9..90fcfeb054b 100644 --- a/packages/cli/src/commands/account/balance.ts +++ b/packages/cli/src/commands/account/balance.ts @@ -1,5 +1,6 @@ +import { StableToken } from '@celo/contractkit' + import { BaseCommand } from '../../base' -import { StableToken } from '../../generated/contracts' import { printValueMap } from '../../utils/cli' import { Args } from '../../utils/command' diff --git a/packages/cli/src/commands/account/transferdollar.ts b/packages/cli/src/commands/account/transferdollar.ts index 03f7c5a65b8..82daaf459f2 100644 --- a/packages/cli/src/commands/account/transferdollar.ts +++ b/packages/cli/src/commands/account/transferdollar.ts @@ -1,7 +1,9 @@ -import { flags } from '@oclif/command' import Web3 from 'web3' + +import { StableToken } from '@celo/contractkit' +import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { StableToken } from '../../generated/contracts' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/account/transfergold.ts b/packages/cli/src/commands/account/transfergold.ts index a117377c94c..a6774a899b6 100644 --- a/packages/cli/src/commands/account/transfergold.ts +++ b/packages/cli/src/commands/account/transfergold.ts @@ -1,7 +1,9 @@ -import { flags } from '@oclif/command' import Web3 from 'web3' + +import { GoldToken } from '@celo/contractkit' +import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { GoldToken } from '../../generated/contracts' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/bonds/deposit.ts b/packages/cli/src/commands/bonds/deposit.ts index d00a7693969..8e1096cb333 100644 --- a/packages/cli/src/commands/bonds/deposit.ts +++ b/packages/cli/src/commands/bonds/deposit.ts @@ -1,7 +1,9 @@ -import { flags } from '@oclif/command' import Web3 from 'web3' + +import { BondedDeposits } from '@celo/contractkit' +import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { BondedDeposits } from '../../generated/contracts' import { BondArgs } from '../../utils/bonds' import { displaySendTx, failWith } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/bonds/notify.ts b/packages/cli/src/commands/bonds/notify.ts index 3012af47db1..5bd1c76c924 100644 --- a/packages/cli/src/commands/bonds/notify.ts +++ b/packages/cli/src/commands/bonds/notify.ts @@ -1,6 +1,7 @@ +import { BondedDeposits } from '@celo/contractkit' import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { BondedDeposits } from '../../generated/contracts' import { BondArgs } from '../../utils/bonds' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/bonds/register.ts b/packages/cli/src/commands/bonds/register.ts index 4a18314b40e..d96128f742f 100644 --- a/packages/cli/src/commands/bonds/register.ts +++ b/packages/cli/src/commands/bonds/register.ts @@ -1,5 +1,6 @@ +import { BondedDeposits } from '@celo/contractkit' + import { BaseCommand } from '../../base' -import { BondedDeposits } from '../../generated/contracts' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/bonds/withdraw.ts b/packages/cli/src/commands/bonds/withdraw.ts index cd49e54a202..a8de719c299 100644 --- a/packages/cli/src/commands/bonds/withdraw.ts +++ b/packages/cli/src/commands/bonds/withdraw.ts @@ -1,5 +1,6 @@ +import { BondedDeposits } from '@celo/contractkit' + import { BaseCommand } from '../../base' -import { BondedDeposits } from '../../generated/contracts' import { BondArgs } from '../../utils/bonds' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/exchange/list.ts b/packages/cli/src/commands/exchange/list.ts index f5deac5a3a5..59429a59bf8 100644 --- a/packages/cli/src/commands/exchange/list.ts +++ b/packages/cli/src/commands/exchange/list.ts @@ -1,7 +1,9 @@ -import { flags } from '@oclif/command' import { cli } from 'cli-ux' + +import { Exchange } from '@celo/contractkit' +import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { Exchange } from '../../generated/contracts' export default class List extends BaseCommand { static description = 'List information about tokens on the exchange (all amounts in wei)' diff --git a/packages/cli/src/commands/exchange/selldollar.ts b/packages/cli/src/commands/exchange/selldollar.ts index 38030e1e859..cd5cc499c0b 100644 --- a/packages/cli/src/commands/exchange/selldollar.ts +++ b/packages/cli/src/commands/exchange/selldollar.ts @@ -1,5 +1,6 @@ +import { StableToken } from '@celo/contractkit' + import { BaseCommand } from '../../base' -import { StableToken } from '../../generated/contracts' import { doSwap, swapArguments } from '../../utils/exchange' export default class SellDollar extends BaseCommand { @@ -14,6 +15,6 @@ export default class SellDollar extends BaseCommand { const stableToken = await StableToken(this.web3, args.from) - doSwap(this.web3, args, stableToken, false) + await doSwap(this.web3, args, stableToken, false) } } diff --git a/packages/cli/src/commands/exchange/sellgold.ts b/packages/cli/src/commands/exchange/sellgold.ts index 99826c81382..9c9bb4261b8 100644 --- a/packages/cli/src/commands/exchange/sellgold.ts +++ b/packages/cli/src/commands/exchange/sellgold.ts @@ -1,5 +1,6 @@ +import { GoldToken } from '@celo/contractkit' + import { BaseCommand } from '../../base' -import { GoldToken } from '../../generated/contracts' import { doSwap, swapArguments } from '../../utils/exchange' export default class SellGold extends BaseCommand { @@ -14,6 +15,6 @@ export default class SellGold extends BaseCommand { const goldToken = await GoldToken(this.web3, args.from) - doSwap(this.web3, args, goldToken, true) + await doSwap(this.web3, args, goldToken, true) } } diff --git a/packages/cli/src/commands/validator/affiliation.ts b/packages/cli/src/commands/validator/affiliation.ts index 34268aa933f..4a304647a3e 100644 --- a/packages/cli/src/commands/validator/affiliation.ts +++ b/packages/cli/src/commands/validator/affiliation.ts @@ -1,6 +1,7 @@ +import { Validators } from '@celo/contractkit' import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { Validators } from '../../generated/contracts' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/validator/register.ts b/packages/cli/src/commands/validator/register.ts index 6f5a9d87fd4..826c579080d 100644 --- a/packages/cli/src/commands/validator/register.ts +++ b/packages/cli/src/commands/validator/register.ts @@ -1,6 +1,7 @@ +import { Validators } from '@celo/contractkit' import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { Validators } from '../../generated/contracts' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts index 9e09726f984..0b1ee5554d5 100644 --- a/packages/cli/src/commands/validatorgroup/member.ts +++ b/packages/cli/src/commands/validatorgroup/member.ts @@ -1,7 +1,8 @@ +import { Validators } from '@celo/contractkit' import { flags } from '@oclif/command' import { IArg } from '@oclif/parser/lib/args' + import { BaseCommand } from '../../base' -import { Validators } from '../../generated/contracts' import { displaySendTx } from '../../utils/cli' import { Args, Flags } from '../../utils/command' diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts index 63f2e3f6b79..6a32a788fda 100644 --- a/packages/cli/src/commands/validatorgroup/register.ts +++ b/packages/cli/src/commands/validatorgroup/register.ts @@ -1,6 +1,7 @@ +import { Validators } from '@celo/contractkit' import { flags } from '@oclif/command' + import { BaseCommand } from '../../base' -import { Validators } from '../../generated/contracts' import { displaySendTx } from '../../utils/cli' import { Flags } from '../../utils/command' diff --git a/packages/cli/src/utils/exchange.ts b/packages/cli/src/utils/exchange.ts index e16f0e8f977..6509815fdaa 100644 --- a/packages/cli/src/utils/exchange.ts +++ b/packages/cli/src/utils/exchange.ts @@ -1,6 +1,7 @@ import Web3 from 'web3' -import { Exchange } from '../generated/contracts' -import { IERC20Token } from '../generated/types/IERC20Token' + +import { CeloTokenType, Exchange } from '@celo/contractkit' + import { displaySendTx } from './cli' export const swapArguments = [ @@ -30,7 +31,7 @@ interface SwapArgs { export const doSwap = async ( web3: Web3, args: SwapArgs, - sellToken: IERC20Token, + sellToken: CeloTokenType, sellGold: boolean ) => { const exchange = await Exchange(web3, args.from) diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index e408348c110..0d2ec9703a4 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -17,6 +17,6 @@ "target": "es6", "esModuleInterop": true, "outDir": "./lib", - "rootDir": "./src" + "rootDirs": ["./src", "./test"] } } diff --git a/packages/cli/tslint.json b/packages/cli/tslint.json index eb9b3511f6c..5cc4dadf889 100644 --- a/packages/cli/tslint.json +++ b/packages/cli/tslint.json @@ -13,6 +13,7 @@ "no-duplicate-variable": false, "no-sparse-arrays": false, "interface-over-type-literal": false, - "no-global-arrow-functions": false + "no-global-arrow-functions": false, + "no-floating-promises": true } } diff --git a/packages/contractkit/index.ts b/packages/contractkit/index.ts index ada66da6710..5aeced6c6b0 100644 --- a/packages/contractkit/index.ts +++ b/packages/contractkit/index.ts @@ -3,6 +3,7 @@ import GenesisBlockUtils from './src/genesis-block-utils' import GoogleStorageUtils from './src/google-storage-utils' import { Logger, LogLevel } from './src/logger' import StaticNodeUtils from './src/static-node-utils' +import { Web3Utils } from './src/web3-utils' export { Attestations, @@ -23,8 +24,8 @@ export { export { CeloFunctionCall, CeloLog, - FunctionABICache, constructFunctionABICache, + FunctionABICache, getFunctionSignatureFromInput, parseFunctionCall, parseLog, @@ -32,8 +33,8 @@ export { export { unlockAccount } from './src/account-utils' export { ActionableAttestation, - AttestationState, attestationMessageToSign, + AttestationState, decodeAttestationCode, extractAttestationCodeFromMessage, findMatchingIssuer, @@ -54,19 +55,19 @@ export { validateAttestationCode, } from './src/attestations' export { + awaitConfirmation, CeloContract, Contracts, - SendTransaction, - SendTransactionLogEvent, - SendTransactionLogEventType, - TxLogger, - TxPromises, - awaitConfirmation, emptyTxLogger, getContracts, selectContractByAddress, + SendTransaction, sendTransaction, sendTransactionAsync, + SendTransactionLogEvent, + SendTransactionLogEventType, + TxLogger, + TxPromises, } from './src/contract-utils' export { getABEContract, @@ -78,10 +79,10 @@ export { getStableTokenContract, } from './src/contracts' export { - CeloTokenType, allowance, approveToken, balanceOf, + CeloTokenType, convertToContractDecimals, getErc20Balance, getGoldTokenAddress, @@ -95,9 +96,4 @@ export { GenesisBlockUtils } export { GoogleStorageUtils } export { Logger as ContractKitLogger, LogLevel as ContractKitLogLevel } export { StaticNodeUtils } - -// Note: If we export Web3Utils here than the mobile app fails with the following error: https://pastebin.com/raw/1QzcaFsW -// Therefore, I am disabling that for now -// TODO(ashishb): Renable this -// import { Web3Utils } from './src/web3-utils' -// export { Web3Utils } +export { Web3Utils } diff --git a/packages/contractkit/src/contract-utils.ts b/packages/contractkit/src/contract-utils.ts index c849f223bde..b55f1a34052 100644 --- a/packages/contractkit/src/contract-utils.ts +++ b/packages/contractkit/src/contract-utils.ts @@ -221,7 +221,8 @@ export async function sendTransactionAsync( tx: TransactionObject, account: string, gasCurrencyContract: StableToken | GoldToken, - logger: TxLogger = emptyTxLogger + logger: TxLogger = emptyTxLogger, + estimatedGas?: number | undefined ): Promise { // @ts-ignore const resolvers: TxPromiseResolvers = {} @@ -257,19 +258,21 @@ export async function sendTransactionAsync( gasCurrency: gasCurrencyContract._address, gasPrice: '0', } - const estimatedGas = Math.round((await tx.estimateGas(txParams)) * gasInflateFactor) - logger(EstimatedGas(estimatedGas)) - await tx - .send({ - from: account, - // @ts-ignore - gasCurrency: gasCurrencyContract._address, - gas: estimatedGas, - // Hack to prevent web3 from adding the suggested gold gas price, allowing geth to add - // the suggested price in the selected gasCurrency. - gasPrice: '0', - }) + if (estimatedGas === undefined) { + estimatedGas = Math.round((await tx.estimateGas(txParams)) * gasInflateFactor) + logger(EstimatedGas(estimatedGas)) + } + + tx.send({ + from: account, + // @ts-ignore + gasCurrency: gasCurrencyContract._address, + gas: estimatedGas, + // Hack to prevent web3 from adding the suggested gold gas price, allowing geth to add + // the suggested price in the selected gasCurrency. + gasPrice: '0', + }) .on('receipt', (r: TransactionReceipt) => { logger(ReceiptReceived(r)) if (resolvers.receipt) { diff --git a/packages/contractkit/test/utils.ts b/packages/contractkit/test/utils.ts index 237d3683807..f65f00257ae 100644 --- a/packages/contractkit/test/utils.ts +++ b/packages/contractkit/test/utils.ts @@ -40,6 +40,9 @@ export function getGasFeeRecipient(networkName: string) { switch (networkName) { case 'alfajoresstaging': return '0xFe6B1360Fb2D31B4574d05c1d8Af29053fE28F1A' + case 'alfajores': + // Seems to work + return '0xFe6B1360Fb2D31B4574d05c1d8Af29053fE28F1A' } throw new Error(`getGasFeeRecipient/Unexpected network ${networkName}`) } @@ -47,17 +50,21 @@ export function getGasFeeRecipient(networkName: string) { // Mnemonic taken from .env.mnemonic. file in celo-monorepo function getMnemonic(networkName: string): string { const mnemonicFile = `${__dirname}/../../../.env.mnemonic.${networkName}` - const mnemonicFileContent = fs - .readFileSync(mnemonicFile) - .toString() - .toString() - // TODO(ashishb): This is hacky, we should eventually move to `properties-reader` or a similar package. - const mnemonic = mnemonicFileContent - .split('=')[1] - .replace('"', '') - .replace('"', '') - .trim() - return mnemonic + Logger.debug('getMnemonic', `Reading mnemonic from ${mnemonicFile}`) + const mnemonicFileContent = fs.readFileSync(mnemonicFile).toString() + const lines = mnemonicFileContent.split('\n') + for (const line of lines) { + if (line.startsWith('MNEMONIC')) { + // TODO(ashishb): This is hacky, we should eventually move to `properties-reader` or a similar package. + const mnemonic = line + .split('=')[1] + .replace('"', '') + .replace('"', '') + .trim() + return mnemonic + } + } + throw new Error(`Mnemonic not found in ${mnemonicFile}`) } // Alternative way to generate this: diff --git a/packages/contractkit/tslint.json b/packages/contractkit/tslint.json index 31ec4172730..36d9fcf33f0 100644 --- a/packages/contractkit/tslint.json +++ b/packages/contractkit/tslint.json @@ -2,5 +2,8 @@ "extends": ["@celo/typescript/tslint.json"], "linterOptions": { "include": ["**/*.ts"] + }, + "rules": { + "no-floating-promises": true } } diff --git a/packages/docs/command-line-interface/introduction.md b/packages/docs/command-line-interface/introduction.md index 66a464c7812..ffaa6bbeee7 100644 --- a/packages/docs/command-line-interface/introduction.md +++ b/packages/docs/command-line-interface/introduction.md @@ -6,9 +6,33 @@ description: >- # Introduction -### Getting Started +## Getting Started -The Celo CLI is published as a node module on NPM. Assuming you have [npm installed](https://www.npmjs.com/get-npm), you can install the Celo CLI using the following command: +### Docker Image + +A docker image that runs the Celo Blockchain client in full sync mode which includes the Celo CLI is available for version pinning. + +`$ docker pull us.gcr.io/celo-testnet/celocli:master` + +For more details on configuring this container, see the [Running a Full Node](../getting-started/running-a-full-node.md) section. You can run the container with the following command. + +`$ docker run --name celo_cli_container -it -p 8545:8545 us.gcr.io/celo-testnet/celocli:master -v` + +With additional arguments to the image, it can also be run in ultralight sync mode. + +`$ docker run --name celo_cli_container -p 8545:8545 --entrypoint=/celo/start_geth.sh us.gcr.io/celo-testnet/celocli:master "/usr/local/bin/geth" "alfajores" "ultralight"` + +An interactive shell where the Celo CLI is available can be obtained via the following command. All of the subsequent documentation should be appropriate from this shell. + +`$ docker exec -it celo_cli_container /bin/sh` + +Make sure to kill the container when you are done. + +`$ docker kill celo_cli_container` + +### NPM Package + +The Celo CLI is also published as a node module on NPM. Assuming you have [npm installed](https://www.npmjs.com/get-npm), you can install the Celo CLI using the following command: `$ npm install -g @celo/celocli` @@ -16,6 +40,8 @@ The Celo CLI is published as a node module on NPM. Assuming you have [npm instal We are currently deploying the CLI with only Node v10.x LTS support. If you are running a different version of Node, consider using [NVM](https://github.com/nvm-sh/nvm#installation-and-update) to manage your node versions. e.g. with: `nvm install 10 && nvm use 10` {% endhint %} +### Overview + The tool is broken down into modules and commands with the following pattern: `celocli : <...args> <...flags?>` diff --git a/packages/docs/getting-started/running-a-validator.md b/packages/docs/getting-started/running-a-validator.md index 2a08f1ef080..3b72ef72e18 100644 --- a/packages/docs/getting-started/running-a-validator.md +++ b/packages/docs/getting-started/running-a-validator.md @@ -66,7 +66,7 @@ In order to allow the node to sync with the network, give it the address for the Start up the node: -`` $ 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 44781 --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug,admin,personal --maxpeers 1100 --mine --etherbase $CELO_VALIDATOR_ADDRESS `` +`` $ 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 44781 --syncmode full --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug,admin,personal --maxpeers 1100 --mine --miner.verificationpool=https://us-central1-celo-testnet-production.cloudfunctions.net/handleVerificationRequestalfajores/v0.1/sms/ --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 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/helm-charts/blockscout/templates/_helpers.tpl b/packages/helm-charts/blockscout/templates/_helpers.tpl index 105a61862b4..b19c522b1d1 100644 --- a/packages/helm-charts/blockscout/templates/_helpers.tpl +++ b/packages/helm-charts/blockscout/templates/_helpers.tpl @@ -31,7 +31,7 @@ volumes: - name: NETWORK value: Celo - name: SUBNETWORK - value: Alfajores + value: {{ .Values.blockscout.subnetwork }} - name: COIN value: cGLD - name: ECTO_USE_SSL @@ -74,4 +74,4 @@ volumes: valueFrom: fieldRef: fieldPath: metadata.namespace -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/packages/helm-charts/testnet/templates/bootnode.service.yaml b/packages/helm-charts/testnet/templates/bootnode.service.yaml index 5b6aaf4c82b..dd3e36b0419 100644 --- a/packages/helm-charts/testnet/templates/bootnode.service.yaml +++ b/packages/helm-charts/testnet/templates/bootnode.service.yaml @@ -9,7 +9,7 @@ metadata: heritage: {{ .Release.Service }} component: bootnode spec: - {{ if not (eq .Values.bootnode.internal true) }} + {{ if $.Values.geth.static_ips }} type: LoadBalancer loadBalancerIP: {{ .Values.geth.bootnodeIpAddress }} {{ end }} diff --git a/packages/helm-charts/testnet/templates/validators.service.yaml b/packages/helm-charts/testnet/templates/validators.service.yaml index 9ae7df1664f..5da32d4ae98 100644 --- a/packages/helm-charts/testnet/templates/validators.service.yaml +++ b/packages/helm-charts/testnet/templates/validators.service.yaml @@ -1,4 +1,4 @@ -{{ if and (not (eq (.Values.geth.validators | toString) "og")) (not (eq .Values.bootnode.internal true)) }} +{{ if and (not (eq (.Values.geth.validators | toString) "og")) .Values.geth.static_ips }} {{ range $index, $e := until (.Values.geth.validators | int) }} kind: Service @@ -18,9 +18,7 @@ spec: component: validators statefulset.kubernetes.io/pod-name: {{ template "ethereum.fullname" $ }}-validators-{{ $index | toString }} type: LoadBalancer - {{ if ($.Values.geth.static_ips) }} loadBalancerIP: {{ index $.Values.geth (print "validators_" $index "IpAddress") }} - {{ end }} ports: - name: discovery port: 30303 @@ -43,9 +41,7 @@ spec: component: validators statefulset.kubernetes.io/pod-name: {{ template "ethereum.fullname" $ }}-validators-{{ $index | toString }} type: LoadBalancer - {{ if ($.Values.geth.static_ips) }} loadBalancerIP: {{ index $.Values.geth (print "validators_" $index "IpAddress") }} - {{ end }} ports: - name: discovery port: 30303 diff --git a/packages/helm-charts/testnet/templates/validators.statefulset.yaml b/packages/helm-charts/testnet/templates/validators.statefulset.yaml index 770b56f5d92..36e82732e65 100644 --- a/packages/helm-charts/testnet/templates/validators.statefulset.yaml +++ b/packages/helm-charts/testnet/templates/validators.statefulset.yaml @@ -60,7 +60,11 @@ spec: celotooljs.sh generate bip32 --mnemonic "$MNEMONIC" --accountType validator --index $RID > /root/.celo/pkey echo 'Generating address' celotooljs.sh generate account-address --private-key `cat /root/.celo/pkey` > /root/.celo/address - echo $IP_ADDRESSES | cut -d '/' -f $((RID + 1)) > /root/.celo/ipAddress + if [ -z $IP_ADDRESSES ]; then + echo $POD_IP > /root/.celo/ipAddress + else + echo $IP_ADDRESSES | cut -d '/' -f $((RID + 1)) > /root/.celo/ipAddress + fi echo -n "Generating IP address for validator: " cat /root/.celo/ipAddress celotooljs.sh generate public-key --mnemonic "$MNEMONIC" --accountType load_testing --index 0 > /root/.celo/bootnodeEnodeAddress @@ -71,6 +75,11 @@ spec: echo -n "Generating Bootnode enode for the validator: " cat /root/.celo/bootnodeEnode env: + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP - name: REPLICA_NAME valueFrom: fieldRef: @@ -113,8 +122,6 @@ spec: ACCOUNT_ADDRESS=`cat /root/.celo/address` RID=`cat /root/.celo/replica_id` FAULTY_NODE_FLAGS=`if [ "$RID" -lt "$FAULTY_NODES" ]; then echo -n "--istanbul.faultymode $FAULTY_NODE_TYPE"; fi` - NAT_FLAG="" - [[ "$STATIC_IPS_FOR_GETH_NODES" == "true" ]] && NAT_FLAG="--nat=extip:`cat /root/.celo/ipAddress`" geth \ --bootnodes=enode://`cat /root/.celo/bootnodeEnode` \ --password=/root/.celo/account/accountSecret \ @@ -142,7 +149,7 @@ spec: ${FAULTY_NODE_FLAGS} \ --istanbul.blockperiod={{ .Values.geth.blocktime }} \ --maxpeers={{ mul 2 (add .Values.geth.validators .Values.geth.tx_nodes) }} \ - ${NAT_FLAG} + --nat=extip:`cat /root/.celo/ipAddress` env: - name: POD_IP valueFrom: @@ -168,7 +175,7 @@ spec: - name: FAULTY_NODE_TYPE value: {{ .Values.geth.faultyValidatorType | quote }} - name: STATIC_IPS_FOR_GETH_NODES - value: "{{ default "false" .Values.geth.static_ips }}" + value: "{{ default false .Values.geth.static_ips }}" ports: - name: discovery-udp containerPort: 30303 diff --git a/packages/mobile/.env.staging b/packages/mobile/.env.staging index 725da068458..27345d026af 100644 --- a/packages/mobile/.env.staging +++ b/packages/mobile/.env.staging @@ -1,6 +1,6 @@ ENVIRONMENT=staging -DEFAULT_TESTNET=alfajoresstaging -BLOCKCHAIN_API_URL=https://alfajoresstaging-dot-celo-testnet.appspot.com/ +DEFAULT_TESTNET=alfajores +BLOCKCHAIN_API_URL=https://alfajores-dot-celo-testnet-production.appspot.com/ DEV_SETTINGS_ACTIVE_INITIALLY=true FIREBASE_ENABLED=true SECRETS_KEY=staging diff --git a/packages/mobile/README.md b/packages/mobile/README.md index 081f29945ae..4f47f24ac85 100644 --- a/packages/mobile/README.md +++ b/packages/mobile/README.md @@ -1,4 +1,8 @@ -# Mobile +# Mobile (Celo Wallet) + +## Architecture + +The app uses [React Native][react native] and a geth [light node][light node]. ## Setup @@ -75,10 +79,6 @@ renders when no state has changed. Reducing renders can be done via pure components in react or overloading the should component update method [example here][rn optimize example]. -## Architecture - -The app uses [React Native][react native] and a geth [light node][light node]. - ## Connecting to networks By default, we have the `alfajores` network set up. If you have other testnets @@ -122,10 +122,6 @@ Next, the VM snapshot settings should be modified: For information on how to run and extend the e2e tests, refer to the [e2e readme][e2e readme]. -## Deployment - -The app is set up to automatically deploy to Google Play beta using fastlane. - ## Generating GraphQL Types We're using [GraphQL Code Generator][graphql code generator] to properly type diff --git a/packages/mobile/__mocks__/src/analytics/CeloAnalytics.ts b/packages/mobile/__mocks__/src/analytics/CeloAnalytics.ts index dd9443099cc..07fa3ad48f3 100644 --- a/packages/mobile/__mocks__/src/analytics/CeloAnalytics.ts +++ b/packages/mobile/__mocks__/src/analytics/CeloAnalytics.ts @@ -1,7 +1,16 @@ class WalletAnalytics { - track = jest.fn((event: any) => { + track = jest.fn((event: string) => { console.log('Track event', event) }) + startTracking = jest.fn((event: string) => { + console.log('Start tracking event', event) + }) + trackSubEvent = jest.fn((event: string, subEvent: string) => { + console.log('Track sub event', event, subEvent) + }) + stopTracking = jest.fn((event: string) => { + console.log('Stop tracking', event) + }) } export default new WalletAnalytics() diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index d2ede6e648a..99509b3d3c3 100644 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -122,7 +122,7 @@ android { minSdkVersion isDetoxTestBuild ? rootProject.ext.minSdkVersion : 18 targetSdkVersion rootProject.ext.targetSdkVersion versionCode appVersionCode - versionName "1.3.1" + versionName "1.3.2" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "build_config_package", "org.celo.mobile" @@ -230,7 +230,6 @@ dependencies { implementation project(':react-native-tcp') implementation project(':react-native-sentry') implementation project(':react-native-secure-randombytes') - implementation project(':react-native-sms-android') implementation project(':react-native-svg') implementation project(':react-native-contacts') implementation project(':react-native-keep-awake') diff --git a/packages/mobile/android/app/src/debug/google-services.json.enc b/packages/mobile/android/app/src/debug/google-services.json.enc index f5e360ebadf..654aaea648c 100644 Binary files a/packages/mobile/android/app/src/debug/google-services.json.enc and b/packages/mobile/android/app/src/debug/google-services.json.enc differ diff --git a/packages/mobile/android/app/src/main/java/org/celo/mobile/MainApplication.java b/packages/mobile/android/app/src/main/java/org/celo/mobile/MainApplication.java index a97efe6dafb..d7d8bffc01a 100644 --- a/packages/mobile/android/app/src/main/java/org/celo/mobile/MainApplication.java +++ b/packages/mobile/android/app/src/main/java/org/celo/mobile/MainApplication.java @@ -41,7 +41,6 @@ import com.peel.react.TcpSocketsModule; import com.reactcommunity.rnlanguages.RNLanguagesPackage; import com.reactnativegeth.RNGethPackage; -import com.rhaker.reactnativesmsandroid.RNSmsAndroidPackage; import com.rnfs.RNFSPackage; import com.rt2zz.reactnativecontacts.ReactNativeContacts; import com.tradle.react.UdpSocketsModule; @@ -103,7 +102,6 @@ protected List getPackages() { new TcpSocketsModule(), new RNSentryPackage(), new RandomBytesPackage(), - new RNSmsAndroidPackage(), new SvgPackage(), new ReactNativeContacts(), new KCKeepAwakePackage(), diff --git a/packages/mobile/android/app/src/staging/google-services.json.enc b/packages/mobile/android/app/src/staging/google-services.json.enc index 18ed05c0b02..30ea3407ea5 100644 Binary files a/packages/mobile/android/app/src/staging/google-services.json.enc and b/packages/mobile/android/app/src/staging/google-services.json.enc differ diff --git a/packages/mobile/android/gradle.properties b/packages/mobile/android/gradle.properties index 58cf4eb788a..3520c2c7a4c 100644 --- a/packages/mobile/android/gradle.properties +++ b/packages/mobile/android/gradle.properties @@ -20,4 +20,4 @@ CELO_RELEASE_STORE_FILE=celo-release-key.keystore CELO_RELEASE_KEY_ALIAS=celo-key-alias # Setting this manually based on version number until we have this deploying via Cloud Build -VERSION_CODE=1003001 \ No newline at end of file +VERSION_CODE=1003002 \ No newline at end of file diff --git a/packages/mobile/android/settings.gradle b/packages/mobile/android/settings.gradle index aef27dc50e0..bc97b54d112 100644 --- a/packages/mobile/android/settings.gradle +++ b/packages/mobile/android/settings.gradle @@ -41,8 +41,6 @@ include ':react-native-sentry' project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-sentry/android') include ':react-native-secure-randombytes' project(':react-native-secure-randombytes').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-secure-randombytes/android') -include ':react-native-sms-android' -project(':react-native-sms-android').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-sms-android/android') include ':react-native-svg' project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') include ':react-native-contacts' diff --git a/packages/mobile/ios/celo-tvOS/Info.plist b/packages/mobile/ios/celo-tvOS/Info.plist index a163c9602c9..f743c1fa53b 100644 --- a/packages/mobile/ios/celo-tvOS/Info.plist +++ b/packages/mobile/ios/celo-tvOS/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.1 + 1.3.2 CFBundleSignature ???? CFBundleVersion - 3 + 4 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/packages/mobile/ios/celo-tvOSTests/Info.plist b/packages/mobile/ios/celo-tvOSTests/Info.plist index a7ae6a236d7..c559ff1f1dc 100644 --- a/packages/mobile/ios/celo-tvOSTests/Info.plist +++ b/packages/mobile/ios/celo-tvOSTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.1 + 1.3.2 CFBundleSignature ???? CFBundleVersion - 3 + 4 diff --git a/packages/mobile/ios/celo/Info.plist b/packages/mobile/ios/celo/Info.plist index 4881360edac..c9b3440dd7c 100644 --- a/packages/mobile/ios/celo/Info.plist +++ b/packages/mobile/ios/celo/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.1 + 1.3.2 CFBundleSignature ???? CFBundleVersion - 3 + 4 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/packages/mobile/ios/celoTests/Info.plist b/packages/mobile/ios/celoTests/Info.plist index a7ae6a236d7..c559ff1f1dc 100644 --- a/packages/mobile/ios/celoTests/Info.plist +++ b/packages/mobile/ios/celoTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.1 + 1.3.2 CFBundleSignature ???? CFBundleVersion - 3 + 4 diff --git a/packages/mobile/locales/en-US/nuxNamePin1.json b/packages/mobile/locales/en-US/nuxNamePin1.json index ed44b27e88a..1665b45ca0a 100644 --- a/packages/mobile/locales/en-US/nuxNamePin1.json +++ b/packages/mobile/locales/en-US/nuxNamePin1.json @@ -9,7 +9,7 @@ "verifyNumber": "Verify your phone number so other Celo users can find you and send you value", "syncNetwork": "Syncing with Network", "welcomeCelo": "Welcome to Celo Wallet", - "chooseCountryCode": "Choose Country Code", + "chooseCountryCode": "Country Code", "joinText": { "0": "Welcome to Celo Wallet, a digital wallet that allows you to easily send, receive, and save value.", diff --git a/packages/mobile/locales/en-US/nuxVerification2.json b/packages/mobile/locales/en-US/nuxVerification2.json index b4de60ba8be..8dd4f5b7ffa 100644 --- a/packages/mobile/locales/en-US/nuxVerification2.json +++ b/packages/mobile/locales/en-US/nuxVerification2.json @@ -53,5 +53,6 @@ "errorRequestCode": "Failed to request codes.", "errorRedeemingCode": "Failed to redeem code.", "pleaseRetry": "Please retry verification.", - "retryVerification": "Retry Verification" + "retryVerification": "Retry Verification", + "codeAccepted": "Accepted" } diff --git a/packages/mobile/locales/es-AR/accountScreen10.json b/packages/mobile/locales/es-AR/accountScreen10.json index 11f3e6a5347..8f476fe1504 100755 --- a/packages/mobile/locales/es-AR/accountScreen10.json +++ b/packages/mobile/locales/es-AR/accountScreen10.json @@ -3,6 +3,7 @@ "invite": "Invitar", "celoRewards": "Recompensas Celo", "languageSettings": "Configuración de idioma", + "licenses": "Licencias", "sendIssueReport": "Reportar un incidente", "analytics": "Estadisticas de uso", "shareAnalytics": "Compartir estadisticas de uso", diff --git a/packages/mobile/locales/es-AR/backupKeyFlow6.json b/packages/mobile/locales/es-AR/backupKeyFlow6.json index 4ca2426ada1..a2ac6d7cded 100755 --- a/packages/mobile/locales/es-AR/backupKeyFlow6.json +++ b/packages/mobile/locales/es-AR/backupKeyFlow6.json @@ -20,7 +20,6 @@ "setBackupKey": "Crear clave de respaldo", "skip": "Saltar paso", "areYouSure": "¿Está seguro?", - "needBackupKey": "Sin una clave de respaldo, puede perder el acceso a la billetera para siempre.", "shareBackupKey": "Compartir clave de respaldo", "backupRecovery": "Comparta la clave de respaldo con una sola persona en la que confíe plenamente.", diff --git a/packages/mobile/locales/es-AR/dev.json b/packages/mobile/locales/es-AR/dev.json new file mode 100644 index 00000000000..c6320750b9a --- /dev/null +++ b/packages/mobile/locales/es-AR/dev.json @@ -0,0 +1,4 @@ +{ + "firebaseDisabled": + "FireBase está desactivado, la carga de notificaciones y registros no funcionará" +} diff --git a/packages/mobile/locales/es-AR/global.json b/packages/mobile/locales/es-AR/global.json index 447913d22d8..c371629f3e5 100755 --- a/packages/mobile/locales/es-AR/global.json +++ b/packages/mobile/locales/es-AR/global.json @@ -33,6 +33,7 @@ "backToWallet": "Volver a la Billetera", "exchangeForGold": "Cambiar a Oro", "restoreCeloWallet": "Restaurar la billetera de Celo", + "cantSelectInvalidPhone": "No se puede seleccionar el contacto: número de teléfono no válido", "invalidKey": "Clave de respaldo inválida ", "invalidPhone": "Número de teléfono inválido", "invalidAmount": "Monto invalido", diff --git a/packages/mobile/locales/es-AR/nuxNamePin1.json b/packages/mobile/locales/es-AR/nuxNamePin1.json index c82bd681a40..0268c0a23a2 100755 --- a/packages/mobile/locales/es-AR/nuxNamePin1.json +++ b/packages/mobile/locales/es-AR/nuxNamePin1.json @@ -10,7 +10,7 @@ "Verifique su número de teléfono para conectarse con otros usuarios y recibir dinero", "syncNetwork": "Sincronizando con la red", "welcomeCelo": "Le damos la bienvenida a Celo", - "chooseCountryCode": "Elegir código de país", + "chooseCountryCode": "Código de país", "joinText": { "0": "Bienvenido a Celo Wallet, una billetera digital que le permite enviar, recibir y guardar valor fácilmente.", diff --git a/packages/mobile/locales/es-AR/nuxVerification2.json b/packages/mobile/locales/es-AR/nuxVerification2.json index 148e7dd1993..e4564a4c033 100755 --- a/packages/mobile/locales/es-AR/nuxVerification2.json +++ b/packages/mobile/locales/es-AR/nuxVerification2.json @@ -54,5 +54,6 @@ "errorRequestCode": "Error al solicitar los códigos.", "errorRedeemingCode": "Error al canjear el código.", "pleaseRetry": "Por favor reinicie la verificación.", - "retryVerification": "Reintentar la verificación" + "retryVerification": "Reintentar la verificación", + "codeAccepted": "Aceptado" } diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 341c41298dd..03f2af88245 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@celo/mobile", - "version": "1.3.1", + "version": "1.3.2", "author": "Celo", "license": "Apache-2.0", "private": true, @@ -31,7 +31,7 @@ "test:run-e2e": "./scripts/run_e2e.sh", "test:detox": "CELO_TEST_CONFIG=e2e detox test -c android.emu.debug -a e2e/tmp/ --take-screenshots=failing --record-logs=failing --detectOpenHandles", "test:unlock": "./scripts/unlock.sh", - "deploy:update-version": "./scripts/update-version.sh", + "pre-deploy": "./scripts/pre-deploy.sh", "prepare": "patch-package", "postinstall": "sh scripts/fix_rn_version.sh; patch-package", "update-disclaimer": "yarn licenses generate-disclaimer > LicenseDisclaimer.txt && mkdir -p android/app/src/main/assets/custom && cp LicenseDisclaimer.txt android/app/src/main/assets/custom/LicenseDisclaimer.txt", @@ -105,11 +105,10 @@ "react-native-restart-android": "^0.0.7", "react-native-screens": "^1.0.0-alpha.22", "react-native-secure-randombytes": "^2.2.3", - "react-native-send-intent": "git+ssh://git@github.com/celo-org/react-native-send-intent#8039938", + "react-native-send-intent": "git+https://github.com/celo-org/react-native-send-intent#8039938", "react-native-sentry": "^0.38.3", "react-native-shadow": "^1.2.2", "react-native-share": "^1.1.3", - "react-native-sms-android": "^0.4.1", "react-native-splash-screen": "^3.1.1", "react-native-svg": "^9.3.6", "react-native-swiper": "^1.5.13", diff --git a/packages/mobile/rn-cli.config.js b/packages/mobile/rn-cli.config.js index ed05e316dcf..33eeae82d4c 100644 --- a/packages/mobile/rn-cli.config.js +++ b/packages/mobile/rn-cli.config.js @@ -24,6 +24,7 @@ module.exports = { extraNodeModules: { ...nodeLibs, 'crypto-js': path.resolve(cwd, 'node_modules/crypto-js'), + 'isomorphic-fetch': require.resolve('cross-fetch'), net: require.resolve('react-native-tcp'), 'react-native': path.resolve(cwd, 'node_modules/react-native'), 'react-native-fs': path.resolve(cwd, 'node_modules/react-native-fs'), diff --git a/packages/mobile/scripts/pre-deploy.sh b/packages/mobile/scripts/pre-deploy.sh new file mode 100755 index 00000000000..9acb47786fd --- /dev/null +++ b/packages/mobile/scripts/pre-deploy.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +echo "This will prepare the mobile app for deployment to the store" + +# Prompt for new version number +echo "===Updating app version===" +yarn version --no-git-tag-version +echo "===Running react-native-version to update the android/ios build files===" +yarn run react-native-version +echo "===Done updating versions. Note: you may need to update the VERSION_CODE in gradle.properties as well if deploying manually===" + +echo "===Update license list and disclaimer===" +yarn update-disclaimer +echo "===Done updating licenses===" + +echo "Pre-deploy steps complete" \ No newline at end of file diff --git a/packages/mobile/scripts/update-version.sh b/packages/mobile/scripts/update-version.sh deleted file mode 100755 index 1560a83d5d1..00000000000 --- a/packages/mobile/scripts/update-version.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -echo "This will update the package version and create a new Git tag." - -# Update yarn configs for more descriptive tag / commit message -echo "Updating yarn configs" -yarn config set version-tag-prefix "wallet-v" -yarn config set version-git-message "Update Wallet App Version to v%s" - -# Prompt for new version number -yarn version -echo "Running react-native-version to update the android/ios build files" -yarn run react-native-version - -echo "Restoring yarn configs" -yarn config set version-tag-prefix "v" -yarn config set version-git-message "v%s" - -echo "Update license list and disclaimer" -yarn update-disclaimer - -echo "Done" -echo "YOU HAVE CREATED A NEW TAG, RUN git push origin --tags TO PUBLISH IT TO GITHUB" -echo "YOU HAVE CREATED A NEW TAG, RUN git push origin --tags TO PUBLISH IT TO GITHUB" -echo "YOU HAVE CREATED A NEW TAG, RUN git push origin --tags TO PUBLISH IT TO GITHUB" \ No newline at end of file diff --git a/packages/mobile/src/account/Account.test.tsx b/packages/mobile/src/account/Account.test.tsx index 14c11b7fc5d..50b3485b967 100644 --- a/packages/mobile/src/account/Account.test.tsx +++ b/packages/mobile/src/account/Account.test.tsx @@ -1,6 +1,5 @@ const { mockNavigationServiceFor } = require('test/utils') const { navigate } = mockNavigationServiceFor('Account') -const isPhoneNumberVerified = jest.fn(async (key) => true) import * as React from 'react' import 'react-native' @@ -11,10 +10,6 @@ import Account from 'src/account/Account' import { Screens } from 'src/navigator/Screens' import { createMockStore } from 'test/utils' -jest.mock('src/identity/verification', () => ({ - isPhoneNumberVerified, -})) - jest.useFakeTimers() describe('Account', () => { diff --git a/packages/mobile/src/account/__snapshots__/Invite.test.tsx.snap b/packages/mobile/src/account/__snapshots__/Invite.test.tsx.snap index 0d9a85a80a1..c7e6bed4e7f 100644 --- a/packages/mobile/src/account/__snapshots__/Invite.test.tsx.snap +++ b/packages/mobile/src/account/__snapshots__/Invite.test.tsx.snap @@ -101,7 +101,75 @@ exports[`Invite renders correctly 1`] = ` /> + + + noResultsFor + + + "" + + + + searchForSomeone + + + } data={Array []} disableVirtualization={false} getItem={[Function]} @@ -109,6 +177,7 @@ exports[`Invite renders correctly 1`] = ` horizontal={false} initialNumToRender={30} keyExtractor={[Function]} + keyboardShouldPersistTaps="handled" maxToRenderPerBatch={10} onContentSizeChange={[Function]} onEndReachedThreshold={2} @@ -128,6 +197,7 @@ exports[`Invite renders correctly 1`] = ` > { + return state.alert ? state.alert.underlyingError || null : null +} diff --git a/packages/mobile/src/analytics/constants.ts b/packages/mobile/src/analytics/constants.ts index 667fd309dd6..fd08741ce25 100644 --- a/packages/mobile/src/analytics/constants.ts +++ b/packages/mobile/src/analytics/constants.ts @@ -30,15 +30,19 @@ export enum CustomEventNames { send_invite_details = 'send_invite_details', send_invite = 'send_invite', edit_send_invite = 'edit_send_invite', - verification_commit = 'verification_commit', - verification_start = 'verification_start', - verification_cancel = 'verification_cancel', - verification_timeout = 'verification_timeout', + + // Verification event and sub-events + verification = 'verification', + verification_setup = 'verification_setup', + verification_get_status = 'verification_get_status', + verification_req_attestations = 'verification_req_attestations', + verification_get_attestations = 'verification_get_attestations', + verification_set_account = 'verification_set_account', + verification_reveal_txs = 'verification_reveal_txs', + verification_codes_received = 'verification_codes_received', + verification_complete_txs = 'verification_complete_txs', verification_manual_selected = 'verification_manual_selected', - verification_code_entered = 'verification_code_entered', - verification_code_confirmed = 'verification_code_confirmed', - verification_complete = 'verification_complete', - verification_failure = 'verification_failure', + photos_education = 'photos_education', get_backup_key = 'earn_celo_gold', earn_celo_gold = 'earn_celo_gold', @@ -139,6 +143,13 @@ export enum CustomEventNames { qrcode_main_screen_visit = 'qrcode_main_screen_visit', } +export enum CommonValues { + success = 'success', + failure = 'failure', + cancel = 'cancel', + timeout = 'timeout', +} + // TODO(nitya): separate this out by event name export const PROPERTY_PATH_WHITELIST = [ 'address', diff --git a/packages/mobile/src/app/ErrorMessages.ts b/packages/mobile/src/app/ErrorMessages.ts index 582feda368f..e63c72aa535 100644 --- a/packages/mobile/src/app/ErrorMessages.ts +++ b/packages/mobile/src/app/ErrorMessages.ts @@ -13,9 +13,9 @@ export enum ErrorMessages { INVALID_PHONE_NUMBER = 'nuxVerification2:invalidPhone', NOT_READY_FOR_CODE = 'nuxVerification2:notReadyForCode', EMPTY_INVITE_CODE = 'nuxNamePin1:emptyInviteCode', - EMPTY_VERIFICATION_CODE = 'nuxVerification2:emptyVerificationCode', + EMPTY_ATTESTATION_CODE = 'nuxVerification2:emptyVerificationCode', INVALID_ATTESTATION_CODE = 'nuxVerification2:invalidVerificationCode', - REPEAT_VERIFICATION_CODE = 'nuxVerification2:repeatVerificationCode', + REPEAT_ATTESTATION_CODE = 'nuxVerification2:repeatVerificationCode', VERIFICATION_FAILURE = 'nuxVerification2:verificationFailure', VERIFICATION_TIMEOUT = 'nuxVerification2:verificationTimeout', INVALID_ACCOUNT = 'invalidAccount', diff --git a/packages/mobile/src/backup/BackupIntroduction.tsx b/packages/mobile/src/backup/BackupIntroduction.tsx index 400c7eb8c00..01141f37406 100644 --- a/packages/mobile/src/backup/BackupIntroduction.tsx +++ b/packages/mobile/src/backup/BackupIntroduction.tsx @@ -5,8 +5,7 @@ import { componentStyles } from '@celo/react-components/styles/styles' import variables from '@celo/react-components/styles/variables' import * as React from 'react' import { WithNamespaces, withNamespaces } from 'react-i18next' -import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native' -import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' +import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native' import Modal from 'react-native-modal' import CeloAnalytics from 'src/analytics/CeloAnalytics' import { CustomEventNames } from 'src/analytics/constants' @@ -26,6 +25,8 @@ interface State { } class BackupIntroduction extends React.Component { + static navigationOptions = { header: null } + state = { selectedAnswer: null, visibleModal: false, @@ -88,10 +89,7 @@ class BackupIntroduction extends React.Component { - + @@ -99,17 +97,25 @@ class BackupIntroduction extends React.Component { {t('backupKeyImportance.0')} {t('backupKeyImportance.1')} {t('backupKeyImportance.2')} + + {this.skip()}