diff --git a/.circleci/config.yml b/.circleci/config.yml index 25d16851fe1..1dde9665e30 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,7 +34,7 @@ defaults: &defaults contract-defaults: &contract-defaults <<: *defaults environment: - RELEASE_TAG: core-contracts.v4 + RELEASE_TAG: core-contracts.v5 e2e-defaults: &e2e-defaults <<: *defaults diff --git a/.env b/.env index 7cbb29e9533..93481a06d38 100644 --- a/.env +++ b/.env @@ -87,6 +87,8 @@ LOAD_TEST_CLIENTS=100 # every 36 seconds, so that 100 transactions are sent by a client every hour LOAD_TEST_TX_DELAY_MS=36000 +LOAD_TEST_USE_RANDOM_RECIPIENT='false' + # the amount in cUSD wei to give faucet, load test, and attestation bot accounts FAUCET_CUSD_WEI=60000000000000000000000 diff --git a/.env.alfajores b/.env.alfajores index b0be45b7728..feb8162c6d3 100644 --- a/.env.alfajores +++ b/.env.alfajores @@ -51,6 +51,7 @@ AZURE_ORACLE_CENTRALUS_FULL_NODES_ROLLING_UPDATE_PARTITION=0 AZURE_ORACLE_CENTRALUS_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" AZURE_ORACLE_CENTRALUS_FULL_NODES_GETH_GC_MODE="full" AZURE_ORACLE_CENTRALUS_FULL_NODES_USE_GSTORAGE_DATA=false +AZURE_ORACLE_CENTRALUS_FULL_NODES_WS_PORT="8546" # Temporarily point to celo-org repository to consume patched image. GETH_NODE_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-org/geth" @@ -91,7 +92,7 @@ ATTESTATION_BOT_MAX_ATTESTATIONS=32 ATTESTATION_BOT_SKIP_ODIS_SALT=bot ATTESTATION_SERVICE_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-testnet/celo-monorepo" -ATTESTATION_SERVICE_DOCKER_IMAGE_TAG="attestation-service-v1.2.0" +ATTESTATION_SERVICE_DOCKER_IMAGE_TAG="attestation-service-v1.3.0" TELEKOM_FROM="+14157389085" SMS_PROVIDERS=twilio,nexmo,telekom @@ -253,7 +254,7 @@ AZURE_ODIS_EASTUS_3_PROM_SIDECAR_GCP_REGION=us-east1 # --- Komenci --- KOMENCI_DOCKER_IMAGE_REPOSITORY="celotestnet.azurecr.io/komenci/komenci" -KOMENCI_DOCKER_IMAGE_TAG="f1119833e7ad90ad741b2b509a1ad4ac647799ff" +KOMENCI_DOCKER_IMAGE_TAG="08081d2d276a6fd0d420805f3bbe3866e866a63a" AZURE_KOMENCI_EASTUS_AZURE_KUBERNETES_RESOURCE_GROUP=staging-komenci-eastus AZURE_KOMENCI_EASTUS_KUBERNETES_CLUSTER_NAME=staging-komenci-eastus @@ -273,6 +274,16 @@ AZURE_KOMENCI_WESTEU_KOMENCI_DB_PORT=5432 AZURE_KOMENCI_WESTEU_KOMENCI_DB_USERNAME=postgres@staging-komenci-weu AZURE_KOMENCI_WESTEU_KOMENCI_DB_PASSWORD_VAULT_NAME=staging-komenci-weu +AZURE_KOMENCI_EASTUS_KOMENCI_REWARD_SERVICE_DB_HOST=staging-komenci-weu.postgres.database.azure.com +AZURE_KOMENCI_EASTUS_KOMENCI_REWARD_SERVICE_DB_PORT=5432 +AZURE_KOMENCI_EASTUS_KOMENCI_REWARD_SERVICE_DB_USERNAME=postgres@staging-komenci-weu +AZURE_KOMENCI_EASTUS_KOMENCI_REWARD_SERVICE_DB_PASSWORD_VAULT_NAME=staging-komenci-weu + +AZURE_KOMENCI_WESTEU_KOMENCI_REWARD_SERVICE_DB_HOST=staging-komenci-weu.postgres.database.azure.com +AZURE_KOMENCI_WESTEU_KOMENCI_REWARD_SERVICE_DB_PORT=5432 +AZURE_KOMENCI_WESTEU_KOMENCI_REWARD_SERVICE_DB_USERNAME=postgres@staging-komenci-weu +AZURE_KOMENCI_WESTEU_KOMENCI_REWARD_SERVICE_DB_PASSWORD_VAULT_NAME=staging-komenci-weu + # Secrets AZURE_KOMENCI_EASTUS_KOMENCI_APP_SECRETS_VAULT_NAME=staging-komenci-eus AZURE_KOMENCI_WESTEU_KOMENCI_APP_SECRETS_VAULT_NAME=staging-komenci-weu @@ -284,8 +295,15 @@ AZURE_KOMENCI_WESTEU_KOMENCI_RULE_CONFIG_CAPTCHA_BYPASS_ENABLED=true # Format should be a comma-separated sequence of: #
:: -AZURE_KOMENCI_EASTUS_KOMENCI_ADDRESS_AZURE_KEY_VAULTS=0x00454cac6dae53f8800f71395b9a174f07a784b1:staging-komenci-eus,0xc6f0f9bfb1aed83620ece3eac0add98a65a8574e:staging-komenci-eus,0xd7fc8227642bfab9aa927066e5952fece574f0d6:staging-komenci-eus,0x04a444af9a79b6784bcd57c50ba1e051ba536ed4:staging-komenci-eus,0xbb5932e6b6a588cd1c6764f50d1fe410e6a2d71e:staging-komenci-eus,0xc934bff63a0db800acdf7061eb5cc03211e7bccf:staging-komenci-eus,0x409832bd2d72017f12cfaa3d6dc0103767bb7e7e:staging-komenci-eus,0x75222b1aed66393fa43c6454000e097363d85c73:staging-komenci-eus,0xefbc10d42f77c778431043bd3a34b283f90f3979:staging-komenci-eus,0x70b69157973cd31dae5dc68ee1891b9eae379c42:staging-komenci-eus +AZURE_KOMENCI_EASTUS_KOMENCI_ADDRESS_AZURE_KEY_VAULTS=0x00454cac6dae53f8800f71395b9a174f07a784b1:staging-komenci-eus,0xc6f0f9bfb1aed83620ece3eac0add98a65a8574e:staging-komenci-eus AZURE_KOMENCI_WESTEU_KOMENCI_ADDRESS_AZURE_KEY_VAULTS=0x0f812be74511b90ea6b2f80e77bea047e69a0b2a:staging-komenci-weu,0xb354d3d2908ba6a2b791683b0f454a38f69cb282:staging-komenci-weu +AZURE_KOMENCI_EASTUS_KOMENCI_CELOLABS_REWARDS_ADDRESS_AZURE_KEY_VAULTS=0xb04390478a57e3c2147599d5380434f25fa5234d:staging-komenci-rewards +AZURE_KOMENCI_WESTEU_KOMENCI_CELOLABS_REWARDS_ADDRESS_AZURE_KEY_VAULTS=0xb04390478a57e3c2147599d5380434f25fa5234d:staging-komenci-rewards + +# Celo Rewards +AZURE_KOMENCI_EASTUS_KOMENCI_REWARD_SERVICE_INSTANCE_COUNT = 1 +AZURE_KOMENCI_WESTEU_KOMENCI_REWARD_SERVICE_INSTANCE_COUNT = 1 +KOMENCI_SHOULD_SEND_REWARDS=true # Network AZURE_KOMENCI_EASTUS_KOMENCI_NETWORK=alfajores diff --git a/.env.baklava b/.env.baklava index ba9a1a6af1a..70b99fb2381 100644 --- a/.env.baklava +++ b/.env.baklava @@ -18,7 +18,7 @@ BLOCKSCOUT_DOCKER_IMAGE_TAG="0362f9f4d1d4842f27adb634d628f969f53c046d" BLOCKSCOUT_DB_SUFFIX=2 BLOCKSCOUT_SUBNETWORK_NAME="Baklava" BLOCKSCOUT_METADATA_CRAWLER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" -BLOCKSCOUT_METADATA_CRAWLER_IMAGE_TAG="metadata-crawler-007f29dc8ebf837543e6e28a14818076e88c69cd" +BLOCKSCOUT_METADATA_CRAWLER_IMAGE_TAG="metadata-crawler-77a392216d4927e85ce4b683508fc0539aa92a34" BLOCKSCOUT_METADATA_CRAWLER_SCHEDULE="*/30 * * * *" CELOSTATS_SERVER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celostats-server" @@ -73,6 +73,7 @@ AZURE_ORACLE_WESTUS2_FULL_NODES_DISK_SIZE=30 AZURE_ORACLE_WESTUS2_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" AZURE_ORACLE_WESTUS2_FULL_NODES_GETH_GC_MODE="full" AZURE_ORACLE_WESTUS2_FULL_NODES_USE_GSTORAGE_DATA=false +AZURE_ORACLE_WESTUS2_FULL_NODES_WS_PORT="8546" AZURE_ORACLE_CENTRALUS_AZURE_SUBSCRIPTION_ID=7a6f5f20-bd43-4267-8c35-a734efca140c AZURE_ORACLE_CENTRALUS_AZURE_TENANT_ID=7cb7628a-e37c-4afb-8332-2029e418980e @@ -90,6 +91,7 @@ AZURE_ORACLE_CENTRALUS_FULL_NODES_DISK_SIZE=30 AZURE_ORACLE_CENTRALUS_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" AZURE_ORACLE_CENTRALUS_FULL_NODES_GETH_GC_MODE="full" AZURE_ORACLE_CENTRALUS_FULL_NODES_USE_GSTORAGE_DATA=false +AZURE_ORACLE_CENTRALUS_FULL_NODES_WS_PORT="8546" # ---- Forno ---- @@ -118,6 +120,7 @@ GCP_FORNO_EUROPE_WEST1_FULL_NODES_STATIC_NODES_FILE_SUFFIX=gcp-europe-west1 GCP_FORNO_EUROPE_WEST1_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" GCP_FORNO_EUROPE_WEST1_FULL_NODES_GETH_GC_MODE="full" GCP_FORNO_EUROPE_WEST1_FULL_NODES_USE_GSTORAGE_DATA=false +GCP_FORNO_EUROPE_WEST1_FULL_NODES_WS_PORT="8546" TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_TAG="transaction-metrics-exporter-dc5e5dfa07231a4ff4664816a95eae606293eae9" diff --git a/.env.blockscoutstagingrc1 b/.env.blockscoutstagingrc1 new file mode 100644 index 00000000000..7390870a360 --- /dev/null +++ b/.env.blockscoutstagingrc1 @@ -0,0 +1,60 @@ +# Don't use "//" for comments in this file. +# This file is meant to be executed as a bash script for testing. +ENV_TYPE="development" + +GETH_VERBOSITY=2 +GETH_ENABLE_METRICS=false + +VM_BASED=true + +KUBERNETES_CLUSTER_NAME="celo-networks-dev" +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_DOCKER_IMAGE_TAG="0362f9f4d1d4842f27adb634d628f969f53c046d" +BLOCKSCOUT_DB_SUFFIX=3 +BLOCKSCOUT_SUBNETWORK_NAME="Celo" +BLOCKSCOUT_METADATA_CRAWLER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" +BLOCKSCOUT_METADATA_CRAWLER_IMAGE_TAG="metadata-crawler-77a392216d4927e85ce4b683508fc0539aa92a34" +BLOCKSCOUT_METADATA_CRAWLER_SCHEDULE="0 */2 * * *" +BLOCKSCOUT_OVERRIDE_RPC_ENDPOINT="http://mainnet-archive-service.blockscoutstagingrc1.svc.cluster.local:8545" +BLOCKSCOUT_OVERRIDE_WS_ENDPOINT="ws://mainnet-archive-service.blockscoutstagingrc1.svc.cluster.local:8545" +METADATA_CRAWLER_DISCORD_CLUSTER_NAME="Mainnet" + +CELOSTATS_SERVER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celostats-server" +CELOSTATS_SERVER_DOCKER_IMAGE_TAG="b4231e1b224f38153c1ccb4263eb08b8b687eb8c" +CELOSTATS_FRONTEND_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celostats-frontend" +CELOSTATS_FRONTEND_DOCKER_IMAGE_TAG="e659c81fe66ad3926a5c8cc46782bde731bb3280" +CELOSTATS_TRUSTED_ADDRESSES="" +CELOSTATS_BANNED_ADDRESSES="" +CELOSTATS_RESERVED_ADDRESSES="" + +GETH_NODE_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-org/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="1.3.2" + +GETH_BOOTNODE_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-org/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="1.3.2" + +CELOTOOL_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" +CELOTOOL_DOCKER_IMAGE_TAG="celotool-1e67ff7e2a9c5c17cd328af700cc0173f1ec3e4a" + +CELOCLI_STANDALONE_IMAGE_REPOSITORY="gcr.io/celo-testnet/celocli-standalone" +CELOCLI_STANDALONE_IMAGE_TAG="0.0.42" + +# Genesis Vars +NETWORK_ID=42220 +CONSENSUS_TYPE="istanbul" +BLOCK_TIME=5 +EPOCH=17280 +LOOKBACK=12 +ISTANBUL_REQUEST_TIMEOUT_MS=3000 + +# Nodes whose RPC ports are only internally exposed +PRIVATE_TX_NODES=4 diff --git a/.env.mnemonic.alfajores.enc b/.env.mnemonic.alfajores.enc index f2bca4330e7..20380532c90 100644 Binary files a/.env.mnemonic.alfajores.enc and b/.env.mnemonic.alfajores.enc differ diff --git a/.env.mnemonic.baklava.enc b/.env.mnemonic.baklava.enc index 196363bbde6..50f1142f374 100644 Binary files a/.env.mnemonic.baklava.enc and b/.env.mnemonic.baklava.enc differ diff --git a/.env.mnemonic.rc1.enc b/.env.mnemonic.rc1.enc index d73959fa31b..8f38d52b2c9 100644 Binary files a/.env.mnemonic.rc1.enc and b/.env.mnemonic.rc1.enc differ diff --git a/.env.rc1 b/.env.rc1 index 62704e8b315..838347b46f0 100644 --- a/.env.rc1 +++ b/.env.rc1 @@ -25,10 +25,10 @@ BLOCKSCOUT_DOCKER_IMAGE_TAG="0362f9f4d1d4842f27adb634d628f969f53c046d" BLOCKSCOUT_DB_SUFFIX=3 BLOCKSCOUT_SUBNETWORK_NAME="Celo" BLOCKSCOUT_METADATA_CRAWLER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" -BLOCKSCOUT_METADATA_CRAWLER_IMAGE_TAG="metadata-crawler-007f29dc8ebf837543e6e28a14818076e88c69cd" +BLOCKSCOUT_METADATA_CRAWLER_IMAGE_TAG="metadata-crawler-77a392216d4927e85ce4b683508fc0539aa92a34" BLOCKSCOUT_METADATA_CRAWLER_SCHEDULE="0 */2 * * *" BLOCKSCOUT_OVERRIDE_RPC_ENDPOINT="http://rc1-fullnodes-rpc.rc1.svc.cluster.local:8545" -BLOCKSCOUT_OVERRIDE_WS_ENDPOINT="ws://rc1-fullnodes-rpc.rc1.svc.cluster.local:8546" +BLOCKSCOUT_OVERRIDE_WS_ENDPOINT="ws://rc1-fullnodes-rpc.rc1.svc.cluster.local:8545" METADATA_CRAWLER_DISCORD_CLUSTER_NAME="Mainnet" CELOSTATS_SERVER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celostats-server" @@ -93,6 +93,7 @@ AZURE_ORACLE_WESTUS_FULL_NODES_DISK_SIZE=100 AZURE_ORACLE_WESTUS_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" AZURE_ORACLE_WESTUS_FULL_NODES_GETH_GC_MODE="full" AZURE_ORACLE_WESTUS_FULL_NODES_USE_GSTORAGE_DATA=false +AZURE_ORACLE_WESTUS_FULL_NODES_WS_PORT="8546" AZURE_ORACLE_WESTEUROPE_AZURE_SUBSCRIPTION_ID=7a6f5f20-bd43-4267-8c35-a734efca140c AZURE_ORACLE_WESTEUROPE_AZURE_TENANT_ID=7cb7628a-e37c-4afb-8332-2029e418980e @@ -109,6 +110,7 @@ AZURE_ORACLE_WESTEUROPE_FULL_NODES_DISK_SIZE=100 AZURE_ORACLE_WESTEUROPE_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" AZURE_ORACLE_WESTEUROPE_FULL_NODES_GETH_GC_MODE="full" AZURE_ORACLE_WESTEUROPE_FULL_NODES_USE_GSTORAGE_DATA=false +AZURE_ORACLE_WESTEUROPE_FULL_NODES_WS_PORT="8546" AZURE_ORACLE_EASTUS2_AZURE_SUBSCRIPTION_ID=7a6f5f20-bd43-4267-8c35-a734efca140c AZURE_ORACLE_EASTUS2_AZURE_TENANT_ID=7cb7628a-e37c-4afb-8332-2029e418980e @@ -125,6 +127,7 @@ AZURE_ORACLE_EASTUS2_FULL_NODES_DISK_SIZE=100 AZURE_ORACLE_EASTUS2_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" AZURE_ORACLE_EASTUS2_FULL_NODES_GETH_GC_MODE="full" AZURE_ORACLE_EASTUS2_FULL_NODES_USE_GSTORAGE_DATA=false +AZURE_ORACLE_EASTUS2_FULL_NODES_WS_PORT="8546" # ---- Forno ---- @@ -154,7 +157,7 @@ GCP_FORNO_US_WEST1_FULL_NODES_STATIC_NODES_FILE_SUFFIX=gcp-us-west1 GCP_FORNO_US_WEST1_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" GCP_FORNO_US_WEST1_FULL_NODES_GETH_GC_MODE="full" GCP_FORNO_US_WEST1_FULL_NODES_USE_GSTORAGE_DATA=true - +GCP_FORNO_US_WEST1_FULL_NODES_WS_PORT="8546" GCP_FORNO_US_EAST1_GCP_PROJECT_NAME=celo-testnet-production GCP_FORNO_US_EAST1_GCP_ZONE=us-east1-b @@ -170,7 +173,7 @@ GCP_FORNO_US_EAST1_FULL_NODES_STATIC_NODES_FILE_SUFFIX=gcp-us-east1 GCP_FORNO_US_EAST1_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" GCP_FORNO_US_EAST1_FULL_NODES_GETH_GC_MODE="full" GCP_FORNO_US_EAST1_FULL_NODES_USE_GSTORAGE_DATA=true - +GCP_FORNO_US_EAST1_FULL_NODES_WS_PORT="8546" GCP_FORNO_ASIA_EAST1_GCP_PROJECT_NAME=celo-testnet-production GCP_FORNO_ASIA_EAST1_GCP_ZONE=asia-east1-a @@ -186,6 +189,7 @@ GCP_FORNO_ASIA_EAST1_FULL_NODES_STATIC_NODES_FILE_SUFFIX=gcp-asia-east1 GCP_FORNO_ASIA_EAST1_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" GCP_FORNO_ASIA_EAST1_FULL_NODES_GETH_GC_MODE="full" GCP_FORNO_ASIA_EAST1_FULL_NODES_USE_GSTORAGE_DATA=true +GCP_FORNO_ASIA_EAST1_FULL_NODES_WS_PORT="8546" GCP_FORNO_EUROPE_WEST1_GCP_PROJECT_NAME=celo-testnet-production GCP_FORNO_EUROPE_WEST1_GCP_ZONE=europe-west1-b @@ -201,7 +205,7 @@ GCP_FORNO_EUROPE_WEST1_FULL_NODES_STATIC_NODES_FILE_SUFFIX=gcp-europe-west1 GCP_FORNO_EUROPE_WEST1_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" GCP_FORNO_EUROPE_WEST1_FULL_NODES_GETH_GC_MODE="full" GCP_FORNO_EUROPE_WEST1_FULL_NODES_USE_GSTORAGE_DATA=true - +GCP_FORNO_EUROPE_WEST1_FULL_NODES_WS_PORT="8546" GCP_FORNO_SOUTHAMERICA_EAST1_GCP_PROJECT_NAME=celo-testnet-production GCP_FORNO_SOUTHAMERICA_EAST1_GCP_ZONE=southamerica-east1-a @@ -217,12 +221,13 @@ GCP_FORNO_SOUTHAMERICA_EAST1_FULL_NODES_STATIC_NODES_FILE_SUFFIX=gcp-southameric GCP_FORNO_SOUTHAMERICA_EAST1_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3" GCP_FORNO_SOUTHAMERICA_EAST1_FULL_NODES_GETH_GC_MODE="full" GCP_FORNO_SOUTHAMERICA_EAST1_FULL_NODES_USE_GSTORAGE_DATA=true - +GCP_FORNO_SOUTHAMERICA_EAST1_FULL_NODES_WS_PORT="8546" # ---- Private txnodes context ---- +# CPU and memory requests can be customized in packages/helm-charts/celo-fullnode/rc1-gcp-private-txnodes-values.yaml GCP_PRIVATE_TXNODES_GCP_PROJECT_NAME=celo-testnet-production -GCP_PRIVATE_TXNODES_GCP_ZONE=us-west1-a +GCP_PRIVATE_TXNODES_GCP_ZONE=us-west1-a GCP_PRIVATE_TXNODES_KUBERNETES_CLUSTER_NAME=mainnet GCP_PRIVATE_TXNODES_FULL_NODES_COUNT=4 GCP_PRIVATE_TXNODES_FULL_NODES_ROLLING_UPDATE_PARTITION=0 @@ -230,6 +235,7 @@ GCP_PRIVATE_TXNODES_FULL_NODES_DISK_SIZE=300 GCP_PRIVATE_TXNODES_FULL_NODES_RPC_API_METHODS="eth,net,rpc,web3,txpool,debug" GCP_PRIVATE_TXNODES_FULL_NODES_GETH_GC_MODE="archive" GCP_PRIVATE_TXNODES_FULL_NODES_USE_GSTORAGE_DATA=false +GCP_PRIVATE_TXNODES_FULL_NODES_WS_PORT="8545" TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_REPOSITORY="gcr.io/celo-testnet/celo-monorepo" TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_TAG="transaction-metrics-exporter-f4a55e143932ea559cf4bcbd9bcccc14da43d6ed" @@ -241,7 +247,7 @@ TRANSACTION_METRICS_EXPORTER_DOCKER_IMAGE_TAG="transaction-metrics-exporter-f4a5 # EKSPORTISTO_SUFFIX='8' EKSPORTISTO_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-testnet/eksportisto" -EKSPORTISTO_DOCKER_IMAGE_TAG="d8790cff0a91fbed82009eaccec232548a91faf4" +EKSPORTISTO_DOCKER_IMAGE_TAG="b314442c7d86c8733088466b2eba67fcfe17a455" EKSPORTISTO_SUFFIX='11' ATTESTATION_SERVICE_DOCKER_IMAGE_REPOSITORY="us.gcr.io/celo-testnet/celo-monorepo" @@ -462,7 +468,7 @@ AZURE_ODIS_BRAZILSOUTH_A_PROM_SIDECAR_GCP_REGION=southamerica-east1-a # --- Komenci --- KOMENCI_DOCKER_IMAGE_REPOSITORY="celotestnet.azurecr.io/komenci/komenci" -KOMENCI_DOCKER_IMAGE_TAG="f1119833e7ad90ad741b2b509a1ad4ac647799ff" +KOMENCI_DOCKER_IMAGE_TAG="e220c5610e196a1d674edde0f24be0d5eca30c00" AZURE_KOMENCI_SOUTHBR_AZURE_KUBERNETES_RESOURCE_GROUP=mainnet-komenci-brazil AZURE_KOMENCI_SOUTHBR_KUBERNETES_CLUSTER_NAME=mainnet-komenci-brazil @@ -488,6 +494,16 @@ AZURE_SEA_KOMENCI_DB_PORT=5432 AZURE_SEA_KOMENCI_DB_USERNAME=postgres@mainnet-komenci-southeastasia AZURE_SEA_KOMENCI_DB_PASSWORD_VAULT_NAME=mainnet-komenci-sea +AZURE_KOMENCI_SOUTHBR_KOMENCI_REWARD_SERVICE_DB_HOST=mainnet-komenci-brazil.postgres.database.azure.com +AZURE_KOMENCI_SOUTHBR_KOMENCI_REWARD_SERVICE_DB_PORT=5432 +AZURE_KOMENCI_SOUTHBR_KOMENCI_REWARD_SERVICE_DB_USERNAME=postgres@mainnet-komenci-brazil +AZURE_KOMENCI_SOUTHBR_KOMENCI_REWARD_SERVICE_DB_PASSWORD_VAULT_NAME=mainnet-komenci-brazil + +AZURE_KOMENCI_SEA_KOMENCI_REWARD_SERVICE_DB_HOST=mainnet-komenci-brazil.postgres.database.azure.com +AZURE_KOMENCI_SEA_KOMENCI_REWARD_SERVICE_DB_PORT=5432 +AZURE_KOMENCI_SEA_KOMENCI_REWARD_SERVICE_DB_USERNAME=postgres@mainnet-komenci-brazil +AZURE_KOMENCI_SEA_KOMENCI_REWARD_SERVICE_DB_PASSWORD_VAULT_NAME=mainnet-komenci-brazil + # App Secrets AZURE_KOMENCI_SOUTHBR_KOMENCI_APP_SECRETS_VAULT_NAME=mainnet-komenci-brazil AZURE_KOMENCI_SEA_KOMENCI_APP_SECRETS_VAULT_NAME=mainnet-komenci-sea @@ -502,13 +518,20 @@ AZURE_KOMENCI_SEA_KOMENCI_RULE_CONFIG_CAPTCHA_BYPASS_ENABLED=false #
:: AZURE_KOMENCI_SOUTHBR_KOMENCI_ADDRESS_AZURE_KEY_VAULTS=0x21888ae301658cdff7ce8c33cdf83a330a5e6273:mainnet-relayer0,0x1438128a2dcc645f0b9706350c1f5dad04845fe6:mainnet-relayer1,0x1e36bf42272a0693eba69332a6f623ce37694a27:mainnet-relayer2,0xd5afaaa7256c9eb86376c4214635dd56dffbd3a8:mainnet-relayer3,0xb09eba8bc1c8bedadd634a8219c0b09042170903:mainnet-relayer4 AZURE_KOMENCI_SEA_KOMENCI_ADDRESS_AZURE_KEY_VAULTS=0x85a1e716608a84f455d7e07befb76c9b540ac040:mainnet-relayer5,0x2a094e77acf3faebb63279eb60e26d144b9048a2:mainnet-relayer6,0x2f23f9a8f68294a9d6b479c3dbe3dff4de510ced:mainnet-relayer7,0x3db3150c1267d3adeb7f960f3eef11c1dd47a38b:mainnet-relayer8,0xe170915ce32bb8e2ce2a4fcd9113e5298a2e10d2:mainnet-relayer9 +AZURE_KOMENCI_SOUTHBR_KOMENCI_CELOLABS_REWARDS_ADDRESS_AZURE_KEY_VAULTS=0x198e0D8601AB509ABf1B0B99Fd8f234583Ef1309:mainnet-komenci-rewards0 +AZURE_KOMENCI_SEA_KOMENCI_CELOLABS_REWARDS_ADDRESS_AZURE_KEY_VAULTS=0xbDD68B64e288171B37F01346042BEe6Eb7dFAE4f:mainnet-komenci-rewards1 + +# Celo Rewards +AZURE_KOMENCI_SOUTHBR_KOMENCI_REWARD_SERVICE_INSTANCE_COUNT=1 +AZURE_KOMENCI_SEA_KOMENCI_REWARD_SERVICE_INSTANCE_COUNT=1 +KOMENCI_SHOULD_SEND_REWARDS=false # Network AZURE_KOMENCI_SOUTHBR_KOMENCI_NETWORK=rc1 AZURE_KOMENCI_SEA_KOMENCI_NETWORK=rc1 # For WalletConnect relay -WALLET_CONNECT_IMAGE_REPOSITORY = 'us.gcr.io/celo-testnet/walletconnect' -WALLET_CONNECT_IMAGE_TAG = '1472bcaad57e3746498f7a661c42ff5cf9acaf5a' -WALLET_CONNECT_REDIS_CLUSTER_ENABLED = false -WALLET_CONNECT_REDIS_CLUSTER_USEPASSWORD = false +WALLET_CONNECT_IMAGE_REPOSITORY='us.gcr.io/celo-testnet/walletconnect' +WALLET_CONNECT_IMAGE_TAG='1472bcaad57e3746498f7a661c42ff5cf9acaf5a' +WALLET_CONNECT_REDIS_CLUSTER_ENABLED=false +WALLET_CONNECT_REDIS_CLUSTER_USEPASSWORD=false diff --git a/.github/workflows/env_tests.yml b/.github/workflows/env_tests.yml new file mode 100644 index 00000000000..4d0f613cde5 --- /dev/null +++ b/.github/workflows/env_tests.yml @@ -0,0 +1,100 @@ +name: env-tests + +# Run this workflow manually +on: + workflow_dispatch: + inputs: + network: + description: 'Network to run env-tests against. Possible values are (staging|baklava|alfajores|mainnet)' + required: true + +jobs: + env-tests: + runs-on: ubuntu-latest + environment: env-tests-environment + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup Cloud SDK + if: ${{ github.event.inputs.network == 'staging' }} + uses: google-github-actions/setup-gcloud@master + with: + project_id: 'celo-testnet' + service_account_key: ${{ secrets.GCP_SA_KEY }} + export_default_credentials: true + + - name: Setup port-forward + if: ${{ github.event.inputs.network == 'staging' }} + run: | + gcloud container clusters get-credentials celo-networks-dev --project celo-testnet --zone us-west1-a + kubectl config set-context --current --namespace default + kubectl port-forward --namespace=staging staging-validators-0 8545:8545 & + sleep 10 + + - name: Setup lightest node + if: ${{ github.event.inputs.network != 'staging' }} + run: | + export CELO_IMAGE=us.gcr.io/celo-org/geth:${{ github.event.inputs.network }} + docker pull $CELO_IMAGE + mkdir celo-data-dir + cd celo-data-dir + if [ ${{ github.event.inputs.network }} == 'mainnet' ] + then + export NETWORK_OPTION="" + else + export NETWORK_OPTION="--${{ github.event.inputs.network }}" + fi + docker run --name celo-lightestnode -d --restart unless-stopped --stop-timeout 300 -p 127.0.0.1:8545:8545 -p 127.0.0.1:8546:8546 -p 30303:30303 -p 30303:30303/udp -v $PWD:/root/.celo $CELO_IMAGE --verbosity 3 --syncmode lightest --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,web3,debug,admin,personal --light.serve 90 --light.maxpeers 1000 --maxpeers 1100 --nousb $NETWORK_OPTION --datadir /root/.celo + cd .. + + - name: Checking if node is synced + if: ${{ github.event.inputs.network != 'staging' }} + run: | + while true; do output=$(docker exec -i celo-lightestnode geth attach --exec "eth.syncing" | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"); echo $output; sleep 1; if [ $output == 'false' ]; then break; fi; done + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: '12' + + - name: Setup yarn + run: | + npm install --global yarn + yarn + yarn lerna bootstrap + + - name: Build packages + run: yarn lerna run build --ignore docs + + - name: Run the env tests + run: | + cd packages/env-tests/ + export TWILIO_ACCOUNT_AUTH_TOKEN=${{ secrets.TWILIO_ACCOUNT_AUTH_TOKEN }} + export TWILIO_ACCOUNT_SID=${{ secrets.TWILIO_ACCOUNT_SID }} + case ${{ github.event.inputs.network }} in + 'staging') + export TWILIO_ADDRESS_SID=${{ secrets.STAGING_TWILIO_ADDRESS_SID }} + export MNEMONIC="${{ secrets.STAGING_MNEMONIC }}" + export RESERVE_SPENDER_MULTISIG_ADDRESS=0x64a7C6C6Ab63fb2b617849B579e555033BC551cb + env CELO_PROVIDER=http://127.0.0.1:8545 ./node_modules/.bin/jest --runInBand + ;; + 'alfajores') + export TWILIO_ADDRESS_SID=${{ secrets.ALFAJORES_TWILIO_ADDRESS_SID }} + export MNEMONIC="${{ secrets.ALFAJORES_MNEMONIC }}" + export RESERVE_SPENDER_MULTISIG_ADDRESS=0x64a7C6C6Ab63fb2b617849B579e555033BC551cb + env CELO_PROVIDER=http://127.0.0.1:8545 ./node_modules/.bin/jest --runInBand --testNamePattern='^(?!.*((report a rate)|(move funds from the Reserve to a custodian and back))).*$' + ;; + 'baklava') + export TWILIO_ADDRESS_SID=${{ secrets.BAKLAVA_TWILIO_ADDRESS_SID }} + export MNEMONIC="${{ secrets.BAKLAVA_MNEMONIC }}" + export RESERVE_SPENDER_MULTISIG_ADDRESS=0x57d0A6Ce0478074Fe577259D8cb07AfFb6D408f5 + env CELO_PROVIDER=http://127.0.0.1:8545 ./node_modules/.bin/jest --runInBand --testNamePattern='^(?!.*((report a rate)|(move funds from the Reserve to a custodian and back))).*$' + ;; + 'mainnet') + export TWILIO_ADDRESS_SID=${{ secrets.MAINNET_TWILIO_ADDRESS_SID }} + export MNEMONIC="${{ secrets.MAINNET_MNEMONIC }}" + export RESERVE_SPENDER_MULTISIG_ADDRESS=0x554Fca0f7c465cd2F8C305a10bF907A2034d2a19 + env CELO_PROVIDER=http://127.0.0.1:8545 ./node_modules/.bin/jest --runInBand --testNamePattern='^(?!.*((report a rate)|(move funds from the Reserve to a custodian and back))).*$' + ;; + esac \ No newline at end of file diff --git a/SETUP.md b/SETUP.md index 1d4bf217914..d1cecb85b58 100644 --- a/SETUP.md +++ b/SETUP.md @@ -50,6 +50,8 @@ export PATH=$PATH:$GOPATH/bin #### Install Node +Currently Node.js v12.x is required in order to work with this repo. + Install `nvm` (allows you to manage multiple versions of Node) by following the instructions here: [https://github.com/nvm-sh/nvm]. Once `nvm` is successfully installed, restart the terminal and run the following commands to install the `npm` versions that [celo-monorepo] will need: diff --git a/dockerfiles/phone-number-privacy/Dockerfile b/dockerfiles/phone-number-privacy/Dockerfile index 60116f081df..e7f41d6b6bd 100644 --- a/dockerfiles/phone-number-privacy/Dockerfile +++ b/dockerfiles/phone-number-privacy/Dockerfile @@ -2,10 +2,13 @@ FROM node:12 WORKDIR /celo-phone-number-privacy/ -COPY packages/phone-number-privacy/signer signer +#Copy monorepo settings +COPY lerna.json package.json yarn.lock ./ +COPY scripts/ scripts/ +COPY patches/ patches/ -WORKDIR /celo-phone-number-privacy/signer -COPY yarn.lock ./ +#Copy identity code +COPY packages/phone-number-privacy packages/phone-number-privacy RUN yarn install --network-timeout 100000 && yarn cache clean @@ -13,5 +16,6 @@ ENV NODE_ENV production RUN yarn build +WORKDIR /celo-phone-number-privacy/packages/phone-number-privacy/signer EXPOSE 8080 -ENTRYPOINT ["yarn", "start:docker"] +ENTRYPOINT ["yarn", "start:docker"] \ No newline at end of file diff --git a/package.json b/package.json index bcd07ba51bf..f33a3fbaeb8 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "react-native-ntp-client": "^1.0.0", "set-value": "^3.0.2", "sha3": "1.2.3", - "tar": "4.4.10", + "tar": "4.4.15", "ua-parser-js": "^0.7.24", "underscore": "^1.12.1", "url-parse": "^1.5.0", diff --git a/packages/attestation-service/config/.env.development b/packages/attestation-service/config/.env.development index c25153d6e27..efcce5c0caf 100644 --- a/packages/attestation-service/config/.env.development +++ b/packages/attestation-service/config/.env.development @@ -7,7 +7,7 @@ ATTESTATION_SIGNER_ADDRESS=x # List all SMS providers here. They are tried from first to last, unless # you specify SMS_PROVIDERS_RANDOMIZED=1, in which case they are tried in a random order. -SMS_PROVIDERS=messagebird,twilio,nexmo +SMS_PROVIDERS=twilio # SMS_PROVIDERS_RANDOMIZED=0 # Optional: set a different list or order of providers for a specific country. @@ -43,6 +43,7 @@ NEXMO_UNSUPPORTED_REGIONS=CU,SY,KP,IR,SD # Twilio parameters (fill in values for 'x') TWILIO_ACCOUNT_SID=x TWILIO_MESSAGING_SERVICE_SID=x +TWILIO_VERIFY_SERVICE_SID=x TWILIO_AUTH_TOKEN=x TWILIO_UNSUPPORTED_REGIONS=CU,SY,KP,IR,SD diff --git a/packages/attestation-service/migrations/20210625231054-attestation-v7.js b/packages/attestation-service/migrations/20210625231054-attestation-v7.js new file mode 100644 index 00000000000..e777c76c24f --- /dev/null +++ b/packages/attestation-service/migrations/20210625231054-attestation-v7.js @@ -0,0 +1,32 @@ +'use strict' +module.exports = { + up: async (queryInterface, Sequelize) => { + const transaction = await queryInterface.sequelize.transaction() + + try { + await queryInterface.addColumn('Attestations', 'appSignature', { + type: Sequelize.STRING, + allowNull: true, + }) + await queryInterface.addColumn('Attestations', 'language', { + type: Sequelize.STRING, + allowNull: true, + }) + + await transaction.commit() + } catch (error) { + await transaction.rollback() + throw error + } + }, + down: async (queryInterface, Sequelize) => { + const transaction = await queryInterface.sequelize.transaction() + try { + await queryInterface.removeColumn('Attestations', 'appSignature') + await queryInterface.removeColumn('Attestations', 'language') + } catch (error) { + await transaction.rollback() + throw error + } + }, +} diff --git a/packages/attestation-service/package.json b/packages/attestation-service/package.json index 54a67f55766..701e088b4c3 100644 --- a/packages/attestation-service/package.json +++ b/packages/attestation-service/package.json @@ -1,6 +1,6 @@ { "name": "@celo/attestation-service", - "version": "1.2.2", + "version": "1.3.0", "description": "Issues attestation messages for Celo's identity protocol", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -27,8 +27,8 @@ "lint": "tslint -c tslint.json --project ." }, "dependencies": { - "@celo/contractkit": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", + "@celo/contractkit": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", "bignumber.js": "^9.0.0", "body-parser": "1.19.0", "bunyan": "1.8.12", diff --git a/packages/attestation-service/src/models/attestation.ts b/packages/attestation-service/src/models/attestation.ts index da197085b6b..d5cefade928 100644 --- a/packages/attestation-service/src/models/attestation.ts +++ b/packages/attestation-service/src/models/attestation.ts @@ -24,6 +24,8 @@ export interface AttestationModel extends Model { recordError: (error: string) => void failure: () => boolean currentError: () => string | undefined + appSignature: string | undefined + language: string | undefined } export interface AttestationKey { @@ -64,6 +66,8 @@ export default (sequelize: Sequelize) => { errors: DataTypes.STRING, createdAt: DataTypes.DATE, completedAt: DataTypes.DATE, + appSignature: DataTypes.STRING, + language: DataTypes.STRING, }) as AttestationStatic model.prototype.key = function (): AttestationKey { diff --git a/packages/attestation-service/src/requestHandlers/attestation.ts b/packages/attestation-service/src/requestHandlers/attestation.ts index 1d6b21f0de2..af50cbec053 100644 --- a/packages/attestation-service/src/requestHandlers/attestation.ts +++ b/packages/attestation-service/src/requestHandlers/attestation.ts @@ -152,35 +152,41 @@ class AttestationRequestHandler { // Main process for handling an attestation. async doAttestation() { Counters.attestationRequestsTotal.inc() + if ( + !this.attestationRequest.securityCodePrefix || + this.attestationRequest.securityCodePrefix.length !== 1 + ) { + throw new ErrorWithResponse('Invalid securityCodePrefix', 422) + } + let attestation = await this.findOrValidateRequest() if (attestation && attestation.message) { // Re-request existing attestation. In this case, security code prefix is ignored (the message sent is the same as before) - attestation = await rerequestAttestation(this.key, this.logger, this.sequelizeLogger) + attestation = await rerequestAttestation( + this.key, + this.attestationRequest.smsRetrieverAppSig, + this.attestationRequest.language, + this.attestationRequest.securityCodePrefix, + this.logger, + this.sequelizeLogger + ) } else { // New attestation: create new attestation code, new delivery. const attestationCode = await this.signAttestation() await this.validateAttestationCode(attestationCode) + // This attestation code is stored in the attestation object + // and will be returned to the user with the get_attestation call const attestationCodeDeeplink = `celo://wallet/v/${toBase64(attestationCode)}` - // Determine if we're sending a security code, or the full deep link. let messageBase, securityCode - if (this.attestationRequest.securityCodePrefix) { - if (this.attestationRequest.securityCodePrefix.length !== 1) { - throw new ErrorWithResponse('Invalid securityCodePrefix', 422) - } - // Client is requesting a security code SMS. Generate a challenge and just store the deeplink. - securityCode = randomBytes(7) - .map((x) => x % 10) - .join('') - messageBase = `${getSecurityCodeText(this.attestationRequest.language)}: ${ - this.attestationRequest.securityCodePrefix - }${securityCode}` - } else { - // Client is requesting direct SMS with the deeplink. - messageBase = attestationCodeDeeplink - } + // Generate a security code to be sent over SMS + securityCode = randomBytes(7) + .map((x) => x % 10) + .join('') + securityCode = `${this.attestationRequest.securityCodePrefix}${securityCode}` + messageBase = `${getSecurityCodeText(this.attestationRequest.language)}: ${securityCode}` let textMessage @@ -200,6 +206,8 @@ class AttestationRequestHandler { textMessage, securityCode, attestationCodeDeeplink, + this.attestationRequest.smsRetrieverAppSig, + this.attestationRequest.language, this.logger, this.sequelizeLogger ) diff --git a/packages/attestation-service/src/requestHandlers/get_attestation.ts b/packages/attestation-service/src/requestHandlers/get_attestation.ts index 8bc586d1e3e..60a292230b3 100644 --- a/packages/attestation-service/src/requestHandlers/get_attestation.ts +++ b/packages/attestation-service/src/requestHandlers/get_attestation.ts @@ -77,7 +77,12 @@ class GetAttestationRequestHandler { } // Security code is supplied. Check it's correct. - if (attestation.securityCode === this.getRequest.securityCode) { + // Check with both methods (can remove second method after 1.3.0) + if ( + attestation.securityCode && + (attestation.securityCode.slice(1) === this.getRequest.securityCode || + attestation.securityCode === this.getRequest.securityCode) + ) { callback(attestation, attestation.attestationCode) await transaction.commit() return diff --git a/packages/attestation-service/src/requestHandlers/status.ts b/packages/attestation-service/src/requestHandlers/status.ts index b4adcfbee1e..8a906ad24f2 100644 --- a/packages/attestation-service/src/requestHandlers/status.ts +++ b/packages/attestation-service/src/requestHandlers/status.ts @@ -53,6 +53,7 @@ export async function handleStatusRequest( 10 ), maxRerequestMins: parseInt(fetchEnvOrDefault('MAX_REREQUEST_MINS', '55'), 10), + twilioVerifySidProvided: !!fetchEnvOrDefault('TWILIO_VERIFY_SERVICE_SID', ''), }) ) .status(200) diff --git a/packages/attestation-service/src/requestHandlers/test_attestation.ts b/packages/attestation-service/src/requestHandlers/test_attestation.ts index feddae91241..c9d45a1d7ed 100644 --- a/packages/attestation-service/src/requestHandlers/test_attestation.ts +++ b/packages/attestation-service/src/requestHandlers/test_attestation.ts @@ -54,8 +54,10 @@ export async function handleTestAttestationRequest( key, testRequest.phoneNumber, testRequest.message, - null, + '12345678', testRequest.message, + undefined, + undefined, logger, sequelizeLogger, testRequest.provider diff --git a/packages/attestation-service/src/sms/index.ts b/packages/attestation-service/src/sms/index.ts index e9c10c642db..aa909713256 100644 --- a/packages/attestation-service/src/sms/index.ts +++ b/packages/attestation-service/src/sms/index.ts @@ -203,6 +203,8 @@ export async function startSendSms( messageToSend: string, securityCode: string | null = null, attestationCode: string | null = null, + appSignature: string | undefined, + language: string | undefined, logger: Logger, sequelizeLogger: SequelizeLogger, onlyUseProvider: string | null = null @@ -243,6 +245,8 @@ export async function startSendSms( ongoingDeliveryId: null, securityCode, securityCodeAttempt: 0, + appSignature, + language, }, transaction ) @@ -285,6 +289,9 @@ export async function startSendSms( // immediately if there are sufficient attempts remaining. export async function rerequestAttestation( key: AttestationKey, + appSignature: string | undefined, + language: string | undefined, + securityCodePrefix: string, logger: Logger, sequelizeLogger: SequelizeLogger ): Promise { @@ -301,6 +308,18 @@ export async function rerequestAttestation( if (!attestation) { throw new Error('Cannot retrieve attestation') } + // For backward compatibility + // Can be removed after 1.3.0 + if (!attestation.appSignature) { + attestation.appSignature = appSignature + } + if (!attestation.language) { + attestation.language = language + } + // Old security code approach did not store the prefix + if (attestation.securityCode?.length === 7) { + attestation.securityCode = `${securityCodePrefix}${attestation.securityCode}` + } if (attestation.completedAt) { const completedAgo = Date.now() - attestation.completedAt!.getTime() diff --git a/packages/attestation-service/src/sms/twilio.ts b/packages/attestation-service/src/sms/twilio.ts index 0b5b6b578cd..d4e1cced30e 100644 --- a/packages/attestation-service/src/sms/twilio.ts +++ b/packages/attestation-service/src/sms/twilio.ts @@ -2,7 +2,7 @@ import bodyParser from 'body-parser' import Logger from 'bunyan' import express from 'express' import twilio, { Twilio } from 'twilio' -import { fetchEnv } from '../env' +import { fetchEnv, fetchEnvOrDefault } from '../env' import { AttestationModel, AttestationStatus } from '../models/attestation' import { readUnsupportedRegionsFromEnv, SmsProvider, SmsProviderType } from './base' import { receivedDeliveryReport } from './index' @@ -12,6 +12,7 @@ export class TwilioSmsProvider extends SmsProvider { return new TwilioSmsProvider( fetchEnv('TWILIO_ACCOUNT_SID'), fetchEnv('TWILIO_MESSAGING_SERVICE_SID'), + fetchEnvOrDefault('TWILIO_VERIFY_SERVICE_SID', ''), fetchEnv('TWILIO_AUTH_TOKEN'), readUnsupportedRegionsFromEnv('TWILIO_UNSUPPORTED_REGIONS', 'TWILIO_BLACKLIST') ) @@ -19,18 +20,60 @@ export class TwilioSmsProvider extends SmsProvider { client: Twilio messagingServiceSid: string + verifyServiceSid: string type = SmsProviderType.TWILIO deliveryStatusURL: string | undefined + // https://www.twilio.com/docs/verify/api/verification#start-new-verification + twilioSupportedLocales = [ + 'af', + 'ar', + 'ca', + 'cs', + 'da', + 'de', + 'el', + 'en', + 'en-gb', + 'es', + 'fi', + 'fr', + 'he', + 'hi', + 'hr', + 'hu', + 'id', + 'it', + 'ja', + 'ko', + 'ms', + 'nb', + 'nl', + 'pl', + 'pt', + 'pr-br', + 'ro', + 'ru', + 'sv', + 'th', + 'tl', + 'tr', + 'vi', + 'zh', + 'zh-cn', + 'zh-hk', + ] constructor( twilioSid: string, messagingServiceSid: string, + verifyServiceSid: string, twilioAuthToken: string, unsupportedRegionCodes: string[] ) { super() this.client = twilio(twilioSid, twilioAuthToken) this.messagingServiceSid = messagingServiceSid + this.verifyServiceSid = verifyServiceSid this.unsupportedRegionCodes = unsupportedRegionCodes } @@ -76,15 +119,76 @@ export class TwilioSmsProvider extends SmsProvider { } catch (error) { throw new Error(`Twilio Messaging Service could not be fetched: ${error}`) } + if (this.verifyServiceSid) { + try { + await this.client.verify.services + .get(this.verifyServiceSid) + .fetch() + .then((service) => { + if (!service.customCodeEnabled) { + // Make sure that custom code is enabled + throw new Error( + 'TWILIO_VERIFY_SERVICE_SID is specified, but customCode is not enabled. Please contact Twilio support to enable it.' + ) + } + }) + } catch (error) { + throw new Error(`Twilio Verify Service could not be fetched: ${error}`) + } + } } async sendSms(attestation: AttestationModel) { - const m = await this.client.messages.create({ - body: attestation.message, - to: attestation.phoneNumber, - from: this.messagingServiceSid, - statusCallback: this.deliveryStatusURL, - }) - return m.sid + // Prefer Verify API if Verify Service is present + if (this.verifyServiceSid) { + const requestParams: any = { + to: attestation.phoneNumber, + channel: 'sms', + customCode: attestation.securityCode, + } + + // This param tells Twilio to add the <#> prefix and app hash postfix + if (attestation.appSignature) { + requestParams.appHash = attestation.appSignature + } + // Normalize to locales that Twilio supports + // If locale is not supported, Twilio API will throw an error + if (attestation.language) { + const locale = attestation.language.toLocaleLowerCase() + if (['es-419', 'es-us', 'es-la'].includes(locale)) { + attestation.language = 'es' + } + if (this.twilioSupportedLocales.includes(locale)) { + requestParams.locale = locale + } + } + try { + const m = await this.client.verify + .services(this.verifyServiceSid) + .verifications.create(requestParams) + return m.sid + } catch (e) { + // Verify landlines using voice + if (e.message.includes('SMS is not supported by landline phone number')) { + requestParams.appHash = undefined + requestParams.channel = 'call' + const m = await this.client.verify + .services(this.verifyServiceSid) + .verifications.create(requestParams) + return m.sid + } else { + throw e + } + } + } else { + // Send using the message service + const m = await this.client.messages.create({ + body: attestation.message, + to: attestation.phoneNumber, + from: this.messagingServiceSid, + statusCallback: this.deliveryStatusURL, + }) + return m.sid + } } } diff --git a/packages/azure/scripts/provider-init.sh b/packages/azure/scripts/provider-init.sh index 56da9c38d89..bd0332fd109 100644 --- a/packages/azure/scripts/provider-init.sh +++ b/packages/azure/scripts/provider-init.sh @@ -27,6 +27,7 @@ PROXY_URL="enode://${proxyPublicKey}@${proxyInternalIpAddress}:30503;enode://${p SMS_PROVIDERS=twilio ATTESTER_TWILIO_ACCOUNT_SID=${attesterTwilioAccountSID} ATTESTER_TWILIO_MESSAGE_SERVICE_SID=${attesterTwilioMessageServiceSID} +ATTESTER_TWILIO_VERIFY_SERVICE_SID=${attesterTwilioVerifyServiceSID} ATTESTER_DB_USERNAME="${attesterPostgreSQLUsername}@${attesterDBName}" ATTESTER_DB_HOSTNAME="${attesterDBName}.postgres.database.azure.com" EOF diff --git a/packages/celotool/package.json b/packages/celotool/package.json index c04fdb55505..6274253f0a5 100644 --- a/packages/celotool/package.json +++ b/packages/celotool/package.json @@ -6,16 +6,16 @@ "author": "Celo", "license": "Apache-2.0", "dependencies": { - "@celo/base": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", - "@celo/contractkit": "1.2.2-dev", + "@celo/base": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", + "@celo/contractkit": "1.2.3-dev", "@celo/env-tests": "1.0.0", "@types/ethereumjs-util": "^5.2.0", - "@celo/explorer": "1.2.2-dev", - "@celo/governance": "1.2.2-dev", - "@celo/identity": "1.2.2-dev", - "@celo/network-utils": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", + "@celo/explorer": "1.2.3-dev", + "@celo/governance": "1.2.3-dev", + "@celo/identity": "1.2.3-dev", + "@celo/network-utils": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", "@google-cloud/monitoring": "0.7.1", "@google-cloud/pubsub": "^0.28.1", "@google-cloud/storage": "^2.4.3", diff --git a/packages/celotool/src/cmds/deploy/initial/load-test.ts b/packages/celotool/src/cmds/deploy/initial/load-test.ts index 869d37c1234..44b340ed96b 100644 --- a/packages/celotool/src/cmds/deploy/initial/load-test.ts +++ b/packages/celotool/src/cmds/deploy/initial/load-test.ts @@ -1,6 +1,6 @@ import { switchToClusterFromEnv } from 'src/lib/cluster' -import { CeloEnvArgv, envVar, fetchEnv } from 'src/lib/env-utils' -import { installHelmChart } from 'src/lib/load-test' +import { CeloEnvArgv } from 'src/lib/env-utils' +import { installHelmChart, setArgvDefaults } from 'src/lib/load-test' import yargs from 'yargs' export const command = 'load-test' @@ -11,6 +11,7 @@ export interface LoadTestArgv extends CeloEnvArgv { blockscoutMeasurePercent: number delay: number replicas: number + threads: number } export const builder = (argv: yargs.Argv) => { @@ -33,22 +34,23 @@ export const builder = (argv: yargs.Argv) => { 'Number of load test clients to create, defaults to LOAD_TEST_CLIENTS in the .env file', default: -1, }) -} - -export function setArgvDefaults(argv: LoadTestArgv) { - // Variables from the .env file are not set as environment variables - // by the time the builder is run, so we set the default here - if (argv.delay < 0) { - argv.delay = parseInt(fetchEnv(envVar.LOAD_TEST_TX_DELAY_MS), 10) - } - if (argv.replicas < 0) { - argv.replicas = parseInt(fetchEnv(envVar.LOAD_TEST_CLIENTS), 10) - } + .option('threads', { + type: 'number', + description: + 'Number of threads for each client, defaults to LOAD_TEST_THREADS in the .env file', + default: -1, + }) } export const handler = async (argv: LoadTestArgv) => { await switchToClusterFromEnv(argv.celoEnv) setArgvDefaults(argv) - await installHelmChart(argv.celoEnv, argv.blockscoutMeasurePercent, argv.delay, argv.replicas) + await installHelmChart( + argv.celoEnv, + argv.blockscoutMeasurePercent, + argv.delay, + argv.replicas, + argv.threads + ) } diff --git a/packages/celotool/src/cmds/deploy/upgrade/load-test.ts b/packages/celotool/src/cmds/deploy/upgrade/load-test.ts index 06f287715f6..7ff50139dce 100644 --- a/packages/celotool/src/cmds/deploy/upgrade/load-test.ts +++ b/packages/celotool/src/cmds/deploy/upgrade/load-test.ts @@ -1,11 +1,7 @@ -import { - builder as initialBuilder, - LoadTestArgv, - setArgvDefaults, -} from 'src/cmds/deploy/initial/load-test' +import { builder as initialBuilder, LoadTestArgv } from 'src/cmds/deploy/initial/load-test' import { switchToClusterFromEnv } from 'src/lib/cluster' import { isCelotoolHelmDryRun } from 'src/lib/helm_deploy' -import { resetAndUpgrade, upgradeHelmChart } from 'src/lib/load-test' +import { resetAndUpgrade, setArgvDefaults, upgradeHelmChart } from 'src/lib/load-test' import yargs from 'yargs' export const command = 'load-test' @@ -29,8 +25,20 @@ export const handler = async (argv: LoadTestUpgradeArgv) => { setArgvDefaults(argv) if (argv.reset === true && !isCelotoolHelmDryRun()) { - await resetAndUpgrade(argv.celoEnv, argv.blockscoutMeasurePercent, argv.delay, argv.replicas) + await resetAndUpgrade( + argv.celoEnv, + argv.blockscoutMeasurePercent, + argv.delay, + argv.replicas, + argv.threads + ) } else { - await upgradeHelmChart(argv.celoEnv, argv.blockscoutMeasurePercent, argv.delay, argv.replicas) + await upgradeHelmChart( + argv.celoEnv, + argv.blockscoutMeasurePercent, + argv.delay, + argv.replicas, + argv.threads + ) } } diff --git a/packages/celotool/src/cmds/generate/address-from-env.ts b/packages/celotool/src/cmds/generate/address-from-env.ts index 16580351304..7a51ce54816 100644 --- a/packages/celotool/src/cmds/generate/address-from-env.ts +++ b/packages/celotool/src/cmds/generate/address-from-env.ts @@ -9,8 +9,7 @@ import yargs from 'yargs' export const command = 'address-from-env' -export const describe = - 'command for fetching addresses for the different account types using the environment mnemonic' +export const describe = 'command for fetching addresses as specified by the current environment' interface AccountAddressArgv extends CeloEnvArgv { index: number diff --git a/packages/celotool/src/cmds/generate/faucet-load-test.ts b/packages/celotool/src/cmds/generate/faucet-load-test.ts new file mode 100644 index 00000000000..230438f082f --- /dev/null +++ b/packages/celotool/src/cmds/generate/faucet-load-test.ts @@ -0,0 +1,101 @@ +/* tslint:disable no-console */ +import { newKit } from '@celo/contractkit' +import { switchToClusterFromEnv } from 'src/lib/cluster' +import { convertToContractDecimals } from 'src/lib/contract-utils' +import { addCeloEnvMiddleware, CeloEnvArgv, envVar, fetchEnv } from 'src/lib/env-utils' +import { AccountType, generateAddress } from 'src/lib/generate_utils' +import { getIndexForLoadTestThread } from 'src/lib/geth' +import { portForwardAnd } from 'src/lib/port_forward' +import yargs from 'yargs' + +interface FaucetLoadTest extends CeloEnvArgv { + gold: number + dollars: number + replica_from: number + replica_to: number + threads_from: number + threads_to: number +} + +export const command = 'faucet-load-test' + +export const describe = 'command for fauceting the addresses used for load testing' + +export const builder = (argv: yargs.Argv) => { + return addCeloEnvMiddleware( + argv + .option('gold', { + type: 'number', + description: 'Celo Gold amount to transfer', + default: 10, + }) + .option('dollars', { + type: 'number', + description: 'Celo Dollars amount to transfer', + default: 10, + }) + .option('replica_from', { + type: 'number', + description: 'Index count from', + demandOption: 'Please specify a key index', + }) + .option('replica_to', { + type: 'number', + description: 'Index count to', + demandOption: 'Please specify a key index', + }) + .option('threads_from', { + type: 'number', + description: 'Index of key to generate', + demandOption: 'Please specify a key threads_from', + }) + .option('threads_to', { + type: 'number', + description: 'Index of key to generate', + demandOption: 'Please specify a key threads_to', + }) + ) +} + +export const handler = async (argv: CeloEnvArgv & FaucetLoadTest) => { + await switchToClusterFromEnv(argv.celoEnv) + const accountType = AccountType.LOAD_TESTING_ACCOUNT + const mnemonic = fetchEnv(envVar.MNEMONIC) + + const cb = async () => { + const kit = newKit('http://localhost:8545') + const account = (await kit.web3.eth.getAccounts())[0] + console.log(`Using account: ${account}`) + kit.defaultAccount = account + + const [goldToken, stableToken] = await Promise.all([ + kit.contracts.getGoldToken(), + kit.contracts.getStableToken(), + ]) + + const [goldAmount, stableTokenAmount] = await Promise.all([ + convertToContractDecimals(argv.gold, goldToken), + convertToContractDecimals(argv.dollars, stableToken), + ]) + + for (let podIndex = argv.replica_from; podIndex <= argv.replica_to; podIndex++) { + for (let threadIndex = argv.threads_from; threadIndex <= argv.threads_to; threadIndex++) { + const index = getIndexForLoadTestThread(podIndex, threadIndex) + const address = generateAddress(mnemonic, accountType, index) + console.log(`${index} --> Fauceting ${goldAmount.toFixed()} Gold to ${address}`) + await goldToken.transfer(address, goldAmount.toFixed()).send() + console.log(`${index} --> Fauceting ${stableTokenAmount.toFixed()} Dollars to ${address}`) + await stableToken.transfer(address, stableTokenAmount.toFixed()).send() + } + } + } + + try { + await portForwardAnd(argv.celoEnv, cb) + await cb + } catch (error) { + console.error(`Unable to faucet load-test accounts on ${argv.celoEnv}`) + console.error(error) + process.exit(1) + } +} diff --git a/packages/celotool/src/cmds/generate/prepare-load-test-client.ts b/packages/celotool/src/cmds/generate/prepare-load-test-client.ts new file mode 100644 index 00000000000..07da73f9a64 --- /dev/null +++ b/packages/celotool/src/cmds/generate/prepare-load-test-client.ts @@ -0,0 +1,50 @@ +/* tslint:disable no-console */ +import * as fs from 'fs' +import { AccountType, generatePrivateKey, privateKeyToAddress } from 'src/lib/generate_utils' +import yargs from 'yargs' + +interface Bip32Argv { + mnemonic: string + index: number + threads: number +} + +export const command = 'prepare-load-test' + +export const describe = + 'command for generating public and private keys for a load test instance. Expected to run inside the loadtest pod' + +export const builder = (argv: yargs.Argv) => { + return argv + .option('mnemonic', { + type: 'string', + description: 'BIP-39 mnemonic', + demandOption: 'Please specify a mnemonic from which to derive a private key', + alias: 'm', + }) + .option('index', { + type: 'number', + description: 'Index of key to generate', + demandOption: 'Please specify a key index', + alias: 'i', + }) + .option('threads', { + type: 'number', + description: 'The number of threads', + demandOption: 'Please specify the number of threads of this node', + alias: 't', + }) +} + +export const handler = async (argv: Bip32Argv) => { + const accountType = AccountType.LOAD_TESTING_ACCOUNT + for (let t = 0; t < argv.threads; t++) { + const index = argv.index * 10000 + t + + const privateKey = generatePrivateKey(argv.mnemonic, accountType, index) + const address = privateKeyToAddress(privateKey) + fs.writeFileSync(`/root/.celo/pkey${t}`, `${privateKey}\n`) + fs.appendFileSync(`/root/.celo/address`, `${address}\n`) + console.log(`Address for index ${argv.index} and thread ${t} --> ${address}`) + } +} diff --git a/packages/celotool/src/cmds/geth/simulate_client.ts b/packages/celotool/src/cmds/geth/simulate_client.ts index c1eafbd2c3b..f37a6591b48 100644 --- a/packages/celotool/src/cmds/geth/simulate_client.ts +++ b/packages/celotool/src/cmds/geth/simulate_client.ts @@ -1,8 +1,7 @@ -import sleep from 'sleep-promise' +/* tslint:disable no-console */ import { AccountType, generateAddress } from 'src/lib/generate_utils' -import { simulateClient } from 'src/lib/geth' +import { getIndexForLoadTestThread, simulateClient } from 'src/lib/geth' import * as yargs from 'yargs' - export const command = 'simulate-client' export const describe = 'command for simulating client behavior' @@ -14,6 +13,8 @@ interface SimulateClientArgv extends yargs.Argv { index: number mnemonic: string recipientIndex: number + clientCount: number + reuseClient: boolean } export const builder = () => { @@ -38,7 +39,6 @@ export const builder = () => { type: 'number', description: 'Index of the load test account to send transactions from. Used to generate account address', - default: 0, }) .option('recipient-index', { type: 'number', @@ -51,30 +51,53 @@ export const builder = () => { description: 'Mnemonic used to generate account addresses', demand: 'A mnemonic must be provided', }) + .options('client-count', { + type: 'number', + description: 'Number of clients to simulate', + default: 1, + }) + .options('reuse-client', { + type: 'boolean', + description: 'Use the same client for all the threads/accounts', + default: false, + }) } export const handler = async (argv: SimulateClientArgv) => { - // So we can transactions to another load testing account - const senderAddress = generateAddress(argv.mnemonic, AccountType.LOAD_TESTING_ACCOUNT, argv.index) - const recipientAddress = generateAddress( - argv.mnemonic, - AccountType.LOAD_TESTING_ACCOUNT, - argv.recipientIndex - ) + for (let thread = 0; thread < argv.clientCount; thread++) { + const senderIndex = getIndexForLoadTestThread(argv.index, thread) + const recipientIndex = getIndexForLoadTestThread(argv.recipientIndex, thread) + const senderAddress = generateAddress( + argv.mnemonic, + AccountType.LOAD_TESTING_ACCOUNT, + senderIndex + ) + const recipientAddress = generateAddress( + argv.mnemonic, + AccountType.LOAD_TESTING_ACCOUNT, + recipientIndex + ) + + const web3ProviderPort = argv.reuseClient ? 8545 : 8545 + thread - // sleep a random amount of time in the range [0, argv.delay] before starting so - // that if multiple simulations are started at the same time, they don't all - // submit transactions at the same time - const sleepMs = Math.random() * argv.delay - console.info(`Sleeping for ${sleepMs} ms`) - await sleep(sleepMs) + console.log( + `Account for sender index ${argv.index} thread ${thread}, final index ${senderIndex}: ${senderAddress}` + ) + console.log( + `Account for recipient index ${argv.recipientIndex} thread ${thread}, final index ${recipientIndex}: ${recipientAddress}` + ) + console.log(`web3ProviderPort for thread ${thread}: ${web3ProviderPort}`) - await simulateClient( - senderAddress, - recipientAddress, - argv.delay, - argv.blockscoutUrl, - argv.blockscoutMeasurePercent, - argv.index - ) + // tslint:disable-next-line: no-floating-promises + simulateClient( + senderAddress, + recipientAddress, + argv.delay, + argv.blockscoutUrl, + argv.blockscoutMeasurePercent, + argv.index, + thread, + `http://localhost:${web3ProviderPort}` + ) + } } diff --git a/packages/celotool/src/e2e-tests/blockchain_parameters_tests.ts b/packages/celotool/src/e2e-tests/blockchain_parameters_tests.ts index d52e0aaa04e..601df548a90 100644 --- a/packages/celotool/src/e2e-tests/blockchain_parameters_tests.ts +++ b/packages/celotool/src/e2e-tests/blockchain_parameters_tests.ts @@ -16,7 +16,7 @@ describe('Blockchain parameters tests', function (this: any) { let parameters: BlockchainParametersWrapper const gethConfig: GethRunConfig = { - useMycelo: true, + migrate: true, runPath: TMP_PATH, keepData: false, networkId: 1101, diff --git a/packages/celotool/src/e2e-tests/cip35_tests.ts b/packages/celotool/src/e2e-tests/cip35_tests.ts index 04d4c969f46..7fc0766832f 100644 --- a/packages/celotool/src/e2e-tests/cip35_tests.ts +++ b/packages/celotool/src/e2e-tests/cip35_tests.ts @@ -128,7 +128,7 @@ function generateTestCases(cipIsActivated: boolean) { function getGethRunConfig(withDonut: boolean): GethRunConfig { console.log('getGethRunConfig', withDonut) return { - useMycelo: true, + migrate: true, runPath: TMP_PATH, keepData: false, networkId: 1101, diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts index 95c74c55063..769efa9647d 100644 --- a/packages/celotool/src/e2e-tests/governance_tests.ts +++ b/packages/celotool/src/e2e-tests/governance_tests.ts @@ -177,7 +177,7 @@ describe('governance tests', () => { const gethConfig: GethRunConfig = { runPath: TMP_PATH, verbosity: 3, - useMycelo: true, + migrate: true, networkId: 1101, network: 'local', genesisConfig: { diff --git a/packages/celotool/src/e2e-tests/replica_tests.ts b/packages/celotool/src/e2e-tests/replica_tests.ts index a168a9faadd..55cdb60df9c 100644 --- a/packages/celotool/src/e2e-tests/replica_tests.ts +++ b/packages/celotool/src/e2e-tests/replica_tests.ts @@ -35,7 +35,7 @@ const verbose = false describe('replica swap tests', () => { const gethConfig: GethRunConfig = { - useMycelo: true, + migrate: true, runPath: TMP_PATH, verbosity: 4, networkId: 1101, diff --git a/packages/celotool/src/e2e-tests/slashing_tests.ts b/packages/celotool/src/e2e-tests/slashing_tests.ts index afd075c11ad..0b260014a6c 100644 --- a/packages/celotool/src/e2e-tests/slashing_tests.ts +++ b/packages/celotool/src/e2e-tests/slashing_tests.ts @@ -97,7 +97,7 @@ describe('slashing tests', function (this: any) { network: 'local', networkId: 1101, runPath: TMP_PATH, - useMycelo: true, + migrate: true, genesisConfig: { churritoBlock: 0, donutBlock: 0, diff --git a/packages/celotool/src/e2e-tests/sync_tests.ts b/packages/celotool/src/e2e-tests/sync_tests.ts index 0182b219476..2d057cabc6c 100644 --- a/packages/celotool/src/e2e-tests/sync_tests.ts +++ b/packages/celotool/src/e2e-tests/sync_tests.ts @@ -21,7 +21,6 @@ describe('sync tests', function (this: any) { churritoBlock: 0, donutBlock: 0, }, - useMycelo: true, instances: [ { name: 'validator0', diff --git a/packages/celotool/src/e2e-tests/transfer_tests.ts b/packages/celotool/src/e2e-tests/transfer_tests.ts index ce1133f6254..887e1c9bb80 100644 --- a/packages/celotool/src/e2e-tests/transfer_tests.ts +++ b/packages/celotool/src/e2e-tests/transfer_tests.ts @@ -170,7 +170,7 @@ describe('Transfer tests', function (this: any) { const syncModes = ['full', 'fast', 'light', 'lightest'] const gethConfig: GethRunConfig = { - useMycelo: true, + migrate: true, networkId: 1101, network: 'local', runPath: TMP_PATH, diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts index 02db2ece41c..b970829f84f 100644 --- a/packages/celotool/src/e2e-tests/utils.ts +++ b/packages/celotool/src/e2e-tests/utils.ts @@ -6,6 +6,7 @@ import { join as joinPath, resolve as resolvePath } from 'path' import readLastLines from 'read-last-lines' import Web3 from 'web3' import { spawnCmd, spawnCmdWithExitOnFailure } from '../lib/cmd-utils' +import { envVar, fetchEnvOrFallback } from '../lib/env-utils' import { AccountType, getPrivateKeysFor, @@ -34,6 +35,7 @@ import { import { GethInstanceConfig } from '../lib/interfaces/geth-instance-config' import { GethRepository } from '../lib/interfaces/geth-repository' import { GethRunConfig } from '../lib/interfaces/geth-run-config' +import { stringToBoolean } from '../lib/utils' const MonorepoRoot = resolvePath(joinPath(__dirname, '../..', '../..')) const verboseOutput = false @@ -215,6 +217,10 @@ export function getHooks(gethConfig: GethRunConfig) { } export function getContext(gethConfig: GethRunConfig, verbose: boolean = verboseOutput) { + // Use of mycelo can be enabled through gethConfig or through an env variable + const useMycelo = + !!gethConfig.useMycelo || + stringToBoolean(fetchEnvOrFallback(envVar.E2E_TESTS_FORCE_USE_MYCELO, 'false')) const validatorInstances = gethConfig.instances.filter((x: any) => x.validating) const numValidators = validatorInstances.length @@ -240,7 +246,7 @@ export function getContext(gethConfig: GethRunConfig, verbose: boolean = verbose await checkoutGethRepo(repo.branch || 'master', repo.path) } - if (gethConfig.useMycelo) { + if (useMycelo) { await buildGethAll(repo.path) } else { await buildGeth(repo.path) @@ -255,7 +261,7 @@ export function getContext(gethConfig: GethRunConfig, verbose: boolean = verbose fs.mkdirSync(gethConfig.runPath, { recursive: true }) } - if (gethConfig.useMycelo) { + if (useMycelo) { // Compile the contracts first because mycelo assumes they are compiled already, unless told not to if (!gethConfig.myceloSkipCompilingContracts) { await spawnCmdWithExitOnFailure('yarn', ['truffle', 'compile'], { @@ -312,7 +318,7 @@ export function getContext(gethConfig: GethRunConfig, verbose: boolean = verbose } } - if (gethConfig.useMycelo || !(gethConfig.migrate || gethConfig.migrateTo)) { + if (useMycelo || !(gethConfig.migrate || gethConfig.migrateTo)) { // Just need to initialize the nodes in this case. No need to actually start the network // since we don't need to run the migrations against it. for (const instance of gethConfig.instances) { diff --git a/packages/celotool/src/e2e-tests/validator_order_tests.ts b/packages/celotool/src/e2e-tests/validator_order_tests.ts index 2e1e9097a76..02ca28556dd 100644 --- a/packages/celotool/src/e2e-tests/validator_order_tests.ts +++ b/packages/celotool/src/e2e-tests/validator_order_tests.ts @@ -16,7 +16,7 @@ describe('governance tests', () => { networkId: 1101, network: 'local', runPath: TMP_PATH, - useMycelo: true, + migrate: true, instances: _.range(VALIDATORS).map((i) => ({ name: `validator${i}`, validating: true, diff --git a/packages/celotool/src/lib/attestation-service.ts b/packages/celotool/src/lib/attestation-service.ts index c181ee294e4..3ff3385b874 100644 --- a/packages/celotool/src/lib/attestation-service.ts +++ b/packages/celotool/src/lib/attestation-service.ts @@ -49,6 +49,7 @@ async function helmParameters(celoEnv: string) { `--set attestation_service.twilio.accountSid="${fetchEnv(envVar.TWILIO_ACCOUNT_SID)}"`, `--set attestation_service.twilio.authToken="${fetchEnv(envVar.TWILIO_ACCOUNT_AUTH_TOKEN)}"`, `--set attestation_service.twilio.addressSid="${fetchEnv(envVar.TWILIO_ADDRESS_SID)}"`, + `--set attestation_service.twilio.verifySid="${fetchEnv(envVar.TWILIO_VERIFY_SERVICE_SID)}"`, `--set attestation_service.nexmo.apiKey="${fetchEnv(envVar.NEXMO_KEY)}"`, `--set attestation_service.nexmo.apiSecret="${fetchEnv(envVar.NEXMO_SECRET)}"`, `--set attestation_service.telekom.apiKey="${fetchEnv(envVar.TELEKOM_API_KEY)}"`, diff --git a/packages/celotool/src/lib/blockscout.ts b/packages/celotool/src/lib/blockscout.ts index 956e3c79b4a..028974aab7a 100644 --- a/packages/celotool/src/lib/blockscout.ts +++ b/packages/celotool/src/lib/blockscout.ts @@ -140,12 +140,13 @@ async function helmParameters( `--set blockscout.jsonrpc_ws_url=${fetchEnv(envVar.BLOCKSCOUT_OVERRIDE_WS_ENDPOINT)}` ) } else if (isVmBased()) { + // TODO: Deprecated const txNodeLbIp = await getInternalTxNodeLoadBalancerIP(celoEnv) params.push(`--set blockscout.jsonrpc_http_url=http://${txNodeLbIp}:8545`) params.push(`--set blockscout.jsonrpc_ws_url=ws://${txNodeLbIp}:8546`) } else if (privateNodes > 0) { params.push(`--set blockscout.jsonrpc_http_url=http://tx-nodes-private:8545`) - params.push(`--set blockscout.jsonrpc_ws_url=ws://tx-nodes-private:8546`) + params.push(`--set blockscout.jsonrpc_ws_url=ws://tx-nodes-private:8545`) } else { params.push(`--set blockscout.jsonrpc_http_url=http://tx-nodes:8545`) params.push(`--set blockscout.jsonrpc_ws_url=ws://tx-nodes:8546`) diff --git a/packages/celotool/src/lib/env-utils.ts b/packages/celotool/src/lib/env-utils.ts index 238d38ed7fc..d0993fd8a8c 100644 --- a/packages/celotool/src/lib/env-utils.ts +++ b/packages/celotool/src/lib/env-utils.ts @@ -53,6 +53,7 @@ export enum envVar { CONSENSUS_TYPE = 'CONSENSUS_TYPE', CONTEXTS = 'CONTEXTS', DONUT_BLOCK = 'DONUT_BLOCK', + E2E_TESTS_FORCE_USE_MYCELO = 'E2E_TESTS_FORCE_USE_MYCELO', EKSPORTISTO_DOCKER_IMAGE_REPOSITORY = 'EKSPORTISTO_DOCKER_IMAGE_REPOSITORY', EKSPORTISTO_DOCKER_IMAGE_TAG = 'EKSPORTISTO_DOCKER_IMAGE_TAG', EKSPORTISTO_SUFFIX = 'EKSPORTISTO_SUFFIX', @@ -84,6 +85,7 @@ export enum envVar { KOMENCI_DOCKER_IMAGE_REPOSITORY = 'KOMENCI_DOCKER_IMAGE_REPOSITORY', KOMENCI_DOCKER_IMAGE_TAG = 'KOMENCI_DOCKER_IMAGE_TAG', KOMENCI_RULE_CONFIG_CAPTCHA_BYPASS_TOKEN = 'KOMENCI_RULE_CONFIG_CAPTCHA_BYPASS_TOKEN', + KOMENCI_SHOULD_SEND_REWARDS = 'KOMENCI_SHOULD_SEND_REWARDS', KOMENCI_UNUSED_KOMENCI_ADDRESSES = 'KOMENCI_UNUSED_KOMENCI_ADDRESSES', KUBECONFIG = 'KUBECONFIG', KUBERNETES_CLUSTER_NAME = 'KUBERNETES_CLUSTER_NAME', @@ -94,8 +96,10 @@ export enum envVar { LEADERBOARD_SHEET = 'LEADERBOARD_SHEET', LEADERBOARD_TOKEN = 'LEADERBOARD_TOKEN', LOAD_TEST_CLIENTS = 'LOAD_TEST_CLIENTS', + LOAD_TEST_THREADS = 'LOAD_TEST_THREADS', LOAD_TEST_GENESIS_BALANCE = 'LOAD_TEST_GENESIS_BALANCE', LOAD_TEST_TX_DELAY_MS = 'LOAD_TEST_TX_DELAY_MS', + LOAD_TEST_USE_RANDOM_RECIPIENT = 'LOAD_TEST_USE_RANDOM_RECIPIENT', LOOKBACK = 'LOOKBACK', METADATA_CRAWLER_DISCORD_CLUSTER_NAME = 'METADATA_CRAWLER_DISCORD_CLUSTER_NAME', METADATA_CRAWLER_DISCORD_WEBHOOK = 'METADATA_CRAWLER_DISCORD_WEBHOOK', @@ -146,6 +150,7 @@ export enum envVar { TWILIO_ACCOUNT_AUTH_TOKEN = 'TWILIO_ACCOUNT_AUTH_TOKEN', TWILIO_ACCOUNT_SID = 'TWILIO_ACCOUNT_SID', TWILIO_ADDRESS_SID = 'TWILIO_ADDRESS_SID', + TWILIO_VERIFY_SERVICE_SID = `TWILIO_VERIFY_SERVICE_SID`, TX_NODES = 'TX_NODES', TX_NODES_PRIVATE_ROLLING_UPDATE_PARTITION = 'TX_NODES_PRIVATE_ROLLING_UPDATE_PARTITION', TX_NODES_ROLLING_UPDATE_PARTITION = 'TX_NODES_ROLLING_UPDATE_PARTITION', @@ -188,15 +193,23 @@ export enum DynamicEnvVar { FULL_NODES_RPC_API_METHODS = '{{ context }}_FULL_NODES_RPC_API_METHODS', FULL_NODES_STATIC_NODES_FILE_SUFFIX = '{{ context }}_FULL_NODES_STATIC_NODES_FILE_SUFFIX', FULL_NODES_USE_GSTORAGE_DATA = '{{ context }}_FULL_NODES_USE_GSTORAGE_DATA', + FULL_NODES_WS_PORT = '{{ context }}_FULL_NODES_WS_PORT', GCP_PROJECT_NAME = '{{ context }}_GCP_PROJECT_NAME', GCP_ZONE = '{{ context }}_GCP_ZONE', KUBERNETES_CLUSTER_NAME = '{{ context }}_KUBERNETES_CLUSTER_NAME', KOMENCI_ADDRESS_AZURE_KEY_VAULTS = '{{ context }}_KOMENCI_ADDRESS_AZURE_KEY_VAULTS', KOMENCI_ADDRESSES_FROM_MNEMONIC_COUNT = '{{ context }}_KOMENCI_ADDRESSES_FROM_MNEMONIC_COUNT', + KOMENCI_CELOLABS_REWARDS_ADDRESS_AZURE_KEY_VAULTS = '{{ context }}_KOMENCI_CELOLABS_REWARDS_ADDRESS_AZURE_KEY_VAULTS', + KOMENCI_FOUNDATION_REWARDS_ADDRESS_AZURE_KEY_VAULTS = '{{ context }}_KOMENCI_FOUNDATION_REWARDS_ADDRESS_AZURE_KEY_VAULTS', + KOMENCI_REWARD_SERVICE_INSTANCE_COUNT = '{{ context }}_KOMENCI_REWARD_SERVICE_INSTANCE_COUNT', KOMENCI_DB_HOST = '{{ context }}_KOMENCI_DB_HOST', KOMENCI_DB_PORT = '{{ context }}_KOMENCI_DB_PORT', KOMENCI_DB_USERNAME = '{{ context }}_KOMENCI_DB_USERNAME', KOMENCI_DB_PASSWORD_VAULT_NAME = '{{ context }}_KOMENCI_DB_PASSWORD_VAULT_NAME', + KOMENCI_REWARD_SERVICE_DB_HOST = '{{ context }}_KOMENCI_REWARD_SERVICE_DB_HOST', + KOMENCI_REWARD_SERVICE_DB_PORT = '{{ context }}_KOMENCI_REWARD_SERVICE_DB_PORT', + KOMENCI_REWARD_SERVICE_DB_USERNAME = '{{ context }}_KOMENCI_REWARD_SERVICE_DB_USERNAME', + KOMENCI_REWARD_SERVICE_DB_PASSWORD_VAULT_NAME = '{{ context }}_KOMENCI_REWARD_SERVICE_DB_PASSWORD_VAULT_NAME', KOMENCI_NETWORK = '{{ context }}_KOMENCI_NETWORK', KOMENCI_APP_SECRETS_VAULT_NAME = '{{ context }}_KOMENCI_APP_SECRETS_VAULT_NAME', KOMENCI_RULE_CONFIG_CAPTCHA_BYPASS_ENABLED = '{{ context }}_KOMENCI_RULE_CONFIG_CAPTCHA_BYPASS_ENABLED', diff --git a/packages/celotool/src/lib/fullnodes.ts b/packages/celotool/src/lib/fullnodes.ts index 044311867be..9fe7c0a67cf 100644 --- a/packages/celotool/src/lib/fullnodes.ts +++ b/packages/celotool/src/lib/fullnodes.ts @@ -26,6 +26,7 @@ const contextFullNodeDeploymentEnvVars: { rollingUpdatePartition: DynamicEnvVar.FULL_NODES_ROLLING_UPDATE_PARTITION, rpcApis: DynamicEnvVar.FULL_NODES_RPC_API_METHODS, gcMode: DynamicEnvVar.FULL_NODES_GETH_GC_MODE, + wsPort: DynamicEnvVar.FULL_NODES_WS_PORT, useGstoreData: DynamicEnvVar.FULL_NODES_USE_GSTORAGE_DATA, } @@ -85,7 +86,7 @@ export async function installFullNodeChart( createNEG: boolean = false ) { const deployer = getFullNodeDeployerForContext(celoEnv, context, staticNodes, createNEG) - const enodes = await deployer.installChart() + const enodes = await deployer.installChart(context) if (enodes) { await uploadStaticNodeEnodes(celoEnv, context, enodes) } @@ -103,7 +104,7 @@ export async function upgradeFullNodeChart( createNEG: boolean = false ) { const deployer = getFullNodeDeployerForContext(celoEnv, context, generateNodeKeys, createNEG) - const enodes = await deployer.upgradeChart(reset) + const enodes = await deployer.upgradeChart(context, reset) if (enodes) { await uploadStaticNodeEnodes(celoEnv, context, enodes) } @@ -155,6 +156,7 @@ function getFullNodeDeploymentConfig(context: string): BaseFullNodeDeploymentCon rollingUpdatePartition: parseInt(fullNodeDeploymentEnvVarValues.rollingUpdatePartition, 10), rpcApis: fullNodeDeploymentEnvVarValues.rpcApis, gcMode: fullNodeDeploymentEnvVarValues.gcMode, + wsPort: parseInt(fullNodeDeploymentEnvVarValues.wsPort, 10), useGstoreData: fullNodeDeploymentEnvVarValues.useGstoreData, } return fullNodeDeploymentConfig diff --git a/packages/celotool/src/lib/generate_utils.ts b/packages/celotool/src/lib/generate_utils.ts index 05e4654eda3..1f2dc710003 100644 --- a/packages/celotool/src/lib/generate_utils.ts +++ b/packages/celotool/src/lib/generate_utils.ts @@ -23,6 +23,7 @@ import { REGISTRY_ADDRESS, TEMPLATE, } from './genesis_constants' +import { getIndexForLoadTestThread } from './geth' import { GenesisConfig } from './interfaces/genesis-config' import { ensure0x, strip0x } from './utils' @@ -198,6 +199,26 @@ const getFaucetedAccountsFor = ( })) } +const getFaucetedAccountsForLoadTest = ( + accountType: AccountType, + mnemonic: string, + clients: number, + threads: number, + balance: string +) => { + const addresses: string[] = [] + for (const podIndex of range(0, clients)) { + for (const threadIndex of range(0, threads)) { + const index = getIndexForLoadTestThread(podIndex, threadIndex) + addresses.push(strip0x(generateAddress(mnemonic, accountType, parseInt(`${index}`, 10)))) + } + } + return addresses.map((address) => ({ + address, + balance, + })) +} + export const getFaucetedAccounts = (mnemonic: string) => { const numFaucetAccounts = parseInt(fetchEnvOrFallback(envVar.FAUCET_GENESIS_ACCOUNTS, '0'), 10) const faucetAccounts = getFaucetedAccountsFor( @@ -208,10 +229,13 @@ export const getFaucetedAccounts = (mnemonic: string) => { ) const numLoadTestAccounts = parseInt(fetchEnvOrFallback(envVar.LOAD_TEST_CLIENTS, '0'), 10) - const loadTestAccounts = getFaucetedAccountsFor( + const numLoadTestThreads = parseInt(fetchEnvOrFallback(envVar.LOAD_TEST_THREADS, '0'), 10) + + const loadTestAccounts = getFaucetedAccountsForLoadTest( AccountType.LOAD_TESTING_ACCOUNT, mnemonic, numLoadTestAccounts, + numLoadTestThreads, faucetBalance() ) @@ -244,9 +268,9 @@ const hardForkActivationBlock = (key: string) => { export const generateGenesisFromEnv = (enablePetersburg: boolean = true) => { const mnemonic = fetchEnv(envVar.MNEMONIC) - const validatorEnv = fetchEnv(envVar.VALIDATORS) + const validatorEnv = parseInt(fetchEnv(envVar.VALIDATORS), 10) const genesisAccountsEnv = fetchEnvOrFallback(envVar.GENESIS_ACCOUNTS, '') - const validators = getValidatorsInformation(mnemonic, parseInt(validatorEnv, 10)) + const validators = getValidatorsInformation(mnemonic, validatorEnv) const consensusType = fetchEnv(envVar.CONSENSUS_TYPE) as ConsensusType @@ -436,9 +460,7 @@ export const generateGenesis = ({ } } - if (timestamp > 0) { - genesis.timestamp = timestamp.toString() - } + genesis.timestamp = timestamp > 0 ? timestamp.toString() : '0x0' return JSON.stringify(genesis, null, 2) } diff --git a/packages/celotool/src/lib/geth.ts b/packages/celotool/src/lib/geth.ts index 700bad62f38..a7b96534630 100644 --- a/packages/celotool/src/lib/geth.ts +++ b/packages/celotool/src/lib/geth.ts @@ -6,6 +6,7 @@ import { StableTokenWrapper } from '@celo/contractkit/lib/wrappers/StableTokenWr import { waitForPortOpen } from '@celo/dev-utils/lib/network' import BigNumber from 'bignumber.js' import { spawn } from 'child_process' +import { randomBytes } from 'crypto' import fs from 'fs' import { merge, range } from 'lodash' import fetch from 'node-fetch' @@ -222,6 +223,13 @@ const validateGethRPC = async ( handleError: HandleErrorCallback ) => { const transaction = await kit.connection.getTransaction(txHash) + handleError(!transaction || !transaction.from, { + location: '[GethRPC]', + error: `Contractkit did not return a valid transaction`, + }) + if (transaction == null) { + return + } const txFrom = transaction.from.toLowerCase() const expectedFrom = from.toLowerCase() handleError(!transaction.from || expectedFrom !== txFrom, { @@ -452,6 +460,7 @@ export const transferCeloGold = async ( feeCurrency?: string gatewayFeeRecipient?: string gatewayFee?: string + nonce?: number } = {} ) => { const kitGoldToken = await kit.contracts.getGoldToken() @@ -459,9 +468,10 @@ export const transferCeloGold = async ( from: fromAddress, gas: txOptions.gas, gasPrice: txOptions.gasPrice, - feeCurrency: txOptions.feeCurrency, + feeCurrency: txOptions.feeCurrency || undefined, gatewayFeeRecipient: txOptions.gatewayFeeRecipient, gatewayFee: txOptions.gatewayFee, + nonce: txOptions.nonce, }) } @@ -476,6 +486,7 @@ export const transferCeloDollars = async ( feeCurrency?: string gatewayFeeRecipient?: string gatewayFee?: string + nonce?: number } = {} ) => { const kitStableToken = await kit.contracts.getStableToken() @@ -483,35 +494,79 @@ export const transferCeloDollars = async ( from: fromAddress, gas: txOptions.gas, gasPrice: txOptions.gasPrice, - feeCurrency: txOptions.feeCurrency, + feeCurrency: txOptions.feeCurrency || undefined, gatewayFeeRecipient: txOptions.gatewayFeeRecipient, gatewayFee: txOptions.gatewayFee, + nonce: txOptions.nonce, }) } +export const unlock = async ( + kit: ContractKit, + address: string, + password: string, + unlockPeriod: number +) => { + try { + await kit.web3.eth.personal.unlockAccount(address, password, unlockPeriod) + } catch (error) { + console.error(`Unlock account ${address} failed:`, error) + } +} + export const simulateClient = async ( senderAddress: string, recipientAddress: string, txPeriodMs: number, // time between new transactions in ms blockscoutUrl: string, blockscoutMeasurePercent: number, // percent of time in range [0, 100] to measure blockscout for a tx - index: number + index: number, + thread: number, + web3Provider: string = 'http://localhost:8545' ) => { // Assume the node is accessible via localhost with senderAddress unlocked - const kit = newKitFromWeb3(new Web3('http://localhost:8545')) + const kit = newKitFromWeb3(new Web3(web3Provider)) + const password = fetchEnv('PASSWORD') + + let lastNonce: number = -1 + let lastTx: string = '' + let lastGasPriceMinimum: BigNumber = new BigNumber(0) + let nonce: number = 0 + let unlockNeeded: boolean = true + let recipientAddressFinal: string = recipientAddress + const useRandomRecipient = fetchEnv(envVar.LOAD_TEST_USE_RANDOM_RECIPIENT) + + const sleepTime = 5000 + while (await kit.connection.isSyncing()) { + console.info( + `LoadTestId ${index} waiting for web3Provider to be synced. Sleeping ${sleepTime}ms` + ) + await sleep(sleepTime) + } kit.defaultAccount = senderAddress + // sleep a random amount of time in the range [0, txPeriodMs) before starting so + // that if multiple simulations are started at the same time, they don't all + // submit transactions at the same time + const randomSleep = Math.random() * txPeriodMs + console.info(`Sleeping for ${randomSleep} ms`) + await sleep(randomSleep) + const baseLogMessage: any = { loadTestID: index, + threadID: thread, sender: senderAddress, - recipient: recipientAddress, + recipient: recipientAddressFinal, feeCurrency: '', txHash: '', } while (true) { const sendTransactionTime = Date.now() - + if (unlockNeeded) { + await unlock(kit, kit.defaultAccount, password, 9223372036) + unlockNeeded = false + } // randomly choose which token to use const transferGold = Boolean(Math.round(Math.random())) const transferFn = transferGold ? transferCeloGold : transferCeloDollars @@ -520,26 +575,66 @@ export const simulateClient = async ( // randomly choose which gas currency to use const feeCurrencyGold = Boolean(Math.round(Math.random())) - let feeCurrency - if (!feeCurrencyGold) { - try { - feeCurrency = await kit.registry.addressFor(CeloContract.StableToken) - } catch (error) { - tracerLog({ - tag: LOG_TAG_CONTRACT_ADDRESS_ERROR, - error: error.toString(), - ...baseLogMessage, - }) + // randomly choose the recipientAddress if configured + if (useRandomRecipient === 'true') { + recipientAddressFinal = `0x${randomBytes(40).toString('hex')}` + baseLogMessage.recipient = recipientAddressFinal + } + + let feeCurrency, txOptions + try { + feeCurrency = feeCurrencyGold ? '' : await kit.registry.addressFor(CeloContract.StableToken) + } catch (error) { + tracerLog({ + tag: LOG_TAG_CONTRACT_ADDRESS_ERROR, + error: error.toString(), + ...baseLogMessage, + }) + } + + try { + baseLogMessage.feeCurrency = feeCurrency + + const gasPriceMinimum = await kit.contracts.getGasPriceMinimum() + + const gasPriceBase = feeCurrency + ? await gasPriceMinimum.getGasPriceMinimum(feeCurrency) + : await gasPriceMinimum.gasPriceMinimum() + let gasPrice = new BigNumber(gasPriceBase).times(2).dp(0) + + // Check if last tx was mined. If not, reuse the same nonce + if (lastTx === '' || lastNonce === -1) { + nonce = await kit.web3.eth.getTransactionCount(kit.defaultAccount, 'latest') + } else if ((await kit.connection.getTransactionReceipt(lastTx))?.blockNumber) { + nonce = await kit.web3.eth.getTransactionCount(kit.defaultAccount, 'latest') + } else { + nonce = (await kit.web3.eth.getTransactionCount(kit.defaultAccount, 'latest')) - 1 + gasPrice = BigNumber.max(gasPrice.toNumber(), lastGasPriceMinimum.times(1.15)).dp(0) + console.warn( + `TX ${lastTx} was not mined. Replacing tx reusing nonce ${nonce} and gasPrice ${gasPrice}` + ) } + + lastGasPriceMinimum = gasPrice + txOptions = { + gasPrice: gasPrice.toString(), + feeCurrency, + nonce, + } + } catch (error) { + tracerLog({ + tag: LOG_TAG_CONTRACT_ADDRESS_ERROR, + error: error.toString(), + ...baseLogMessage, + }) } - baseLogMessage.feeCurrency = feeCurrency || '' // We purposely do not use await syntax so we sleep after sending the transaction, // not after processing a transaction's result - transferFn(kit, senderAddress, recipientAddress, LOAD_TEST_TRANSFER_WEI, { - feeCurrency, - }) + await transferFn(kit, senderAddress, recipientAddressFinal, LOAD_TEST_TRANSFER_WEI, txOptions) .then(async (txResult: TransactionResult) => { + lastTx = await txResult.getHash() + lastNonce = (await kit.web3.eth.getTransaction(lastTx)).nonce await onLoadTestTxResult( kit, senderAddress, @@ -551,14 +646,24 @@ export const simulateClient = async ( ) }) .catch((error: any) => { - console.error('Load test transaction failed with error:', JSON.stringify(error)) - tracerLog({ - tag: LOG_TAG_TRANSACTION_ERROR, - error: error.toString(), - ...baseLogMessage, - }) + if ( + typeof error === 'string' && + error.includes('Error: authentication needed: password or unlock') + ) { + console.warn('Load test transaction failed with locked account:', error) + unlockNeeded = true + } else { + console.error('Load test transaction failed with error:', error) + tracerLog({ + tag: LOG_TAG_TRANSACTION_ERROR, + error: error.toString(), + ...baseLogMessage, + }) + } }) - await sleep(txPeriodMs) + if (sendTransactionTime + txPeriodMs > Date.now()) { + await sleep(sendTransactionTime + txPeriodMs - Date.now()) + } } } @@ -582,7 +687,6 @@ export const onLoadTestTxResult = async ( p_time: receiptTime - sendTransactionTime, ...baseLogMessage, }) - // Continuing only with receipt received validateTransactionAndReceipt(senderAddress, txReceipt, (isError, data) => { if (isError) { @@ -615,6 +719,18 @@ export const onLoadTestTxResult = async ( }) } +/** + * This method generates key derivation index for loadtest clients and threads + * + * @param pod the pod replica number + * @param thread the thread number inside the pod + */ +export function getIndexForLoadTestThread(pod: number, thread: number) { + // max number of threads to avoid overlap is [0, base) + const base = 10000 + return pod * base + thread +} + /** * This method sends ERC20 tokens * @@ -1071,7 +1187,7 @@ export function writeGenesis(gethConfig: GethRunConfig, validators: Validator[], fs.writeFileSync(genesisPath, genesis) if (verbose) { - console.log(`wrote genesis to ${genesisPath}`) + console.log(`wrote genesis to ${genesisPath}`) } } diff --git a/packages/celotool/src/lib/helm_deploy.ts b/packages/celotool/src/lib/helm_deploy.ts index 8a44d638990..7d9449bcee4 100644 --- a/packages/celotool/src/lib/helm_deploy.ts +++ b/packages/celotool/src/lib/helm_deploy.ts @@ -24,7 +24,12 @@ import { } from './env-utils' import { ensureAuthenticatedGcloudAccount } from './gcloud_utils' import { generateGenesisFromEnv } from './generate_utils' -import { buildGethAll, checkoutGethRepo, retrieveBootnodeIPAddress } from './geth' +import { + buildGethAll, + checkoutGethRepo, + getEnodesWithExternalIPAddresses, + retrieveBootnodeIPAddress, +} from './geth' import { BaseClusterConfig, CloudProvider } from './k8s-cluster/base' import { getStatefulSetReplicas, scaleResource } from './kubernetes' import { installPrometheusIfNotExists } from './prometheus' @@ -755,6 +760,9 @@ async function helmIPParameters(celoEnv: string) { } async function helmParameters(celoEnv: string, useExistingGenesis: boolean) { + const valueFilePath = `/tmp/${celoEnv}-testnet-values.yaml` + await saveHelmValuesFile(celoEnv, valueFilePath, useExistingGenesis) + const gethMetricsOverrides = fetchEnvOrFallback('GETH_ENABLE_METRICS', 'false') === 'true' ? [ @@ -783,6 +791,7 @@ async function helmParameters(celoEnv: string, useExistingGenesis: boolean) { ) return [ + `-f ${valueFilePath}`, `--set bootnode.image.repository=${fetchEnv('GETH_BOOTNODE_DOCKER_IMAGE_REPOSITORY')}`, `--set bootnode.image.tag=${fetchEnv('GETH_BOOTNODE_DOCKER_IMAGE_TAG')}`, `--set celotool.image.repository=${fetchEnv('CELOTOOL_DOCKER_IMAGE_REPOSITORY')}`, @@ -1104,6 +1113,26 @@ function rollingUpdateHelmVariables() { ] } +export async function saveHelmValuesFile( + celoEnv: string, + valueFilePath: string, + useExistingGenesis: boolean +) { + const genesisContent = useExistingGenesis + ? await getGenesisBlockFromGoogleStorage(celoEnv) + : generateGenesisFromEnv() + + const enodes = await getEnodesWithExternalIPAddresses(celoEnv) + + const valueFileContent = ` +genesis: + genesisFileBase64: ${Buffer.from(genesisContent).toString('base64')} +staticnodes: + staticnodesBase64: ${Buffer.from(JSON.stringify(enodes)).toString('base64')} +` + fs.writeFileSync(valueFilePath, valueFileContent) +} + const celoBlockchainDir: string = path.join(os.tmpdir(), 'celo-blockchain-celotool') export async function createAndPushGenesis(celoEnv: string, reset: boolean, useMyCelo: boolean) { diff --git a/packages/celotool/src/lib/k8s-fullnode/base.ts b/packages/celotool/src/lib/k8s-fullnode/base.ts index 6dc19b704f1..9f2e32ca903 100644 --- a/packages/celotool/src/lib/k8s-fullnode/base.ts +++ b/packages/celotool/src/lib/k8s-fullnode/base.ts @@ -1,4 +1,6 @@ +import fs from 'fs' import { range } from 'lodash' +import { readableContext } from 'src/lib/context-utils' import { createNamespaceIfNotExists } from '../cluster' import { envVar, fetchEnv, fetchEnvOrFallback, isProduction } from '../env-utils' import { generatePrivateKeyWithDerivations, privateKeyToPublicKey } from '../generate_utils' @@ -27,6 +29,7 @@ export interface BaseFullNodeDeploymentConfig { rpcApis: string gcMode: string useGstoreData: string + wsPort: number // If undefined, node keys will not be predetermined and will be random nodeKeyGenerationInfo?: NodeKeyGenerationInfo } @@ -42,14 +45,14 @@ export abstract class BaseFullNodeDeployer { // If the node key is generated, then a promise containing the enodes is returned. // Otherwise, the enode cannot be calculated deterministically so a Promise is returned. - async installChart(): Promise { + async installChart(context: string): Promise { await createNamespaceIfNotExists(this.kubeNamespace) await installGenericHelmChart( this.kubeNamespace, this.releaseName, helmChartPath, - await this.helmParameters() + await this.helmParameters(context) ) if (this._deploymentConfig.nodeKeyGenerationInfo) { @@ -59,7 +62,7 @@ export abstract class BaseFullNodeDeployer { // If the node key is generated, then a promise containing the enodes is returned. // Otherwise, the enode cannot be calculated deterministically so a Promise is returned. - async upgradeChart(reset: boolean): Promise { + async upgradeChart(context: string, reset: boolean): Promise { if (reset) { await scaleResource(this.celoEnv, 'StatefulSet', `${this.celoEnv}-fullnodes`, 0) await deletePersistentVolumeClaims(this.celoEnv, ['celo-fullnode']) @@ -69,7 +72,7 @@ export abstract class BaseFullNodeDeployer { this.kubeNamespace, this.releaseName, helmChartPath, - await this.helmParameters() + await this.helmParameters(context) ) await scaleResource( @@ -89,7 +92,7 @@ export abstract class BaseFullNodeDeployer { await this.deallocateAllIPs() } - async helmParameters() { + async helmParameters(context: string) { let nodeKeys: string[] | undefined if (this._deploymentConfig.nodeKeyGenerationInfo) { nodeKeys = range(this._deploymentConfig.replicas).map((index: number) => @@ -101,6 +104,9 @@ export abstract class BaseFullNodeDeployer { ? this._deploymentConfig.rpcApis : 'eth,net,rpc,web3' const gcMode = this._deploymentConfig.gcMode ? this._deploymentConfig.gcMode : 'full' + const customValuesFile = `${helmChartPath}/${this._celoEnv}-${readableContext( + context + )}-values.yaml` return [ `--set namespace=${this.kubeNamespace}`, `--set replicaCount=${this._deploymentConfig.replicas}`, @@ -116,6 +122,7 @@ export abstract class BaseFullNodeDeployer { `--set genesis.network=${this.celoEnv}`, `--set genesis.epoch_size=${fetchEnv(envVar.EPOCH)}`, `--set geth.use_gstorage_data=${this._deploymentConfig.useGstoreData}`, + `--set geth.ws_port=${this._deploymentConfig.wsPort}`, `--set geth.gstorage_data_bucket=${fetchEnvOrFallback('GSTORAGE_DATA_BUCKET', '')}`, // Disable by default block age check in fullnode readinessProbe except for production envs `--set geth.fullnodeCheckBlockAge=${fetchEnvOrFallback( @@ -124,6 +131,7 @@ export abstract class BaseFullNodeDeployer { )}`, ...(await this.additionalHelmParameters()), nodeKeys ? `--set geth.node_keys='{${nodeKeys.join(',')}}'` : '', + fs.existsSync(customValuesFile) ? `-f ${customValuesFile}` : '', ] } diff --git a/packages/celotool/src/lib/k8s-fullnode/gcp.ts b/packages/celotool/src/lib/k8s-fullnode/gcp.ts index 520540de068..dc7f4197722 100644 --- a/packages/celotool/src/lib/k8s-fullnode/gcp.ts +++ b/packages/celotool/src/lib/k8s-fullnode/gcp.ts @@ -17,6 +17,7 @@ export class GCPFullNodeDeployer extends BaseFullNodeDeployer { `--set storage.storageClass=ssd`, `--set geth.public_ip_per_node='{${staticIps}}'`, `--set geth.create_network_endpoint_group=${this.deploymentConfig.createNEG}`, + `--set geth.flags='--txpool.nolocals'`, ] } diff --git a/packages/celotool/src/lib/komenci.ts b/packages/celotool/src/lib/komenci.ts index 0b8d378c04d..842a4d5bcff 100644 --- a/packages/celotool/src/lib/komenci.ts +++ b/packages/celotool/src/lib/komenci.ts @@ -43,7 +43,10 @@ interface KomenciIdentity { * Configuration of multiple relayers */ interface KomenciConfig { - identities: KomenciIdentity[] + relayerIdentities: KomenciIdentity[] + // TODO: For Signup rewards + // foundationRewardsIdentities: KomenciIdentity[] + cLabsRewardsIdentities: KomenciIdentity[] } interface KomenciKeyVaultIdentityConfig { @@ -54,6 +57,10 @@ interface KomenciMnemonicIdentityConfig { addressesFromMnemonicCount: string } +interface KomenciRewardServiceConfig { + instanceCount: number +} + interface KomenciDatabaseConfig { host: string port: string @@ -61,6 +68,11 @@ interface KomenciDatabaseConfig { passwordVaultName: string } +enum RewardType { + Foundation, + CeloLabs, +} + /** * Env vars corresponding to each value for the KomenciKeyVaultIdentityConfig for a particular context */ @@ -79,6 +91,33 @@ const contextKomenciMnemonicIdentityConfigDynamicEnvVars: { addressesFromMnemonicCount: DynamicEnvVar.KOMENCI_ADDRESSES_FROM_MNEMONIC_COUNT, } +/** + * Env vars corresponding to each value for the KomenciFoundationRewardsKeyVaultIdentityConfig for a particular context + */ +const contextKomenciFoundationRewardsKeyVaultIdentityConfigDynamicEnvVars: { + [k in keyof KomenciKeyVaultIdentityConfig]: DynamicEnvVar +} = { + addressAzureKeyVaults: DynamicEnvVar.KOMENCI_FOUNDATION_REWARDS_ADDRESS_AZURE_KEY_VAULTS, +} + +/** + * Env vars corresponding to each value for the KomenciCeloLabsRewardsKeyVaultIdentityConfig for a particular context + */ +const contextKomenciCeloLabsRewardsKeyVaultIdentityConfigDynamicEnvVars: { + [k in keyof KomenciKeyVaultIdentityConfig]: DynamicEnvVar +} = { + addressAzureKeyVaults: DynamicEnvVar.KOMENCI_CELOLABS_REWARDS_ADDRESS_AZURE_KEY_VAULTS, +} + +/** + * Env vars corresponding to each value for the KomenciCeloLabsRewardsKeyVaultIdentityConfig for a particular context + */ +const contextKomenciRewardsServiceConfigDynamicEnvVars: { + [k in keyof KomenciRewardServiceConfig]: DynamicEnvVar +} = { + instanceCount: DynamicEnvVar.KOMENCI_REWARD_SERVICE_INSTANCE_COUNT, +} + const contextDatabaseConfigDynamicEnvVars: { [k in keyof KomenciDatabaseConfig]: DynamicEnvVar } = { host: DynamicEnvVar.KOMENCI_DB_HOST, port: DynamicEnvVar.KOMENCI_DB_PORT, @@ -86,6 +125,15 @@ const contextDatabaseConfigDynamicEnvVars: { [k in keyof KomenciDatabaseConfig]: passwordVaultName: DynamicEnvVar.KOMENCI_DB_PASSWORD_VAULT_NAME, } +const contextRewardServiceDatabaseConfigDynamicEnvVars: { + [k in keyof KomenciDatabaseConfig]: DynamicEnvVar +} = { + host: DynamicEnvVar.KOMENCI_REWARD_SERVICE_DB_HOST, + port: DynamicEnvVar.KOMENCI_REWARD_SERVICE_DB_PORT, + username: DynamicEnvVar.KOMENCI_REWARD_SERVICE_DB_USERNAME, + passwordVaultName: DynamicEnvVar.KOMENCI_REWARD_SERVICE_DB_PASSWORD_VAULT_NAME, +} + function releaseName(celoEnv: string) { return `${celoEnv}-komenci` } @@ -118,7 +166,7 @@ export async function removeHelmRelease(celoEnv: string, context: string) { await removeGenericHelmChart(releaseName(celoEnv), celoEnv) await removeKomenciRBACHelmRelease(celoEnv) const komenciConfig = getKomenciConfig(context) - for (const identity of komenciConfig.identities) { + for (const identity of komenciConfig.relayerIdentities) { // If the identity is using Azure HSM signing, clean it up too if (identity.azureHsmIdentity) { await deleteAzureKeyVaultIdentity( @@ -145,10 +193,24 @@ async function getPasswordFromKeyVaultSecret(vaultName: string, secretName: stri async function helmParameters(celoEnv: string, context: string, useForno: boolean) { const komenciConfig = getKomenciConfig(context) - const replicas = komenciConfig.identities.length - const kubeServiceAccountSecretNames = await rbacServiceAccountSecretNames(celoEnv, replicas) + const onboardingRelayerCount = komenciConfig.relayerIdentities.length + const rewardsRelayerCount = komenciConfig.cLabsRewardsIdentities.length + const kubeServiceAccountSecretNames = await rbacServiceAccountSecretNames( + celoEnv, + '', + onboardingRelayerCount + ) + const kubeRewardsServiceAccountSecretNames = await rbacServiceAccountSecretNames( + celoEnv, + 'rewards-', + rewardsRelayerCount + ) const databaseConfig = getContextDynamicEnvVarValues(contextDatabaseConfigDynamicEnvVars, context) + const rewardDatabaseConfig = getContextDynamicEnvVarValues( + contextRewardServiceDatabaseConfigDynamicEnvVars, + context + ) const vars = getContextDynamicEnvVarValues( { network: DynamicEnvVar.KOMENCI_NETWORK, @@ -166,6 +228,10 @@ async function helmParameters(celoEnv: string, context: string, useForno: boolea databaseConfig.passwordVaultName, 'DB-PASSWORD' ) + const rewardDatabasePassword = await getPasswordFromKeyVaultSecret( + rewardDatabaseConfig.passwordVaultName, + 'DB-PASSWORD' + ) const recaptchaToken = await getPasswordFromKeyVaultSecret( vars.appSecretsKeyVault, 'RECAPTCHA-SECRET-KEY' @@ -174,6 +240,14 @@ async function helmParameters(celoEnv: string, context: string, useForno: boolea vars.appSecretsKeyVault, 'LOGGER-SERVICE-ACCOUNT' ) + const segmentApiKey = await getPasswordFromKeyVaultSecret( + vars.appSecretsKeyVault, + 'SEGMENT-API-KEY' + ) + const rewardServiceConfig = getContextDynamicEnvVarValues( + contextKomenciRewardsServiceConfigDynamicEnvVars, + context + ) const clusterConfig = getAksClusterConfig(context) return [ @@ -189,7 +263,7 @@ async function helmParameters(celoEnv: string, context: string, useForno: boolea `--set komenci.azureHsm.initTryCount=5`, `--set komenci.azureHsm.initMaxRetryBackoffMs=30000`, `--set onboarding.recaptchaToken=${recaptchaToken}`, - `--set onboarding.replicas=${replicas}`, + `--set onboarding.replicas=${onboardingRelayerCount}`, `--set onboarding.relayer.host=${celoEnv + '-relayer'}`, `--set onboarding.db.host=${databaseConfig.host}`, `--set onboarding.db.port=${databaseConfig.port}`, @@ -203,7 +277,7 @@ async function helmParameters(celoEnv: string, context: string, useForno: boolea `--set onboarding.ruleConfig.captcha.bypassToken=${fetchEnv( envVar.KOMENCI_RULE_CONFIG_CAPTCHA_BYPASS_TOKEN )}`, - `--set relayer.replicas=${replicas}`, + `--set relayer.replicas=${onboardingRelayerCount}`, `--set relayer.rpcProviderUrls.http=${httpRpcProviderUrl}`, `--set relayer.rpcProviderUrls.ws=${wsRpcProviderUrl}`, `--set relayer.metrics.enabled=true`, @@ -214,7 +288,35 @@ async function helmParameters(celoEnv: string, context: string, useForno: boolea ) .split(',') .join('\\,')}'`, - ].concat(await komenciIdentityHelmParameters(context, komenciConfig)) + `--set rewards.replicas=${rewardServiceConfig.instanceCount}`, + `--set rewards.db.host=${rewardDatabaseConfig.host}`, + `--set rewards.db.port=${rewardDatabaseConfig.port}`, + `--set rewards.db.username=${rewardDatabaseConfig.username}`, + `--set rewards.db.password=${rewardDatabasePassword}`, + `--set rewards.segmentApiKey=${segmentApiKey}`, + `--set rewards.shouldSendRewards=${fetchEnv(envVar.KOMENCI_SHOULD_SEND_REWARDS)}`, + `--set rewards.metrics.enabled=true`, + `--set rewards.metrics.prometheusPort=9090`, + `--set rewards.relayer.replicas=${rewardsRelayerCount}`, + `--set rewards.relayer.rpcProviderUrls.http=${httpRpcProviderUrl}`, + `--set rewards.relayer.rpcProviderUrls.ws=${wsRpcProviderUrl}`, + `--set rewards.relayer.metrics.enabled=true`, + `--set rewards.relayer.metrics.prometheusPort=9090`, + `--set rewards.relayer.host=${celoEnv + '-rewards-relayer'}`, + `--set kube.rewardsServiceAccountSecretNames='{${kubeRewardsServiceAccountSecretNames.join( + ',' + )}}'`, + ] + .concat( + await komenciIdentityHelmParameters(context, komenciConfig.relayerIdentities, 'relayer') + ) + .concat( + await komenciIdentityHelmParameters( + context, + komenciConfig.cLabsRewardsIdentities, + 'rewards.relayer' + ) + ) } function getPublicHostname(regionName: string, celoEnv: string): string { @@ -225,12 +327,16 @@ function getPublicHostname(regionName: string, celoEnv: string): string { * Returns an array of helm command line parameters for the komenci relayer identities. * Supports both private key and Azure HSM signing. */ -async function komenciIdentityHelmParameters(context: string, komenciConfig: KomenciConfig) { - const replicas = komenciConfig.identities.length +async function komenciIdentityHelmParameters( + context: string, + relayerIdentities: KomenciIdentity[], + envVarPrefix: string +) { + const replicas = relayerIdentities.length let params: string[] = [] for (let i = 0; i < replicas; i++) { - const komenciIdentity = komenciConfig.identities[i] - const prefix = `--set relayer.identities[${i}]` + const komenciIdentity = relayerIdentities[i] + const prefix = `--set ${envVarPrefix}.identities[${i}]` params.push(`${prefix}.address=${komenciIdentity.address}`) // An komenci identity can specify either a private key or some information // about an Azure Key Vault that houses an HSM with the address provided. @@ -259,11 +365,13 @@ async function komenciIdentityHelmParameters(context: string, komenciConfig: Kom } /** - * Gives a config for all komencis for a particular context + * Gives a config for all komenci services for a particular context */ function getKomenciConfig(context: string): KomenciConfig { return { - identities: getKomenciIdentities(context), + relayerIdentities: getKomenciRelayerIdentities(context), + cLabsRewardsIdentities: getKomenciRewardIdentities(context, RewardType.CeloLabs), + // foundationRewardsIdentities: getKomenciRewardIdentities(context, RewardType.Foundation), } } @@ -272,7 +380,7 @@ function getKomenciConfig(context: string): KomenciConfig { * the identities are created from that. Otherwise, the identities are created * with private keys generated by the mnemonic. */ -function getKomenciIdentities(context: string): KomenciIdentity[] { +function getKomenciRelayerIdentities(context: string): KomenciIdentity[] { const { addressAzureKeyVaults } = getContextDynamicEnvVarValues( contextKomenciKeyVaultIdentityConfigDynamicEnvVars, context, @@ -301,6 +409,25 @@ function getKomenciIdentities(context: string): KomenciIdentity[] { throw Error('No komenci identity env vars specified') } +/** + * Returns an array of komenci reward identities. The identities are created from the Azure Key Vault env var. + */ +function getKomenciRewardIdentities(context: string, rewardType: RewardType): KomenciIdentity[] { + const envVars = + rewardType === RewardType.Foundation + ? contextKomenciFoundationRewardsKeyVaultIdentityConfigDynamicEnvVars + : contextKomenciCeloLabsRewardsKeyVaultIdentityConfigDynamicEnvVars + const { addressAzureKeyVaults } = getContextDynamicEnvVarValues(envVars, context, { + addressAzureKeyVaults: '', + }) + + if (addressAzureKeyVaults) { + return getAzureHsmKomenciIdentities(addressAzureKeyVaults) + } + + throw Error('No komenci reward identity env vars specified') +} + /** * Given a string addressAzureKeyVaults of the form: *
:,
: @@ -359,7 +486,7 @@ function getKomenciAzureIdentityName(keyVaultName: string, address: string) { async function installKomenciRBACHelmChart(celoEnv: string, context: string) { return installGenericHelmChart( celoEnv, - rbacReleaseName(celoEnv), + rbacReleaseName(celoEnv, ''), rbacHelmChartPath, rbacHelmParameters(celoEnv, context) ) @@ -368,33 +495,42 @@ async function installKomenciRBACHelmChart(celoEnv: string, context: string) { async function upgradeKomenciRBACHelmChart(celoEnv: string, context: string) { return upgradeGenericHelmChart( celoEnv, - rbacReleaseName(celoEnv), + rbacReleaseName(celoEnv, ''), rbacHelmChartPath, rbacHelmParameters(celoEnv, context) ) } function removeKomenciRBACHelmRelease(celoEnv: string) { - return removeGenericHelmChart(rbacReleaseName(celoEnv), celoEnv) + return removeGenericHelmChart(rbacReleaseName(celoEnv, ''), celoEnv) } function rbacHelmParameters(celoEnv: string, context: string) { const komenciConfig = getKomenciConfig(context) console.info(komenciConfig) - const replicas = komenciConfig.identities.length - return [`--set environment.name=${celoEnv}`, `--set relayer.replicas=${replicas}`] + const relayerReplicas = komenciConfig.relayerIdentities.length + const rewardsRelayerReplicas = komenciConfig.cLabsRewardsIdentities.length + return [ + `--set environment.name=${celoEnv}`, + `--set relayer.replicas=${relayerReplicas}`, + `--set rewards.relayer.replicas=${rewardsRelayerReplicas}`, + ] } -function rbacReleaseName(celoEnv: string) { - return `${celoEnv}-komenci-rbac` +function rbacReleaseName(celoEnv: string, prefix: string) { + return `${celoEnv}-komenci-${prefix}rbac` } -async function rbacServiceAccountSecretNames(celoEnv: string, replicas: number) { - const names = [...Array(replicas).keys()].map((i) => `${rbacReleaseName(celoEnv)}-${i}`) +async function rbacServiceAccountSecretNames(celoEnv: string, prefix: string, replicas: number) { + const names = [...Array(replicas).keys()].map((i) => `${rbacReleaseName(celoEnv, prefix)}-${i}`) + let jsonSecretPath = '"{.items[*].secrets[0][\'name\']}"' + if (names.length === 1) { + jsonSecretPath = '"{.secrets[0][\'name\']}"' + } const [tokenName] = await execCmdWithExitOnFailure( `kubectl get serviceaccount --namespace=${celoEnv} ${names.join( ' ' - )} -o=jsonpath="{.items[*].secrets[0]['name']}"` + )} -o=jsonpath=${jsonSecretPath}` ) const tokenNames = tokenName.trim().split(' ') return tokenNames diff --git a/packages/celotool/src/lib/load-test.ts b/packages/celotool/src/lib/load-test.ts index be47c3853b5..485ab6eaca4 100644 --- a/packages/celotool/src/lib/load-test.ts +++ b/packages/celotool/src/lib/load-test.ts @@ -1,14 +1,15 @@ import sleep from 'sleep-promise' +import { LoadTestArgv } from 'src/cmds/deploy/initial/load-test' import { getBlockscoutUrl } from 'src/lib/endpoints' import { envVar, fetchEnv } from 'src/lib/env-utils' import { getEnodesWithExternalIPAddresses } from 'src/lib/geth' import { installGenericHelmChart, removeGenericHelmChart, + saveHelmValuesFile, upgradeGenericHelmChart, } from 'src/lib/helm_deploy' import { scaleResource } from 'src/lib/kubernetes' -import { getGenesisBlockFromGoogleStorage } from 'src/lib/testnet-utils' const chartDir = '../helm-charts/load-test/' @@ -20,9 +21,10 @@ export async function installHelmChart( celoEnv: string, blockscoutProb: number, delayMs: number, - replicas: number + replicas: number, + threads: number ) { - const params = await helmParameters(celoEnv, blockscoutProb, delayMs, replicas) + const params = await helmParameters(celoEnv, blockscoutProb, delayMs, replicas, threads) return installGenericHelmChart(celoEnv, releaseName(celoEnv), chartDir, params) } @@ -30,9 +32,10 @@ export async function upgradeHelmChart( celoEnv: string, blockscoutProb: number, delayMs: number, - replicas: number + replicas: number, + threads: number ) { - const params = await helmParameters(celoEnv, blockscoutProb, delayMs, replicas) + const params = await helmParameters(celoEnv, blockscoutProb, delayMs, replicas, threads) await upgradeGenericHelmChart(celoEnv, releaseName(celoEnv), chartDir, params) } @@ -41,7 +44,8 @@ export async function resetAndUpgrade( celoEnv: string, blockscoutProb: number, delayMs: number, - replicas: number + replicas: number, + threads: number ) { const loadTestStatefulSetName = `${celoEnv}-load-test` @@ -50,7 +54,7 @@ export async function resetAndUpgrade( await sleep(3000) - await upgradeHelmChart(celoEnv, blockscoutProb, delayMs, replicas) + await upgradeHelmChart(celoEnv, blockscoutProb, delayMs, replicas, threads) await sleep(3000) @@ -58,6 +62,20 @@ export async function resetAndUpgrade( await scaleResource(celoEnv, 'StatefulSet', loadTestStatefulSetName, replicas) } +export function setArgvDefaults(argv: LoadTestArgv) { + // Variables from the .env file are not set as environment variables + // by the time the builder is run, so we set the default here + if (argv.delay < 0) { + argv.delay = parseInt(fetchEnv(envVar.LOAD_TEST_TX_DELAY_MS), 10) + } + if (argv.replicas < 0) { + argv.replicas = parseInt(fetchEnv(envVar.LOAD_TEST_CLIENTS), 10) + } + if (argv.threads < 0) { + argv.replicas = parseInt(fetchEnv(envVar.LOAD_TEST_THREADS), 1) + } +} + export async function removeHelmRelease(celoEnv: string) { return removeGenericHelmChart(`${celoEnv}-load-test`, celoEnv) } @@ -66,14 +84,17 @@ async function helmParameters( celoEnv: string, blockscoutProb: number, delayMs: number, - replicas: number + replicas: number, + threads: number ) { const enodes = await getEnodesWithExternalIPAddresses(celoEnv) const staticNodesJsonB64 = Buffer.from(JSON.stringify(enodes)).toString('base64') // Uses the genesis file from google storage to ensure it's the correct genesis for the network - const genesisContents = await getGenesisBlockFromGoogleStorage(celoEnv) - const genesisFileJsonB64 = Buffer.from(genesisContents).toString('base64') + const valueFilePath = `/tmp/${celoEnv}-testnet-values.yaml` + await saveHelmValuesFile(celoEnv, valueFilePath, true) + return [ + `-f ${valueFilePath}`, `--set geth.accountSecret="${fetchEnv(envVar.GETH_ACCOUNT_SECRET)}"`, `--set blockscout.measurePercent=${blockscoutProb}`, `--set blockscout.url=${getBlockscoutUrl(celoEnv)}`, @@ -81,7 +102,6 @@ async function helmParameters( `--set celotool.image.tag=${fetchEnv(envVar.CELOTOOL_DOCKER_IMAGE_TAG)}`, `--set delay=${delayMs}`, // send txs every 5 seconds `--set environment=${celoEnv}`, - `--set geth.genesisFile=${genesisFileJsonB64}`, `--set geth.image.repository=${fetchEnv(envVar.GETH_NODE_DOCKER_IMAGE_REPOSITORY)}`, `--set geth.image.tag=${fetchEnv(envVar.GETH_NODE_DOCKER_IMAGE_TAG)}`, `--set geth.networkID=${fetchEnv(envVar.NETWORK_ID)}`, @@ -89,5 +109,8 @@ async function helmParameters( `--set geth.verbosity=${fetchEnv('GETH_VERBOSITY')}`, `--set mnemonic="${fetchEnv(envVar.MNEMONIC)}"`, `--set replicas=${replicas}`, + `--set threads=${threads}`, + `--set genesis.useGenesisFileBase64=true`, + `--set reuse_light_clients=true`, ] } diff --git a/packages/celotool/src/lib/prometheus.ts b/packages/celotool/src/lib/prometheus.ts index fcd82926ecc..fb0a399d2e6 100644 --- a/packages/celotool/src/lib/prometheus.ts +++ b/packages/celotool/src/lib/prometheus.ts @@ -7,7 +7,6 @@ import { fetchEnv, fetchEnvOrFallback, getDynamicEnvVarValue, - isProduction, } from './env-utils' import { installGenericHelmChart, @@ -33,7 +32,7 @@ const kubeServiceAccountName = releaseName // Container registry with latest tags: https://console.cloud.google.com/gcr/images/stackdriver-prometheus/GLOBAL/stackdriver-prometheus-sidecar?gcrImageListsize=30 const sidecarImageTag = '0.8.2' // Prometheus container registry with latest tags: https://hub.docker.com/r/prom/prometheus/tags -const prometheusImageTag = 'v2.25.0' +const prometheusImageTag = 'v2.27.1' const grafanaHelmChartPath = '../helm-charts/grafana' const grafanaReleaseName = 'grafana' @@ -111,6 +110,30 @@ async function helmParameters(context?: string, clusterConfig?: BaseClusterConfi '__name__!~"workqueue_.+"', '__name__!~"nginx_.+"', '__name__!~"etcd_.+"', + '__name__!~"erlang_.+"', + '__name__!~"container_tasks_state"', + '__name__!~"storage_.+"', + '__name__!~"container_memory_[^w].*"', + '__name__!~"rest_client_.+"', + '__name__!~"container_fs_.+"', + '__name__!~"container_file_.+"', + '__name__!~"container_spec_.+"', + '__name__!~"container_start_.+"', + '__name__!~"container_last_.+"', + '__name__!~"kube_pod_[^cs].+"', + '__name__!~"kube_pod_container_[^r].+"', + '__name__!~"kube_pod_container_status_waiting_reason"', + '__name__!~"kube_pod_container_status_terminated_reason"', + '__name__!~"kube_pod_container_status_last_terminated_reason"', + '__name__!~"container_network_.+"', + '__name__!~"container_cpu_user_seconds_total"', + '__name__!~"container_cpu_load_average_10s"', + '__name__!~"container_cpu_system_seconds_total"', + '__name__!~"container_sockets"', + '__name__!~"container_processes"', + '__name__!~"container_threads"', + '__name__!~"container_threads_max"', + '__name__!~"kube_node_status_condition"', ] const usingGCP = !clusterConfig || clusterConfig.cloudProvider === CloudProvider.GCP @@ -153,15 +176,50 @@ async function helmParameters(context?: string, clusterConfig?: BaseClusterConfi ] if (fetchEnvOrFallback(envVar.PROMETHEUS_REMOTE_WRITE_URL, '') !== '') { + const droppedRemoteWriteSeries = [ + 'apiserver_.+', + 'etcd_.+', + 'nginx_.+', + 'erlang_.+', + 'kubelet_[^v].+', + 'container_tasks_state', + 'storage_.+', + 'container_memory_[^w].*', + 'rest_client_.+', + 'container_fs_.+', + 'container_file_.+', + 'container_spec_.+', + 'container_start_.+', + 'container_last_.+', + 'kube_pod_container_status_waiting_reason', + 'kube_pod_container_status_terminated_reason', + 'kube_pod_status_phase', + 'container_network_.+', + 'container_cpu_user_seconds_total', + 'container_cpu_load_average_10s', + 'container_cpu_system_seconds_total', + 'container_sockets', + 'container_processes', + 'container_threads', + 'container_threads_max', + 'kube_node_status_condition', + 'kube_pod_container_status_last_terminated_reason', + 'kube_pod_container_[^r].+', + 'kube_pod_[^cs].+', + 'workqueue_.+', + 'kube_secret_.+', + ] params.push( - `--set remote_write[0].url=${fetchEnv(envVar.PROMETHEUS_REMOTE_WRITE_URL)}`, - `--set remote_write[0].basic_auth.username=${fetchEnv( + `--set remote_write.url='${fetchEnv(envVar.PROMETHEUS_REMOTE_WRITE_URL)}'`, + `--set remote_write.basic_auth.username='${fetchEnv( envVar.PROMETHEUS_REMOTE_WRITE_USERNAME - )}`, - `--set remote_write[0].basic_auth.password=${fetchEnv( + )}'`, + `--set remote_write.basic_auth.password='${fetchEnv( envVar.PROMETHEUS_REMOTE_WRITE_PASSWORD - )}`, - `--set enable_alerts="${isProduction()}"` + )}'`, + `--set remote_write.write_relabel_configs.source_labels='[__name__]'`, + `--set remote_write.write_relabel_configs.regex='(${droppedRemoteWriteSeries.join('|')})'`, + `--set remote_write.write_relabel_configs.action='drop'` ) } diff --git a/packages/cli/package.json b/packages/cli/package.json index 56f372704a2..609ffa266a8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,14 +33,14 @@ "test": "TZ=UTC jest --runInBand" }, "dependencies": { - "@celo/contractkit": "1.2.2-dev", - "@celo/explorer": "1.2.2-dev", - "@celo/governance": "1.2.2-dev", - "@celo/identity": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/wallet-hsm-azure": "1.2.2-dev", - "@celo/wallet-ledger": "1.2.2-dev", - "@celo/wallet-local": "1.2.2-dev", + "@celo/contractkit": "1.2.3-dev", + "@celo/explorer": "1.2.3-dev", + "@celo/governance": "1.2.3-dev", + "@celo/identity": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-hsm-azure": "1.2.3-dev", + "@celo/wallet-ledger": "1.2.3-dev", + "@celo/wallet-local": "1.2.3-dev", "@ledgerhq/hw-transport-node-hid": "~5.11.0", "@oclif/command": "^1.6.0", "@oclif/config": "^1.6.0", diff --git a/packages/cli/src/commands/governance/build-proposal.ts b/packages/cli/src/commands/governance/build-proposal.ts index 858f8aeea08..7235153e1eb 100644 --- a/packages/cli/src/commands/governance/build-proposal.ts +++ b/packages/cli/src/commands/governance/build-proposal.ts @@ -2,6 +2,7 @@ import { InteractiveProposalBuilder, ProposalBuilder } from '@celo/governance/li import { flags } from '@oclif/command' import { writeFileSync } from 'fs-extra' import { BaseCommand } from '../../base' +import { checkProposal } from '../../utils/governance' export default class BuildProposal extends BaseCommand { static description = 'Interactively build a governance proposal' @@ -27,5 +28,10 @@ export default class BuildProposal extends BaseCommand { const output = await promptBuilder.promptTransactions() console.info(`Outputting proposal to ${res.flags.output}`) writeFileSync(res.flags.output!, JSON.stringify(output)) + + output.forEach((tx) => builder.addJsonTx(tx)) + const proposal = await builder.build() + + await checkProposal(proposal, this.kit) } } diff --git a/packages/cli/src/commands/governance/hashhotfix.ts b/packages/cli/src/commands/governance/hashhotfix.ts index 23b643cc601..673583b1037 100644 --- a/packages/cli/src/commands/governance/hashhotfix.ts +++ b/packages/cli/src/commands/governance/hashhotfix.ts @@ -4,6 +4,7 @@ import { flags } from '@oclif/command' import { readFileSync } from 'fs-extra' import { BaseCommand } from '../../base' import { printValueMap } from '../../utils/cli' +import { checkProposal } from '../../utils/governance' export default class HashHotfix extends BaseCommand { static description = 'Hash a governance hotfix specified by JSON and a salt' @@ -14,6 +15,7 @@ export default class HashHotfix extends BaseCommand { required: true, description: 'Path to json transactions of the hotfix', }), + force: flags.boolean({ description: 'Skip execution check', default: false }), salt: flags.string({ required: true, description: 'Secret salt associated with hotfix' }), } @@ -31,6 +33,13 @@ export default class HashHotfix extends BaseCommand { jsonTransactions.forEach((tx) => builder.addJsonTx(tx)) const hotfix = await builder.build() + if (!res.flags.force) { + const ok = await checkProposal(hotfix, this.kit) + if (!ok) { + return + } + } + // Combine with the salt and hash the proposal. const saltBuff = Buffer.from(trimLeading0x(res.flags.salt), 'hex') console.log(`salt: ${res.flags.salt}, buf: ${saltBuff.toString('hex')}`) diff --git a/packages/cli/src/commands/governance/propose.ts b/packages/cli/src/commands/governance/propose.ts index f981a821251..c5ebb057e67 100644 --- a/packages/cli/src/commands/governance/propose.ts +++ b/packages/cli/src/commands/governance/propose.ts @@ -6,6 +6,7 @@ import { BaseCommand } from '../../base' import { newCheckBuilder } from '../../utils/checks' import { displaySendTx, printValueMapRecursive } from '../../utils/cli' import { Flags } from '../../utils/command' +import { checkProposal } from '../../utils/governance' export default class Propose extends BaseCommand { static description = 'Submit a governance proposal' @@ -17,6 +18,7 @@ export default class Propose extends BaseCommand { }), deposit: flags.string({ required: true, description: 'Amount of Gold to attach to proposal' }), from: Flags.address({ required: true, description: "Proposer's address" }), + force: flags.boolean({ description: 'Skip execution check', default: false }), descriptionURL: flags.string({ required: true, description: 'A URL where further information about the proposal can be viewed', @@ -55,6 +57,14 @@ export default class Propose extends BaseCommand { printValueMapRecursive(await proposalToJSON(this.kit, proposal)) const governance = await this.kit.contracts.getGovernance() + + if (!res.flags.force) { + const ok = await checkProposal(proposal, this.kit) + if (!ok) { + return + } + } + await displaySendTx( 'proposeTx', governance.propose(proposal, res.flags.descriptionURL), diff --git a/packages/cli/src/commands/governance/test-proposal.ts b/packages/cli/src/commands/governance/test-proposal.ts new file mode 100644 index 00000000000..069181b922d --- /dev/null +++ b/packages/cli/src/commands/governance/test-proposal.ts @@ -0,0 +1,43 @@ +import { ProposalBuilder, proposalToJSON, ProposalTransactionJSON } from '@celo/governance' +import { flags } from '@oclif/command' +import { readFileSync } from 'fs' +import { BaseCommand } from '../../base' +import { printValueMapRecursive } from '../../utils/cli' +import { Flags } from '../../utils/command' +import { executeProposal } from '../../utils/governance' +export default class TestProposal extends BaseCommand { + static description = 'Test a governance proposal' + + static hidden = true + + static flags = { + ...BaseCommand.flags, + jsonTransactions: flags.string({ + required: true, + description: 'Path to json transactions', + }), + from: Flags.address({ required: true, description: "Proposer's address" }), + } + + static examples = [ + 'test-proposal --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --jsonTransactions proposal.json', + ] + + async run() { + const res = this.parse(TestProposal) + const account = res.flags.from + this.kit.defaultAccount = account + + const builder = new ProposalBuilder(this.kit) + + // BUILD FROM JSON + const jsonString = readFileSync(res.flags.jsonTransactions).toString() + const jsonTransactions: ProposalTransactionJSON[] = JSON.parse(jsonString) + jsonTransactions.forEach((tx) => builder.addJsonTx(tx)) + + const proposal = await builder.build() + printValueMapRecursive(await proposalToJSON(this.kit, proposal)) + + await executeProposal(proposal, this.kit, account) + } +} diff --git a/packages/cli/src/utils/governance.ts b/packages/cli/src/utils/governance.ts new file mode 100644 index 00000000000..71b8195120b --- /dev/null +++ b/packages/cli/src/utils/governance.ts @@ -0,0 +1,53 @@ +import { toTxResult } from '@celo/connect' +import { ContractKit } from '@celo/contractkit' +import { ProposalTransaction } from '@celo/contractkit/src/wrappers/Governance' +import chalk from 'chalk' + +export async function checkProposal(proposal: ProposalTransaction[], kit: ContractKit) { + const governance = await kit.contracts.getGovernance() + return tryProposal(proposal, kit, governance.address, true) +} + +export async function executeProposal( + proposal: ProposalTransaction[], + kit: ContractKit, + from: string +) { + return tryProposal(proposal, kit, from, false) +} + +async function tryProposal( + proposal: ProposalTransaction[], + kit: ContractKit, + from: string, + call: boolean +) { + console.log('Simulating proposal execution') + let ok = true + for (const [i, tx] of proposal.entries()) { + if (!tx.to) { + continue + } + + try { + if (call) { + await kit.web3.eth.call({ + to: tx.to, + from, + value: tx.value, + data: tx.input, + }) + } else { + const txRes = toTxResult( + kit.web3.eth.sendTransaction({ to: tx.to, from, value: tx.value, data: tx.input }) + ) + await txRes.waitReceipt() + } + console.log(chalk.green(` ${chalk.bold('✔')} Transaction ${i} success!`)) + } catch (err) { + console.log(chalk.red(` ${chalk.bold('✘')} Transaction ${i} failure: ${err.toString()}`)) + ok = false + } + } + return ok +} diff --git a/packages/contractkit/README.md b/packages/contractkit/README.md deleted file mode 100644 index 962a0c0fc41..00000000000 --- a/packages/contractkit/README.md +++ /dev/null @@ -1,5 +0,0 @@ -This directory has been deprecated. - -ContractKit was split into 15 packages and is now located under [`/packages/sdk/contractkit`](https://github.com/celo-org/celo-monorepo/tree/master/packages/sdk/contractkit) - -See [#4790](https://github.com/celo-org/celo-monorepo/pull/4790) for details. diff --git a/packages/dev-utils/src/migration-override.json b/packages/dev-utils/src/migration-override.json index 1ca5ce37693..66423736933 100644 --- a/packages/dev-utils/src/migration-override.json +++ b/packages/dev-utils/src/migration-override.json @@ -23,7 +23,8 @@ "referendumStageDuration": 100, "executionStageDuration": 100, "minDeposit": 1, - "concurrentProposals": 5 + "concurrentProposals": 5, + "skipTransferOwnership": false }, "governanceApproverMultiSig": { "signatories": [ @@ -31,13 +32,17 @@ ], "numRequiredConfirmations": 1 }, + "grandaMento": { + "approver": "0x5409ED021D9299bf6814279A6A1411A7e866A631", + "spread": 0.01, + "vetoPeriodSeconds": 10800 + }, "oracles": { "reportExpiry": 300 }, "reserve": { "initialBalance": 100000000, "otherAddresses": ["0x91c987bf62D25945dB517BDAa840A6c661374402"] - }, "reserveSpenderMultiSig": { "signatories": ["0x5409ed021d9299bf6814279a6a1411a7e866a631", "0x4404ac8bd8F9618D27Ad2f1485AA1B2cFD82482D"], diff --git a/packages/docs/command-line-interface/governance.md b/packages/docs/command-line-interface/governance.md index cdd1eaa007d..745f714a2d3 100644 --- a/packages/docs/command-line-interface/governance.md +++ b/packages/docs/command-line-interface/governance.md @@ -105,6 +105,7 @@ USAGE $ celocli governance:hashhotfix OPTIONS + --force Skip execution check --globalHelp View all available global flags --jsonTransactions=jsonTransactions (required) Path to json transactions of the @@ -189,6 +190,8 @@ OPTIONS information about the proposal can be viewed + --force Skip execution check + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Proposer's address --globalHelp View all available global flags diff --git a/packages/docs/community/release-process/smart-contracts.md b/packages/docs/community/release-process/smart-contracts.md index 40a1829bd10..e524c111613 100644 --- a/packages/docs/community/release-process/smart-contracts.md +++ b/packages/docs/community/release-process/smart-contracts.md @@ -31,25 +31,20 @@ Whenever Celo Core Contracts need to be re-initialized, their initialization arg ### Release management in Git/Github -Github branches/tags and Github releases are used to coordinate past and ongoing releases. Ongoing smart contract development is done on the `master` branch (even after release branches are cut). Every smart contract release has a designated release branch, e.g. `release/celo-core-contracts/${N}` in the celo-monorepo. +Github branches/tags and Github releases are used to coordinate past and ongoing releases. Ongoing smart contract development is done on the `master` branch (even after release branches are cut). Every smart contract release has a designated release branch, e.g. `release/core-contracts/${N}` in the celo-monorepo. #### When a new release branch is cut: -1. A new release branch is created `release/celo-core-contracts/${N}` with the contracts to be audited. -2. The latest commit on the release branch is tagged with `celo-core-contracts-v${N}.pre-audit`. +1. A new release branch is created `release/core-contracts/${N}` with the contracts to be audited. +2. The latest commit on the release branch is tagged with `core-contracts.v${N}.pre-audit`. 3. On Github, a pre-release Github release should be created pointing at the latest tag on the release branch. -4. On master branch, `.circleci/config.yml` should be edited so that the variable `RELEASE_TAG` points to the tag `celo-core-contracts-v${N}.pre-audit` so that all future changes to master are versioned against the new release. -5. Ongoing audit responses/fixes should continue to go into `release/celo-core-contracts/${N}`. - -#### During the release proposal stage: - -1. Whenever release candidates are available they should be tagged `celo-core-contracts-v${N}.rc1`, `...rc2`, etc. -2. When a release is getting proposed on a Baklava/Alfajores/Mainnet, the commit that was used (which should be the latest release candidate) should be tagged with `celo-core-contracts-v${N}.(baklava | alfajores | mainnet)`. +4. On master branch, `.circleci/config.yml` should be edited so that the variable `RELEASE_TAG` points to the tag `core-contracts.v${N}.pre-audit` so that all future changes to master are versioned against the new release. +5. Ongoing audit responses/fixes should continue to go into `release/core-contracts/${N}`. #### After a completed release process: 1. The release branch should be merged into `master` with a merge commit (instead of the usual squash merge strategy). -2. On master branch, `.circleci/config.yml` should be edited so that the variable `RELEASE_TAG` points to the tag `celo-core-contracts-v${N}.mainnet` +2. On master branch, `.circleci/config.yml` should be edited so that the variable `RELEASE_TAG` points to the tag `core-contracts.v${N}` ## Release Process @@ -78,8 +73,8 @@ yarn view-tags ```bash # Run from `packages/protocol` in the celo-monorepo +PREVIOUS_RELEASE="core-contracts.v${N-1}" NETWORK=${"baklava"|"alfajores"|"mainnet"} -PREVIOUS_RELEASE="celo-core-contracts-v${N-1}.${NETWORK}" # A -f boolean flag can be provided to use a forno full node to connect to the provided network yarn verify-deployed -n $NETWORK -b $PREVIOUS_RELEASE -f ``` @@ -112,9 +107,8 @@ check for it. The script generates a detailed report on version changes in JSON format. ```bash -NETWORK=${"baklava"|"alfajores"|"mainnet"} -PREVIOUS_RELEASE="celo-core-contracts-v${N-1}.${NETWORK}" -RELEASE_CANDIDATE="celo-core-contracts-v${N}.rc${X}" +PREVIOUS_RELEASE="core-contracts.v${N-1}" +RELEASE_CANDIDATE="core-contracts.v${N}" yarn check-versions -a $PREVIOUS_RELEASE -b $RELEASE_CANDIDATE -r "report.json" ``` @@ -127,7 +121,7 @@ STORAGE updates are adopted by deploying a new proxy/implementation pair. This s ```bash NETWORK=${"baklava"|"alfajores"|"mainnet"} -RELEASE_CANDIDATE="celo-core-contracts-v${N}.rc${X}" +RELEASE_CANDIDATE="core-contracts.v${N}" yarn make-release -b $RELEASE_CANDIDATE -n $NETWORK -r "report.json" -i "releaseData/initializationData/release${N}.json" -p "proposal.json" -l "libraries.json" ``` @@ -159,7 +153,7 @@ accepted contract upgrade (in the form of the proposal.json you fetched in the s Additionally, include `initialization_data.json` from the CGP if any of the contracts have to be initialized. ```bash -RELEASE_CANDIDATE="celo-core-contracts-v${N}.rc${X}" +RELEASE_CANDIDATE="core-contracts.v${N}" NETWORK=${"baklava"|"alfajores"|"mainnet"} # A -f boolean flag can be provided to use a forno full node to connect to the provided network yarn verify-release -p "upgrade_proposal.json" -b $RELEASE_CANDIDATE -n $NETWORK -f -i initialization_data.json @@ -170,7 +164,7 @@ yarn verify-release -p "upgrade_proposal.json" -b $RELEASE_CANDIDATE -n $NETWORK After a release executes via Governance, you can use `verify-deployed` again to check that the resulting network state does indeed reflect the tagged release candidate: ```bash -RELEASE="celo-core-contracts-v${N}.rc${X}" +RELEASE="core-contracts.v${N}" NETWORK=${"baklava"|"alfajores"|"mainnet"} yarn verify-deployed -n $NETWORK -b $RELEASE -f ``` @@ -288,13 +282,9 @@ Deploying a new contract release should occur with the following process. On-cha T+2w
    -
  1. On Tuesday: Run the smart contract release script in order to to deploy the contracts to Baklava as well as submit a governance proposal. -
      -
    • Transition proposal through Baklava governance process.
    • -
    • Tag the commit that is being proposed with celo-core-contracts-v${N}.baklava
    • -
    -
  2. -
  3. Update your forum post with the Baklava PROPOSAL_ID, updated timings (if any changes), and notify the community in the Discord #governance channel.
  4. +
  5. On Tuesday: Run the smart contract release script in order to to deploy the contracts to Baklava as well as submit a governance proposal. +
  6. Transition proposal through Baklava governance process.
  7. +
  8. Update your forum post with the Baklava PROPOSAL_ID, updated timings (if any changes), and notify the community in the Discord #governance channel.
@@ -304,7 +294,6 @@ Deploying a new contract release should occur with the following process. On-cha
  1. Confirm all contracts working as intended on Baklava.
  2. Run the smart contract release script in order to to deploy the contracts to Alfajores as well as submit a governance proposal.
  3. -
  4. Tag the commit that is being proposed with celo-core-contracts-v${N}.alfajores
  5. Update your forum post with the Alfajores PROPOSAL_ID, updated timings (if any changes), and notify the community in the Discord #governance channel.
@@ -316,7 +305,6 @@ Deploying a new contract release should occur with the following process. On-cha
  • Confirm all contracts working as intended on Alfajores.
  • Confirm audit is complete and make the release notes and forum post contain a link to it.
  • On Tuesday: Run the smart contract release script in order to to deploy the contracts to Mainnet as well as submit a governance proposal.
  • -
  • Tag the commit that is being proposed with celo-core-contracts-v${N}.mainnet
  • Update the corresponding governance proposal with the updated on-chain PROPOSAL_ID and mark CGP status as "PROPOSED".
  • Update your forum post with the Mainnet PROPOSAL_ID, updated timings (if any changes), and notify the community in the Discord #governance channel.
  • At this point all stakeholders are encouraged to verify the proposed contracts deployed match the contracts from the release branch.
  • diff --git a/packages/docs/developer-resources/celo-for-eth-devs.md b/packages/docs/developer-resources/celo-for-eth-devs.md index dd5d098650d..bff6236e221 100644 --- a/packages/docs/developer-resources/celo-for-eth-devs.md +++ b/packages/docs/developer-resources/celo-for-eth-devs.md @@ -80,3 +80,34 @@ You can [view the implementation here.](https://explorer.celo.org/address/0xaa93 and to allow users to [pay transaction fees in different currencies.](../overview.md#richer-transactions) As of May 19th, 2021, with the [Donut hardfork](https://medium.com/celoorg/dissecting-the-donut-hardfork-23cad6015fa2), the Celo network accepts both Celo transaction objects and Ethereum transaction objects as valid Celo transactions. This means that you can use most Ethereum tools with Celo, right out of the box (just point them at the Celo network). When sending Ethereum formatted transactions on Celo, you will not be able to use Celo features of specifying transaction fee currencies or full node incentives. 3. When using mnemonic seed phrases (or secret phrases), Celo accounts (a private key and corresponding address) are derived differently from Ethereum accounts. The Celo key derivation path is `m/44'/52752'/0'/0` whereas Ethereum’s is `m/44'/60'/0'/0`. This means that going from a seed phrase to accounts will be different when using Ethereum vs Celo wallets. + + 4. The Valora wallet uses two types of accounts: externally owned accounts and meta-transaction wallets. There are important consequences for wallet developers and dapp developers building on Celo as Valora is one of the main interfaces for Celo users. You can find more information about [Valora accounts here](../celo-codebase/protocol/identity/valora-accounts.md). + +## Deploying Ethereum Contracts to Celo + +Celo runs the EVM which means that smart contracts written for Ethereum can easily be deployed to Celo, the main difference being that you just need to connect to a Celo node instead of an Ethereum node. You can connect to your own Celo node or to a Celo node service provider like [Figment Datahub](https://figment.io/datahub/celo/). + +[This tutorial](./walkthroughs/hellocontracts.md) goes over how to start an ultralight node that runs locally and use it to deploy a contract to the Alfajores testnet using Truffle. + +[This tutorial](./walkthroughs/hello-contract-remote-node.md) goes over how to connect to a remote node and use ContractKit to deploy a contract to Alfajores using Truffle. + +## Protocol Differences + +### OPCODES & Block headers + +Celo does not support the `DIFFICULTY` or `GASLIMIT` opcodes. These fields are also absent from Celo block headers. + +### Precompiled Contracts + +Celo includes all of the precompiled contracts in Ethereum, but also adds additional contracts. [Here](https://github.com/celo-org/celo-blockchain/blob/v1.3.2/core/vm/contracts.go#L157) is the list of Celo precompiled contracts as of Celo version 1.3.2. You can find the latest updates by selecting the most recent release tag. + +### Core Contract Calls + +The blockchain client makes some core contract calls at the end of a block, outside of transactions. Many are done on epoch blocks ([epoch rewards](../celo-codebase/protocol/proof-of-stake/epoch-rewards.md), [validator elections](../celo-codebase/protocol/proof-of-stake/validator-elections.md), etc.), but not all. For example, the [gas price minimum](../celo-codebase/protocol/transactions/gas-pricing.md) update can happen on any block. +Logs created by these contract changes are included in a single additional receipt in that block, which references the block hash as its transaction hash, even though there is no transaction with this hash. If no logs were created by such calls in that block, no receipt is added. + +### Node management APIs + +Celo nodes have a slightly different RPC interface than geth nodes. There are some additional RPC endpoints to help validators manage their nodes, they can be found [here](../validator-guide/proxy.md#rpc-api) and [here](../validator-guide/node-upgrades.md#hotswapping-validator-nodes). + +You can find the full list of RPC API endpoints in [this file](https://github.com/celo-org/celo-blockchain/blob/master/internal/web3ext/web3ext.go). diff --git a/packages/docs/developer-resources/walkthroughs/development-chain.md b/packages/docs/developer-resources/walkthroughs/development-chain.md index 87bba3d2bca..895f39a62e8 100644 --- a/packages/docs/developer-resources/walkthroughs/development-chain.md +++ b/packages/docs/developer-resources/walkthroughs/development-chain.md @@ -6,9 +6,25 @@ At the end of this tutorial, you will have a local Celo development blockchain r Running the development Celo blockchain is helpful because it greatly speeds up development time. You will start with 10 accounts pre-funded with cGLD and cUSD and all transactions on the network are virtually instant. -### **Download the Celo monorepo** +You can run the development Celo blockchain in two ways: -To start, download the Celo monorepo [here](https://github.com/celo-org/celo-monorepo) or with the following command. +### 1. Use the celo-devchain NPM package + +The easiest is to use a "pre-generated" devchain from the [celo-devchain](https://github.com/zviadm/celo-devchain) NPM package. For that all you have to do is: + +```sh +> npm install --save-dev celo-devchain +> npx celo-devchain --port 7545 + +or + +> yarn add --dev celo-devchain +> yarn run celo-devchain --port 7545 +``` + +### 2. Initialize your own devchain from the monorepo + +If you prefer, you can initialize your own devchain and build it from scratch. To start, download the Celo monorepo [here](https://github.com/celo-org/celo-monorepo) or with the following command. ```text git clone https://github.com/celo-org/celo-monorepo.git @@ -32,6 +48,8 @@ This will start the development Celo blockchain. It will take at least a few min The process will finish and print `Ganache started`. Leave this terminal window open to leave the development chain running. +## Interacting with the chain + ### **Inspecting the chain** Now that we have a Celo development chain running, we probably want to know what accounts we have access to, how much cGLD and cUSD they have as well as the addresses of the deployed protocol contracts. diff --git a/packages/docs/getting-started/wallets.md b/packages/docs/getting-started/wallets.md index 7f72a6808f0..df84c802de3 100644 --- a/packages/docs/getting-started/wallets.md +++ b/packages/docs/getting-started/wallets.md @@ -16,7 +16,7 @@ Valora is a mobile wallet focused on making global peer-to-peer payments simple - [valoraapp.com](https://valoraapp.com) - Platforms: iOS, Android -- Maintainers: [cLabs](https://clabs.co) +- Maintainers: [Valora](https://valoraapp.com) - Ledger support: No ### CeloWallet.app @@ -67,3 +67,8 @@ There are currently a few other compatible wallets. When the upcoming [Donut har - [pesabase.com](https://pesabase.com/) - Platforms: iOS, Android + +### D’CENT + +- [https://dcentwallet.com/](https://dcentwallet.com/) +- Platforms: Browser, iOS, Android diff --git a/packages/docs/overview.md b/packages/docs/overview.md index fe71f116dd2..595ff668627 100644 --- a/packages/docs/overview.md +++ b/packages/docs/overview.md @@ -108,18 +108,18 @@ Since light clients need not trust full nodes, as they can verify their work, th ### Stable Cryptocurrencies -Celo enables a family of stable-value tokens whose values can track the value of any asset, including fiat currencies, commodities, and even natural resources. Stablecoins supported include the Celo Dollar (cUSD) and the Celo Euro (cEUR), which track the value of the U.S. Dollar and Euro respectively. CELO and a basket of other assets including BTC and ETH serves as the collateral for these stablecoins. These stablecoins are redeemable for CELO, ensuring that transactions can occur quickly, cheaply and reliably on-chain. +Celo enables a family of stablecoins that track the value of any asset, including fiat currencies, commodities, and even natural resources. Stablecoins supported include the Celo Dollar (cUSD) and the Celo Euro (cEUR), which track the value of the U.S. Dollar and Euro respectively. CELO and a basket of other assets including BTC and ETH serves as the collateral for these stablecoins. These stablecoins are redeemable for CELO, ensuring that transactions can occur quickly, cheaply and reliably on-chain. Celo's stability mechanism allows users to create a new cUSD and cEUR by sending CELO to the reserve, or burn cUSD and cEUR by redeeming it for their equivalent value in CELO. -This mechanism relies on a series of Oracles, or information feeds from exchanges external to the network, to report the CELO to US Dollar or Euro market rate. To minimize the risk of a run on CELO collateral when these reported values are inaccurate or out-of-date, Celo uses an on-chain constant-product-market-maker model, inspired by the [Uniswap](https://uniswap.io/) system. This mechanism adjusts the redemption price of CELO until either arbitrage occurs (so that the on-chain price dynamically adjusts until the offered rate meets the external rate) or Oracles reset the on-chain price. +This mechanism relies on a series of Oracles, or information feeds from exchanges external to the network, to report the CELO to US Dollar or Euro market rates. To minimize the risk of a run on CELO collateral when these reported values are inaccurate or out-of-date, Celo uses an on-chain constant-product-market-maker model, inspired by the [Uniswap](https://uniswap.io/) system. This mechanism adjusts the redemption price of CELO until either arbitrage occurs (so that the on-chain price dynamically adjusts until the offered rate meets the external rate) or Oracles reset the on-chain price. The Celo protocol ensures that there is sufficient CELO collateral to redeem the amount of CELO in circulation through several sources. These include a [stability fee](celo-codebase/protocol/stability/stability-fees.md) levied on Celo Dollar balances, a transfer from [epoch rewards](celo-codebase/protocol/proof-of-stake/community-fund.md#bolstering-the-reserve), plus the proceeds from the spread when interacting with the on-chain market-maker mechanism. In addition, a back-up reserve of cryptocurrencies is held off-chain. This off-chain reserve is managed to preserve value and minimize volatility by maintaining a diversified portfolio of cryptocurrencies through algorithmic rebalancing trading and periodically "topping-up" the CELO collateral available to ensure it exceeds the amount required to redeem Celo Dollars in circulation. The approved cryptocurrencies, distribution ratios, and rebalancing period are all subject to on-chain governance. {% hint style="success" %} -**Roadmap**: Celo envisages a number of stable currencies pegged to different fiat currencies as well as natural resources such as forests. In addition, once bridges between other chains and the Celo blockchain are fully developed, and liquid trading on decentralized exchanges occurs, the rebalancing can be handled transparently on-chain. +**Roadmap**: Celo envisages a number of stable currencies tracking different fiat currencies as well as natural resources such as forests. In addition, once bridges between other chains and the Celo blockchain are fully developed, and liquid trading on decentralized exchanges occurs, the rebalancing can be handled transparently on-chain. {% endhint %} ### Lightweight Identity diff --git a/packages/docs/validator-guide/attestation-service.md b/packages/docs/validator-guide/attestation-service.md index 0860b413036..c6607cc6121 100644 --- a/packages/docs/validator-guide/attestation-service.md +++ b/packages/docs/validator-guide/attestation-service.md @@ -63,6 +63,26 @@ To actually be able to send SMS, you need to create a messaging service under [P Now that you have provisioned your messaging service, you need to buy at least 1 phone number to send SMS from. You can do so under the `Numbers` option of the messaging service page. It is strongly recommended that you purchase at least a US (`+1`) number which seem to provide high delivery success rates. If you purchase numbers in other locales, Twilio will intelligently select the best number to send each SMS. +#### Verify Service (post v1.3.0) + +We're in the process of transitioning to [Twilio's Verify Service](https://www.twilio.com/verify) which will automatically manage a set of phone numbers for global reach. Create a Verify Service in the Twilio Portal by navigating to [Verify](https://www.twilio.com/console/verify/services) and click `+` to create a new service. It's important to provide `Celo` as the service friendly name, since this will show up in the text message content. + +1. Set the code length to `8 digits`. +2. Enter `sell-oh` in the `TTS SERVICE NAME`. +3. Enable `SMS`, `CALL`, and `EMAIL` delivery channels. + +After you create the Verify Service, you **[must create a support ticket](https://www.twilio.com/console/support/tickets/create)** to enable the `custom code` feature. Provide Twilio support your new Verify SID and request enabling the `custom code` feature. Please monitor for a response and respond to any follow up questions. + +Support ticket request template + +> Hello, I'd like to enable custom codes for our Verify API with SID {YOUR_VERIFY_SID}. I understand that we will be charged on each attempted user verification. + +After Twilio enables custom codes, you'll see the following property in the Twilio dashboard when viewing your Verify Service: + +![Custom Code Property](https://storage.googleapis.com/celo-website/docs/custom-code.png) + +Once you have confirmation that custom codes are enabled on your Twilio account, you can provide the resulting `SID` in the `TWILIO_VERIFY_SERVICE_SID` configuration variable and start the service. In the future, we'll likely switch entirely to the Verify Service and deprecate the Messaging Service, but for now it's important to specify both. + ### Nexmo After signing up for [Nexmo](https://dashboard.nexmo.com/sign-up), click the balance in the top-left to go to [Billing and Payments](https://dashboard.nexmo.com/billing-and-payments), where you can add funds. It is strongly recommended that you use a credit or debit card (as opposed to other forms of payment) as you will then be able to enable `Auto reload`. You should also enable `Low balance alerts`. Both of these will help avoid failing to deliver SMS when your funds are exhausted. It appears that these options may not be immediately available for all new accounts due to fraud checks: try sending a few SMS, checking back after a few days, or raising a support ticket. @@ -186,6 +206,7 @@ Twilio configuration options: | ------------------------------ | --------------------------------------------------------------- | | `TWILIO_ACCOUNT_SID` | The Twilio account ID | | `TWILIO_MESSAGING_SERVICE_SID` | The Twilio Message Service ID. Starts with `MG` | +| `TWILIO_VERIFY_SERVICE_SID` | The Twilio Verify Service ID. Starts with `VA` | | `TWILIO_AUTH_TOKEN` | The API authentication token | | `TWILIO_UNSUPPORTED_REGIONS` | Optional. A comma-separated list of country codes to not serve, recommended value `CU,SY,KP,IR,SD` | diff --git a/packages/env-tests/package.json b/packages/env-tests/package.json index 2bac12e4f46..8e0aeaef131 100644 --- a/packages/env-tests/package.json +++ b/packages/env-tests/package.json @@ -5,11 +5,11 @@ "main": "index.js", "license": "MIT", "dependencies": { - "@celo/contractkit": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/base": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", - "@celo/identity": "1.2.2-dev", + "@celo/contractkit": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/base": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", + "@celo/identity": "1.2.3-dev", "bunyan": "1.8.12", "bunyan-gke-stackdriver": "0.1.2", "bunyan-debug-stream": "2.0.0", diff --git a/packages/helm-charts/attestation-service/templates/attestation.secret.yaml b/packages/helm-charts/attestation-service/templates/attestation.secret.yaml index d6241b3c15c..6bc412007f2 100644 --- a/packages/helm-charts/attestation-service/templates/attestation.secret.yaml +++ b/packages/helm-charts/attestation-service/templates/attestation.secret.yaml @@ -15,4 +15,5 @@ data: TWILIO_ACCOUNT_SID: {{ .Values.attestation_service.twilio.accountSid | b64enc }} TWILIO_AUTH_TOKEN: {{ .Values.attestation_service.twilio.authToken | b64enc }} TWILIO_MESSAGING_SERVICE_SID: {{ .Values.attestation_service.twilio.addressSid | b64enc }} + TWILIO_VERIFY_SERVICE_SID: {{ .Values.attestation_service.twilio.verifySid | b64enc }} TELEKOM_API_KEY: {{ .Values.attestation_service.telekom.apiKey | b64enc }} diff --git a/packages/helm-charts/attestation-service/templates/attestation.statefulset.yaml b/packages/helm-charts/attestation-service/templates/attestation.statefulset.yaml index 06a24607dd7..aa6c440bff2 100644 --- a/packages/helm-charts/attestation-service/templates/attestation.statefulset.yaml +++ b/packages/helm-charts/attestation-service/templates/attestation.statefulset.yaml @@ -130,9 +130,11 @@ spec: args: - "-c" - | + RID=`cat /root/.celo/replica_id` + export DATABASE_URL=$DATABASE_URL_PREFIX$DATABASE_NAME + yarn sequelize db:create $DATABASE_NAME yarn db:migrate sleep 5 - RID=`cat /root/.celo/replica_id` NEXMO_APPLICATIONS={{- range $index, $application := .Values.attestation_service.nexmo.applications -}}{{ $application }},{{- end }} NEXMO_APPLICATION=`echo -n $NEXMO_APPLICATIONS | cut -d ',' -f $((RID + 1))` ATTESTATION_SIGNER_ADDRESS=`cat /root/.celo/attestationSignerAddress` CELO_VALIDATOR_ADDRESS=`cat /root/.celo/address` exec yarn start ports: @@ -143,8 +145,12 @@ spec: cpu: 50m memory: 150Mi env: - - name: DATABASE_URL - value: postgres://postgres:password@{{ .Release.Namespace }}-attestation-service-postgresql:5432/AttestationService + - name: DATABASE_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: DATABASE_URL_PREFIX + value: postgres://postgres:password@{{ .Release.Namespace }}-attestation-service-postgresql:5432/ - name: NODE_ENV value: production - name: CELO_PROVIDER @@ -182,6 +188,11 @@ spec: secretKeyRef: name: {{ .Release.Name }} key: TWILIO_MESSAGING_SERVICE_SID + - name: TWILIO_VERIFY_SERVICE_SID + valueFrom: + secretKeyRef: + name: {{ .Release.Name }} + key: TWILIO_VERIFY_SERVICE_SID - name: TWILIO_AUTH_TOKEN valueFrom: secretKeyRef: diff --git a/packages/helm-charts/blockscout/Chart.yaml b/packages/helm-charts/blockscout/Chart.yaml index 4d7e5860909..86afbdba21a 100644 --- a/packages/helm-charts/blockscout/Chart.yaml +++ b/packages/helm-charts/blockscout/Chart.yaml @@ -1,3 +1,4 @@ +apiVersion: v1 name: blockscout version: 0.0.2 description: Chart which is used to deploy a blockscout setup for a celo testnet diff --git a/packages/helm-charts/blockscout/templates/blockscout-api.deployment.yaml b/packages/helm-charts/blockscout/templates/blockscout-api.deployment.yaml index a6a51040e92..ade7325ac72 100644 --- a/packages/helm-charts/blockscout/templates/blockscout-api.deployment.yaml +++ b/packages/helm-charts/blockscout/templates/blockscout-api.deployment.yaml @@ -21,6 +21,12 @@ spec: component: blockscout-api template: metadata: + {{- if .Values.blockscout.metrics.enabled}} + annotations: + prometheus.io/path: /metrics/web + prometheus.io/port: "4000" + prometheus.io/scrape: "true" + {{- end}} labels: app: blockscout release: {{ .Release.Name }} @@ -43,7 +49,7 @@ spec: - -c args: - | - exec mix cmd --app block_scout_web "iex -e 'IEx.configure(default_prompt: \"\", alive_prompt: \"\")' -S mix phx.server" + exec mix cmd --app block_scout_web "iex --name {{ .Values.blockscout.api.erlangNodeName}}@0.0.0.0 -e 'IEx.configure(default_prompt: \"\", alive_prompt: \"\")' -S mix phx.server" ports: - name: http containerPort: {{ .Values.blockscout.api.port }} diff --git a/packages/helm-charts/blockscout/templates/blockscout-indexer.deployment.yaml b/packages/helm-charts/blockscout/templates/blockscout-indexer.deployment.yaml index 16ed8d71d9d..762753eab85 100644 --- a/packages/helm-charts/blockscout/templates/blockscout-indexer.deployment.yaml +++ b/packages/helm-charts/blockscout/templates/blockscout-indexer.deployment.yaml @@ -21,6 +21,12 @@ spec: component: blockscout-indexer template: metadata: + {{- if .Values.blockscout.metrics.enabled}} + annotations: + prometheus.io/path: /metrics/indexer + prometheus.io/port: "4001" + prometheus.io/scrape: "true" + {{- end}} labels: app: blockscout release: {{ .Release.Name }} @@ -44,7 +50,7 @@ spec: - -c args: - | - exec mix cmd --app indexer "iex -e 'IEx.configure(default_prompt: \"\", alive_prompt: \"\")' -S mix" + exec mix cmd --app indexer "iex --name {{ .Values.blockscout.indexer.erlangNodeName}}@0.0.0.0 -e 'IEx.configure(default_prompt: \"\", alive_prompt: \"\")' -S mix" ports: - name: health containerPort: {{ .Values.blockscout.indexer.port }} @@ -81,6 +87,8 @@ spec: value: "true" - name: POOL_SIZE value: {{ .Values.blockscout.indexer.pool_size | quote }} + - name: METRICS_ENABLED + value: "{{.Values.blockscout.indexer.metrics.enabled}}" {{ include "celo.blockscout-env-vars" . | indent 8 }} {{- $data := dict "Values" .Values "Database" .Values.blockscout.indexer.db }} {{ include "celo.blockscout-db-sidecar" $data | indent 6 }} diff --git a/packages/helm-charts/blockscout/templates/blockscout-web.deployment.yaml b/packages/helm-charts/blockscout/templates/blockscout-web.deployment.yaml index a46b29ac274..7ce2107e0e7 100644 --- a/packages/helm-charts/blockscout/templates/blockscout-web.deployment.yaml +++ b/packages/helm-charts/blockscout/templates/blockscout-web.deployment.yaml @@ -21,6 +21,12 @@ spec: component: blockscout-web template: metadata: + {{- if .Values.blockscout.metrics.enabled}} + annotations: + prometheus.io/path: /metrics/web + prometheus.io/port: "4000" + prometheus.io/scrape: "true" + {{- end}} labels: app: blockscout release: {{ .Release.Name }} @@ -43,7 +49,7 @@ spec: - -c args: - | - exec mix cmd --app block_scout_web "iex -e 'IEx.configure(default_prompt: \"\", alive_prompt: \"\")' -S mix phx.server" + exec mix cmd --app block_scout_web "iex --name {{ .Values.blockscout.web.erlangNodeName}}@0.0.0.0 -e 'IEx.configure(default_prompt: \"\", alive_prompt: \"\")' -S mix phx.server" ports: - name: http containerPort: {{ .Values.blockscout.web.port }} diff --git a/packages/helm-charts/blockscout/values-alfajores.yaml b/packages/helm-charts/blockscout/values-alfajores.yaml index b5eb21b698b..5e8b715a40d 100644 --- a/packages/helm-charts/blockscout/values-alfajores.yaml +++ b/packages/helm-charts/blockscout/values-alfajores.yaml @@ -43,11 +43,9 @@ blockscout: memory: 250Mi cpu: 200m pool_size: 10 - sourcify: - enabled: true - serverUrl: https://sourcify.dev/server - repoUrl: https://repo.sourcify.dev/contracts/full_match/ resources: requests: memory: 250M cpu: 500m + metrics: + enabled: false diff --git a/packages/helm-charts/blockscout/values-baklava.yaml b/packages/helm-charts/blockscout/values-baklava.yaml index d5724c1d933..89362723e51 100644 --- a/packages/helm-charts/blockscout/values-baklava.yaml +++ b/packages/helm-charts/blockscout/values-baklava.yaml @@ -12,6 +12,8 @@ blockscout: requests: memory: 1000Mi cpu: 2 + metrics: + enabled: true api: autoscaling: maxReplicas: 3 @@ -43,11 +45,9 @@ blockscout: memory: 250Mi cpu: 200m pool_size: 10 - sourcify: - enabled: true - serverUrl: https://sourcify.dev/server - repoUrl: https://repo.sourcify.dev/contracts/full_match/ resources: requests: memory: 250M cpu: 500m + metrics: + enabled: false diff --git a/packages/helm-charts/blockscout/values-blockscoutstagingrc1.yaml b/packages/helm-charts/blockscout/values-blockscoutstagingrc1.yaml new file mode 100644 index 00000000000..1ff0179e206 --- /dev/null +++ b/packages/helm-charts/blockscout/values-blockscoutstagingrc1.yaml @@ -0,0 +1,53 @@ +imagePullPolicy: IfNotPresent +blockscout: + indexer: + db: + proxy: + resources: + requests: + memory: 4Gi + cpu: 2 + pool_size: 100 + resources: + requests: + memory: 12Gi + cpu: 5 + metrics: + enabled: true + api: + autoscaling: + maxReplicas: 10 + minReplicas: 2 + target: + cpu: 70 + db: + proxy: + resources: + requests: + memory: 500Mi + cpu: 700m + pool_size: 10 + resources: + requests: + memory: 500Mi + cpu: 500m + web: + host: blockscoutstagingrc1-blockscout3.celo-testnet.org + autoscaling: + maxReplicas: 10 + minReplicas: 2 + target: + cpu: 70 + db: + proxy: + resources: + requests: + memory: 500Mi + cpu: 700m + pool_size: 10 + resources: + requests: + memory: 250M + cpu: 500m + metrics: + enabled: false diff --git a/packages/helm-charts/blockscout/values-rc1.yaml b/packages/helm-charts/blockscout/values-rc1.yaml index d16d2a1380d..c30f143eabf 100644 --- a/packages/helm-charts/blockscout/values-rc1.yaml +++ b/packages/helm-charts/blockscout/values-rc1.yaml @@ -12,6 +12,8 @@ blockscout: requests: memory: 12Gi cpu: 5 + metrics: + enabled: true api: autoscaling: maxReplicas: 10 @@ -43,11 +45,9 @@ blockscout: memory: 500Mi cpu: 700m pool_size: 10 - sourcify: - enabled: true - serverUrl: https://sourcify.dev/server - repoUrl: https://repo.sourcify.dev/contracts/full_match/ resources: requests: memory: 250M cpu: 500m + metrics: + enabled: false diff --git a/packages/helm-charts/blockscout/values.yaml b/packages/helm-charts/blockscout/values.yaml index db223f83857..e4687b91a80 100644 --- a/packages/helm-charts/blockscout/values.yaml +++ b/packages/helm-charts/blockscout/values.yaml @@ -47,6 +47,7 @@ blockscout: requests: memory: 1000Mi cpu: 2 + erlangNodeName: blockscout-indexer api: port: 4000 strategy: @@ -98,6 +99,7 @@ blockscout: requests: memory: 500Mi cpu: 500m + erlangNodeName: blockscout-api web: port: 4000 strategy: @@ -145,10 +147,15 @@ blockscout: timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 + sourcify: + enabled: true + serverUrl: https://sourcify.dev/server + repoUrl: https://repo.sourcify.dev/contracts/full_match/ resources: requests: memory: 250M cpu: 500m + erlangNodeName: blockscout-web image: repository: gcr.io/celo-testnet/blockscout tag: v2.0.4-beta-celo @@ -161,9 +168,9 @@ blockscout: name: blockscout drop: "false" jsonrpc_http_url: http://tx-nodes-private:8545 - jsonrpc_ws_url: ws://tx-nodes-private:8546 + jsonrpc_ws_url: ws://tx-nodes-private:8545 metadata_crawler: schedule: "*/30 * * * *" image: repository: gcr.io/celo-testnet/celo-monorepo - tag: metadata-crawler-33bb740ffb4ee7804046f48299f8686416afbe47 \ No newline at end of file + tag: metadata-crawler-77a392216d4927e85ce4b683508fc0539aa92a34 diff --git a/packages/helm-charts/celo-fullnode/baklava-gcp-forno-europe-west1-values.yaml b/packages/helm-charts/celo-fullnode/baklava-gcp-forno-europe-west1-values.yaml new file mode 100644 index 00000000000..7888c91e7cb --- /dev/null +++ b/packages/helm-charts/celo-fullnode/baklava-gcp-forno-europe-west1-values.yaml @@ -0,0 +1,5 @@ +geth: + resources: + requests: + memory: "2Gi" + cpu: "1" diff --git a/packages/helm-charts/celo-fullnode/rc1-gcp-private-txnodes-values.yaml b/packages/helm-charts/celo-fullnode/rc1-gcp-private-txnodes-values.yaml new file mode 100644 index 00000000000..c518612ffa5 --- /dev/null +++ b/packages/helm-charts/celo-fullnode/rc1-gcp-private-txnodes-values.yaml @@ -0,0 +1,5 @@ +geth: + resources: + requests: + memory: "14Gi" + cpu: "7" diff --git a/packages/helm-charts/celo-fullnode/templates/statefulset.yaml b/packages/helm-charts/celo-fullnode/templates/statefulset.yaml index e4fa6e918fc..09b0ea1aab7 100644 --- a/packages/helm-charts/celo-fullnode/templates/statefulset.yaml +++ b/packages/helm-charts/celo-fullnode/templates/statefulset.yaml @@ -83,7 +83,7 @@ spec: {{ include "common.full-node-container" (dict "Values" $.Values "Release" $.Release "Chart" $.Chart "expose" true "ip_addresses" .Values.geth.public_ip_per_node "syncmode" .Values.geth.syncmode "gcmode" .Values.geth.gcmode "rpc_apis" .Values.geth.rpc_apis "pprof" (or (.Values.prometheus) (.Values.pprof.enabled)) "pprof_port" (.Values.pprof.port) "metrics" (or (.Values.prometheus) (.Values.metrics)) "ports" .Values.geth.service_node_port_per_full_node "light_serve" .Values.geth.light.serve "light_maxpeers" .Values.geth.light.maxpeers "maxpeers" .Values.geth.maxpeers "geth_flags" (.Values.geth.flags | default "") "extra_setup" ((include "celo-fullnode.aws-subnet-specific-nat-ip" .) | indent 4)) | indent 6 }} {{ if .Values.geth.create_network_endpoint_group }} {{ include "celo-fullnode.health-checker-server" (dict "protocol_name" "http" "tcp_check_port" 8545 "server_port" 6000) | indent 6 }} -{{ include "celo-fullnode.health-checker-server" (dict "protocol_name" "ws" "tcp_check_port" 8546 "server_port" 6001) | indent 6 }} +{{ include "celo-fullnode.health-checker-server" (dict "protocol_name" "ws" "tcp_check_port" .Values.geth.ws_port "server_port" 6001) | indent 6 }} {{ end }} terminationGracePeriodSeconds: {{ .Values.geth.terminationGracePeriodSeconds | default 300 }} volumes: diff --git a/packages/helm-charts/celo-fullnode/values.yaml b/packages/helm-charts/celo-fullnode/values.yaml index c57fdd7e6ea..5e21a0062d1 100644 --- a/packages/helm-charts/celo-fullnode/values.yaml +++ b/packages/helm-charts/celo-fullnode/values.yaml @@ -56,7 +56,7 @@ geth: imagePullPolicy: IfNotPresent resources: requests: - memory: "8Gi" + memory: "6Gi" cpu: "3" # limits: # memory: "1Gi" @@ -93,6 +93,7 @@ geth: maxpeers: 1000 serve: 90 fullnodeCheckBlockAge: true + ws_port: 8546 genesis: networkId: 31418 diff --git a/packages/helm-charts/common/templates/_helpers.tpl b/packages/helm-charts/common/templates/_helpers.tpl index 7c710889f0e..739222a848b 100644 --- a/packages/helm-charts/common/templates/_helpers.tpl +++ b/packages/helm-charts/common/templates/_helpers.tpl @@ -142,7 +142,8 @@ fi {{- end }} {{- if .expose }} RPC_APIS="{{ .rpc_apis | default "eth,net,web3,debug,txpool" }}" - ADDITIONAL_FLAGS="${ADDITIONAL_FLAGS} --rpc --rpcaddr 0.0.0.0 --rpcapi=${RPC_APIS} --rpccorsdomain='*' --rpcvhosts=* --ws --wsaddr 0.0.0.0 --wsorigins=* --wsapi=${RPC_APIS}" + ADDITIONAL_FLAGS="${ADDITIONAL_FLAGS} --rpc --rpcaddr 0.0.0.0 --rpcapi=${RPC_APIS} --rpccorsdomain='*' --rpcvhosts=*" + ADDITIONAL_FLAGS="${ADDITIONAL_FLAGS} --ws --wsaddr 0.0.0.0 --wsorigins=* --wsapi=${RPC_APIS} --wsport={{ default .Values.geth.ws_port .ws_port }}" {{- end }} {{- if .ping_ip_from_packet | default false }} ADDITIONAL_FLAGS="${ADDITIONAL_FLAGS} --ping-ip-from-packet" @@ -190,7 +191,7 @@ fi {{- end }} --light.serve={{- if kindIs "invalid" .light_serve -}}90{{- else -}}{{- .light_serve -}}{{- end }} \ --light.maxpeers={{- if kindIs "invalid" .light_maxpeers -}}1000{{- else -}}{{- .light_maxpeers -}}{{- end }} \ - --maxpeers {{ .maxpeers | default 1200 }} \ + --maxpeers={{- if kindIs "invalid" .maxpeers -}}1200{{- else -}}{{- .maxpeers -}}{{- end }} \ --nousb \ --syncmode={{ .syncmode | default .Values.geth.syncmode }} \ --gcmode={{ .gcmode | default .Values.geth.gcmode }} \ @@ -252,7 +253,7 @@ fi - name: rpc containerPort: 8545 - name: ws - containerPort: 8546 + containerPort: {{ default .Values.geth.ws_port .ws_port }} {{ end }} {{- if .pprof }} - name: pprof @@ -493,7 +494,7 @@ prometheus.io/port: "{{ $pprof.port | default 6060 }}" - "-c" - | if [ -d /root/.celo/celo/chaindata ]; then - lastBlockTimestamp=$(timeout 60 geth console --maxpeers 0 --light.maxpeers 0 --syncmode full --exec "eth.getBlock(\"latest\").timestamp" 2> /dev/null) + lastBlockTimestamp=$(timeout 120 geth console --maxpeers 0 --light.maxpeers 0 --syncmode full --txpool.nolocals --exec "eth.getBlock(\"latest\").timestamp") day=$(date +%s) diff=$(($day - $lastBlockTimestamp)) # If lastBlockTimestamp is older than 1 day old, pull the chaindata rather than using the current PVC. diff --git a/packages/helm-charts/common/values.yaml b/packages/helm-charts/common/values.yaml index 27d50dc4caa..94b41c25b16 100644 --- a/packages/helm-charts/common/values.yaml +++ b/packages/helm-charts/common/values.yaml @@ -26,6 +26,7 @@ geth: use_static_ips: true azure_mixed_protocols: true fullnodeCheckBlockAge: true + ws_port: 8546 # Genesis genesis: diff --git a/packages/helm-charts/grafana/templates/ingress.yaml b/packages/helm-charts/grafana/templates/ingress.yaml index 10684dc6d25..6ccabe0f46c 100644 --- a/packages/helm-charts/grafana/templates/ingress.yaml +++ b/packages/helm-charts/grafana/templates/ingress.yaml @@ -3,11 +3,7 @@ {{- $servicePort := .Values.service.port -}} {{- $ingressPath := .Values.ingress.path -}} {{- $extraPaths := .Values.ingress.extraPaths -}} -{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} -apiVersion: networking.k8s.io/v1 -{{ else }} -apiVersion: extensions/v1beta1 -{{ end -}} +apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: {{ $fullName }} @@ -40,19 +36,15 @@ spec: - path: {{ $ingressPath }} pathType: Prefix backend: - service: - name: {{ $fullName }} - port: - number: {{ $servicePort }} + serviceName: {{ $fullName }} + servicePort: {{ $servicePort }} {{- end }} {{- else }} - http: paths: - backend: - service: - name: {{ $fullName }} - port: - number: {{ $servicePort }} + serviceName: {{ $fullName }} + servicePort: {{ $servicePort }} {{- if $ingressPath }} path: {{ $ingressPath }} pathType: Prefix diff --git a/packages/helm-charts/komenci-rbac/templates/_helper.tpl b/packages/helm-charts/komenci-rbac/templates/_helper.tpl index 77f405b1bf6..1527741e2c3 100644 --- a/packages/helm-charts/komenci-rbac/templates/_helper.tpl +++ b/packages/helm-charts/komenci-rbac/templates/_helper.tpl @@ -5,3 +5,11 @@ {{- define "komenci-pod-name" -}} {{- .Values.environment.name -}}-relayer-{{- .index -}} {{- end -}} + +{{- define "rewards-name" -}} +{{- .Values.environment.name -}}-komenci-rewards-rbac-{{- .index -}} +{{- end -}} + +{{- define "komenci-rewards-pod-name" -}} +{{- .Values.environment.name -}}-rewards-relayer-{{- .index -}} +{{- end -}} \ No newline at end of file diff --git a/packages/helm-charts/komenci-rbac/templates/role.yaml b/packages/helm-charts/komenci-rbac/templates/role.yaml index dd4be55079b..df04f4e72b0 100644 --- a/packages/helm-charts/komenci-rbac/templates/role.yaml +++ b/packages/helm-charts/komenci-rbac/templates/role.yaml @@ -11,3 +11,17 @@ rules: verbs: ["get", "patch"] --- {{ end }} + +{{ range $index, $e := until (.Values.rewards.relayer.replicas | int) }} +{{- $index_counter := (dict "Values" $.Values "index" $index) -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "rewards-name" $index_counter }} +rules: +- apiGroups: [""] + resources: ["pods"] + resourceNames: ["{{ template "komenci-rewards-pod-name" $index_counter }}"] + verbs: ["get", "patch"] +--- +{{ end }} diff --git a/packages/helm-charts/komenci-rbac/templates/rolebinding.yaml b/packages/helm-charts/komenci-rbac/templates/rolebinding.yaml index dbc187d2d87..f9ebfb24826 100644 --- a/packages/helm-charts/komenci-rbac/templates/rolebinding.yaml +++ b/packages/helm-charts/komenci-rbac/templates/rolebinding.yaml @@ -13,3 +13,19 @@ subjects: name: {{ template "name" $index_counter }} --- {{ end }} + +{{ range $index, $e := until (.Values.rewards.relayer.replicas | int) }} +{{- $index_counter := (dict "Values" $.Values "index" $index) -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "rewards-name" $index_counter }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "rewards-name" $index_counter }} +subjects: +- kind: ServiceAccount + name: {{ template "rewards-name" $index_counter }} +--- +{{ end }} diff --git a/packages/helm-charts/komenci-rbac/templates/service-account.yaml b/packages/helm-charts/komenci-rbac/templates/service-account.yaml index 61cadbb621d..da8ea5730db 100644 --- a/packages/helm-charts/komenci-rbac/templates/service-account.yaml +++ b/packages/helm-charts/komenci-rbac/templates/service-account.yaml @@ -6,3 +6,12 @@ metadata: name: {{ template "name" $index_counter}} --- {{ end }} + +{{ range $index, $e := until (.Values.rewards.relayer.replicas | int) }} +{{- $index_counter := (dict "Values" $.Values "index" $index) -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "rewards-name" $index_counter}} +--- +{{ end }} diff --git a/packages/helm-charts/komenci/templates/_helpers.tpl b/packages/helm-charts/komenci/templates/_helpers.tpl index d92b63640d2..df8bc9c4a72 100644 --- a/packages/helm-charts/komenci/templates/_helpers.tpl +++ b/packages/helm-charts/komenci/templates/_helpers.tpl @@ -5,10 +5,18 @@ The name of the deployment {{- .Values.environment.name -}}-relayer {{- end -}} +{{- define "rewards-relayer-name" -}} +{{- .Values.environment.name -}}-rewards-relayer +{{- end -}} + {{- define "komenci-onboarding-fullname" -}} {{- .Values.environment.name -}}-onboarding {{- end -}} +{{- define "komenci-rewards-fullname" -}} +{{- .Values.environment.name -}}-rewards +{{- end -}} + {{/* Common labels that are recommended to be used by Helm and Kubernetes */}} @@ -41,6 +49,20 @@ Label specific to the komenci onboarding component app.kubernetes.io/component: komenci-onboarding {{- end -}} +{{/* +Label specific to the komenci rewards component +*/}} +{{- define "komenci-rewards-component-label" -}} +app.kubernetes.io/component: komenci-rewards +{{- end -}} + +{{/* +Label specific to the komenci rewards relayer component +*/}} +{{- define "komenci-rewards-relayer-component-label" -}} +app.kubernetes.io/component: komenci-rewards-relayer +{{- end -}} + {{/* The name of the azure identity binding for all relayers */}} @@ -49,8 +71,22 @@ The name of the azure identity binding for all relayers {{- end -}} {{/* -The name of the azure identity for all oracles +The name of the azure identity binding for all rewards relayers +*/}} +{{- define "azure-rewards-identity-binding-name" -}} +{{- with .dot -}}{{ template "rewards-relayer-name" . }}{{- end -}}-{{ .index }}-identity-binding +{{- end -}} + +{{/* +The name of the azure identity for all relayers */}} {{- define "azure-identity-name" -}} {{- with .dot -}}{{ template "name" . }}{{- end -}}-{{ .index }}-identity +{{- end -}} + +{{/* +The name of the azure identity for all rewards relayers +*/}} +{{- define "azure-rewards-identity-name" -}} +{{- with .dot -}}{{ template "rewards-relayer-name" . }}{{- end -}}-{{ .index }}-identity {{- end -}} \ No newline at end of file diff --git a/packages/helm-charts/komenci/templates/azure-identity-binding.yaml b/packages/helm-charts/komenci/templates/azure-identity-binding.yaml index 455ed7b1c49..6160db69102 100644 --- a/packages/helm-charts/komenci/templates/azure-identity-binding.yaml +++ b/packages/helm-charts/komenci/templates/azure-identity-binding.yaml @@ -10,3 +10,16 @@ spec: --- {{ end }} {{ end }} + +{{- range $index, $identity := .Values.rewards.relayer.identities -}} +{{ if (hasKey $identity "azure") }} +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentityBinding +metadata: + name: {{ template "azure-rewards-identity-binding-name" (dict "dot" $ "index" $index) }} +spec: + azureIdentity: {{ template "azure-rewards-identity-name" (dict "dot" $ "index" $index) }} + selector: {{ template "azure-rewards-identity-binding-name" (dict "dot" $ "index" $index) }} +--- +{{ end }} +{{ end }} diff --git a/packages/helm-charts/komenci/templates/azure-identity.yaml b/packages/helm-charts/komenci/templates/azure-identity.yaml index 69098472a2d..b93930624c4 100644 --- a/packages/helm-charts/komenci/templates/azure-identity.yaml +++ b/packages/helm-charts/komenci/templates/azure-identity.yaml @@ -13,3 +13,19 @@ spec: --- {{ end }} {{ end }} + +{{- range $index, $identity := .Values.rewards.relayer.identities -}} +{{ if (hasKey $identity "azure") }} +apiVersion: aadpodidentity.k8s.io/v1 +kind: AzureIdentity +metadata: + name: {{ template "azure-rewards-identity-name" (dict "dot" $ "index" $index) }} + annotations: + aadpodidentity.k8s.io/Behavior: namespaced +spec: + type: 0 + resourceID: {{ $identity.azure.id }} + clientID: {{ $identity.azure.clientId }} +--- +{{ end }} +{{ end }} diff --git a/packages/helm-charts/komenci/templates/onboarding-deployment.yaml b/packages/helm-charts/komenci/templates/onboarding-deployment.yaml index b993754216c..fd6bf98290e 100644 --- a/packages/helm-charts/komenci/templates/onboarding-deployment.yaml +++ b/packages/helm-charts/komenci/templates/onboarding-deployment.yaml @@ -27,7 +27,7 @@ spec: - bash - "-c" - | - node dist/apps/onboarding/main.js + node packages/apps/api/dist/main.js resources: {{- toYaml .Values.onboarding.resources | nindent 12 }} env: diff --git a/packages/helm-charts/komenci/templates/onboarding-ingress.yaml b/packages/helm-charts/komenci/templates/onboarding-ingress.yaml index d5727df8f34..842c2870d60 100644 --- a/packages/helm-charts/komenci/templates/onboarding-ingress.yaml +++ b/packages/helm-charts/komenci/templates/onboarding-ingress.yaml @@ -14,7 +14,11 @@ spec: - host: {{ .Values.onboarding.publicHostname }} http: paths: + - path: /rewards + backend: + serviceName: {{ .Release.Namespace }}-rewards + servicePort: 3000 - path: / backend: serviceName: {{ .Release.Namespace }}-onboarding - servicePort: 3000 \ No newline at end of file + servicePort: 3000 diff --git a/packages/helm-charts/komenci/templates/pkey-secret.yaml b/packages/helm-charts/komenci/templates/pkey-secret.yaml deleted file mode 100644 index 48a3fde41bf..00000000000 --- a/packages/helm-charts/komenci/templates/pkey-secret.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: pkey-secret - labels: -{{ include "labels" . | indent 4 }} -type: Opaque -data: -{{ range $index, $identity := .Values.relayer.identities }} -{{ if (hasKey $identity "privateKey") }} - private-key-{{ $index }}: {{ $identity.privateKey }} -{{ end }} -{{ end }} diff --git a/packages/helm-charts/komenci/templates/relayer-statefulset.yaml b/packages/helm-charts/komenci/templates/relayer-statefulset.yaml index 352bd5904fb..a6e9fd4dea9 100644 --- a/packages/helm-charts/komenci/templates/relayer-statefulset.yaml +++ b/packages/helm-charts/komenci/templates/relayer-statefulset.yaml @@ -104,7 +104,7 @@ spec: ADDRESSES={{- range $index, $identity := .Values.relayer.identities -}}{{ $identity.address }},{{- end }} export WALLET_ADDRESS=`echo -n $ADDRESSES | cut -d ',' -f $((RID + 1))` - node dist/apps/relayer/main.js + node packages/apps/relayer/dist/main.js env: - name: REPLICA_NAME valueFrom: @@ -133,11 +133,3 @@ spec: port: 3000 initialDelaySeconds: 15 periodSeconds: 20 - volumeMounts: - - name: private-key-volume - readOnly: true - mountPath: "/private-keys" - volumes: - - name: private-key-volume - secret: - secretName: pkey-secret diff --git a/packages/helm-charts/komenci/templates/rewards-deployment.yaml b/packages/helm-charts/komenci/templates/rewards-deployment.yaml new file mode 100644 index 00000000000..61f77c8d3b8 --- /dev/null +++ b/packages/helm-charts/komenci/templates/rewards-deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "komenci-rewards-fullname" . }} + labels: +{{- include "komenci-rewards-component-label" . | nindent 4 }} +spec: + replicas: {{ .Values.rewards.replicaCount }} + selector: + matchLabels: + {{- include "komenci-rewards-component-label" . | nindent 6 }} + template: + metadata: + labels: +{{- include "komenci-rewards-component-label" . | nindent 8 }} + spec: + containers: + - name: komenci-rewards + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: Always + ports: + - name: http + containerPort: 3000 + command: + - bash + - "-c" + - | + node packages/apps/rewards/dist/main.js + env: + - name: REPLICA_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +{{ include "common.env-var" (dict "name" "DB_HOST" "dict" .Values.rewards.db "value_name" "host" "optional" true) | indent 10 }} +{{ include "common.env-var" (dict "name" "DB_PORT" "dict" .Values.rewards.db "value_name" "port" "optional" true) | indent 10 }} +{{ include "common.env-var" (dict "name" "DB_USERNAME" "dict" .Values.rewards.db "value_name" "username") | indent 10 }} +{{ include "common.env-var" (dict "name" "DB_PASSWORD" "dict" .Values.rewards.db "value_name" "password") | indent 10 }} +{{ include "common.env-var" (dict "name" "DB_DATABASE" "dict" .Values.rewards.db "value_name" "database") | indent 10 }} +{{ include "common.env-var" (dict "name" "DB_SYNCHRONIZE" "dict" .Values.rewards.db "value_name" "synchronize") | indent 10 }} +{{ include "common.env-var" (dict "name" "DB_SSL" "dict" .Values.rewards.db "value_name" "ssl") | indent 10 }} +{{ include "common.env-var" (dict "name" "RELAYER_HOST" "dict" .Values.rewards.relayer "value_name" "host") | indent 10 }} +{{ include "common.env-var" (dict "name" "RELAYER_PORT" "dict" .Values.rewards.relayer "value_name" "port") | indent 10 }} +{{ include "common.env-var" (dict "name" "NETWORK" "dict" .Values.environment "value_name" "network") | indent 10 }} +{{ include "common.env-var" (dict "name" "SEGMENT_API_KEY" "dict" .Values.rewards "value_name" "segmentApiKey") | indent 10 }} +{{ include "common.env-var" (dict "name" "SHOULD_SEND_REWARDS" "dict" .Values.rewards "value_name" "shouldSendRewards") | indent 10 }} diff --git a/packages/helm-charts/komenci/templates/rewards-relayer-statefulset.yaml b/packages/helm-charts/komenci/templates/rewards-relayer-statefulset.yaml new file mode 100644 index 00000000000..70fc0e4a129 --- /dev/null +++ b/packages/helm-charts/komenci/templates/rewards-relayer-statefulset.yaml @@ -0,0 +1,128 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "rewards-relayer-name" . }} + labels: +{{ include "labels" . | indent 4 }} +{{ include "komenci-rewards-relayer-component-label" . | indent 4 }} +spec: + ports: + - name: http + port: 3000 + clusterIP: None + selector: +{{ include "komenci-rewards-relayer-component-label" . | indent 4 }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "rewards-relayer-name" . }} + labels: +{{ include "labels" . | indent 4 }} +{{ include "komenci-rewards-relayer-component-label" . | indent 4 }} +spec: + podManagementPolicy: Parallel + updateStrategy: + type: RollingUpdate + replicas: {{ .Values.rewards.relayer.replicas }} + serviceName: relayer + selector: + matchLabels: +{{ include "labels" . | indent 6 }} +{{ include "komenci-rewards-relayer-component-label" . | indent 6 }} + template: + metadata: + labels: +{{ include "labels" . | indent 8 }} +{{ include "komenci-rewards-relayer-component-label" . | indent 8 }} + annotations: +{{ if .Values.rewards.relayer.metrics.enabled }} +{{ include "metric-annotations" . | indent 8 }} +{{ end }} + spec: + initContainers: + - name: set-label + image: {{ .Values.kubectl.image.repository }}:{{ .Values.kubectl.image.tag }} + command: + - /bin/bash + - -c + args: + - | + RID=${POD_NAME##*-} + TOKEN_ENV_VAR_NAME="TOKEN_$RID" + kubectl \ + --namespace "$POD_NAMESPACE" \ + --server="https://kubernetes.default.svc" \ + --token="${!TOKEN_ENV_VAR_NAME}" \ + --certificate-authority="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" \ + label pod "$POD_NAME" \ + --overwrite \ + "aadpodidbinding=$POD_NAME-identity-binding" + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{ range $index, $e := .Values.kube.rewardsServiceAccountSecretNames }} + - name: TOKEN_{{ $index }} + valueFrom: + secretKeyRef: + key: token + name: {{ $e }} + {{ end }} + containers: + - name: komenci-rewards-relayer + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: Always + ports: + - name: prometheus + containerPort: {{ .Values.rewards.relayer.metrics.prometheusPort }} + - name: relayer + containerPort: 3000 + command: + - bash + - "-c" + - | + [[ $REPLICA_NAME =~ -([0-9]+)$ ]] || exit 1 + RID=${BASH_REMATCH[1]} + + # Get the correct key vault name. If this relayer's identity is not + # using Azure HSM signing, the key vault name will be empty and ignored + AZURE_KEY_VAULT_NAMES={{- range $index, $identity := .Values.rewards.relayer.identities -}}{{- if (hasKey $identity "azure" ) -}}{{ $identity.azure.keyVaultName | default "" }}{{- end }},{{- end }} + export AZURE_KEY_NAME=`echo -n $AZURE_KEY_VAULT_NAMES | cut -d ',' -f $((RID + 1))` + export AZURE_VAULT_NAME=`echo -n $AZURE_KEY_VAULT_NAMES | cut -d ',' -f $((RID + 1))` + + # Get the correct relayer account address + ADDRESSES={{- range $index, $identity := .Values.rewards.relayer.identities -}}{{ $identity.address }},{{- end }} + export WALLET_ADDRESS=`echo -n $ADDRESSES | cut -d ',' -f $((RID + 1))` + + node packages/apps/relayer/dist/main.js + env: + - name: REPLICA_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +{{ include "common.env-var" (dict "name" "AZURE_HSM_INIT_TRY_COUNT" "dict" .Values.komenci.azureHsm "value_name" "initTryCount") | indent 8 }} +{{ include "common.env-var" (dict "name" "AZURE_HSM_INIT_MAX_RETRY_BACKOFF_MS" "dict" .Values.komenci.azureHsm "value_name" "initMaxRetryBackoffMs") | indent 8 }} +{{ include "common.env-var" (dict "name" "METRICS" "dict" .Values.rewards.relayer.metrics "value_name" "enabled") | indent 8 }} +{{ include "common.env-var" (dict "name" "OVERRIDE_INDEX" "dict" .Values.rewards.relayer "value_name" "overrideIndex" "optional" true) | indent 8 }} +{{ include "common.env-var" (dict "name" "PROMETHEUS_PORT" "dict" .Values.rewards.relayer.metrics "value_name" "prometheusPort") | indent 8 }} +{{ include "common.env-var" (dict "name" "NODE_ENV" "dict" .Values.rewards.relayer "value_name" "node_env") | indent 8 }} +{{ include "common.env-var" (dict "name" "RELAYER_PORT" "dict" .Values.rewards.relayer "value_name" "port") | indent 8 }} +{{ include "common.env-var" (dict "name" "NETWORK" "dict" .Values.environment "value_name" "network") | indent 8 }} +{{ include "common.env-var" (dict "name" "WALLET_TYPE" "dict" .Values.rewards.relayer "value_name" "walletType") | indent 8 }} +{{ include "common.env-var" (dict "name" "GAS_PRICE_UPDATE_INTERVAL_MS" "dict" .Values.rewards.relayer "value_name" "gasPriceUpdateIntervalMs") | indent 8 }} + readinessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 20 \ No newline at end of file diff --git a/packages/helm-charts/komenci/templates/rewards-service.yaml b/packages/helm-charts/komenci/templates/rewards-service.yaml new file mode 100644 index 00000000000..0653de5454a --- /dev/null +++ b/packages/helm-charts/komenci/templates/rewards-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "komenci-rewards-fullname" . }} + labels: +{{ include "labels" . | indent 4 }} +{{ include "komenci-rewards-component-label" . | indent 4 }} +spec: + clusterIP: None + selector: +{{ include "komenci-rewards-component-label" . | indent 4 }} + ports: + - name: http + port: 3000 diff --git a/packages/helm-charts/komenci/values.yaml b/packages/helm-charts/komenci/values.yaml index 1537194abf6..7992c7df226 100644 --- a/packages/helm-charts/komenci/values.yaml +++ b/packages/helm-charts/komenci/values.yaml @@ -86,5 +86,38 @@ onboarding: synchronize: true ssl: true +rewards: + segmentApiKey: 'writeApiKey' + shouldSendRewards: false + relayer: + node_env: production + image: + repository: celotestnet.azurecr.io/komenci/relayer + tag: dae43ddce108a73da07dce73875b980ff077c7d4 + replicas: 2 + port: 3000 + identities: + - address: 0xb04390478A57E3C2147599D5380434f25fa5234d + privateKey: 0x000 + azure: + id: defaultId + clientId: defaultClientId + keyVaultName: staging-komenci-rewards + azureHsm: + initTryCount: 5 + initMaxRetryBackoffMs: 30000 + metrics: + enabled: true + prometheusPort: 9090 + walletType: azure-hsm + gasPriceUpdateIntervalMs: "1200000" + db: + host: komenci-komenci-postgresql + port: 5432 + username: 'postgres' + database: 'postgres' + synchronize: true + ssl: true + loggingAgent: credentials: eydleGFtcGxlJzogJ2NyZWRlbnRpYWxzJ30K # base64 credentials.json of a gcloud service account \ No newline at end of file diff --git a/packages/helm-charts/load-test/Chart.yaml b/packages/helm-charts/load-test/Chart.yaml index 1d9a9e24228..cb5463e3193 100644 --- a/packages/helm-charts/load-test/Chart.yaml +++ b/packages/helm-charts/load-test/Chart.yaml @@ -8,5 +8,5 @@ keywords: appVersion: v1.7.3 dependencies: - name: common - version: 0.1.0 + version: 0.1.1 repository: file://../common diff --git a/packages/helm-charts/load-test/templates/load-test.configmap.yaml b/packages/helm-charts/load-test/templates/load-test.configmap.yaml index cfbef10db62..ad1ea815500 100644 --- a/packages/helm-charts/load-test/templates/load-test.configmap.yaml +++ b/packages/helm-charts/load-test/templates/load-test.configmap.yaml @@ -9,5 +9,5 @@ metadata: heritage: {{ .Release.Service }} component: load-test data: - genesis.json: {{ .Values.geth.genesisFile | b64dec | quote }} + genesis.json: {{ .Values.genesis.genesisFileBase64 | b64dec | quote }} static-nodes.json: {{ .Values.geth.staticNodes | b64dec | quote }} diff --git a/packages/helm-charts/load-test/templates/load-test.statefulset.yaml b/packages/helm-charts/load-test/templates/load-test.statefulset.yaml index 4ad40177ca6..559a9899676 100644 --- a/packages/helm-charts/load-test/templates/load-test.statefulset.yaml +++ b/packages/helm-charts/load-test/templates/load-test.statefulset.yaml @@ -1,3 +1,4 @@ +{{- $reuseClient := .Values.reuse_light_clients | default false -}} apiVersion: v1 kind: Service metadata: @@ -42,25 +43,27 @@ spec: - name: generate-keys image: {{ .Values.celotool.image.repository }}:{{ .Values.celotool.image.tag }} imagePullPolicy: {{ .Values.imagePullPolicy }} - command: - - bash - - "-c" - - | - [[ $REPLICA_NAME =~ -([0-9]+)$ ]] || exit 1 - RID=${BASH_REMATCH[1]} - echo $RID > /root/.celo/rid + args: + - | + [[ $REPLICA_NAME =~ -([0-9]+)$ ]] || exit 1 + RID=${BASH_REMATCH[1]} + echo $RID > /root/.celo/rid + celotooljs.sh generate public-key --mnemonic "$MNEMONIC" --accountType bootnode --index 0 > /root/.celo/bootnodeEnodeAddress + echo -n "Generating Bootnode enode address for the validator: " + cat /root/.celo/bootnodeEnodeAddress - echo "Generating private key for rid=$RID" - celotooljs.sh generate bip32 \ - --mnemonic "$MNEMONIC" \ - --accountType load_testing \ - --index $RID \ - > /root/.celo/pkey + BOOTNODE_IP_ADDRESS=${{ .Release.Namespace | upper }}_BOOTNODE_SERVICE_HOST + echo `cat /root/.celo/bootnodeEnodeAddress`@$BOOTNODE_IP_ADDRESS:30301 > /root/.celo/bootnodeEnode + echo -n "Generating Bootnode enode for the validator: " + cat /root/.celo/bootnodeEnode - echo "Generating account address for rid=$RID" - celotooljs.sh generate account-address \ - --private-key `cat /root/.celo/pkey` \ - > /root/.celo/address + celotooljs.sh generate prepare-load-test \ + --mnemonic "$MNEMONIC" \ + --threads {{ .Values.threads | default "1" }} \ + --index $RID + command: + - bash + - -c env: - name: REPLICA_NAME valueFrom: @@ -75,47 +78,59 @@ spec: - name: data mountPath: /root/.celo {{ include "common.init-genesis-container" . | indent 6 }} -{{ include "common.import-geth-account-container" . | indent 6 }} - containers: - - name: geth + - name: import-geth-account image: {{ .Values.geth.image.repository }}:{{ .Values.geth.image.tag }} imagePullPolicy: {{ .Values.imagePullPolicy }} command: ["/bin/sh"] args: - "-c" + - | + for thread in $(seq 0 {{ sub .Values.threads 1 | default "0" }}); do + geth --nousb account import --password /root/.celo/account/accountSecret /root/.celo/pkey$thread || true + done + volumeMounts: + - name: data + mountPath: /root/.celo + - name: account + mountPath: "/root/.celo/account" + readOnly: true + containers: +{{- if $reuseClient }} + - name: geth + image: {{ $.Values.geth.image.repository }}:{{ $.Values.geth.image.tag }} + imagePullPolicy: {{ $.Values.imagePullPolicy }} + command: ["/bin/sh"] + args: + - "-c" - |- set -euo pipefail cp /var/geth/static-nodes.json /root/.celo/static-nodes.json - ACCOUNT_ADDRESS=`cat /root/.celo/address` + ACCOUNT_ADDRESSES=$(cat /root/.celo/address | tr '\n' ',') + ACCOUNT_ADDRESSES=${ACCOUNT_ADDRESSES::-1} exec geth \ + --datadir /root/.celo \ + --ipcpath=geth.ipc \ + --nousb \ --rpc \ --rpcapi=eth,web3,debug,admin,personal,net \ + --allow-insecure-unlock \ --rpcvhosts=* \ - --networkid={{ .Values.geth.networkID }} \ - --nodekey=/root/.celo/pkey \ - --syncmode=ultralight \ + --networkid={{ $.Values.geth.networkID }} \ + --nodekey=/root/.celo/pkey0 \ + --syncmode=fast \ --consoleformat=json \ --consoleoutput=stdout \ - --verbosity={{ .Values.geth.verbosity }} \ - --unlock=${ACCOUNT_ADDRESS} \ - --etherbase=${ACCOUNT_ADDRESS} \ - --password=/root/.celo/account/accountSecret - ports: - - name: discovery-udp - containerPort: 30303 - protocol: UDP - - name: discovery-tcp - containerPort: 30303 - - name: rpc - containerPort: 8545 - - name: ws - containerPort: 8546 + --verbosity=1 \ + --unlock=$ACCOUNT_ADDRESSES \ + --password=/root/.celo/account/accountSecret \ + --port 30303 \ + --rpcport 8545 resources: requests: - memory: 250Mi - cpu: 40m + memory: 4Gi + cpu: 2 volumeMounts: - name: data mountPath: /root/.celo @@ -124,9 +139,57 @@ spec: - name: account mountPath: "/root/.celo/account" readOnly: true +{{- else }} +{{- range $index, $e := until (.Values.threads | int) }} + - name: geth-{{ $index }} + image: {{ $.Values.geth.image.repository }}:{{ $.Values.geth.image.tag }} + imagePullPolicy: {{ $.Values.imagePullPolicy }} + command: ["/bin/sh"] + args: + - "-c" + - |- + set -euo pipefail + cp -rp /root/.celo_share /root/.celo + cp /var/geth/static-nodes.json /root/.celo/static-nodes.json + + ACCOUNT_ADDRESS=$(awk 'NR=={{ add $index 1 }}' /root/.celo/address) + + exec geth \ + --nousb \ + --rpc \ + --rpcapi=eth,web3,debug,admin,personal,net \ + --allow-insecure-unlock \ + --rpcvhosts=* \ + --networkid={{ $.Values.geth.networkID }} \ + --nodekey=/root/.celo/pkey{{ $index }} \ + --syncmode=lightest \ + --consoleformat=json \ + --consoleoutput=stdout \ + --verbosity=1 \ + --unlock=$ACCOUNT_ADDRESS \ + --password=/root/.celo/account/accountSecret \ + --port {{ add 30303 $index }} \ + --rpcport {{ add 8545 $index }} + resources: + requests: + memory: 200Mi + cpu: 100m + volumeMounts: + - name: data + mountPath: /root/.celo_share + readOnly: true + - name: config + mountPath: /var/geth + - name: account + mountPath: "/root/.celo_share/account" + readOnly: true +{{- end }} +{{- end }} - name: simulate-client image: {{ .Values.celotool.image.repository }}:{{ .Values.celotool.image.tag }} imagePullPolicy: {{ .Values.imagePullPolicy }} + securityContext: + runAsUser: 0 command: - bash - "-c" @@ -137,22 +200,31 @@ spec: RECIPIENT_INDEX=$(( ($RID + 1) % {{ .Values.replicas }} )) exec celotooljs.sh geth simulate-client \ +{{- if $reuseClient }} + --reuse-client \ +{{- end }} --index $RID \ --recipient-index $RECIPIENT_INDEX \ --delay {{ .Values.delay }} \ --mnemonic "$MNEMONIC" \ --blockscout-url {{ .Values.blockscout.url }} \ - --blockscoutMeasurePercent {{ .Values.blockscout.measurePercent }} + --blockscoutMeasurePercent {{ .Values.blockscout.measurePercent }} \ + --client-count {{ .Values.threads | default "1" }} resources: requests: - memory: 256Mi - cpu: 5m + memory: 4Gi + cpu: 2 env: - name: MNEMONIC valueFrom: secretKeyRef: name: {{ .Values.environment }}-load-test key: mnemonic + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.environment }}-load-test + key: accountSecret volumeMounts: - name: data mountPath: /root/.celo @@ -165,3 +237,14 @@ spec: - name: account secret: secretName: {{ .Values.environment }}-load-test +{{- if $reuseClient }} + volumeClaimTemplates: + - metadata: + name: data + spec: + storageClassName: ssd + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: {{ .Values.geth.diskSize | default 10 }}Gi +{{- end }} diff --git a/packages/helm-charts/load-test/values.yaml b/packages/helm-charts/load-test/values.yaml index d4289f1a1f2..77e40c04353 100644 --- a/packages/helm-charts/load-test/values.yaml +++ b/packages/helm-charts/load-test/values.yaml @@ -1 +1 @@ -imagePullPolicy: IfNotPresent +imagePullPolicy: Always diff --git a/packages/helm-charts/prometheus-stackdriver/templates/configmap.yaml b/packages/helm-charts/prometheus-stackdriver/templates/configmap.yaml index 88d0af0f1d8..62290c83b6d 100644 --- a/packages/helm-charts/prometheus-stackdriver/templates/configmap.yaml +++ b/packages/helm-charts/prometheus-stackdriver/templates/configmap.yaml @@ -27,12 +27,17 @@ data: # Label the metrics with a custom label if using multiple prometheus for same environments external_labels: cluster_name: {{ .Values.cluster }} - enable_alerts: {{ .Values.enable_alerts }} - - {{- with .Values.remote_write }} + {{- if .Values.remote_write }} remote_write: -{{ toYaml . | indent 6 }} + - basic_auth: + password: {{ .Values.remote_write.basic_auth.password }} + username: {{ .Values.remote_write.basic_auth.username }} + url: {{ .Values.remote_write.url }} + write_relabel_configs: + - action: {{ .Values.remote_write.write_relabel_configs.action }} + regex: {{ .Values.remote_write.write_relabel_configs.regex }} + source_labels: {{ .Values.remote_write.write_relabel_configs.source_labels }} {{- end }} scrape_configs: diff --git a/packages/helm-charts/prometheus-stackdriver/values.yaml b/packages/helm-charts/prometheus-stackdriver/values.yaml index 0e8cb0d52c6..ceef360d508 100644 --- a/packages/helm-charts/prometheus-stackdriver/values.yaml +++ b/packages/helm-charts/prometheus-stackdriver/values.yaml @@ -34,8 +34,8 @@ serviceAccount: # If not set and create is true, a name is generated using the fullname template name: "" -remote_write: -- url: https://prometheus-us-central1.grafana.net/api/prom/push - basic_auth: - username: 51505 - password: eyIxJ... +remote_write: {} +# - url: https://prometheus-us-central1.grafana.net/api/prom/push +# basic_auth: +# username: 51505 +# password: ey... diff --git a/packages/helm-charts/testnet/templates/_helpers.tpl b/packages/helm-charts/testnet/templates/_helpers.tpl index 2b5ae1bcf24..c03f2a821a4 100644 --- a/packages/helm-charts/testnet/templates/_helpers.tpl +++ b/packages/helm-charts/testnet/templates/_helpers.tpl @@ -57,8 +57,11 @@ spec: ports: - port: 8545 name: rpc - - port: 8546 +{{- $wsPort := ((.ws_port | default .Values.geth.ws_port) | int) -}} +{{- if ne $wsPort 8545 }} + - port: {{ $wsPort }} name: ws +{{- end }} selector: {{- if .proxy | default false }} {{- $validatorProxied := printf "%s-validators-%d" .Release.Namespace .validator_index }} @@ -82,8 +85,10 @@ spec: ports: - port: 8545 name: rpc - - port: 8546 +{{- if ne $wsPort 8545 }} + - port: {{ .ws_port | default .Values.geth.ws_port }} name: ws +{{- end }} selector: {{- if .proxy | default false }} {{- $validatorProxied := printf "%s-validators-%d" .Release.Namespace .validator_index }} @@ -150,7 +155,7 @@ spec: {{ include "common.import-geth-account-container" . | indent 6 }} {{ end }} containers: -{{ include "common.full-node-container" (dict "Values" .Values "Release" .Release "Chart" .Chart "proxy" .proxy "proxy_allow_private_ip_flag" .proxy_allow_private_ip_flag "unlock" .unlock "expose" .expose "syncmode" .syncmode "gcmode" .gcmode "pprof" (or (.Values.metrics) (.Values.pprof.enabled)) "pprof_port" (.Values.pprof.port) "metrics" .Values.metrics "public_ips" .public_ips "ethstats" (printf "%s-ethstats.%s" (include "common.fullname" .) .Release.Namespace)) | indent 6 }} +{{ include "common.full-node-container" (dict "Values" .Values "Release" .Release "Chart" .Chart "proxy" .proxy "proxy_allow_private_ip_flag" .proxy_allow_private_ip_flag "unlock" .unlock "rpc_apis" .rpc_apis "expose" .expose "syncmode" .syncmode "gcmode" .gcmode "ws_port" (default .Values.geth.ws_port .ws_port) "pprof" (or (.Values.metrics) (.Values.pprof.enabled)) "pprof_port" (.Values.pprof.port) "metrics" .Values.metrics "public_ips" .public_ips "ethstats" (printf "%s-ethstats.%s" (include "common.fullname" .) .Release.Namespace)) | indent 6 }} terminationGracePeriodSeconds: {{ .Values.geth.terminationGracePeriodSeconds | default 300 }} volumes: - name: data diff --git a/packages/helm-charts/testnet/templates/txnode-private.statefulset.yaml b/packages/helm-charts/testnet/templates/txnode-private.statefulset.yaml index e3f536575b2..c9331b862cb 100644 --- a/packages/helm-charts/testnet/templates/txnode-private.statefulset.yaml +++ b/packages/helm-charts/testnet/templates/txnode-private.statefulset.yaml @@ -1 +1 @@ -{{ include "celo.full-node-statefulset" (dict "Values" $.Values "Release" $.Release "Chart" $.Chart "name" "tx-nodes-private" "component_label" "tx_nodes_private" "replicas" .Values.geth.private_tx_nodes "mnemonic_account_type" "tx_node_private" "expose" true "syncmode" "full" "gcmode" "archive" "rpc_apis" "eth,net,web3,debug,txpool" "ip_addresses" (join "/" .Values.geth.private_tx_node_ip_addresses)) }} +{{ include "celo.full-node-statefulset" (dict "Values" $.Values "Release" $.Release "Chart" $.Chart "name" "tx-nodes-private" "component_label" "tx_nodes_private" "replicas" .Values.geth.private_tx_nodes "mnemonic_account_type" "tx_node_private" "expose" true "syncmode" "full" "gcmode" "archive" "rpc_apis" "eth,net,web3,debug,txpool" "ws_port" "8545" "ip_addresses" (join "/" .Values.geth.private_tx_node_ip_addresses)) }} diff --git a/packages/helm-charts/testnet/values.yaml b/packages/helm-charts/testnet/values.yaml index 7eaa5816773..beec5bfdfed 100644 --- a/packages/helm-charts/testnet/values.yaml +++ b/packages/helm-charts/testnet/values.yaml @@ -37,6 +37,7 @@ geth: # memory: "4Gi" # cpu: "4" fullnodeCheckBlockAge: true + ws_port: 8546 # UpdateStrategy for statefulsets only. Partition=0 is default rollingUpdate behaviour. updateStrategy: diff --git a/packages/metadata-crawler/package.json b/packages/metadata-crawler/package.json index 6f9d2955452..ccafeb2a704 100644 --- a/packages/metadata-crawler/package.json +++ b/packages/metadata-crawler/package.json @@ -9,9 +9,9 @@ "homepage": "https://github.com/celo-org/celo-monorepo/tree/master/packages/metadata-crawler", "repository": "https://github.com/celo-org/celo-monorepo/tree/master/packages/metadata-crawler", "dependencies": { - "@celo/connect": "1.2.2-dev", - "@celo/contractkit": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", + "@celo/connect": "1.2.3-dev", + "@celo/contractkit": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", "@types/pg": "^7.14.3", "bunyan": "1.8.12", "bunyan-gke-stackdriver": "0.1.2", diff --git a/packages/metadata-crawler/src/crawler.ts b/packages/metadata-crawler/src/crawler.ts index 3bd791fae37..d4489be8d38 100644 --- a/packages/metadata-crawler/src/crawler.ts +++ b/packages/metadata-crawler/src/crawler.ts @@ -193,6 +193,7 @@ async function processAttestationServiceStatusForValidator( smsProvidersRandomized, maxDeliveryAttempts, maxRerequestMins, + twilioVerifySidProvided, } = status const isElected = electedValidators.has(validator.address) dataLogger.info( @@ -214,6 +215,7 @@ async function processAttestationServiceStatusForValidator( smsProvidersRandomized, maxDeliveryAttempts, maxRerequestMins, + twilioVerifySidProvided, }, 'checked_attestation_service_status' ) diff --git a/packages/phone-number-privacy/combiner/package.json b/packages/phone-number-privacy/combiner/package.json index 1a2ea203300..c04154f3948 100644 --- a/packages/phone-number-privacy/combiner/package.json +++ b/packages/phone-number-privacy/combiner/package.json @@ -1,6 +1,6 @@ { "name": "@celo/phone-number-privacy-combiner", - "version": "1.1.5-dev", + "version": "1.1.6-dev", "description": "Orchestrates and combines threshold signatures for use in ODIS", "author": "Celo", "license": "Apache-2.0", @@ -24,9 +24,9 @@ "db:migrate": "NODE_ENV=dev ts-node scripts/run-migrations.ts" }, "dependencies": { - "@celo/contractkit": "1.0.2", - "@celo/phone-number-privacy-common": "1.0.33", - "@celo/identity": "1.0.2", + "@celo/contractkit": "1.2.0", + "@celo/phone-number-privacy-common": "1.0.34", + "@celo/identity": "1.2.0", "@celo/utils": "1.0.2", "blind-threshold-bls": "https://github.com/celo-org/blind-threshold-bls-wasm#e1e2f8a", "elliptic": "^6.5.4", diff --git a/packages/phone-number-privacy/combiner/src/match-making/get-contact-matches.ts b/packages/phone-number-privacy/combiner/src/match-making/get-contact-matches.ts index 6ad4fd27d43..1c10af3a68e 100644 --- a/packages/phone-number-privacy/combiner/src/match-making/get-contact-matches.ts +++ b/packages/phone-number-privacy/combiner/src/match-making/get-contact-matches.ts @@ -4,9 +4,9 @@ import { GetContactMatchesRequest, hasValidAccountParam, hasValidContactPhoneNumbersParam, + hasValidIdentifier, hasValidUserPhoneNumberParam, isVerified, - phoneNumberHashIsValidIfExists, WarningMessage, } from '@celo/phone-number-privacy-common' import Logger from 'bunyan' @@ -71,7 +71,6 @@ function isValidGetContactMatchesInput(requestBody: GetContactMatchesRequest): b hasValidAccountParam(requestBody) && hasValidUserPhoneNumberParam(requestBody) && hasValidContactPhoneNumbersParam(requestBody) && - !!requestBody.hashedPhoneNumber && - phoneNumberHashIsValidIfExists(requestBody) + hasValidIdentifier(requestBody) ) } diff --git a/packages/phone-number-privacy/combiner/src/signing/get-threshold-signature.ts b/packages/phone-number-privacy/combiner/src/signing/get-threshold-signature.ts index 9c93239f5f6..5401fdcc2ab 100644 --- a/packages/phone-number-privacy/combiner/src/signing/get-threshold-signature.ts +++ b/packages/phone-number-privacy/combiner/src/signing/get-threshold-signature.ts @@ -3,11 +3,10 @@ import { ErrorMessage, GetBlindedMessageSigRequest, hasValidAccountParam, - hasValidQueryPhoneNumberParam, - hasValidTimestamp, + hasValidBlindedPhoneNumberParam, + identifierIsValidIfExists, isBodyReasonablySized, MAX_BLOCK_DISCREPANCY_THRESHOLD, - phoneNumberHashIsValidIfExists, SignMessageResponse, SignMessageResponseFailure, SignMessageResponseSuccess, @@ -354,10 +353,9 @@ function getMajorityErrorCode(errorCodes: Map, logger: Logger) { function isValidGetSignatureInput(requestBody: GetBlindedMessageSigRequest): boolean { return ( hasValidAccountParam(requestBody) && - hasValidQueryPhoneNumberParam(requestBody) && - phoneNumberHashIsValidIfExists(requestBody) && - isBodyReasonablySized(requestBody) && - hasValidTimestamp(requestBody) + hasValidBlindedPhoneNumberParam(requestBody) && + identifierIsValidIfExists(requestBody) && + isBodyReasonablySized(requestBody) ) } diff --git a/packages/phone-number-privacy/combiner/test/end-to-end/get-blinded-sig.test.ts b/packages/phone-number-privacy/combiner/test/end-to-end/get-blinded-sig.test.ts index fb97ed41732..a9c599bac0c 100644 --- a/packages/phone-number-privacy/combiner/test/end-to-end/get-blinded-sig.test.ts +++ b/packages/phone-number-privacy/combiner/test/end-to-end/get-blinded-sig.test.ts @@ -36,7 +36,6 @@ describe('Running against a deployed service', () => { account: '0x1234', authenticationMethod: AuthenticationMethod.WALLET_KEY, blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - timestamp: Date.now(), version: 'ignore', sessionID: genSessionID(), } @@ -51,7 +50,6 @@ describe('Running against a deployed service', () => { account: ACCOUNT_ADDRESS, authenticationMethod: AuthenticationMethod.WALLET_KEY, blindedQueryPhoneNumber: '', - timestamp: Date.now(), version: 'ignore', sessionID: genSessionID(), } @@ -67,7 +65,6 @@ describe('Running against a deployed service', () => { account: ACCOUNT_ADDRESS, authenticationMethod: AuthenticationMethod.WALLET_KEY, blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - timestamp: Date.now(), version: 'ignore', } await expect( @@ -92,14 +89,12 @@ describe('Running against a deployed service', () => { describe('With enough quota', () => { // if these tests are failing, it may just be that the address needs to be fauceted: // celotooljs account faucet --account 0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb --dollar 1 --gold 1 -e --verbose - const timestamp = Date.now() it('Returns sig when querying with unused and used request', async () => { await replenishQuota(ACCOUNT_ADDRESS, contractKit) const body: SignMessageRequest = { account: ACCOUNT_ADDRESS, authenticationMethod: AuthenticationMethod.WALLET_KEY, blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - timestamp, version: 'ignore', sessionID: genSessionID(), } diff --git a/packages/phone-number-privacy/combiner/test/index.test.ts b/packages/phone-number-privacy/combiner/test/index.test.ts index 38bb74886eb..4ca0b16221c 100644 --- a/packages/phone-number-privacy/combiner/test/index.test.ts +++ b/packages/phone-number-privacy/combiner/test/index.test.ts @@ -1,4 +1,4 @@ -import { isVerified, REQUEST_EXPIRY_WINDOW_MS } from '@celo/phone-number-privacy-common' +import { isVerified } from '@celo/phone-number-privacy-common' import { Request, Response } from 'firebase-functions' import { BLSCryptographyClient } from '../src/bls/bls-cryptography-client' import { VERSION } from '../src/config' @@ -72,7 +72,6 @@ describe(`POST /getBlindedMessageSig endpoint`, () => { blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, hashedPhoneNumber: '0x5f6e88c3f724b3a09d3194c0514426494955eff7127c29654e48a361a19b4b96', account: '0x78dc5D2D739606d31509C31d654056A45185ECb6', - timestamp: Date.now(), } beforeEach(() => { @@ -146,17 +145,6 @@ describe(`POST /getBlindedMessageSig endpoint`, () => { getBlindedMessageSig(req, invalidResponseExpected(done, 400)) }) - it('expired timestamp returns 400', (done) => { - const req = { - body: { - ...validRequest, - timestamp: Date.now() - REQUEST_EXPIRY_WINDOW_MS, - }, - headers: mockHeaders, - } as Request - - getBlindedMessageSig(req, invalidResponseExpected(done, 400)) - }) it('invalid blinded phone number returns 400', (done) => { const req = { body: { diff --git a/packages/phone-number-privacy/common/jest.config.js b/packages/phone-number-privacy/common/jest.config.js new file mode 100644 index 00000000000..650c513d7e6 --- /dev/null +++ b/packages/phone-number-privacy/common/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + preset: 'ts-jest', +} diff --git a/packages/phone-number-privacy/common/package.json b/packages/phone-number-privacy/common/package.json index 5b604a7efa4..c795303df13 100644 --- a/packages/phone-number-privacy/common/package.json +++ b/packages/phone-number-privacy/common/package.json @@ -1,6 +1,6 @@ { "name": "@celo/phone-number-privacy-common", - "version": "1.0.34-dev", + "version": "1.0.35-dev", "description": "Common library for the combiner and signer libraries", "author": "Celo", "license": "Apache-2.0", @@ -10,6 +10,8 @@ "prepublishOnly": "yarn build", "build": "tsc -b .", "clean": "tsc -b . --clean", + "test": "jest --testPathIgnorePatterns test\/end-to-end", + "test:coverage": "jest --testPathIgnorePatterns test\/end-to-end --coverage", "lint": "tslint -c tslint.json --project ." }, "files": [ diff --git a/packages/phone-number-privacy/common/src/utils/input-validation.ts b/packages/phone-number-privacy/common/src/utils/input-validation.ts index 4ac78e78044..cf99bbd7f4f 100644 --- a/packages/phone-number-privacy/common/src/utils/input-validation.ts +++ b/packages/phone-number-privacy/common/src/utils/input-validation.ts @@ -52,6 +52,8 @@ function isValidObfuscatedPhoneNumber(phoneNumber: string) { return isBase64(phoneNumber) && Buffer.from(phoneNumber, 'base64').length === 32 } +const hexString = new RegExp(/[0-9A-Fa-f]{32}/, 'i') + function isByte32(hashedData: string): boolean { - return Buffer.byteLength(trimLeading0x(hashedData), 'hex') === 32 + return hexString.test(trimLeading0x(hashedData)) } diff --git a/packages/phone-number-privacy/common/test/utils/authentication.test.ts b/packages/phone-number-privacy/common/test/utils/authentication.test.ts new file mode 100644 index 00000000000..e7eea7ad387 --- /dev/null +++ b/packages/phone-number-privacy/common/test/utils/authentication.test.ts @@ -0,0 +1,147 @@ +import { Request } from 'express' +import Logger from 'bunyan' + +import * as auth from '../../src/utils/authentication' +import { ContractKit } from '@celo/contractkit' +import { AuthenticationMethod } from '@celo/identity/lib/odis/query' + +describe('Authentication test suite', () => { + const logger = Logger.createLogger({ + name: 'logger', + level: 'warn', + }) + + describe('authenticateUser utility', () => { + it("Should fail authentication with missing 'Authorization' header", async () => { + const sampleRequest: Request = { + get: (_: string) => '', + body: { + account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', + }, + } as Request + const mockContractKit = {} as ContractKit + + const result = await auth.authenticateUser(sampleRequest, mockContractKit, logger) + + expect(result).toBeFalsy() + }) + + it('Should fail authentication with missing signer', async () => { + const sampleRequest: Request = { + get: (name: string) => (name === 'Authorization' ? 'Test' : ''), + body: {}, + } as Request + const mockContractKit = {} as ContractKit + + const result = await auth.authenticateUser(sampleRequest, mockContractKit, logger) + + expect(result).toBeFalsy() + }) + + it('Should succeed authentication with error in getDataEncryptionKey', async () => { + const sampleRequest: Request = { + get: (name: string) => (name === 'Authorization' ? 'Test' : ''), + body: { + account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', + authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, + }, + } as Request + const mockContractKit = {} as ContractKit + + const result = await auth.authenticateUser(sampleRequest, mockContractKit, logger) + + expect(result).toBeTruthy() + }) + + it('Should fail authentication when key is not registered', async () => { + const sampleRequest: Request = { + get: (name: string) => (name === 'Authorization' ? 'Test' : ''), + body: { + account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', + authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, + }, + } as Request + const mockContractKit = { + contracts: { + getAccounts: async () => { + return Promise.resolve({ + getDataEncryptionKey: async (_: string) => { + return '' + }, + }) + }, + }, + } as ContractKit + + const result = await auth.authenticateUser(sampleRequest, mockContractKit, logger) + + expect(result).toBeFalsy() + }) + + it('Should fail authentication when key is registered but not valid', async () => { + const sampleRequest: Request = { + get: (name: string) => (name === 'Authorization' ? 'Test' : ''), + body: { + account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', + authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, + }, + } as Request + const mockContractKit = { + contracts: { + getAccounts: async () => { + return Promise.resolve({ + getDataEncryptionKey: async (_: string) => { + return 'notAValidKeyEncryption' + }, + }) + }, + }, + } as ContractKit + + const result = await auth.authenticateUser(sampleRequest, mockContractKit, logger) + + expect(result).toBeFalsy() + }) + }) + + describe('isVerified utility', () => { + it('Should succeed when verification is ok', async () => { + const mockContractKit = { + contracts: { + getAttestations: async () => { + return { + getVerifiedStatus: async (_: string, __: string) => { + return { + isVerified: true, + } + }, + } + }, + }, + } as ContractKit + + const result = await auth.isVerified('', '', mockContractKit, logger) + + expect(result).toBeTruthy() + }) + it('Should fail when verification is not ok', async () => { + const mockContractKit = { + contracts: { + getAttestations: async () => { + return { + getVerifiedStatus: async (_: string, __: string) => { + return { + isVerified: false, + } + }, + } + }, + }, + } as ContractKit + + const result = await auth.isVerified('', '', mockContractKit, logger) + + expect(result).toBeFalsy() + }) + }) +}) diff --git a/packages/phone-number-privacy/common/test/utils/input-validation.test.ts b/packages/phone-number-privacy/common/test/utils/input-validation.test.ts new file mode 100644 index 00000000000..d6dfbf82cfa --- /dev/null +++ b/packages/phone-number-privacy/common/test/utils/input-validation.test.ts @@ -0,0 +1,254 @@ +import * as utils from '../../src/utils/input-validation' +import { + GetBlindedMessageSigRequest, + GetContactMatchesRequest, + GetQuotaRequest, + OdisRequest, +} from '../../src/interfaces' +import { REASONABLE_BODY_CHAR_LIMIT } from '../../src/utils/constants' + +describe('Input Validation test suite', () => { + describe('hasValidIdentifier utility', () => { + it('Should return false with empty phone number', () => { + const sampleData: GetContactMatchesRequest = { + account: 'account', + contactPhoneNumbers: [], + userPhoneNumber: 'number', + hashedPhoneNumber: '', + } + + const result = utils.hasValidIdentifier(sampleData) + + expect(result).toBeFalsy() + }) + + it('Should return false with non-hex phone number', () => { + const sampleData: GetContactMatchesRequest = { + account: 'account', + contactPhoneNumbers: [], + userPhoneNumber: 'number', + hashedPhoneNumber: '0xTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTEST', + } + + const result = utils.hasValidIdentifier(sampleData) + + expect(result).toBeFalsy() + }) + + it('Should return true with hex phone number', () => { + const sampleData: GetContactMatchesRequest = { + account: 'account', + contactPhoneNumbers: [], + userPhoneNumber: 'number', + hashedPhoneNumber: '0x0000123400001234000012340000123400001234000012340000123400001234', + } + + const result = utils.hasValidIdentifier(sampleData) + + expect(result).toBeTruthy() + }) + }) + + describe('identifierIsValidIfExists utility', () => { + it('Should return true with empty phone number', () => { + const sampleData: GetContactMatchesRequest = { + account: 'account', + contactPhoneNumbers: [], + userPhoneNumber: 'number', + hashedPhoneNumber: '', + } + + const result = utils.identifierIsValidIfExists(sampleData) + + expect(result).toBeTruthy() + }) + + it('Should return true with valid phone number', () => { + const sampleData: GetContactMatchesRequest = { + account: 'account', + contactPhoneNumbers: [], + userPhoneNumber: 'number', + hashedPhoneNumber: '0x0000123400001234000012340000123400001234000012340000123400001234', + } + + const result = utils.identifierIsValidIfExists(sampleData) + + expect(result).toBeTruthy() + }) + }) + + describe('isBodyReasonablySized utility', () => { + it('Should return true with small body', () => { + const sampleData: GetQuotaRequest = { + account: 'account', + hashedPhoneNumber: 'x'.repeat(10), + } + + const result = utils.isBodyReasonablySized(sampleData) + + expect(result).toBeTruthy() + }) + + it('Should return false with giant body', () => { + const sampleData: GetQuotaRequest = { + account: 'account', + hashedPhoneNumber: 'x'.repeat(REASONABLE_BODY_CHAR_LIMIT * 2), + } + + const result = utils.isBodyReasonablySized(sampleData) + + expect(result).toBeFalsy() + }) + }) + + describe('hasValidAccountParam utility', () => { + it('Should return true for proper address', () => { + const sampleData: OdisRequest = { + account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', + } + + const result = utils.hasValidAccountParam(sampleData) + + expect(result).toBeTruthy() + }) + + it('Should return false for nonsense address', () => { + const sampleData: OdisRequest = { + account: '0xAA', + } + + const result = utils.hasValidAccountParam(sampleData) + + expect(result).toBeFalsy() + }) + + it('Should return false with missing address', () => { + const sampleData: OdisRequest = { + account: '', + } + + const result = utils.hasValidAccountParam(sampleData) + + expect(result).toBeFalsy() + }) + }) + + describe('hasValidUserPhoneNumberParam utility', () => { + it('Should return true for proper phone number', () => { + const sampleData: GetContactMatchesRequest = { + userPhoneNumber: Buffer.from('1912fee45d61c87cc5ea59dae31190ff').toString('base64'), + hashedPhoneNumber: 'hash', + contactPhoneNumbers: [], + account: '', + } + + const result = utils.hasValidUserPhoneNumberParam(sampleData) + + expect(result).toBeTruthy() + }) + + it('Should return false with wrong phone number', () => { + const sampleData: GetContactMatchesRequest = { + userPhoneNumber: Buffer.from('z').toString('base64'), + hashedPhoneNumber: 'hash', + contactPhoneNumbers: [], + account: '', + } + + const result = utils.hasValidUserPhoneNumberParam(sampleData) + + expect(result).toBeFalsy() + }) + }) + + describe('hasValidContactPhoneNumbersParam utility', () => { + it('Should return true for proper contact phone numbers', () => { + const sampleData: GetContactMatchesRequest = { + userPhoneNumber: 'phone', + hashedPhoneNumber: 'hash', + contactPhoneNumbers: [Buffer.from('1912fee45d61c87cc5ea59dae31190ff').toString('base64')], + account: '', + } + + const result = utils.hasValidContactPhoneNumbersParam(sampleData) + + expect(result).toBeTruthy() + }) + + it('Should return false for wrong contact phone number', () => { + const sampleData: GetContactMatchesRequest = { + userPhoneNumber: 'phone', + hashedPhoneNumber: 'hash', + contactPhoneNumbers: [Buffer.from('zz').toString('base64')], + account: '', + } + + const result = utils.hasValidContactPhoneNumbersParam(sampleData) + + expect(result).toBeFalsy() + }) + + it('Should return false for missing contact phone number', () => { + const sampleData: GetContactMatchesRequest = { + userPhoneNumber: 'phone', + hashedPhoneNumber: 'hash', + contactPhoneNumbers: [], + account: '', + } + + const result = utils.hasValidContactPhoneNumbersParam(sampleData) + + expect(result).toBeFalsy() + }) + }) + + describe('hasValidBlindedPhoneNumberParam utility', () => { + it('Should return true for blinded query', () => { + const sampleData: GetBlindedMessageSigRequest = { + blindedQueryPhoneNumber: Buffer.from( + '1912fee45d61c87cc5ea59dae31190ff1912fee45d61c8' + ).toString('base64'), + account: 'acc', + } + + const result = utils.hasValidBlindedPhoneNumberParam(sampleData) + + expect(result).toBeTruthy() + }) + + it('Should return false for not base64 query', () => { + const sampleData: GetBlindedMessageSigRequest = { + blindedQueryPhoneNumber: Buffer.from( + 'JanAdamMickiewicz1234!@JanAdamMickiewicz1234!@123412345678901234' + ).toString('utf-8'), + account: 'acc', + } + + const result = utils.hasValidBlindedPhoneNumberParam(sampleData) + + expect(result).toBeFalsy() + }) + + it('Should return false for too short blinded query', () => { + const sampleData: GetBlindedMessageSigRequest = { + blindedQueryPhoneNumber: Buffer.from('1912fee45d61c87cc5e').toString('base64'), + account: 'acc', + } + + const result = utils.hasValidBlindedPhoneNumberParam(sampleData) + + expect(result).toBeFalsy() + }) + + it('Should return false for missing param in query', () => { + const sampleData: GetBlindedMessageSigRequest = { + blindedQueryPhoneNumber: '', + account: 'acc', + } + + const result = utils.hasValidBlindedPhoneNumberParam(sampleData) + + expect(result).toBeFalsy() + }) + }) +}) diff --git a/packages/phone-number-privacy/signer/package.json b/packages/phone-number-privacy/signer/package.json index cdba85d7659..08d1f85ae57 100644 --- a/packages/phone-number-privacy/signer/package.json +++ b/packages/phone-number-privacy/signer/package.json @@ -1,6 +1,6 @@ { "name": "@celo/phone-number-privacy-signer", - "version": "1.1.6-dev", + "version": "1.1.9-dev", "description": "Signing participator of ODIS", "author": "Celo", "license": "Apache-2.0", @@ -19,9 +19,9 @@ }, "dependencies": { "@celo/base": "1.1.1", - "@celo/contractkit": "1.0.2", - "@celo/phone-number-privacy-common": "1.0.33", - "@celo/identity": "1.0.2", + "@celo/contractkit": "1.2.0", + "@celo/phone-number-privacy-common": "1.0.34", + "@celo/identity": "1.2.0", "@celo/utils": "1.0.2", "@celo/wallet-hsm-azure": "1.0.0-beta3", "@google-cloud/secret-manager": "3.0.0", diff --git a/packages/phone-number-privacy/signer/src/common/metrics.ts b/packages/phone-number-privacy/signer/src/common/metrics.ts index 97acb8aa2c3..809fcf5b153 100644 --- a/packages/phone-number-privacy/signer/src/common/metrics.ts +++ b/packages/phone-number-privacy/signer/src/common/metrics.ts @@ -22,8 +22,8 @@ export const Counters = { labelNames: ['endpoint', 'statusCode'], }), databaseErrors: new Counter({ - name: 'database_read_errors', - help: 'Counter for the number of database read errors', + name: 'database_errors', + help: 'Counter for the number of database errors', labelNames: ['type'], }), blockchainErrors: new Counter({ diff --git a/packages/phone-number-privacy/signer/src/config.ts b/packages/phone-number-privacy/signer/src/config.ts index 0983bf0ef7d..da7251a624d 100644 --- a/packages/phone-number-privacy/signer/src/config.ts +++ b/packages/phone-number-privacy/signer/src/config.ts @@ -39,6 +39,7 @@ interface Config { additionalVerifiedQueryMax: number queryPerTransaction: number minDollarBalance: BigNumber + minEuroBalance: BigNumber minCeloBalance: BigNumber } attestations: { @@ -94,6 +95,8 @@ const config: Config = { queryPerTransaction: toNum(env.QUERY_PER_TRANSACTION) || 2, // Min balance is .01 cUSD minDollarBalance: new BigNumber(env.MIN_DOLLAR_BALANCE || 1e16), + // Min balance is .01 cEUR + minEuroBalance: new BigNumber(env.MIN_DOLLAR_BALANCE || 1e16), // Min balance is .005 CELO minCeloBalance: new BigNumber(env.MIN_DOLLAR_BALANCE || 5e15), }, diff --git a/packages/phone-number-privacy/signer/src/database/models/request.ts b/packages/phone-number-privacy/signer/src/database/models/request.ts index 5e50f2b3d82..f8fea9db8cb 100644 --- a/packages/phone-number-privacy/signer/src/database/models/request.ts +++ b/packages/phone-number-privacy/signer/src/database/models/request.ts @@ -13,7 +13,7 @@ export class Request { constructor(request: GetBlindedMessagePartialSigRequest) { this[REQUESTS_COLUMNS.address] = request.account - this[REQUESTS_COLUMNS.timestamp] = new Date(request.timestamp as number) + this[REQUESTS_COLUMNS.timestamp] = new Date() this[REQUESTS_COLUMNS.blindedQuery] = request.blindedQueryPhoneNumber } } diff --git a/packages/phone-number-privacy/signer/src/database/wrappers/request.ts b/packages/phone-number-privacy/signer/src/database/wrappers/request.ts index 9a3b96b5742..6b4dc7b6f49 100644 --- a/packages/phone-number-privacy/signer/src/database/wrappers/request.ts +++ b/packages/phone-number-privacy/signer/src/database/wrappers/request.ts @@ -13,10 +13,6 @@ export async function getRequestExists( request: GetBlindedMessagePartialSigRequest, logger: Logger ): Promise { - if (!request.timestamp) { - logger.debug('request does not have timestamp') - return false - } logger.debug({ request }, 'Checking if request exists') const getRequestExistsMeter = Histograms.dbOpsInstrumentation .labels('getRequestExists') @@ -24,7 +20,6 @@ export async function getRequestExists( try { const existingRequest = await requests() .where({ - [REQUESTS_COLUMNS.timestamp]: new Date(request.timestamp as number), [REQUESTS_COLUMNS.address]: request.account, [REQUESTS_COLUMNS.blindedQuery]: request.blindedQueryPhoneNumber, }) @@ -42,10 +37,6 @@ export async function getRequestExists( } export async function storeRequest(request: GetBlindedMessagePartialSigRequest, logger: Logger) { - if (!request.timestamp) { - logger.debug('request does not have timestamp') - return true - } const storeRequestMeter = Histograms.dbOpsInstrumentation.labels('storeRequest').startTimer() logger.debug({ request }, 'Storing salt request') try { diff --git a/packages/phone-number-privacy/signer/src/signing/get-partial-signature.ts b/packages/phone-number-privacy/signer/src/signing/get-partial-signature.ts index 10b05aec65c..7e30aea1763 100644 --- a/packages/phone-number-privacy/signer/src/signing/get-partial-signature.ts +++ b/packages/phone-number-privacy/signer/src/signing/get-partial-signature.ts @@ -2,10 +2,9 @@ import { authenticateUser, ErrorMessage, hasValidAccountParam, - hasValidQueryPhoneNumberParam, - hasValidTimestamp, + hasValidBlindedPhoneNumberParam, + identifierIsValidIfExists, isBodyReasonablySized, - phoneNumberHashIsValidIfExists, SignMessageResponse, SignMessageResponseFailure, WarningMessage, @@ -203,10 +202,9 @@ export async function handleGetBlindedMessagePartialSig( function isValidGetSignatureInput(requestBody: GetBlindedMessagePartialSigRequest): boolean { return ( hasValidAccountParam(requestBody) && - hasValidQueryPhoneNumberParam(requestBody) && - phoneNumberHashIsValidIfExists(requestBody) && - isBodyReasonablySized(requestBody) && - hasValidTimestamp(requestBody) + hasValidBlindedPhoneNumberParam(requestBody) && + identifierIsValidIfExists(requestBody) && + isBodyReasonablySized(requestBody) ) } diff --git a/packages/phone-number-privacy/signer/src/signing/query-quota.ts b/packages/phone-number-privacy/signer/src/signing/query-quota.ts index bb50b01710d..e0b3973792f 100644 --- a/packages/phone-number-privacy/signer/src/signing/query-quota.ts +++ b/packages/phone-number-privacy/signer/src/signing/query-quota.ts @@ -1,14 +1,14 @@ import { retryAsyncWithBackOffAndTimeout } from '@celo/base' -import { NULL_ADDRESS } from '@celo/contractkit' +import { NULL_ADDRESS, StableToken } from '@celo/contractkit' import { authenticateUser, ErrorMessage, FULL_NODE_TIMEOUT_IN_MS, GetQuotaRequest, hasValidAccountParam, + identifierIsValidIfExists, isBodyReasonablySized, isVerified, - phoneNumberHashIsValidIfExists, RETRY_COUNT, RETRY_DELAY_IN_MS, WarningMessage, @@ -68,7 +68,7 @@ export async function handleGetQuota( function isValidGetQuotaInput(requestBody: GetQuotaRequest): boolean { return ( hasValidAccountParam(requestBody) && - phoneNumberHashIsValidIfExists(requestBody) && + identifierIsValidIfExists(requestBody) && isBodyReasonablySized(requestBody) ) } @@ -159,11 +159,15 @@ async function _getQueryQuota(logger: Logger, account: string, hashedPhoneNumber .labels('balances') .startTimer() let cUSDAccountBalance = new BigNumber(0) + let cEURAccountBalance = new BigNumber(0) let celoAccountBalance = new BigNumber(0) await Promise.all([ new Promise((resolve) => { - resolve(getDollarBalance(logger, account, walletAddress)) + resolve(getStableTokenBalance(StableToken.cUSD, logger, account, walletAddress)) + }), + new Promise((resolve) => { + resolve(getStableTokenBalance(StableToken.cEUR, logger, account, walletAddress)) }), new Promise((resolve) => { resolve(getCeloBalance(logger, account, walletAddress)) @@ -171,13 +175,15 @@ async function _getQueryQuota(logger: Logger, account: string, hashedPhoneNumber ]) .then((values) => { cUSDAccountBalance = values[0] as BigNumber - celoAccountBalance = values[1] as BigNumber + cEURAccountBalance = values[1] as BigNumber + celoAccountBalance = values[2] as BigNumber }) .finally(getBalancesMeter) - // Min balance can be in either cUSD or CELO + // Min balance can be in either cUSD, cEUR or CELO if ( cUSDAccountBalance.isGreaterThanOrEqualTo(config.quota.minDollarBalance) || + cEURAccountBalance.isGreaterThanOrEqualTo(config.quota.minEuroBalance) || celoAccountBalance.isGreaterThanOrEqualTo(config.quota.minCeloBalance) ) { Counters.requestsWithUnverifiedAccountWithMinBalance.inc() @@ -185,8 +191,10 @@ async function _getQueryQuota(logger: Logger, account: string, hashedPhoneNumber { account, cUSDAccountBalance, + cEURAccountBalance, celoAccountBalance, minDollarBalance: config.quota.minDollarBalance, + minEuroBalance: config.quota.minEuroBalance, minCeloBalance: config.quota.minCeloBalance, }, 'Account is not verified but meets min balance' @@ -208,8 +216,10 @@ async function _getQueryQuota(logger: Logger, account: string, hashedPhoneNumber logger.trace({ account, cUSDAccountBalance, + cEURAccountBalance, celoAccountBalance, minDollarBalance: config.quota.minDollarBalance, + minEuroBalance: config.quota.minEuroBalance, minCeloBalance: config.quota.minCeloBalance, quota: 0, }) @@ -246,13 +256,18 @@ export async function getTransactionCount(logger: Logger, ...addresses: string[] return res } -export async function getDollarBalance(logger: Logger, ...addresses: string[]): Promise { +export async function getStableTokenBalance( + stableToken: StableToken, + logger: Logger, + ...addresses: string[] +): Promise { return Promise.all( addresses .filter((address) => address !== NULL_ADDRESS) .map((address) => retryAsyncWithBackOffAndTimeout( - async () => (await getContractKit().contracts.getStableToken()).balanceOf(address), + async () => + (await getContractKit().contracts.getStableToken(stableToken)).balanceOf(address), RETRY_COUNT, [], RETRY_DELAY_IN_MS, @@ -266,7 +281,7 @@ export async function getDollarBalance(logger: Logger, ...addresses: string[]): ).then((values) => { logger.trace( { addresses, balances: values.map((bn) => bn.toString()) }, - 'Fetched cusd balances for addresses' + `Fetched ${stableToken} balances for addresses` ) return values.reduce((a, b) => a.plus(b)) }) diff --git a/packages/phone-number-privacy/signer/test/end-to-end/get-blinded-sig.test.ts b/packages/phone-number-privacy/signer/test/end-to-end/get-blinded-sig.test.ts index 5254e90d37a..d2606f03653 100644 --- a/packages/phone-number-privacy/signer/test/end-to-end/get-blinded-sig.test.ts +++ b/packages/phone-number-privacy/signer/test/end-to-end/get-blinded-sig.test.ts @@ -4,7 +4,10 @@ import { rootLogger as logger, TestUtils, } from '@celo/phone-number-privacy-common' +import { getBlindedPhoneNumber } from '@celo/phone-number-privacy-common/lib/test/utils' +import { PHONE_NUMBER } from '@celo/phone-number-privacy-common/lib/test/values' import { serializeSignature, signMessage } from '@celo/utils/lib/signatureUtils' +import { randomBytes } from 'crypto' import 'isomorphic-fetch' import Web3 from 'web3' import config from '../../src/config' @@ -38,7 +41,16 @@ contractkit.addAccount(PRIVATE_KEY3) jest.setTimeout(30000) +const getRandomBlindedPhoneNumber = () => { + return getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)) +} + describe('Running against a deployed service', () => { + beforeAll(() => { + console.log(DEFAULT_FORNO_URL) + console.log(ODIS_SIGNER) + }) + describe('Returns status 400 with invalid input', () => { it('With invalid address', async () => { const response = await postToSignMessage(BLINDED_PHONE_NUMBER, '0x1234', Date.now(), 'ignore') @@ -68,13 +80,11 @@ describe('Running against a deployed service', () => { }) it('With auth header signer mismatch', async () => { - const timestamp = Date.now() // Sign body with different account const body = JSON.stringify({ hashedPhoneNumber: '+1455556600', blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER.trim(), ACCOUNT_ADDRESS1, - timestamp, }) const signature = signMessage(JSON.stringify(body), PRIVATE_KEY2, ACCOUNT_ADDRESS2) const authHeader = serializeSignature(signature) @@ -82,19 +92,14 @@ describe('Running against a deployed service', () => { const response = await postToSignMessage( BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS1, - timestamp, + undefined, authHeader ) expect(response.status).toBe(401) }) - - it('With missing blindedQueryPhoneNumber', async () => { - const response = await postToSignMessage('', ACCOUNT_ADDRESS1, Date.now()) - expect(response.status).toBe(400) - }) }) - it('Returns error when querying out of quota', async () => { + it('Returns 403 error when querying out of quota', async () => { const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS1, Date.now()) expect(response.status).toBe(403) }) @@ -102,50 +107,67 @@ describe('Running against a deployed service', () => { describe('When account address has enough quota', () => { // if these tests are failing, it may just be that the address needs to be fauceted: // celotooljs account faucet --account ACCOUNT_ADDRESS2 --dollar 1 --gold 1 -e --verbose - let initialQueryCount: number - let timestamp: number + beforeAll(async () => { console.log('ACCOUNT_ADDRESS1 ' + ACCOUNT_ADDRESS1) console.log('ACCOUNT_ADDRESS2 ' + ACCOUNT_ADDRESS2) console.log('ACCOUNT_ADDRESS3 ' + ACCOUNT_ADDRESS3) contractkit.defaultAccount = ACCOUNT_ADDRESS2 - - initialQueryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - timestamp = Date.now() }) - it('Returns sig when querying succeeds with unused request', async () => { + it('Returns sig when querying succeeds', async () => { await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2, timestamp) + const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2) expect(response.status).toBe(200) }) - it('Returns count when querying with unused request increments query count', async () => { - const queryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - expect(queryCount).toEqual(initialQueryCount + 1) + // Backwards compatibility check + it('Returns sig when querying succeeds w/ expired timestamp', async () => { + await replenishQuota(ACCOUNT_ADDRESS2, contractkit) + const response = await postToSignMessage( + BLINDED_PHONE_NUMBER, + ACCOUNT_ADDRESS2, + Date.now() - 10 * 60 * 1000 + ) // 10 minutes ago + expect(response.status).toBe(200) }) - it('Returns sig when querying succeeds with used request', async () => { + it('Increments query count when querying succeeds w/ unused request', async () => { await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2, timestamp) - expect(response.status).toBe(200) + const initialQueryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) + await postToSignMessage(getRandomBlindedPhoneNumber(), ACCOUNT_ADDRESS2) + expect(initialQueryCount).toEqual((await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) - 1) }) - it('Returns count when querying with used request does not increment query count', async () => { - const queryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - expect(queryCount).toEqual(initialQueryCount + 1) + // Backwards compatibility check + it('Increments query count when querying succeeds w/ timestamp', async () => { + await replenishQuota(ACCOUNT_ADDRESS2, contractkit) + const initialQueryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) + await postToSignMessage(getRandomBlindedPhoneNumber(), ACCOUNT_ADDRESS2, Date.now()) + expect(initialQueryCount).toEqual((await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) - 1) }) - it('Returns sig when querying succeeds with missing timestamp', async () => { + it('Returns sig when querying succeeds with replayed request without incrementing query count', async () => { await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2) - expect(response.status).toBe(200) + const blindedPhoneNumber = getRandomBlindedPhoneNumber() + const res1 = await postToSignMessage(blindedPhoneNumber, ACCOUNT_ADDRESS2) + expect(res1.status).toBe(200) + const queryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) + const res2 = await postToSignMessage(blindedPhoneNumber, ACCOUNT_ADDRESS2) + expect(res2.status).toBe(200) + expect(queryCount).toEqual(await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) }) - it('Returns count when querying with missing timestamp increments query count', async () => { + // Backwards compatibility check + it('Returns sig when querying succeeds with replayed request without incrementing query count w/ timestamp', async () => { + await replenishQuota(ACCOUNT_ADDRESS2, contractkit) + const res1 = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2, Date.now()) + expect(res1.status).toBe(200) const queryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - expect(queryCount).toEqual(initialQueryCount + 2) + const res2 = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2, Date.now()) + expect(res2.status).toBe(200) + expect(queryCount).toEqual(await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) }) }) @@ -155,7 +177,6 @@ describe('Running against a deployed service', () => { // NOTE: DO NOT FAUCET ACCOUNT_ADDRESS3 let initialQuota: number let initialQueryCount: number - let timestamp: number beforeAll(async () => { contractkit.defaultAccount = ACCOUNT_ADDRESS3 await registerWalletAddress(ACCOUNT_ADDRESS3, ACCOUNT_ADDRESS2, PRIVATE_KEY2, contractkit) @@ -163,7 +184,6 @@ describe('Running against a deployed service', () => { // and ACCOUNT_ADDRESS3 is account address (does not have quota on it's own, only bc of walletAddress) initialQuota = await getQuota(ACCOUNT_ADDRESS3, IDENTIFIER) initialQueryCount = await getQueryCount(ACCOUNT_ADDRESS3, IDENTIFIER) - timestamp = Date.now() }) it('Check that accounts are set up correctly', async () => { @@ -173,7 +193,7 @@ describe('Running against a deployed service', () => { it('Returns sig when querying succeeds with unused request', async () => { await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS3, timestamp) + const response = await postToSignMessage(getRandomBlindedPhoneNumber(), ACCOUNT_ADDRESS3) expect(response.status).toBe(200) }) @@ -184,7 +204,7 @@ describe('Running against a deployed service', () => { it('Returns sig when querying succeeds with used request', async () => { await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS3, timestamp) + const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS3) expect(response.status).toBe(200) }) @@ -192,17 +212,6 @@ describe('Running against a deployed service', () => { const queryCount = await getQueryCount(ACCOUNT_ADDRESS3, IDENTIFIER) expect(queryCount).toEqual(initialQueryCount + 1) }) - - it('Returns sig when querying succeeds with missing timestamp', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS3) - expect(response.status).toBe(200) - }) - - it('Returns count when querying with missing timestamp increments query count', async () => { - const queryCount = await getQueryCount(ACCOUNT_ADDRESS3, IDENTIFIER) - expect(queryCount).toEqual(initialQueryCount + 2) - }) }) }) diff --git a/packages/phone-number-privacy/signer/test/index.test.ts b/packages/phone-number-privacy/signer/test/index.test.ts index 0755b618239..05dcb7b2b16 100644 --- a/packages/phone-number-privacy/signer/test/index.test.ts +++ b/packages/phone-number-privacy/signer/test/index.test.ts @@ -1,4 +1,4 @@ -import { authenticateUser, REQUEST_EXPIRY_WINDOW_MS } from '@celo/phone-number-privacy-common' +import { authenticateUser } from '@celo/phone-number-privacy-common' import BigNumber from 'bignumber.js' import request from 'supertest' import { ErrorMessage, WarningMessage } from '../../common/src/interfaces/error-utils' @@ -78,7 +78,6 @@ describe(`POST /getBlindedMessageSignature endpoint`, () => { blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, hashedPhoneNumber: '0x5f6e88c3f724b3a09d3194c0514426494955eff7127c29654e48a361a19b4b96', account: '0x78dc5D2D739606d31509C31d654056A45185ECb6', - timestamp: Date.now(), } describe('with valid input', () => { @@ -102,6 +101,27 @@ describe(`POST /getBlindedMessageSignature endpoint`, () => { done ) }) + // Backwards compatibility check + it('provides signature w/ expired timestamp', (done) => { + mockGetRemainingQueryCount.mockResolvedValue({ performedQueryCount: 0, totalQuota: 10 }) + mockGetBlockNumber.mockResolvedValue(10000) + request(app) + .post('/getBlindedMessagePartialSig') + .send({ ...validRequest, timestamp: Date.now() - 10 * 60 * 1000 }) // 10 minutes ago + .expect('Content-Type', /json/) + .expect( + 200, + { + success: true, + signature: BLS_SIGNATURE, + version: getVersion(), + performedQueryCount: 1, + totalQuota: 10, + blockNumber: 10000, + }, + done + ) + }) it('returns 403 on query count 0', (done) => { mockGetRemainingQueryCount.mockResolvedValue({ performedQueryCount: 10, totalQuota: 10 }) request(app) @@ -213,15 +233,6 @@ describe(`POST /getBlindedMessageSignature endpoint`, () => { request(app).post('/getBlindedMessagePartialSig').send(mockRequestData).expect(400, done) }) - it('expired timestamp returns 400', (done) => { - const mockRequestData = { - ...validRequest, - timestamp: Date.now() - REQUEST_EXPIRY_WINDOW_MS, - } - - request(app).post('/getBlindedMessagePartialSig').send(mockRequestData).expect(400, done) - }) - it('invalid blinded phone number returns 400', (done) => { const mockRequestData = { ...validRequest, diff --git a/packages/phone-number-privacy/signer/test/signing/query-quota.test.ts b/packages/phone-number-privacy/signer/test/signing/query-quota.test.ts index bb179b78d03..8a4d557e4bf 100644 --- a/packages/phone-number-privacy/signer/test/signing/query-quota.test.ts +++ b/packages/phone-number-privacy/signer/test/signing/query-quota.test.ts @@ -1,4 +1,5 @@ import { isVerified, rootLogger } from '@celo/phone-number-privacy-common' +import { StableToken } from '@celo/contractkit' import BigNumber from 'bignumber.js' import allSettled from 'promise.allsettled' import { @@ -118,16 +119,48 @@ describe(getRemainingQueryCount, () => { totalQuota: 0, }) }) - it('Calculates remaining query count for unverified account with only cUSD balance', async () => { + it('Calculates remaining query count for unverified account with cUSD balance', async () => { const contractKitVerifiedNoTx = createMockContractKit( { [ContractRetrieval.getAttestations]: createMockAttestation(0, 0), - [ContractRetrieval.getStableToken]: createMockToken(new BigNumber(200000000000000000)), + [ContractRetrieval.getStableToken]: createMockToken(new BigNumber(0)), [ContractRetrieval.getGoldToken]: createMockToken(new BigNumber(0)), [ContractRetrieval.getAccounts]: createMockAccounts('0x0'), }, createMockWeb3(0) ) + contractKitVerifiedNoTx.contracts[ContractRetrieval.getStableToken] = jest.fn( + (stableToken: StableToken) => { + return stableToken === StableToken.cUSD + ? createMockToken(new BigNumber(200000000000000000)) + : createMockToken(new BigNumber(0)) + } + ) + mockPerformedQueryCount.mockImplementation(() => new Promise((resolve) => resolve(1))) + mockIsVerified.mockReturnValue(false) + mockGetContractKit.mockImplementation(() => contractKitVerifiedNoTx) + expect(await getRemainingQueryCount(rootLogger, mockAccount, mockPhoneNumber)).toEqual({ + performedQueryCount: 1, + totalQuota: 10, + }) + }) + it('Calculates remaining query count for unverified account with cEUR balance', async () => { + const contractKitVerifiedNoTx = createMockContractKit( + { + [ContractRetrieval.getAttestations]: createMockAttestation(0, 0), + [ContractRetrieval.getStableToken]: createMockToken(new BigNumber(0)), + [ContractRetrieval.getGoldToken]: createMockToken(new BigNumber(0)), + [ContractRetrieval.getAccounts]: createMockAccounts('0x0'), + }, + createMockWeb3(0) + ) + contractKitVerifiedNoTx.contracts[ContractRetrieval.getStableToken] = jest.fn( + (stableToken: StableToken) => { + return stableToken === StableToken.cEUR + ? createMockToken(new BigNumber(200000000000000000)) + : createMockToken(new BigNumber(0)) + } + ) mockPerformedQueryCount.mockImplementation(() => new Promise((resolve) => resolve(1))) mockIsVerified.mockReturnValue(false) mockGetContractKit.mockImplementation(() => contractKitVerifiedNoTx) diff --git a/packages/protocol/contracts/common/Accounts.sol b/packages/protocol/contracts/common/Accounts.sol index a9ce16f962f..670136bab17 100644 --- a/packages/protocol/contracts/common/Accounts.sol +++ b/packages/protocol/contracts/common/Accounts.sol @@ -108,7 +108,7 @@ contract Accounts is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 2, 0); + return (1, 1, 2, 1); } /** diff --git a/packages/protocol/contracts/common/MetaTransactionWallet.sol b/packages/protocol/contracts/common/MetaTransactionWallet.sol index f515f12eb8c..9eb7ed16562 100644 --- a/packages/protocol/contracts/common/MetaTransactionWallet.sol +++ b/packages/protocol/contracts/common/MetaTransactionWallet.sol @@ -73,7 +73,7 @@ contract MetaTransactionWallet is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 0); + return (1, 1, 1, 1); } /** diff --git a/packages/protocol/contracts/common/Signatures.sol b/packages/protocol/contracts/common/Signatures.sol index 16dec10c507..9d80692a335 100644 --- a/packages/protocol/contracts/common/Signatures.sol +++ b/packages/protocol/contracts/common/Signatures.sol @@ -3,14 +3,6 @@ pragma solidity ^0.5.13; import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol"; library Signatures { - /** - * @notice Returns the storage, major, minor, and patch version of the contract. - * @return The storage, major, minor, and patch version of the contract. - */ - function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 2, 0); - } - /** * @notice Given a signed address, returns the signer of the address. * @param message The address that was signed. diff --git a/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol b/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol index bb3f649dfaa..5f5bef0123e 100644 --- a/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol +++ b/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol @@ -12,14 +12,6 @@ library AddressLinkedList { using LinkedList for LinkedList.List; using SafeMath for uint256; - /** - * @notice Returns the storage, major, minor, and patch version of the contract. - * @return The storage, major, minor, and patch version of the contract. - */ - function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 0); - } - function toBytes(address a) public pure returns (bytes32) { return bytes32(uint256(a) << 96); } diff --git a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol index 401aa79b423..5a74ecdcb48 100644 --- a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol +++ b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol @@ -12,14 +12,6 @@ library AddressSortedLinkedList { using SafeMath for uint256; using SortedLinkedList for SortedLinkedList.List; - /** - * @notice Returns the storage, major, minor, and patch version of the contract. - * @return The storage, major, minor, and patch version of the contract. - */ - function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 0); - } - function toBytes(address a) public pure returns (bytes32) { return bytes32(uint256(a) << 96); } diff --git a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedListWithMedian.sol b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedListWithMedian.sol index 867f502e26a..2cf62ee1d10 100644 --- a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedListWithMedian.sol +++ b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedListWithMedian.sol @@ -11,14 +11,6 @@ library AddressSortedLinkedListWithMedian { using SafeMath for uint256; using SortedLinkedListWithMedian for SortedLinkedListWithMedian.List; - /** - * @notice Returns the storage, major, minor, and patch version of the contract. - * @return The storage, major, minor, and patch version of the contract. - */ - function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 0); - } - function toBytes(address a) public pure returns (bytes32) { return bytes32(uint256(a) << 96); } diff --git a/packages/protocol/contracts/common/linkedlists/IntegerSortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/IntegerSortedLinkedList.sol index 74d6cae53cd..70fd1acec6d 100644 --- a/packages/protocol/contracts/common/linkedlists/IntegerSortedLinkedList.sol +++ b/packages/protocol/contracts/common/linkedlists/IntegerSortedLinkedList.sol @@ -11,14 +11,6 @@ library IntegerSortedLinkedList { using SafeMath for uint256; using SortedLinkedList for SortedLinkedList.List; - /** - * @notice Returns the storage, major, minor, and patch version of the contract. - * @return The storage, major, minor, and patch version of the contract. - */ - function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 0); - } - /** * @notice Inserts an element into a doubly linked list. * @param list A storage pointer to the underlying list. diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 5331b1c702d..218de3b7e69 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -136,7 +136,7 @@ contract Election is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 2, 0); + return (1, 1, 2, 1); } /** diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol index 85d54235a68..7f62473ec9a 100644 --- a/packages/protocol/contracts/governance/Governance.sol +++ b/packages/protocol/contracts/governance/Governance.sol @@ -200,7 +200,7 @@ contract Governance is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 2, 1, 0); + return (1, 2, 1, 1); } /** diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol index a1724e5b739..a77c9f2ac04 100644 --- a/packages/protocol/contracts/governance/LockedGold.sol +++ b/packages/protocol/contracts/governance/LockedGold.sol @@ -81,7 +81,7 @@ contract LockedGold is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 1); + return (1, 1, 1, 2); } /** diff --git a/packages/protocol/contracts/governance/Proposals.sol b/packages/protocol/contracts/governance/Proposals.sol index c47a5e183f4..8e1b84a22a0 100644 --- a/packages/protocol/contracts/governance/Proposals.sol +++ b/packages/protocol/contracts/governance/Proposals.sol @@ -47,14 +47,6 @@ library Proposals { string descriptionUrl; } - /** - * @notice Returns the storage, major, minor, and patch version of the contract. - * @return The storage, major, minor, and patch version of the contract. - */ - function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 0); - } - /** * @notice Constructs a proposal. * @param proposal The proposal struct to be constructed. diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol index 5570b106a5f..361c1e392ec 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts/governance/Validators.sol @@ -163,7 +163,7 @@ contract Validators is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 2, 0, 1); + return (1, 2, 0, 2); } /** diff --git a/packages/protocol/contracts/identity/Attestations.sol b/packages/protocol/contracts/identity/Attestations.sol index 6a1316e0aef..147b03613c8 100644 --- a/packages/protocol/contracts/identity/Attestations.sol +++ b/packages/protocol/contracts/identity/Attestations.sol @@ -177,7 +177,7 @@ contract Attestations is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 1); + return (1, 1, 1, 2); } /** diff --git a/packages/protocol/contracts/identity/Escrow.sol b/packages/protocol/contracts/identity/Escrow.sol index 2d63dfe4d4f..285dd5ce55a 100644 --- a/packages/protocol/contracts/identity/Escrow.sol +++ b/packages/protocol/contracts/identity/Escrow.sol @@ -74,7 +74,7 @@ contract Escrow is * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 1, 1); + return (1, 1, 1, 2); } /** diff --git a/packages/protocol/contracts/stability/SortedOracles.sol b/packages/protocol/contracts/stability/SortedOracles.sol index fa361c4008e..137a7851247 100644 --- a/packages/protocol/contracts/stability/SortedOracles.sol +++ b/packages/protocol/contracts/stability/SortedOracles.sol @@ -59,7 +59,7 @@ contract SortedOracles is ISortedOracles, ICeloVersionedContract, Ownable, Initi * @return The storage, major, minor, and patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 2, 0); + return (1, 1, 2, 1); } /** diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 27141e555a3..df74a71c5ea 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -10,16 +10,15 @@ "lint:sol": "solhint './contracts/**/*.sol'", "lint": "yarn run lint:ts && yarn run lint:sol", "clean": "rm -rf ./types/typechain && rm -rf build/* && rm -rf .0x-artifacts/* && rm -rf migrations/*.js* && rm -rf test/**/*.js* && rm -f lib/*.js* && rm -f lib/**/*.js* && rm -f scripts/*.js*", - "pretest": "yarn run build", "test": "rm test/**/*.js ; node runTests.js", "quicktest": "./scripts/bash/quicktest.sh", "test:coverage": "yarn run test --coverage", "test:devchain-release": "./scripts/bash/release-on-devchain.sh", "test:release-snapshots": "./scripts/bash/release-snapshots.sh", "test:generate-old-devchain-and-build": "./scripts/bash/generate-old-devchain-and-build.sh", + "build:ts": "rm -f migrations/*.js* && ts-node ./scripts/build.ts --truffleTypes ./types/typechain && tsc -b", "gas": "yarn run test --gas", - "build:ts": "rm -f migrations/*.js* && tsc -b", - "build:sol": "ts-node ./scripts/build.ts", + "build:sol": "ts-node ./scripts/build.ts --solidity ${BUILD_DIR:-./build}", "build": "yarn build:sol && yarn build:ts", "migrate": "./scripts/bash/migrate.sh", "set_block_gas_limit": "./scripts/bash/set_block_gas_limit.sh", @@ -40,7 +39,6 @@ "verify-release": "./scripts/bash/verify-release.sh", "invite": "./scripts/bash/invite.sh", "ganache-dev": "./scripts/bash/ganache.sh", - "pretruffle:migrate": "yarn build:ts", "truffle:migrate": "truffle migrate", "devchain": "ts-node scripts/devchain.ts", "set-exchange-rate": "./scripts/bash/set_exchange_rate.sh", @@ -52,10 +50,10 @@ "@0x/sol-profiler": "^3.0.0", "@0x/sol-trace": "^2.0.16", "@0x/subproviders": "^5.0.0", - "@celo/base": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", + "@celo/base": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", "@celo/ganache-cli": "git+https://github.com/celo-org/ganache-cli.git#e933297", - "@celo/utils": "1.2.2-dev", + "@celo/utils": "1.2.3-dev", "@openzeppelin/upgrades": "^2.8.0", "bip39": "https://github.com/bitcoinjs/bip39#d8ea080a18b40f301d4e2219a2991cd2417e83c2", "bls12377js": "https://github.com/celo-org/bls12377js#cb38a4cfb643c778619d79b20ca3e5283a2122a6", diff --git a/packages/protocol/releaseData/initializationData/release6.json b/packages/protocol/releaseData/initializationData/release6.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/packages/protocol/releaseData/initializationData/release6.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/protocol/releaseData/versionReports/release5-report.json b/packages/protocol/releaseData/versionReports/release5-report.json new file mode 100644 index 00000000000..62e24408edc --- /dev/null +++ b/packages/protocol/releaseData/versionReports/release5-report.json @@ -0,0 +1,105 @@ +{ + "oldArtifactsFolder": "/home/circleci/app/packages/protocol/build/core-contracts.v4/contracts", + "newArtifactsFolder": "/home/circleci/app/packages/protocol/build/core-contracts.v5/contracts", + "exclude": "/.*Test|Mock.*|I[A-Z].*|.*Proxy|MultiSig.*|ReleaseGold|SlasherUtil|UsingPrecompiles/", + "report": { + "contracts": { + "GrandaMento": { + "changes": { + "storage": [], + "major": [ + { + "contract": "GrandaMento", + "type": "NewContract" + } + ], + "minor": [], + "patch": [] + }, + "versionDelta": { + "storage": "=", + "major": "+1", + "minor": "0", + "patch": "0" + } + }, + "UsingRegistryV2": { + "changes": { + "storage": [], + "major": [ + { + "contract": "UsingRegistryV2", + "type": "NewContract" + } + ], + "minor": [], + "patch": [] + }, + "versionDelta": { + "storage": "=", + "major": "+1", + "minor": "0", + "patch": "0" + } + }, + "StableToken": { + "changes": { + "storage": [], + "major": [], + "minor": [], + "patch": [ + { + "contract": "StableToken", + "type": "DeployedBytecode" + } + ] + }, + "versionDelta": { + "storage": "=", + "major": "=", + "minor": "=", + "patch": "+1" + } + }, + "StableTokenEUR": { + "changes": { + "storage": [], + "major": [], + "minor": [], + "patch": [ + { + "contract": "StableTokenEUR", + "type": "DeployedBytecode" + } + ] + }, + "versionDelta": { + "storage": "=", + "major": "=", + "minor": "=", + "patch": "+1" + } + }, + "Validators": { + "changes": { + "storage": [], + "major": [], + "minor": [], + "patch": [ + { + "contract": "Validators", + "type": "DeployedBytecode" + } + ] + }, + "versionDelta": { + "storage": "=", + "major": "=", + "minor": "=", + "patch": "+1" + } + } + }, + "libraries": {} + } +} diff --git a/packages/protocol/scripts/bash/check-versions.sh b/packages/protocol/scripts/bash/check-versions.sh index 4f0f2759884..363a4970163 100755 --- a/packages/protocol/scripts/bash/check-versions.sh +++ b/packages/protocol/scripts/bash/check-versions.sh @@ -37,9 +37,6 @@ if [ ! -z "$REPORT" ]; then REPORT_FLAG="--output_file "$REPORT fi -# fetch tags -git fetch origin +'refs/tags/celo-core-contracts*:refs/tags/celo-core-contracts*' >> $LOG_FILE - source scripts/bash/release-lib.sh build_tag $OLD_BRANCH $LOG_FILE diff --git a/packages/protocol/scripts/bash/generate-old-devchain-and-build.sh b/packages/protocol/scripts/bash/generate-old-devchain-and-build.sh index d663da8e037..c44f02349ee 100755 --- a/packages/protocol/scripts/bash/generate-old-devchain-and-build.sh +++ b/packages/protocol/scripts/bash/generate-old-devchain-and-build.sh @@ -30,7 +30,7 @@ done [ -z "$BUILD_DIR" ] && BUILD_DIR=$(echo build/$(echo $BRANCH | sed -e 's/\//_/g')); echo "- Checkout source code at $BRANCH" -git fetch origin +'refs/tags/celo-core-contracts*:refs/tags/celo-core-contracts*' 2>>$LOG_FILE >> $LOG_FILE +git fetch origin +'refs/tags/core-contracts.*:refs/tags/core-contracts.*' 2>>$LOG_FILE >> $LOG_FILE git checkout $BRANCH 2>>$LOG_FILE >> $LOG_FILE echo "- Build contract artifacts" diff --git a/packages/protocol/scripts/bash/release-lib.sh b/packages/protocol/scripts/bash/release-lib.sh index c27684f8f45..9a0f90062dd 100644 --- a/packages/protocol/scripts/bash/release-lib.sh +++ b/packages/protocol/scripts/bash/release-lib.sh @@ -11,21 +11,19 @@ function build_tag() { local BRANCH="$1" local LOG_FILE="$2" + git fetch origin +'refs/tags/core-contracts.v*:refs/tags/core-contracts.v*' >> $LOG_FILE + echo " - Checkout contracts source code at $BRANCH" BUILD_DIR=$(echo build/$(echo $BRANCH | sed -e 's/\//_/g')) [ -d contracts ] && rm -r contracts git checkout $BRANCH -- contracts 2>>$LOG_FILE >> $LOG_FILE - [ -d "build/contracts" ] && mv build/contracts build/contracts_tmp if [ ! -d $BUILD_DIR ]; then echo " - Build contract artifacts at $BUILD_DIR" - yarn build:sol >> $LOG_FILE - rm -rf $BUILD_DIR && mkdir -p $BUILD_DIR - mv build/contracts $BUILD_DIR + BUILD_DIR=$BUILD_DIR yarn build:sol >> $LOG_FILE else echo " - Contract artifacts already built at $BUILD_DIR" fi - [ -d "build/contracts_tmp" ] && mv build/contracts_tmp build/contracts [ -d contracts ] && rm -r contracts git checkout - -- contracts 2>>$LOG_FILE >> $LOG_FILE diff --git a/packages/protocol/scripts/bash/release-on-devchain.sh b/packages/protocol/scripts/bash/release-on-devchain.sh index ace4c76dc2a..0c0f7fb44b5 100755 --- a/packages/protocol/scripts/bash/release-on-devchain.sh +++ b/packages/protocol/scripts/bash/release-on-devchain.sh @@ -47,7 +47,7 @@ yarn run truffle exec ./scripts/truffle/verify-bytecode.js --network development echo "- Check versions of current branch" # From check-versions.sh -CONTRACT_EXCLUSION_REGEX=".*Test|Mock.*|I[A-Z].*|.*Proxy|.*LinkedList.*|MultiSig.*|ReleaseGold|SlasherUtil|FixidityLib|Signatures|Proposals|UsingPrecompiles" +CONTRACT_EXCLUSION_REGEX=".*Test|Mock.*|I[A-Z].*|.*Proxy|MultiSig.*|ReleaseGold|SlasherUtil|UsingPrecompiles" yarn ts-node scripts/check-backward.ts sem_check --old_contracts $BUILD_DIR/contracts --new_contracts build/contracts --exclude $CONTRACT_EXCLUSION_REGEX --output_file report.json # From make-release.sh diff --git a/packages/protocol/scripts/bash/verify-release.sh b/packages/protocol/scripts/bash/verify-release.sh index 121244f4936..138fe777daf 100755 --- a/packages/protocol/scripts/bash/verify-release.sh +++ b/packages/protocol/scripts/bash/verify-release.sh @@ -37,8 +37,6 @@ done [ -z "$NETWORK" ] && echo "Need to set the network via the -n flag" && exit 1; [ -z "$PROPOSAL" ] && echo "Need to set the proposal via the -p flag" && exit 1; -git fetch origin +'refs/tags/celo-core-contracts*:refs/tags/celo-core-contracts*' >> $LOG_FILE - source scripts/bash/release-lib.sh build_tag $BRANCH $LOG_FILE diff --git a/packages/protocol/scripts/build.ts b/packages/protocol/scripts/build.ts index 9ceb32f940b..a8aa8f99f99 100644 --- a/packages/protocol/scripts/build.ts +++ b/packages/protocol/scripts/build.ts @@ -1,13 +1,12 @@ /* tslint:disable no-console */ import Web3V1Celo from '@celo/typechain-target-web3-v1-celo' import { execSync } from 'child_process' -import fs from 'fs' +import { readJSONSync } from 'fs-extra' import path from 'path' import { tsGenerator } from 'ts-generator' const ROOT_DIR = path.normalize(path.join(__dirname, '../')) -const BUILD_DIR = path.join(ROOT_DIR, 'build') -const CONTRACTKIT_GEN_DIR = path.normalize(path.join(ROOT_DIR, '../sdk/contractkit/src/generated')) +const BUILD_DIR = path.join(ROOT_DIR, process.env.BUILD_DIR ?? './build') export const ProxyContracts = [ 'AccountsProxy', @@ -25,6 +24,7 @@ export const ProxyContracts = [ 'GoldTokenProxy', 'GovernanceApproverMultiSigProxy', 'GovernanceProxy', + 'GrandaMentoProxy', 'LockedGoldProxy', 'MetaTransactionWalletProxy', 'MetaTransactionWalletDeployerProxy', @@ -74,6 +74,9 @@ export const CoreContracts = [ 'StableToken', 'StableTokenEUR', 'SortedOracles', + + // liquidity + 'GrandaMento', ] const OtherContracts = [ @@ -88,11 +91,6 @@ const Interfaces = ['ICeloToken', 'IERC20', 'ICeloVersionedContract'] export const ImplContracts = OtherContracts.concat(ProxyContracts).concat(CoreContracts) -function getArtifact(contractName: string) { - const file = fs.readFileSync(`${BUILD_DIR}/contracts/${contractName}.json`).toString() - return JSON.parse(file) -} - function exec(cmd: string) { return execSync(cmd, { cwd: ROOT_DIR, stdio: 'inherit' }) } @@ -101,14 +99,14 @@ function hasEmptyBytecode(contract: any) { return contract.bytecode === '0x' } -function compile() { - console.log('Compiling') +function compile(outdir: string) { + console.log(`protocol: Compiling solidity to ${outdir}`) - exec(`yarn run --silent truffle compile --build_directory=${BUILD_DIR}`) + exec(`yarn run --silent truffle compile --build_directory=${outdir}`) for (const contractName of ImplContracts) { try { - const fileStr = getArtifact(contractName) + const fileStr = readJSONSync(`${outdir}/contracts/${contractName}.json`) if (hasEmptyBytecode(fileStr)) { console.error( `${contractName} has empty bytecode. Maybe you forgot to fully implement an interface?` @@ -123,23 +121,20 @@ function compile() { } } -function generateFilesForTruffle() { - console.log('protocol: Generating Truffle Types') - exec(`rm -rf "${ROOT_DIR}/typechain"`) +function generateFilesForTruffle(outdir: string) { + console.log(`protocol: Generating Truffle Types to ${outdir}`) + exec(`rm -rf "${outdir}"`) const globPattern = `${BUILD_DIR}/contracts/*.json` - exec( - `yarn run --silent typechain --target=truffle --outDir "./types/typechain" "${globPattern}" ` - ) + exec(`yarn run --silent typechain --target=truffle --outDir "${outdir}" "${globPattern}" `) } -async function generateFilesForContractKit() { - console.log('contractkit: Generating Types') - exec(`rm -rf ${CONTRACTKIT_GEN_DIR}`) - const relativePath = path.relative(ROOT_DIR, CONTRACTKIT_GEN_DIR) +async function generateFilesForContractKit(outdir: string) { + console.log(`protocol: Generating Web3 Types to ${outdir}`) + exec(`rm -rf ${outdir}`) + const relativePath = path.relative(ROOT_DIR, outdir) const contractKitContracts = CoreContracts.concat('Proxy').concat(Interfaces) - const globPattern = `${BUILD_DIR}/contracts/@(${contractKitContracts.join('|')}).json` const cwd = process.cwd() @@ -154,16 +149,33 @@ async function generateFilesForContractKit() { await tsGenerator({ cwd, loggingLvl: 'info' }, web3Generator) - exec(`yarn --cwd "${ROOT_DIR}/../.." prettier --write "${CONTRACTKIT_GEN_DIR}/**/*.ts"`) + exec(`yarn prettier --write "${outdir}/**/*.ts"`) } -async function main() { - compile() - generateFilesForTruffle() - await generateFilesForContractKit() +const _buildTargets = { + solidity: undefined, + truffleTypes: undefined, + web3Types: undefined, } -main().catch((err) => { +async function main(buildTargets: typeof _buildTargets) { + if (buildTargets.solidity) { + compile(buildTargets.solidity) + } + if (buildTargets.truffleTypes) { + generateFilesForTruffle(buildTargets.truffleTypes) + } + if (buildTargets.web3Types) { + await generateFilesForContractKit(buildTargets.web3Types) + } +} + +const minimist = require('minimist') +const argv = minimist(process.argv.slice(2), { + string: Object.keys(_buildTargets), +}) + +main(argv).catch((err) => { console.error(err) process.exit(1) }) diff --git a/packages/protocol/scripts/truffle/make-release.ts b/packages/protocol/scripts/truffle/make-release.ts index bfc895f61ce..ce6b2c538eb 100644 --- a/packages/protocol/scripts/truffle/make-release.ts +++ b/packages/protocol/scripts/truffle/make-release.ts @@ -80,7 +80,8 @@ const deployImplementation = async ( contractName: string, Contract: TruffleContract, dryRun: boolean, - from: string + from: string, + requireVersion = true ) => { const testingDeployment = false if (from) { @@ -95,7 +96,7 @@ const deployImplementation = async ( const getVersionNumberAbi = contract.abi.find( (abi: any) => abi.type === 'function' && abi.name === 'getVersionNumber' ) - if (!getVersionNumberAbi) { + if (requireVersion && !getVersionNumberAbi) { throw new Error(`Contract ${contractName} has changes but does not specify a version number`) } return contract @@ -208,7 +209,7 @@ const deployLibrary = async ( isDryRun: boolean, from: string ) => { - const contract = await deployImplementation(contractName, contractArtifact, isDryRun, from) + const contract = await deployImplementation(contractName, contractArtifact, isDryRun, from, false) addresses.set(contractName, contract.address) return } diff --git a/packages/protocol/specs/governance.spec b/packages/protocol/specs/governance.spec index 25fe61c4fef..0023b06274e 100644 --- a/packages/protocol/specs/governance.spec +++ b/packages/protocol/specs/governance.spec @@ -23,13 +23,15 @@ methods { getVoteTotals(uint256) returns uint256,uint256,uint256 envfree approver() returns address envfree getVoteRecord(address,uint256) returns uint256, uint256, uint256 envfree - getMostRecentReferendumProposal(address) returns uint256 envfree - proposalExists(uint256) returns bool - getVotedId(address, uint256) returns uint256 envfree - - // dispatches to Accounts - voteSignerToAccount(address) envfree => DISPATCHER(true) - getVoteSigner(address) returns address envfree => DISPATCHER(true) + getMostRecentReferendumProposal(address) returns uint256 envfree + proposalExists(uint256) returns bool + getVotedId(address, uint256) returns uint256 envfree + + // dispatches to Accounts + voteSignerToAccount(address) => DISPATCHER(true) UNRESOLVED + getVoteSigner(address) returns address => DISPATCHER(true) UNRESOLVED + accounts.voteSignerToAccount(address) returns (address) envfree + accounts.getVoteSigner(address) returns (address) envfree } ghost votedFor(address,uint256) returns bool { // voter to proposal id voted for diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 30bf74763be..921e815390a 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -5,7 +5,7 @@ This package will follow the release process outlined [here](https://docs.celo.o ## Development (not published yet) -### **[1.2.2--dev]** +### **[1.2.3--dev]** Features - one-line summary - [#](link PR) @@ -18,6 +18,33 @@ Other Changes ## Published +### **[1.2.2]** -- 2021-07-08 +Features +- new package: JS keystore support [#8096](https://github.com/celo-org/celo-monorepo/pull/8096) +- Add euro in the currencies enum [#7786](https://github.com/celo-org/celo-monorepo/pull/7786) +- CIP10 contracts changes [#5913](https://github.com/celo-org/celo-monorepo/pull/5913) +- Use Node 12 [#7851](https://github.com/celo-org/celo-monorepo/pull/7851) +- Add token address support to oracle commands [#8010](https://github.com/celo-org/celo-monorepo/pull/8010) +- Add manual personal_decrypt flow to WalletConnect CLI client [#8037](https://github.com/celo-org/celo-monorepo/pull/8037) +- Account utilities for normalization and heuristic based correction of mnemonic phrases [#8034] (https://github.com/celo-org/celo-monorepo/pull/8034) +- Add getBlockHeader utilities to connect package [#8213](https://github.com/celo-org/celo-monorepo/pull/8213) +- Granda Mento smart contract implementation [#8129](https://github.com/celo-org/celo-monorepo/pull/8129) + +Bug Fixes +- Fix governance:build-proposal bigNumber bug [#8028](https://github.com/celo-org/celo-monorepo/pull/8028) +- Remove ERC20 from core contracts [#8212](https://github.com/celo-org/celo-monorepo/pull/8212) + +Other Changes +- celocli: transfer:erc20 and balance commands [#7753](https://github.com/celo-org/celo-monorepo/pull/7753) +- Tutorial: Integrating WalletConnect into your DApp [#7802](https://github.com/celo-org/celo-monorepo/pull/7802) +- chore: remove @umpirsky/country-list package to save space [#7895](https://github.com/celo-org/celo-monorepo/pull/7895) +- Add script for running + testing eth_signTypedData [#7951](https://github.com/celo-org/celo-monorepo/pull/7951) +- Bump walletConnect version [#8012](https://github.com/celo-org/celo-monorepo/pull/8012) +- Upgrade vulnerable dependencies [#8073](https://github.com/celo-org/celo-monorepo/pull/8073) +- Add more params to status endpoint [#8125](https://github.com/celo-org/celo-monorepo/pull/8125) +- Add check for phrase length in mnemonic phrase correction [#8146](https://github.com/celo-org/celo-monorepo/pull/8146) +- JS Keystore Docs (with updated file structure) [#8185](https://github.com/celo-org/celo-monorepo/pull/8185) + ### **[1.2.1]** (only utils and base) -- 2021-04-22 Other Changes diff --git a/packages/sdk/base/package.json b/packages/sdk/base/package.json index 3f4ee1815a4..6f7d6bd4da9 100644 --- a/packages/sdk/base/package.json +++ b/packages/sdk/base/package.json @@ -1,6 +1,6 @@ { "name": "@celo/base", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Celo base common utils, no dependencies", "author": "Celo", "license": "Apache-2.0", diff --git a/packages/sdk/connect/package.json b/packages/sdk/connect/package.json index 75e1dfa3146..e3960a0b415 100644 --- a/packages/sdk/connect/package.json +++ b/packages/sdk/connect/package.json @@ -1,6 +1,6 @@ { "name": "@celo/connect", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Toolkit for connecting with the Celo network", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -23,7 +23,7 @@ "dependencies": { "@types/debug": "^4.1.5", "@types/utf8": "^2.1.6", - "@celo/utils": "1.2.2-dev", + "@celo/utils": "1.2.3-dev", "bignumber.js": "^9.0.0", "debug": "^4.1.1", "utf8": "3.0.0" diff --git a/packages/sdk/contractkit/package.json b/packages/sdk/contractkit/package.json index cd1b9a1a20f..0c802875a6f 100644 --- a/packages/sdk/contractkit/package.json +++ b/packages/sdk/contractkit/package.json @@ -1,6 +1,6 @@ { "name": "@celo/contractkit", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Celo's ContractKit to interact with Celo network", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -14,11 +14,12 @@ "contractkit" ], "scripts": { - "build": "tsc -b .", + "build:ts": "tsc -b .", + "build:gen": "BUILD_DIR=./build/$RELEASE_TAG yarn --cwd ../../protocol ts-node ./scripts/build.ts --web3Types ../sdk/contractkit/src/generated", + "build": "yarn build:gen && yarn build:ts", "clean": "tsc -b . --clean", "clean:all": "yarn clean && rm -rf src/generated", - "build:gen": "yarn --cwd ../../protocol build", - "prepublishOnly": "yarn build:gen && yarn build", + "prepublishOnly": "yarn build", "docs": "typedoc && ts-node ../utils/scripts/linkdocs.ts contractkit", "test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 25", "test:livechain": "yarn --cwd ../../protocol devchain run-tar .tmp/devchain.tar.gz", @@ -26,17 +27,17 @@ "lint": "tslint -c tslint.json --project ." }, "dependencies": { + "@celo/base": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-local": "1.2.3-dev", "@types/debug": "^4.1.5", - "@celo/base": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/wallet-local": "1.2.2-dev", "bignumber.js": "^9.0.0", "cross-fetch": "3.0.4", "debug": "^4.1.1", "fp-ts": "2.1.1", "io-ts": "2.0.1", - "moment": "^2.29.0", + "semver": "^7.3.5", "web3": "1.3.6" }, "devDependencies": { @@ -57,7 +58,7 @@ "engines": { "node": ">=8.13.0" }, - "browser":{ + "browser": { "child_process": false - } + } } diff --git a/packages/sdk/contractkit/src/base.ts b/packages/sdk/contractkit/src/base.ts index ed079912dae..36db4337a6f 100644 --- a/packages/sdk/contractkit/src/base.ts +++ b/packages/sdk/contractkit/src/base.ts @@ -15,6 +15,7 @@ export enum CeloContract { GasPriceMinimum = 'GasPriceMinimum', GoldToken = 'GoldToken', Governance = 'Governance', + GrandaMento = 'GrandaMento', LockedGold = 'LockedGold', MetaTransactionWallet = 'MetaTransactionWallet', MetaTransactionWalletDeployer = 'MetaTransactionWalletDeployer', diff --git a/packages/sdk/contractkit/src/contract-cache.ts b/packages/sdk/contractkit/src/contract-cache.ts index 7bb5e2d9b18..769aae08bb0 100644 --- a/packages/sdk/contractkit/src/contract-cache.ts +++ b/packages/sdk/contractkit/src/contract-cache.ts @@ -16,6 +16,7 @@ import { FreezerWrapper } from './wrappers/Freezer' import { GasPriceMinimumWrapper } from './wrappers/GasPriceMinimum' import { GoldTokenWrapper } from './wrappers/GoldTokenWrapper' import { GovernanceWrapper } from './wrappers/Governance' +import { GrandaMentoWrapper } from './wrappers/GrandaMento' import { LockedGoldWrapper } from './wrappers/LockedGold' import { MetaTransactionWalletWrapper } from './wrappers/MetaTransactionWallet' import { MetaTransactionWalletDeployerWrapper } from './wrappers/MetaTransactionWalletDeployer' @@ -42,6 +43,7 @@ const WrapperFactories = { [CeloContract.GasPriceMinimum]: GasPriceMinimumWrapper, [CeloContract.GoldToken]: GoldTokenWrapper, [CeloContract.Governance]: GovernanceWrapper, + [CeloContract.GrandaMento]: GrandaMentoWrapper, [CeloContract.LockedGold]: LockedGoldWrapper, // [CeloContract.Random]: RandomWrapper, // [CeloContract.Registry]: RegistryWrapper, @@ -75,6 +77,7 @@ interface WrapperCacheMap { [CeloContract.GasPriceMinimum]?: GasPriceMinimumWrapper [CeloContract.GoldToken]?: GoldTokenWrapper [CeloContract.Governance]?: GovernanceWrapper + [CeloContract.GrandaMento]?: GrandaMentoWrapper [CeloContract.LockedGold]?: LockedGoldWrapper [CeloContract.MetaTransactionWallet]?: MetaTransactionWalletWrapper [CeloContract.MetaTransactionWalletDeployer]?: MetaTransactionWalletDeployerWrapper @@ -143,6 +146,9 @@ export class WrapperCache { getGovernance() { return this.getContract(CeloContract.Governance) } + getGrandaMento() { + return this.getContract(CeloContract.GrandaMento) + } getLockedGold() { return this.getContract(CeloContract.LockedGold) } diff --git a/packages/sdk/contractkit/src/kit.ts b/packages/sdk/contractkit/src/kit.ts index 0decae712b6..5a84764a708 100644 --- a/packages/sdk/contractkit/src/kit.ts +++ b/packages/sdk/contractkit/src/kit.ts @@ -130,6 +130,7 @@ export class ContractKit { this.contracts.getValidators(), this.contracts.getDowntimeSlasher(), this.contracts.getBlockchainParameters(), + this.contracts.getGrandaMento(), ] const contracts = await Promise.all(promises) const res = await Promise.all([ @@ -145,6 +146,7 @@ export class ContractKit { contracts[7].getConfig(), contracts[8].getConfig(), contracts[9].getConfig(), + contracts[10].getConfig(), ]) return { exchanges: res[0], @@ -175,6 +177,7 @@ export class ContractKit { this.contracts.getValidators(), this.contracts.getDowntimeSlasher(), this.contracts.getBlockchainParameters(), + this.contracts.getGrandaMento(), ] const contracts = await Promise.all(promises) const res = await Promise.all([ @@ -190,6 +193,7 @@ export class ContractKit { contracts[7].getHumanReadableConfig(), contracts[8].getHumanReadableConfig(), contracts[9].getConfig(), + contracts[10].getConfig(), ]) return { exchanges: res[0], diff --git a/packages/sdk/contractkit/src/test-utils/transferownership.ts b/packages/sdk/contractkit/src/test-utils/transferownership.ts new file mode 100644 index 00000000000..53787bbbe7d --- /dev/null +++ b/packages/sdk/contractkit/src/test-utils/transferownership.ts @@ -0,0 +1,62 @@ +import { NetworkConfig, timeTravel } from '@celo/dev-utils/lib/ganache-test' +import Web3 from 'web3' +import { newKitFromWeb3 } from '../kit' +import { AccountsWrapper } from '../wrappers/Accounts' +import { Proposal, ProposalTransaction } from '../wrappers/Governance' + +// Implements a transfer ownership function using only contractkit primitives + +const expConfigGovernance = NetworkConfig.governance + +export async function assumeOwnership(web3: Web3, to: string, proposalId: number = 1) { + const kit = newKitFromWeb3(web3) + const ONE_CGLD = web3.utils.toWei('1', 'ether') + const accounts = await web3.eth.getAccounts() + let accountWrapper: AccountsWrapper + accountWrapper = await kit.contracts.getAccounts() + const lockedGold = await kit.contracts.getLockedGold() + + try { + await accountWrapper.createAccount().sendAndWaitForReceipt({ from: accounts[0] }) + await lockedGold.lock().sendAndWaitForReceipt({ from: accounts[0], value: ONE_CGLD }) + } catch (error) { + console.log('Account already created') + } + + const grandaMento = await kit._web3Contracts.getGrandaMento() + const governance = await kit.contracts.getGovernance() + const multiSig = await kit.contracts.getMultiSig(await governance.getApprover()) + + const tenMillionCELO = web3.utils.toWei('10000000') + + await lockedGold.lock().sendAndWaitForReceipt({ value: tenMillionCELO }) + + const ownershiptx: ProposalTransaction = { + value: '0', + to: (grandaMento as any)._address, + input: grandaMento.methods.transferOwnership(to).encodeABI(), + } + const proposal: Proposal = [ownershiptx] + + await governance.propose(proposal, 'URL').sendAndWaitForReceipt({ + from: accounts[0], + value: (await governance.getConfig()).minDeposit.toNumber(), + }) + + const tx = await governance.upvote(proposalId, accounts[1]) + await tx.sendAndWaitForReceipt() + await timeTravel(expConfigGovernance.dequeueFrequency, web3) + await governance.dequeueProposalsIfReady().sendAndWaitForReceipt() + + const tx2 = await governance.approve(proposalId) + const multisigTx = await multiSig.submitOrConfirmTransaction(governance.address, tx2.txo) + await multisigTx.sendAndWaitForReceipt({ from: accounts[0] }) + await timeTravel(expConfigGovernance.approvalStageDuration, web3) + + const tx3 = await governance.vote(proposalId, 'Yes') + await tx3.sendAndWaitForReceipt({ from: accounts[0] }) + await timeTravel(expConfigGovernance.referendumStageDuration, web3) + + const tx4 = await governance.execute(proposalId) + await tx4.sendAndWaitForReceipt() +} diff --git a/packages/sdk/contractkit/src/versions.ts b/packages/sdk/contractkit/src/versions.ts new file mode 100644 index 00000000000..814f966da5e --- /dev/null +++ b/packages/sdk/contractkit/src/versions.ts @@ -0,0 +1,19 @@ +const semverGte = require('semver/functions/gte') + +export class ContractVersion { + constructor( + public readonly storage: number | string, + public readonly major: number | string, + public readonly minor: number | string, + public readonly patch: number | string + ) {} + private toSemver = () => `${this.storage}.${this.major}.${this.minor}` + isAtLeast = (other: ContractVersion) => semverGte(this.toSemver(), other.toSemver()) + toString = () => this.toSemver().concat(`.${this.patch}`) + toRaw = () => [this.storage, this.major, this.minor, this.patch] + static fromRaw = (raw: ReturnType) => + new ContractVersion(raw[0], raw[1], raw[2], raw[3]) +} + +export const newContractVersion = (storage: number, major: number, minor: number, patch: number) => + new ContractVersion(storage, major, minor, patch) diff --git a/packages/sdk/contractkit/src/web3-contract-cache.ts b/packages/sdk/contractkit/src/web3-contract-cache.ts index a120d9d7aea..7bc2bbce812 100644 --- a/packages/sdk/contractkit/src/web3-contract-cache.ts +++ b/packages/sdk/contractkit/src/web3-contract-cache.ts @@ -16,6 +16,7 @@ import { newFreezer } from './generated/Freezer' import { newGasPriceMinimum } from './generated/GasPriceMinimum' import { newGoldToken } from './generated/GoldToken' import { newGovernance } from './generated/Governance' +import { newGrandaMento } from './generated/GrandaMento' import { newIerc20 } from './generated/IERC20' import { newLockedGold } from './generated/LockedGold' import { newMetaTransactionWallet } from './generated/MetaTransactionWallet' @@ -50,6 +51,7 @@ export const ContractFactories = { [CeloContract.GasPriceMinimum]: newGasPriceMinimum, [CeloContract.GoldToken]: newGoldToken, [CeloContract.Governance]: newGovernance, + [CeloContract.GrandaMento]: newGrandaMento, [CeloContract.LockedGold]: newLockedGold, [CeloContract.MetaTransactionWallet]: newMetaTransactionWallet, [CeloContract.MetaTransactionWalletDeployer]: newMetaTransactionWalletDeployer, @@ -124,6 +126,9 @@ export class Web3ContractCache { getGovernance() { return this.getContract(CeloContract.Governance) } + getGrandaMento() { + return this.getContract(CeloContract.GrandaMento) + } getLockedGold() { return this.getContract(CeloContract.LockedGold) } diff --git a/packages/sdk/contractkit/src/wrappers/Accounts.ts b/packages/sdk/contractkit/src/wrappers/Accounts.ts index 359cdce7532..1ceabd47909 100644 --- a/packages/sdk/contractkit/src/wrappers/Accounts.ts +++ b/packages/sdk/contractkit/src/wrappers/Accounts.ts @@ -10,6 +10,7 @@ import { soliditySha3 } from '@celo/utils/lib/solidity' import { authorizeSigner as buildAuthorizeSignerTypedData } from '@celo/utils/lib/typed-data-constructors' import { keccak256 } from 'web3-utils' import { Accounts } from '../generated/Accounts' +import { newContractVersion } from '../versions' import { BaseWrapper, proxyCall, @@ -35,6 +36,8 @@ interface AccountSummary { * Contract for handling deposits needed for voting. */ export class AccountsWrapper extends BaseWrapper { + private RELEASE_4_VERSION = newContractVersion(1, 1, 2, 0) + /** * Creates an account. */ @@ -284,6 +287,7 @@ export class AccountsWrapper extends BaseWrapper { } async authorizeSigner(signer: Address, role: string) { + await this.onlyVersionOrGreater(this.RELEASE_4_VERSION) const [accounts, chainId, accountsContract] = await Promise.all([ this.kit.connection.getAccounts(), this.kit.connection.chainId(), @@ -308,6 +312,7 @@ export class AccountsWrapper extends BaseWrapper { } async startSignerAuthorization(signer: Address, role: string) { + await this.onlyVersionOrGreater(this.RELEASE_4_VERSION) return toTransactionObject( this.kit.connection, this.contract.methods.authorizeSigner(signer, keccak256(role)) @@ -315,6 +320,7 @@ export class AccountsWrapper extends BaseWrapper { } async completeSignerAuthorization(account: Address, role: string) { + await this.onlyVersionOrGreater(this.RELEASE_4_VERSION) return toTransactionObject( this.kit.connection, this.contract.methods.completeSignerAuthorization(account, keccak256(role)) diff --git a/packages/sdk/contractkit/src/wrappers/Attestations.ts b/packages/sdk/contractkit/src/wrappers/Attestations.ts index f008dded32d..397a529c6b4 100644 --- a/packages/sdk/contractkit/src/wrappers/Attestations.ts +++ b/packages/sdk/contractkit/src/wrappers/Attestations.ts @@ -708,6 +708,7 @@ export class AttestationsWrapper extends BaseWrapper { smsProvidersRandomized: null, maxDeliveryAttempts: null, maxRerequestMins: null, + twilioVerifySidProvided: null, } if (!hasAttestationSigner) { @@ -767,6 +768,7 @@ export class AttestationsWrapper extends BaseWrapper { ret.smsProvidersRandomized = statusResponseBody.smsProvidersRandomized ret.maxDeliveryAttempts = statusResponseBody.maxDeliveryAttempts ret.maxRerequestMins = statusResponseBody.maxRerequestMins + ret.twilioVerifySidProvided = statusResponseBody.twilioVerifySidProvided // Healthcheck was added in 1.0.1, same time version started being reported. if (statusResponseBody.version) { @@ -853,4 +855,5 @@ export interface AttestationServiceStatusResponse { smsProvidersRandomized: boolean | null maxDeliveryAttempts: number | null maxRerequestMins: number | null + twilioVerifySidProvided: boolean | null } diff --git a/packages/sdk/contractkit/src/wrappers/BaseWrapper.test.ts b/packages/sdk/contractkit/src/wrappers/BaseWrapper.test.ts new file mode 100644 index 00000000000..33509b2eefe --- /dev/null +++ b/packages/sdk/contractkit/src/wrappers/BaseWrapper.test.ts @@ -0,0 +1,100 @@ +import { NULL_ADDRESS } from '@celo/base' +import { CeloTxObject } from '@celo/connect' +import BigNumber from 'bignumber.js' +import Web3 from 'web3' +import { + ICeloVersionedContract, + newICeloVersionedContract, +} from '../generated/ICeloVersionedContract' +import { newKitFromWeb3 } from '../kit' +import { ContractVersion, newContractVersion } from '../versions' +import { BaseWrapper, unixSecondsTimestampToDateString } from './BaseWrapper' + +const web3 = new Web3('http://localhost:8545') +const mockContract = newICeloVersionedContract(web3, NULL_ADDRESS) +const mockVersion = newContractVersion(1, 1, 1, 1) +// @ts-ignore +mockContract.methods.getVersionNumber = (): CeloTxObject => ({ + call: async () => mockVersion.toRaw(), +}) + +class TestWrapper extends BaseWrapper { + constructor() { + super(newKitFromWeb3(web3), mockContract) + } + + async protectedFunction(v: ContractVersion) { + await this.onlyVersionOrGreater(v) + } +} + +describe('TestWrapper', () => { + const tw = new TestWrapper() + + describe(`#onlyVersionOrGreater (actual = ${mockVersion})`, () => { + const throwTests = [ + newContractVersion(2, 0, 0, 0), + newContractVersion(1, 2, 0, 0), + newContractVersion(1, 1, 2, 0), + ] + const resolveTests = [newContractVersion(1, 1, 1, 2), newContractVersion(1, 0, 0, 0)] + + throwTests.forEach((v) => { + it(`should throw with incompatible version ${v}`, async () => { + await expect(tw.protectedFunction(v)).rejects.toThrow( + `Bytecode version ${mockVersion} is not compatible with ${v} yet` + ) + }) + }) + + resolveTests.forEach((v) => { + it(`should resolve with compatible version ${v}`, async () => { + await expect(tw.protectedFunction(v)).resolves.not.toThrow() + }) + }) + }) +}) + +describe('unixSecondsTimestampToDateString()', () => { + const date = new BigNumber(1627489780) + + const timezoneMock = (zone: string) => { + const DateTimeFormat = Intl.DateTimeFormat + + jest + .spyOn(global.Intl, 'DateTimeFormat') + .mockImplementation( + (locale, options) => new DateTimeFormat(locale, { ...options, timeZone: zone }) + ) + } + + afterEach(() => { + jest.restoreAllMocks() + }) + + describe('when Brazil/East', () => { + it('returns local time', () => { + timezoneMock('Brazil/East') + expect(unixSecondsTimestampToDateString(date)).toEqual('Wed, Jul 28, 2021, 1:29 PM GMT-3') + }) + }) + + describe('when UTC', () => { + it('returns utc time', () => { + timezoneMock('UTC') + expect(unixSecondsTimestampToDateString(date)).toEqual('Wed, Jul 28, 2021, 4:29 PM UTC') + }) + }) + describe('when Australia/Adelaide', () => { + it('returns local time', () => { + timezoneMock('Australia/Adelaide') + expect(unixSecondsTimestampToDateString(date)).toEqual('Thu, Jul 29, 2021, 1:59 AM GMT+9:30') + }) + }) + describe('when Europe/London', () => { + it('returns local time', () => { + timezoneMock('Europe/London') + expect(unixSecondsTimestampToDateString(date)).toEqual('Wed, Jul 28, 2021, 5:29 PM GMT+1') + }) + }) +}) diff --git a/packages/sdk/contractkit/src/wrappers/BaseWrapper.ts b/packages/sdk/contractkit/src/wrappers/BaseWrapper.ts index 2b8984643ce..36d7344d8fa 100644 --- a/packages/sdk/contractkit/src/wrappers/BaseWrapper.ts +++ b/packages/sdk/contractkit/src/wrappers/BaseWrapper.ts @@ -10,8 +10,9 @@ import { } from '@celo/connect' import { fromFixed, toFixed } from '@celo/utils/lib/fixidity' import BigNumber from 'bignumber.js' -import moment from 'moment' +import { ICeloVersionedContract } from '../generated/ICeloVersionedContract' import { ContractKit } from '../kit' +import { ContractVersion } from '../versions' /** Represents web3 native contract Method */ type Method = (...args: I) => CeloTxObject @@ -24,6 +25,10 @@ type EventsEnum = { /** Base ContractWrapper */ export abstract class BaseWrapper { + protected _version?: T['methods'] extends ICeloVersionedContract['methods'] + ? ContractVersion + : never + constructor(protected readonly kit: ContractKit, protected readonly contract: T) {} /** Contract address */ @@ -31,6 +36,21 @@ export abstract class BaseWrapper { return this.contract.options.address } + async version() { + if (!this._version) { + const raw = await this.contract.methods.getVersionNumber().call() + // @ts-ignore conditional type + this._version = ContractVersion.fromRaw(raw) + } + return this._version! + } + + protected async onlyVersionOrGreater(version: ContractVersion) { + if (!(await this.version()).isAtLeast(version)) { + throw new Error(`Bytecode version ${this._version} is not compatible with ${version} yet`) + } + } + /** Contract getPastEvents */ public getPastEvents(event: Events, options: PastEventOptions): Promise { return this.contract.getPastEvents(event as string, options) @@ -123,11 +143,23 @@ export function secondsToDurationString( export const blocksToDurationString = (input: BigNumber.Value) => secondsToDurationString(valueToBigNumber(input).times(5)) // TODO: fetch blocktime -export const unixSecondsTimestampToDateString = (input: BigNumber.Value) => - moment.unix(valueToInt(input)).local().format('llll [UTC]Z') +const DATE_TIME_OPTIONS = { + year: 'numeric', + month: 'short', + weekday: 'short', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + timeZoneName: 'short', +} as const + +export const unixSecondsTimestampToDateString = (input: BigNumber.Value) => { + const date = new Date(valueToInt(input) * TimeDurations.second) + return Intl.DateTimeFormat('default', DATE_TIME_OPTIONS).format(date) +} -// Type of bytes in solidity gets repesented as a string of number array by typechain and web3 -// Hopefull this will improve in the future, at which point we can make improvements here +// Type of bytes in solidity gets represented as a string of number array by typechain and web3 +// Hopefully this will improve in the future, at which point we can make improvements here type SolidityBytes = string | number[] export const stringToSolidityBytes = (input: string) => ensureLeading0x(input) as SolidityBytes export const bufferToSolidityBytes = (input: Buffer) => stringToSolidityBytes(bufferToHex(input)) diff --git a/packages/sdk/contractkit/src/wrappers/GrandaMento.test.ts b/packages/sdk/contractkit/src/wrappers/GrandaMento.test.ts new file mode 100644 index 00000000000..98c5ebd95a5 --- /dev/null +++ b/packages/sdk/contractkit/src/wrappers/GrandaMento.test.ts @@ -0,0 +1,160 @@ +import { Address } from '@celo/base/lib/address' +import { NetworkConfig, testWithGanache, timeTravel } from '@celo/dev-utils/lib/ganache-test' +import BigNumber from 'bignumber.js' +import Web3 from 'web3' +import { StableToken } from '../celo-tokens' +import { newKitFromWeb3 } from '../kit' +import { assumeOwnership } from '../test-utils/transferownership' +import { GoldTokenWrapper } from './GoldTokenWrapper' +import { ExchangeProposalState, GrandaMentoWrapper } from './GrandaMento' +import { StableTokenWrapper } from './StableTokenWrapper' + +const expConfig = NetworkConfig.grandaMento + +testWithGanache('GrandaMento Wrapper', (web3: Web3) => { + const kit = newKitFromWeb3(web3) + let accounts: Address[] = [] + let grandaMento: GrandaMentoWrapper + let celoToken: GoldTokenWrapper + let stableToken: StableTokenWrapper + const newLimitMin = new BigNumber('1000') + const newLimitMax = new BigNumber('1000000000000') + let sellAmount: BigNumber + + beforeAll(async () => { + accounts = await web3.eth.getAccounts() + kit.defaultAccount = accounts[0] + grandaMento = await kit.contracts.getGrandaMento() + + stableToken = await kit.contracts.getStableToken(StableToken.cUSD) + celoToken = await kit.contracts.getGoldToken() + }) + + const increaseLimits = async () => { + await ( + await grandaMento.setStableTokenExchangeLimits( + 'StableToken', + newLimitMin.toString(), + newLimitMax.toString() + ) + ).sendAndWaitForReceipt() + } + + describe('No limits sets', () => { + it('gets the proposals', async () => { + const activeProposals = await grandaMento.getActiveProposalIds() + expect(activeProposals).toEqual([]) + }) + + it('fetches empty limits', async () => { + const limits = await grandaMento.stableTokenExchangeLimits(StableToken.cUSD) + expect(limits.minExchangeAmount).toEqBigNumber(new BigNumber(0)) + expect(limits.maxExchangeAmount).toEqBigNumber(new BigNumber(0)) + }) + }) + + it("fetchs a proposal it doesn't exist", async () => { + await expect(grandaMento.getExchangeProposal(0)).rejects.toThrow("Proposal doesn't exist") + }) + + describe('When Granda Mento is enabled', () => { + beforeEach(async () => { + await assumeOwnership(web3, accounts[0]) + await increaseLimits() + }) + + it('updated the config', async () => { + const config = await grandaMento.getConfig() + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cUSD))?.minExchangeAmount + ).toEqBigNumber(new BigNumber(newLimitMin)) + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cUSD))?.maxExchangeAmount + ).toEqBigNumber(new BigNumber(newLimitMax)) + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cEUR))?.minExchangeAmount + ).toEqBigNumber(new BigNumber(0)) + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cEUR))?.maxExchangeAmount + ).toEqBigNumber(new BigNumber(0)) + }) + + it('has new limits', async () => { + const limits = await grandaMento.stableTokenExchangeLimits(StableToken.cUSD) + expect(limits.minExchangeAmount).toEqBigNumber(newLimitMin) + expect(limits.maxExchangeAmount).toEqBigNumber(newLimitMax) + }) + + describe('Has a proposal', () => { + beforeEach(async () => { + sellAmount = new BigNumber('100000000') + await ( + await celoToken.increaseAllowance(grandaMento.address, sellAmount) + ).sendAndWaitForReceipt() + + await ( + await grandaMento.createExchangeProposal( + kit.celoTokens.getContract(StableToken.cUSD), + sellAmount, + true + ) + ).sendAndWaitForReceipt() + }) + + it('executes', async () => { + const activeProposals = await grandaMento.getActiveProposalIds() + expect(activeProposals).not.toEqual([]) + + let proposal = await grandaMento.getExchangeProposal(activeProposals[0]) + expect(proposal.exchanger).toEqual(accounts[0]) + expect(proposal.stableToken).toEqual(stableToken.address) + expect(proposal.sellAmount).toEqBigNumber(sellAmount) + expect(proposal.buyAmount).toEqBigNumber(new BigNumber('99000000')) + expect(proposal.approvalTimestamp).toEqual(new BigNumber(0)) + expect(proposal.state).toEqual(ExchangeProposalState.Proposed) + expect(proposal.sellCelo).toEqual(true) + + await ( + await grandaMento.approveExchangeProposal(activeProposals[0]) + ).sendAndWaitForReceipt() + + proposal = await grandaMento.getExchangeProposal(activeProposals[0]) + + expect(proposal.state).toEqual(ExchangeProposalState.Approved) + await timeTravel(expConfig.vetoPeriodSeconds, web3) + await ( + await grandaMento.executeExchangeProposal(activeProposals[0]) + ).sendAndWaitForReceipt() + + proposal = await grandaMento.getExchangeProposal(activeProposals[0]) + expect(proposal.state).toEqual(ExchangeProposalState.Executed) + }) + + it('cancels proposal', async () => { + await (await grandaMento.cancelExchangeProposal(1)).sendAndWaitForReceipt() + + const proposal = await grandaMento.getExchangeProposal('1') + expect(proposal.state).toEqual(ExchangeProposalState.Cancelled) + }) + }) + }) + + it('#getConfig', async () => { + const config = await grandaMento.getConfig() + // expect(config.approver).toBe(expConfig.approver) // TODO FIX this tests, for some reason `expConfig.approver` is 0x0000...0 even it's writen on the migrations-override.json + expect(config.spread).toEqBigNumber(expConfig.spread) + expect(config.vetoPeriodSeconds).toEqBigNumber(expConfig.vetoPeriodSeconds) + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cUSD))?.minExchangeAmount + ).toEqBigNumber(new BigNumber(0)) + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cUSD))?.maxExchangeAmount + ).toEqBigNumber(new BigNumber(0)) + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cEUR))?.minExchangeAmount + ).toEqBigNumber(new BigNumber(0)) + expect( + config.exchangeLimits.get(kit.celoTokens.getContract(StableToken.cEUR))?.maxExchangeAmount + ).toEqBigNumber(new BigNumber(0)) + }) +}) diff --git a/packages/sdk/contractkit/src/wrappers/GrandaMento.ts b/packages/sdk/contractkit/src/wrappers/GrandaMento.ts new file mode 100644 index 00000000000..1805e323f3e --- /dev/null +++ b/packages/sdk/contractkit/src/wrappers/GrandaMento.ts @@ -0,0 +1,142 @@ +import BigNumber from 'bignumber.js' +import { StableTokenContract } from '../base' +import { StableToken } from '../celo-tokens' +import { GrandaMento } from '../generated/GrandaMento' +import { + BaseWrapper, + fixidityValueToBigNumber, + proxyCall, + proxySend, + valueToBigNumber, +} from './BaseWrapper' + +export enum ExchangeProposalState { + None, + Proposed, + Approved, + Executed, + Cancelled, +} + +export interface GrandaMentoConfig { + approver: string + spread: BigNumber + vetoPeriodSeconds: BigNumber + exchangeLimits: AllStableConfig +} + +export interface StableTokenExchangeLimits { + minExchangeAmount: BigNumber + maxExchangeAmount: BigNumber +} + +export interface ExchangeProposal { + exchanger: string + stableToken: string + sellAmount: BigNumber + buyAmount: BigNumber + approvalTimestamp: BigNumber + state: ExchangeProposalState + sellCelo: boolean +} + +type AllStableConfig = Map + +export class GrandaMentoWrapper extends BaseWrapper { + approver = proxyCall(this.contract.methods.approver) + + spread = proxyCall(this.contract.methods.spread, undefined, fixidityValueToBigNumber) + + vetoPeriodSeconds = proxyCall( + this.contract.methods.vetoPeriodSeconds, + undefined, + valueToBigNumber + ) + + owner = proxyCall(this.contract.methods.owner) + + getActiveProposalIds = proxyCall(this.contract.methods.getActiveProposalIds) + + setStableTokenExchangeLimits = proxySend( + this.kit, + this.contract.methods.setStableTokenExchangeLimits + ) + + approveExchangeProposal = proxySend(this.kit, this.contract.methods.approveExchangeProposal) + + executeExchangeProposal = proxySend(this.kit, this.contract.methods.executeExchangeProposal) + cancelExchangeProposal = proxySend(this.kit, this.contract.methods.cancelExchangeProposal) + + async createExchangeProposal( + stableTokenRegistryId: StableTokenContract, + sellAmount: BigNumber, + sellCelo: true + ) { + const createExchangeProposalInner = proxySend( + this.kit, + this.contract.methods.createExchangeProposal + ) + return createExchangeProposalInner(stableTokenRegistryId, sellAmount.toNumber(), sellCelo) + } + + async getExchangeProposal(exchangeProposalID: string | number): Promise { + const result = await this.contract.methods.exchangeProposals(exchangeProposalID).call() + const state = parseInt(result.state, 10) + + if (state === ExchangeProposalState.None) { + throw new Error("Proposal doesn't exist") + } + + return { + exchanger: result.exchanger, + stableToken: result.stableToken, + sellAmount: new BigNumber(result.sellAmount), + buyAmount: new BigNumber(result.buyAmount), + approvalTimestamp: new BigNumber(result.approvalTimestamp), + sellCelo: result.sellCelo, + state, + } + } + + async stableTokenExchangeLimits( + stableTokenTymbol: StableToken + ): Promise { + const stableTokenRegistryId = this.kit.celoTokens.getContract(stableTokenTymbol) + const result = await this.contract.methods + .stableTokenExchangeLimits(stableTokenRegistryId.toString()) + .call() + return { + minExchangeAmount: new BigNumber(result.minExchangeAmount), + maxExchangeAmount: new BigNumber(result.maxExchangeAmount), + } + } + + async getAllStableTokenLimits(): Promise { + const out: AllStableConfig = new Map() + + const res = await Promise.all( + Object.values(StableToken).map((key) => this.stableTokenExchangeLimits(key)) + ) + + Object.values(StableToken).map((key, index) => + out.set(this.kit.celoTokens.getContract(key), res[index]) + ) + + return out + } + + async getConfig(): Promise { + const res = await Promise.all([ + this.approver(), + this.spread(), + this.vetoPeriodSeconds(), + this.getAllStableTokenLimits(), + ]) + return { + approver: res[0], + spread: res[1], + vetoPeriodSeconds: res[2], + exchangeLimits: res[3], + } + } +} diff --git a/packages/sdk/contractkit/src/wrappers/Reserve.test.ts b/packages/sdk/contractkit/src/wrappers/Reserve.test.ts index 83242482536..aad8eba6922 100644 --- a/packages/sdk/contractkit/src/wrappers/Reserve.test.ts +++ b/packages/sdk/contractkit/src/wrappers/Reserve.test.ts @@ -1,4 +1,5 @@ import { testWithGanache } from '@celo/dev-utils/lib/ganache-test' +import BigNumber from 'bignumber.js' import { newKitFromWeb3 } from '../kit' import { MultiSigWrapper } from './MultiSig' import { ReserveWrapper } from './Reserve' @@ -22,6 +23,23 @@ testWithGanache('Reserve Wrapper', (web3) => { reserveSpenderMultiSig = await kit.contracts.getMultiSig(multiSigAddress) }) + test('can get asset target weights which sum to 100%', async () => { + const targets = await reserve.getAssetAllocationWeights() + expect(targets.reduce((total, current) => total.plus(current), new BigNumber(0))).toEqual( + new BigNumber(100 * 10_000_000_000_000_000_000_000) + ) + }) + + test('can get asset target symbols ', async () => { + const targets = await reserve.getAssetAllocationSymbols() + + const expectation = ['cGLD', 'BTC', 'ETH', 'DAI'] + + targets.forEach((sym, i) => { + expect(sym).toEqual(expect.stringMatching(expectation[i])) + }) + }) + test('can get reserve unfrozen balance ', async () => { const balance = await reserve.getUnfrozenBalance() expect(balance).toEqBigNumber('1e+26') diff --git a/packages/sdk/contractkit/src/wrappers/Reserve.ts b/packages/sdk/contractkit/src/wrappers/Reserve.ts index fb7f7932651..fcc9a43ef34 100644 --- a/packages/sdk/contractkit/src/wrappers/Reserve.ts +++ b/packages/sdk/contractkit/src/wrappers/Reserve.ts @@ -54,6 +54,26 @@ export class ReserveWrapper extends BaseWrapper { valueToBigNumber ) + /** + * @notice Returns a list of weights used for the allocation of reserve assets. + * @return An array of a list of weights used for the allocation of reserve assets. + */ + getAssetAllocationWeights = proxyCall( + this.contract.methods.getAssetAllocationWeights, + undefined, + (weights) => weights.map(valueToBigNumber) + ) + + /** + * @notice Returns a list of token symbols that have been allocated. + * @return An array of token symbols that have been allocated. + */ + getAssetAllocationSymbols = proxyCall( + this.contract.methods.getAssetAllocationSymbols, + undefined, + (symbols) => symbols.map(this.kit.web3.utils.hexToAscii) + ) + /** * @alias {getReserveCeloBalance} */ diff --git a/packages/sdk/contractkit/src/wrappers/StableTokenWrapper.ts b/packages/sdk/contractkit/src/wrappers/StableTokenWrapper.ts index d45683bc298..d56bf33335f 100644 --- a/packages/sdk/contractkit/src/wrappers/StableTokenWrapper.ts +++ b/packages/sdk/contractkit/src/wrappers/StableTokenWrapper.ts @@ -123,6 +123,7 @@ export class StableTokenWrapper extends CeloTokenWrapper { async getHumanReadableConfig() { const config = await this.getConfig() const inflationParameters = { + ...config.inflationParameters, updatePeriod: secondsToDurationString(config.inflationParameters.updatePeriod), factorLastUpdated: unixSecondsTimestampToDateString( config.inflationParameters.factorLastUpdated diff --git a/packages/sdk/dappkit/package.json b/packages/sdk/dappkit/package.json index f180779488d..109943d9ece 100644 --- a/packages/sdk/dappkit/package.json +++ b/packages/sdk/dappkit/package.json @@ -1,15 +1,15 @@ { "name": "@celo/dappkit", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "scripts": { "build": "tsc", "clean": "tsc -b . --clean", "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/connect": "1.2.2-dev", - "@celo/contractkit": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", + "@celo/connect": "1.2.3-dev", + "@celo/contractkit": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", "react-native": "^0.63.4" }, "devDependencies": { diff --git a/packages/sdk/explorer/package.json b/packages/sdk/explorer/package.json index f968ff87172..0135e2a2e57 100644 --- a/packages/sdk/explorer/package.json +++ b/packages/sdk/explorer/package.json @@ -1,6 +1,6 @@ { "name": "@celo/explorer", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Celo's block explorer consumer", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -23,9 +23,9 @@ }, "dependencies": { "@types/debug": "^4.1.5", - "@celo/base": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", - "@celo/contractkit": "1.2.2-dev", + "@celo/base": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", + "@celo/contractkit": "1.2.3-dev", "debug": "^4.1.1" }, "devDependencies": { diff --git a/packages/sdk/governance/package.json b/packages/sdk/governance/package.json index 38fef9679c3..5018dd94ed0 100644 --- a/packages/sdk/governance/package.json +++ b/packages/sdk/governance/package.json @@ -1,6 +1,6 @@ { "name": "@celo/governance", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Celo's governance proposals", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -21,11 +21,11 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/base": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", - "@celo/contractkit": "1.2.2-dev", - "@celo/explorer": "1.2.2-dev", + "@celo/base": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", + "@celo/contractkit": "1.2.3-dev", + "@celo/explorer": "1.2.3-dev", "@types/debug": "^4.1.5", "@types/inquirer": "^6.5.0", "@types/ethereumjs-util": "^5.2.0", diff --git a/packages/sdk/identity/package.json b/packages/sdk/identity/package.json index 80e7d4a0687..41f58e35312 100644 --- a/packages/sdk/identity/package.json +++ b/packages/sdk/identity/package.json @@ -1,6 +1,6 @@ { "name": "@celo/identity", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Utilities for interacting with Celo's identity protocol", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -25,9 +25,9 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/base": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/contractkit": "1.2.2-dev", + "@celo/base": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/contractkit": "1.2.3-dev", "@types/debug": "^4.1.5", "bignumber.js": "^9.0.0", "blind-threshold-bls": "https://github.com/celo-org/blind-threshold-bls-wasm#e1e2f8a", @@ -39,7 +39,7 @@ }, "devDependencies": { "@celo/dev-utils": "0.0.1-dev", - "@celo/wallet-local": "1.2.2-dev", + "@celo/wallet-local": "1.2.3-dev", "fetch-mock": "9.10.4", "@types/elliptic": "^6.4.12", "@celo/flake-tracker": "0.0.1-dev", diff --git a/packages/sdk/identity/src/odis/bls-blinding-client.ts b/packages/sdk/identity/src/odis/bls-blinding-client.ts index 9f0c688bc4b..c406b6ca1ec 100644 --- a/packages/sdk/identity/src/odis/bls-blinding-client.ts +++ b/packages/sdk/identity/src/odis/bls-blinding-client.ts @@ -1,7 +1,7 @@ import { randomBytes } from 'crypto' export interface BlsBlindingClient { - blindMessage: (base64PhoneNumber: string) => Promise + blindMessage: (base64PhoneNumber: string, seed?: Uint8Array) => Promise unblindAndVerifyMessage: (blindedMessage: string) => Promise } @@ -36,8 +36,16 @@ export class WasmBlsBlindingClient implements BlsBlindingClient { } } - async blindMessage(base64PhoneNumber: string): Promise { - const userSeed = randomBytes(32) + async blindMessage(base64PhoneNumber: string, seed?: Uint8Array): Promise { + let userSeed + if (!seed) { + userSeed = randomBytes(32) + console.warn( + 'Warning: Use a private deterministic seed (i.e. DEK private key) to preserve user quota when requests are replayed.' + ) + } else { + userSeed = seed + } this.rawMessage = Buffer.from(base64PhoneNumber, 'base64') this.blindedValue = await this.thresholdBls.blind(this.rawMessage, userSeed) const blindedMessage = this.blindedValue.message diff --git a/packages/sdk/identity/src/odis/phone-number-identifier.ts b/packages/sdk/identity/src/odis/phone-number-identifier.ts index 542a148bf38..5315af5669b 100644 --- a/packages/sdk/identity/src/odis/phone-number-identifier.ts +++ b/packages/sdk/identity/src/odis/phone-number-identifier.ts @@ -49,6 +49,7 @@ export async function getPhoneNumberIdentifier( throw new Error(`Invalid phone number: ${e164Number}`) } // Fallback to using Wasm version if not specified + if (!blsBlindingClient) { debug('No BLSBlindingClient found, using WasmBlsBlindingClient') blsBlindingClient = new WasmBlsBlindingClient(context.odisPubKey) @@ -98,7 +99,6 @@ export async function getBlindedPhoneNumberSignature( ): Promise { const body: SignMessageRequest = { account, - timestamp: Date.now(), blindedQueryPhoneNumber: base64BlindedMessage, hashedPhoneNumber: selfPhoneHash, version: clientVersion ? clientVersion : 'unknown', diff --git a/packages/sdk/identity/src/odis/query.ts b/packages/sdk/identity/src/odis/query.ts index d3a02c89edf..4989d3d8b33 100644 --- a/packages/sdk/identity/src/odis/query.ts +++ b/packages/sdk/identity/src/odis/query.ts @@ -43,7 +43,6 @@ export interface PhoneNumberPrivacyRequest { export interface SignMessageRequest extends PhoneNumberPrivacyRequest { blindedQueryPhoneNumber: string - timestamp?: number hashedPhoneNumber?: string } diff --git a/packages/sdk/keystores/package.json b/packages/sdk/keystores/package.json index 7e512efef07..c8aa8dec988 100644 --- a/packages/sdk/keystores/package.json +++ b/packages/sdk/keystores/package.json @@ -1,6 +1,6 @@ { "name": "@celo/keystores", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "keystore implementation", "author": "Celo", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/utils": "1.2.2-dev", - "@celo/wallet-local": "1.2.2-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-local": "1.2.3-dev", "@types/ethereumjs-util": "^5.2.0", "ethereumjs-wallet": "^1.0.1" }, diff --git a/packages/sdk/network-utils/package.json b/packages/sdk/network-utils/package.json index 1f9921688f1..584e228aed4 100644 --- a/packages/sdk/network-utils/package.json +++ b/packages/sdk/network-utils/package.json @@ -1,6 +1,6 @@ { "name": "@celo/network-utils", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Utilities for fetching static information about the Celo network", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/sdk/transactions-uri/package.json b/packages/sdk/transactions-uri/package.json index 06b319e11bb..281ea0fc4a7 100644 --- a/packages/sdk/transactions-uri/package.json +++ b/packages/sdk/transactions-uri/package.json @@ -1,6 +1,6 @@ { "name": "@celo/transactions-uri", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Celo's transactions uri generation", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -26,15 +26,15 @@ "dependencies": { "@types/debug": "^4.1.5", "@types/qrcode": "^1.3.4", - "@celo/base": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", + "@celo/base": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", "bn.js": "4.11.8", "qrcode": "^1.4.4", "web3-eth-abi": "1.3.6" }, "devDependencies": { "@celo/dev-utils": "0.0.1-dev", - "@celo/contractkit": "1.2.2-dev", + "@celo/contractkit": "1.2.3-dev", "dotenv": "^8.2.0", "@celo/flake-tracker": "0.0.1-dev" }, diff --git a/packages/sdk/utils/package.json b/packages/sdk/utils/package.json index 64620fa1fc0..8d4c8a61cf8 100644 --- a/packages/sdk/utils/package.json +++ b/packages/sdk/utils/package.json @@ -1,6 +1,6 @@ { "name": "@celo/utils", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Celo common utils", "author": "Celo", "license": "Apache-2.0", @@ -19,7 +19,7 @@ "lib/**/*" ], "dependencies": { - "@celo/base": "1.2.2-dev", + "@celo/base": "1.2.3-dev", "@types/country-data": "^0.0.0", "@types/elliptic": "^6.4.9", "@types/ethereumjs-util": "^5.2.0", diff --git a/packages/sdk/utils/src/io.ts b/packages/sdk/utils/src/io.ts index ea5e6ed349d..5b4bf3ebd8e 100644 --- a/packages/sdk/utils/src/io.ts +++ b/packages/sdk/utils/src/io.ts @@ -90,6 +90,7 @@ export const AttestationServiceStatusResponseType = t.type({ smsProvidersRandomized: t.boolean, maxDeliveryAttempts: t.number, maxRerequestMins: t.number, + twilioVerifySidProvided: t.boolean, }) export const AttestationServiceTestRequestType = t.type({ diff --git a/packages/sdk/wallets/wallet-base/package.json b/packages/sdk/wallets/wallet-base/package.json index 577284d3e83..e7603f5c5ec 100644 --- a/packages/sdk/wallets/wallet-base/package.json +++ b/packages/sdk/wallets/wallet-base/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-base", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Wallet base implementation", "author": "Celo", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/connect": "1.2.2-dev", - "@celo/base": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", + "@celo/connect": "1.2.3-dev", + "@celo/base": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", "@types/ethereumjs-util": "^5.2.0", "@types/debug": "^4.1.5", "bignumber.js": "^9.0.0", diff --git a/packages/sdk/wallets/wallet-hsm-aws/package.json b/packages/sdk/wallets/wallet-hsm-aws/package.json index eba766452e2..9731fbd7188 100644 --- a/packages/sdk/wallets/wallet-hsm-aws/package.json +++ b/packages/sdk/wallets/wallet-hsm-aws/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-hsm-aws", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "AWS HSM wallet implementation", "author": "Celo", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/utils": "1.2.2-dev", - "@celo/wallet-base": "1.2.2-dev", - "@celo/wallet-remote": "1.2.2-dev", - "@celo/wallet-hsm": "1.2.2-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-base": "1.2.3-dev", + "@celo/wallet-remote": "1.2.3-dev", + "@celo/wallet-hsm": "1.2.3-dev", "@types/debug": "^4.1.5", "@types/secp256k1": "^4.0.0", "aws-sdk": "^2.705.0", @@ -34,7 +34,7 @@ "secp256k1": "^4.0.0" }, "devDependencies": { - "@celo/connect": "1.2.2-dev", + "@celo/connect": "1.2.3-dev", "elliptic": "^6.5.4", "web3": "1.3.6" }, diff --git a/packages/sdk/wallets/wallet-hsm-azure/package.json b/packages/sdk/wallets/wallet-hsm-azure/package.json index 8c0094594c9..825710e46cf 100644 --- a/packages/sdk/wallets/wallet-hsm-azure/package.json +++ b/packages/sdk/wallets/wallet-hsm-azure/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-hsm-azure", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Azure HSM wallet implementation", "author": "Celo", "license": "Apache-2.0", @@ -23,11 +23,11 @@ "@azure/identity": "^1.1.0", "@azure/keyvault-keys": "^4.1.0", "@azure/keyvault-secrets": "^4.1.0", - "@celo/utils": "1.2.2-dev", - "@celo/wallet-base": "1.2.2-dev", - "@celo/wallet-remote": "1.2.2-dev", - "@celo/wallet-hsm": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-base": "1.2.3-dev", + "@celo/wallet-remote": "1.2.3-dev", + "@celo/wallet-hsm": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", "@types/secp256k1": "^4.0.0", "eth-lib": "^0.2.8", "ethereumjs-util": "^5.2.0", diff --git a/packages/sdk/wallets/wallet-hsm/package.json b/packages/sdk/wallets/wallet-hsm/package.json index 6db1099d600..76eff8bd1bf 100644 --- a/packages/sdk/wallets/wallet-hsm/package.json +++ b/packages/sdk/wallets/wallet-hsm/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-hsm", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "HSM wallet implementation utils", "author": "Celo", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/base": "1.2.2-dev", + "@celo/base": "1.2.3-dev", "@types/asn1js": "^0.0.2", "@types/secp256k1": "^4.0.0", "@types/debug": "^4.1.5", diff --git a/packages/sdk/wallets/wallet-ledger/package.json b/packages/sdk/wallets/wallet-ledger/package.json index 41c3d45dc5c..ecd65dba1c0 100644 --- a/packages/sdk/wallets/wallet-ledger/package.json +++ b/packages/sdk/wallets/wallet-ledger/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-ledger", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Ledger wallet implementation", "author": "Celo", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/utils": "1.2.2-dev", - "@celo/wallet-base": "1.2.2-dev", - "@celo/wallet-remote": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-base": "1.2.3-dev", + "@celo/wallet-remote": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", "@types/ethereumjs-util": "^5.2.0", "eth-lib": "^0.2.8", "ethereumjs-util": "^5.2.0", diff --git a/packages/sdk/wallets/wallet-local/package.json b/packages/sdk/wallets/wallet-local/package.json index cca5117a460..7daa6e57e35 100644 --- a/packages/sdk/wallets/wallet-local/package.json +++ b/packages/sdk/wallets/wallet-local/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-local", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Local wallet implementation", "author": "Celo", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/utils": "1.2.2-dev", - "@celo/connect": "1.2.2-dev", - "@celo/wallet-base": "1.2.2-dev", + "@celo/utils": "1.2.3-dev", + "@celo/connect": "1.2.3-dev", + "@celo/wallet-base": "1.2.3-dev", "@types/ethereumjs-util": "^5.2.0", "eth-lib": "^0.2.8", "ethereumjs-util": "^5.2.0" diff --git a/packages/sdk/wallets/wallet-remote/package.json b/packages/sdk/wallets/wallet-remote/package.json index 139baed71e5..5df279ee48d 100644 --- a/packages/sdk/wallets/wallet-remote/package.json +++ b/packages/sdk/wallets/wallet-remote/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-remote", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Remote wallet implementation", "author": "Celo", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/connect": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/wallet-base": "1.2.2-dev", + "@celo/connect": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-base": "1.2.3-dev", "@types/ethereumjs-util": "^5.2.0", "@types/debug": "^4.1.5", "eth-lib": "^0.2.8", diff --git a/packages/sdk/wallets/wallet-rpc/package.json b/packages/sdk/wallets/wallet-rpc/package.json index 2c5e2898b69..483d855960e 100644 --- a/packages/sdk/wallets/wallet-rpc/package.json +++ b/packages/sdk/wallets/wallet-rpc/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-rpc", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "Geth RPC wallet implementation", "author": "Celo", "license": "Apache-2.0", @@ -20,16 +20,16 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/connect": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/wallet-base": "1.2.2-dev", - "@celo/wallet-remote": "1.2.2-dev", + "@celo/connect": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-base": "1.2.3-dev", + "@celo/wallet-remote": "1.2.3-dev", "bignumber.js": "^9.0.0", "debug": "^4.1.1" }, "devDependencies": { "@celo/dev-utils": "0.0.1-dev", - "@celo/contractkit": "1.2.2-dev" + "@celo/contractkit": "1.2.3-dev" }, "engines": { "node": ">=8.13.0" diff --git a/packages/sdk/wallets/wallet-walletconnect/package.json b/packages/sdk/wallets/wallet-walletconnect/package.json index a53e0f74d13..b75cd098f18 100644 --- a/packages/sdk/wallets/wallet-walletconnect/package.json +++ b/packages/sdk/wallets/wallet-walletconnect/package.json @@ -1,6 +1,6 @@ { "name": "@celo/wallet-walletconnect", - "version": "1.2.2-dev", + "version": "1.2.3-dev", "description": "WalletConnect wallet implementation", "author": "Celo", "license": "Apache-2.0", @@ -24,17 +24,17 @@ "prepublishOnly": "yarn build" }, "dependencies": { - "@celo/connect": "1.2.2-dev", - "@celo/utils": "1.2.2-dev", - "@celo/wallet-base": "1.2.2-dev", - "@celo/wallet-remote": "1.2.2-dev", + "@celo/connect": "1.2.3-dev", + "@celo/utils": "1.2.3-dev", + "@celo/wallet-base": "1.2.3-dev", + "@celo/wallet-remote": "1.2.3-dev", "@walletconnect/client": "2.0.0-beta.9", "@walletconnect/types": "2.0.0-beta.9", "debug": "^4.1.1", "ethereumjs-util": "^7.0.8" }, "devDependencies": { - "@celo/contractkit": "1.2.2-dev", + "@celo/contractkit": "1.2.3-dev", "@celo/dev-utils": "0.0.1-dev" }, "engines": { diff --git a/packages/terraform-modules-public/aws/examples/mainnet/main.tf b/packages/terraform-modules-public/aws/examples/mainnet/main.tf index 6a87320beeb..3ebd6d3442a 100644 --- a/packages/terraform-modules-public/aws/examples/mainnet/main.tf +++ b/packages/terraform-modules-public/aws/examples/mainnet/main.tf @@ -28,6 +28,7 @@ module "celo_cluster" { celo_image_attestation = var.celo_image_attestation ethstats_host = var.ethstats_host twilio_messaging_service_sid = var.twilio_messaging_service_sid + twilio_verify_service_sid = var.twilio_verify_service_sid twilio_account_sid = var.twilio_account_sid twilio_unsupported_regions = var.twilio_unsupported_regions twilio_auth_token = var.twilio_auth_token diff --git a/packages/terraform-modules-public/aws/examples/mainnet/variables.tf b/packages/terraform-modules-public/aws/examples/mainnet/variables.tf index 9829705766c..4c83e420ca0 100644 --- a/packages/terraform-modules-public/aws/examples/mainnet/variables.tf +++ b/packages/terraform-modules-public/aws/examples/mainnet/variables.tf @@ -169,6 +169,10 @@ variable twilio_messaging_service_sid { type = string } +variable twilio_verify_service_sid { + type = string +} + variable twilio_account_sid { type = string } diff --git a/packages/terraform-modules-public/aws/examples/testnet/main.tf b/packages/terraform-modules-public/aws/examples/testnet/main.tf index 6a87320beeb..3ebd6d3442a 100644 --- a/packages/terraform-modules-public/aws/examples/testnet/main.tf +++ b/packages/terraform-modules-public/aws/examples/testnet/main.tf @@ -28,6 +28,7 @@ module "celo_cluster" { celo_image_attestation = var.celo_image_attestation ethstats_host = var.ethstats_host twilio_messaging_service_sid = var.twilio_messaging_service_sid + twilio_verify_service_sid = var.twilio_verify_service_sid twilio_account_sid = var.twilio_account_sid twilio_unsupported_regions = var.twilio_unsupported_regions twilio_auth_token = var.twilio_auth_token diff --git a/packages/terraform-modules-public/aws/examples/testnet/variables.tf b/packages/terraform-modules-public/aws/examples/testnet/variables.tf index aa04487f545..c40a387b605 100644 --- a/packages/terraform-modules-public/aws/examples/testnet/variables.tf +++ b/packages/terraform-modules-public/aws/examples/testnet/variables.tf @@ -169,6 +169,10 @@ variable twilio_messaging_service_sid { type = string } +variable twilio_verify_service_sid { + type = string +} + variable twilio_account_sid { type = string } diff --git a/packages/terraform-modules-public/aws/testnet/main.tf b/packages/terraform-modules-public/aws/testnet/main.tf index e76a5fae7df..1784231e90f 100644 --- a/packages/terraform-modules-public/aws/testnet/main.tf +++ b/packages/terraform-modules-public/aws/testnet/main.tf @@ -184,6 +184,7 @@ module "celo_attestation_service_az1" { celo_image_attestation = var.celo_image_attestation database_url = local.attestation_db_url twilio_messaging_service_sid = var.twilio_messaging_service_sid + twilio_verify_service_sid = var.twilio_verify_service_sid twilio_account_sid = var.twilio_account_sid twilio_unsupported_regions = var.twilio_unsupported_regions twilio_auth_token = var.twilio_auth_token @@ -210,6 +211,7 @@ module "celo_attestation_service_az2" { celo_image_attestation = var.celo_image_attestation database_url = local.attestation_db_url twilio_messaging_service_sid = var.twilio_messaging_service_sid + twilio_verify_service_sid = var.twilio_verify_service_sid twilio_account_sid = var.twilio_account_sid twilio_unsupported_regions = var.twilio_unsupported_regions twilio_auth_token = var.twilio_auth_token diff --git a/packages/terraform-modules-public/aws/testnet/modules/attestation-service/main.tf b/packages/terraform-modules-public/aws/testnet/modules/attestation-service/main.tf index 7dd24cc6a39..7d765c1e966 100644 --- a/packages/terraform-modules-public/aws/testnet/modules/attestation-service/main.tf +++ b/packages/terraform-modules-public/aws/testnet/modules/attestation-service/main.tf @@ -30,6 +30,7 @@ resource "aws_instance" "attestation_service" { attestation_signer_private_key_password = each.value.attestation_signer_private_key_password database_url = var.database_url twilio_messaging_service_sid = var.twilio_messaging_service_sid + twilio_verify_service_sid = var.twilio_verify_service_sid twilio_account_sid = var.twilio_account_sid twilio_unsupported_regions = var.twilio_unsupported_regions twilio_auth_token = var.twilio_auth_token diff --git a/packages/terraform-modules-public/aws/testnet/modules/attestation-service/variables.tf b/packages/terraform-modules-public/aws/testnet/modules/attestation-service/variables.tf index 0f2d240dad7..7b01b1badbd 100644 --- a/packages/terraform-modules-public/aws/testnet/modules/attestation-service/variables.tf +++ b/packages/terraform-modules-public/aws/testnet/modules/attestation-service/variables.tf @@ -44,6 +44,10 @@ variable twilio_messaging_service_sid { type = string } +variable twilio_verify_service_sid { + type = string +} + variable twilio_account_sid { type = string } diff --git a/packages/terraform-modules-public/aws/testnet/modules/startup-scripts/run-attestation-service.sh b/packages/terraform-modules-public/aws/testnet/modules/startup-scripts/run-attestation-service.sh index f5e5251633f..b3a56b162e4 100644 --- a/packages/terraform-modules-public/aws/testnet/modules/startup-scripts/run-attestation-service.sh +++ b/packages/terraform-modules-public/aws/testnet/modules/startup-scripts/run-attestation-service.sh @@ -45,6 +45,7 @@ echo 'NEXMO_UNSUPPORTED_REGIONS=${nexmo_unsupported_regions}' >> $CONFIG_FILE_PA echo 'TWILIO_ACCOUNT_SID=${twilio_account_sid}' >> $CONFIG_FILE_PATH echo 'TWILIO_MESSAGING_SERVICE_SID=${twilio_messaging_service_sid}' >> $CONFIG_FILE_PATH +echo 'TWILIO_VERIFY_SERVICE_SID=${twilio_verify_service_sid}' >> $CONFIG_FILE_PATH echo 'TWILIO_AUTH_TOKEN=${twilio_auth_token}' >> $CONFIG_FILE_PATH echo 'TWILIO_UNSUPPORTED_REGIONS=${twilio_unsupported_regions}' >> $CONFIG_FILE_PATH diff --git a/packages/terraform-modules-public/aws/testnet/variables.tf b/packages/terraform-modules-public/aws/testnet/variables.tf index 8fa1e4eef4b..6d4150e1b3f 100644 --- a/packages/terraform-modules-public/aws/testnet/variables.tf +++ b/packages/terraform-modules-public/aws/testnet/variables.tf @@ -95,6 +95,10 @@ variable twilio_messaging_service_sid { type = string } +variable twilio_verify_service_sid { + type = string +} + variable twilio_account_sid { type = string } diff --git a/packages/terraform-modules-public/gcp/celo-infra/main.tf b/packages/terraform-modules-public/gcp/celo-infra/main.tf index 7a639290d5c..f97db443e9b 100644 --- a/packages/terraform-modules-public/gcp/celo-infra/main.tf +++ b/packages/terraform-modules-public/gcp/celo-infra/main.tf @@ -263,6 +263,7 @@ module "attestation-service" { nexmo_unsupported_regions = var.attestation_service_nexmo_unsupported_regions twilio_account_sid = var.attestation_service_twilio_account_sid twilio_messaging_service_sid = var.attestation_service_twilio_messaging_service_sid + twilio_verify_service_sid = var.attestation_service_twilio_verify_service_sid twilio_auth_token = var.attestation_service_twilio_auth_token twilio_blacklist = var.attestation_service_twilio_blacklist twilio_unsupported_regions = var.attestation_service_twilio_unsupported_regions diff --git a/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/main.tf b/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/main.tf index 8b71c876399..21f07da7843 100644 --- a/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/main.tf +++ b/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/main.tf @@ -78,6 +78,7 @@ resource "google_compute_instance" "attestation_service" { nexmo_unsupported_regions : var.nexmo_unsupported_regions, twilio_account_sid : var.twilio_account_sid, twilio_messaging_service_sid : var.twilio_messaging_service_sid, + twilio_verify_service_sid : var.twilio_verify_service_sid, twilio_auth_token : var.twilio_auth_token, twilio_blacklist : var.twilio_blacklist, twilio_unsupported_regions : var.twilio_unsupported_regions, diff --git a/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/startup.sh b/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/startup.sh index 3e518b1662a..3f03124f275 100644 --- a/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/startup.sh +++ b/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/startup.sh @@ -206,6 +206,7 @@ NEXMO_BLACKLIST='${nexmo_blacklist}' NEXMO_UNSUPPORTED_REGIONS='${nexmo_unsupported_regions}' TWILIO_ACCOUNT_SID='${twilio_account_sid}' TWILIO_MESSAGING_SERVICE_SID='${twilio_messaging_service_sid}' +TWILIO_VERIFY_SERVICE_SID='${twilio_verify_service_sid}' TWILIO_AUTH_TOKEN='${twilio_auth_token}' TWILIO_BLACKLIST='${twilio_blacklist}' TWILIO_UNSUPPORTED_REGIONS='${twilio_unsupported_regions}' @@ -256,6 +257,7 @@ save_variable "$NEXMO_SECRET" "$DATA_DIR/nexmoSecret" save_variable "$NEXMO_BLACKLIST" "$DATA_DIR/nexmoBlacklist" save_variable "$TWILIO_ACCOUNT_SID" "$DATA_DIR/twilioAccountSid" save_variable "$TWILIO_MESSAGING_SERVICE_SID" "$DATA_DIR/twilioMessagingServiceSid" +save_variable "$TWILIO_VERIFY_SERVICE_SID" "$DATA_DIR/twilioVerifyServiceSid" save_variable "$TWILIO_AUTH_TOKEN" "$DATA_DIR/twilioAuthToken" save_variable "$TWILIO_BLACKLIST" "$DATA_DIR/twilioBlacklist" save_variable "$TWILIO_UNSUPPORTED_REGIONS" "$DATA_DIR/twilioUnsupportedRegions" @@ -291,6 +293,7 @@ ExecStart=/usr/bin/docker run \\ -e NEXMO_UNSUPPORTED_REGIONS="$NEXMO_UNSUPPORTED_REGIONS" \\ -e TWILIO_ACCOUNT_SID="$TWILIO_ACCOUNT_SID" \\ -e TWILIO_MESSAGING_SERVICE_SID="$TWILIO_MESSAGING_SERVICE_SID" \\ + -e TWILIO_VERIFY_SERVICE_SID="$TWILIO_VERIFY_SERVICE_SID" \\ -e TWILIO_AUTH_TOKEN="$TWILIO_AUTH_TOKEN" \\ -e TWILIO_BLACKLIST="$TWILIO_BLACKLIST" \\ -e TWILIO_UNSUPPORTED_REGIONS="$TWILIO_UNSUPPORTED_REGIONS" \\ diff --git a/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/variables.tf b/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/variables.tf index 1f57aea1aa6..db03e5aab62 100644 --- a/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/variables.tf +++ b/packages/terraform-modules-public/gcp/celo-infra/modules/attestation-service/variables.tf @@ -106,7 +106,12 @@ variable twilio_account_sid { variable twilio_messaging_service_sid { type = string - description = "Twilio account messagin service SID (check twilio documentation)" + description = "Twilio account messaging service SID (check twilio documentation)" +} + +variable twilio_verify_service_sid { + type = string + description = "Twilio account verify service SID (check twilio documentation)" } variable twilio_auth_token { diff --git a/packages/terraform-modules-public/gcp/celo-infra/variables.tf b/packages/terraform-modules-public/gcp/celo-infra/variables.tf index e7cc5c11c4d..30e300709d0 100644 --- a/packages/terraform-modules-public/gcp/celo-infra/variables.tf +++ b/packages/terraform-modules-public/gcp/celo-infra/variables.tf @@ -234,7 +234,13 @@ variable attestation_service_twilio_account_sid { variable attestation_service_twilio_messaging_service_sid { type = string - description = "Twilio account messagin service SID (check twilio documentation)" + description = "Twilio account messaging service SID (check twilio documentation)" + default = "" +} + +variable attestation_service_twilio_verify_service_sid { + type = string + description = "Twilio account verify service SID (check twilio documentation)" default = "" } diff --git a/packages/terraform-modules-public/gcp/example/README.md b/packages/terraform-modules-public/gcp/example/README.md index 835145dcda6..ec311f55364 100644 --- a/packages/terraform-modules-public/gcp/example/README.md +++ b/packages/terraform-modules-public/gcp/example/README.md @@ -132,6 +132,7 @@ nexmo_blacklist = "" twilio_account_sid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" twilio_messaging_service_sid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + twilio_verify_service_sid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" twilio_auth_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" twilio_blacklist = "" } diff --git a/packages/terraform-modules-public/gcp/example/main.tf b/packages/terraform-modules-public/gcp/example/main.tf index 027d28e69c0..b3bce24d4a8 100644 --- a/packages/terraform-modules-public/gcp/example/main.tf +++ b/packages/terraform-modules-public/gcp/example/main.tf @@ -115,6 +115,7 @@ module "celo_cluster" { attestation_service_nexmo_unsupported_regions = var.attestation_service_credentials["nexmo_unsupported_regions"] attestation_service_twilio_account_sid = var.attestation_service_credentials["twilio_account_sid"] attestation_service_twilio_messaging_service_sid = var.attestation_service_credentials["twilio_messaging_service_sid"] + attestation_service_twilio_verify_service_sid = var.attestation_service_credentials["twilio_verify_service_sid"] attestation_service_twilio_auth_token = var.attestation_service_credentials["twilio_auth_token"] attestation_service_twilio_blacklist = var.attestation_service_credentials["twilio_blacklist"] attestation_service_twilio_unsupported_regions = var.attestation_service_credentials["twilio_unsupported_regions"] diff --git a/packages/terraform-modules-public/gcp/example/terraform.tfvars.example b/packages/terraform-modules-public/gcp/example/terraform.tfvars.example index 82f2d51b382..b009532fa7c 100644 --- a/packages/terraform-modules-public/gcp/example/terraform.tfvars.example +++ b/packages/terraform-modules-public/gcp/example/terraform.tfvars.example @@ -71,6 +71,7 @@ attestation_service_credentials = { nexmo_unsupported_regions = "CU,SY,KP,IR,SD" twilio_account_sid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" twilio_messaging_service_sid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + twilio_verify_service_sid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" twilio_auth_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" twilio_blacklist = "CU,SY,KP,IR,SD" twilio_unsupported_regions = "CU,SY,KP,IR,SD" diff --git a/packages/terraform-modules-public/gcp/example/variables.tf b/packages/terraform-modules-public/gcp/example/variables.tf index 639512f3bf8..cd51c5fd375 100644 --- a/packages/terraform-modules-public/gcp/example/variables.tf +++ b/packages/terraform-modules-public/gcp/example/variables.tf @@ -189,6 +189,7 @@ variable attestation_service_credentials { nexmo_unsupported_regions = "CU,SY,KP,IR,SD" twilio_account_sid = "secret in terraform.tfvars" twilio_messaging_service_sid = "secret in terraform.tfvars" + twilio_verify_service_sid = "secret in terraform.tfvars" twilio_auth_token = "secret in terraform.tfvars" twilio_blacklist = "CU,SY,KP,IR,SD,BY,TD,CZ,EG,ID,IL,CI,JP,JO,KZ,KE,KW,LB,MW,MX,MA,NP,NG,OM,PK,PS,PH,QA,RU,SA,LK,TZ,TH,TN,TR,AE,UA,VN,ZM,ZW" twilio_unsupported_regions = "CU,SY,KP,IR,SD,BY,TD,CZ,EG,ID,IL,CI,JP,JO,KZ,KE,KW,LB,MW,MX,MA,NP,NG,OM,PK,PS,PH,QA,RU,SA,LK,TZ,TH,TN,TR,AE,UA,VN,ZM,ZW" diff --git a/scripts/run-docker-validator-network.sh b/scripts/run-docker-validator-network.sh index ee088676b41..caceebde22c 100755 --- a/scripts/run-docker-validator-network.sh +++ b/scripts/run-docker-validator-network.sh @@ -21,6 +21,7 @@ export VALIDATOR_GROUP_NAME=tijuana_$(cat /dev/urandom | tr -dc 'a-z0-9' | fold export CELOCLI="npx celocli" export TWILIO_MESSAGING_SERVICE_SID=MG00000000000000000000000000000000 +export TWILIO_VERIFY_SERVICE_SID=VA00000000000000000000000000000000 export TWILIO_ACCOUNT_SID=AC00000000000000000000000000000000 export TWILIO_BLACKLIST="" export TWILIO_AUTH_TOKEN="ffffffffffffffffffffffffffffffff" @@ -288,7 +289,7 @@ if [[ $COMMAND == *"run-attestation"* ]]; then export CELO_PROVIDER=http://localhost:8545 echo -e "\tStarting attestation service" - screen -S celo-attestation-service -d -m docker run --name celo-attestation-service -it --restart always --entrypoint /bin/bash --network host -e ATTESTATION_SIGNER_ADDRESS=0x$CELO_ATTESTATION_SIGNER_ADDRESS -e CELO_VALIDATOR_ADDRESS=0x$CELO_VALIDATOR_ADDRESS -e CELO_PROVIDER=$CELO_PROVIDER -e DATABASE_URL=$DATABASE_URL -e SMS_PROVIDERS=twilio -e TWILIO_MESSAGING_SERVICE_SID=$TWILIO_MESSAGING_SERVICE_SID -e TWILIO_ACCOUNT_SID=$TWILIO_ACCOUNT_SID -e TWILIO_BLACKLIST=$TWILIO_BLACKLIST -e TWILIO_AUTH_TOKEN=$TWILIO_AUTH_TOKEN -e PORT=80 -p 80:80 $CELO_IMAGE_ATTESTATION -c " cd /celo-monorepo/packages/attestation-service && yarn run db:migrate && yarn start " + screen -S celo-attestation-service -d -m docker run --name celo-attestation-service -it --restart always --entrypoint /bin/bash --network host -e ATTESTATION_SIGNER_ADDRESS=0x$CELO_ATTESTATION_SIGNER_ADDRESS -e CELO_VALIDATOR_ADDRESS=0x$CELO_VALIDATOR_ADDRESS -e CELO_PROVIDER=$CELO_PROVIDER -e DATABASE_URL=$DATABASE_URL -e SMS_PROVIDERS=twilio -e TWILIO_MESSAGING_SERVICE_SID=$TWILIO_MESSAGING_SERVICE_SID -e TWILIO_VERIFY_SERVICE_SID=$TWILIO_VERIFY_SERVICE_SID -e TWILIO_ACCOUNT_SID=$TWILIO_ACCOUNT_SID -e TWILIO_BLACKLIST=$TWILIO_BLACKLIST -e TWILIO_AUTH_TOKEN=$TWILIO_AUTH_TOKEN -e PORT=80 -p 80:80 $CELO_IMAGE_ATTESTATION -c " cd /celo-monorepo/packages/attestation-service && yarn run db:migrate && yarn start " echo -e "\tAttestation service should be running, you can check running 'screen -ls'" echo -e "\tYou can re-attach to the attestation-service node running:" diff --git a/scripts/validator-config.rc b/scripts/validator-config.rc index 857b7fbbff0..d612f8b71da 100644 --- a/scripts/validator-config.rc +++ b/scripts/validator-config.rc @@ -6,6 +6,7 @@ #VALIDATOR_NAME="validator-0" #TWILIO_MESSAGING_SERVICE_SID= +#TWILIO_VERIFY_SERVICE_SID= #TWILIO_ACCOUNT_SID= #TWILIO_BLACKLIST="" #TWILIO_AUTH_TOKEN="" diff --git a/yarn.lock b/yarn.lock index f9f02e1d89e..82a9a3edc5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1549,6 +1549,26 @@ is-base64 "^1.1.0" libphonenumber-js "^1.9.11" +"@celo/phone-number-privacy-common@1.0.34": + version "1.0.34" + resolved "https://registry.yarnpkg.com/@celo/phone-number-privacy-common/-/phone-number-privacy-common-1.0.34.tgz#59b3b4d03a0482c967f26f7638d1afd326d4115b" + integrity sha512-xAIusO9N66lBG11/21ro0w8BZTLcRs6sWluam2qlTCf5e1vSPAxQeDx8GaqpcAJ2500zSsvmFOzlAA1iZiAD8g== + dependencies: + "@celo/base" "1.1.1" + "@celo/contractkit" "1.2.0" + "@celo/identity" "1.2.0" + "@celo/utils" "1.0.2" + bignumber.js "^9.0.0" + blind-threshold-bls "https://github.com/celo-org/blind-threshold-bls-wasm#e1e2f8a" + btoa "1.2.1" + bunyan "1.8.12" + bunyan-debug-stream "2.0.0" + bunyan-gke-stackdriver "0.1.2" + dotenv "^8.2.0" + elliptic "^6.5.4" + is-base64 "^1.1.0" + libphonenumber-js "^1.9.11" + "@celo/typechain-target-web3-v1-celo@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@celo/typechain-target-web3-v1-celo/-/typechain-target-web3-v1-celo-0.2.0.tgz#2560b470e348d2628debe899885724ce5b218bc3" @@ -18553,7 +18573,7 @@ minimist@0.0.5, minimist@0.0.8, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1. resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.3.3, minipass@^2.3.5, minipass@^2.6.0, minipass@^2.9.0: +minipass@^2.3.3, minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== @@ -22974,6 +22994,13 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + semver@~5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" @@ -24425,14 +24452,14 @@ tar-stream@^2.0.0, tar-stream@^2.1.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@4.4.10, tar@^4, tar@^4.0.2, tar@^4.3.0, tar@^4.4.0, tar@^4.4.10, tar@^4.4.2, tar@^4.4.3, tar@^4.4.8, tar@^6.0.5: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== +tar@4.4.15, tar@^4, tar@^4.0.2, tar@^4.3.0, tar@^4.4.0, tar@^4.4.10, tar@^4.4.2, tar@^4.4.3, tar@^4.4.8, tar@^6.0.5: + version "4.4.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" + integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.5" + minipass "^2.8.6" minizlib "^1.2.1" mkdirp "^0.5.0" safe-buffer "^5.1.2"