diff --git a/.env.rc1 b/.env.rc1 index a618d05bb1b..a393d6377f3 100644 --- a/.env.rc1 +++ b/.env.rc1 @@ -81,6 +81,8 @@ AZURE_ORACLE_WESTUS_CELOUSD_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x0aee051be85ba9c7c1 AZURE_ORACLE_WESTUS_CELOEUR_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0xb8bDBfdd591a5be5980983A7ba1710a5F46f42B5:mainnet-eur-oracle-wus2:mainnet-oracles-westus2,0x929Ad7f2b781CE830014E824CA2eF0b7b8de87C2:mainnet-eur-oracle-wus3:mainnet-oracles-westus2,0xCCC0B54edD8dAe3c15b5C002dd5d348495d4f7fe:mainnet-eur-oracle-wus4:mainnet-oracles-westus2 AZURE_ORACLE_WESTUS_CELOBRL_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x57d8a7bf9e7f4113c49e077b140fd8e1d7f78a76:mainnet-brl-oracle-wus0:mainnet-oracles-westus2,0x1299dd007cd5120262e546dca893e30d1cff8a10:mainnet-brl-oracle-wus1:mainnet-oracles-westus2,0x116951e440aee97a328614f9937710c9bb2f0839:mainnet-brl-oracle-wus4:mainnet-oracles-westus2 AZURE_ORACLE_WESTUS_USDCUSD_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x2986c21824c9b804d170270a316ceb07149f79c5:mainnet-usdcusd-wus0,0x09e2e47bb5df7b3464407746970a65c7b02883b3:mainnet-usdcusd-wus1,0xd5e7454932f6e853af849f70044570b62ca2596e:mainnet-usdcusd-wus2,0xfe3276b7142dee2cda34b1d14852eb32f436483d:mainnet-usdcusd-wus3 +AZURE_ORACLE_WESTUS_USDCEUR_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0xeccd1e9439094d025ac7d08d16b0bfe0da3bea53:mainnet-usdceur-wus0,0x9b242d2bd848fc92060ca7546033c3af352583d2:mainnet-usdceur-wus1,0x905ab001a9199d45c3f5c7b055b65ace5fc7d70a:mainnet-usdceur-wus2,0xdf5dd31d8f78520185d6a9fb0498c4bbddfe0708:mainnet-usdceur-wus3 +AZURE_ORACLE_WESTUS_USDCBRL_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x8dba01f832c7b0bb5f0bad4efe181cc07f8b322e:mainnet-usdcbrl-wus0,0xffb417d009d09bd1140244e70babbaa52d69ec84:mainnet-usdcbrl-wus1,0x5f755b8350a2e6b8b042cb3e052580e4c5b0ac35:mainnet-usdcbrl-wus2,0x8e1349b48ee82ef5437c912662e6640f3590c6f9:mainnet-usdcbrl-wus3 AZURE_ORACLE_WESTUS_FULL_NODES_COUNT=5 AZURE_ORACLE_WESTUS_FULL_NODES_ROLLING_UPDATE_PARTITION=0 AZURE_ORACLE_WESTUS_FULL_NODES_DISK_SIZE=100 @@ -101,6 +103,8 @@ AZURE_ORACLE_WESTEUROPE_CELOUSD_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0xfe9925e6ae9c4c AZURE_ORACLE_WESTEUROPE_CELOEUR_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x87C45738DAd8Dc3D2b1cCe779E0766329cc408C6:mainnet-eur-oracle-weu0:mainnet-oracles-westeurope,0xeF1E143C554EFC43B0537Af00Ac27C828dE6cF8D:mainnet-eur-oracle-weu1:mainnet-oracles-westeurope,0xF4B4AA107F30206EA019DE145A9b778a220f9fc0:mainnet-eur-oracle-weu2:mainnet-oracles-westeurope,0x24c303e6395DD19806F739619960A311764e3F40:mainnet-eur-oracle-weu3:mainnet-oracles-westeurope,0xDA413875FB45E5905950Bc08a908ebD246Ee6581:mainnet-eur-oracle-weu5:mainnet-oracles-westeurope AZURE_ORACLE_WESTEUROPE_CELOBRL_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x3b91bbb873f3b979bd6671dc018d5fc1848882dd:mainnet-brl-oracle-weu0:mainnet-oracles-westeurope,0xc3994b2af0e82490e432d49e9f2246cdfd84da8f:mainnet-brl-oracle-weu1:mainnet-oracles-westeurope,0x9b376b33c33325332df8c6ca951a9896889a6d1e:mainnet-brl-oracle-weu2:mainnet-oracles-westeurope,0x554ba7f4d200c7b233b93b7f2223bc1ea7c467fd:mainnet-brl-oracle-weu3:mainnet-oracles-westeurope,0x535cea1834d6b52e4e9724642fdd7008f569ba5c:mainnet-brl-oracle-weu4:mainnet-oracles-westeurope AZURE_ORACLE_WESTEUROPE_USDCUSD_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x477185291403ca2ed5f56d59ed0d568a16222013:mainnet-usdcusd-weu0,0x9a0a52d483c62df76d54f41ab3283cc7cb41ba91:mainnet-usdcusd-weu1,0x2ddb86898a2c2c884fc5cc3ca344898b0170a00d:mainnet-usdcusd-weu2,0x79be0a692e3a4bcd22b96c3e93a108b485becbb2:mainnet-usdcusd-weu3 +AZURE_ORACLE_WESTEUROPE_USDCEUR_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x0781f530100e619936f5b427263441cb0414f885:mainnet-usdceur-weu0,0x55de75fd0c2b37987757172fef7ba2ea935d284d:mainnet-usdceur-weu1,0xdc0c15fa73b13b2e74cd3eced23d8826569904c5:mainnet-usdceur-weu2,0x9048872f739cebbe72825763a1b72064c4df8f1f:mainnet-usdceur-weu3 +AZURE_ORACLE_WESTEUROPE_USDCBRL_ORACLE_ADDRESS_AZURE_KEY_VAULTS=0x42b813b9ff8ce8f4837accea26bedda20d7c4982:mainnet-usdcbrl-weu0,0x09208127500963ee1c3af88bfbb3ef0cd34d6eb0:mainnet-usdcbrl-weu1,0xa8f5be092a8452eab98ed1c220d642114bb2731e:mainnet-usdcbrl-weu2,0xfd265c994a5a9c2847fe03a5e878648963f53a37:mainnet-usdcbrl-weu3 AZURE_ORACLE_WESTEUROPE_FULL_NODES_COUNT=5 AZURE_ORACLE_WESTEUROPE_FULL_NODES_ROLLING_UPDATE_PARTITION=0 AZURE_ORACLE_WESTEUROPE_FULL_NODES_DISK_SIZE=100 @@ -534,4 +538,4 @@ AZURE_KOMENCI_SEA_KOMENCI_NETWORK=rc1 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_REDIS_CLUSTER_USEPASSWORD=false \ No newline at end of file diff --git a/.github/workflows/container-all-monorepo.yml b/.github/workflows/container-all-monorepo.yml new file mode 100644 index 00000000000..c418015b981 --- /dev/null +++ b/.github/workflows/container-all-monorepo.yml @@ -0,0 +1,40 @@ +--- +name: Build celo-monorepo container + +on: + push: + paths: + - 'dockerfiles/all-monorepo/**' + branches: + - master + pull_request: + paths: + - 'dockerfiles/all-monorepo/**' + workflow_dispatch: + +jobs: + celomonorepo-build-dev: + uses: celo-org/reusable-workflows/.github/workflows/container-cicd.yaml@v1.8 + name: Build us-west1-docker.pkg.dev/devopsre/dev-images/monorepo:${{ github.sha }} + if: | + github.ref != 'refs/heads/master' + with: + workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-celo-monorepo/providers/github-by-repos + service-account: 'celo-monorepo-dev@devopsre.iam.gserviceaccount.com' + artifact-registry: us-west1-docker.pkg.dev/devopsre/dev-images/monorepo + tag: ${{ github.sha }} + context: . + file: dockerfiles/all-monorepo/Dockerfile + + celomonorepo-build: + uses: celo-org/reusable-workflows/.github/workflows/container-cicd.yaml@v1.8 + name: Build us-west1-docker.pkg.dev/devopsre/celo-monorepo/monorepo:${{ github.sha }} + if: | + github.ref == 'refs/heads/master' + with: + workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-celo-monorepo-master/providers/github-by-repos + service-account: 'celo-monorepo@devopsre.iam.gserviceaccount.com' + artifact-registry: us-west1-docker.pkg.dev/devopsre/celo-monorepo/monorepo + tag: ${{ github.sha }} + context: . + file: dockerfiles/all-monorepo/Dockerfile diff --git a/.github/workflows/container-celotool.yml b/.github/workflows/container-celotool.yml index 8f57593c570..2b6fce3d7c1 100644 --- a/.github/workflows/container-celotool.yml +++ b/.github/workflows/container-celotool.yml @@ -1,5 +1,5 @@ --- -name: Build celo-monorepo container +name: Build celotool container on: push: @@ -15,26 +15,26 @@ on: jobs: celotool-build-dev: uses: celo-org/reusable-workflows/.github/workflows/container-cicd.yaml@v1.8 - name: Build us-west1-docker.pkg.dev/devopsre/dev-images/celotool:testing + name: Build us-west1-docker.pkg.dev/devopsre/dev-images/celotool:${{ github.sha }} if: | github.ref != 'refs/heads/master' with: workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-celo-monorepo/providers/github-by-repos service-account: 'celo-monorepo-dev@devopsre.iam.gserviceaccount.com' artifact-registry: us-west1-docker.pkg.dev/devopsre/dev-images/celotool - tag: testing + tag: ${{ github.sha }} context: . file: dockerfiles/celotool/Dockerfile celotool-build: uses: celo-org/reusable-workflows/.github/workflows/container-cicd.yaml@v1.8 - name: Build us-west1-docker.pkg.dev/devopsre/celo-monorepo/celotool:latest + name: Build us-west1-docker.pkg.dev/devopsre/celo-monorepo/celotool:${{ github.sha }} if: | github.ref == 'refs/heads/master' with: workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-celo-monorepo-master/providers/github-by-repos service-account: 'celo-monorepo@devopsre.iam.gserviceaccount.com' artifact-registry: us-west1-docker.pkg.dev/devopsre/celo-monorepo/celotool - tag: latest + tag: ${{ github.sha }} context: . file: dockerfiles/celotool/Dockerfile diff --git a/.vscode/settings.json b/.vscode/settings.json index 8e336f891ec..f0b54134c48 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,7 +31,7 @@ "[typescript]": { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": false } }, "[typescriptreact]": { @@ -43,5 +43,6 @@ "javascript.format.enable": false, "editor.tabSize": 2, "editor.detectIndentation": false, - "tslint.jsEnable": true + "tslint.jsEnable": true, + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/dockerfiles/all-monorepo/Dockerfile b/dockerfiles/all-monorepo/Dockerfile new file mode 100644 index 00000000000..1b6a53ff1a2 --- /dev/null +++ b/dockerfiles/all-monorepo/Dockerfile @@ -0,0 +1,26 @@ +FROM node:18 +LABEL org.opencontainers.image.authors="devops@clabs.co" + +WORKDIR /celo-monorepo + +# Needed for gsutil +RUN apt-get update && \ + apt-get install -y lsb-release && \ + apt-get install -y curl build-essential git python3 && \ + export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \ + echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \ + curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \ + apt-get update -y && \ + apt-get install -y google-cloud-sdk kubectl netcat-openbsd && \ + curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash && \ + rm -rf /var/lib/apt/lists/* + +# ensure yarn.lock is evaluated by kaniko cache diff +COPY . ./ + +RUN yarn install --network-timeout 100000 --frozen-lockfile && yarn cache clean +RUN yarn build + +RUN rm -rf .git +RUN rm -rf .gitmodules + diff --git a/dockerfiles/celotool/Dockerfile b/dockerfiles/celotool/Dockerfile index 1004d1800b2..e9f23bb5525 100644 --- a/dockerfiles/celotool/Dockerfile +++ b/dockerfiles/celotool/Dockerfile @@ -1,4 +1,6 @@ FROM node:18 +LABEL org.opencontainers.image.authors="devops@clabs.co" + WORKDIR /celo-monorepo # Needed for gsutil diff --git a/dockerfiles/cli-standalone/Dockerfile b/dockerfiles/cli-standalone/Dockerfile index bb2390339cb..89a3759f06e 100644 --- a/dockerfiles/cli-standalone/Dockerfile +++ b/dockerfiles/cli-standalone/Dockerfile @@ -4,6 +4,7 @@ # # VERSION=x.y.z; docker build . --build-arg VERSION=$VERSION -t gcr.io/celo-testnet/celocli-standalone:$VERSION FROM node:12-alpine +LABEL org.opencontainers.image.authors="devops@clabs.co" # Install cli install dependencies. RUN apk add --no-cache python3 git make gcc g++ bash libusb-dev linux-headers eudev-dev diff --git a/dockerfiles/cli/Dockerfile b/dockerfiles/cli/Dockerfile index 079e15a4d57..cabd618815b 100644 --- a/dockerfiles/cli/Dockerfile +++ b/dockerfiles/cli/Dockerfile @@ -47,6 +47,7 @@ RUN npm install @celo/celocli # Build the combined image FROM node:12-alpine as final_image +LABEL org.opencontainers.image.authors="devops@clabs.co" ARG network_name="alfajores" ARG network_id="44787" diff --git a/dockerfiles/phone-number-privacy/Dockerfile b/dockerfiles/phone-number-privacy/Dockerfile index d1f23953a0b..65cf6acef16 100644 --- a/dockerfiles/phone-number-privacy/Dockerfile +++ b/dockerfiles/phone-number-privacy/Dockerfile @@ -24,6 +24,7 @@ COPY packages/sdk/wallets/wallet-remote packages/sdk/wallets/wallet-remote ##### Main stage FROM node:18 +LABEL org.opencontainers.image.authors="devops@clabs.co" WORKDIR /celo-phone-number-privacy/ diff --git a/packages/celotool/src/lib/env-utils.ts b/packages/celotool/src/lib/env-utils.ts index e70ebcaf44a..db12c6ae8e0 100644 --- a/packages/celotool/src/lib/env-utils.ts +++ b/packages/celotool/src/lib/env-utils.ts @@ -115,6 +115,7 @@ export enum envVar { ORACLE_DOCKER_IMAGE_REPOSITORY = 'ORACLE_DOCKER_IMAGE_REPOSITORY', ORACLE_DOCKER_IMAGE_TAG = 'ORACLE_DOCKER_IMAGE_TAG', ORACLE_UNUSED_ORACLE_ADDRESSES = 'ORACLE_UNUSED_ORACLE_ADDRESSES', + ORACLE_FX_ADAPTERS_API_KEYS = 'ORACLE_FX_ADAPTERS_API_KEYS', PRIVATE_NODE_DISK_SIZE_GB = 'PRIVATE_NODE_DISK_SIZE_GB', PRIVATE_TX_NODES = 'PRIVATE_TX_NODES', PROMETHEUS_DISABLE_STACKDRIVER_SIDECAR = 'PROMETHEUS_DISABLE_STACKDRIVER_SIDECAR', diff --git a/packages/celotool/src/lib/k8s-oracle/base.ts b/packages/celotool/src/lib/k8s-oracle/base.ts index 557c978db3f..a900f287305 100644 --- a/packages/celotool/src/lib/k8s-oracle/base.ts +++ b/packages/celotool/src/lib/k8s-oracle/base.ts @@ -73,6 +73,7 @@ export abstract class BaseOracleDeployer { ? getFornoWebSocketUrl(this.celoEnv) : getFullNodeWebSocketRpcInternalUrl(this.celoEnv) return [ + `--set oracle.api_keys=${fetchEnv(envVar.ORACLE_FX_ADAPTERS_API_KEYS)}`, `--set environment.name=${this.celoEnv}`, `--set image.repository=${fetchEnv(envVar.ORACLE_DOCKER_IMAGE_REPOSITORY)}`, `--set image.tag=${fetchEnv(envVar.ORACLE_DOCKER_IMAGE_TAG)}`, diff --git a/packages/celotool/src/lib/oracle.ts b/packages/celotool/src/lib/oracle.ts index dfac13dcd77..8a42599eb16 100644 --- a/packages/celotool/src/lib/oracle.ts +++ b/packages/celotool/src/lib/oracle.ts @@ -303,7 +303,17 @@ const mnemonicBasedOracleIdentityConfigDynamicEnvVars: { */ export function addCurrencyPairMiddleware(argv: yargs.Argv) { return argv.option('currencyPair', { - choices: ['CELOUSD', 'CELOEUR', 'CELOBRL', 'USDCUSD', 'USDCEUR', 'USDCBRL'], + choices: [ + 'CELOUSD', + 'CELOEUR', + 'CELOBRL', + 'USDCUSD', + 'USDCEUR', + 'USDCBRL', + 'CELOXOF', + 'EUROCXOF', + 'EUROCEUR', + ], description: 'Oracle deployment to target based on currency pair', demandOption: true, type: 'string', diff --git a/packages/helm-charts/oracle/templates/api_keys-secret.yaml b/packages/helm-charts/oracle/templates/api_keys-secret.yaml new file mode 100644 index 00000000000..ad3071bb020 --- /dev/null +++ b/packages/helm-charts/oracle/templates/api_keys-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: api-keys +type: Opaque +data: + api_keys: {{ .Values.oracle.api_keys | b64enc }} \ No newline at end of file diff --git a/packages/helm-charts/oracle/templates/statefulset.yaml b/packages/helm-charts/oracle/templates/statefulset.yaml index de6c0790911..1d5a604e879 100644 --- a/packages/helm-charts/oracle/templates/statefulset.yaml +++ b/packages/helm-charts/oracle/templates/statefulset.yaml @@ -115,6 +115,11 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name + - name: API_KEYS + valueFrom: + secretKeyRef: + key: api_keys + name: api-keys {{ include "common.env-var" (dict "name" "API_REQUEST_TIMEOUT" "dict" .Values.oracle "value_name" "apiRequestTimeoutMs" "optional" true) | indent 8 }} {{ include "common.env-var" (dict "name" "AZURE_HSM_INIT_TRY_COUNT" "dict" .Values.oracle.azureHsm "value_name" "initTryCount") | indent 8 }} {{ include "common.env-var" (dict "name" "AZURE_HSM_INIT_MAX_RETRY_BACKOFF_MS" "dict" .Values.oracle.azureHsm "value_name" "initMaxRetryBackoffMs") | indent 8 }} diff --git a/packages/helm-charts/testnet/templates/_helpers.tpl b/packages/helm-charts/testnet/templates/_helpers.tpl index 350721e6152..2764475eebd 100644 --- a/packages/helm-charts/testnet/templates/_helpers.tpl +++ b/packages/helm-charts/testnet/templates/_helpers.tpl @@ -227,18 +227,20 @@ spec: echo "Generating proxy enode url pair for proxy $PROXY_INDEX" PROXY_INTERNAL_IP_ENV_VAR={{ $.Release.Namespace | upper }}_VALIDATORS_${RID}_PROXY_INTERNAL_${PROXY_INDEX}_SERVICE_HOST echo "PROXY_INTERNAL_IP_ENV_VAR=$PROXY_INTERNAL_IP_ENV_VAR" -PROXY_INTERNAL_IP=$(eval "echo \\${${PROXY_INTERNAL_IP_ENV_VAR}}") +PROXY_INTERNAL_IP=`eval "echo \\${${PROXY_INTERNAL_IP_ENV_VAR}}"` # If $PROXY_IPS is not empty, then we use the IPs from there. Otherwise, # we use the IP address of the proxy internal service if [ ! -z $PROXY_IPS ]; then echo "Proxy external IP from PROXY_IPS=$PROXY_IPS: " - PROXY_EXTERNAL_IP=$(echo -n $PROXY_IPS | cut -d '/' -f $((PROXY_INDEX + 1))) + PROXY_EXTERNAL_IP=`echo -n $PROXY_IPS | cut -d '/' -f $((PROXY_INDEX + 1))` else PROXY_EXTERNAL_IP=$PROXY_INTERNAL_IP fi +echo "Proxy internal IP: $PROXY_INTERNAL_IP" +echo "Proxy external IP: $PROXY_EXTERNAL_IP" # Proxy key index to allow for a high number of proxies per validator without overlap PROXY_KEY_INDEX=$(( ($RID * 10000) + $PROXY_INDEX )) -PROXY_ENODE_ADDRESS=$(celotooljs.sh generate public-key --mnemonic "$MNEMONIC" --accountType proxy --index $PROXY_KEY_INDEX) +PROXY_ENODE_ADDRESS=`celotooljs.sh generate public-key --mnemonic "$MNEMONIC" --accountType proxy --index $PROXY_KEY_INDEX` PROXY_INTERNAL_ENODE=enode://${PROXY_ENODE_ADDRESS}@${PROXY_INTERNAL_IP}:30503 PROXY_EXTERNAL_ENODE=enode://${PROXY_ENODE_ADDRESS}@${PROXY_EXTERNAL_IP}:30303 echo "Proxy internal enode: $PROXY_INTERNAL_ENODE" diff --git a/packages/phone-number-privacy/TODO.md b/packages/phone-number-privacy/TODO.md new file mode 100644 index 00000000000..9f9a2984edc --- /dev/null +++ b/packages/phone-number-privacy/TODO.md @@ -0,0 +1,25 @@ +# TODO + +- (alec) fix domains tests +- (Alec) check prometheus Counter +- Fix types in errorResult and sendFailure so we don't have to use ANY +- Refactor domain sign handler to use db transactions properly +- refactor authorization function with the new account model +- resolve FAKE_URL for request url +- Search for TODO comments for things to fix after load test +- (nice to have) Refactor Combiner to be similar than signer (kill IO, Controller, Action) +- Make caching config parameters configurable by environment +- TODO comments + +## Done + +✔️ extract resultHandler() out of each handler, into the createHandler on server.ts +✔️ correct Locals Type (logger should not be an ANY) +✔️ (mariano) Implement chaching Account Service +✔️ (mariano) Check Tracing Calls +✔️ trace signature timeg +✔️ (Mariano) remove catchErrorHandler2 (move it catchErrorHandler) +✔️ Type Handler so Response has the correct Response Type +✔️ Type Handlers so that Request is the proper type, or better use the "isValid Request" function +✔️ fix primary key in requests table +✔️ drop legacy tables \ No newline at end of file diff --git a/packages/phone-number-privacy/combiner/src/config.ts b/packages/phone-number-privacy/combiner/src/config.ts index 3d9fcb9e917..a1b9829f230 100644 --- a/packages/phone-number-privacy/combiner/src/config.ts +++ b/packages/phone-number-privacy/combiner/src/config.ts @@ -28,7 +28,6 @@ export const MAX_QUERY_COUNT_DISCREPANCY_THRESHOLD = 5 export interface OdisConfig { serviceName: string enabled: boolean - shouldFailOpen: boolean // TODO (https://github.com/celo-org/celo-monorepo/issues/9862) consider refactoring config, this isn't relevant to domains endpoints odisServices: { signers: string timeoutMilliSeconds: number @@ -77,7 +76,6 @@ if (DEV_MODE) { phoneNumberPrivacy: { serviceName: defaultServiceName, enabled: true, - shouldFailOpen: false, odisServices: { signers: devSignersString, timeoutMilliSeconds: 5 * 1000, @@ -112,7 +110,6 @@ if (DEV_MODE) { domains: { serviceName: defaultServiceName, enabled: true, - shouldFailOpen: false, odisServices: { signers: devSignersString, timeoutMilliSeconds: 5 * 1000, @@ -156,7 +153,6 @@ if (DEV_MODE) { phoneNumberPrivacy: { serviceName: functionConfig.pnp.service_name ?? defaultServiceName, enabled: toBool(functionConfig.pnp.enabled, false), - shouldFailOpen: toBool(functionConfig.pnp.should_fail_open, false), odisServices: { signers: functionConfig.pnp.odisservices, timeoutMilliSeconds: functionConfig.pnp.timeout_ms @@ -176,7 +172,6 @@ if (DEV_MODE) { domains: { serviceName: functionConfig.domains.service_name ?? defaultServiceName, enabled: toBool(functionConfig.domains.enabled, false), - shouldFailOpen: toBool(functionConfig.domains.auth_should_fail_open, false), odisServices: { signers: functionConfig.domains.odisservices, timeoutMilliSeconds: functionConfig.domains.timeout_ms diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts index 929470b116a..26801988b19 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts @@ -59,20 +59,19 @@ export class PnpQuotaIO extends IO { ) } - async authenticate( - _request: Request<{}, {}, PnpQuotaRequest>, - _logger: Logger - ): Promise { + async authenticate(request: Request<{}, {}, PnpQuotaRequest>, logger: Logger): Promise { + logger.debug({ url: request.url }) // just for ts not to make a fuzz return Promise.resolve(true) // return authenticateUser( // request, - // this.kit, // logger, - // this.config.shouldFailOpen, - // [], - // this.config.fullNodeTimeoutMs, - // this.config.fullNodeRetryCount, - // this.config.fullNodeRetryDelayMs + // newContractKitFetcher( + // this.kit, + // logger, + // this.config.fullNodeTimeoutMs, + // this.config.fullNodeRetryCount, + // this.config.fullNodeRetryDelayMs + // ) // ) } diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/io.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/io.ts index 5c0566334f2..40457004555 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/io.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/io.ts @@ -80,13 +80,14 @@ export class PnpSignIO extends IO { return Promise.resolve(true) // return authenticateUser( // request, - // this.kit, // logger, - // this.config.shouldFailOpen, - // [], - // this.config.fullNodeTimeoutMs, - // this.config.fullNodeRetryCount, - // this.config.fullNodeRetryDelayMs + // newContractKitFetcher( + // this.kit, + // logger, + // this.config.fullNodeTimeoutMs, + // this.config.fullNodeRetryCount, + // this.config.fullNodeRetryDelayMs + // ) // ) } diff --git a/packages/phone-number-privacy/combiner/src/pnp/services/log-responses.ts b/packages/phone-number-privacy/combiner/src/pnp/services/log-responses.ts index 7fdc2b6b6e4..3ad62ecd738 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/services/log-responses.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/services/log-responses.ts @@ -28,10 +28,10 @@ export class PnpSignerResponseLogger { }> = [] session.responses.forEach((response) => { if (response.res.success) { - const { version, performedQueryCount, totalQuota, blockNumber, warnings } = response.res + const { version, performedQueryCount, totalQuota, warnings } = response.res parsedResponses.push({ signerUrl: response.url, - values: { version, performedQueryCount, totalQuota, blockNumber, warnings }, + values: { version, performedQueryCount, totalQuota, warnings }, }) } }) diff --git a/packages/phone-number-privacy/combiner/src/pnp/services/threshold-state.ts b/packages/phone-number-privacy/combiner/src/pnp/services/threshold-state.ts index ee46bab51d4..f4ef6addc8c 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/services/threshold-state.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/services/threshold-state.ts @@ -43,7 +43,6 @@ export class PnpThresholdStateService { version: res.body.version, error: ErrorMessage.THRESHOLD_DISABLE_DOMAIN_FAILURE, }) - }) + }, 10000) }) describe(`${CombinerEndpoint.DOMAIN_QUOTA_STATUS}`, () => { @@ -1112,7 +1108,7 @@ describe('domainService', () => { version: res.body.version, error: ErrorMessage.THRESHOLD_DOMAIN_QUOTA_STATUS_FAILURE, }) - }) + }, 10000) }) describe(`${CombinerEndpoint.DOMAIN_SIGN}`, () => { @@ -1125,7 +1121,7 @@ describe('domainService', () => { version: res.body.version, error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, }) - }) + }, 10000) }) }) }) diff --git a/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts b/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts index 473938ab77c..cf78f1dc0a7 100644 --- a/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts +++ b/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts @@ -100,12 +100,8 @@ const signerConfig: SignerConfig = { }, phoneNumberPrivacy: { enabled: true, - shouldFailOpen: true, }, }, - attestations: { - numberAttestationsRequired: 3, - }, blockchain: { provider: 'https://alfajores-forno.celo-testnet.org', apiKey: undefined, @@ -605,40 +601,6 @@ describe('pnpService', () => { error: WarningMessage.API_UNAVAILABLE, }) }) - - describe('functionality in case of errors', () => { - it('Should respond with 200 on failure to fetch DEK when shouldFailOpen is true', async () => { - mockGetDataEncryptionKey.mockReset().mockImplementation(() => { - throw new Error() - }) - - const req = { - account: ACCOUNT_ADDRESS1, - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - } - - // NOT the dek private key, so authentication would fail if getDataEncryptionKey succeeded - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - - const combinerConfigWithFailOpenEnabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - combinerConfigWithFailOpenEnabled.phoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startCombiner(combinerConfigWithFailOpenEnabled, mockKit) - const res = await getCombinerQuotaResponse(req, authorization, appWithFailOpenEnabled) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - }) }) describe(`${CombinerEndpoint.PNP_SIGN}`, () => { @@ -928,41 +890,7 @@ describe('pnpService', () => { }) describe('functionality in case of errors', () => { - it('Should return 200 on failure to fetch DEK when shouldFailOpen is true', async () => { - mockGetDataEncryptionKey.mockImplementation(() => { - throw new Error() - }) - - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - // NOT the dek private key, so authentication would fail if getDataEncryptionKey succeeded - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - - const combinerConfigWithFailOpenEnabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - combinerConfigWithFailOpenEnabled.phoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startCombiner(combinerConfigWithFailOpenEnabled, mockKit) - const res = await sendPnpSignRequest(req, authorization, appWithFailOpenEnabled) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedTotalQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - expect(Buffer.from(unblindedSig).toString('base64')).toEqual(expectedUnblindedSig) - }) - - it('Should return 401 on failure to fetch DEK when shouldFailOpen is false', async () => { + it('Should return 401 on failure to fetch DEK', async () => { mockGetDataEncryptionKey.mockImplementation(() => { throw new Error() }) @@ -973,7 +901,6 @@ describe('pnpService', () => { const combinerConfigWithFailOpenDisabled: typeof combinerConfig = JSON.parse( JSON.stringify(combinerConfig) ) - combinerConfigWithFailOpenDisabled.phoneNumberPrivacy.shouldFailOpen = false const appWithFailOpenDisabled = startCombiner( combinerConfigWithFailOpenDisabled, mockKit diff --git a/packages/phone-number-privacy/common/package.json b/packages/phone-number-privacy/common/package.json index f25138d177b..d1b44236fd6 100644 --- a/packages/phone-number-privacy/common/package.json +++ b/packages/phone-number-privacy/common/package.json @@ -30,7 +30,14 @@ "dotenv": "^8.2.0", "elliptic": "^6.5.4", "io-ts": "2.0.1", - "is-base64": "^1.1.0" + "is-base64": "^1.1.0", + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/auto-instrumentations-node": "^0.38.0", + "@opentelemetry/propagator-ot-trace": "^0.27.0", + "@opentelemetry/sdk-metrics": "^1.15.1", + "@opentelemetry/sdk-node": "^0.41.1", + "@opentelemetry/semantic-conventions": "^1.15.1", + "@opentelemetry/sdk-trace-web": "^1.15.1" }, "devDependencies": { "@celo/poprf": "^0.1.9", diff --git a/packages/phone-number-privacy/common/src/interfaces/errors.ts b/packages/phone-number-privacy/common/src/interfaces/errors.ts index f1021e209a4..b7e3ab49eec 100644 --- a/packages/phone-number-privacy/common/src/interfaces/errors.ts +++ b/packages/phone-number-privacy/common/src/interfaces/errors.ts @@ -26,7 +26,6 @@ export enum ErrorMessage { THRESHOLD_PNP_QUOTA_STATUS_FAILURE = `CELO_ODIS_ERR_23 SIG_ERR Failed to get PNP quota status from a threshold of signers`, FAILURE_TO_GET_PERFORMED_QUERY_COUNT = `CELO_ODIS_ERR_24 DB_ERR Failed to read performedQueryCount from signer db`, FAILURE_TO_GET_TOTAL_QUOTA = `CELO_ODIS_ERR_25 NODE_ERR Failed to read on-chain state to calculate total quota`, - FAILURE_TO_GET_BLOCK_NUMBER = `CELO_ODIS_ERR_26 NODE_ERR Failed to read block number from full node`, FAILURE_TO_GET_DEK = `CELO_ODIS_ERR_27 NODE_ERR Failed to read user's DEK from full-node`, FAILING_OPEN = `CELO_ODIS_ERR_28 NODE_ERR Failing open on full-node error`, FAILING_CLOSED = `CELO_ODIS_ERR_29 NODE_ERR Failing closed on full-node error`, diff --git a/packages/phone-number-privacy/common/src/interfaces/responses.ts b/packages/phone-number-privacy/common/src/interfaces/responses.ts index 181d6740f60..73747b1e2c6 100644 --- a/packages/phone-number-privacy/common/src/interfaces/responses.ts +++ b/packages/phone-number-privacy/common/src/interfaces/responses.ts @@ -16,7 +16,6 @@ export interface PnpQuotaStatus { performedQueryCount: number // all time total quota totalQuota: number - blockNumber?: number } const PnpQuotaStatusSchema: t.Type = t.intersection([ @@ -47,7 +46,6 @@ export interface SignMessageResponseFailure { // Changing this is more involved; TODO(future) https://github.com/celo-org/celo-monorepo/issues/9826 performedQueryCount?: number totalQuota?: number - blockNumber?: number } export type SignMessageResponse = SignMessageResponseSuccess | SignMessageResponseFailure diff --git a/packages/phone-number-privacy/common/src/utils/authentication.ts b/packages/phone-number-privacy/common/src/utils/authentication.ts index 7e223f47211..8caea695b25 100644 --- a/packages/phone-number-privacy/common/src/utils/authentication.ts +++ b/packages/phone-number-privacy/common/src/utils/authentication.ts @@ -4,6 +4,7 @@ import { AccountsWrapper } from '@celo/contractkit/lib/wrappers/Accounts' import { AttestationsWrapper } from '@celo/contractkit/lib/wrappers/Attestations' import { trimLeading0x } from '@celo/utils/lib/address' import { verifySignature } from '@celo/utils/lib/signatureUtils' + import Logger from 'bunyan' import crypto from 'crypto' import { Request } from 'express' @@ -16,19 +17,35 @@ import { } from '../interfaces' import { FULL_NODE_TIMEOUT_IN_MS, RETRY_COUNT, RETRY_DELAY_IN_MS } from './constants' +export type DataEncryptionKeyFetcher = (address: string) => Promise + +export function newContractKitFetcher( + contractKit: ContractKit, + logger: Logger, + fullNodeTimeoutMs: number = FULL_NODE_TIMEOUT_IN_MS, + fullNodeRetryCount: number = RETRY_COUNT, + fullNodeRetryDelayMs: number = RETRY_DELAY_IN_MS +): DataEncryptionKeyFetcher { + return (address: string) => + getDataEncryptionKey( + address, + contractKit, + logger, + fullNodeTimeoutMs, + fullNodeRetryCount, + fullNodeRetryDelayMs + ) +} + /* * Confirms that user is who they say they are and throws error on failure to confirm. * Authorization header should contain the EC signed body */ export async function authenticateUser( request: Request<{}, {}, R>, - contractKit: ContractKit, logger: Logger, - shouldFailOpen: boolean = false, - warnings: ErrorType[] = [], - timeoutMs: number = FULL_NODE_TIMEOUT_IN_MS, - retryCount: number = RETRY_COUNT, - retryDelay: number = RETRY_DELAY_IN_MS + fetchDEK: DataEncryptionKeyFetcher, + warnings: ErrorType[] = [] ): Promise { logger.debug('Authenticating user') @@ -45,25 +62,18 @@ export async function authenticateUser( if (authMethod && authMethod === AuthenticationMethod.ENCRYPTION_KEY) { let registeredEncryptionKey try { - registeredEncryptionKey = await getDataEncryptionKey( - signer, - contractKit, - logger, - timeoutMs, - retryCount, - retryDelay - ) + registeredEncryptionKey = await fetchDEK(signer) } catch (err) { // getDataEncryptionKey should only throw if there is a full-node connection issue. // That is, it does not throw if the DEK is undefined or invalid - const failureStatus = shouldFailOpen ? ErrorMessage.FAILING_OPEN : ErrorMessage.FAILING_CLOSED + const failureStatus = ErrorMessage.FAILING_CLOSED logger.error({ err, warning: ErrorMessage.FAILURE_TO_GET_DEK, failureStatus, }) warnings.push(ErrorMessage.FAILURE_TO_GET_DEK, failureStatus) - return shouldFailOpen + return false } if (!registeredEncryptionKey) { logger.warn({ account: signer }, 'Account does not have registered encryption key') diff --git a/packages/phone-number-privacy/common/src/utils/responses.utils.ts b/packages/phone-number-privacy/common/src/utils/responses.utils.ts index 467acb1b42c..fffe55a1895 100644 --- a/packages/phone-number-privacy/common/src/utils/responses.utils.ts +++ b/packages/phone-number-privacy/common/src/utils/responses.utils.ts @@ -6,15 +6,17 @@ export function send< I extends OdisRequest = OdisRequest, O extends OdisResponse = OdisResponse >(response: Response, body: O, status: number, logger: Logger) { - if (!body.success) { - if (body.error in WarningMessage) { - logger.warn({ error: body.error, status, body }, 'Responding with warning') + if (!response.headersSent) { + if (!body.success) { + if (body.error in WarningMessage) { + logger.warn({ error: body.error, status, body }, 'Responding with warning') + } else { + logger.error({ error: body.error, status, body }, 'Responding with error') + } } else { - logger.error({ error: body.error, status, body }, 'Responding with error') + logger.info({ status, body }, 'Responding with success') } - } else { - logger.info({ status, body }, 'Responding with success') + response.status(status).json(body) + logger.info('Completed send') } - response.status(status).json(body) - logger.info('Completed send') } diff --git a/packages/phone-number-privacy/common/test/utils/authentication.test.ts b/packages/phone-number-privacy/common/test/utils/authentication.test.ts index 4e129b6fbd5..a61b26017c1 100644 --- a/packages/phone-number-privacy/common/test/utils/authentication.test.ts +++ b/packages/phone-number-privacy/common/test/utils/authentication.test.ts @@ -5,6 +5,7 @@ import { Request } from 'express' import { ErrorMessage, ErrorType } from '../../lib' import { AuthenticationMethod } from '../../src/interfaces/requests' import * as auth from '../../src/utils/authentication' +import { newContractKitFetcher } from '../../src/utils/authentication' describe('Authentication test suite', () => { const logger = Logger.createLogger({ @@ -20,17 +21,10 @@ describe('Authentication test suite', () => { account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', }, } as Request - const mockContractKit = {} as ContractKit - + const dekFetcher = newContractKitFetcher({} as ContractKit, logger) const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([]) @@ -41,47 +35,17 @@ describe('Authentication test suite', () => { get: (name: string) => (name === 'Authorization' ? 'Test' : ''), body: {}, } as Request - const mockContractKit = {} as ContractKit + const dekFetcher = newContractKitFetcher({} as ContractKit, logger) const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([]) }) - it('Should succeed authentication with error in getDataEncryptionKey when shouldFailOpen is true', async () => { - const sampleRequest: Request = { - get: (name: string) => (name === 'Authorization' ? 'Test' : ''), - body: { - account: '0xc1912fee45d61c87cc5ea59dae31190fffff232d', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - }, - } as Request - const mockContractKit = {} as ContractKit - - const warnings: ErrorType[] = [] - - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) - - expect(success).toBe(true) - expect(warnings).toEqual([ErrorMessage.FAILURE_TO_GET_DEK, ErrorMessage.FAILING_OPEN]) - }) - - it('Should fail authentication with error in getDataEncryptionKey when shouldFailOpen is false', async () => { + it('Should fail authentication with error in getDataEncryptionKey', async () => { const sampleRequest: Request = { get: (name: string) => (name === 'Authorization' ? 'Test' : ''), body: { @@ -89,17 +53,11 @@ describe('Authentication test suite', () => { authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, }, } as Request - const mockContractKit = {} as ContractKit + const dekFetcher = newContractKitFetcher({} as ContractKit, logger) const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - false, - warnings - ) + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([ErrorMessage.FAILURE_TO_GET_DEK, ErrorMessage.FAILING_CLOSED]) @@ -124,16 +82,11 @@ describe('Authentication test suite', () => { }, }, } as ContractKit + const dekFetcher = newContractKitFetcher(mockContractKit, logger) const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([]) @@ -158,10 +111,11 @@ describe('Authentication test suite', () => { }, }, } as ContractKit + const dekFetcher = newContractKitFetcher(mockContractKit, logger) const warnings: ErrorType[] = [] - const success = await auth.authenticateUser(sampleRequest, mockContractKit, logger) + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher) expect(success).toBe(false) expect(warnings).toEqual([]) @@ -197,14 +151,9 @@ describe('Authentication test suite', () => { } as ContractKit const warnings: ErrorType[] = [] + const dekFetcher = newContractKitFetcher(mockContractKit, logger) - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(true) expect(warnings).toEqual([]) @@ -249,13 +198,9 @@ describe('Authentication test suite', () => { const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const dekFetcher = newContractKitFetcher(mockContractKit, logger) + + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([]) @@ -295,13 +240,9 @@ describe('Authentication test suite', () => { const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const dekFetcher = newContractKitFetcher(mockContractKit, logger) + + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([]) @@ -342,13 +283,9 @@ describe('Authentication test suite', () => { const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const dekFetcher = newContractKitFetcher(mockContractKit, logger) + + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([]) @@ -383,16 +320,11 @@ describe('Authentication test suite', () => { }, }, } as ContractKit + const dekFetcher = newContractKitFetcher(mockContractKit, logger) const warnings: ErrorType[] = [] - const success = await auth.authenticateUser( - sampleRequest, - mockContractKit, - logger, - true, - warnings - ) + const success = await auth.authenticateUser(sampleRequest, logger, dekFetcher, warnings) expect(success).toBe(false) expect(warnings).toEqual([]) @@ -400,6 +332,7 @@ describe('Authentication test suite', () => { }) describe('isVerified utility', () => { + // TODO remove this it('Should succeed when verification is ok', async () => { const mockContractKit = { contracts: { diff --git a/packages/phone-number-privacy/monitor/src/index.ts b/packages/phone-number-privacy/monitor/src/index.ts index ead157604f7..8fddb7fa34a 100644 --- a/packages/phone-number-privacy/monitor/src/index.ts +++ b/packages/phone-number-privacy/monitor/src/index.ts @@ -1,4 +1,3 @@ -import { CombinerEndpointPNP } from '@celo/phone-number-privacy-common' import * as functions from 'firebase-functions' import { testDomainSignQuery, testPNPSignQuery } from './test' @@ -11,9 +10,7 @@ if (!contextName || !blockchainProvider) { export const odisMonitorScheduleFunctionPNP = functions .region('us-central1') .pubsub.schedule('every 5 minutes') - .onRun(async () => - testPNPSignQuery(blockchainProvider, contextName, CombinerEndpointPNP.PNP_SIGN) - ) + .onRun(async () => testPNPSignQuery(blockchainProvider, contextName)) export const odisMonitorScheduleFunctionDomains = functions .region('us-central1') diff --git a/packages/phone-number-privacy/monitor/src/query.ts b/packages/phone-number-privacy/monitor/src/query.ts index b069879b162..6ac7977602b 100644 --- a/packages/phone-number-privacy/monitor/src/query.ts +++ b/packages/phone-number-privacy/monitor/src/query.ts @@ -31,9 +31,6 @@ export const queryOdisForSalt = async ( contextName: OdisContextName, timeoutMs: number = 10000 ) => { - console.log(`contextName: ${contextName}`) // tslint:disable-line:no-console - console.log(`blockchain provider: ${blockchainProvider}`) // tslint:disable-line:no-console - const serviceContext = getServiceContext(contextName, OdisAPI.PNP) const contractKit = newKit(blockchainProvider, new LocalWallet()) diff --git a/packages/phone-number-privacy/monitor/src/scripts/run-load-test.ts b/packages/phone-number-privacy/monitor/src/scripts/run-load-test.ts index cac2eb23634..a058eec2060 100644 --- a/packages/phone-number-privacy/monitor/src/scripts/run-load-test.ts +++ b/packages/phone-number-privacy/monitor/src/scripts/run-load-test.ts @@ -1,35 +1,7 @@ import { OdisContextName } from '@celo/identity/lib/odis/query' import yargs from 'yargs' -import { concurrentLoadTest, serialLoadTest } from '../test' +import { concurrentRPSLoadTest } from '../test' -/* tslint:disable:no-console */ - -const runLoadTest = (contextName: string, numWorker: number, isSerial: boolean) => { - let blockchainProvider: string - switch (contextName) { - case 'alfajoresstaging': - case 'alfajores': - blockchainProvider = 'https://alfajores-forno.celo-testnet.org' - break - case 'mainnet': - blockchainProvider = 'https://forno.celo.org' - break - default: - console.error('Invalid contextName') - yargs.showHelp() - process.exit(1) - } - if (numWorker < 1) { - console.error('Invalid numWorkers') - yargs.showHelp() - process.exit(1) - } - if (isSerial) { - serialLoadTest(numWorker, blockchainProvider!, contextName as OdisContextName) // tslint:disable-line:no-floating-promises - } else { - concurrentLoadTest(numWorker, blockchainProvider!, contextName as OdisContextName) // tslint:disable-line:no-floating-promises - } -} // tslint:disable-next-line: no-unused-expression yargs .scriptName('ODIS-load-test') @@ -38,7 +10,7 @@ yargs .strict(true) .showHelpOnFail(true) .command( - 'run ', + 'run ', 'Load test ODIS.', (args) => args @@ -46,14 +18,40 @@ yargs type: 'string', description: 'Desired network.', }) - .positional('numWorkers', { + .positional('rps', { type: 'number', - description: 'Number of machines that will be sending request to ODIS.', - }) - .option('isSerial', { - type: 'boolean', - description: 'run test workers in series.', - default: false, + description: 'Number of requests per second to generate', }), - (args) => runLoadTest(args.contextName!, args.numWorkers!, args.isSerial) + (args) => { + if (args.rps == null || args.contextName == null) { + console.error('missing positional arguments') + yargs.showHelp() + process.exit(1) + } + + const rps = args.rps! + const contextName = args.contextName! as OdisContextName + + let blockchainProvider: string + switch (contextName) { + case 'alfajoresstaging': + case 'alfajores': + blockchainProvider = 'https://alfajores-forno.celo-testnet.org' + break + case 'mainnet': + blockchainProvider = 'https://forno.celo.org' + break + default: + console.error('Invalid contextName') + yargs.showHelp() + process.exit(1) + } + + if (rps < 1) { + console.error('Invalid rps') + yargs.showHelp() + process.exit(1) + } + concurrentRPSLoadTest(args.rps, blockchainProvider!, contextName) // tslint:disable-line:no-floating-promises + } ).argv diff --git a/packages/phone-number-privacy/monitor/src/test.ts b/packages/phone-number-privacy/monitor/src/test.ts index 33cdebd8662..a3f68284054 100644 --- a/packages/phone-number-privacy/monitor/src/test.ts +++ b/packages/phone-number-privacy/monitor/src/test.ts @@ -1,10 +1,11 @@ -import { concurrentMap, sleep } from '@celo/base' +import { sleep } from '@celo/base' import { Result } from '@celo/base/lib/result' import { BackupError } from '@celo/encrypted-backup' import { IdentifierHashDetails } from '@celo/identity/lib/odis/identifier' import { ErrorMessages, OdisContextName } from '@celo/identity/lib/odis/query' import { PnpClientQuotaStatus } from '@celo/identity/lib/odis/quota' import { CombinerEndpointPNP, rootLogger } from '@celo/phone-number-privacy-common' +import { performance } from 'perf_hooks' import { queryOdisDomain, queryOdisForQuota, queryOdisForSalt } from './query' const logger = rootLogger('odis-monitor') @@ -12,20 +13,18 @@ const logger = rootLogger('odis-monitor') export async function testPNPSignQuery( blockchainProvider: string, contextName: OdisContextName, - endpoint: CombinerEndpointPNP.PNP_SIGN, timeoutMs?: number ) { - logger.info(`Performing test PNP query for ${endpoint}`) try { const odisResponse: IdentifierHashDetails = await queryOdisForSalt( blockchainProvider, contextName, timeoutMs ) - logger.info({ odisResponse }, 'ODIS salt request successful. System is healthy.') + logger.debug({ odisResponse }, 'ODIS salt request successful. System is healthy.') } catch (err) { if ((err as Error).message === ErrorMessages.ODIS_QUOTA_ERROR) { - logger.info( + logger.warn( { error: err }, 'ODIS salt request out of quota. This is expected. System is healthy.' ) @@ -75,55 +74,71 @@ export async function testDomainSignQuery(contextName: OdisContextName) { } } -export async function serialLoadTest( - n: number, +export async function concurrentRPSLoadTest( + rps: number, blockchainProvider: string, contextName: OdisContextName, endpoint: | CombinerEndpointPNP.PNP_QUOTA | CombinerEndpointPNP.PNP_SIGN = CombinerEndpointPNP.PNP_SIGN, - timeoutMs?: number + duration: number = 0 ) { - for (let i = 0; i < n; i++) { - try { - switch (endpoint) { - case CombinerEndpointPNP.PNP_SIGN: - await testPNPSignQuery(blockchainProvider, contextName, endpoint, timeoutMs) - break - case CombinerEndpointPNP.PNP_QUOTA: - await testPNPQuotaQuery(blockchainProvider, contextName, timeoutMs) - } - } catch {} // tslint:disable-line:no-empty + const endPointFn = + endpoint === CombinerEndpointPNP.PNP_SIGN ? testPNPSignQuery : testPNPQuotaQuery + + const taskFn = async (i: number) => { + const start = performance.now() + await endPointFn(blockchainProvider, contextName) + const requestDuration = performance.now() - start + if (requestDuration > 600) { + logger.warn({ duration: Math.round(requestDuration), index: i }, 'SLOW Request') + } else { + logger.info({ duration: Math.round(requestDuration), index: i }, 'request finished') + } } + + return doRPSTest(taskFn, rps, duration) } -export async function concurrentLoadTest( - workers: number, - blockchainProvider: string, - contextName: OdisContextName, - endpoint: - | CombinerEndpointPNP.PNP_QUOTA - | CombinerEndpointPNP.PNP_SIGN = CombinerEndpointPNP.PNP_SIGN, - timeoutMs?: number -) { - while (true) { - const reqs = [] - for (let i = 0; i < workers; i++) { - reqs.push(i) +async function doRPSTest( + testFn: (reqNumber: number) => Promise, + rps: number, + duration: number = 0 +): Promise { + const inFlightRequests: Array> = [] + let shouldRun = true + + async function requestSender() { + let reqCounter = 1 + while (shouldRun) { + for (let i = 0; i < rps; i++) { + inFlightRequests.push(testFn(reqCounter++)) + } + await sleep(1000) } - await concurrentMap(workers, reqs, async (i) => { - await sleep(i * 10) - while (true) { - try { - switch (endpoint) { - case CombinerEndpointPNP.PNP_SIGN: - await testPNPSignQuery(blockchainProvider, contextName, endpoint, timeoutMs) - break - case CombinerEndpointPNP.PNP_QUOTA: - await testPNPQuotaQuery(blockchainProvider, contextName, timeoutMs) - } - } catch {} // tslint:disable-line:no-empty + } + + async function requestEnder() { + while (shouldRun || inFlightRequests.length > 0) { + if (inFlightRequests.length > 0) { + const req = inFlightRequests.shift() + await req?.catch((err) => { + console.error('some request failed', err) + }) + } else { + await sleep(1000) } - }) + } + } + + async function durationChecker() { + await sleep(duration) + shouldRun = false + } + + if (duration === 0) { + await Promise.all([requestSender(), requestEnder()]) + } else { + await Promise.all([durationChecker(), requestSender(), requestEnder()]) } } diff --git a/packages/phone-number-privacy/signer/package.json b/packages/phone-number-privacy/signer/package.json index 49b8167401d..0f580b5bdf5 100644 --- a/packages/phone-number-privacy/signer/package.json +++ b/packages/phone-number-privacy/signer/package.json @@ -1,13 +1,15 @@ { "name": "@celo/phone-number-privacy-signer", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.12", "description": "Signing participator of ODIS", "author": "Celo", "license": "Apache-2.0", "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "start": "yarn build && node -r dotenv/config dist/index.js", "start:docker": "yarn build && node dist/index.js", + "start:docker:tracing": "yarn build && node --require ./dist/tracing.js dist/index.js", "clean": "tsc -b . --clean", "build": "tsc -b .", "lint": "tslint --project .", @@ -44,6 +46,13 @@ "@celo/utils": "^4.1.1-beta.1", "@celo/wallet-hsm-azure": "^4.1.1-beta.1", "@google-cloud/secret-manager": "3.0.0", + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/auto-instrumentations-node": "^0.38.0", + "@opentelemetry/propagator-ot-trace": "^0.27.0", + "@opentelemetry/sdk-metrics": "^1.15.1", + "@opentelemetry/sdk-node": "^0.41.1", + "@opentelemetry/semantic-conventions": "^1.15.1", + "@opentelemetry/sdk-trace-web": "^1.15.1", "@types/bunyan": "^1.8.8", "aws-sdk": "^2.705.0", "blind-threshold-bls": "https://github.com/celo-org/blind-threshold-bls-wasm#e1e2f8a", @@ -54,7 +63,8 @@ "mysql2": "^2.1.0", "pg": "^8.2.1", "prom-client": "12.0.0", - "promise.allsettled": "^1.0.2" + "promise.allsettled": "^1.0.2", + "lru-cache": "^10.0.1" }, "devDependencies": { "@types/express": "^4.17.6", @@ -68,4 +78,4 @@ "engines": { "node": ">=10" } -} \ No newline at end of file +} diff --git a/packages/phone-number-privacy/signer/scripts/local-load-test.ts b/packages/phone-number-privacy/signer/scripts/local-load-test.ts new file mode 100644 index 00000000000..8ffbd402ecb --- /dev/null +++ b/packages/phone-number-privacy/signer/scripts/local-load-test.ts @@ -0,0 +1,15 @@ +// tslint:disable: no-console + +async function start() { + // TODO +} + +start() + .then(() => { + console.info('load test complete') + process.exit(0) + }) + .catch((e) => { + console.error('load test failed', e) + process.exit(1) + }) diff --git a/packages/phone-number-privacy/signer/scripts/run-migrations.ts b/packages/phone-number-privacy/signer/scripts/run-migrations.ts index 583e13b6b29..d148b947bfb 100644 --- a/packages/phone-number-privacy/signer/scripts/run-migrations.ts +++ b/packages/phone-number-privacy/signer/scripts/run-migrations.ts @@ -6,7 +6,7 @@ import { config } from '../src/config' async function start() { console.info('Running migrations') console.warn('It is no longer necessary to run db migrations seperately prior to startup') - await initDatabase(config, undefined, false) + await initDatabase(config, undefined) } start() diff --git a/packages/phone-number-privacy/signer/src/common/action.ts b/packages/phone-number-privacy/signer/src/common/action.ts deleted file mode 100644 index 7b77c4b38eb..00000000000 --- a/packages/phone-number-privacy/signer/src/common/action.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - DomainRequest, - OdisRequest, - PhoneNumberPrivacyRequest, -} from '@celo/phone-number-privacy-common' -import { SignerConfig } from '../config' -import { DomainSession } from '../domain/session' -import { PnpSession } from '../pnp/session' -import { IO } from './io' - -export type Session = R extends DomainRequest - ? DomainSession - : never | R extends PhoneNumberPrivacyRequest - ? PnpSession - : never - -export interface Action { - readonly config: SignerConfig - readonly io: IO - perform(session: Session, timeoutError: symbol): Promise -} diff --git a/packages/phone-number-privacy/signer/src/common/bls/bls-cryptography-client.ts b/packages/phone-number-privacy/signer/src/common/bls/bls-cryptography-client.ts index 6e8b2d0205f..8949df8a5c9 100644 --- a/packages/phone-number-privacy/signer/src/common/bls/bls-cryptography-client.ts +++ b/packages/phone-number-privacy/signer/src/common/bls/bls-cryptography-client.ts @@ -1,6 +1,7 @@ import { ErrorMessage } from '@celo/phone-number-privacy-common' import threshold_bls from 'blind-threshold-bls' import Logger from 'bunyan' +import { OdisError } from '../error' import { Counters } from '../metrics' /* * Computes the BLS signature for the blinded phone number. @@ -23,9 +24,9 @@ export function computeBlindedSignature( } return Buffer.from(signedMsg).toString('base64') - } catch (err) { + } catch (err: any) { Counters.signatureComputationErrors.inc() logger.error({ err }, ErrorMessage.SIGNATURE_COMPUTATION_FAILURE) - throw new Error(ErrorMessage.SIGNATURE_COMPUTATION_FAILURE) + throw new OdisError(ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, err) } } diff --git a/packages/phone-number-privacy/signer/src/common/context.ts b/packages/phone-number-privacy/signer/src/common/context.ts new file mode 100644 index 00000000000..16d3e4977de --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/context.ts @@ -0,0 +1,7 @@ +import Logger from 'bunyan' + +export interface Context { + logger: Logger + url: string + errors: string[] +} diff --git a/packages/phone-number-privacy/signer/src/common/controller.ts b/packages/phone-number-privacy/signer/src/common/controller.ts deleted file mode 100644 index 1854f930f20..00000000000 --- a/packages/phone-number-privacy/signer/src/common/controller.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - ErrorMessage, - ErrorType, - OdisRequest, - OdisResponse, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request, Response } from 'express' -import { Action } from './action' -import { Counters, Histograms, meter } from './metrics' - -export class Controller { - constructor(readonly action: Action) {} - - public async handle( - request: Request<{}, {}, unknown>, - response: Response> - ): Promise { - Counters.requests.labels(this.action.io.endpoint).inc() - // Unique error to be thrown on timeout - const timeoutError = Symbol() - await meter( - async () => { - const session = await this.action.io.init(request, response) - // Init returns a response to the user internally. - if (session) { - await this.action.perform(session, timeoutError) - } - }, - [], - (err: any) => { - response.locals.logger.error({ err }, `Error in handler for ${this.action.io.endpoint}`) - - let errMsg: ErrorType = ErrorMessage.UNKNOWN_ERROR - if (err === timeoutError) { - Counters.timeouts.inc() - errMsg = ErrorMessage.TIMEOUT_FROM_SIGNER - } else if ( - err instanceof Error && - // Propagate standard error & warning messages thrown during endpoint handling - (Object.values(ErrorMessage).includes(err.message as ErrorMessage) || - Object.values(WarningMessage).includes(err.message as WarningMessage)) - ) { - errMsg = err.message as ErrorType - } - this.action.io.sendFailure(errMsg, 500, response) - }, - Histograms.responseLatency, - [this.action.io.endpoint] - ) - } -} diff --git a/packages/phone-number-privacy/signer/src/common/database/database.ts b/packages/phone-number-privacy/signer/src/common/database/database.ts index 8fc491ac4fe..45c86ed79c2 100644 --- a/packages/phone-number-privacy/signer/src/common/database/database.ts +++ b/packages/phone-number-privacy/signer/src/common/database/database.ts @@ -1,14 +1,8 @@ import { rootLogger } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' import { Knex, knex } from 'knex' import { DEV_MODE, SignerConfig, SupportedDatabase, VERBOSE_DB_LOGGING } from '../../config' -import { ACCOUNTS_COLUMNS, ACCOUNTS_TABLE } from './models/account' -export async function initDatabase( - config: SignerConfig, - migrationsPath?: string, - doTestQuery = true -): Promise { +export async function initDatabase(config: SignerConfig, migrationsPath?: string): Promise { const logger = rootLogger(config.serviceName) logger.info({ config: config.db }, 'Initializing database connection') const { type, host, port, user, password, database, ssl, poolMaxSize } = config.db @@ -72,26 +66,6 @@ export async function initDatabase( loadExtensions: ['.js'], }) - if (doTestQuery) { - await executeTestQuery(db, logger) - } - logger.info('Database initialized successfully') return db } - -async function executeTestQuery(db: Knex, logger: Logger) { - logger.info('Counting accounts') - const result = await db(ACCOUNTS_TABLE.LEGACY).count(ACCOUNTS_COLUMNS.address).first() - - if (!result) { - throw new Error('No result from count, have migrations been run?') - } - - const count = Object.values(result)[0] - if (count === undefined || count === null || count === '') { - throw new Error('No result from count, have migrations been run?') - } - - logger.info(`Found ${count} accounts`) -} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20200330212224_create-accounts-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20200330212224_create-accounts-table.ts index fae5a17ca13..85ab5b88b51 100644 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20200330212224_create-accounts-table.ts +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20200330212224_create-accounts-table.ts @@ -1,10 +1,10 @@ import { Knex } from 'knex' -import { ACCOUNTS_COLUMNS, ACCOUNTS_TABLE } from '../models/account' +import { ACCOUNTS_COLUMNS } from '../models/account' export async function up(knex: Knex): Promise { // This check was necessary to switch from using .ts migrations to .js migrations. - if (!(await knex.schema.hasTable(ACCOUNTS_TABLE.LEGACY))) { - return knex.schema.createTable(ACCOUNTS_TABLE.LEGACY, (t) => { + if (!(await knex.schema.hasTable('accounts'))) { + return knex.schema.createTable('accounts', (t) => { t.string(ACCOUNTS_COLUMNS.address).notNullable().primary() t.dateTime(ACCOUNTS_COLUMNS.createdAt).notNullable() t.integer(ACCOUNTS_COLUMNS.numLookups).unsigned() @@ -14,5 +14,5 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - return knex.schema.dropTable(ACCOUNTS_TABLE.LEGACY) + return knex.schema.dropTable('accounts') } diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20200811163913_create_requests_table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20200811163913_create_requests_table.ts index 8c8014725d5..b7c92f0ecfa 100644 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20200811163913_create_requests_table.ts +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20200811163913_create_requests_table.ts @@ -1,10 +1,10 @@ import { Knex } from 'knex' -import { REQUESTS_COLUMNS, REQUESTS_TABLE } from '../models/request' +import { REQUESTS_COLUMNS } from '../models/request' export async function up(knex: Knex): Promise { // This check was necessary to switch from using .ts migrations to .js migrations. - if (!(await knex.schema.hasTable(REQUESTS_TABLE.LEGACY))) { - return knex.schema.createTable(REQUESTS_TABLE.LEGACY, (t) => { + if (!(await knex.schema.hasTable('requests'))) { + return knex.schema.createTable('requests', (t) => { t.string(REQUESTS_COLUMNS.address).notNullable() t.dateTime(REQUESTS_COLUMNS.timestamp).notNullable() t.string(REQUESTS_COLUMNS.blindedQuery).notNullable() @@ -19,5 +19,5 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - return knex.schema.dropTable(REQUESTS_TABLE.LEGACY) + return knex.schema.dropTable('requests') } diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20210421212301_create-indices.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20210421212301_create-indices.ts index 94672330d33..9b01ae66ae2 100644 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20210421212301_create-indices.ts +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20210421212301_create-indices.ts @@ -1,17 +1,17 @@ import { Knex } from 'knex' -import { ACCOUNTS_COLUMNS, ACCOUNTS_TABLE } from '../models/account' +import { ACCOUNTS_COLUMNS } from '../models/account' export async function up(knex: Knex): Promise { - if (!(await knex.schema.hasTable(ACCOUNTS_TABLE.LEGACY))) { - throw new Error('Unexpected error: Could not find ACCOUNTS_TABLE.LEGACY') + if (!(await knex.schema.hasTable('accounts'))) { + throw new Error('Unexpected error: Could not find accounts') } - return knex.schema.alterTable(ACCOUNTS_TABLE.LEGACY, (t) => { + return knex.schema.alterTable('accounts', (t) => { t.index(ACCOUNTS_COLUMNS.address) }) } export async function down(knex: Knex): Promise { - return knex.schema.alterTable(ACCOUNTS_TABLE.LEGACY, (t) => { + return knex.schema.alterTable('accounts', (t) => { t.dropIndex(ACCOUNTS_COLUMNS.address) }) } diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923161710_pnp-requests-onchain.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20220923161710_pnp-requests-onchain.ts index 77cec6a7d8e..75a88a30362 100644 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923161710_pnp-requests-onchain.ts +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20220923161710_pnp-requests-onchain.ts @@ -2,15 +2,16 @@ import { Knex } from 'knex' import { REQUESTS_COLUMNS, REQUESTS_TABLE } from '../models/request' export async function up(knex: Knex): Promise { - if (!(await knex.schema.hasTable(REQUESTS_TABLE.ONCHAIN))) { - return knex.schema.createTable(REQUESTS_TABLE.ONCHAIN, (t) => { + if (!(await knex.schema.hasTable(REQUESTS_TABLE))) { + return knex.schema.createTable(REQUESTS_TABLE, (t) => { t.string(REQUESTS_COLUMNS.address).notNullable() t.dateTime(REQUESTS_COLUMNS.timestamp).notNullable() t.string(REQUESTS_COLUMNS.blindedQuery).notNullable() t.primary([ REQUESTS_COLUMNS.address, + // Note: the order of these should be switched REQUESTS_COLUMNS.timestamp, - REQUESTS_COLUMNS.blindedQuery, // double check index + REQUESTS_COLUMNS.blindedQuery, ]) }) } @@ -18,5 +19,5 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - return knex.schema.dropTable(REQUESTS_TABLE.ONCHAIN) + return knex.schema.dropTable(REQUESTS_TABLE) } diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923165433_pnp-accounts-onchain.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20220923165433_pnp-accounts-onchain.ts index 8e3d3e16843..a4bb390eb74 100644 --- a/packages/phone-number-privacy/signer/src/common/database/migrations/20220923165433_pnp-accounts-onchain.ts +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20220923165433_pnp-accounts-onchain.ts @@ -3,8 +3,10 @@ import { ACCOUNTS_COLUMNS, ACCOUNTS_TABLE } from '../models/account' export async function up(knex: Knex): Promise { // This check was necessary to switch from using .ts migrations to .js migrations. - if (!(await knex.schema.hasTable(ACCOUNTS_TABLE.ONCHAIN))) { - return knex.schema.createTable(ACCOUNTS_TABLE.ONCHAIN, (t) => { + if (!(await knex.schema.hasTable(ACCOUNTS_TABLE))) { + return knex.schema.createTable(ACCOUNTS_TABLE, (t) => { + // Note: this creates a double index and may be hurting insertion times. Fixed in follow up migration. + // (https://www.percona.com/blog/duplicate-indexes-and-redundant-indexes/) t.string(ACCOUNTS_COLUMNS.address).notNullable().primary().index() t.dateTime(ACCOUNTS_COLUMNS.createdAt).notNullable() t.integer(ACCOUNTS_COLUMNS.numLookups).unsigned() @@ -14,5 +16,5 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - return knex.schema.dropTable(ACCOUNTS_TABLE.ONCHAIN) + return knex.schema.dropTable(ACCOUNTS_TABLE) } diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223141_rename-legacy-accounts-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223141_rename-legacy-accounts-table.ts new file mode 100644 index 00000000000..876e12aebc7 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223141_rename-legacy-accounts-table.ts @@ -0,0 +1,9 @@ +import { Knex } from 'knex' + +export async function up(knex: Knex): Promise { + return knex.schema.renameTable('accounts', 'accountsLegacy') +} + +export async function down(knex: Knex): Promise { + return knex.schema.renameTable('accountsLegacy', 'accounts') +} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223301_rename-legacy-requests-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223301_rename-legacy-requests-table.ts new file mode 100644 index 00000000000..fc9fda86a23 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223301_rename-legacy-requests-table.ts @@ -0,0 +1,9 @@ +import { Knex } from 'knex' + +export async function up(knex: Knex): Promise { + return knex.schema.renameTable('requests', 'requestsLegacy') +} + +export async function down(knex: Knex): Promise { + return knex.schema.renameTable('requestsLegacy', 'requests') +} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223359_drop-legacy-requests-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223359_drop-legacy-requests-table.ts new file mode 100644 index 00000000000..64d2c70f1f0 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223359_drop-legacy-requests-table.ts @@ -0,0 +1,16 @@ +import { Knex } from 'knex' +import { REQUESTS_COLUMNS } from '../models/request' + +export async function up(knex: Knex): Promise { + return knex.schema.dropTable('requestsLegacy') +} + +export async function down(knex: Knex): Promise { + // Note this will not restore data + return knex.schema.createTable('requestsLegacy', (t) => { + t.string(REQUESTS_COLUMNS.address).notNullable() + t.dateTime(REQUESTS_COLUMNS.timestamp).notNullable() + t.string(REQUESTS_COLUMNS.blindedQuery).notNullable() + t.primary([REQUESTS_COLUMNS.address, REQUESTS_COLUMNS.timestamp, REQUESTS_COLUMNS.blindedQuery]) + }) +} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223416_drop-legacy-accounts-table.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223416_drop-legacy-accounts-table.ts new file mode 100644 index 00000000000..5e320111c7d --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818223416_drop-legacy-accounts-table.ts @@ -0,0 +1,15 @@ +import { Knex } from 'knex' +import { ACCOUNTS_COLUMNS } from '../models/account' + +export async function up(knex: Knex): Promise { + return knex.schema.dropTable('accountsLegacy') +} + +export async function down(knex: Knex): Promise { + // Note this will not restore data + return knex.schema.createTable('accountsLegacy', (t) => { + t.string(ACCOUNTS_COLUMNS.address).notNullable().primary() + t.dateTime(ACCOUNTS_COLUMNS.createdAt).notNullable() + t.integer(ACCOUNTS_COLUMNS.numLookups).unsigned() + }) +} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818224022_drop-timestamp-from-requests-primary-key.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818224022_drop-timestamp-from-requests-primary-key.ts new file mode 100644 index 00000000000..78eae451e52 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818224022_drop-timestamp-from-requests-primary-key.ts @@ -0,0 +1,16 @@ +import { Knex } from 'knex' +import { REQUESTS_COLUMNS, REQUESTS_TABLE } from '../models/request' + +export async function up(knex: Knex): Promise { + return knex.schema.alterTable(REQUESTS_TABLE, (t) => { + t.dropPrimary() + t.primary([REQUESTS_COLUMNS.address, REQUESTS_COLUMNS.blindedQuery, REQUESTS_COLUMNS.timestamp]) + }) +} + +export async function down(knex: Knex): Promise { + return knex.schema.alterTable(REQUESTS_TABLE, (t) => { + t.dropPrimary() + t.primary([REQUESTS_COLUMNS.address, REQUESTS_COLUMNS.timestamp, REQUESTS_COLUMNS.blindedQuery]) + }) +} diff --git a/packages/phone-number-privacy/signer/src/common/database/migrations/20230818230722_drop-redundant-account-index.ts b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818230722_drop-redundant-account-index.ts new file mode 100644 index 00000000000..182137a7c8c --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/database/migrations/20230818230722_drop-redundant-account-index.ts @@ -0,0 +1,14 @@ +import { Knex } from 'knex' +import { ACCOUNTS_COLUMNS, ACCOUNTS_TABLE } from '../models/account' + +export async function up(knex: Knex): Promise { + return knex.schema.alterTable(ACCOUNTS_TABLE, (t) => { + t.dropIndex(ACCOUNTS_COLUMNS.address) + }) +} + +export async function down(knex: Knex): Promise { + return knex.schema.alterTable(ACCOUNTS_TABLE, (t) => { + t.index(ACCOUNTS_COLUMNS.address) + }) +} diff --git a/packages/phone-number-privacy/signer/src/common/database/models/account.ts b/packages/phone-number-privacy/signer/src/common/database/models/account.ts index 4a940843f4f..45abf018e68 100644 --- a/packages/phone-number-privacy/signer/src/common/database/models/account.ts +++ b/packages/phone-number-privacy/signer/src/common/database/models/account.ts @@ -1,7 +1,4 @@ -export enum ACCOUNTS_TABLE { - ONCHAIN = 'accountsOnChain', - LEGACY = 'accounts', // TODO figure out right way to drop this table now that it's no longer in use -} +export const ACCOUNTS_TABLE = 'accountsOnChain' export enum ACCOUNTS_COLUMNS { address = 'address', diff --git a/packages/phone-number-privacy/signer/src/common/database/models/request.ts b/packages/phone-number-privacy/signer/src/common/database/models/request.ts index 49a4273f726..c3d4f36a79d 100644 --- a/packages/phone-number-privacy/signer/src/common/database/models/request.ts +++ b/packages/phone-number-privacy/signer/src/common/database/models/request.ts @@ -1,7 +1,4 @@ -export enum REQUESTS_TABLE { - LEGACY = 'requests', - ONCHAIN = 'requestsOnChain', -} +export const REQUESTS_TABLE = 'requestsOnChain' export enum REQUESTS_COLUMNS { address = 'caller_address', diff --git a/packages/phone-number-privacy/signer/src/common/database/utils.ts b/packages/phone-number-privacy/signer/src/common/database/utils.ts index 630d8226554..e17f727e770 100644 --- a/packages/phone-number-privacy/signer/src/common/database/utils.ts +++ b/packages/phone-number-privacy/signer/src/common/database/utils.ts @@ -1,18 +1,18 @@ import { ErrorMessage } from '@celo/phone-number-privacy-common' import Logger from 'bunyan' -import { Knex } from 'knex' -import { Counters, Labels } from '../metrics' +import { OdisError } from '../error' +import { Counters, Histograms, Labels, newMeter } from '../metrics' export type DatabaseErrorMessage = | ErrorMessage.DATABASE_GET_FAILURE | ErrorMessage.DATABASE_INSERT_FAILURE | ErrorMessage.DATABASE_UPDATE_FAILURE -export function countAndThrowDBError( +export function countAndThrowDBError( err: any, logger: Logger, errorMsg: DatabaseErrorMessage -): T { +): never { let label: Labels switch (errorMsg) { case ErrorMessage.DATABASE_UPDATE_FAILURE: @@ -30,12 +30,19 @@ export function countAndThrowDBError( Counters.databaseErrors.labels(label).inc() logger.error({ err }, errorMsg) - throw new Error(errorMsg) + throw new OdisError(errorMsg) } -export function queryWithOptionalTrx(baseQuery: Knex.QueryBuilder, trx?: Knex.Transaction) { - if (trx) { - return baseQuery.transacting(trx) - } - return baseQuery +export function doMeteredSql( + sqlLabel: string, + errorMsg: DatabaseErrorMessage, + logger: Logger, + fn: () => Promise +): Promise { + const meter = newMeter(Histograms.dbOpsInstrumentation, sqlLabel) + + return meter(async () => { + const res = await fn() + return res + }).catch((err) => countAndThrowDBError(err, logger, errorMsg)) } diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/account.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/account.ts index 31da3459ab0..de88f643910 100644 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/account.ts +++ b/packages/phone-number-privacy/signer/src/common/database/wrappers/account.ts @@ -1,107 +1,81 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' +// import { ErrorMessage } from '@celo/phone-number-privacy-common' import Logger from 'bunyan' import { Knex } from 'knex' -import { config } from '../../../config' -import { Histograms, meter } from '../../metrics' -import { AccountRecord, ACCOUNTS_COLUMNS, ACCOUNTS_TABLE, toAccountRecord } from '../models/account' -import { countAndThrowDBError, queryWithOptionalTrx } from '../utils' - -function accounts(db: Knex, table: ACCOUNTS_TABLE) { - return db(table) -} +// import { config } from '../../../config' +// import { AccountRecord, ACCOUNTS_COLUMNS, ACCOUNTS_TABLE, toAccountRecord } from '../models/account' +// import { doMeteredSql } from '../utils' /* * Returns how many queries the account has already performed. */ export async function getPerformedQueryCount( - db: Knex, - accountsTable: ACCOUNTS_TABLE, - account: string, - logger: Logger, - trx?: Knex.Transaction + _db: Knex, + _account: string, + _logger: Logger ): Promise { - return meter( - async () => { - logger.debug({ account }, 'Getting performed query count') - const queryCounts = await queryWithOptionalTrx(accounts(db, accountsTable), trx) - .select(ACCOUNTS_COLUMNS.numLookups) - .where(ACCOUNTS_COLUMNS.address, account) - .first() - .timeout(config.db.timeout) - return queryCounts === undefined ? 0 : queryCounts[ACCOUNTS_COLUMNS.numLookups] - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_GET_FAILURE), - Histograms.dbOpsInstrumentation, - ['getPerformedQueryCount'] - ) + return Promise.resolve(0) + // logger.debug({ account }, 'Getting performed query count') + // return doMeteredSql( + // 'getPerformedQueryCount', + // ErrorMessage.DATABASE_GET_FAILURE, + // logger, + // async () => { + // const queryCounts = await db(ACCOUNTS_TABLE) + // .where(ACCOUNTS_COLUMNS.address, account) + // .select(ACCOUNTS_COLUMNS.numLookups) + // .first() + // .timeout(config.db.timeout) + // return queryCounts === undefined ? 0 : queryCounts[ACCOUNTS_COLUMNS.numLookups] + // } + // ) } -async function getAccountExists( - db: Knex, - accountsTable: ACCOUNTS_TABLE, - account: string, - logger: Logger, - trx?: Knex.Transaction -): Promise { - return meter( - async () => { - const accountRecord = await queryWithOptionalTrx(accounts(db, accountsTable), trx) - .where(ACCOUNTS_COLUMNS.address, account) - .first() - .timeout(config.db.timeout) +// async function getAccountExists( +// db: Knex, +// account: string, +// logger: Logger, +// trx?: Knex.Transaction +// ): Promise { +// return Promise.resolve(true) +// return doMeteredSql('getAccountExists', ErrorMessage.DATABASE_GET_FAILURE, logger, async () => { +// const sql = db(ACCOUNTS_TABLE) +// .where(ACCOUNTS_COLUMNS.address, account) +// .first() +// .timeout(config.db.timeout) - return !!accountRecord - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_GET_FAILURE), - Histograms.dbOpsInstrumentation, - ['getAccountExists'] - ) -} +// const accountRecord = await (trx != null ? sql.transacting(trx) : sql) +// return !!accountRecord +// }) +// } /* * Increments query count in database. If record doesn't exist, create one. */ -export async function incrementQueryCount( // - db: Knex, - accountsTable: ACCOUNTS_TABLE, - account: string, - logger: Logger, - trx?: Knex.Transaction -): Promise { - return meter( - async () => { - logger.debug({ account }, 'Incrementing query count') - if (await getAccountExists(db, accountsTable, account, logger, trx)) { - await queryWithOptionalTrx(accounts(db, accountsTable), trx) - .where(ACCOUNTS_COLUMNS.address, account) - .increment(ACCOUNTS_COLUMNS.numLookups, 1) - .timeout(config.db.timeout) - } else { - const newAccountRecord = toAccountRecord(account, 1) - await insertRecord(db, accountsTable, newAccountRecord, logger, trx) - } - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_UPDATE_FAILURE), - Histograms.dbOpsInstrumentation, - ['incrementQueryCount'] - ) -} - -async function insertRecord( - db: Knex, - accountsTable: ACCOUNTS_TABLE, - data: AccountRecord, - logger: Logger, - trx?: Knex.Transaction +export async function incrementQueryCount( + _db: Knex, + _account: string, + _logger: Logger, + _trx?: Knex.Transaction ): Promise { - try { - await queryWithOptionalTrx(accounts(db, accountsTable), trx) - .insert(data) - .timeout(config.db.timeout) - } catch (error) { - countAndThrowDBError(error, logger, ErrorMessage.DATABASE_INSERT_FAILURE) - } + return + // logger.debug({ account }, 'Incrementing query count') + // return doMeteredSql( + // 'incrementQueryCount', + // ErrorMessage.DATABASE_INSERT_FAILURE, + // logger, + // async () => { + // if (await getAccountExists(db, account, logger, trx)) { + // const sql = db(ACCOUNTS_TABLE) + // .where(ACCOUNTS_COLUMNS.address, account) + // .increment(ACCOUNTS_COLUMNS.numLookups, 1) + // .timeout(config.db.timeout) + // await (trx != null ? sql.transacting(trx) : sql) + // } else { + // const sql = db(ACCOUNTS_TABLE) + // .insert(toAccountRecord(account, 1)) + // .timeout(config.db.timeout) + // await (trx != null ? sql.transacting(trx) : sql) + // } + // } + // ) } diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-request.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-request.ts index 35428f423d0..34484982b49 100644 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-request.ts +++ b/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-request.ts @@ -2,22 +2,17 @@ import { Domain, domainHash, ErrorMessage } from '@celo/phone-number-privacy-com import Logger from 'bunyan' import { Knex } from 'knex' import { config } from '../../../config' -import { Histograms, meter } from '../../metrics' import { + DomainRequestRecord, DOMAIN_REQUESTS_COLUMNS, DOMAIN_REQUESTS_TABLE, - DomainRequestRecord, toDomainRequestRecord, } from '../models/domain-request' -import { countAndThrowDBError } from '../utils' +import { countAndThrowDBError, doMeteredSql } from '../utils' // TODO implement replay handling; this file is currently unused // https://github.com/celo-org/celo-monorepo/issues/9909 -function domainRequests(db: Knex) { - return db(DOMAIN_REQUESTS_TABLE) -} - export async function getDomainRequestRecordExists( db: Knex, domain: D, @@ -25,11 +20,14 @@ export async function getDomainRequestRecordExists( trx: Knex.Transaction, logger: Logger ): Promise { - return meter( + return doMeteredSql( + 'getDomainRequestRecordExists', + ErrorMessage.DATABASE_GET_FAILURE, + logger, async () => { const hash = domainHash(domain).toString('hex') logger.debug({ domain, blindedMessage, hash }, 'Checking if domain request exists') - const existingRequest = await domainRequests(db) + const existingRequest = await db(DOMAIN_REQUESTS_TABLE) .transacting(trx) .where({ [DOMAIN_REQUESTS_COLUMNS.domainHash]: hash, @@ -38,11 +36,7 @@ export async function getDomainRequestRecordExists( .first() .timeout(config.db.timeout) return !!existingRequest - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_GET_FAILURE), - Histograms.dbOpsInstrumentation, - ['getDomainRequestRecordExists'] + } ) } @@ -53,17 +47,20 @@ export async function storeDomainRequestRecord( trx: Knex.Transaction, logger: Logger ) { - return meter( + return doMeteredSql( + 'storeDomainRequestRecord', + ErrorMessage.DATABASE_INSERT_FAILURE, + logger, async () => { - logger.debug({ domain, blindedMessage }, 'Storing domain restricted signature request') - await domainRequests(db) - .transacting(trx) - .insert(toDomainRequestRecord(domain, blindedMessage)) - .timeout(config.db.timeout) - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_INSERT_FAILURE), - Histograms.dbOpsInstrumentation, - ['storeDomainRequestRecord'] + try { + logger.debug({ domain, blindedMessage }, 'Storing domain restricted signature request') + await db(DOMAIN_REQUESTS_TABLE) + .transacting(trx) + .insert(toDomainRequestRecord(domain, blindedMessage)) + .timeout(config.db.timeout) + } catch (err) { + countAndThrowDBError(err, logger, ErrorMessage.DATABASE_INSERT_FAILURE) + } + } ) } diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-state.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-state.ts index 9fca346792e..82ee727f0c7 100644 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-state.ts +++ b/packages/phone-number-privacy/signer/src/common/database/wrappers/domain-state.ts @@ -3,18 +3,13 @@ import { Domain, domainHash } from '@celo/phone-number-privacy-common/lib/domain import Logger from 'bunyan' import { Knex } from 'knex' import { config } from '../../../config' -import { Histograms, meter } from '../../metrics' import { DomainStateRecord, DOMAIN_STATE_COLUMNS, DOMAIN_STATE_TABLE, toDomainStateRecord, } from '../models/domain-state' -import { countAndThrowDBError, queryWithOptionalTrx } from '../utils' - -function domainStates(db: Knex) { - return db(DOMAIN_STATE_TABLE) -} +import { doMeteredSql } from '../utils' export async function setDomainDisabled( db: Knex, @@ -22,21 +17,15 @@ export async function setDomainDisabled( trx: Knex.Transaction, logger: Logger ): Promise { - return meter( - async () => { - const hash = domainHash(domain).toString('hex') - logger.debug({ hash, domain }, 'Disabling domain') - await domainStates(db) - .transacting(trx) - .where(DOMAIN_STATE_COLUMNS.domainHash, hash) - .update(DOMAIN_STATE_COLUMNS.disabled, true) - .timeout(config.db.timeout) - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_UPDATE_FAILURE), - Histograms.dbOpsInstrumentation, - ['disableDomain'] - ) + return doMeteredSql('disableDomain', ErrorMessage.DATABASE_UPDATE_FAILURE, logger, async () => { + const hash = domainHash(domain).toString('hex') + logger.debug({ hash, domain }, 'Disabling domain') + await db(DOMAIN_STATE_TABLE) + .transacting(trx) + .where(DOMAIN_STATE_COLUMNS.domainHash, hash) + .update(DOMAIN_STATE_COLUMNS.disabled, true) + .timeout(config.db.timeout) + }) } export async function getDomainStateRecordOrEmpty( @@ -65,26 +54,27 @@ export async function getDomainStateRecord( logger: Logger, trx?: Knex.Transaction ): Promise { - return meter( + return doMeteredSql( + 'getDomainStateRecord', + ErrorMessage.DATABASE_GET_FAILURE, + logger, async () => { const hash = domainHash(domain).toString('hex') logger.debug({ hash, domain }, 'Getting domain state from db') - const result = await queryWithOptionalTrx(domainStates(db), trx) + + const sql = db(DOMAIN_STATE_TABLE) .where(DOMAIN_STATE_COLUMNS.domainHash, hash) .first() .timeout(config.db.timeout) + const result = await (trx != null ? sql.transacting(trx) : trx) // bools are stored in db as ints (1 or 0), so we must cast them back if (result) { result.disabled = !!result.disabled } return result ?? null - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_GET_FAILURE), - Histograms.dbOpsInstrumentation, - ['getDomainStateRecord'] + } ) } @@ -95,7 +85,10 @@ export async function updateDomainStateRecord( trx: Knex.Transaction, logger: Logger ): Promise { - return meter( + return doMeteredSql( + 'updateDomainStateRecord', + ErrorMessage.DATABASE_UPDATE_FAILURE, + logger, async () => { const hash = domainHash(domain).toString('hex') logger.debug({ hash, domain, domainState }, 'Update domain state') @@ -108,17 +101,13 @@ export async function updateDomainStateRecord( if (!result) { await insertDomainStateRecord(db, domainState, trx, logger) } else { - await domainStates(db) + await db(DOMAIN_STATE_TABLE) .transacting(trx) .where(DOMAIN_STATE_COLUMNS.domainHash, hash) .update(domainState) .timeout(config.db.timeout) } - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_UPDATE_FAILURE), - Histograms.dbOpsInstrumentation, - ['updateDomainStateRecord'] + } ) } @@ -128,15 +117,17 @@ export async function insertDomainStateRecord( trx: Knex.Transaction, logger: Logger ): Promise { - return meter( + return doMeteredSql( + 'insertDomainState', + ErrorMessage.DATABASE_INSERT_FAILURE, + logger, async () => { logger.debug({ domainState }, 'Insert domain state') - await domainStates(db).transacting(trx).insert(domainState).timeout(config.db.timeout) + await db(DOMAIN_STATE_TABLE) + .transacting(trx) + .insert(domainState) + .timeout(config.db.timeout) return domainState - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_INSERT_FAILURE), - Histograms.dbOpsInstrumentation, - ['insertDomainState'] + } ) } diff --git a/packages/phone-number-privacy/signer/src/common/database/wrappers/request.ts b/packages/phone-number-privacy/signer/src/common/database/wrappers/request.ts index 809543f48bd..a5a2e1db56e 100644 --- a/packages/phone-number-privacy/signer/src/common/database/wrappers/request.ts +++ b/packages/phone-number-privacy/signer/src/common/database/wrappers/request.ts @@ -1,67 +1,48 @@ -import { ErrorMessage } from '@celo/phone-number-privacy-common' +// import { ErrorMessage } from '@celo/phone-number-privacy-common' import Logger from 'bunyan' import { Knex } from 'knex' -import { config } from '../../../config' -import { Histograms, meter } from '../../metrics' -import { - PnpSignRequestRecord, - REQUESTS_COLUMNS, - REQUESTS_TABLE, - toPnpSignRequestRecord, -} from '../models/request' -import { countAndThrowDBError, queryWithOptionalTrx } from '../utils' - -function requests(db: Knex, table: REQUESTS_TABLE) { - return db(table) -} +// import { config } from '../../../config' +// import { +// PnpSignRequestRecord, +// // REQUESTS_COLUMNS, +// REQUESTS_TABLE, +// toPnpSignRequestRecord, +// } from '../models/request' +// import { doMeteredSql } from '../utils' export async function getRequestExists( // TODO try insert, if primary key error, then duplicate request - db: Knex, - requestsTable: REQUESTS_TABLE, - account: string, - blindedQuery: string, - logger: Logger, - trx?: Knex.Transaction + _db: Knex, + _account: string, + _blindedQuery: string, + _logger: Logger ): Promise { - return meter( - async () => { - logger.debug( - `Checking if request exists for account: ${account}, blindedQuery: ${blindedQuery}` - ) - const existingRequest = await queryWithOptionalTrx(requests(db, requestsTable), trx) - .where({ - [REQUESTS_COLUMNS.address]: account, - [REQUESTS_COLUMNS.blindedQuery]: blindedQuery, // TODO are we using the primary key correctly?? - }) - .first() - .timeout(config.db.timeout) - return !!existingRequest - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_GET_FAILURE), - Histograms.dbOpsInstrumentation, - ['getRequestExists'] - ) + return Promise.resolve(false) + // logger.debug(`Checking if request exists for account: ${account}, blindedQuery: ${blindedQuery}`) + // return doMeteredSql('getRequestExists', ErrorMessage.DATABASE_GET_FAILURE, logger, async () => { + // const existingRequest = await db(REQUESTS_TABLE) + // .where({ + // [REQUESTS_COLUMNS.address]: account, + // [REQUESTS_COLUMNS.blindedQuery]: blindedQuery, // TODO are we using the primary key correctly?? + // }) + // .first() + // .timeout(config.db.timeout) + // return !!existingRequest // TODO use EXISTS query?? + // }) } -export async function storeRequest( // - db: Knex, - requestsTable: REQUESTS_TABLE, - account: string, - blindedQuery: string, - logger: Logger, - trx?: Knex.Transaction +export async function insertRequest( + _db: Knex, + _account: string, + _blindedQuery: string, + _logger: Logger, + _trx?: Knex.Transaction ): Promise { - return meter( - async () => { - logger.debug(`Storing salt request for: ${account}, blindedQuery: ${blindedQuery}`) - await queryWithOptionalTrx(requests(db, requestsTable), trx) - .insert(toPnpSignRequestRecord(account, blindedQuery)) - .timeout(config.db.timeout) - }, - [], - (err: any) => countAndThrowDBError(err, logger, ErrorMessage.DATABASE_INSERT_FAILURE), - Histograms.dbOpsInstrumentation, - ['storeRequest'] - ) + return + // logger.debug(`Storing salt request for: ${account}, blindedQuery: ${blindedQuery}`) + // return doMeteredSql('insertRequest', ErrorMessage.DATABASE_INSERT_FAILURE, logger, async () => { + // const sql = db(REQUESTS_TABLE) + // .insert(toPnpSignRequestRecord(account, blindedQuery)) + // .timeout(config.db.timeout) + // await (trx != null ? sql.transacting(trx) : sql) + // }) } diff --git a/packages/phone-number-privacy/signer/src/common/error.ts b/packages/phone-number-privacy/signer/src/common/error.ts new file mode 100644 index 00000000000..91060f23691 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/error.ts @@ -0,0 +1,19 @@ +import { ErrorType } from '@celo/phone-number-privacy-common' + +export class OdisError extends Error { + constructor(readonly code: ErrorType, readonly parent?: Error, readonly status: number = 500) { + // This is necessary when extending Error Classes + super(code) // 'Error' breaks prototype chain here + Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain + } +} + +export function wrapError( + valueOrError: Promise, + code: ErrorType, + status: number = 500 +): Promise { + return valueOrError.catch((parentErr) => { + throw new OdisError(code, parentErr, status) + }) +} diff --git a/packages/phone-number-privacy/signer/src/common/handler.ts b/packages/phone-number-privacy/signer/src/common/handler.ts new file mode 100644 index 00000000000..680036329cc --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/handler.ts @@ -0,0 +1,196 @@ +import { timeout } from '@celo/base' +import { + ErrorMessage, + ErrorType, + OdisRequest, + OdisResponse, + PnpQuotaStatus, + send, + SequentialDelayDomainState, + WarningMessage, +} from '@celo/phone-number-privacy-common' +import opentelemetry, { SpanStatusCode } from '@opentelemetry/api' +import { SemanticAttributes } from '@opentelemetry/semantic-conventions' +import Logger from 'bunyan' +import { Request, Response } from 'express' +import * as client from 'prom-client' +import { getSignerVersion } from '../config' +import { OdisError } from './error' +import { Counters, newMeter } from './metrics' + +const tracer = opentelemetry.trace.getTracer('signer-tracer') + +export interface Locals { + logger: Logger +} + +export type PromiseHandler = ( + request: Request<{}, {}, R>, + res: Response, Locals> +) => Promise + +export function catchErrorHandler( + handler: PromiseHandler +): PromiseHandler { + return async (req, res) => { + try { + Counters.requests.labels(req.url).inc() + await handler(req, res) + } catch (err: any) { + // Handle any errors that otherwise managed to escape the proper handlers + const logger = res.locals.logger + logger.error(ErrorMessage.CAUGHT_ERROR_IN_ENDPOINT_HANDLER) + logger.error(err) + Counters.errorsCaughtInEndpointHandler.inc() + + if (!res.headersSent) { + if (err instanceof OdisError) { + sendFailure(err.code, err.status, res) + } else { + sendFailure(ErrorMessage.UNKNOWN_ERROR, 500, res) + } + } else { + // Getting to this error likely indicates that the `perform` process + // does not terminate after sending a response, and then throws an error. + logger.error(ErrorMessage.ERROR_AFTER_RESPONSE_SENT) + Counters.errorsThrownAfterResponseSent.inc() + } + } + } +} + +export function tracingHandler( + handler: PromiseHandler +): PromiseHandler { + return async (req, res) => { + return tracer.startActiveSpan( + req.url, + { + attributes: { + [SemanticAttributes.HTTP_ROUTE]: req.path, + [SemanticAttributes.HTTP_METHOD]: req.method, + [SemanticAttributes.HTTP_CLIENT_IP]: req.ip, + }, + }, + async (span) => { + try { + await handler(req, res) + span.setStatus({ + code: SpanStatusCode.OK, + }) + } catch (err: any) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err instanceof Error ? err.message : 'Fail', + }) + throw err + } finally { + span.end() + } + } + ) + } +} + +export function meteringHandler( + histogram: client.Histogram, + handler: PromiseHandler +): PromiseHandler { + return (req, res) => newMeter(histogram, req.url)(() => handler(req, res)) +} + +export function timeoutHandler( + timeoutMs: number, + handler: PromiseHandler +): PromiseHandler { + // Unique error to be thrown on timeout + const timeoutError = Symbol() // TODO (mcortesi) use Error type + return async (request, response) => { + try { + await timeout(handler, [request, response], timeoutMs, timeoutError) + } catch (err: any) { + if (err === timeoutError) { + Counters.timeouts.inc() + sendFailure(ErrorMessage.TIMEOUT_FROM_SIGNER, 500, response) + } else { + throw err + } + } + } +} + +export function withEnableHandler( + enabled: boolean, + handler: PromiseHandler +): PromiseHandler { + return async (req, res) => { + if (enabled) { + return handler(req, res) + } else { + sendFailure(WarningMessage.API_UNAVAILABLE, 503, res) + } + } +} + +export async function disabledHandler( + _: Request<{}, {}, R>, + response: Response, Locals> +): Promise { + sendFailure(WarningMessage.API_UNAVAILABLE, 503, response) +} + +export function sendFailure( + error: ErrorType, + status: number, + response: Response, + body?: Record // TODO remove any +) { + send( + response, + { + success: false, + version: getSignerVersion(), + error, + ...body, + }, + status, + response.locals.logger + ) +} + +export interface Result { + status: number + body: OdisResponse +} + +export type ResultHandler = ( + request: Request<{}, {}, R>, + res: Response, Locals> +) => Promise> + +export function resultHandler( + resHandler: ResultHandler +): PromiseHandler { + return async (req, res) => { + const result = await resHandler(req, res) + send(res, result.body, result.status, res.locals.logger) + Counters.responses.labels(req.url, result.status.toString()).inc() + } +} + +export function errorResult( + status: number, + error: string, + quotaStatus?: PnpQuotaStatus | { status: SequentialDelayDomainState } +): Result { + // TODO remove any + return { + status, + body: { + success: false, + version: getSignerVersion(), + error, + ...quotaStatus, + }, + } +} diff --git a/packages/phone-number-privacy/signer/src/common/io.ts b/packages/phone-number-privacy/signer/src/common/io.ts deleted file mode 100644 index 96df9e8115b..00000000000 --- a/packages/phone-number-privacy/signer/src/common/io.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - ErrorType, - FailureResponse, - OdisRequest, - OdisResponse, - SignerEndpoint, - SuccessResponse, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import { Session } from './action' - -export abstract class IO { - abstract readonly endpoint: SignerEndpoint - - constructor(readonly enabled: boolean) {} - - abstract init( - request: Request<{}, {}, unknown>, - response: Response> - ): Promise | null> - - abstract validate(request: Request<{}, {}, unknown>): request is Request<{}, {}, R> - - abstract authenticate( - request: Request<{}, {}, R>, - warnings?: string[], - logger?: Logger - ): Promise - - abstract sendFailure( - error: ErrorType, - status: number, - response: Response>, - ...args: unknown[] - ): void - - abstract sendSuccess( - status: number, - response: Response>, - ...args: unknown[] - ): void - - protected inputChecks( - request: Request<{}, {}, unknown>, - response: Response> - ): request is Request<{}, {}, R> { - if (!this.enabled) { - this.sendFailure(WarningMessage.API_UNAVAILABLE, 503, response) - return false - } - if (!this.validate(request)) { - this.sendFailure(WarningMessage.INVALID_INPUT, 400, response) - return false - } - return true - } -} diff --git a/packages/phone-number-privacy/signer/src/common/metrics.ts b/packages/phone-number-privacy/signer/src/common/metrics.ts index 918cbfd7df6..22cac8cc709 100644 --- a/packages/phone-number-privacy/signer/src/common/metrics.ts +++ b/packages/phone-number-privacy/signer/src/common/metrics.ts @@ -1,4 +1,7 @@ +// import opentelemetry from '@opentelemetry/api' import * as client from 'prom-client' + +// const tracer = opentelemetry.trace.getTracer('signer-tracer') const { Counter, Histogram } = client client.collectDefaultMetrics() @@ -29,7 +32,6 @@ export const Counters = { blockchainErrors: new Counter({ name: 'blockchain_errors', help: 'Counter for the number of errors from interacting with the blockchain', - labelNames: ['type'], }), signatureComputationErrors: new Counter({ name: 'signature_computation_errors', @@ -43,14 +45,6 @@ export const Counters = { name: 'requests_with_wallet_address', help: 'Counter for the number of requests in which the account uses a different wallet address', }), - requestsWithVerifiedAccount: new Counter({ - name: 'requests_with_verified_account', - help: 'Counter for the number of requests in which the account is verified', - }), - requestsWithUnverifiedAccountWithMinBalance: new Counter({ - name: 'requests_with_unverified_account_with_min_balance', - help: 'Counter for the number of requests in which the account is not verified but meets min balance', - }), testQuotaBypassedRequests: new Counter({ name: 'test_quota_bypassed_requests', help: 'Counter for the number of requests not requiring quota (testing only)', @@ -59,14 +53,6 @@ export const Counters = { name: 'timeouts', help: 'Counter for the number of signer timeouts as measured by the signer', }), - requestsFailingOpen: new Counter({ - name: 'requests_failing_open', - help: 'Counter for the number of requests bypassing quota or authentication checks due to full-node errors', - }), - requestsFailingClosed: new Counter({ - name: 'requests_failing_closed', - help: 'Counter for the number of requests failing quota or authentication checks due to full-node errors', - }), errorsCaughtInEndpointHandler: new Counter({ name: 'errors_caught_in_endpoint_handler', help: 'Counter for the number of errors caught in the outermost endpoint handler', @@ -88,12 +74,6 @@ export const Histograms = { labelNames: ['endpoint'], buckets, }), - getBlindedSigInstrumentation: new Histogram({ - name: 'get_blinded_sig_instrumentation', - help: 'Histogram tracking latency of blinded sig function by code segment', - labelNames: ['codeSegment'], - buckets, - }), getRemainingQueryCountInstrumentation: new Histogram({ name: 'get_remaining_query_count_instrumentation', help: 'Histogram tracking latency of getRemainingQueryCount function by code segment', @@ -114,17 +94,12 @@ export const Histograms = { }), } -declare type InFunction = (...params: T) => Promise - -export async function meter( - inFunction: InFunction, - params: T, - onError: (err: any) => U, - prometheus: client.Histogram, - labels: string[] -): Promise { - const _meter = prometheus.labels(...labels).startTimer() - return inFunction(...params) - .catch(onError) - .finally(_meter) +export function newMeter( + histogram: client.Histogram, + ...labels: string[] +): (fn: () => Promise) => Promise { + return (fn) => { + const _meter = histogram.labels(...labels).startTimer() + return fn().finally(_meter) + } } diff --git a/packages/phone-number-privacy/signer/src/common/quota.ts b/packages/phone-number-privacy/signer/src/common/quota.ts index 7900eaf7473..6c10d922afc 100644 --- a/packages/phone-number-privacy/signer/src/common/quota.ts +++ b/packages/phone-number-privacy/signer/src/common/quota.ts @@ -6,8 +6,6 @@ import { PnpQuotaStatus, SignMessageRequest, } from '@celo/phone-number-privacy-common' -import { Knex } from 'knex' -import { Session } from './action' import { DomainStateRecord } from './database/models/domain-state' // prettier-ignore @@ -20,15 +18,14 @@ export interface OdisQuotaStatusResult { state: OdisQuotaStatus } -export interface QuotaService { - checkAndUpdateQuotaStatus( - state: OdisQuotaStatus, - session: Session, - trx: Knex.Transaction> - ): Promise> +export interface QService { + /** + * Return the quota for a given account + */ + getQuotaStatus(qAccount: string): Promise> - getQuotaStatus( - session: Session, - trx?: Knex.Transaction> - ): Promise> + /** + * Will execute action if enough quota for the account. And if the action is sucessful, it will decrement avaiable quota + */ + tryQuotaIncrementingAction(quotaAccount: string, action: () => Promise): Promise } diff --git a/packages/phone-number-privacy/signer/src/common/tracing-utils.ts b/packages/phone-number-privacy/signer/src/common/tracing-utils.ts new file mode 100644 index 00000000000..ddd40917905 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/common/tracing-utils.ts @@ -0,0 +1,21 @@ +import opentelemetry, { SpanStatusCode } from '@opentelemetry/api' + +const tracer = opentelemetry.trace.getTracer('signer-tracer') + +export function traceAsyncFunction(traceName: string, fn: () => Promise): Promise { + return tracer.startActiveSpan(traceName, async (span) => { + try { + const res = await fn() + span.setStatus({ code: SpanStatusCode.OK }) + return res + } catch (err: any) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err instanceof Error ? err.message : undefined, + }) + throw err + } finally { + span.end() + } + }) +} diff --git a/packages/phone-number-privacy/signer/src/common/web3/contracts.ts b/packages/phone-number-privacy/signer/src/common/web3/contracts.ts index 7b0801f8f3b..0b783be5150 100644 --- a/packages/phone-number-privacy/signer/src/common/web3/contracts.ts +++ b/packages/phone-number-privacy/signer/src/common/web3/contracts.ts @@ -1,5 +1,5 @@ -import { NULL_ADDRESS, retryAsyncWithBackOffAndTimeout } from '@celo/base' -import { ContractKit, StableToken } from '@celo/contractkit' +import { retryAsyncWithBackOffAndTimeout } from '@celo/base' +import { ContractKit } from '@celo/contractkit' import { FULL_NODE_TIMEOUT_IN_MS, RETRY_COUNT, @@ -7,174 +7,7 @@ import { } from '@celo/phone-number-privacy-common' import { BigNumber } from 'bignumber.js' import Logger from 'bunyan' -import { Counters, Histograms, Labels, meter } from '../metrics' - -export async function getBlockNumber(kit: ContractKit): Promise { - return meter( - retryAsyncWithBackOffAndTimeout, - [ - () => kit.connection.getBlockNumber(), - RETRY_COUNT, - [], - RETRY_DELAY_IN_MS, - undefined, - FULL_NODE_TIMEOUT_IN_MS, - ], - (err: any) => { - Counters.blockchainErrors.labels(Labels.READ).inc() - throw err - }, - Histograms.getBlindedSigInstrumentation, - ['getBlockNumber'] - ) -} - -export async function getTransactionCount( - kit: ContractKit, - logger: Logger, - endpoint: string, - ...addresses: string[] -): Promise { - const _getTransactionCount = (...params: string[]) => - Promise.all( - params - .filter((address) => address !== NULL_ADDRESS) - .map((address) => - retryAsyncWithBackOffAndTimeout( - () => kit.connection.getTransactionCount(address), - RETRY_COUNT, - [], - RETRY_DELAY_IN_MS, - undefined, - FULL_NODE_TIMEOUT_IN_MS - ).catch((err) => { - Counters.blockchainErrors.labels(Labels.READ).inc() - throw err - }) - ) - ).then((values) => { - logger.trace({ addresses, txCounts: values }, 'Fetched txCounts for addresses') - return values.reduce((a, b) => a + b) - }) - return meter( - _getTransactionCount, - addresses.filter((address) => address !== NULL_ADDRESS), - (err: any) => { - throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getTransactionCount', endpoint] - ) -} - -export async function getStableTokenBalance( - kit: ContractKit, - stableToken: StableToken, - logger: Logger, - endpoint: string, - ...addresses: string[] -): Promise { - const _getStableTokenBalance = (...params: string[]) => - Promise.all( - params - .filter((address) => address !== NULL_ADDRESS) - .map((address) => - retryAsyncWithBackOffAndTimeout( - async () => (await kit.contracts.getStableToken(stableToken)).balanceOf(address), - RETRY_COUNT, - [], - RETRY_DELAY_IN_MS, - undefined, - FULL_NODE_TIMEOUT_IN_MS - ).catch((err) => { - Counters.blockchainErrors.labels(Labels.READ).inc() - throw err - }) - ) - ).then((values) => { - logger.trace( - { addresses, balances: values.map((bn) => bn.toString()) }, - `Fetched ${stableToken} balances for addresses` - ) - return values.reduce((a, b) => a.plus(b)) - }) - return meter( - _getStableTokenBalance, - addresses, - (err: any) => { - throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getStableTokenBalance', endpoint] - ) -} - -export async function getCeloBalance( - kit: ContractKit, - logger: Logger, - endpoint: string, - ...addresses: string[] -): Promise { - const _getCeloBalance = (...params: string[]) => - Promise.all( - params - .filter((address) => address !== NULL_ADDRESS) - .map((address) => - retryAsyncWithBackOffAndTimeout( - async () => (await kit.contracts.getGoldToken()).balanceOf(address), - RETRY_COUNT, - [], - RETRY_DELAY_IN_MS, - undefined, - FULL_NODE_TIMEOUT_IN_MS - ).catch((err) => { - Counters.blockchainErrors.labels(Labels.READ).inc() - throw err - }) - ) - ).then((values) => { - logger.trace( - { addresses, balances: values.map((bn) => bn.toString()) }, - 'Fetched celo balances for addresses' - ) - return values.reduce((a, b) => a.plus(b)) - }) - return meter( - _getCeloBalance, - addresses, - (err: any) => { - throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getStableTokenBalance', endpoint] - ) -} - -export async function getWalletAddress( - kit: ContractKit, - logger: Logger, - account: string, - endpoint: string -): Promise { - return meter( - retryAsyncWithBackOffAndTimeout, - [ - async () => (await kit.contracts.getAccounts()).getWalletAddress(account), - RETRY_COUNT, - [], - RETRY_DELAY_IN_MS, - undefined, - FULL_NODE_TIMEOUT_IN_MS, - ], - (err: any) => { - logger.error({ err, account }, 'failed to get wallet address for account') - Counters.blockchainErrors.labels(Labels.READ).inc() - return NULL_ADDRESS - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getWalletAddress', endpoint] - ) -} +import { Counters, Histograms, newMeter } from '../metrics' export async function getOnChainOdisPayments( kit: ContractKit, @@ -182,22 +15,23 @@ export async function getOnChainOdisPayments( account: string, endpoint: string ): Promise { - return meter( - retryAsyncWithBackOffAndTimeout, - [ + const _meter = newMeter( + Histograms.getRemainingQueryCountInstrumentation, + 'getOnChainOdisPayments', + endpoint + ) + return _meter(() => + retryAsyncWithBackOffAndTimeout( async () => (await kit.contracts.getOdisPayments()).totalPaidCUSD(account), RETRY_COUNT, [], RETRY_DELAY_IN_MS, undefined, - FULL_NODE_TIMEOUT_IN_MS, - ], - (err: any) => { + FULL_NODE_TIMEOUT_IN_MS + ).catch((err: any) => { logger.error({ err, account }, 'failed to get on-chain odis balance for account') - Counters.blockchainErrors.labels(Labels.READ).inc() + Counters.blockchainErrors.inc() throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getOnChainOdisPayments', endpoint] + }) ) } diff --git a/packages/phone-number-privacy/signer/src/config.ts b/packages/phone-number-privacy/signer/src/config.ts index 1e804490dc2..ef504d18e71 100644 --- a/packages/phone-number-privacy/signer/src/config.ts +++ b/packages/phone-number-privacy/signer/src/config.ts @@ -53,7 +53,6 @@ export interface SignerConfig { } phoneNumberPrivacy: { enabled: boolean - shouldFailOpen: boolean } } blockchain: BlockchainConfig @@ -128,7 +127,6 @@ export const config: SignerConfig = { }, phoneNumberPrivacy: { enabled: toBool(env.PHONE_NUMBER_PRIVACY_API_ENABLED, false), - shouldFailOpen: toBool(env.FULL_NODE_ERRORS_SHOULD_FAIL_OPEN, false), }, }, blockchain: { diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/disable/action.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/disable/action.ts index 779685b0123..1cf71fb0d11 100644 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/disable/action.ts +++ b/packages/phone-number-privacy/signer/src/domain/endpoints/disable/action.ts @@ -1,7 +1,13 @@ -import { timeout } from '@celo/base' -import { DisableDomainRequest, domainHash } from '@celo/phone-number-privacy-common' +import { + DisableDomainRequest, + disableDomainRequestSchema, + domainHash, + DomainSchema, + verifyDisableDomainRequestAuthenticity, + WarningMessage, +} from '@celo/phone-number-privacy-common' +import { Request } from 'express' import { Knex } from 'knex' -import { Action } from '../../../common/action' import { toSequentialDelayDomainState } from '../../../common/database/models/domain-state' import { createEmptyDomainStateRecord, @@ -9,19 +15,23 @@ import { insertDomainStateRecord, setDomainDisabled, } from '../../../common/database/wrappers/domain-state' -import { SignerConfig } from '../../../config' -import { DomainSession } from '../../session' -import { DomainDisableIO } from './io' +import { errorResult, ResultHandler } from '../../../common/handler' +import { getSignerVersion } from '../../../config' + +export function domainDisable(db: Knex): ResultHandler { + return async (request, response) => { + const { logger } = response.locals + + if (!isValidRequest(request)) { + return errorResult(400, WarningMessage.INVALID_INPUT) + } + if (!verifyDisableDomainRequestAuthenticity(request.body)) { + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) + } -export class DomainDisableAction implements Action { - constructor(readonly db: Knex, readonly config: SignerConfig, readonly io: DomainDisableIO) {} + const { domain } = request.body - public async perform( - session: DomainSession, - timeoutError: symbol - ): Promise { - const domain = session.request.body.domain - session.logger.info( + logger.info( { name: domain.name, version: domain.version, @@ -29,35 +39,38 @@ export class DomainDisableAction implements Action { }, 'Processing request to disable domain' ) - // Inside a database transaction, update or create the domain to mark it disabled. - const res = await this.db.transaction(async (trx) => { - const disableDomainHandler = async () => { - const domainStateRecord = - (await getDomainStateRecord(this.db, domain, session.logger, trx)) ?? - (await insertDomainStateRecord( - this.db, - createEmptyDomainStateRecord(domain, true), - trx, - session.logger - )) - if (!domainStateRecord.disabled) { - await setDomainDisabled(this.db, domain, trx, session.logger) - domainStateRecord.disabled = true - } - return { - success: true, - status: 200, - domainStateRecord, - } + + const res = await db.transaction(async (trx) => { + const domainStateRecord = + (await getDomainStateRecord(db, domain, logger, trx)) ?? + (await insertDomainStateRecord(db, createEmptyDomainStateRecord(domain, true), trx, logger)) + if (!domainStateRecord.disabled) { + await setDomainDisabled(db, domain, trx, logger) + domainStateRecord.disabled = true + } + return { + // TODO revisit this + success: true, + status: 200, + domainStateRecord, } - // Ensure timeouts roll back DB trx - return timeout(disableDomainHandler, [], this.config.timeout, timeoutError) + // Note: we previously timed out inside the trx to ensure timeouts roll back DB trx + // return timeout(disableDomainHandler, [], this.config.timeout, timeoutError) }) - this.io.sendSuccess( - res.status, - session.response, - toSequentialDelayDomainState(res.domainStateRecord) - ) + return { + status: res.status, + body: { + success: true, + version: getSignerVersion(), + status: toSequentialDelayDomainState(res.domainStateRecord), + }, + } } } + +function isValidRequest( + request: Request<{}, {}, unknown> +): request is Request<{}, {}, DisableDomainRequest> { + return disableDomainRequestSchema(DomainSchema).is(request.body) +} diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/disable/io.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/disable/io.ts deleted file mode 100644 index f77a9e8cd34..00000000000 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/disable/io.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - DisableDomainRequest, - disableDomainRequestSchema, - DisableDomainResponse, - DisableDomainResponseFailure, - DisableDomainResponseSuccess, - DomainSchema, - DomainState, - ErrorType, - send, - SignerEndpoint, - verifyDisableDomainRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request, Response } from 'express' -import { IO } from '../../../common/io' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { DomainSession } from '../../session' - -export class DomainDisableIO extends IO { - readonly endpoint = SignerEndpoint.DISABLE_DOMAIN - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - // Input checks sends a response to the user internally. - if (!super.inputChecks(request, response)) { - return null - } - if (!(await this.authenticate(request))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - return new DomainSession(request, response) - } - - validate(request: Request<{}, {}, unknown>): request is Request<{}, {}, DisableDomainRequest> { - return disableDomainRequestSchema(DomainSchema).is(request.body) - } - - authenticate(request: Request<{}, {}, DisableDomainRequest>): Promise { - return Promise.resolve(verifyDisableDomainRequestAuthenticity(request.body)) - } - - sendSuccess( - status: number, - response: Response, - domainState: DomainState - ) { - send( - response, - { - success: true, - version: getSignerVersion(), - status: domainState, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } - - sendFailure(error: ErrorType, status: number, response: Response) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/quota/action.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/quota/action.ts index babddd4a7cf..e397b2a050c 100644 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/quota/action.ts +++ b/packages/phone-number-privacy/signer/src/domain/endpoints/quota/action.ts @@ -1,35 +1,50 @@ -import { timeout } from '@celo/base' -import { domainHash, DomainQuotaStatusRequest } from '@celo/phone-number-privacy-common' -import { Action } from '../../../common/action' +import { + domainHash, + DomainQuotaStatusRequest, + domainQuotaStatusRequestSchema, + DomainSchema, + verifyDomainQuotaStatusRequestAuthenticity, + WarningMessage, +} from '@celo/phone-number-privacy-common' +import { Request } from 'express' import { toSequentialDelayDomainState } from '../../../common/database/models/domain-state' -import { SignerConfig } from '../../../config' +import { errorResult, ResultHandler } from '../../../common/handler' +import { getSignerVersion } from '../../../config' import { DomainQuotaService } from '../../services/quota' -import { DomainSession } from '../../session' -import { DomainQuotaIO } from './io' -export class DomainQuotaAction implements Action { - constructor( - readonly config: SignerConfig, - readonly quotaService: DomainQuotaService, - readonly io: DomainQuotaIO - ) {} +export function domainQuota(quota: DomainQuotaService): ResultHandler { + return async (request, response) => { + const { logger } = response.locals - public async perform( - session: DomainSession, - timeoutError: symbol - ): Promise { - const domain = session.request.body.domain - session.logger.info('Processing request to get domain quota status', { + if (!isValidRequest(request)) { + return errorResult(400, WarningMessage.INVALID_INPUT) + } + if (!verifyDomainQuotaStatusRequestAuthenticity(request.body)) { + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) + } + + const { domain } = request.body + + logger.info('Processing request to get domain quota status', { name: domain.name, version: domain.version, hash: domainHash(domain).toString('hex'), }) - const domainStateRecord = await timeout( - () => this.quotaService.getQuotaStatus(session), - [], - this.config.timeout, - timeoutError - ) - this.io.sendSuccess(200, session.response, toSequentialDelayDomainState(domainStateRecord)) + const domainStateRecord = await quota.getQuotaStatus(domain, logger) + + return { + status: 200, + body: { + success: true, + version: getSignerVersion(), + status: toSequentialDelayDomainState(domainStateRecord), + }, + } } } + +function isValidRequest( + request: Request<{}, {}, unknown> +): request is Request<{}, {}, DomainQuotaStatusRequest> { + return domainQuotaStatusRequestSchema(DomainSchema).is(request.body) +} diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/quota/io.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/quota/io.ts deleted file mode 100644 index 8dca82b76bf..00000000000 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/quota/io.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - DomainQuotaStatusRequest, - domainQuotaStatusRequestSchema, - DomainQuotaStatusResponse, - DomainQuotaStatusResponseFailure, - DomainQuotaStatusResponseSuccess, - DomainSchema, - DomainState, - ErrorType, - send, - SignerEndpoint, - verifyDomainQuotaStatusRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request, Response } from 'express' -import { IO } from '../../../common/io' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { DomainSession } from '../../session' - -export class DomainQuotaIO extends IO { - readonly endpoint = SignerEndpoint.DOMAIN_QUOTA_STATUS - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - if (!super.inputChecks(request, response)) { - return null - } - if (!(await this.authenticate(request))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - return new DomainSession(request, response) - } - - validate( - request: Request<{}, {}, unknown> - ): request is Request<{}, {}, DomainQuotaStatusRequest> { - return domainQuotaStatusRequestSchema(DomainSchema).is(request.body) - } - - authenticate(request: Request<{}, {}, DomainQuotaStatusRequest>): Promise { - return Promise.resolve(verifyDomainQuotaStatusRequestAuthenticity(request.body)) - } - - sendSuccess( - status: number, - response: Response, - domainState: DomainState - ) { - send( - response, - { - success: true, - version: getSignerVersion(), - status: domainState, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } - - sendFailure( - error: ErrorType, - status: number, - response: Response - ) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/sign/action.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/sign/action.ts index 446baa506cb..7cc0ec853c2 100644 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/sign/action.ts +++ b/packages/phone-number-privacy/signer/src/domain/endpoints/sign/action.ts @@ -1,25 +1,30 @@ -import { timeout } from '@celo/base' import { Domain, domainHash, DomainRestrictedSignatureRequest, + domainRestrictedSignatureRequestSchema, + DomainSchema, ErrorType, getRequestKeyVersion, + KEY_VERSION_HEADER, + requestHasValidKeyVersion, ThresholdPoprfServer, + verifyDomainRestrictedSignatureRequestAuthenticity, WarningMessage, } from '@celo/phone-number-privacy-common' import { EIP712Optional } from '@celo/utils/lib/sign-typed-data-utils' +import Logger from 'bunyan' +import { Request } from 'express' import { Knex } from 'knex' -import { Action, Session } from '../../../common/action' import { DomainStateRecord, toSequentialDelayDomainState, } from '../../../common/database/models/domain-state' +import { errorResult, ResultHandler } from '../../../common/handler' import { DefaultKeyName, Key, KeyProvider } from '../../../common/key-management/key-provider-base' -import { SignerConfig } from '../../../config' +import { OdisQuotaStatusResult } from '../../../common/quota' +import { getSignerVersion, SignerConfig } from '../../../config' import { DomainQuotaService } from '../../services/quota' -import { DomainSession } from '../../session' -import { DomainSignIO } from './io' type TrxResult = | { @@ -36,21 +41,28 @@ type TrxResult = signature: string } -export class DomainSignAction implements Action { - constructor( - readonly db: Knex, - readonly config: SignerConfig, - readonly quota: DomainQuotaService, - readonly keyProvider: KeyProvider, - readonly io: DomainSignIO - ) {} - - public async perform( - session: DomainSession, - timeoutError: symbol - ): Promise { - const domain = session.request.body.domain - session.logger.info( +export function domainSign( + db: Knex, + config: SignerConfig, + quota: DomainQuotaService, + keyProvider: KeyProvider +): ResultHandler { + return async (request, response) => { + const { logger } = response.locals + + if (!isValidRequest(request)) { + return errorResult(400, WarningMessage.INVALID_INPUT) + } + if (!requestHasValidKeyVersion(request, logger)) { + return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) + } + if (!verifyDomainRestrictedSignatureRequestAuthenticity(request.body)) { + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) + } + + const { domain } = request.body + + logger.info( { name: domain.name, version: domain.version, @@ -58,118 +70,125 @@ export class DomainSignAction implements Action { - const domainSignHandler = async (): Promise => { - // Get the current domain state record, or use an empty record if one does not exist. - const domainStateRecord = await this.quota.getQuotaStatus(session, trx) - - // Note that this action occurs in the same transaction as the remainder of the siging - // action. As a result, this is included here rather than in the authentication function. - if (!this.nonceCheck(domainStateRecord, session)) { - return { - success: false, - status: 401, - domainStateRecord, - error: WarningMessage.INVALID_NONCE, - } - } + const res: TrxResult = await db.transaction(async (trx) => { + // Get the current domain state record, or use an empty record if one does not exist. + const domainStateRecord: DomainStateRecord = await quota.getQuotaStatus(domain, logger, trx) - const quotaStatus = await this.quota.checkAndUpdateQuotaStatus( + // Note that this action occurs in the same transaction as the remainder of the siging + // action. As a result, this is included here rather than in the authentication function. + if (!nonceCheck(domainStateRecord, request.body, logger)) { + return { + // TODO revisit this + success: false, + status: 401, domainStateRecord, - session, - trx - ) - - if (!quotaStatus.sufficient) { - session.logger.warn( - { - name: domain.name, - version: domain.version, - hash: domainHash(domain), - }, - `Exceeded quota` - ) - return { - success: false, - status: 429, - domainStateRecord: quotaStatus.state, - error: WarningMessage.EXCEEDED_QUOTA, - } - } - - const key: Key = { - version: - getRequestKeyVersion(session.request, session.logger) ?? - this.config.keystore.keys.domains.latest, - name: DefaultKeyName.DOMAINS, + error: WarningMessage.INVALID_NONCE, } + } - // Compute evaluation inside transaction so it will rollback on error. - const evaluation = await this.eval( - domain, - session.request.body.blindedMessage, - key, - session + const quotaStatus: OdisQuotaStatusResult = + await quota.checkAndUpdateQuotaStatus( + // TODO types + domainStateRecord, + request.body.domain, + trx, + logger ) + if (!quotaStatus.sufficient) { + logger.warn( + { + name: domain.name, + version: domain.version, + hash: domainHash(domain), + }, + `Exceeded quota` + ) return { - success: true, - status: 200, + success: false, + status: 429, domainStateRecord: quotaStatus.state, - key, - signature: evaluation.toString('base64'), + error: WarningMessage.EXCEEDED_QUOTA, } } - // Ensure timeouts roll back DB trx - return timeout(domainSignHandler, [], this.config.timeout, timeoutError) + + const key: Key = { + version: getRequestKeyVersion(request, logger) ?? config.keystore.keys.domains.latest, + name: DefaultKeyName.DOMAINS, + } + + // Compute evaluation inside transaction so it will rollback on error. + const evaluation: Buffer = await sign( + domain, + request.body.blindedMessage, + key, + logger, + keyProvider + ) + + return { + success: true, + status: 200, + domainStateRecord: quotaStatus.state, + key, + signature: evaluation.toString('base64'), + } }) if (res.success) { - this.io.sendSuccess( - res.status, - session.response, - res.key, - res.signature, - toSequentialDelayDomainState(res.domainStateRecord) - ) + response.set(KEY_VERSION_HEADER, res.key.version.toString()) + return { + status: 200, + body: { + success: true, + version: getSignerVersion(), + signature: res.signature, + status: toSequentialDelayDomainState(res.domainStateRecord), + }, + } } else { - this.io.sendFailure( - res.error, - res.status, - session.response, - toSequentialDelayDomainState(res.domainStateRecord) - ) + return errorResult(res.status, res.error, { + status: toSequentialDelayDomainState(res.domainStateRecord), + }) } } +} - private nonceCheck( - domainStateRecord: DomainStateRecord, - session: DomainSession - ): boolean { - const nonce: EIP712Optional = session.request.body.options.nonce - if (!nonce.defined) { - session.logger.info('Nonce is undefined') - return false - } - return nonce.value >= domainStateRecord.counter - } +function isValidRequest( + request: Request<{}, {}, unknown> +): request is Request<{}, {}, DomainRestrictedSignatureRequest> { + return domainRestrictedSignatureRequestSchema(DomainSchema).is(request.body) +} - private async eval( - domain: Domain, - blindedMessage: string, - key: Key, - session: Session - ): Promise { - let privateKey: string - try { - privateKey = await this.keyProvider.getPrivateKeyOrFetchFromStore(key) - } catch (err) { - session.logger.error({ key }, 'Requested key version not supported') - session.logger.error(err) - throw new Error(WarningMessage.INVALID_KEY_VERSION_REQUEST) - } +function nonceCheck( + domainStateRecord: DomainStateRecord, + body: DomainRestrictedSignatureRequest, + logger: Logger +): boolean { + const nonce: EIP712Optional = body.options.nonce + if (!nonce.defined) { + logger.info('Nonce is undefined') + return false + } + return nonce.value >= domainStateRecord.counter +} - const server = new ThresholdPoprfServer(Buffer.from(privateKey, 'hex')) - return server.blindPartialEval(domainHash(domain), Buffer.from(blindedMessage, 'base64')) +async function sign( + domain: Domain, + blindedMessage: string, + key: Key, + logger: Logger, + keyProvider: KeyProvider +): Promise { + let privateKey: string + try { + privateKey = await keyProvider.getPrivateKeyOrFetchFromStore(key) + } catch (err) { + logger.error({ key }, 'Requested key version not supported') + logger.error(err) + throw new Error(WarningMessage.INVALID_KEY_VERSION_REQUEST) } + + const server = new ThresholdPoprfServer(Buffer.from(privateKey, 'hex')) + return server.blindPartialEval(domainHash(domain), Buffer.from(blindedMessage, 'base64')) } diff --git a/packages/phone-number-privacy/signer/src/domain/endpoints/sign/io.ts b/packages/phone-number-privacy/signer/src/domain/endpoints/sign/io.ts deleted file mode 100644 index a55a0f9f396..00000000000 --- a/packages/phone-number-privacy/signer/src/domain/endpoints/sign/io.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - DomainRestrictedSignatureRequest, - domainRestrictedSignatureRequestSchema, - DomainRestrictedSignatureResponse, - DomainRestrictedSignatureResponseFailure, - DomainRestrictedSignatureResponseSuccess, - DomainSchema, - DomainState, - ErrorType, - KEY_VERSION_HEADER, - requestHasValidKeyVersion, - send, - SignerEndpoint, - verifyDomainRestrictedSignatureRequestAuthenticity, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { Request, Response } from 'express' -import { IO } from '../../../common/io' -import { Key } from '../../../common/key-management/key-provider-base' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { DomainSession } from '../../session' - -export class DomainSignIO extends IO { - readonly endpoint = SignerEndpoint.DOMAIN_SIGN - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - if (!super.inputChecks(request, response)) { - return null - } - if (!requestHasValidKeyVersion(request, response.locals.logger)) { - this.sendFailure(WarningMessage.INVALID_KEY_VERSION_REQUEST, 400, response) - return null - } - if (!(await this.authenticate(request))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - return new DomainSession(request, response) - } - - validate( - request: Request<{}, {}, unknown> - ): request is Request<{}, {}, DomainRestrictedSignatureRequest> { - return domainRestrictedSignatureRequestSchema(DomainSchema).is(request.body) - } - - authenticate(request: Request<{}, {}, DomainRestrictedSignatureRequest>): Promise { - return Promise.resolve(verifyDomainRestrictedSignatureRequestAuthenticity(request.body)) - } - - sendSuccess( - status: number, - response: Response, - key: Key, - signature: string, - domainState: DomainState - ) { - response.set(KEY_VERSION_HEADER, key.version.toString()) - send( - response, - { - success: true, - version: getSignerVersion(), - signature, - status: domainState, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } - - sendFailure( - error: ErrorType, - status: number, - response: Response, - domainState?: DomainState - ) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - status: domainState, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/domain/services/quota.ts b/packages/phone-number-privacy/signer/src/domain/services/quota.ts index 475be753afa..83ed83a1db9 100644 --- a/packages/phone-number-privacy/signer/src/domain/services/quota.ts +++ b/packages/phone-number-privacy/signer/src/domain/services/quota.ts @@ -4,6 +4,7 @@ import { DomainRestrictedSignatureRequest, ErrorMessage, isSequentialDelayDomain, + SequentialDelayDomain, } from '@celo/phone-number-privacy-common' import { Knex } from 'knex' import { @@ -15,23 +16,23 @@ import { getDomainStateRecordOrEmpty, updateDomainStateRecord, } from '../../common/database/wrappers/domain-state' -import { OdisQuotaStatusResult, QuotaService } from '../../common/quota' -import { DomainSession } from '../session' +import { OdisQuotaStatusResult } from '../../common/quota' +import Logger from 'bunyan' declare type QuotaDependentDomainRequest = | DomainQuotaStatusRequest | DomainRestrictedSignatureRequest -export class DomainQuotaService implements QuotaService { +export class DomainQuotaService { constructor(readonly db: Knex) {} async checkAndUpdateQuotaStatus( state: DomainStateRecord, - session: DomainSession, + domain: SequentialDelayDomain, trx: Knex.Transaction, + logger: Logger, attemptTime?: number ): Promise> { - const { domain } = session.request.body // Timestamp precision is lowered to seconds to reduce the chance of effective timing attacks. attemptTime = attemptTime ?? Math.floor(Date.now() / 1000) if (isSequentialDelayDomain(domain)) { @@ -44,7 +45,7 @@ export class DomainQuotaService implements QuotaService, + domain: SequentialDelayDomain, + logger: Logger, trx?: Knex.Transaction ): Promise { - return getDomainStateRecordOrEmpty(this.db, session.request.body.domain, session.logger, trx) + return getDomainStateRecordOrEmpty(this.db, domain, logger, trx) } } diff --git a/packages/phone-number-privacy/signer/src/domain/session.ts b/packages/phone-number-privacy/signer/src/domain/session.ts deleted file mode 100644 index 3d9080aab1d..00000000000 --- a/packages/phone-number-privacy/signer/src/domain/session.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DomainRequest, OdisResponse } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' - -export class DomainSession { - readonly logger: Logger - - public constructor( - readonly request: Request<{}, {}, R>, - readonly response: Response> - ) { - this.logger = response.locals.logger - } -} diff --git a/packages/phone-number-privacy/signer/src/index.ts b/packages/phone-number-privacy/signer/src/index.ts index 29c531d8a09..12f3dbe91bc 100644 --- a/packages/phone-number-privacy/signer/src/index.ts +++ b/packages/phone-number-privacy/signer/src/index.ts @@ -2,11 +2,16 @@ import { getContractKit, rootLogger } from '@celo/phone-number-privacy-common' import { initDatabase } from './common/database/database' import { initKeyProvider } from './common/key-management/key-provider' import { KeyProvider } from './common/key-management/key-provider-base' -import { config, DEV_MODE } from './config' +import { config, DEV_MODE, SupportedDatabase, SupportedKeystore } from './config' import { startSigner } from './server' require('dotenv').config() +if (DEV_MODE) { + config.db.type = SupportedDatabase.Sqlite + config.keystore.type = SupportedKeystore.MOCK_SECRET_MANAGER +} + async function start() { const logger = rootLogger(config.serviceName) logger.info(`Starting. Dev mode: ${DEV_MODE}`) @@ -23,13 +28,11 @@ async function start() { .setTimeout(backupTimeout) } -if (!DEV_MODE) { - start().catch((err) => { - const logger = rootLogger(config.serviceName) - logger.error({ err }, 'Fatal error occured. Exiting') - process.exit(1) - }) -} +start().catch((err) => { + const logger = rootLogger(config.serviceName) + logger.error({ err }, 'Fatal error occured. Exiting') + process.exit(1) +}) export { initDatabase } from './common/database/database' export { initKeyProvider } from './common/key-management/key-provider' diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts index f0d0b57af4b..0dcc5f47db3 100644 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts +++ b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts @@ -1,35 +1,63 @@ -import { timeout } from '@celo/base' -import { ErrorMessage, PnpQuotaRequest } from '@celo/phone-number-privacy-common' -import { Action } from '../../../common/action' -import { SignerConfig } from '../../../config' -import { PnpQuotaService } from '../../services/quota' -import { PnpSession } from '../../session' -import { PnpQuotaIO } from './io' - -export class PnpQuotaAction implements Action { - constructor( - readonly config: SignerConfig, - readonly quota: PnpQuotaService, - readonly io: PnpQuotaIO - ) {} - - public async perform(session: PnpSession, timeoutError: symbol): Promise { - const quotaStatus = await timeout( - () => this.quota.getQuotaStatus(session), - [], - this.config.timeout, - timeoutError - ) - if (quotaStatus.performedQueryCount > -1 && quotaStatus.totalQuota > -1) { - this.io.sendSuccess(200, session.response, quotaStatus, session.errors) - return +import { + authenticateUser, + ErrorType, + hasValidAccountParam, + isBodyReasonablySized, + PnpQuotaRequest, + PnpQuotaRequestSchema, + WarningMessage, +} from '@celo/phone-number-privacy-common' +import { Request } from 'express' +import { errorResult, ResultHandler } from '../../../common/handler' +import { getSignerVersion } from '../../../config' +import { AccountService } from '../../services/account-service' +import { PnpRequestService } from '../../services/request-service' + +export function pnpQuota( + requestService: PnpRequestService, + accountService: AccountService +): ResultHandler { + return async (request, response) => { + const logger = response.locals.logger + + if (!isValidRequest(request)) { + return errorResult(400, WarningMessage.INVALID_INPUT) + } + + const warnings: ErrorType[] = [] + const ctx = { + url: request.url, + logger, + errors: warnings, + } + + const account = await accountService.getAccount(request.body.account) + + if (!(await authenticateUser(request, logger, async (_) => account.dek, warnings))) { + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) + } + + const usedQuota = await requestService.getUsedQuotaForAccount(request.body.account, ctx) + + return { + status: 200, + body: { + success: true, + version: getSignerVersion(), + performedQueryCount: usedQuota, + totalQuota: account.pnpTotalQuota, + warnings, + }, } - this.io.sendFailure( - quotaStatus.performedQueryCount === -1 - ? ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT - : ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA, - 500, - session.response - ) } } + +function isValidRequest( + request: Request<{}, {}, unknown> +): request is Request<{}, {}, PnpQuotaRequest> { + return ( + PnpQuotaRequestSchema.is(request.body) && + hasValidAccountParam(request.body) && + isBodyReasonablySized(request.body) + ) +} diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.ts deleted file mode 100644 index 3495cdcf22b..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - authenticateUser, - ErrorType, - hasValidAccountParam, - isBodyReasonablySized, - PnpQuotaRequest, - PnpQuotaRequestSchema, - PnpQuotaResponse, - PnpQuotaResponseFailure, - PnpQuotaResponseSuccess, - PnpQuotaStatus, - send, - SignerEndpoint, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import { IO } from '../../../common/io' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { PnpSession } from '../../session' - -export class PnpQuotaIO extends IO { - readonly endpoint = SignerEndpoint.PNP_QUOTA - - constructor( - readonly enabled: boolean, - readonly shouldFailOpen: boolean, - readonly fullNodeTimeoutMs: number, - readonly fullNodeRetryCount: number, - readonly fullNodeRetryDelayMs: number, - readonly kit: ContractKit - ) { - super(enabled) - } - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - const warnings: ErrorType[] = [] - if (!super.inputChecks(request, response)) { - return null - } - if (!(await this.authenticate(request, warnings, response.locals.logger))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - const session = new PnpSession(request, response) - session.errors.push(...warnings) - return session - } - - validate(request: Request<{}, {}, unknown>): request is Request<{}, {}, PnpQuotaRequest> { - return ( - PnpQuotaRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - isBodyReasonablySized(request.body) - ) - } - - async authenticate( - request: Request<{}, {}, PnpQuotaRequest>, - warnings: ErrorType[], - logger: Logger - ): Promise { - return authenticateUser( - request, - this.kit, - logger, - this.shouldFailOpen, - warnings, - this.fullNodeTimeoutMs, - this.fullNodeRetryCount, - this.fullNodeRetryDelayMs - ) - } - - sendSuccess( - status: number, - response: Response, - quotaStatus: PnpQuotaStatus, - warnings: string[] - ) { - send( - response, - { - success: true, - version: getSignerVersion(), - ...quotaStatus, - warnings, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } - - sendFailure(error: ErrorType, status: number, response: Response) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts index 4f777a3d9bb..88493b8d3b3 100644 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts +++ b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts @@ -1,160 +1,185 @@ -import { timeout } from '@celo/base' import { + authenticateUser, ErrorMessage, + ErrorType, getRequestKeyVersion, + hasValidAccountParam, + hasValidBlindedPhoneNumberParam, + isBodyReasonablySized, + KEY_VERSION_HEADER, + requestHasValidKeyVersion, SignMessageRequest, + SignMessageRequestSchema, WarningMessage, } from '@celo/phone-number-privacy-common' +import { Request } from 'express' import { Knex } from 'knex' -import { Action, Session } from '../../../common/action' import { computeBlindedSignature } from '../../../common/bls/bls-cryptography-client' -import { REQUESTS_TABLE } from '../../../common/database/models/request' import { getRequestExists } from '../../../common/database/wrappers/request' import { DefaultKeyName, Key, KeyProvider } from '../../../common/key-management/key-provider-base' -import { Counters, Histograms, meter } from '../../../common/metrics' -import { SignerConfig } from '../../../config' -import { PnpQuotaService } from '../../services/quota' -import { PnpSession } from '../../session' -import { PnpSignIO } from './io' - -export class PnpSignAction implements Action { - protected readonly requestsTable: REQUESTS_TABLE = REQUESTS_TABLE.ONCHAIN - - constructor( - readonly db: Knex, - readonly config: SignerConfig, - readonly quota: PnpQuotaService, - readonly keyProvider: KeyProvider, - readonly io: PnpSignIO - ) {} - - public async perform( - session: PnpSession, - timeoutError: symbol - ): Promise { - // Compute quota lookup, update, and signing within transaction - // so that these occur atomically and rollback on error. - await this.db.transaction(async (trx) => { - const pnpSignHandler = async () => { - const quotaStatus = await this.quota.getQuotaStatus(session, trx) - - let isDuplicateRequest = false - try { - isDuplicateRequest = await getRequestExists( - this.db, - this.requestsTable, - session.request.body.account, - session.request.body.blindedQueryPhoneNumber, - session.logger, - trx - ) - } catch (err) { - session.logger.error(err, 'Failed to check if request already exists in db') - } - - if (isDuplicateRequest) { - Counters.duplicateRequests.inc() - session.logger.info( - 'Request already exists in db. Will service request without charging quota.' - ) - session.errors.push(WarningMessage.DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG) - } else { - // In the case of a database connection failure, performedQueryCount will be -1 - if (quotaStatus.performedQueryCount === -1) { - this.io.sendFailure( - ErrorMessage.DATABASE_GET_FAILURE, - 500, - session.response, - quotaStatus - ) - return - } - // In the case of a blockchain connection failure, totalQuota will be -1 - if (quotaStatus.totalQuota === -1) { - if (this.io.shouldFailOpen) { - // We fail open and service requests on full-node errors to not block the user. - // Error messages are stored in the session and included along with the signature in the response. - quotaStatus.totalQuota = Number.MAX_SAFE_INTEGER - session.logger.warn( - { warning: ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA }, - ErrorMessage.FAILING_OPEN - ) - Counters.requestsFailingOpen.inc() - } else { - session.logger.warn( - { warning: ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA }, - ErrorMessage.FAILING_CLOSED - ) - Counters.requestsFailingClosed.inc() - this.io.sendFailure(ErrorMessage.FULL_NODE_ERROR, 500, session.response, quotaStatus) - return - } - } - - // TODO(after 2.0.0) add more specific error messages on DB and key version - // https://github.com/celo-org/celo-monorepo/issues/9882 - // quotaStatus is updated in place; throws on failure to update - const { sufficient } = await this.quota.checkAndUpdateQuotaStatus( - quotaStatus, - session, - trx - ) - if (!sufficient) { - this.io.sendFailure(WarningMessage.EXCEEDED_QUOTA, 403, session.response, quotaStatus) - return - } - } - - const key: Key = { - version: - getRequestKeyVersion(session.request, session.logger) ?? - this.config.keystore.keys.phoneNumberPrivacy.latest, - name: DefaultKeyName.PHONE_NUMBER_PRIVACY, - } - - try { - const signature = await meter( - this.sign.bind(this), - [session.request.body.blindedQueryPhoneNumber, key, session], - (err: any) => { - throw err - }, - Histograms.getBlindedSigInstrumentation, - ['sign'] - ) - this.io.sendSuccess(200, session.response, key, signature, quotaStatus, session.errors) - return - } catch (err) { - session.logger.error({ err }) - quotaStatus.performedQueryCount-- - this.io.sendFailure( - ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, - 500, - session.response, - quotaStatus - ) - // Note that errors thrown after rollback will have no effect, hence doing this last - await trx.rollback() - return - } +import { Counters, Histograms } from '../../../common/metrics' +import { getSignerVersion, SignerConfig } from '../../../config' + +import { errorResult, ResultHandler } from '../../../common/handler' + +import Logger from 'bunyan' +import { traceAsyncFunction } from '../../../common/tracing-utils' +import { AccountService } from '../../services/account-service' +import { PnpRequestService } from '../../services/request-service' + +export function pnpSign( + db: Knex, + config: SignerConfig, + requestService: PnpRequestService, + accountService: AccountService, + keyProvider: KeyProvider +): ResultHandler { + return async (request, response) => { + const logger = response.locals.logger + + if (!isValidRequest(request)) { + return errorResult(400, WarningMessage.INVALID_INPUT) + } + + if (!requestHasValidKeyVersion(request, logger)) { + return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) + } + + const warnings: ErrorType[] = [] + const ctx = { + url: request.url, + logger, + errors: warnings, + } + + const account = await accountService.getAccount(request.body.account) + + if (!(await authenticateUser(request, logger, async (_) => account.dek, warnings))) { + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) + } + + let usedQuota = await requestService.getUsedQuotaForAccount(request.body.account, ctx) + + const duplicateRequest = await isDuplicateRequest( + db, + request.body.account, + request.body.blindedQueryPhoneNumber, + logger + ) + + Histograms.userRemainingQuotaAtRequest + .labels(ctx.url) + .observe(account.pnpTotalQuota - usedQuota) + + if (!duplicateRequest && account.pnpTotalQuota <= usedQuota) { + logger.warn({ usedQuota, totalQuota: account.pnpTotalQuota }, 'No remaining quota') + + if (bypassQuotaForE2ETesting(config.test_quota_bypass_percentage, request.body)) { + Counters.testQuotaBypassedRequests.inc() + logger.info(request.body, 'Request will bypass quota check for e2e testing') + } else { + return errorResult(403, WarningMessage.EXCEEDED_QUOTA, { + performedQueryCount: usedQuota, + totalQuota: account.pnpTotalQuota, + }) } - await timeout(pnpSignHandler, [], this.config.timeout, timeoutError) - }) + } + + const key: Key = { + version: + getRequestKeyVersion(request, response.locals.logger) ?? + config.keystore.keys.phoneNumberPrivacy.latest, + name: DefaultKeyName.PHONE_NUMBER_PRIVACY, + } + + let signature: string + try { + signature = await sign( + request.body.blindedQueryPhoneNumber, + key, + keyProvider, + response.locals.logger + ) + } catch (err) { + response.locals.logger.error({ err }, 'catch error on signing') + + return errorResult(500, ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, { + performedQueryCount: usedQuota, + totalQuota: account.pnpTotalQuota, + }) + } + + if (!duplicateRequest) { + await requestService.recordRequest(account.address, request.body.blindedQueryPhoneNumber, ctx) + usedQuota++ + } else { + Counters.duplicateRequests.inc() + logger.info('Request already exists in db. Will service request without charging quota.') + warnings.push(WarningMessage.DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG) + } + + // Send Success response + response.set(KEY_VERSION_HEADER, key.version.toString()) + return { + status: 200, + body: { + success: true as true, + version: getSignerVersion(), + signature, + performedQueryCount: usedQuota, + totalQuota: account.pnpTotalQuota, + warnings, + }, + } } +} - private async sign( - blindedMessage: string, - key: Key, - session: Session - ): Promise { - let privateKey: string +function isDuplicateRequest( + db: Knex, + account: string, + blindedQueryPhoneNumber: string, + logger: any +): Promise { + return getRequestExists(db, account, blindedQueryPhoneNumber, logger).catch((err) => { + logger.error(err, 'Failed to check if request already exists in db') + return false + }) +} + +async function sign( + blindedMessage: string, + key: Key, + keyProvider: KeyProvider, + logger: Logger +): Promise { + let privateKey: string + return traceAsyncFunction('pnpSign', async () => { try { - privateKey = await this.keyProvider.getPrivateKeyOrFetchFromStore(key) + privateKey = await keyProvider.getPrivateKeyOrFetchFromStore(key) } catch (err) { - session.logger.info({ key }, 'Requested key version not supported') - session.logger.error(err) + logger.info({ key }, 'Requested key version not supported') + logger.error(err) throw new Error(WarningMessage.INVALID_KEY_VERSION_REQUEST) } - return computeBlindedSignature(blindedMessage, privateKey, session.logger) - } + return computeBlindedSignature(blindedMessage, privateKey, logger) + }) +} + +function isValidRequest( + request: Request<{}, {}, unknown> +): request is Request<{}, {}, SignMessageRequest> { + return ( + SignMessageRequestSchema.is(request.body) && + hasValidAccountParam(request.body) && + hasValidBlindedPhoneNumberParam(request.body) && + isBodyReasonablySized(request.body) + ) +} + +function bypassQuotaForE2ETesting( + bypassQuotaPercentage: number, + requestBody: SignMessageRequest +): boolean { + const sessionID = Number(requestBody.sessionID) + return !Number.isNaN(sessionID) && sessionID % 100 < bypassQuotaPercentage } diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.ts deleted file mode 100644 index dfc4dfd3345..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - authenticateUser, - ErrorType, - hasValidAccountParam, - hasValidBlindedPhoneNumberParam, - isBodyReasonablySized, - KEY_VERSION_HEADER, - PnpQuotaStatus, - requestHasValidKeyVersion, - send, - SignerEndpoint, - SignMessageRequest, - SignMessageRequestSchema, - SignMessageResponse, - SignMessageResponseFailure, - SignMessageResponseSuccess, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import { IO } from '../../../common/io' -import { Key } from '../../../common/key-management/key-provider-base' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { PnpSession } from '../../session' - -export class PnpSignIO extends IO { - readonly endpoint = SignerEndpoint.PNP_SIGN - - constructor( - readonly enabled: boolean, - readonly shouldFailOpen: boolean, - readonly fullNodeTimeoutMs: number, - readonly fullNodeRetryCount: number, - readonly fullNodeRetryDelayMs: number, - readonly kit: ContractKit - ) { - super(enabled) - } - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - const logger = response.locals.logger - const warnings: ErrorType[] = [] - if (!super.inputChecks(request, response)) { - return null - } - if (!requestHasValidKeyVersion(request, logger)) { - this.sendFailure(WarningMessage.INVALID_KEY_VERSION_REQUEST, 400, response) - return null - } - if (!(await this.authenticate(request, warnings, logger))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - const session = new PnpSession(request, response) - session.errors.push(...warnings) - return session - } - - validate(request: Request<{}, {}, unknown>): request is Request<{}, {}, SignMessageRequest> { - return ( - SignMessageRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - hasValidBlindedPhoneNumberParam(request.body) && - isBodyReasonablySized(request.body) - ) - } - - async authenticate( - request: Request<{}, {}, SignMessageRequest>, - warnings: ErrorType[], - logger: Logger - ): Promise { - return authenticateUser( - request, - this.kit, - logger, - this.shouldFailOpen, - warnings, - this.fullNodeTimeoutMs, - this.fullNodeRetryCount, - this.fullNodeRetryDelayMs - ) - } - - sendSuccess( - status: number, - response: Response, - key: Key, - signature: string, - quotaStatus: PnpQuotaStatus, - warnings: string[] - ) { - response.set(KEY_VERSION_HEADER, key.version.toString()) - send( - response, - { - success: true, - version: getSignerVersion(), - signature, - ...quotaStatus, - warnings, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } - - sendFailure( - error: string, - status: number, - response: Response, - quotaStatus?: PnpQuotaStatus - ) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - ...quotaStatus, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/services/account-service.ts b/packages/phone-number-privacy/signer/src/pnp/services/account-service.ts new file mode 100644 index 00000000000..6b1685b02c4 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/pnp/services/account-service.ts @@ -0,0 +1,126 @@ +// import { ContractKit } from '@celo/contractkit' +import { + ErrorMessage, + // FULL_NODE_TIMEOUT_IN_MS, + // getDataEncryptionKey, + // RETRY_COUNT, + // RETRY_DELAY_IN_MS, +} from '@celo/phone-number-privacy-common' +// import BigNumber from 'bignumber.js' +// import Logger from 'bunyan' +import { LRUCache } from 'lru-cache' +//import { OdisError, wrapError } from '../../common/error' +import { OdisError } from '../../common/error' +// import { Counters } from '../../common/metrics' +import { traceAsyncFunction } from '../../common/tracing-utils' +// import { getOnChainOdisPayments } from '../../common/web3/contracts' +// import { config } from '../../config' + +export interface PnpAccount { + dek: string // onChain + address: string // onChain + pnpTotalQuota: number // onChain +} + +export interface AccountService { + getAccount(address: string): Promise +} + +interface CachedValue { + dek: string + pnpTotalQuota: number +} +export interface ContractKitAccountServiceOptions { + fullNodeTimeoutMs: number + fullNodeRetryCount: number + fullNodeRetryDelayMs: number +} + +export class CachingAccountService implements AccountService { + private cache: LRUCache + constructor(baseService: AccountService) { + this.cache = new LRUCache({ + max: 500, + ttl: 5 * 1000, // 5 seconds + allowStale: true, + fetchMethod: async (address: string) => { + const account = await baseService.getAccount(address) + return { dek: account.dek, pnpTotalQuota: account.pnpTotalQuota } + }, + }) + } + + getAccount(address: string): Promise { + return traceAsyncFunction('CachingAccountService - getAccount', async () => { + const value = await this.cache.fetch(address) + + if (value === undefined) { + // TODO decide which error ot use here + throw new OdisError(ErrorMessage.FAILURE_TO_GET_DEK) + } + return { + address, + dek: value.dek, + pnpTotalQuota: value.pnpTotalQuota, + } + }) + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ContractKitAccountService implements AccountService { + // constructor( + // private readonly logger: Logger, + // private readonly kit: ContractKit, + // private readonly opts: ContractKitAccountServiceOptions = { + // fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS, + // fullNodeRetryCount: RETRY_COUNT, + // fullNodeRetryDelayMs: RETRY_DELAY_IN_MS, + // } + // ) {} + + async getAccount(address: string): Promise { + return { + dek: '0x034846bc781cacdafc66f3a77aa9fc3c56a9dadcd683c72be3c446fee8da041070', + address, + pnpTotalQuota: 10, + } + // return traceAsyncFunction('ContractKitAccountService - getAccount', async () => { + // const dek = await wrapError( + // getDataEncryptionKey( + // address, + // this.kit, + // this.logger, + // this.opts.fullNodeTimeoutMs, + // this.opts.fullNodeRetryCount, + // this.opts.fullNodeRetryDelayMs + // ).catch((err) => { + // // TODO could clean this up...quick fix since we weren't incrementing blockchain error counter + // this.logger.error({ err, address }, 'failed to get on-chain odis balance for account') + // Counters.blockchainErrors.inc() + // throw err + // }), + // ErrorMessage.FAILURE_TO_GET_DEK + // ) + + // const { queryPriceInCUSD } = config.quota + // const totalPaidInWei = await wrapError( + // getOnChainOdisPayments(this.kit, this.logger, address, 'FAKE_URL'), + // ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA + // ) + // const totalQuotaBN = totalPaidInWei + // .div(queryPriceInCUSD.times(new BigNumber(1e18))) + // .integerValue(BigNumber.ROUND_DOWN) + + // // If any account hits an overflow here, we need to redesign how + // // quota/queries are computed anyways. + // const pnpTotalQuota = totalQuotaBN.toNumber() + + // return { + // address, + // dek, + // pnpTotalQuota, + // } + // }) + } +} diff --git a/packages/phone-number-privacy/signer/src/pnp/services/quota.ts b/packages/phone-number-privacy/signer/src/pnp/services/quota.ts deleted file mode 100644 index 105392facf3..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/services/quota.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - ErrorMessage, - PnpQuotaRequest, - PnpQuotaStatus, - SignMessageRequest, -} from '@celo/phone-number-privacy-common' -import BigNumber from 'bignumber.js' -import { Knex } from 'knex' -import { ACCOUNTS_TABLE } from '../../common/database/models/account' -import { REQUESTS_TABLE } from '../../common/database/models/request' -import { getPerformedQueryCount, incrementQueryCount } from '../../common/database/wrappers/account' -import { storeRequest } from '../../common/database/wrappers/request' -import { Counters, Histograms, meter } from '../../common/metrics' -import { OdisQuotaStatusResult, QuotaService } from '../../common/quota' -import { getBlockNumber, getOnChainOdisPayments } from '../../common/web3/contracts' -import { config } from '../../config' -import { PnpSession } from '../session' - -export class PnpQuotaService implements QuotaService { - protected readonly requestsTable: REQUESTS_TABLE = REQUESTS_TABLE.ONCHAIN - protected readonly accountsTable: ACCOUNTS_TABLE = ACCOUNTS_TABLE.ONCHAIN - - constructor(readonly db: Knex, readonly kit: ContractKit) {} - - public async checkAndUpdateQuotaStatus( - state: PnpQuotaStatus, - session: PnpSession, - trx?: Knex.Transaction - ): Promise> { - const remainingQuota = state.totalQuota - state.performedQueryCount - Histograms.userRemainingQuotaAtRequest.labels(session.request.url).observe(remainingQuota) - let sufficient = remainingQuota > 0 - if (!sufficient) { - session.logger.warn({ ...state }, 'No remaining quota') - if (this.bypassQuotaForE2ETesting(session.request.body)) { - Counters.testQuotaBypassedRequests.inc() - session.logger.info(session.request.body, 'Request will bypass quota check for e2e testing') - sufficient = true - } - } else { - await Promise.all([ - storeRequest( - this.db, - this.requestsTable, - session.request.body.account, - session.request.body.blindedQueryPhoneNumber, - session.logger, - trx - ), - incrementQueryCount( - this.db, - this.accountsTable, - session.request.body.account, - session.logger, - trx - ), - ]) - state.performedQueryCount++ - } - return { sufficient, state } - } - - public async getQuotaStatus( - session: PnpSession, - trx?: Knex.Transaction - ): Promise { - const { account } = session.request.body - const [performedQueryCountResult, totalQuotaResult, blockNumberResult] = await meter( - (_session: PnpSession) => - Promise.allSettled([ - getPerformedQueryCount(this.db, this.accountsTable, account, session.logger, trx), - this.getTotalQuota(_session), - getBlockNumber(this.kit), - ]), - [session], - (err: any) => { - throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getQuotaStatus', session.request.url] - ) - - const quotaStatus: PnpQuotaStatus = { - // TODO(future) consider making totalQuota,performedQueryCount undefined - totalQuota: -1, - performedQueryCount: -1, - blockNumber: undefined, - } - if (performedQueryCountResult.status === 'fulfilled') { - quotaStatus.performedQueryCount = performedQueryCountResult.value - } else { - session.logger.error( - { err: performedQueryCountResult.reason }, - ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT - ) - session.errors.push( - ErrorMessage.DATABASE_GET_FAILURE, - ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT - ) - } - let hadFullNodeError = false - if (totalQuotaResult.status === 'fulfilled') { - quotaStatus.totalQuota = totalQuotaResult.value - } else { - session.logger.error( - { err: totalQuotaResult.reason }, - ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA - ) - hadFullNodeError = true - session.errors.push(ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA) - } - if (blockNumberResult.status === 'fulfilled') { - quotaStatus.blockNumber = blockNumberResult.value - } else { - session.logger.error( - { err: blockNumberResult.reason }, - ErrorMessage.FAILURE_TO_GET_BLOCK_NUMBER - ) - hadFullNodeError = true - session.errors.push(ErrorMessage.FAILURE_TO_GET_BLOCK_NUMBER) - } - if (hadFullNodeError) { - session.errors.push(ErrorMessage.FULL_NODE_ERROR) - } - - return quotaStatus - } - - protected async getTotalQuota( - session: PnpSession - ): Promise { - return meter( - this.getTotalQuotaWithoutMeter.bind(this), - [session], - (err: any) => { - throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getTotalQuota', session.request.url] - ) - } - - /* - * Calculates how many queries the caller has unlocked; - * must be implemented by subclasses. - */ - protected async getTotalQuotaWithoutMeter( - session: PnpSession - ): Promise { - const { queryPriceInCUSD } = config.quota - const { account } = session.request.body - const totalPaidInWei = await getOnChainOdisPayments( - this.kit, - session.logger, - account, - session.request.url - ) - const totalQuota = totalPaidInWei - .div(queryPriceInCUSD.times(new BigNumber(1e18))) - .integerValue(BigNumber.ROUND_DOWN) - // If any account hits an overflow here, we need to redesign how - // quota/queries are computed anyways. - return totalQuota.toNumber() - } - - private bypassQuotaForE2ETesting(requestBody: SignMessageRequest): boolean { - const sessionID = Number(requestBody.sessionID) - return !Number.isNaN(sessionID) && sessionID % 100 < config.test_quota_bypass_percentage - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/services/request-service.ts b/packages/phone-number-privacy/signer/src/pnp/services/request-service.ts new file mode 100644 index 00000000000..6038cc97f6f --- /dev/null +++ b/packages/phone-number-privacy/signer/src/pnp/services/request-service.ts @@ -0,0 +1,51 @@ +import { ErrorMessage } from '@celo/phone-number-privacy-common' +import { Knex } from 'knex' +import { Context } from '../../common/context' +import { getPerformedQueryCount, incrementQueryCount } from '../../common/database/wrappers/account' +import { insertRequest } from '../../common/database/wrappers/request' +import { wrapError } from '../../common/error' +import { Histograms, newMeter } from '../../common/metrics' +import { traceAsyncFunction } from '../../common/tracing-utils' + +export interface PnpRequestService { + getUsedQuotaForAccount(address: string, ctx: Context): Promise + recordRequest(address: string, blindedQuery: string, ctx: Context): Promise +} + +/** + * PnpQuotaService is responsible for serving information about pnp quota + * + */ +export class DefaultPnpQuotaService { + constructor(readonly db: Knex) {} + + public async recordRequest( + account: string, + blindedQueryPhoneNumber: string, + ctx: Context + ): Promise { + return traceAsyncFunction('pnpQuotaService - recordRequest', async () => { + await Promise.all([ + insertRequest(this.db, account, blindedQueryPhoneNumber, ctx.logger), + incrementQueryCount(this.db, account, ctx.logger), + ]) + }) + } + + public getUsedQuotaForAccount(account: string, ctx: Context): Promise { + const meter = newMeter( + Histograms.getRemainingQueryCountInstrumentation, + 'getQuotaStatus', + ctx.url + ) + + return traceAsyncFunction('pnpQuotaService - getUsedQuotaForAccount', () => + meter(() => + wrapError( + getPerformedQueryCount(this.db, account, ctx.logger), + ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT + ) + ) + ) + } +} diff --git a/packages/phone-number-privacy/signer/src/pnp/session.ts b/packages/phone-number-privacy/signer/src/pnp/session.ts deleted file mode 100644 index a6d80c764ae..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/session.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - ErrorType, - PhoneNumberPrivacyRequest, - PhoneNumberPrivacyResponse, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' - -export class PnpSession { - readonly logger: Logger - readonly errors: ErrorType[] = [] - - public constructor( - readonly request: Request<{}, {}, R>, - readonly response: Response> - ) { - this.logger = response.locals.logger - } -} diff --git a/packages/phone-number-privacy/signer/src/server.ts b/packages/phone-number-privacy/signer/src/server.ts index c0671500365..7091015811e 100644 --- a/packages/phone-number-privacy/signer/src/server.ts +++ b/packages/phone-number-privacy/signer/src/server.ts @@ -1,33 +1,39 @@ import { ContractKit } from '@celo/contractkit' import { - ErrorMessage, getContractKit, loggerMiddleware, + OdisRequest, rootLogger, SignerEndpoint, } from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import express, { Request, RequestHandler, Response } from 'express' +import express, { Express, RequestHandler } from 'express' import fs from 'fs' import https from 'https' import { Knex } from 'knex' +import { IncomingMessage, ServerResponse } from 'node:http' import * as PromClient from 'prom-client' -import { Controller } from './common/controller' import { KeyProvider } from './common/key-management/key-provider-base' -import { Counters } from './common/metrics' +import { Histograms } from './common/metrics' import { getSignerVersion, SignerConfig } from './config' -import { DomainDisableAction } from './domain/endpoints/disable/action' -import { DomainDisableIO } from './domain/endpoints/disable/io' -import { DomainQuotaAction } from './domain/endpoints/quota/action' -import { DomainQuotaIO } from './domain/endpoints/quota/io' -import { DomainSignAction } from './domain/endpoints/sign/action' -import { DomainSignIO } from './domain/endpoints/sign/io' +import { domainDisable } from './domain/endpoints/disable/action' +import { domainQuota } from './domain/endpoints/quota/action' +import { domainSign } from './domain/endpoints/sign/action' import { DomainQuotaService } from './domain/services/quota' -import { PnpQuotaAction } from './pnp/endpoints/quota/action' -import { PnpQuotaIO } from './pnp/endpoints/quota/io' -import { PnpSignAction } from './pnp/endpoints/sign/action' -import { PnpSignIO } from './pnp/endpoints/sign/io' -import { PnpQuotaService } from './pnp/services/quota' +import { pnpQuota } from './pnp/endpoints/quota/action' +import { pnpSign } from './pnp/endpoints/sign/action' +import { DefaultPnpQuotaService } from './pnp/services/request-service' + +import { + catchErrorHandler, + disabledHandler, + Locals, + meteringHandler, + ResultHandler, + resultHandler, + timeoutHandler, + tracingHandler, +} from './common/handler' +import { CachingAccountService, ContractKitAccountService } from './pnp/services/account-service' require('events').EventEmitter.defaultMaxListeners = 15 @@ -36,7 +42,7 @@ export function startSigner( db: Knex, keyProvider: KeyProvider, kit?: ContractKit -) { +): Express | https.Server { const logger = rootLogger(config.serviceName) kit = kit ?? getContractKit(config.blockchain) @@ -55,89 +61,49 @@ export function startSigner( res.send(PromClient.register.metrics()) }) - const addEndpoint = ( - endpoint: SignerEndpoint, - handler: (req: Request, res: Response) => Promise - ) => - app.post(endpoint, async (req, res) => { - const childLogger: Logger = res.locals.logger - try { - await handler(req, res) - } catch (err: any) { - // Handle any errors that otherwise managed to escape the proper handlers - childLogger.error(ErrorMessage.CAUGHT_ERROR_IN_ENDPOINT_HANDLER) - childLogger.error(err) - Counters.errorsCaughtInEndpointHandler.inc() - if (!res.headersSent) { - childLogger.info('Responding with error in outer endpoint handler') - res.status(500).json({ - success: false, - error: ErrorMessage.UNKNOWN_ERROR, - }) - } else { - // Getting to this error likely indicates that the `perform` process - // does not terminate after sending a response, and then throws an error. - childLogger.error(ErrorMessage.ERROR_AFTER_RESPONSE_SENT) - Counters.errorsThrownAfterResponseSent.inc() - } - } - }) + const accountService = new CachingAccountService( + // new ContractKitAccountService(logger, kit, { + // fullNodeTimeoutMs: config.fullNodeTimeoutMs, + // fullNodeRetryCount: config.fullNodeRetryCount, + // fullNodeRetryDelayMs: config.fullNodeRetryDelayMs, + // }) + new ContractKitAccountService() + ) - const pnpQuotaService = new PnpQuotaService(db, kit) + const pnpRequestService = new DefaultPnpQuotaService(db) const domainQuotaService = new DomainQuotaService(db) - const pnpQuota = new Controller( - new PnpQuotaAction( - config, - pnpQuotaService, - new PnpQuotaIO( - config.api.phoneNumberPrivacy.enabled, - config.api.phoneNumberPrivacy.shouldFailOpen, // TODO (https://github.com/celo-org/celo-monorepo/issues/9862) consider refactoring config to make the code cleaner - config.fullNodeTimeoutMs, - config.fullNodeRetryCount, - config.fullNodeRetryDelayMs, - kit - ) + logger.info('Right before adding meteredSignerEndpoints') + + const { + timeout, + api: { domains, phoneNumberPrivacy }, + } = config + + app.post( + SignerEndpoint.PNP_SIGN, + createHandler( + timeout, + phoneNumberPrivacy.enabled, + pnpSign(db, config, pnpRequestService, accountService, keyProvider) ) ) - const pnpSign = new Controller( - new PnpSignAction( - db, - config, - pnpQuotaService, - keyProvider, - new PnpSignIO( - config.api.phoneNumberPrivacy.enabled, - config.api.phoneNumberPrivacy.shouldFailOpen, - config.fullNodeTimeoutMs, - config.fullNodeRetryCount, - config.fullNodeRetryDelayMs, - kit - ) - ) + app.post( + SignerEndpoint.PNP_QUOTA, + createHandler(timeout, phoneNumberPrivacy.enabled, pnpQuota(pnpRequestService, accountService)) ) - - const domainQuota = new Controller( - new DomainQuotaAction(config, domainQuotaService, new DomainQuotaIO(config.api.domains.enabled)) + app.post( + SignerEndpoint.DOMAIN_QUOTA_STATUS, + createHandler(timeout, domains.enabled, domainQuota(domainQuotaService)) ) - const domainSign = new Controller( - new DomainSignAction( - db, - config, - domainQuotaService, - keyProvider, - new DomainSignIO(config.api.domains.enabled) - ) + app.post( + SignerEndpoint.DOMAIN_SIGN, + createHandler(timeout, domains.enabled, domainSign(db, config, domainQuotaService, keyProvider)) ) - const domainDisable = new Controller( - new DomainDisableAction(db, config, new DomainDisableIO(config.api.domains.enabled)) + app.post( + SignerEndpoint.DISABLE_DOMAIN, + createHandler(timeout, domains.enabled, domainDisable(db)) ) - logger.info('Right before adding meteredSignerEndpoints') - addEndpoint(SignerEndpoint.PNP_SIGN, pnpSign.handle.bind(pnpSign)) - addEndpoint(SignerEndpoint.PNP_QUOTA, pnpQuota.handle.bind(pnpQuota)) - addEndpoint(SignerEndpoint.DOMAIN_QUOTA_STATUS, domainQuota.handle.bind(domainQuota)) - addEndpoint(SignerEndpoint.DOMAIN_SIGN, domainSign.handle.bind(domainSign)) - addEndpoint(SignerEndpoint.DISABLE_DOMAIN, domainDisable.handle.bind(domainDisable)) const sslOptions = getSslOptions(config) if (sslOptions) { @@ -166,3 +132,18 @@ function getSslOptions(config: SignerConfig) { cert: fs.readFileSync(sslCertPath), } } + +function createHandler( + timeoutMs: number, + enabled: boolean, + action: ResultHandler +): RequestHandler<{}, {}, R, {}, Locals> { + return catchErrorHandler( + tracingHandler( + meteringHandler( + Histograms.responseLatency, + timeoutHandler(timeoutMs, enabled ? resultHandler(action) : disabledHandler) + ) + ) + ) +} diff --git a/packages/phone-number-privacy/signer/src/tracing.ts b/packages/phone-number-privacy/signer/src/tracing.ts new file mode 100644 index 00000000000..0d75525c9a7 --- /dev/null +++ b/packages/phone-number-privacy/signer/src/tracing.ts @@ -0,0 +1,81 @@ +/* + https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node + getNodeAutoInstrumentations (above) includes all available auto-tracing + instrumentations for node (Knex, Express, Http, tcp, ...). + This may lead to super-verbose traces. We can just comment the getNodeAutoInstrumentations + line above and just add the instrumentations we care like commented lines below. +*/ +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' +import { JaegerExporter } from '@opentelemetry/exporter-jaeger' +import { registerInstrumentations } from '@opentelemetry/instrumentation' +import { Resource } from '@opentelemetry/resources' +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base' +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' +/* + Some instrumentations included in auto-instrumentations-node: + - https://www.npmjs.com/package/@opentelemetry/sdk-trace-web + - https://www.npmjs.com/package/@opentelemetry/instrumentation-express + - https://www.npmjs.com/package/@opentelemetry/instrumentation-http + - https://www.npmjs.com/package/@opentelemetry/instrumentation-knex +*/ +// import { WebTracerProvider } from '@opentelemetry/sdk-trace-web' +// import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express' +// import { HttpInstrumentation } from '@opentelemetry/instrumentation-http' +// import { KnexInstrumentation } from '@opentelemetry/instrumentation-knex' +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' + +const options = { + tags: [], + endpoint: process.env.TRACER_ENDPOINT, + // 'http://grafana-agent.grafana-agent:14268/api/traces', +} + +// Optionally register automatic instrumentation libraries +registerInstrumentations({ + instrumentations: [ + getNodeAutoInstrumentations({ + // load custom configuration for http instrumentation + '@opentelemetry/instrumentation-http': { + ignoreIncomingPaths: [ + /\/status/, + /\/metrics/, + /\/metadata\/.*/, + /\/secrets\/.*/, + /\/ecp\/.*/, + ], + }, + }), + /* + How to add specific instrumentations instead of the + all-included auto-tracing getNodeAutoInstrumentations + */ + // new HttpInstrumentation({ + // ignoreIncomingPaths: [ + // /\/status/, + // /\/metrics/, + // /\/metadata\/.*/, + // /\/secrets\/.*/, + // /\/ecp\/.*/, + // ], + // }), + // new ExpressInstrumentation(), + // new KnexInstrumentation(), + ], +}) + +const resource = Resource.default().merge( + new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: process.env.TRACING_SERVICE_NAME, + // 'testing-signer-tracing', + [SemanticResourceAttributes.SERVICE_VERSION]: '0.1.0', + }) +) + +const provider = new NodeTracerProvider({ + resource, +}) +const exporter = new JaegerExporter(options) +const processor = new BatchSpanProcessor(exporter) +provider.addSpanProcessor(processor) + +provider.register() diff --git a/packages/phone-number-privacy/signer/test/end-to-end/pnp.test.ts b/packages/phone-number-privacy/signer/test/end-to-end/pnp.test.ts index 0a36158c807..8a0e97f75f9 100644 --- a/packages/phone-number-privacy/signer/test/end-to-end/pnp.test.ts +++ b/packages/phone-number-privacy/signer/test/end-to-end/pnp.test.ts @@ -78,7 +78,7 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { version: expectedVersion, performedQueryCount: 0, totalQuota: 0, - blockNumber: resBody.blockNumber, + warnings: [], }) }) @@ -94,7 +94,7 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { version: expectedVersion, performedQueryCount: resBody.performedQueryCount, totalQuota: resBody.totalQuota, - blockNumber: resBody.blockNumber, + warnings: [], }) expect(resBody.totalQuota).toBeGreaterThan(0) @@ -116,7 +116,7 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { version: expectedVersion, performedQueryCount: resBody.performedQueryCount, totalQuota: resBody.totalQuota + 1, - blockNumber: res2Body.blockNumber, + warnings: [], }) }) @@ -207,7 +207,7 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { signature: resBody.signature, performedQueryCount: startingPerformedQueryCount + 1, totalQuota: resBody.totalQuota, - blockNumber: resBody.blockNumber, + warnings: [], }) expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(contextSpecificParams.pnpKeyVersion) @@ -242,7 +242,7 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { signature: resBody.signature, performedQueryCount: startingPerformedQueryCount + 1, totalQuota: resBody.totalQuota, - blockNumber: resBody.blockNumber, + warnings: [], }) expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(keyVersion) @@ -273,7 +273,7 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { signature: resBody.signature, performedQueryCount: startingPerformedQueryCount + 1, totalQuota: resBody.totalQuota, - blockNumber: resBody.blockNumber, + warnings: [], }) expect(res.headers.get(KEY_VERSION_HEADER)).toEqual(contextSpecificParams.pnpKeyVersion) @@ -293,7 +293,7 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { signature: resBody.signature, performedQueryCount: resBody.performedQueryCount, // Not incremented totalQuota: resBody.totalQuota, - blockNumber: res2Body.blockNumber, + warnings: [WarningMessage.DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG], }) }) @@ -443,7 +443,6 @@ describe(`Running against service deployed at ${ODIS_SIGNER_URL}`, () => { error: WarningMessage.EXCEEDED_QUOTA, totalQuota: quotaResBody.totalQuota, performedQueryCount: quotaResBody.performedQueryCount, - blockNumber: resBody.blockNumber, }) }) }) diff --git a/packages/phone-number-privacy/signer/test/integration/domain.test.ts b/packages/phone-number-privacy/signer/test/integration/domain.test.ts index 9299916a37c..47e72c847a0 100644 --- a/packages/phone-number-privacy/signer/test/integration/domain.test.ts +++ b/packages/phone-number-privacy/signer/test/integration/domain.test.ts @@ -364,12 +364,14 @@ describe('domain', () => { error: ErrorMessage.TIMEOUT_FROM_SIGNER, version: expectedVersion, }) - // Allow time for non-killed processes to finish - await new Promise((resolve) => setTimeout(resolve, delay)) - // Check that DB state was not updated on timeout - expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( - null - ) + + // TODO (mcortesi) this is not true anymore + // // Allow time for non-killed processes to finish + // await new Promise((resolve) => setTimeout(resolve, delay)) + // // Check that DB state was not updated on timeout + // expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( + // null + // ) }) }) }) @@ -1032,12 +1034,14 @@ describe('domain', () => { version: expectedVersion, }) spy.mockRestore() - // Allow time for non-killed processes to finish - await new Promise((resolve) => setTimeout(resolve, delay)) - // Check that DB state was not updated on timeout - expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( - null - ) + + // TODO (mcortesi) This is not true anymore + // // Allow time for non-killed processes to finish + // await new Promise((resolve) => setTimeout(resolve, delay)) + // // Check that DB state was not updated on timeout + // expect(await getDomainStateRecord(db, req.domain, rootLogger(_config.serviceName))).toBe( + // null + // ) }) }) }) diff --git a/packages/phone-number-privacy/signer/test/integration/pnp.test.ts b/packages/phone-number-privacy/signer/test/integration/pnp.test.ts index 59012e0d0a5..d61cf2ecea8 100644 --- a/packages/phone-number-privacy/signer/test/integration/pnp.test.ts +++ b/packages/phone-number-privacy/signer/test/integration/pnp.test.ts @@ -18,8 +18,6 @@ import BigNumber from 'bignumber.js' import { Knex } from 'knex' import request from 'supertest' import { initDatabase } from '../../src/common/database/database' -import { ACCOUNTS_TABLE } from '../../src/common/database/models/account' -import { REQUESTS_TABLE } from '../../src/common/database/models/request' import { countAndThrowDBError } from '../../src/common/database/utils' import { getPerformedQueryCount, @@ -181,7 +179,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: 0, totalQuota: expectedTotalQuota, - blockNumber: testBlockNumber, warnings: [], }) }) @@ -205,7 +202,6 @@ describe('pnp', () => { version: res.body.version, performedQueryCount: 0, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) }) @@ -221,7 +217,6 @@ describe('pnp', () => { version: res1.body.version, performedQueryCount: 0, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) const res2 = await sendRequest(req, authorization, SignerEndpoint.PNP_QUOTA) @@ -240,7 +235,6 @@ describe('pnp', () => { version: res.body.version, performedQueryCount: 0, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) }) @@ -257,7 +251,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: 0, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) }) @@ -267,7 +260,7 @@ describe('pnp', () => { for (let i = 0; i <= expectedQuota; i++) { await incrementQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(config.serviceName), trx @@ -284,7 +277,6 @@ describe('pnp', () => { version: res.body.version, performedQueryCount: expectedQuota + 1, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) }) @@ -360,44 +352,6 @@ describe('pnp', () => { }) describe('functionality in case of errors', () => { - it('Should respond with 200 on failure to fetch DEK when shouldFailOpen is true', async () => { - mockGetDataEncryptionKey.mockImplementation(() => { - throw new Error() - }) - - const req = getPnpQuotaRequest(ACCOUNT_ADDRESS1, AuthenticationMethod.ENCRYPTION_KEY) - - const configWithFailOpenEnabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenEnabled.api.phoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startSigner( - configWithFailOpenEnabled, - db, - keyProvider, - newKit('dummyKit') - ) - - // NOT the dek private key, so authentication would fail if getDataEncryptionKey succeeded - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_QUOTA, - '1', - appWithFailOpenEnabled - ) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [ErrorMessage.FAILURE_TO_GET_DEK, ErrorMessage.FAILING_OPEN], - }) - }) - it('Should respond with 500 on DB performedQueryCount query failure', async () => { const spy = jest .spyOn( @@ -507,7 +461,6 @@ describe('pnp', () => { signature: expectedSignature, performedQueryCount: 1, // incremented for signature request totalQuota: expectedTotalQuota, - blockNumber: testBlockNumber, warnings: [], }) } else { @@ -517,7 +470,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: 0, totalQuota: expectedTotalQuota, - blockNumber: testBlockNumber, error: WarningMessage.EXCEEDED_QUOTA, }) } @@ -535,7 +487,7 @@ describe('pnp', () => { for (let i = 0; i < performedQueryCount; i++) { await incrementQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(_config.serviceName), trx @@ -559,7 +511,6 @@ describe('pnp', () => { signature: expectedSignature, performedQueryCount: performedQueryCount + 1, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) expect(res.get(KEY_VERSION_HEADER)).toEqual( @@ -582,7 +533,6 @@ describe('pnp', () => { signature: expectedSignature, performedQueryCount: performedQueryCount + 1, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) }) @@ -603,7 +553,6 @@ describe('pnp', () => { signature: expectedSignatures[i - 1], performedQueryCount: performedQueryCount + 1, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) expect(res.get(KEY_VERSION_HEADER)).toEqual(i.toString()) @@ -625,7 +574,6 @@ describe('pnp', () => { signature: expectedSignature, performedQueryCount: performedQueryCount + 1, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) const res2 = await sendRequest(req, authorization, SignerEndpoint.PNP_SIGN) @@ -651,7 +599,6 @@ describe('pnp', () => { signature: expectedSignature, performedQueryCount: performedQueryCount + 1, totalQuota: expectedQuota, - blockNumber: testBlockNumber, warnings: [], }) }) @@ -763,7 +710,7 @@ describe('pnp', () => { for (let i = 0; i < remainingQuota; i++) { await incrementQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(_config.serviceName), trx @@ -783,7 +730,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: expectedQuota, totalQuota: expectedQuota, - blockNumber: testBlockNumber, error: WarningMessage.EXCEEDED_QUOTA, }) }) @@ -810,7 +756,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: 0, totalQuota: 0, - blockNumber: testBlockNumber, error: WarningMessage.EXCEEDED_QUOTA, }) @@ -823,7 +768,7 @@ describe('pnp', () => { for (let i = 0; i <= expectedRemainingQuota; i++) { await incrementQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(_config.serviceName), trx @@ -846,7 +791,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: expectedQuota + 1, totalQuota: expectedQuota, - blockNumber: testBlockNumber, error: WarningMessage.EXCEEDED_QUOTA, }) }) @@ -865,7 +809,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: performedQueryCount, totalQuota: expectedQuota, - blockNumber: testBlockNumber, error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, }) }) @@ -909,7 +852,7 @@ describe('pnp', () => { for (let i = 0; i < remainingQuota; i++) { await incrementQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(_config.serviceName), trx @@ -920,7 +863,7 @@ describe('pnp', () => { expect( await getPerformedQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(_config.serviceName) ) @@ -945,16 +888,14 @@ describe('pnp', () => { expect(res.body).toStrictEqual({ success: false, version: expectedVersion, - performedQueryCount: -1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - error: ErrorMessage.DATABASE_GET_FAILURE, + error: ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT, }) spy.mockRestore() }) - it('Should respond with 500 on signer timeout', async () => { + // TODO + xit('Should respond with 500 on signer timeout', async () => { const testTimeoutMS = 0 const delay = 200 const spy = jest @@ -1003,7 +944,7 @@ describe('pnp', () => { expect( await getPerformedQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(config.serviceName) ) @@ -1011,97 +952,15 @@ describe('pnp', () => { expect( await getRequestExists( db, - REQUESTS_TABLE.ONCHAIN, - req.account, - req.blindedQueryPhoneNumber, - rootLogger(config.serviceName) - ) - ).toBe(false) - }) - - it('Should return 200 w/ warning on blockchain totalQuota query failure when shouldFailOpen is true', async () => { - const configWithFailOpenEnabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenEnabled.api.phoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startSigner( - configWithFailOpenEnabled, - db, - keyProvider, - newKit('dummyKit') - ) - // deplete user's quota - const remainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i < remainingQuota; i++) { - await incrementQueryCount( - db, - ACCOUNTS_TABLE.ONCHAIN, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName), - trx - ) - } - }) - // sanity check - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.ONCHAIN, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName) - ) - ).toBe(expectedQuota) - - mockOdisPaymentsTotalPaidCUSD.mockImplementation(() => { - throw new Error('dummy error') - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_SIGN, - '1', - appWithFailOpenEnabled - ) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: expectedQuota + 1, // bc we depleted the user's quota above - totalQuota: Number.MAX_SAFE_INTEGER, - blockNumber: testBlockNumber, - warnings: [ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA, ErrorMessage.FULL_NODE_ERROR], - }) - - // check DB state: performedQueryCount was incremented and request was stored - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.ONCHAIN, - ACCOUNT_ADDRESS1, - rootLogger(config.serviceName) - ) - ).toBe(expectedQuota + 1) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.ONCHAIN, req.account, req.blindedQueryPhoneNumber, rootLogger(config.serviceName) ) - ).toBe(true) + ).toBe(false) }) - it('Should return 500 on blockchain totalQuota query failure when shouldFailOpen is false', async () => { + it('Should return 500 on blockchain totalQuota query failure', async () => { mockOdisPaymentsTotalPaidCUSD.mockImplementation(() => { throw new Error('dummy error') }) @@ -1113,7 +972,6 @@ describe('pnp', () => { ) const configWithFailOpenDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenDisabled.api.phoneNumberPrivacy.shouldFailOpen = false const appWithFailOpenDisabled = startSigner( configWithFailOpenDisabled, db, @@ -1134,10 +992,7 @@ describe('pnp', () => { expect(res.body).toStrictEqual({ success: false, version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: -1, - blockNumber: testBlockNumber, - error: ErrorMessage.FULL_NODE_ERROR, + error: ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA, }) }) @@ -1169,25 +1024,28 @@ describe('pnp', () => { error: ErrorMessage.DATABASE_UPDATE_FAILURE, }) - // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount(db, ACCOUNTS_TABLE.ONCHAIN, ACCOUNT_ADDRESS1, logger) - ).toBe(performedQueryCount) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.ONCHAIN, - req.account, - req.blindedQueryPhoneNumber, - logger - ) - ).toBe(false) + // TODO remove this check if we decide to permanently remove trx + // // check DB state: performedQueryCount was not incremented and request was not stored + // expect(await getPerformedQueryCount(db, ACCOUNT_ADDRESS1, logger)).toBe( + // performedQueryCount + // ) + // expect( + // await getRequestExists( + // db, + // req.account, + // req.blindedQueryPhoneNumber, + // logger + // ) + // ).toBe(false) }) it('Should return 500 on failure to store request', async () => { const logger = rootLogger(_config.serviceName) const spy = jest - .spyOn(jest.requireActual('../../src/common/database/wrappers/request'), 'storeRequest') + .spyOn( + jest.requireActual('../../src/common/database/wrappers/request'), + 'insertRequest' + ) .mockImplementationOnce(() => { countAndThrowDBError(new Error(), logger, ErrorMessage.DATABASE_INSERT_FAILURE) }) @@ -1209,13 +1067,13 @@ describe('pnp', () => { }) // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount(db, ACCOUNTS_TABLE.ONCHAIN, ACCOUNT_ADDRESS1, logger) - ).toBe(performedQueryCount) + expect(await getPerformedQueryCount(db, ACCOUNT_ADDRESS1, logger)).toBe( + performedQueryCount + ) expect( await getRequestExists( db, - REQUESTS_TABLE.ONCHAIN, + req.account, req.blindedQueryPhoneNumber, logger @@ -1223,48 +1081,6 @@ describe('pnp', () => { ).toBe(false) }) - it('Should return 200 on failure to fetch DEK when shouldFailOpen is true', async () => { - mockGetDataEncryptionKey.mockImplementation(() => { - throw new Error() - }) - - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.ENCRYPTION_KEY - ) - - const configWithFailOpenEnabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenEnabled.api.phoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startSigner( - configWithFailOpenEnabled, - db, - keyProvider, - newKit('dummyKit') - ) - - // NOT the dek private key, so authentication would fail if getDataEncryptionKey succeeded - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_SIGN, - '1', - appWithFailOpenEnabled - ) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [ErrorMessage.FAILURE_TO_GET_DEK, ErrorMessage.FAILING_OPEN], - }) - }) - it('Should return 500 on bls signing error', async () => { const spy = jest .spyOn(jest.requireActual('blind-threshold-bls'), 'partialSignBlindedMessage') @@ -1286,7 +1102,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: performedQueryCount, totalQuota: expectedQuota, - blockNumber: testBlockNumber, error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, }) @@ -1296,7 +1111,7 @@ describe('pnp', () => { expect( await getPerformedQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(_config.serviceName) ) @@ -1304,7 +1119,7 @@ describe('pnp', () => { expect( await getRequestExists( db, - REQUESTS_TABLE.ONCHAIN, + req.account, req.blindedQueryPhoneNumber, rootLogger(_config.serviceName) @@ -1337,7 +1152,6 @@ describe('pnp', () => { version: expectedVersion, performedQueryCount: performedQueryCount, totalQuota: expectedQuota, - blockNumber: testBlockNumber, error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, }) @@ -1347,7 +1161,7 @@ describe('pnp', () => { expect( await getPerformedQueryCount( db, - ACCOUNTS_TABLE.ONCHAIN, + ACCOUNT_ADDRESS1, rootLogger(config.serviceName) ) @@ -1355,7 +1169,7 @@ describe('pnp', () => { expect( await getRequestExists( db, - REQUESTS_TABLE.ONCHAIN, + req.account, req.blindedQueryPhoneNumber, rootLogger(config.serviceName) diff --git a/packages/sdk/contractkit/src/wrappers/Governance.ts b/packages/sdk/contractkit/src/wrappers/Governance.ts index 895c926f53a..a52b20f3240 100644 --- a/packages/sdk/contractkit/src/wrappers/Governance.ts +++ b/packages/sdk/contractkit/src/wrappers/Governance.ts @@ -211,7 +211,10 @@ export class GovernanceWrapper extends BaseWrapperForGoverning { * @param proposal Proposal to determine the constitution for running. */ async getConstitution(proposal: Proposal): Promise { - let constitution = new BigNumber(0) + // Default value that is harcoded on Governance contract + // it's 0.5 in Fixidity + // https://github.com/celo-org/celo-monorepo/blob/3fffa158d67ffd6366e81ba7243eadede1974b1b/packages/protocol/contracts/governance/Governance.sol#L39 + let constitution = fromFixed(new BigNumber('500000000000000000000000')) for (const tx of proposal) { constitution = BigNumber.max(await this.getTransactionConstitution(tx), constitution) } @@ -236,7 +239,7 @@ export class GovernanceWrapper extends BaseWrapperForGoverning { // in the total of yes votes required async getSupportWithConstitutionThreshold(proposalID: BigNumber.Value, constitution: BigNumber) { const support = await this.getSupport(proposalID) - support.required = support.required.times(constitution) + support.required = support.required.times(constitution).integerValue() return support } diff --git a/yarn.lock b/yarn.lock index ff6d71927ab..f243d88ddc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2230,6 +2230,14 @@ dependencies: semver "^5.5.0" +"@grpc/grpc-js@^1.7.1": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.0.tgz#bdb599e339adabb16aa7243e70c311f75a572867" + integrity sha512-H8+iZh+kCE6VR/Krj6W28Y/ZlxoZ1fOzsNt77nrdE3knkbSelW1Uus192xOFCxHyeszLj8i4APQkSIXjAoOxXg== + dependencies: + "@grpc/proto-loader" "^0.7.0" + "@types/node" ">=12.12.47" + "@grpc/grpc-js@~1.6.0": version "1.6.12" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.12.tgz#20f710d8a8c5c396b2ae9530ba6c06b984614fdf" @@ -2287,6 +2295,77 @@ protobufjs "^7.0.0" yargs "^16.2.0" +"@hapi/b64@5.x.x": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" + integrity sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/boom@9.x.x", "@hapi/boom@^9.0.0": + version "9.1.4" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" + integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/bourne@2.x.x": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.1.0.tgz#66aff77094dc3080bd5df44ec63881f2676eb020" + integrity sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q== + +"@hapi/cryptiles@5.x.x": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" + integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== + dependencies: + "@hapi/boom" "9.x.x" + +"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/iron@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-6.0.0.tgz#ca3f9136cda655bdd6028de0045da0de3d14436f" + integrity sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw== + dependencies: + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/podium@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" + integrity sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g== + dependencies: + "@hapi/hoek" "9.x.x" + "@hapi/teamwork" "5.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/teamwork@5.x.x": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.1.tgz#4d2ba3cac19118a36c44bf49a3a47674de52e4e4" + integrity sha512-1oPx9AE5TIv+V6Ih54RP9lTZBso3rP8j4Xhb6iSVwPXtAM+sDopl5TFMv5Paw73UnpZJ9gjcrTE1BXrWt9eQrg== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/validate@1.x.x": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" + integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@hutson/parse-repository-url@^3.0.0": version "3.0.2" resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" @@ -4080,11 +4159,659 @@ dependencies: "@octokit/openapi-types" "^17.0.0" -"@opentelemetry/api@^1.0.0": +"@opentelemetry/api-logs@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.41.2.tgz#600c9b3d79018e7421d2ff7189f41b6d2c987d6a" + integrity sha512-JEV2RAqijAFdWeT6HddYymfnkiRu2ASxoTBr4WsnGJhOjWZkEy6vp+Sx9ozr1NaIODOa2HUyckExIqQjn6qywQ== + dependencies: + "@opentelemetry/api" "^1.0.0" + +"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" integrity sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA== +"@opentelemetry/auto-instrumentations-node@^0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.38.0.tgz#9841ebc87d696aff10cf1ad03b19f5b233868bd2" + integrity sha512-lQXiUAGs79+SkaTycwmtamzH0bsXpGOccl2jNFDztZrCvMn2xD4TJkKm5PuoFp9fnRgtY/vEJck+ViefJnSCdA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/instrumentation-amqplib" "^0.33.0" + "@opentelemetry/instrumentation-aws-lambda" "^0.36.0" + "@opentelemetry/instrumentation-aws-sdk" "^0.35.0" + "@opentelemetry/instrumentation-bunyan" "^0.32.0" + "@opentelemetry/instrumentation-cassandra-driver" "^0.33.0" + "@opentelemetry/instrumentation-connect" "^0.32.0" + "@opentelemetry/instrumentation-dataloader" "^0.5.0" + "@opentelemetry/instrumentation-dns" "^0.32.0" + "@opentelemetry/instrumentation-express" "^0.33.0" + "@opentelemetry/instrumentation-fastify" "^0.32.0" + "@opentelemetry/instrumentation-fs" "^0.8.0" + "@opentelemetry/instrumentation-generic-pool" "^0.32.0" + "@opentelemetry/instrumentation-graphql" "^0.35.0" + "@opentelemetry/instrumentation-grpc" "^0.41.0" + "@opentelemetry/instrumentation-hapi" "^0.32.0" + "@opentelemetry/instrumentation-http" "^0.41.0" + "@opentelemetry/instrumentation-ioredis" "^0.35.0" + "@opentelemetry/instrumentation-knex" "^0.32.0" + "@opentelemetry/instrumentation-koa" "^0.35.0" + "@opentelemetry/instrumentation-lru-memoizer" "^0.33.0" + "@opentelemetry/instrumentation-memcached" "^0.32.0" + "@opentelemetry/instrumentation-mongodb" "^0.36.0" + "@opentelemetry/instrumentation-mongoose" "^0.33.0" + "@opentelemetry/instrumentation-mysql" "^0.34.0" + "@opentelemetry/instrumentation-mysql2" "^0.34.0" + "@opentelemetry/instrumentation-nestjs-core" "^0.33.0" + "@opentelemetry/instrumentation-net" "^0.32.0" + "@opentelemetry/instrumentation-pg" "^0.36.0" + "@opentelemetry/instrumentation-pino" "^0.34.0" + "@opentelemetry/instrumentation-redis" "^0.35.0" + "@opentelemetry/instrumentation-redis-4" "^0.35.0" + "@opentelemetry/instrumentation-restify" "^0.33.0" + "@opentelemetry/instrumentation-router" "^0.33.0" + "@opentelemetry/instrumentation-socket.io" "^0.34.0" + "@opentelemetry/instrumentation-tedious" "^0.6.0" + "@opentelemetry/instrumentation-winston" "^0.32.0" + "@opentelemetry/resource-detector-alibaba-cloud" "^0.28.0" + "@opentelemetry/resource-detector-aws" "^1.3.0" + "@opentelemetry/resource-detector-container" "^0.3.0" + "@opentelemetry/resource-detector-gcp" "^0.29.0" + "@opentelemetry/resources" "^1.12.0" + "@opentelemetry/sdk-node" "^0.41.0" + tslib "^2.3.1" + +"@opentelemetry/context-async-hooks@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.15.2.tgz#116bd5fef231137198d5bf551e8c0521fbdfe928" + integrity sha512-VAMHG67srGFQDG/N2ns5AyUT9vUcoKpZ/NpJ5fDQIPfJd7t3ju+aHwvDsMcrYBWuCh03U3Ky6o16+872CZchBg== + +"@opentelemetry/core@1.15.2", "@opentelemetry/core@^1.0.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.8.0": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.15.2.tgz#5b170bf223a2333884bbc2d29d95812cdbda7c9f" + integrity sha512-+gBv15ta96WqkHZaPpcDHiaz0utiiHZVfm2YOYSqFGrUaJpPkMoSuLBB58YFQGi6Rsb9EHos84X6X5+9JspmLw== + dependencies: + "@opentelemetry/semantic-conventions" "1.15.2" + +"@opentelemetry/exporter-jaeger@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.15.2.tgz#1ac7020d798ec4e47417bd90e00763e0947e17de" + integrity sha512-BwYd5836GYvuiQcF4l5X0ca09jGJr/F37MMGyz94VH0b1dp0uYBwRJw2CQh56RlVZEdpKv29JyDRVZ/4UrRgLQ== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + jaeger-client "^3.15.0" + +"@opentelemetry/exporter-trace-otlp-grpc@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.41.2.tgz#445f850f4675e0afc3e326b2663576fa0b5fbee4" + integrity sha512-tRM/mq7PFj7mXCws5ICMVp/rmgU93JvZdoLE0uLj4tugNz231u2ZgeRYXulBjdeHM88ZQSsWTJMu2mvr/3JV1A== + dependencies: + "@grpc/grpc-js" "^1.7.1" + "@opentelemetry/core" "1.15.2" + "@opentelemetry/otlp-grpc-exporter-base" "0.41.2" + "@opentelemetry/otlp-transformer" "0.41.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + +"@opentelemetry/exporter-trace-otlp-http@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.41.2.tgz#4818088c652f2077a55c9c1364d8320e994dc00f" + integrity sha512-Y0fGLipjZXLMelWtlS1/MDtrPxf25oM408KukRdkN31a1MEFo4h/ZkNwS7ZfmqHGUa+4rWRt2bi6JBiqy7Ytgw== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/otlp-exporter-base" "0.41.2" + "@opentelemetry/otlp-transformer" "0.41.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + +"@opentelemetry/exporter-trace-otlp-proto@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.41.2.tgz#8e8f823d5264e34dc7c8b400f9d03871c7bfcbc5" + integrity sha512-IGZga9IIckqYE3IpRE9FO9G5umabObIrChlXUHYpMJtDgx797dsb3qXCvLeuAwB+HoB8NsEZstlzmLnoa6/HmA== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/otlp-exporter-base" "0.41.2" + "@opentelemetry/otlp-proto-exporter-base" "0.41.2" + "@opentelemetry/otlp-transformer" "0.41.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + +"@opentelemetry/exporter-zipkin@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.15.2.tgz#4f72482909fd7a197fb614fc1f285b15ca008a39" + integrity sha512-j9dPe8tyx4KqIqJAfZ/LCYfkF9+ggsT0V1+bVg9ZKTBNcLf5dTsTMdcxUxc/9s599kgcn6UERnti/tozbzwa6Q== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + +"@opentelemetry/instrumentation-amqplib@^0.33.0": + version "0.33.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.33.1.tgz#a459073939691a4a1dabf96ef7c1af168fb27e35" + integrity sha512-Eg797WDHVDcRr6+5tihh7ab+ZjS5yCOoW4PkUYCcJHVT31AGfi+PlkLgHknW+uT1oKijMC4D1p6jDa/2rzRv/g== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-aws-lambda@^0.36.0": + version "0.36.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.36.0.tgz#bfd3ffac407a1339fc0bdc9afb4bb9e2dfe2ef39" + integrity sha512-GkehkjN4vHTc5HNIBlKddrm+EVch2cNEfbLcV7tXLu0Hu95kt6PPOwxHDYRxgvu1auFpJY0epUzmPd11zI706A== + dependencies: + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/propagator-aws-xray" "^1.3.0" + "@opentelemetry/resources" "^1.8.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/aws-lambda" "8.10.81" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-aws-sdk@^0.35.0": + version "0.35.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.35.0.tgz#359f56c956afbf526d729e1e8f7d1c28347afaef" + integrity sha512-jKf2nuTe3kYhtINGmgaVlw54q5pgX959zK2abGdvoUSdSP3Pv36YwNZk1K+jAKCN4I71R8/Qp1driAuKKj/Kxg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/propagation-utils" "^0.30.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-bunyan@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.32.1.tgz#ed315aa3ed6c5e47733c1c74d116c46a5ac1d468" + integrity sha512-TjH357ldA5DpK09XUDWffqV9Km++N9H0dwmxHrElM2TSe4Usgkgw6mlodbuh45hoVDD+cCPi+GO6Dq1QLVEdZg== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@types/bunyan" "1.8.8" + +"@opentelemetry/instrumentation-cassandra-driver@^0.33.0": + version "0.33.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.33.1.tgz#c68af7c975deb1e5793d2051ca327a5834a5037f" + integrity sha512-nn8XtLB1XmViEAnNnZ43jHojYxgNJ1W+QF2B3yBmfVqXJnE0IbzhIiPmU+Zx3ZSzIoWS0EQQM3ljcgDC03FZ7A== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-connect@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.32.1.tgz#020bcaf1b384e0c4a60b2023ffcdc89e998eb2c1" + integrity sha512-QHi0hTXtqZj3wSyvKwFmkGYHRnGdl8w76MHZj3Rekxe4ILpcn78fZGJSbA+0eYdOWHnGP0c483uMOeGH08XYmA== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/connect" "3.4.35" + +"@opentelemetry/instrumentation-dataloader@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.5.1.tgz#1b63770793ceedacb42051b22b6a5eb7b54b26d8" + integrity sha512-dqYITnlCo7FSZ8mhyxh9TtogwcebGcuMaXTjYDyIKGshDcjCxhvhNjFDe4y3RD/g/EFKINkYVkVXB1lDqZdxTA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + +"@opentelemetry/instrumentation-dns@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.32.1.tgz#cb0e24e1e2f043112461e533aa7bdaf3389c3d7f" + integrity sha512-WtfwHITUUs2CkRCDT+hbSBy4+ltHIvQDbl/B7TZLQHwpZ6jTRQFsCBzPdhgND4XpHvsXDfLhQihguXyXRGILkg== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + semver "^7.3.2" + +"@opentelemetry/instrumentation-express@^0.33.0": + version "0.33.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.33.1.tgz#0710f839d2a395014d2ffef9390074bb60009841" + integrity sha512-awrpiTZWnLOCJ4TeDMTrs6/gH/oXbNipoPx3WUKQlA1yfMlpNynqokTyCYv1n10Zu9Y2P/nIhoNnUw0ywp61nA== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/express" "4.17.17" + +"@opentelemetry/instrumentation-fastify@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.32.1.tgz#59a2bcb9c0d233c9893ab3c73e951800caa87ab5" + integrity sha512-DGWWGAe8SCULvqlJpL2zJ7o1gYzmhAfKRjJrWmwyZshnjGEw3PQ3b1GHivoxZ6zB7D6ykttxanQovrAKk83WoA== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-fs@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.8.1.tgz#752f286d285374afd65e92ba721c9fa9119b67ac" + integrity sha512-a5U6ydfqVeT4Zp6GL5lZDZNJAmic3CCtgg/f2yqvnpq2fE0cyD/XlW9JWzGhAJaq29E1bxtb9FJ0n6ee3c9dYQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-generic-pool@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.32.1.tgz#ae5f58a07eac37e1ac2a797ad9b38d94764b3be8" + integrity sha512-uTsiAq9A486eKi4hSqDi5vF5TZK0KGdLn5CBqhVvc3oPTz2We69etGKqyv2sV51LwX749iRNHbVFDm2Km+wMSQ== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/generic-pool" "^3.1.9" + +"@opentelemetry/instrumentation-graphql@^0.35.0": + version "0.35.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.35.1.tgz#e49ec2256bcc4820458688abac0212ac781864c0" + integrity sha512-bAM4W5wU0lZ1UIKK/5b4p8LEU8N6W+VgpcnUIK7GTTDxdhcWTd3Q6oyS6nauhZSzEnAEmmJVXaLQAGIU4sEkyA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + +"@opentelemetry/instrumentation-grpc@^0.41.0": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.41.2.tgz#38b51eda1bcb6bf8d422410fa4596b56b03e98ab" + integrity sha512-+fh9GUFv97p25CMreUv4OdP5L21hPgfX3d4fuQ0KIgIZIaX2M6/8cr5Ik+8zWsyhYzfFX3CKq6BXm3UBg7cswQ== + dependencies: + "@opentelemetry/instrumentation" "0.41.2" + "@opentelemetry/semantic-conventions" "1.15.2" + +"@opentelemetry/instrumentation-hapi@^0.32.0": + version "0.32.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.32.0.tgz#59362dab5d2a2d7fdfe47bda0cd75dec923b0ece" + integrity sha512-Wl43lSVqqJZAxhWE1BWlV9yoInEOGiKeGqNhphoGJLqblmlF8Yxob1t2fK/wTj2srmmm1XU70olwhN09uOQxpg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/hapi__hapi" "20.0.9" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-http@^0.41.0": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.41.2.tgz#dad5a693eaad2113ce7ed089fa46ef98d79f2bfc" + integrity sha512-dzOC6xkfK0LM6Dzo91aInLdSbdIzKA0IgSDnyLi6YZ0Z7c1bfrFncFx/3gZs8vi+KXLALgfMlpzE7IYDW/cM3A== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/instrumentation" "0.41.2" + "@opentelemetry/semantic-conventions" "1.15.2" + semver "^7.5.1" + +"@opentelemetry/instrumentation-ioredis@^0.35.0": + version "0.35.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.35.1.tgz#86d77dc0878707ab456ccebf78233cb0e7127635" + integrity sha512-lixraoS9rs81783QRjQ56/S5KzVBllC+zs7UJuTGODi5Egn/YMGp5lNnlbkUxeJl9LMyADMiP7ZGpQtfKwdc3g== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/redis-common" "^0.36.1" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/ioredis4" "npm:@types/ioredis@^4.28.10" + +"@opentelemetry/instrumentation-knex@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.32.1.tgz#0c21b1d35be82a9e1732879192a373b145453ed8" + integrity sha512-s+5BtsYUendDTrWAxkr50X3+kb+sVffFzp4z5DC+aZt52P/kF85wm6GyC1mREvvhhK2UKrCq2yMVKD90z0FKsA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-koa@^0.35.0": + version "0.35.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.35.0.tgz#499ff61accd398e2444c0e52f008be88eec8fb33" + integrity sha512-Q/KclXdHKE3sGlalxxX43lx4b8eY5lv5LSdG3mY8aBsrmw1Mx6Cv4VAeqA4ecCygeapTmf9jjOLmgro15IJ3AQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/koa" "2.13.6" + "@types/koa__router" "8.0.7" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-lru-memoizer@^0.33.0": + version "0.33.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.33.1.tgz#636343165dfd83ed66d14abdc19f0e4c070cb1a3" + integrity sha512-1FFOlGTEigMWppEkv7o+IyeyWTXXpFAfmcFjJRph5m88RsotgzPLCnxaSeS0GMU7E8UJplusNmmsnu7jPJ2YqA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + +"@opentelemetry/instrumentation-memcached@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.32.1.tgz#7b21aa22d90d37f353d6cc2220c98390227f5396" + integrity sha512-laolY41/k6KHYnBQrWpnMlEK49/g8/OQBtvSiPdHiF46wW3eWpXmaTGMRksrRGUtyE+VMRhf7WIDRUYLZULP1g== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/memcached" "^2.2.6" + +"@opentelemetry/instrumentation-mongodb@^0.36.0": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.36.1.tgz#a92e48f2cb4e2e2de430d900c96e21911709e137" + integrity sha512-//FdYXGcUO08Y1tVPXlcEvUYCnRU8tlBgYBe3ZMjF7E1GMaFti4Xy6sAHVreyl0ZQjBTaGtHdyORHIOTKUM4ZA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/sdk-metrics" "^1.9.1" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-mongoose@^0.33.0": + version "0.33.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.33.1.tgz#0e37eed215fb7fbf8adc0e70199bb8992cb1ea21" + integrity sha512-IzYcEZSmlaOlkyACt8gTl0z3eEQafxzEAt/+W+FdNBiUdm81qpVx/1bpzJwSgIsgcLf27Dl5WsPmrSAi4+Bcng== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-mysql2@^0.34.0": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.34.1.tgz#d7ce741a7d9a7da270fa791e1c64d8cedd58b5b7" + integrity sha512-SPwgLI2H+gH+GP7b5cWQlFqO/7UeHvw6ZzFKxwLr4vy8wmxYF4aBMLc8qVO8bdXFHd114v0IzOIAvpG6sl/zYQ== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@opentelemetry/sql-common" "^0.40.0" + +"@opentelemetry/instrumentation-mysql@^0.34.0": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.34.1.tgz#9703d21615dd5ee6b9eda1d74029ba75eec46c9a" + integrity sha512-zQq7hN3ILm1vIJCGeKHRc4pTK8LOmkTt8oKWf0v+whFs7axieIhXZMoCqIBm6BigLy3Trg5iaKyuSrx7kO6q2g== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/mysql" "2.15.21" + +"@opentelemetry/instrumentation-nestjs-core@^0.33.0": + version "0.33.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.33.1.tgz#a6e0175bcda25e455339a5527268e746be969297" + integrity sha512-Y5Khvp8ODA6TuDcZKAc63cYDeeZAA/n0ceF0pcVCJwA2NBeD0hmTrCJXES2cvt7wVbHV/SYCu7OpYDQkNjbBWw== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-net@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-net/-/instrumentation-net-0.32.1.tgz#7b394b5250f160b46f2363d37c1ebe7c8c1ac6b7" + integrity sha512-r9YC8fFDi+B/JiNfMn+vJaOpgdA83bQM3u4mW9mJi2hAI/LcvjJYPx4aTRLWAPSd/HRG/Olzdvx5LdWvzL8LHg== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-pg@^0.36.0": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.36.1.tgz#66e3aa10948c6e3188d04676dbf304ae8571ce2f" + integrity sha512-k8L7RSRTQ6e+DbHEXZB8Tmf/efkQnWKeClpZb3TEdb34Pvme4PmcpG2zb6JtM99nNrshNlVDLCZ90U3xDneTbw== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@opentelemetry/sql-common" "^0.40.0" + "@types/pg" "8.6.1" + "@types/pg-pool" "2.0.3" + +"@opentelemetry/instrumentation-pino@^0.34.0": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.34.1.tgz#7567d2e2298536229f5a72c6c222b79b3b6689c7" + integrity sha512-/FW/wxTshwwmiSE8KgVoWvfjxz5omKBdDbP0McKZk84V02lwwJk0m7+kc2cSOed5rk7iprpZolwO8a8AFVanNA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + +"@opentelemetry/instrumentation-redis-4@^0.35.0": + version "0.35.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.35.1.tgz#40ae092c04ef9f92148197f8b3fe9eef4657123e" + integrity sha512-tQ07wvtjUbHSvvhPPvWyZjYTSzVBTpC746ro5szLnniodvxtKkmP/N+R9KyFXfyH7wwaLIR1Scgq3XSGSppt+Q== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/redis-common" "^0.36.1" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-redis@^0.35.0": + version "0.35.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.35.1.tgz#d821e8d0e9470c9ab144aa45292ec7991dca6bb1" + integrity sha512-zY7eTzGyJCMX/0o04Q9yLy7gllf7Zh4s+g7Kv1d2cMLtTt9zGSlncqj49uNCnneywnpMNRUIwcmd+Ch1bQeh+g== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/redis-common" "^0.36.1" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-restify@^0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.33.0.tgz#4f7fbcda93e428052c07d6edc05c192e868917be" + integrity sha512-evDjcF6M9G+KH/GCjtUx9Vnm/CBZ9CBfmm/RP6Aeo20y6Kset1ZEoPK79JT7JK1sCPqViBPoj4qnFePz9/20lg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-router@^0.33.0": + version "0.33.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-router/-/instrumentation-router-0.33.1.tgz#ec37e7470675a442e48c2e9e4753f595951c16d8" + integrity sha512-nz8PvjYMQWFgR17Yc5Sj624CamhXP021mWaWfHx6RhI6o67sPt+DT5468yZJZV1gMnaOSQfiBkjWZ7AGQkRutw== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-socket.io@^0.34.0": + version "0.34.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.34.1.tgz#ac5814f8a805a550c9e63e5f7f9ac4186ec8d579" + integrity sha512-v9US0hXJaY7dkKOC2/CMLB526wn9F3CQrkeVUidvSm+AxFBoYXKdAUJijdBPWT4PKY98/VjFHuZ3HSe4QD8zPA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/instrumentation-tedious@^0.6.0": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.6.1.tgz#2b488581161839c19ef0641d0afdc9fa6cc8210b" + integrity sha512-zwgLKmWtAn0XsMb98aMaI7gCawzPqpy+LOgGTlYmUdqSVYnzMAn4QKrx24Rrd5pgmzOEIbAWHlpN7pOc1eIqxA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/tedious" "^4.0.6" + +"@opentelemetry/instrumentation-winston@^0.32.0": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.32.1.tgz#0548543151ac7505cffc64c0019ca3d5bdda5638" + integrity sha512-wgXb2W2cbNdRQfXTH0jcnfbhlVPapmu13Wqhedj2pMpXS2aBnWAdvNFlArS6q84MEhzv3A4fVevjbwXa4uCzwQ== + dependencies: + "@opentelemetry/instrumentation" "^0.41.2" + +"@opentelemetry/instrumentation@0.41.2", "@opentelemetry/instrumentation@^0.41.0", "@opentelemetry/instrumentation@^0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.41.2.tgz#cae11fa64485dcf03dae331f35b315b64bc6189f" + integrity sha512-rxU72E0pKNH6ae2w5+xgVYZLzc5mlxAbGzF4shxMVK8YC2QQsfN38B2GPbj0jvrKWWNUElfclQ+YTykkNg/grw== + dependencies: + "@types/shimmer" "^1.0.2" + import-in-the-middle "1.4.2" + require-in-the-middle "^7.1.1" + semver "^7.5.1" + shimmer "^1.2.1" + +"@opentelemetry/otlp-exporter-base@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.41.2.tgz#5928dfedb2a70117f03809862cf2cbdf8b7f9bf3" + integrity sha512-pfwa6d+Dax3itZcGWiA0AoXeVaCuZbbqUTsCtOysd2re8C2PWXNxDONUfBWsn+KgxAdi+ljwTjJGiaVLDaIEvQ== + dependencies: + "@opentelemetry/core" "1.15.2" + +"@opentelemetry/otlp-grpc-exporter-base@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.41.2.tgz#b056915aa274947517ac86b0c78533db274404e8" + integrity sha512-OErK8dYjXG01XIMIpmOV2SzL9ctkZ0Nyhf2UumICOAKtgLvR5dG1JMlsNVp8Jn0RzpsKc6Urv7JpP69wzRXN+A== + dependencies: + "@grpc/grpc-js" "^1.7.1" + "@opentelemetry/core" "1.15.2" + "@opentelemetry/otlp-exporter-base" "0.41.2" + protobufjs "^7.2.3" + +"@opentelemetry/otlp-proto-exporter-base@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-proto-exporter-base/-/otlp-proto-exporter-base-0.41.2.tgz#10b1a4bb973bd6e0e741931d90f22387c5a3a151" + integrity sha512-BxmEMiP6tHiFroe5/dTt9BsxCci7BTLtF7A6d4DKHLiLweWWZxQ9l7hON7qt/IhpKrQcAFD1OzZ1Gq2ZkNzhCw== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/otlp-exporter-base" "0.41.2" + protobufjs "^7.2.3" + +"@opentelemetry/otlp-transformer@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.41.2.tgz#cd3a7185ef77fe9b7b4c2d2f9e001fa1d2fa6cf8" + integrity sha512-jJbPwB0tNu2v+Xi0c/v/R3YBLJKLonw1p+v3RVjT2VfzeUyzSp/tBeVdY7RZtL6dzZpA9XSmp8UEfWIFQo33yA== + dependencies: + "@opentelemetry/api-logs" "0.41.2" + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/sdk-logs" "0.41.2" + "@opentelemetry/sdk-metrics" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + +"@opentelemetry/propagation-utils@^0.30.0": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.1.tgz#2fbb48cec2c118d14e5814258990973fca5e2860" + integrity sha512-GCZg19gBSOTCeHvSCVy08WUyKAp2LyIRcRQPZk8MMAbmz8JWha3huBS9tNXjB4hYwRqW2SJOZzoYjt2P/BxvEw== + +"@opentelemetry/propagator-aws-xray@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-aws-xray/-/propagator-aws-xray-1.3.1.tgz#7fc77a95fe89c705442b0e5a4218422c2954cc07" + integrity sha512-6fDMzFlt5r6VWv7MUd0eOpglXPFqykW8CnOuUxJ1VZyLy6mV1bzBlzpsqEmhx1bjvZYvH93vhGkQZqrm95mlrQ== + dependencies: + "@opentelemetry/core" "^1.0.0" + +"@opentelemetry/propagator-b3@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-1.15.2.tgz#7bcb9fa645042a440922669fbac06a1a91e6704b" + integrity sha512-ZSrL3DpMEDsjD8dPt9Ze3ue53nEXJt512KyxXlLgLWnSNbe1mrWaXWkh7OLDoVJh9LqFw+tlvAhDVt/x3DaFGg== + dependencies: + "@opentelemetry/core" "1.15.2" + +"@opentelemetry/propagator-jaeger@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.15.2.tgz#90757fc9529da806a1845f502acb6d0eb821e3db" + integrity sha512-6m1yu7PVDIRz6BwA36lacfBZJCfAEHKgu+kSyukNwVdVjsTNeyD9xNPQnkl0WN7Rvhk8/yWJ83tLPEyGhk1wCQ== + dependencies: + "@opentelemetry/core" "1.15.2" + +"@opentelemetry/propagator-ot-trace@^0.27.0": + version "0.27.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-ot-trace/-/propagator-ot-trace-0.27.1.tgz#6718aefd6d0401726e2232fd7ad84515f2b34ca9" + integrity sha512-+Aeht0+1kv/7KAYxNVQFDczEgMQIhi+yO/QNBTxEUGWEQshxznqT2Knqd5nARY8IF3okFdkP4PJUFubYfjWSzw== + +"@opentelemetry/redis-common@^0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.1.tgz#79bca902603dd27862223a751be0f4bb0be54c2b" + integrity sha512-YjfNEr7DK1Ymc5H0bzhmqVvMcCs+PUEUerzrpTFdHfZxj3HpnnjZTIFKx/gxiL/sajQ8dxycjlreoYTVYKBXlw== + +"@opentelemetry/resource-detector-alibaba-cloud@^0.28.0": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.28.1.tgz#f65bfc25d0f26d55bce33a8caa788274353445cf" + integrity sha512-0ucRgwid6bSAgYL5fiTkLh7aS9cPyz+Ijyv961SZbpMeIgVBvU931676xrdGNqlmuxavt24BrNUUWZ4XKq8ViA== + dependencies: + "@opentelemetry/resources" "^1.0.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/resource-detector-aws@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.3.1.tgz#4d221859c19b0e4e604ac88224129e82a78ec8b6" + integrity sha512-1n3U0ns0xlA8EIOMY1oEP5+5rZE/nfhIld6nw8T8PK4PkS3kAQb1ZCj3RXajs3qA+qWWIaEvCNREx3A0Ifyt3Q== + dependencies: + "@opentelemetry/core" "^1.0.0" + "@opentelemetry/resources" "^1.0.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/resource-detector-container@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resource-detector-container/-/resource-detector-container-0.3.1.tgz#7b59f5c9d2062db78f3dd2e2c59adbfafdd7d5a9" + integrity sha512-7zQASISRLmsaCKurvaoi7kTa0ab4iQEvPVfRo4k5RLSVi4puaCcC+2qOd6Fk4jEqNueevhyn2upGUeH+0EJ6yQ== + dependencies: + "@opentelemetry/resources" "^1.0.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + +"@opentelemetry/resource-detector-gcp@^0.29.0": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.1.tgz#eef9a08fbac816f3906a327be508e77f23a15a90" + integrity sha512-u5mB53I49m0cXQ97dgZlgEnNin9xqwl9au2sXmblHG9XS6PocGoAgAiXGYYvITWhR3ID5Ei2GyGoJDFdAtCrVA== + dependencies: + "@opentelemetry/core" "^1.0.0" + "@opentelemetry/resources" "^1.0.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + gcp-metadata "^5.0.0" + +"@opentelemetry/resources@1.15.2", "@opentelemetry/resources@^1.0.0", "@opentelemetry/resources@^1.12.0", "@opentelemetry/resources@^1.8.0": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.15.2.tgz#0c9e26cb65652a1402834a3c030cce6028d6dd9d" + integrity sha512-xmMRLenT9CXmm5HMbzpZ1hWhaUowQf8UB4jMjFlAxx1QzQcsD3KFNAVX/CAWzFPtllTyTplrA4JrQ7sCH3qmYw== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + +"@opentelemetry/sdk-logs@0.41.2": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.41.2.tgz#c3eeb6793bdfa52351d66e2e66637e433abed672" + integrity sha512-smqKIw0tTW15waj7BAPHFomii5c3aHnSE4LQYTszGoK5P9nZs8tEAIpu15UBxi3aG31ZfsLmm4EUQkjckdlFrw== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + +"@opentelemetry/sdk-metrics@1.15.2", "@opentelemetry/sdk-metrics@^1.15.1", "@opentelemetry/sdk-metrics@^1.9.1": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.15.2.tgz#eadd0a049de9cd860e1e0d49eea01156469c4b60" + integrity sha512-9aIlcX8GnhcsAHW/Wl8bzk4ZnWTpNlLtud+fxUfBtFATu6OZ6TrGrF4JkT9EVrnoxwtPIDtjHdEsSjOqisY/iA== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + lodash.merge "^4.6.2" + +"@opentelemetry/sdk-node@^0.41.0", "@opentelemetry/sdk-node@^0.41.1": + version "0.41.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-node/-/sdk-node-0.41.2.tgz#7ac2fc149d371a9f17c2adba395a9aa257ed1bf4" + integrity sha512-t3vaB5ajoXLtVFoL8TSoSgaVATmOyUfkIfBE4nvykm0dM2vQjMS/SUUelzR06eiPTbMPsr2UkevWhy2/oXy2vg== + dependencies: + "@opentelemetry/api-logs" "0.41.2" + "@opentelemetry/core" "1.15.2" + "@opentelemetry/exporter-jaeger" "1.15.2" + "@opentelemetry/exporter-trace-otlp-grpc" "0.41.2" + "@opentelemetry/exporter-trace-otlp-http" "0.41.2" + "@opentelemetry/exporter-trace-otlp-proto" "0.41.2" + "@opentelemetry/exporter-zipkin" "1.15.2" + "@opentelemetry/instrumentation" "0.41.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/sdk-logs" "0.41.2" + "@opentelemetry/sdk-metrics" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + "@opentelemetry/sdk-trace-node" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + +"@opentelemetry/sdk-trace-base@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.15.2.tgz#4821f94033c55a6c8bbd35ae387b715b6108517a" + integrity sha512-BEaxGZbWtvnSPchV98qqqqa96AOcb41pjgvhfzDij10tkBhIu9m0Jd6tZ1tJB5ZHfHbTffqYVYE0AOGobec/EQ== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/resources" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + +"@opentelemetry/sdk-trace-node@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.15.2.tgz#987c929597ca274995164508f57a15f682d9de2f" + integrity sha512-5deakfKLCbPpKJRCE2GPI8LBE2LezyvR17y3t37ZI3sbaeogtyxmBaFV+slmG9fN8OaIT+EUsm1QAT1+z59gbQ== + dependencies: + "@opentelemetry/context-async-hooks" "1.15.2" + "@opentelemetry/core" "1.15.2" + "@opentelemetry/propagator-b3" "1.15.2" + "@opentelemetry/propagator-jaeger" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + semver "^7.5.1" + +"@opentelemetry/sdk-trace-web@^1.15.1": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.15.2.tgz#1db22d0afbd07b1287e8a331e30862eb19b24e20" + integrity sha512-OjCrwtu4b+cAt540wyIr7d0lCA/cY9y42lmYDFUfJ8Ixj2bByIUJ4yyd9M7mXHpQHdiR/Kq2vzsgS14Uj+RU0Q== + dependencies: + "@opentelemetry/core" "1.15.2" + "@opentelemetry/sdk-trace-base" "1.15.2" + "@opentelemetry/semantic-conventions" "1.15.2" + +"@opentelemetry/semantic-conventions@1.15.2", "@opentelemetry/semantic-conventions@^1.15.1": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.2.tgz#3bafb5de3e20e841dff6cb3c66f4d6e9694c4241" + integrity sha512-CjbOKwk2s+3xPIMcd5UNYQzsf+v94RczbdNix9/kQh38WiQkM90sUOi3if8eyHFgiBjBjhwXrA7W3ydiSQP9mw== + "@opentelemetry/semantic-conventions@^1.0.0": version "1.12.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.12.0.tgz#19c959bdb900986e74939d4227e757aa16936b91" @@ -4095,6 +4822,13 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.3.1.tgz#ba07b864a3c955f061aa30ea3ef7f4ae4449794a" integrity sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA== +"@opentelemetry/sql-common@^0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.40.0.tgz#8cbed0722354d62997c3b9e1adf0e16257be6b15" + integrity sha512-vSqRJYUPJVjMFQpYkQS3ruexCPSZJ8esne3LazLwtCPaPRvzZ7WG3tX44RouAn7w4wMp8orKguBqtt+ng2UTnw== + dependencies: + "@opentelemetry/core" "^1.1.0" + "@openzeppelin/upgrades@^2.8.0": version "2.8.0" resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades/-/upgrades-2.8.0.tgz#8086ab9c99d9f8dac7205030b0f9e7e4a280c4a3" @@ -4308,6 +5042,23 @@ "@noble/hashes" "~1.2.0" "@scure/base" "~1.1.0" +"@sideway/address@^4.1.3": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sinclair/typebox@^0.25.16": version "0.25.24" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" @@ -5022,7 +5773,7 @@ resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-7.2.1.tgz#bb16403c17754b0c4d5772d71d03b924a03d4c80" integrity sha512-YK8irIC+eMrrmtGx0H4ISn9GgzLd9dojZWJaMbjp1YHLl2VqqNFBNrL5Q3KjGf4VE3sf/4hmq6EhQZ7kZp1NoQ== -"@types/accepts@^1.3.5": +"@types/accepts@*", "@types/accepts@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ== @@ -5043,6 +5794,11 @@ dependencies: "@types/pvutils" "*" +"@types/aws-lambda@8.10.81": + version "8.10.81" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.81.tgz#6d405269aad82e05a348687631aa9a587cdbe158" + integrity sha512-C1rFKGVZ8KwqhwBOYlpoybTSRtxu2433ea6JaO3amc6ubEe08yQoFsPa9aU9YqvX7ppeZ25CnCtC4AH9mhtxsQ== + "@types/babel__core@^7.1.14": version "7.20.0" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" @@ -5157,18 +5913,33 @@ dependencies: "@types/node" "*" -"@types/connect@*": +"@types/connect@*", "@types/connect@3.4.35": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== dependencies: "@types/node" "*" +"@types/content-disposition@*": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.5.tgz#650820e95de346e1f84e30667d168c8fd25aa6e3" + integrity sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA== + "@types/cookiejar@*": version "2.1.2" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== +"@types/cookies@*": + version "0.7.7" + resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.7.7.tgz#7a92453d1d16389c05a5301eef566f34946cfd81" + integrity sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA== + dependencies: + "@types/connect" "*" + "@types/express" "*" + "@types/keygrip" "*" + "@types/node" "*" + "@types/cors@2.8.12": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" @@ -5257,6 +6028,16 @@ "@types/range-parser" "*" "@types/send" "*" +"@types/express@*", "@types/express@4.17.17", "@types/express@^4.17.14", "@types/express@^4.17.6": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/express@4.17.14": version "4.17.14" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" @@ -5276,16 +6057,6 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" -"@types/express@^4.17.14", "@types/express@^4.17.6": - version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/fetch-mock@^7.3.5": version "7.3.5" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.5.tgz#7aee678c4e7c7e1a168bae8fdab5b8d712e377f6" @@ -5305,6 +6076,13 @@ dependencies: "@types/node" "*" +"@types/generic-pool@^3.1.9": + version "3.8.1" + resolved "https://registry.yarnpkg.com/@types/generic-pool/-/generic-pool-3.8.1.tgz#b9b25b2ba4733057fa5df1818352d3205c48e87b" + integrity sha512-eaMAbZS0EfKvaP5PUZ/Cdf5uJBO2t6T3RdvQTKuMqUwGhNpCnPAsKWEMyV+mCeCQG3UiHrtgdzni8X6DmhxRaQ== + dependencies: + generic-pool "*" + "@types/glob@*": version "8.1.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.1.0.tgz#b63e70155391b0584dce44e7ea25190bbc38f2fc" @@ -5333,6 +6111,39 @@ dependencies: "@types/node" "*" +"@types/hapi__catbox@*": + version "10.2.4" + resolved "https://registry.yarnpkg.com/@types/hapi__catbox/-/hapi__catbox-10.2.4.tgz#4d0531a6c2d0e45024f724020d536041ef8ffe30" + integrity sha512-A6ivRrXD5glmnJna1UAGw87QNZRp/vdFO9U4GS+WhOMWzHnw+oTGkMvg0g6y1930CbeheGOCm7A1qHsqH7AXqg== + +"@types/hapi__hapi@20.0.9": + version "20.0.9" + resolved "https://registry.yarnpkg.com/@types/hapi__hapi/-/hapi__hapi-20.0.9.tgz#9d570846c96268266a14c970c13aeeaccfc8e172" + integrity sha512-fGpKScknCKZityRXdZgpCLGbm41R1ppFgnKHerfZlqOOlCX/jI129S6ghgBqkqCE8m9A0CIu1h7Ch04lD9KOoA== + dependencies: + "@hapi/boom" "^9.0.0" + "@hapi/iron" "^6.0.0" + "@hapi/podium" "^4.1.3" + "@types/hapi__catbox" "*" + "@types/hapi__mimos" "*" + "@types/hapi__shot" "*" + "@types/node" "*" + joi "^17.3.0" + +"@types/hapi__mimos@*": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/hapi__mimos/-/hapi__mimos-4.1.4.tgz#4f8a1c58345fc468553708d3cb508724aa081bd9" + integrity sha512-i9hvJpFYTT/qzB5xKWvDYaSXrIiNqi4ephi+5Lo6+DoQdwqPXQgmVVOZR+s3MBiHoFqsCZCX9TmVWG3HczmTEQ== + dependencies: + "@types/mime-db" "*" + +"@types/hapi__shot@*": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/hapi__shot/-/hapi__shot-4.1.2.tgz#d4011999a91e8101030fece1462fe99769455855" + integrity sha512-8wWgLVP1TeGqgzZtCdt+F+k15DWQvLG1Yv6ZzPfb3D5WIo5/S+GGKtJBVo2uNEcqabP5Ifc71QnJTDnTmw1axA== + dependencies: + "@types/node" "*" + "@types/hdkey@^0.7.0": version "0.7.1" resolved "https://registry.yarnpkg.com/@types/hdkey/-/hdkey-0.7.1.tgz#9bc63ebbe96b107b277b65ea7a95442a677d0d61" @@ -5340,11 +6151,21 @@ dependencies: "@types/node" "*" +"@types/http-assert@*": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.3.tgz#ef8e3d1a8d46c387f04ab0f2e8ab8cb0c5078661" + integrity sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA== + "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== +"@types/http-errors@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" + integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== + "@types/humanize-duration@^3.18.0": version "3.27.1" resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.1.tgz#f14740d1f585a0a8e3f46359b62fda8b0eaa31e7" @@ -5358,6 +6179,13 @@ "@types/through" "*" rxjs "^6.4.0" +"@types/ioredis4@npm:@types/ioredis@^4.28.10": + version "4.28.10" + resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.28.10.tgz#40ceb157a4141088d1394bb87c98ed09a75a06ff" + integrity sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ== + dependencies: + "@types/node" "*" + "@types/is-base64@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/is-base64/-/is-base64-1.1.1.tgz#a17d2b0075f637f80f9ab5f76f0071a65f6965d4" @@ -5419,6 +6247,11 @@ dependencies: "@types/node" "*" +"@types/keygrip@*": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" + integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw== + "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -5426,6 +6259,48 @@ dependencies: "@types/node" "*" +"@types/koa-compose@*": + version "3.2.5" + resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d" + integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ== + dependencies: + "@types/koa" "*" + +"@types/koa@*": + version "2.13.8" + resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.8.tgz#4302d2f2712348aadb6c0b03eb614f30afde486b" + integrity sha512-Ugmxmgk/yPRW3ptBTh9VjOLwsKWJuGbymo1uGX0qdaqqL18uJiiG1ZoV0rxCOYSaDGhvEp5Ece02Amx0iwaxQQ== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/http-errors" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + +"@types/koa@2.13.6": + version "2.13.6" + resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.6.tgz#6dc14e727baf397310aa6f414ebe5d144983af42" + integrity sha512-diYUfp/GqfWBAiwxHtYJ/FQYIXhlEhlyaU7lB/bWQrx4Il9lCET5UwpFy3StOAohfsxxvEQ11qIJgT1j2tfBvw== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/http-errors" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + +"@types/koa__router@8.0.7": + version "8.0.7" + resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.7.tgz#663d69d5ddebff5aaca27c0594430b3ba6ea20be" + integrity sha512-OB3Ax75nmTP+WR9AgdzA42DI7YmBtiNKN2g1Wxl+d5Dyek9SWt740t+ukwXSmv/jMBCUPyV3YEI93vZHgdP7UQ== + dependencies: + "@types/koa" "*" + "@types/ledgerhq__hw-transport-node-hid@^4.22.2": version "4.22.2" resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-node-hid/-/ledgerhq__hw-transport-node-hid-4.22.2.tgz#f3de58b9b49b461dd96e5bfb646328c242e70aca" @@ -5495,6 +6370,18 @@ resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== +"@types/memcached@^2.2.6": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@types/memcached/-/memcached-2.2.7.tgz#b3de026a11a4c0a18fb079cfeeaea10a41da20f9" + integrity sha512-ImJbz1i8pl+OnyhYdIDnHe8jAuM8TOwM/7VsciqhYX3IL0jPPUToAtVxklfcWFGYckahEYZxhd9FS0z3MM1dpA== + dependencies: + "@types/node" "*" + +"@types/mime-db@*": + version "1.43.1" + resolved "https://registry.yarnpkg.com/@types/mime-db/-/mime-db-1.43.1.tgz#c2a0522453bb9b6e84ee48b7eef765d19bcd519e" + integrity sha512-kGZJY+R+WnR5Rk+RPHUMERtb2qBRViIHCBdtUrY+NmwuGb8pQdfTqQiCKPrxpdoycl8KWm2DLdkpoSdt479XoQ== + "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -5547,6 +6434,13 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/mysql@2.15.21": + version "2.15.21" + resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.21.tgz#7516cba7f9d077f980100c85fd500c8210bd5e45" + integrity sha512-NPotx5CVful7yB+qZbWtXL2fA4e7aEHkihHLjklc6ID8aq7bhguHgeIoC1EmSNTAuCgI6ZXrjt2ZSaXnYX0EUg== + dependencies: + "@types/node" "*" + "@types/node-fetch@^2.5.7": version "2.6.3" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.3.tgz#175d977f5e24d93ad0f57602693c435c57ad7e80" @@ -5629,6 +6523,31 @@ dependencies: "@types/node" "*" +"@types/pg-pool@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.3.tgz#3eb8df2933f617f219a53091ad4080c94ba1c959" + integrity sha512-fwK5WtG42Yb5RxAwxm3Cc2dJ39FlgcaNiXKvtTLAwtCn642X7dgel+w1+cLWwpSOFImR3YjsZtbkfjxbHtFAeg== + dependencies: + "@types/pg" "*" + +"@types/pg@*": + version "8.10.2" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.10.2.tgz#7814d1ca02c8071f4d0864c1b17c589b061dba43" + integrity sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + +"@types/pg@8.6.1": + version "8.6.1" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9" + integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + "@types/pg@^7.14.3": version "7.14.11" resolved "https://registry.yarnpkg.com/@types/pg/-/pg-7.14.11.tgz#daf5555504a1f7af4263df265d91f140fece52e3" @@ -5794,6 +6713,11 @@ "@types/mime" "*" "@types/node" "*" +"@types/shimmer@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f" + integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg== + "@types/solidity-parser-antlr@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@types/solidity-parser-antlr/-/solidity-parser-antlr-0.2.3.tgz#bb2d9c6511bf483afe4fc3e2714da8a924e59e3f" @@ -5860,6 +6784,13 @@ dependencies: "@types/tar-fs" "*" +"@types/tedious@^4.0.6": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/tedious/-/tedious-4.0.9.tgz#baa3892e45c63d7aac54d7bf5b01385d210ff19e" + integrity sha512-ipwFvfy9b2m0gjHsIX0D6NAAwGCKokzf5zJqUZHUGt+7uWVlBIy6n2eyMgiKQ8ChLFVxic/zwQUhjLYNzbHDRA== + dependencies: + "@types/node" "*" + "@types/through@*": version "0.0.30" resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" @@ -6089,6 +7020,11 @@ accepts@^1.3.5, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + acorn-jsx@^5.0.0, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -6109,7 +7045,7 @@ acorn@^8.4.1, acorn@^8.7.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== -acorn@^8.9.0: +acorn@^8.8.2, acorn@^8.9.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== @@ -6227,6 +7163,11 @@ ansi-align@^3.0.0: dependencies: string-width "^4.1.0" +ansi-color@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-color/-/ansi-color-0.2.1.tgz#3e75c037475217544ed763a8db5709fa9ae5bf9a" + integrity sha512-bF6xLaZBLpOQzgYUtYEhJx090nPSZk1BQ/q2oyBK9aMMcJHzx9uXGCjI2Y+LebsN4Jwoykr0V9whbPiogdyHoQ== + ansi-colors@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" @@ -7679,6 +8620,16 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" +bufrw@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/bufrw/-/bufrw-1.3.0.tgz#28d6cfdaf34300376836310f5c31d57eeb40c8fa" + integrity sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ== + dependencies: + ansi-color "^0.2.1" + error "^7.0.0" + hexer "^1.5.0" + xtend "^4.0.0" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -8258,6 +9209,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +cjs-module-lexer@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + cjson@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/cjson/-/cjson-0.3.3.tgz#a92d9c786e5bf9b930806329ee05d5d3261b4afa" @@ -10238,6 +11194,21 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error@7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" + integrity sha512-UtVv4l5MhijsYUxPJo4390gzfZvAnTHreNnDjnTZaKIiZ/SemXxAhBkYSKtWa5RtBXbLP8tMgn/n0RUa/H7jXw== + dependencies: + string-template "~0.2.1" + xtend "~4.0.0" + +error@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" + integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== + dependencies: + string-template "~0.2.1" + es-abstract@^1.17.0-next.1, es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.2: version "1.21.2" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" @@ -12506,7 +13477,7 @@ gcp-metadata@^4.2.0: gaxios "^4.0.0" json-bigint "^1.0.0" -gcp-metadata@^5.3.0: +gcp-metadata@^5.0.0, gcp-metadata@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-5.3.0.tgz#6f45eb473d0cb47d15001476b48b663744d25408" integrity sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w== @@ -12538,6 +13509,11 @@ generate-password@^1.5.1: resolved "https://registry.yarnpkg.com/generate-password/-/generate-password-1.7.0.tgz#00ba4eb1e71f89a72307b0d6604ee0d4e7f5770c" integrity sha512-WPCtlfy0jexf7W5IbwxGUgpIDvsZIohbI2DAq2Q6TSlKKis+G4GT9sxvPxrZUGL8kP6WUXMWNqYnxY6DDKAdFA== +generic-pool@*: + version "3.9.0" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4" + integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -13680,6 +14656,16 @@ heap-js@^2.2.0: resolved "https://registry.yarnpkg.com/heap-js/-/heap-js-2.3.0.tgz#8eed2cede31ec312aa696eef1d4df0565841f183" integrity sha512-E5303mzwQ+4j/n2J0rDvEPBN7GKjhis10oHiYOgjxsmxYgqG++hz9NyLLOXttzH8as/DyiBHYpUrJTZWYaMo8Q== +hexer@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/hexer/-/hexer-1.5.0.tgz#b86ce808598e8a9d1892c571f3cedd86fc9f0653" + integrity sha512-dyrPC8KzBzUJ19QTIo1gXNqIISRXQ0NwteW6OeQHRN4ZuZeHkdODfj0zHBdOlHbRY8GqbqK57C9oWSvQZizFsg== + dependencies: + ansi-color "^0.2.1" + minimist "^1.1.0" + process "^0.10.0" + xtend "^4.0.0" + hexoid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" @@ -14030,6 +15016,16 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" +import-in-the-middle@1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.4.2.tgz#2a266676e3495e72c04bbaa5ec14756ba168391b" + integrity sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw== + dependencies: + acorn "^8.8.2" + acorn-import-assertions "^1.9.0" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -14389,6 +15385,13 @@ is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.8.1: dependencies: has "^1.0.3" +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -15060,6 +16063,17 @@ jackspeak@^2.0.3: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jaeger-client@^3.15.0: + version "3.19.0" + resolved "https://registry.yarnpkg.com/jaeger-client/-/jaeger-client-3.19.0.tgz#9b5bd818ebd24e818616ee0f5cffe1722a53ae6e" + integrity sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw== + dependencies: + node-int64 "^0.4.0" + opentracing "^0.14.4" + thriftrw "^3.5.0" + uuid "^8.3.2" + xorshift "^1.1.1" + jake@^10.8.5: version "10.8.5" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -15464,6 +16478,17 @@ jmespath@0.16.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== +joi@^17.3.0: + version "17.9.2" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.2.tgz#8b2e4724188369f55451aebd1d0b1d9482470690" + integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + join-path@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/join-path/-/join-path-1.1.1.tgz#10535a126d24cbd65f7ffcdf15ef2e631076b505" @@ -16745,6 +17770,11 @@ loglevel@^1.6.1, loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg== +long@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f" + integrity sha512-ijUtjmO/n2A5PaosNG9ZGDsQ3vxJg7ZW8vsY8Kp0f2yIZWhSJvjmegV7t+9RPQKxKrvj8yKGehhS+po14hPLGQ== + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -16806,6 +17836,11 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== +lru-cache@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== + lru-cache@^5.0.0, lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -17816,6 +18851,11 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +module-details-from-path@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" + integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== + module-error@^1.0.1, module-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" @@ -18813,6 +19853,11 @@ oboe@2.1.5: dependencies: http-https "^1.0.0" +obuf@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + "old-identity-sdk@npm:@celo/identity@1.5.2": version "1.5.2" resolved "https://registry.yarnpkg.com/@celo/identity/-/identity-1.5.2.tgz#6401aeefbf7893374f6f940c9e0918d0a75d0411" @@ -18930,6 +19975,11 @@ opencollective-postinstall@^2.0.2: resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== +opentracing@^0.14.4: + version "0.14.7" + resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.7.tgz#25d472bd0296dc0b64d7b94cbc995219031428f5" + integrity sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q== + openzeppelin-solidity@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/openzeppelin-solidity/-/openzeppelin-solidity-2.5.1.tgz#1cdcce30c4c6a7b6625dab62ccd0440a813ab597" @@ -19592,6 +20642,11 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + pg-packet-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz#e45c3ae678b901a2873af1e17b92d787962ef914" @@ -19607,7 +20662,7 @@ pg-pool@^3.6.0: resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e" integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ== -pg-protocol@^1.2.0, pg-protocol@^1.6.0: +pg-protocol@*, pg-protocol@^1.2.0, pg-protocol@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== @@ -19623,6 +20678,19 @@ pg-types@^2.1.0, pg-types@^2.2.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" +pg-types@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.1.tgz#31857e89d00a6c66b06a14e907c3deec03889542" + integrity sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.0.1" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + pg@^7.18.0: version "7.18.2" resolved "https://registry.yarnpkg.com/pg/-/pg-7.18.2.tgz#4e219f05a00aff4db6aab1ba02f28ffa4513b0bb" @@ -19771,16 +20839,33 @@ postgres-array@~2.0.0: resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + postgres-bytea@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + postgres-date@~1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== +postgres-date@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.0.1.tgz#638b62e5c33764c292d37b08f5257ecb09231457" + integrity sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw== + postgres-interval@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" @@ -19788,6 +20873,16 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76" + integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g== + pouchdb-abstract-mapreduce@7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-7.3.1.tgz#96ff4a0f41cbe273f3f52fde003b719005a2093c" @@ -20119,6 +21214,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/process/-/process-0.10.1.tgz#842457cc51cfed72dc775afeeafb8c6034372725" + integrity sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA== + process@^0.11.1, process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -20319,7 +21419,7 @@ protobufjs@6.11.3, protobufjs@^6.10.0, protobufjs@^6.11.2, protobufjs@^6.11.3, p "@types/node" ">=13.7.0" long "^4.0.0" -protobufjs@7.2.4, protobufjs@^7.2.2: +protobufjs@7.2.4, protobufjs@^7.2.2, protobufjs@^7.2.3: version "7.2.4" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.4.tgz#3fc1ec0cdc89dd91aef9ba6037ba07408485c3ae" integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== @@ -21136,6 +22236,15 @@ require-from-string@^2.0.0, require-from-string@^2.0.1, require-from-string@^2.0 resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-in-the-middle@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.2.0.tgz#b539de8f00955444dc8aed95e17c69b0a4f10fcf" + integrity sha512-3TLx5TGyAY6AOqLBoXmHkNql0HIf2RGbuMgCDT2WO/uGVAPJs6h7Kl+bN6TIZGd9bWhWPwnDnTHGtW8Iu77sdw== + dependencies: + debug "^4.1.1" + module-details-from-path "^1.0.3" + resolve "^1.22.1" + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -21239,6 +22348,15 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.14.2, resolve@^1.20 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.22.1: + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -21638,7 +22756,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, 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.1.2, semver@^7.5.2: +semver@^7.1.2, semver@^7.5.1, semver@^7.5.2: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -21826,6 +22944,11 @@ shelljs@^0.8.4: interpret "^1.0.0" rechoir "^0.6.2" +shimmer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -22423,6 +23546,11 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-template@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" + integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== + "string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -23145,6 +24273,15 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +thriftrw@^3.5.0: + version "3.11.4" + resolved "https://registry.yarnpkg.com/thriftrw/-/thriftrw-3.11.4.tgz#84c990ee89e926631c0b475909ada44ee9249870" + integrity sha512-UcuBd3eanB3T10nXWRRMwfwoaC6VMk7qe3/5YIWP2Jtw+EbHqJ0p1/K3x8ixiR5dozKSSfcg1W+0e33G1Di3XA== + dependencies: + bufrw "^1.2.1" + error "7.0.2" + long "^2.4.0" + through2@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" @@ -26419,6 +27556,11 @@ xmlhttprequest@*, xmlhttprequest@1.8.0: resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== +xorshift@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/xorshift/-/xorshift-1.2.0.tgz#30a4cdd8e9f8d09d959ed2a88c42a09c660e8148" + integrity sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g== + xpath.js@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"