From 26304fad26ca785f1634999331790554fe4472e3 Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 11 Sep 2024 10:18:52 +0300 Subject: [PATCH 01/38] Updating workflows/actions to be compatible to and updating to the step-security maintained version. Signed-off-by: Vasil Boyadzhiev Resolving conflicts and rebasing. --- .github/workflows/acceptance-public.yml | 4 ++-- .github/workflows/acceptance-workflow.yml | 12 +++++++--- .github/workflows/acceptance.yml | 7 +++--- .github/workflows/manual-testing.yml | 29 ++++++++++++++--------- .github/workflows/release-acceptance.yml | 6 ++--- .github/workflows/test.yml | 7 +++--- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/.github/workflows/acceptance-public.yml b/.github/workflows/acceptance-public.yml index 7284d724e2..2e9e4ea9c0 100644 --- a/.github/workflows/acceptance-public.yml +++ b/.github/workflows/acceptance-public.yml @@ -174,8 +174,8 @@ jobs: merge-multiple: true - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: check_name: Test Results json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index feb61071b3..43b67f94a6 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -57,6 +57,12 @@ jobs: with: node-version: 20 + - name: Install make + run: sudo apt-get update; sudo apt-get install build-essential -y + + - name: Checkout repo + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - name: Install packages run: npm ci @@ -114,10 +120,10 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 if: ${{ !cancelled() }} with: - check_run_disabled: true + check_name: '' # Set to empty to disable check run comment_mode: off json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index db7a21f737..9fc002884f 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -139,9 +139,8 @@ jobs: merge-multiple: true - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: - check_name: Acceptance Tests - check_run_disabled: true + check_name: '' # Set to empty to disable check run json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/manual-testing.yml b/.github/workflows/manual-testing.yml index 7e2700a15e..3b2b342061 100644 --- a/.github/workflows/manual-testing.yml +++ b/.github/workflows/manual-testing.yml @@ -42,7 +42,7 @@ jobs: testfilter: api_batch3 networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + erc20: name: ERC20 uses: ./.github/workflows/acceptance-workflow.yml @@ -50,7 +50,7 @@ jobs: testfilter: erc20 networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + ratelimiter: name: Rate Limiter uses: ./.github/workflows/acceptance-workflow.yml @@ -67,7 +67,7 @@ jobs: testfilter: hbarlimiter networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + tokencreate: name: Token Create uses: ./.github/workflows/acceptance-workflow.yml @@ -75,7 +75,7 @@ jobs: testfilter: tokencreate networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + tokenmanagement: name: Token Management uses: ./.github/workflows/acceptance-workflow.yml @@ -83,7 +83,7 @@ jobs: testfilter: tokenmanagement networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + htsprecompilev1: name: Precompile uses: ./.github/workflows/acceptance-workflow.yml @@ -91,7 +91,7 @@ jobs: testfilter: htsprecompilev1 networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + precompilecalls: name: Precompile Calls uses: ./.github/workflows/acceptance-workflow.yml @@ -100,8 +100,13 @@ jobs: networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} +<<<<<<< HEAD websocket-batch-1: name: Websocket Batch 1 +======= + websocket: + name: Websocket +>>>>>>> 96afa059 (Updating workflows/actions to be compatible to and updating to the step-security maintained version.) uses: ./.github/workflows/acceptance-workflow.yml with: testfilter: ws_batch1 @@ -126,7 +131,7 @@ jobs: test_ws_server: true networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + cacheservice: name: Cache Service uses: ./.github/workflows/acceptance-workflow.yml @@ -135,6 +140,7 @@ jobs: networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} +<<<<<<< HEAD server-config: name: Server Config uses: ./.github/workflows/acceptance-workflow.yml @@ -143,6 +149,8 @@ jobs: networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} +======= +>>>>>>> 96afa059 (Updating workflows/actions to be compatible to and updating to the step-security maintained version.) publish_results: name: Publish Results if: ${{ !cancelled() }} @@ -174,9 +182,8 @@ jobs: merge-multiple: true - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: - check_name: Acceptance Tests - check_run_disabled: true + check_name: '' # Set to empty to disable check run json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/release-acceptance.yml b/.github/workflows/release-acceptance.yml index cab5f2df74..b42e708b00 100644 --- a/.github/workflows/release-acceptance.yml +++ b/.github/workflows/release-acceptance.yml @@ -89,10 +89,10 @@ jobs: path: test-*.xml - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 if: ${{ !cancelled() }} with: - check_run_disabled: true + check_name: '' # Set to empty to disable check run comment_mode: off json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 941aa920e2..6b1a3e656b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,9 +64,8 @@ jobs: - name: Publish Test Report if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && github.actor != 'dependabot[bot]' && github.actor != 'swirlds-automation' && !cancelled() && !failure() }} - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: - check_name: Tests - check_run_disabled: true + check_name: '' # Set to empty to disable check run json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' From e1a77d7fabceebc73263ccfb942ed12fc9cdbd17 Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 11 Sep 2024 10:42:12 +0300 Subject: [PATCH 02/38] Adding github Token to the action. Signed-off-by: Vasil Boyadzhiev --- .github/workflows/acceptance-public.yml | 1 + .github/workflows/acceptance-workflow.yml | 1 + .github/workflows/acceptance.yml | 1 + .github/workflows/manual-testing.yml | 1 + .github/workflows/release-acceptance.yml | 1 + .github/workflows/test.yml | 1 + 6 files changed, 6 insertions(+) diff --git a/.github/workflows/acceptance-public.yml b/.github/workflows/acceptance-public.yml index 2e9e4ea9c0..1bd68f5e9a 100644 --- a/.github/workflows/acceptance-public.yml +++ b/.github/workflows/acceptance-public.yml @@ -179,3 +179,4 @@ jobs: check_name: Test Results json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index 43b67f94a6..f2fb54bb9a 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -127,3 +127,4 @@ jobs: comment_mode: off json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 9fc002884f..4c2a0854ff 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -144,3 +144,4 @@ jobs: check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/manual-testing.yml b/.github/workflows/manual-testing.yml index 3b2b342061..f0ae3e482e 100644 --- a/.github/workflows/manual-testing.yml +++ b/.github/workflows/manual-testing.yml @@ -187,3 +187,4 @@ jobs: check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-acceptance.yml b/.github/workflows/release-acceptance.yml index b42e708b00..70354e15bf 100644 --- a/.github/workflows/release-acceptance.yml +++ b/.github/workflows/release-acceptance.yml @@ -96,3 +96,4 @@ jobs: comment_mode: off json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b1a3e656b..cc75355c9a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -69,3 +69,4 @@ jobs: check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} From e43690809c7db8468703ea7ea6269d7d833e3de9 Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 11 Sep 2024 11:15:39 +0300 Subject: [PATCH 03/38] Adding permissions for checks to: write Signed-off-by: Vasil Boyadzhiev Resolving conflicts and rebasing. --- .github/workflows/acceptance-workflow.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index f2fb54bb9a..28547a18ae 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -40,7 +40,11 @@ jobs: timeout-minutes: 50 permissions: contents: write +<<<<<<< HEAD actions: read +======= + checks: write +>>>>>>> bc640352 (Adding permissions for checks to: write) # issues: read steps: From 3d169d16f557eff01b23a77f401d4087941cb67f Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Fri, 13 Sep 2024 16:28:19 +0300 Subject: [PATCH 04/38] Applying suggestions from code review. Signed-off-by: Vasil Boyadzhiev --- .github/workflows/acceptance.yml | 1 + .github/workflows/manual-testing.yml | 1 + .github/workflows/test.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 4c2a0854ff..13e68595ed 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -141,6 +141,7 @@ jobs: - name: Publish Test Report uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: + # check_name: Acceptance Tests check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' diff --git a/.github/workflows/manual-testing.yml b/.github/workflows/manual-testing.yml index f0ae3e482e..b316e913c6 100644 --- a/.github/workflows/manual-testing.yml +++ b/.github/workflows/manual-testing.yml @@ -184,6 +184,7 @@ jobs: - name: Publish Test Report uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: + # check_name: Acceptance Tests check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc75355c9a..ea593998b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,6 +66,7 @@ jobs: if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && github.actor != 'dependabot[bot]' && github.actor != 'swirlds-automation' && !cancelled() && !failure() }} uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: + # check_name: Tests check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' From e37dcef671071515e81c81acd6509fab5727374e Mon Sep 17 00:00:00 2001 From: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:38:19 +0300 Subject: [PATCH 05/38] chore: Move unneeded dependencies to `devDependencies` (#3036) * chore: Move unneeded dependencies to `devDependencies` Signed-off-by: Victor Yanev * chore: Move unneeded dependencies to `devDependencies` Signed-off-by: Victor Yanev * chore: Move unneeded dependencies to `devDependencies` Signed-off-by: Victor Yanev * Merge branch 'main' into 3035-Move-unneeded-dependencies-to-devDependencies Signed-off-by: Victor Yanev * chore: move `@hashgraph/hedera-local` to `devDependencies` Signed-off-by: Victor Yanev * chore: move `pnpm` Signed-off-by: Victor Yanev --------- Signed-off-by: Victor Yanev --- package-lock.json | 2749 ++++++++++++------------------- package.json | 27 +- packages/relay/package.json | 1 - packages/server/package.json | 6 +- packages/ws-server/package.json | 9 +- 5 files changed, 1089 insertions(+), 1703 deletions(-) diff --git a/package-lock.json b/package-lock.json index dde6cac82e..e55ce72b9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,38 +9,37 @@ "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/trie": "^6.2.1", "@ethereumjs/util": "^9.1.0", - "@hashgraph/hedera-local": "^2.31.0", - "@open-rpc/schema-utils-js": "^1.16.1", - "@types/find-config": "^1.0.4", - "@types/sinon": "^10.0.20", - "ajv": "^8.16.0", - "ajv-formats": "^3.0.1", - "chai-exclude": "^2.1.1", - "eslint-config-standard-with-typescript": "^43.0.1", - "eslint-plugin-n": "^15.7.0", "keyv-file": "^0.3.3", "koa-cors": "^0.0.16", "koa-websocket": "^7.0.0", "lerna": "^8.1.8", "pino": "^7.11.0", "pino-pretty": "^7.6.1", + "pnpm": "^8.7.1", "prom-client": "^14.0.1", - "redis": "^4.7.0", - "ts-node": "^10.9.2", - "typescript": "^4.6.3" + "redis": "^4.7.0" }, "devDependencies": { + "@hashgraph/hedera-local": "^2.31.0", + "@open-rpc/schema-utils-js": "^1.16.1", "@types/chai-as-promised": "^7.1.5", "@types/co-body": "6.1.0", + "@types/find-config": "^1.0.4", "@types/koa-cors": "^0.0.6", + "@types/sinon": "^10.0.20", "@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/parser": "^6.5.0", + "ajv": "^8.16.0", + "ajv-formats": "^3.0.1", "axios-mock-adapter": "^1.20.0", "chai-as-promised": "^7.1.1", + "chai-exclude": "^2.1.1", "eslint": "^8.48.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.3.0", + "eslint-config-standard-with-typescript": "^43.0.1", "eslint-plugin-import": "^2.28.1", + "eslint-plugin-n": "^15.7.0", "ethereum-waffle": "^4.0.7", "husky": "^8.0.3", "ioredis": "^5.3.2", @@ -50,7 +49,9 @@ "nodemon": "^2.0.15", "nyc": "^15.1.0", "prettier": "^3.0.3", - "replace": "^1.2.2" + "replace": "^1.2.2", + "ts-node": "^10.9.2", + "typescript": "^4.6.3" }, "workspaces": { "packages": [ @@ -62,6 +63,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -451,12 +453,16 @@ "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.90" @@ -466,6 +472,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -477,6 +484,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -860,6 +868,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -874,6 +883,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -882,6 +892,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -904,6 +915,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -920,12 +932,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/@eslint/js": { "version": "8.48.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2289,9 +2303,10 @@ "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==" }, "node_modules/@hashgraph/cryptography": { - "version": "1.4.8-beta.5", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.5.tgz", - "integrity": "sha512-soq2vGLRkdl2Evr+gIvIjCXJjqA1hOAjysBGG+dhP6tKx2PEgEjb3hON/sMbxm3Q4qQdkML/vEthdAV707+flw==", + "version": "1.4.8-beta.8", + "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", + "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", + "license": "Apache-2.0", "dependencies": { "asn1js": "^3.0.5", "bignumber.js": "^9.1.1", @@ -2329,6 +2344,8 @@ "version": "2.31.0", "resolved": "https://registry.npmjs.org/@hashgraph/hedera-local/-/hedera-local-2.31.0.tgz", "integrity": "sha512-TsDmz3AJrokGk9s6o88GUoXIBqz3YKvUlIZH4Bs+amtUN30Fzv6kaoD9kB49rYk8VsGTAwkY+9ABCsumoAiV+g==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "@hashgraph/sdk": "^2.49.2", "blessed": "^0.1.81", @@ -2353,6 +2370,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "license": "MIT", "dependencies": { "@noble/hashes": "1.3.2" }, @@ -2363,17 +2382,23 @@ "node_modules/@hashgraph/hedera-local/node_modules/@types/node": { "version": "18.15.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "dev": true, + "license": "MIT" }, "node_modules/@hashgraph/hedera-local/node_modules/aes-js": { "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "dev": true, + "license": "MIT" }, "node_modules/@hashgraph/hedera-local/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -2382,6 +2407,8 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -2390,6 +2417,7 @@ "version": "6.13.2", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", "integrity": "sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==", + "dev": true, "funding": [ { "type": "individual", @@ -2400,6 +2428,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], + "license": "MIT", "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -2414,9 +2443,11 @@ } }, "node_modules/@hashgraph/hedera-local/node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -2432,6 +2463,8 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^4.0.1", @@ -2451,9 +2484,11 @@ } }, "node_modules/@hashgraph/hedera-local/node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -2462,15 +2497,14 @@ }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/@hashgraph/hedera-local/node_modules/lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "dev": true, + "license": "ISC", "engines": { "node": "20 || >=22" } @@ -2479,6 +2513,8 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2493,6 +2529,8 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -2501,6 +2539,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" @@ -2516,6 +2556,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" @@ -2534,6 +2576,8 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -2545,6 +2589,8 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz", "integrity": "sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==", + "dev": true, + "license": "MIT", "dependencies": { "ts-node": "7.0.1" }, @@ -2565,6 +2611,8 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "license": "MIT", "dependencies": { "arrify": "^1.0.0", "buffer-from": "^1.1.0", @@ -2585,12 +2633,16 @@ "node_modules/@hashgraph/hedera-local/node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD" }, "node_modules/@hashgraph/hedera-local/node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -2611,6 +2663,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2628,9 +2682,10 @@ "link": true }, "node_modules/@hashgraph/proto": { - "version": "2.15.0-beta.3", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.3.tgz", - "integrity": "sha512-/95cydqBQRaO1gagBOenNpcfJIcnRx+vzefhuzSFNp4pfl0AM3oXau39hM2raKLhFHoKZysSjkXkMp24AaCE5w==", + "version": "2.15.0-beta.4", + "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", + "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", + "license": "Apache-2.0", "dependencies": { "long": "^4.0.0", "protobufjs": "^7.2.5" @@ -2640,17 +2695,18 @@ } }, "node_modules/@hashgraph/sdk": { - "version": "2.49.2", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.49.2.tgz", - "integrity": "sha512-HqESeH6gF/QEm69qmEyPZ40i9w2jBXsyXFqT/kRsrb7yEyCdVrG1Wnt88HxOSP9XyOsm3hj4OBTEiy2sI+kl+A==", + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", + "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", + "license": "Apache-2.0", "dependencies": { "@ethersproject/abi": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/rlp": "^5.7.0", "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.5", - "@hashgraph/proto": "2.15.0-beta.3", + "@hashgraph/cryptography": "1.4.8-beta.8", + "@hashgraph/proto": "2.15.0-beta.4", "axios": "^1.6.4", "bignumber.js": "^9.1.1", "bn.js": "^5.1.1", @@ -2679,6 +2735,7 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", "engines": { "node": "*" } @@ -2687,6 +2744,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -2695,6 +2753,7 @@ "version": "8.21.0", "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", @@ -2716,6 +2775,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "license": "MIT", "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" @@ -2725,6 +2785,7 @@ "version": "10.3.1", "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", + "license": "MIT", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", @@ -2748,17 +2809,20 @@ "node_modules/@hashgraph/sdk/node_modules/pino-std-serializers": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "license": "MIT" }, "node_modules/@hashgraph/sdk/node_modules/process-warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" }, "node_modules/@hashgraph/sdk/node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -2774,6 +2838,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -2782,6 +2847,7 @@ "version": "3.8.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" } @@ -2790,6 +2856,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", "engines": { "node": ">= 10.x" } @@ -2798,6 +2865,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "license": "MIT", "dependencies": { "real-require": "^0.2.0" } @@ -2806,6 +2874,7 @@ "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -2819,6 +2888,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "engines": { "node": ">=12.22" }, @@ -2830,7 +2900,8 @@ "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", @@ -3077,6 +3148,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -3093,7 +3165,8 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.19", @@ -3108,12 +3181,14 @@ "node_modules/@json-schema-spec/json-pointer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@json-schema-spec/json-pointer/-/json-pointer-0.1.2.tgz", - "integrity": "sha512-BYY7IavBjwsWWSmVcMz2A9mKiDD9RvacnsItgmy1xV8cmgbtxFfKmKMtkVpD7pYtkx4mIW4800yZBXueVFIWPw==" + "integrity": "sha512-BYY7IavBjwsWWSmVcMz2A9mKiDD9RvacnsItgmy1xV8cmgbtxFfKmKMtkVpD7pYtkx4mIW4800yZBXueVFIWPw==", + "dev": true }, "node_modules/@json-schema-tools/dereferencer": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@json-schema-tools/dereferencer/-/dereferencer-1.5.4.tgz", "integrity": "sha512-4cmEdRPIG7WrcSWGRV6HBDCLXEOXGkaOZnopqBxoG24mKYuCHWg4M6N9nioTQyNfKqlPkOPvT4lStQqkPnhLgA==", + "dev": true, "dependencies": { "@json-schema-tools/reference-resolver": "^1.2.4", "@json-schema-tools/traverse": "^1.7.8", @@ -3123,12 +3198,14 @@ "node_modules/@json-schema-tools/meta-schema": { "version": "1.6.19", "resolved": "https://registry.npmjs.org/@json-schema-tools/meta-schema/-/meta-schema-1.6.19.tgz", - "integrity": "sha512-55zuWFW7tr4tf/G5AYmybcPdGOkVAreQbt2JdnogX4I2r/zkxZiimYPJESDf5je9BI2oRveak2p296HzDppeaA==" + "integrity": "sha512-55zuWFW7tr4tf/G5AYmybcPdGOkVAreQbt2JdnogX4I2r/zkxZiimYPJESDf5je9BI2oRveak2p296HzDppeaA==", + "dev": true }, "node_modules/@json-schema-tools/reference-resolver": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@json-schema-tools/reference-resolver/-/reference-resolver-1.2.4.tgz", "integrity": "sha512-Oag20zDuapO6nBQp00k8Rd5sDTb8Gfz9uH43Tf7dHKNx7nHDK/WdeTe7OxkOmLQCL6aS+mCJx1Zv+fZBCD+tzQ==", + "dev": true, "dependencies": { "@json-schema-spec/json-pointer": "^0.1.2", "isomorphic-fetch": "^3.0.0" @@ -3137,7 +3214,8 @@ "node_modules/@json-schema-tools/traverse": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@json-schema-tools/traverse/-/traverse-1.10.1.tgz", - "integrity": "sha512-vYY5EIxCPzEXEWL/vTjdHy4g92tv1ApUQCjPJsj9gEoXLNNVwJlwwgRZisuvgFBZ3zeLzQygrbehERSpYdmFZA==" + "integrity": "sha512-vYY5EIxCPzEXEWL/vTjdHy4g92tv1ApUQCjPJsj9gEoXLNNVwJlwwgRZisuvgFBZ3zeLzQygrbehERSpYdmFZA==", + "dev": true }, "node_modules/@keyvhq/core": { "version": "1.6.26", @@ -4434,12 +4512,14 @@ "node_modules/@open-rpc/meta-schema": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/@open-rpc/meta-schema/-/meta-schema-1.14.2.tgz", - "integrity": "sha512-vD4Nbkrb7wYFRcSQf+j228LwOy1C6/KKpy5NADlpMElGrAWPRxhTa2yTi6xG+x88OHzg2+cydQ0GAD6o40KUcg==" + "integrity": "sha512-vD4Nbkrb7wYFRcSQf+j228LwOy1C6/KKpy5NADlpMElGrAWPRxhTa2yTi6xG+x88OHzg2+cydQ0GAD6o40KUcg==", + "dev": true }, "node_modules/@open-rpc/schema-utils-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@open-rpc/schema-utils-js/-/schema-utils-js-1.16.1.tgz", "integrity": "sha512-8D4OgBnHDAv7JeaYZ5v7SL4yR0YLLO4WLTWtdR8vmqSqvX3SvPzSsGYv06zqm9z1Lhm563MAcuearrc8g9eJ4w==", + "dev": true, "dependencies": { "@json-schema-tools/dereferencer": "1.5.4", "@json-schema-tools/meta-schema": "1.6.19", @@ -4457,6 +4537,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -4473,6 +4554,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/@pkgjs/parseargs": { @@ -4843,22 +4925,26 @@ "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", @@ -5049,7 +5135,8 @@ "node_modules/@types/find-config": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/find-config/-/find-config-1.0.4.tgz", - "integrity": "sha512-BCXaKgzHK7KnfCQBRQBWGTA+QajOE9uFolXPt+9EktiiMS56D8oXF2ZCh9eCxuEyfqDmX/mYIcmWg9j9f659eg==" + "integrity": "sha512-BCXaKgzHK7KnfCQBRQBWGTA+QajOE9uFolXPt+9EktiiMS56D8oXF2ZCh9eCxuEyfqDmX/mYIcmWg9j9f659eg==", + "dev": true }, "node_modules/@types/heapdump": { "version": "0.3.4", @@ -5072,12 +5159,14 @@ "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true }, "node_modules/@types/keygrip": { "version": "1.0.6", @@ -5277,7 +5366,8 @@ "node_modules/@types/semver": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==" + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true }, "node_modules/@types/send": { "version": "0.17.4", @@ -5304,6 +5394,7 @@ "version": "10.0.20", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", "integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==", + "dev": true, "dependencies": { "@types/sinonjs__fake-timers": "*" } @@ -5311,7 +5402,8 @@ "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", - "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==" + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true }, "node_modules/@types/uuid": { "version": "10.0.0", @@ -5333,6 +5425,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", + "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.5.0", @@ -5367,6 +5460,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, "engines": { "node": ">=16.13.0" }, @@ -5378,6 +5472,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "6.5.0", "@typescript-eslint/types": "6.5.0", @@ -5405,6 +5500,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "dev": true, "dependencies": { "@typescript-eslint/types": "6.5.0", "@typescript-eslint/visitor-keys": "6.5.0" @@ -5421,6 +5517,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", + "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "6.5.0", "@typescript-eslint/utils": "6.5.0", @@ -5447,6 +5544,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, "engines": { "node": ">=16.13.0" }, @@ -5458,6 +5556,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" }, @@ -5470,6 +5569,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "dev": true, "dependencies": { "@typescript-eslint/types": "6.5.0", "@typescript-eslint/visitor-keys": "6.5.0", @@ -5496,6 +5596,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, "engines": { "node": ">=16.13.0" }, @@ -5507,6 +5608,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", + "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", @@ -5531,6 +5633,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "dev": true, "dependencies": { "@typescript-eslint/types": "6.5.0", "eslint-visitor-keys": "^3.4.1" @@ -5547,6 +5650,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true, "peer": true }, "node_modules/@yarnpkg/lockfile": { @@ -5600,12 +5704,14 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" }, @@ -5669,6 +5775,7 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5680,6 +5787,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5688,6 +5796,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -5701,6 +5810,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -5737,6 +5848,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5753,6 +5865,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -5770,6 +5883,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5831,6 +5945,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/ansi-term/-/ansi-term-0.0.2.tgz", "integrity": "sha512-jLnGE+n8uAjksTJxiWZf/kcUmXq+cRWSl550B9NmQ8YiqaTM+lILcSe5dHdp8QkJPhaOghDjnMKwyYSMjosgAA==", + "dev": true, + "license": "ISC", "dependencies": { "x256": ">=0.0.1" } @@ -5838,12 +5954,15 @@ "node_modules/ansicolors": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true, + "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5878,7 +5997,8 @@ "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true }, "node_modules/argparse": { "version": "2.0.1", @@ -5920,6 +6040,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -5945,6 +6066,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -5971,6 +6093,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -5989,6 +6112,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -6006,6 +6130,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -6023,6 +6148,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -6050,6 +6176,7 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, "dependencies": { "safer-buffer": "~2.1.0" } @@ -6058,6 +6185,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "license": "BSD-3-Clause", "dependencies": { "pvtsutils": "^1.3.2", "pvutils": "^1.1.3", @@ -6080,6 +6208,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, "engines": { "node": "*" } @@ -6111,6 +6240,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, "engines": { "node": ">= 4.0.0" } @@ -6127,6 +6257,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -6218,6 +6349,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, "dependencies": { "tweetnacl": "^0.14.3" } @@ -6225,7 +6357,8 @@ "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true }, "node_modules/bech32": { "version": "1.1.4", @@ -6274,6 +6407,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, "engines": { "node": ">=8" } @@ -6344,6 +6478,8 @@ "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", + "dev": true, + "license": "MIT", "bin": { "blessed": "bin/tput.js" }, @@ -6355,6 +6491,8 @@ "version": "0.1.22", "resolved": "https://registry.npmjs.org/blessed-terminal/-/blessed-terminal-0.1.22.tgz", "integrity": "sha512-R8Ej+yzsaey7gW5DSmPhIC28gNLYQad6lMODuEw0X4KzudWWxZQ632Z+BRJk2EHN5dsFWRWhvLbD+M5Vs5J+AA==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-term": ">=0.0.2", "chalk": "^2.4.2", @@ -6406,7 +6544,9 @@ "node_modules/bresenham": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/bresenham/-/bresenham-0.0.3.tgz", - "integrity": "sha512-wbMxoJJM1p3+6G7xEFXYNCJ30h2qkwmVxebkbwIl4OcnWtno5R3UT9VuYLfStlVNAQCmRjkGwjPFdfaPd4iNXw==" + "integrity": "sha512-wbMxoJJM1p3+6G7xEFXYNCJ30h2qkwmVxebkbwIl4OcnWtno5R3UT9VuYLfStlVNAQCmRjkGwjPFdfaPd4iNXw==", + "dev": true, + "license": "MIT" }, "node_modules/brorand": { "version": "1.1.0", @@ -6416,7 +6556,8 @@ "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true }, "node_modules/browserify-aes": { "version": "1.2.0", @@ -6540,6 +6681,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true, "engines": { "node": ">=0.2.0" } @@ -6548,6 +6690,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "dev": true, "optional": true, "engines": { "node": ">=10.0.0" @@ -6557,6 +6700,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, "dependencies": { "semver": "^7.0.0" } @@ -6786,6 +6930,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "license": "MIT", "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" @@ -6804,6 +6950,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", @@ -6833,6 +6980,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/chai-exclude/-/chai-exclude-2.1.1.tgz", "integrity": "sha512-IHgNmgAFOkyRPnmOtZio9UsOHQ6RnzVr2LOs+5V9urYYqjhV/ERLQapC0Eq2DmID5eDWyngAcBxNUm0ZK0QbrQ==", + "dev": true, "license": "MIT", "dependencies": { "fclone": "^1.0.11" @@ -6871,12 +7019,15 @@ "node_modules/charm": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "dev": true, + "license": "MIT/X11" }, "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, "engines": { "node": "*" } @@ -6885,6 +7036,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "funding": [ { "type": "individual", @@ -6970,9 +7122,11 @@ } }, "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.2.0" }, @@ -7581,14 +7735,15 @@ } }, "node_modules/cpu-features": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz", - "integrity": "sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ==", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { "buildcheck": "~0.0.6", - "nan": "^2.17.0" + "nan": "^2.19.0" }, "engines": { "node": ">=10.0.0" @@ -7636,7 +7791,8 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -7680,6 +7836,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", + "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -7782,6 +7940,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, "dependencies": { "type-detect": "^4.0.0" }, @@ -7806,7 +7965,8 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/default-require-extensions": { "version": "3.0.1", @@ -7899,6 +8059,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -7965,12 +8126,15 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true }, "node_modules/detect-port": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "dev": true, + "license": "MIT", "dependencies": { "address": "^1.0.1", "debug": "4" @@ -7987,6 +8151,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, "engines": { "node": ">=0.3.1" } @@ -8014,6 +8179,8 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", "integrity": "sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "debug": "^4.1.1", "readable-stream": "^3.5.0", @@ -8028,6 +8195,8 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz", "integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "@balena/dockerignore": "^1.0.2", "docker-modem": "^5.0.3", @@ -8041,6 +8210,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8087,12 +8257,16 @@ "node_modules/drawille-blessed-contrib": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/drawille-blessed-contrib/-/drawille-blessed-contrib-1.0.0.tgz", - "integrity": "sha512-WnHMgf5en/hVOsFhxLI8ZX0qTJmerOsVjIMQmn4cR1eI8nLGu+L7w5ENbul+lZ6w827A3JakCuernES5xbHLzQ==" + "integrity": "sha512-WnHMgf5en/hVOsFhxLI8ZX0qTJmerOsVjIMQmn4cR1eI8nLGu+L7w5ENbul+lZ6w827A3JakCuernES5xbHLzQ==", + "dev": true, + "license": "MIT" }, "node_modules/drawille-canvas-blessed-contrib": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/drawille-canvas-blessed-contrib/-/drawille-canvas-blessed-contrib-0.1.3.tgz", "integrity": "sha512-bdDvVJOxlrEoPLifGDPaxIzFh3cD7QH05ePoQ4fwnqfi08ZSxzEhOUpI5Z0/SQMlWgcCQOEtuw0zrwezacXglw==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-term": ">=0.0.2", "bresenham": "0.0.3", @@ -8316,6 +8490,7 @@ "version": "1.22.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.1", @@ -8368,6 +8543,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3", "has": "^1.0.3", @@ -8381,6 +8557,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, "dependencies": { "has": "^1.0.3" } @@ -8389,6 +8566,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -8432,6 +8610,7 @@ "version": "8.48.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8517,6 +8696,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-43.0.1.tgz", "integrity": "sha512-WfZ986+qzIzX6dcr4yGUyVb/l9N3Z8wPXCc5z/70fljs3UbWhhV+WxrfgsqMToRzuuyX9MqZ974pq2UPhDTOcA==", "deprecated": "Please use eslint-config-love, instead.", + "dev": true, "dependencies": { "@typescript-eslint/parser": "^6.4.0", "eslint-config-standard": "17.1.0" @@ -8534,6 +8714,7 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, "funding": [ { "type": "github", @@ -8562,6 +8743,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -8572,6 +8754,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -8580,6 +8763,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, "dependencies": { "debug": "^3.2.7" }, @@ -8596,6 +8780,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -8604,6 +8789,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, "dependencies": { "eslint-utils": "^2.0.0", "regexpp": "^3.0.0" @@ -8622,6 +8808,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, "dependencies": { "eslint-visitor-keys": "^1.1.0" }, @@ -8636,6 +8823,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, "engines": { "node": ">=4" } @@ -8644,6 +8832,7 @@ "version": "2.28.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.findlastindex": "^1.2.2", @@ -8674,6 +8863,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -8682,6 +8872,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8693,6 +8884,7 @@ "version": "15.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, "dependencies": { "builtins": "^5.0.1", "eslint-plugin-es": "^4.1.0", @@ -8717,6 +8909,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -8729,6 +8922,7 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -8744,6 +8938,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "dependencies": { "eslint-visitor-keys": "^2.0.0" }, @@ -8761,6 +8956,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, "engines": { "node": ">=10" } @@ -8769,6 +8965,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -8780,6 +8977,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -8796,6 +8994,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8810,6 +9009,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8825,6 +9025,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -8835,12 +9036,14 @@ "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -8852,6 +9055,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -8863,6 +9067,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -8871,12 +9076,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -8888,6 +9095,7 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -8916,6 +9124,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -8927,6 +9136,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -8938,6 +9148,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { "node": ">=4.0" } @@ -8946,6 +9157,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9299,6 +9511,7 @@ "version": "0.9.8", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-0.9.8.tgz", "integrity": "sha512-o5h0Mp1bkoR6B0i7pTCAzRy+VzdsRWH997KQD4Psb0EOPoKEIiaRx/EsOdUl7p1Ktjw7aIWvweI/OY1R9XrlUg==", + "dev": true, "dependencies": { "optimist": "0.2" }, @@ -9310,6 +9523,8 @@ "version": "0.2.8", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.2.8.tgz", "integrity": "sha512-Wy7E3cQDpqsTIFyW7m22hSevyTLxw850ahYv7FWsw4G6MIKVTZ8NSA95KBrQ95a4SMsMr1UGUUnwEFKhVaSzIg==", + "dev": true, + "license": "MIT/X11", "dependencies": { "wordwrap": ">=0.0.1 <0.1.0" }, @@ -9321,6 +9536,8 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -9329,6 +9546,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -9342,6 +9560,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -9467,12 +9686,14 @@ "node_modules/fast-copy": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "node_modules/fast-glob": { "version": "3.3.1", @@ -9493,12 +9714,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fast-redact": { "version": "3.3.0", @@ -9517,6 +9740,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -9530,7 +9754,8 @@ "node_modules/fclone": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==" + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", + "dev": true }, "node_modules/fd-slicer": { "version": "1.1.0", @@ -9559,6 +9784,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -9654,6 +9880,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -9677,6 +9904,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -9688,7 +9916,8 @@ "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true }, "node_modules/follow-redirects": { "version": "1.15.6", @@ -9713,6 +9942,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -9817,6 +10047,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -9855,6 +10086,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -9873,6 +10105,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -9896,6 +10129,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10475,6 +10709,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, "engines": { "node": "*" } @@ -10584,6 +10819,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -10683,7 +10919,9 @@ "node_modules/gl-matrix": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz", - "integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==" + "integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==", + "dev": true, + "license": "MIT" }, "node_modules/glob": { "version": "7.2.0", @@ -10719,6 +10957,7 @@ "version": "13.21.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -10733,6 +10972,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -10766,6 +11006,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -10781,12 +11022,14 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, "peer": true, "engines": { "node": ">=4.x" @@ -10882,6 +11125,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10898,6 +11142,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -10998,6 +11243,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, "bin": { "he": "bin/he" } @@ -11005,12 +11251,15 @@ "node_modules/help-me": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" }, "node_modules/here": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/here/-/here-0.0.2.tgz", - "integrity": "sha512-U7VYImCTcPoY27TSmzoiFsmWLEqQFaYNdpsPb9K0dXJhE6kufUqycaz51oR09CW85dDU9iWyy7At8M+p7hb3NQ==" + "integrity": "sha512-U7VYImCTcPoY27TSmzoiFsmWLEqQFaYNdpsPb9K0dXJhE6kufUqycaz51oR09CW85dDU9iWyy7At8M+p7hb3NQ==", + "dev": true, + "license": "MIT" }, "node_modules/hmac-drbg": { "version": "1.0.1", @@ -11422,6 +11671,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -11435,6 +11685,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, "engines": { "node": ">= 0.10" } @@ -11499,6 +11750,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -11517,6 +11769,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -11528,6 +11781,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -11539,6 +11793,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -11577,6 +11832,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -11610,6 +11866,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -11702,6 +11959,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -11721,6 +11979,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -11743,6 +12002,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -11767,6 +12027,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -11793,6 +12054,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -11820,6 +12082,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -11834,6 +12097,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -11859,6 +12123,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -11889,7 +12154,8 @@ "node_modules/is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true }, "node_modules/is-utf8": { "version": "0.2.1", @@ -11902,6 +12168,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -11932,7 +12199,8 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -11951,6 +12219,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dev": true, "dependencies": { "node-fetch": "^2.6.1", "whatwg-fetch": "^3.4.1" @@ -12388,12 +12657,14 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/json-stringify-nice": { "version": "1.1.4", @@ -13205,6 +13476,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -13587,6 +13859,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -13661,7 +13934,8 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -13953,6 +14227,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, "dependencies": { "get-func-name": "^2.0.0" } @@ -13989,7 +14264,8 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "node_modules/make-fetch-happen": { "version": "13.0.1", @@ -14025,6 +14301,8 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/map-canvas/-/map-canvas-0.1.5.tgz", "integrity": "sha512-f7M3sOuL9+up0NCOZbb1rQpWDLZwR/ftCiNbyscjl9LUUEwrRaoumH4sz6swgs58lF21DQ0hsYOCw5C6Zz7hbg==", + "dev": true, + "license": "ISC", "dependencies": { "drawille-canvas-blessed-contrib": ">=0.0.1", "xml2js": "^0.4.5" @@ -14045,6 +14323,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -14056,6 +14336,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.2.0.tgz", "integrity": "sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^6.2.0", "cardinal": "^2.1.1", @@ -14072,12 +14354,11 @@ } }, "node_modules/marked-terminal/node_modules/ansi-escapes": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", - "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", - "dependencies": { - "type-fest": "^3.0.0" - }, + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -14089,6 +14370,8 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -14096,17 +14379,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/marked-terminal/node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mcl-wasm": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", @@ -14219,6 +14491,8 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/memory-streams/-/memory-streams-0.1.3.tgz", "integrity": "sha512-qVQ/CjkMyMInPaaRMrwWNDvf6boRZXaT/DbQeMYcCWuXPEBf1v8qChOc9OlEVQp2uOvRXa1Qu30fLmKhY6NipA==", + "dev": true, + "license": "MIT", "dependencies": { "readable-stream": "~1.0.2" } @@ -14226,12 +14500,16 @@ "node_modules/memory-streams/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" }, "node_modules/memory-streams/node_modules/readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -14242,12 +14520,15 @@ "node_modules/memory-streams/node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, "engines": { "node": ">= 0.10.0" } @@ -14759,6 +15040,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "dependencies": { "minimist": "^1.2.6" }, @@ -14769,12 +15051,15 @@ "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" }, "node_modules/mocha": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, "peer": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", @@ -14865,6 +15150,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, "peer": true, "engines": { "node": ">=6" @@ -14874,6 +15160,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, "peer": true, "dependencies": { "ms": "2.1.2" @@ -14891,12 +15178,14 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, "peer": true }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "peer": true, "engines": { "node": ">=10" @@ -14909,6 +15198,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "peer": true, "engines": { "node": ">=8" @@ -14918,6 +15208,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, "peer": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -14930,12 +15221,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "peer": true }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -14951,6 +15244,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "peer": true, "dependencies": { "cliui": "^7.0.2", @@ -15018,15 +15312,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "dev": true, + "license": "MIT", "optional": true }, "node_modules/nanoid": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true, "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -15038,7 +15335,8 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node_modules/negotiator": { "version": "0.6.3", @@ -15093,6 +15391,8 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.21" } @@ -15120,6 +15420,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } @@ -15369,6 +15670,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -15975,6 +16277,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -15983,6 +16286,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -16014,6 +16318,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -16030,6 +16335,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16041,6 +16347,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -16116,6 +16423,8 @@ "version": "0.3.7", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", "integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==", + "dev": true, + "license": "MIT/X11", "dependencies": { "wordwrap": "~0.0.2" } @@ -16124,6 +16433,8 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -16132,6 +16443,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -16271,6 +16583,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -16285,6 +16598,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -16598,6 +16912,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, "engines": { "node": "*" } @@ -16651,6 +16966,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/picture-tuber/-/picture-tuber-1.0.2.tgz", "integrity": "sha512-49/xq+wzbwDeI32aPvwQJldM8pr7dKDRuR76IjztrkmiCkAQDaWFJzkmfVqCHmt/iFoPFhHmI9L0oKhthrTOQw==", + "dev": true, + "license": "MIT", "dependencies": { "buffers": "~0.1.1", "charm": "~0.1.0", @@ -16848,7 +17165,8 @@ "node_modules/png-js": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/png-js/-/png-js-0.1.1.tgz", - "integrity": "sha512-NTtk2SyfjBm+xYl2/VZJBhFnTQ4kU5qWC7VC4/iGbrgiU4FuB4xC+74erxADYJIqZICOR1HCvRA7EBHkpjTg9g==" + "integrity": "sha512-NTtk2SyfjBm+xYl2/VZJBhFnTQ4kU5qWC7VC4/iGbrgiU4FuB4xC+74erxADYJIqZICOR1HCvRA7EBHkpjTg9g==", + "dev": true }, "node_modules/pnpm": { "version": "8.15.9", @@ -16881,6 +17199,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "engines": { "node": ">= 0.8.0" } @@ -16936,6 +17255,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -17094,6 +17414,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", + "dev": true, "engines": { "node": ">=6" } @@ -17102,6 +17423,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "license": "MIT", "dependencies": { "tslib": "^2.6.1" } @@ -17110,6 +17432,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -17164,6 +17487,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -17416,6 +17740,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -17435,6 +17760,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, "dependencies": { "resolve": "^1.1.6" }, @@ -17458,6 +17784,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "license": "MIT", "dependencies": { "esprima": "~4.0.0" } @@ -17677,6 +18005,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -17693,6 +18022,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, "engines": { "node": ">=8" }, @@ -18100,6 +18430,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -18136,6 +18467,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -18159,9 +18491,11 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC" }, "node_modules/scrypt-js": { "version": "3.0.1", @@ -18232,6 +18566,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, "peer": true, "dependencies": { "randombytes": "^2.1.0" @@ -18300,6 +18635,7 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -18545,6 +18881,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -18553,12 +18890,14 @@ "node_modules/spark-md5": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "license": "(WTFPL OR MIT)" }, "node_modules/sparkline": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/sparkline/-/sparkline-0.1.2.tgz", "integrity": "sha512-t//aVOiWt9fi/e22ea1vXVWBDX+gp18y+Ch9sKqmHl828bRfvP2VtfTJVEcgWFBQHd0yDPNQRiHdqzCvbcYSDA==", + "dev": true, "dependencies": { "here": "0.0.2", "nopt": "~2.1.2" @@ -18574,6 +18913,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", "integrity": "sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==", + "dev": true, + "license": "MIT", "dependencies": { "abbrev": "1" }, @@ -18640,7 +18981,9 @@ "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "dev": true, + "license": "ISC" }, "node_modules/split2": { "version": "3.2.2", @@ -18656,9 +18999,10 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/ssh2": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", - "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", + "dev": true, "hasInstallScript": true, "dependencies": { "asn1": "^0.2.6", @@ -18668,8 +19012,8 @@ "node": ">=10.16.0" }, "optionalDependencies": { - "cpu-features": "~0.0.9", - "nan": "^2.18.0" + "cpu-features": "~0.0.10", + "nan": "^2.20.0" } }, "node_modules/sshpk": { @@ -18795,6 +19139,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -18811,6 +19156,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -18824,6 +19170,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -18943,6 +19290,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -18955,6 +19304,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -18963,6 +19314,8 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -19034,6 +19387,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "dev": true, + "license": "MIT", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -19044,7 +19399,9 @@ "node_modules/tar-fs/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" }, "node_modules/tar-stream": { "version": "2.2.0", @@ -19113,7 +19470,8 @@ "node_modules/term-canvas": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/term-canvas/-/term-canvas-0.0.5.tgz", - "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==" + "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==", + "dev": true }, "node_modules/test-exclude": { "version": "6.0.0", @@ -19148,7 +19506,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/thread-stream": { "version": "0.15.2", @@ -19472,6 +19831,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -19514,6 +19874,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, "engines": { "node": ">=0.3.1" } @@ -19522,6 +19883,7 @@ "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -19533,6 +19895,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -19544,6 +19907,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "engines": { "node": ">=4" } @@ -19589,12 +19953,14 @@ "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -19606,6 +19972,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "engines": { "node": ">=4" } @@ -19614,6 +19981,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, "engines": { "node": ">=10" }, @@ -19740,6 +20108,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -19753,6 +20122,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -19770,6 +20140,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -19788,6 +20159,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -19848,6 +20220,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -19950,6 +20323,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -20004,7 +20378,8 @@ "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -20140,7 +20515,8 @@ "node_modules/whatwg-fetch": { "version": "3.6.17", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==" + "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==", + "dev": true }, "node_modules/whatwg-url": { "version": "5.0.0", @@ -20169,6 +20545,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -20191,6 +20568,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -20257,6 +20635,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true, "peer": true }, "node_modules/wrap-ansi": { @@ -20468,6 +20847,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/x256/-/x256-0.0.2.tgz", "integrity": "sha512-ZsIH+sheoF8YG9YG+QKEEIdtqpHRA9FYuD7MqhfyB1kayXU43RUNBFSxBEnF8ywSUxdg+8no4+bPr5qLbyxKgA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -20482,6 +20863,8 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "license": "MIT", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -20494,6 +20877,8 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=4.0" } @@ -20560,6 +20945,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -20574,6 +20960,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "engines": { "node": ">=10" }, @@ -20585,6 +20972,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, "engines": { "node": ">=10" }, @@ -20596,6 +20984,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, "engines": { "node": ">=8" } @@ -20689,6 +21078,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, "engines": { "node": ">=6" } @@ -20697,6 +21087,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "engines": { "node": ">=10" }, @@ -20724,7 +21115,6 @@ "lodash": "^4.17.21", "lru-cache": "^7.14.0", "pino": "^7.11.0", - "pnpm": "^8.7.1", "redis": "^4.6.7", "rlp": "^3.0.0" }, @@ -20739,112 +21129,6 @@ "typescript": "^4.6.4" } }, - "packages/relay/node_modules/@hashgraph/cryptography": { - "version": "1.4.8-beta.8", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", - "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", - "dependencies": { - "asn1js": "^3.0.5", - "bignumber.js": "^9.1.1", - "bn.js": "^5.2.1", - "buffer": "^6.0.3", - "crypto-js": "^4.2.0", - "elliptic": "^6.5.4", - "js-base64": "^3.7.4", - "node-forge": "^1.3.1", - "spark-md5": "^3.0.2", - "tweetnacl": "^1.0.3", - "utf8": "^3.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "expo": "^49.0.16", - "expo-crypto": "^10.1.2", - "expo-random": "^12.1.2" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - }, - "expo-crypto": { - "optional": true - }, - "expo-random": { - "optional": true - } - } - }, - "packages/relay/node_modules/@hashgraph/proto": { - "version": "2.15.0-beta.4", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", - "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", - "dependencies": { - "long": "^4.0.0", - "protobufjs": "^7.2.5" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "packages/relay/node_modules/@hashgraph/sdk": { - "version": "2.51.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", - "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.8", - "@hashgraph/proto": "2.15.0-beta.4", - "axios": "^1.6.4", - "bignumber.js": "^9.1.1", - "bn.js": "^5.1.1", - "crypto-js": "^4.2.0", - "js-base64": "^3.7.4", - "long": "^4.0.0", - "pino": "^8.14.1", - "pino-pretty": "^10.0.0", - "protobufjs": "^7.2.5", - "rfc4648": "^1.5.3", - "utf8": "^3.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "expo": "^49.0.16" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - } - } - }, - "packages/relay/node_modules/@hashgraph/sdk/node_modules/pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^3.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.6.0" - }, - "bin": { - "pino": "bin.js" - } - }, "packages/relay/node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -20859,21 +21143,14 @@ "packages/relay/node_modules/@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true }, "packages/relay/node_modules/aes-js": { "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" }, - "packages/relay/node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "engines": { - "node": "*" - } - }, "packages/relay/node_modules/ethers": { "version": "6.13.2", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", @@ -20906,132 +21183,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" }, - "packages/relay/node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "engines": { - "node": ">=14.0.0" - } - }, - "packages/relay/node_modules/pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "packages/relay/node_modules/pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "packages/relay/node_modules/pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" - }, - "packages/relay/node_modules/process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" - }, - "packages/relay/node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "packages/relay/node_modules/protobufjs/node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, - "packages/relay/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "packages/relay/node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "engines": { - "node": ">= 12.13.0" - } - }, - "packages/relay/node_modules/sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "packages/relay/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "engines": { - "node": ">= 10.x" - } - }, - "packages/relay/node_modules/thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", - "dependencies": { - "real-require": "^0.2.0" - } - }, "packages/relay/node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -21062,7 +21213,6 @@ "version": "0.57.0-SNAPSHOT", "dependencies": { "@hashgraph/json-rpc-relay": "file:../relay", - "axios": "^1.4.0", "co-body": "6.2.0", "dotenv": "^16.0.0", "koa": "^2.13.4", @@ -21070,14 +21220,11 @@ "koa-cors": "^0.0.16", "koa-logger": "^3.2.1", "koa-router": "^13.0.1", - "mocha": "^10.6.0", "pino": "^7.11.0", "pino-pretty": "^7.6.1", - "pnpm": "^8.7.1", "uuid": "^3.3.2" }, "devDependencies": { - "@hashgraph/hedera-local": "^2.29.2", "@hashgraph/sdk": "^2.50.0-beta.3", "@koa/cors": "^5.0.0", "@types/chai": "^4.3.0", @@ -21089,151 +21236,18 @@ "@types/mocha": "^9.1.0", "@types/node": "^17.0.31", "@types/uuid": "^10.0.0", + "axios": "^1.4.0", "axios-retry": "^3.5.1", "chai": "^4.3.6", "ethers": "^6.7.0", "execution-apis": "git://github.com/ethereum/execution-apis.git#7907424db935b93c2fe6a3c0faab943adebe8557", + "mocha": "^10.6.0", "shelljs": "^0.8.5", "ts-mocha": "^9.0.2", "ts-node": "^10.8.1", "typescript": "^4.5.5" } }, - "packages/server/node_modules/@hashgraph/cryptography": { - "version": "1.4.8-beta.8", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", - "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", - "dev": true, - "dependencies": { - "asn1js": "^3.0.5", - "bignumber.js": "^9.1.1", - "bn.js": "^5.2.1", - "buffer": "^6.0.3", - "crypto-js": "^4.2.0", - "elliptic": "^6.5.4", - "js-base64": "^3.7.4", - "node-forge": "^1.3.1", - "spark-md5": "^3.0.2", - "tweetnacl": "^1.0.3", - "utf8": "^3.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "expo": "^49.0.16", - "expo-crypto": "^10.1.2", - "expo-random": "^12.1.2" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - }, - "expo-crypto": { - "optional": true - }, - "expo-random": { - "optional": true - } - } - }, - "packages/server/node_modules/@hashgraph/proto": { - "version": "2.15.0-beta.4", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", - "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", - "dev": true, - "dependencies": { - "long": "^4.0.0", - "protobufjs": "^7.2.5" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "packages/server/node_modules/@hashgraph/sdk": { - "version": "2.51.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", - "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.8", - "@hashgraph/proto": "2.15.0-beta.4", - "axios": "^1.6.4", - "bignumber.js": "^9.1.1", - "bn.js": "^5.1.1", - "crypto-js": "^4.2.0", - "js-base64": "^3.7.4", - "long": "^4.0.0", - "pino": "^8.14.1", - "pino-pretty": "^10.0.0", - "protobufjs": "^7.2.5", - "rfc4648": "^1.5.3", - "utf8": "^3.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "expo": "^49.0.16" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - } - } - }, - "packages/server/node_modules/@hashgraph/sdk/node_modules/pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", - "dev": true, - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^3.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.6.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "packages/server/node_modules/@hashgraph/sdk/node_modules/pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", - "dev": true, - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, "packages/server/node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -21262,23 +21276,16 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } }, - "packages/server/node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "engines": { - "node": "*" - } - }, "packages/server/node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, "engines": { "node": ">=0.3.1" } @@ -21287,6 +21294,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -21333,6 +21341,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -21351,6 +21360,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -21387,6 +21397,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -21398,6 +21409,7 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, "dependencies": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", @@ -21431,16 +21443,8 @@ "packages/server/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "packages/server/node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "packages/server/node_modules/path-to-regexp": { "version": "8.1.0", @@ -21450,107 +21454,13 @@ "node": ">=16" } }, - "packages/server/node_modules/pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", - "dev": true, - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "packages/server/node_modules/pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", - "dev": true - }, - "packages/server/node_modules/process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", - "dev": true - }, - "packages/server/node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "packages/server/node_modules/protobufjs/node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "dev": true - }, - "packages/server/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "packages/server/node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "dev": true, - "engines": { - "node": ">= 12.13.0" - } - }, "packages/server/node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "packages/server/node_modules/sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dev": true, "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "packages/server/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "engines": { - "node": ">= 10.x" + "randombytes": "^2.1.0" } }, "packages/server/node_modules/statuses": { @@ -21565,6 +21475,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -21575,15 +21486,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "packages/server/node_modules/thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", - "dev": true, - "dependencies": { - "real-require": "^0.2.0" - } - }, "packages/server/node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -21593,7 +21495,8 @@ "packages/server/node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==" + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true }, "packages/server/node_modules/ws": { "version": "8.17.1", @@ -21620,6 +21523,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -21637,6 +21541,7 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, "engines": { "node": ">=10" } @@ -21647,7 +21552,6 @@ "dependencies": { "@hashgraph/json-rpc-relay": "file:../relay", "@hashgraph/json-rpc-server": "file:../server", - "axios": "^1.4.0", "co-body": "6.2.0", "dotenv": "^16.0.0", "koa": "^2.13.4", @@ -21656,13 +21560,10 @@ "koa-logger": "^3.2.1", "koa-router": "^13.0.1", "koa-websocket": "^7.0.0", - "mocha": "^10.1.0", "pino": "^7.11.0", - "pino-pretty": "^7.6.1", - "pnpm": "8.15.9" + "pino-pretty": "^7.6.1" }, "devDependencies": { - "@hashgraph/hedera-local": "^2.29.2", "@hashgraph/sdk": "^2.50.0-beta.3", "@koa/cors": "^5.0.0", "@types/chai": "^4.3.0", @@ -21672,150 +21573,17 @@ "@types/koa-router": "^7.4.4", "@types/mocha": "^9.1.0", "@types/node": "^17.0.31", + "axios": "^1.4.0", "axios-retry": "^3.5.1", "chai": "^4.3.6", "ethers": "^6.7.0", + "mocha": "^10.1.0", "shelljs": "^0.8.5", "ts-mocha": "^9.0.2", "ts-node": "^10.8.1", "typescript": "^4.5.5" } }, - "packages/ws-server/node_modules/@hashgraph/cryptography": { - "version": "1.4.8-beta.8", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", - "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", - "dev": true, - "dependencies": { - "asn1js": "^3.0.5", - "bignumber.js": "^9.1.1", - "bn.js": "^5.2.1", - "buffer": "^6.0.3", - "crypto-js": "^4.2.0", - "elliptic": "^6.5.4", - "js-base64": "^3.7.4", - "node-forge": "^1.3.1", - "spark-md5": "^3.0.2", - "tweetnacl": "^1.0.3", - "utf8": "^3.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "expo": "^49.0.16", - "expo-crypto": "^10.1.2", - "expo-random": "^12.1.2" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - }, - "expo-crypto": { - "optional": true - }, - "expo-random": { - "optional": true - } - } - }, - "packages/ws-server/node_modules/@hashgraph/proto": { - "version": "2.15.0-beta.4", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", - "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", - "dev": true, - "dependencies": { - "long": "^4.0.0", - "protobufjs": "^7.2.5" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "packages/ws-server/node_modules/@hashgraph/sdk": { - "version": "2.51.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", - "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.8", - "@hashgraph/proto": "2.15.0-beta.4", - "axios": "^1.6.4", - "bignumber.js": "^9.1.1", - "bn.js": "^5.1.1", - "crypto-js": "^4.2.0", - "js-base64": "^3.7.4", - "long": "^4.0.0", - "pino": "^8.14.1", - "pino-pretty": "^10.0.0", - "protobufjs": "^7.2.5", - "rfc4648": "^1.5.3", - "utf8": "^3.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "expo": "^49.0.16" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - } - } - }, - "packages/ws-server/node_modules/@hashgraph/sdk/node_modules/pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", - "dev": true, - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^3.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.6.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "packages/ws-server/node_modules/@hashgraph/sdk/node_modules/pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", - "dev": true, - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, "packages/ws-server/node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -21844,23 +21612,16 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } }, - "packages/ws-server/node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "engines": { - "node": "*" - } - }, "packages/ws-server/node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, "engines": { "node": ">=0.3.1" } @@ -21869,6 +21630,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -21915,6 +21677,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -21933,6 +21696,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -21969,6 +21733,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -21980,6 +21745,7 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, "dependencies": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", @@ -22013,16 +21779,8 @@ "packages/ws-server/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "packages/ws-server/node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "packages/ws-server/node_modules/path-to-regexp": { "version": "8.1.0", @@ -22032,107 +21790,13 @@ "node": ">=16" } }, - "packages/ws-server/node_modules/pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", - "dev": true, - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "packages/ws-server/node_modules/pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", - "dev": true - }, - "packages/ws-server/node_modules/process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", - "dev": true - }, - "packages/ws-server/node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "packages/ws-server/node_modules/protobufjs/node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "dev": true - }, - "packages/ws-server/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "packages/ws-server/node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "dev": true, - "engines": { - "node": ">= 12.13.0" - } - }, "packages/ws-server/node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "packages/ws-server/node_modules/sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dev": true, "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "packages/ws-server/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "engines": { - "node": ">= 10.x" + "randombytes": "^2.1.0" } }, "packages/ws-server/node_modules/statuses": { @@ -22147,6 +21811,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -22157,15 +21822,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "packages/ws-server/node_modules/thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", - "dev": true, - "dependencies": { - "real-require": "^0.2.0" - } - }, "packages/ws-server/node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -22175,7 +21831,8 @@ "packages/ws-server/node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==" + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true }, "packages/ws-server/node_modules/ws": { "version": "8.17.1", @@ -22202,6 +21859,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -22219,6 +21877,7 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, "engines": { "node": ">=10" } @@ -22228,7 +21887,8 @@ "@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==" + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true }, "@adraffy/ens-normalize": { "version": "1.10.1", @@ -22519,18 +22179,21 @@ "@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "dev": true }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, "optional": true }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -22539,6 +22202,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -22873,6 +22537,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, "requires": { "eslint-visitor-keys": "^3.3.0" } @@ -22880,12 +22545,14 @@ "@eslint-community/regexpp": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==" + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true }, "@eslint/eslintrc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -22902,6 +22569,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -22912,14 +22580,16 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true } } }, "@eslint/js": { "version": "8.48.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", - "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==" + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "dev": true }, "@ethereum-waffle/chai": { "version": "4.0.10", @@ -23838,9 +23508,9 @@ "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==" }, "@hashgraph/cryptography": { - "version": "1.4.8-beta.5", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.5.tgz", - "integrity": "sha512-soq2vGLRkdl2Evr+gIvIjCXJjqA1hOAjysBGG+dhP6tKx2PEgEjb3hON/sMbxm3Q4qQdkML/vEthdAV707+flw==", + "version": "1.4.8-beta.8", + "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", + "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", "requires": { "asn1js": "^3.0.5", "bignumber.js": "^9.1.1", @@ -23859,6 +23529,7 @@ "version": "2.31.0", "resolved": "https://registry.npmjs.org/@hashgraph/hedera-local/-/hedera-local-2.31.0.tgz", "integrity": "sha512-TsDmz3AJrokGk9s6o88GUoXIBqz3YKvUlIZH4Bs+amtUN30Fzv6kaoD9kB49rYk8VsGTAwkY+9ABCsumoAiV+g==", + "dev": true, "requires": { "@hashgraph/sdk": "^2.49.2", "blessed": "^0.1.81", @@ -23880,6 +23551,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, "requires": { "@noble/hashes": "1.3.2" } @@ -23887,17 +23559,20 @@ "@types/node": { "version": "18.15.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "dev": true }, "aes-js": { "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "dev": true }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "requires": { "balanced-match": "^1.0.0" } @@ -23905,12 +23580,14 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true }, "ethers": { "version": "6.13.2", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", "integrity": "sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==", + "dev": true, "requires": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -23922,9 +23599,10 @@ } }, "foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -23934,6 +23612,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, "requires": { "foreground-child": "^3.1.0", "jackspeak": "^4.0.1", @@ -23944,23 +23623,25 @@ } }, "jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" + "@isaacs/cliui": "^8.0.2" } }, "lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==" + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "dev": true }, "minimatch": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -23968,12 +23649,14 @@ "minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true }, "path-scurry": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, "requires": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" @@ -23983,6 +23666,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, "requires": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" @@ -23991,12 +23675,14 @@ "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true }, "ts-mocha": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz", "integrity": "sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==", + "dev": true, "requires": { "ts-node": "7.0.1", "tsconfig-paths": "^3.5.0" @@ -24006,6 +23692,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, "requires": { "arrify": "^1.0.0", "buffer-from": "^1.1.0", @@ -24020,18 +23707,21 @@ "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true }, "ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, "requires": {} }, "yn": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==" + "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==", + "dev": true } } }, @@ -24058,7 +23748,6 @@ "lodash": "^4.17.21", "lru-cache": "^7.14.0", "pino": "^7.11.0", - "pnpm": "^8.7.1", "redis": "^4.6.7", "redis-memory-server": "^0.10.0", "rlp": "^3.0.0", @@ -24067,78 +23756,6 @@ "typescript": "^4.6.4" }, "dependencies": { - "@hashgraph/cryptography": { - "version": "1.4.8-beta.8", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", - "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", - "requires": { - "asn1js": "^3.0.5", - "bignumber.js": "^9.1.1", - "bn.js": "^5.2.1", - "buffer": "^6.0.3", - "crypto-js": "^4.2.0", - "elliptic": "^6.5.4", - "js-base64": "^3.7.4", - "node-forge": "^1.3.1", - "spark-md5": "^3.0.2", - "tweetnacl": "^1.0.3", - "utf8": "^3.0.0" - } - }, - "@hashgraph/proto": { - "version": "2.15.0-beta.4", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", - "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", - "requires": { - "long": "^4.0.0", - "protobufjs": "^7.2.5" - } - }, - "@hashgraph/sdk": { - "version": "2.51.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", - "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", - "requires": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.8", - "@hashgraph/proto": "2.15.0-beta.4", - "axios": "^1.6.4", - "bignumber.js": "^9.1.1", - "bn.js": "^5.1.1", - "crypto-js": "^4.2.0", - "js-base64": "^3.7.4", - "long": "^4.0.0", - "pino": "^8.14.1", - "pino-pretty": "^10.0.0", - "protobufjs": "^7.2.5", - "rfc4648": "^1.5.3", - "utf8": "^3.0.0" - }, - "dependencies": { - "pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", - "requires": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^3.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.6.0" - } - } - } - }, "@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -24150,18 +23767,14 @@ "@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true }, "aes-js": { "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" }, - "dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" - }, "ethers": { "version": "6.13.2", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", @@ -24183,115 +23796,6 @@ } } }, - "on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" - }, - "pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", - "requires": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", - "requires": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" - }, - "process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" - }, - "protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "dependencies": { - "long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" - }, - "sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", - "requires": { - "atomic-sleep": "^1.0.0" - } - }, - "split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" - }, - "thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", - "requires": { - "real-require": "^0.2.0" - } - }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -24308,7 +23812,6 @@ "@hashgraph/json-rpc-server": { "version": "file:packages/server", "requires": { - "@hashgraph/hedera-local": "^2.29.2", "@hashgraph/json-rpc-relay": "file:../relay", "@hashgraph/sdk": "^2.50.0-beta.3", "@koa/cors": "^5.0.0", @@ -24336,7 +23839,6 @@ "mocha": "^10.6.0", "pino": "^7.11.0", "pino-pretty": "^7.6.1", - "pnpm": "^8.7.1", "shelljs": "^0.8.5", "ts-mocha": "^9.0.2", "ts-node": "^10.8.1", @@ -24344,104 +23846,6 @@ "uuid": "^3.3.2" }, "dependencies": { - "@hashgraph/cryptography": { - "version": "1.4.8-beta.8", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", - "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", - "dev": true, - "requires": { - "asn1js": "^3.0.5", - "bignumber.js": "^9.1.1", - "bn.js": "^5.2.1", - "buffer": "^6.0.3", - "crypto-js": "^4.2.0", - "elliptic": "^6.5.4", - "js-base64": "^3.7.4", - "node-forge": "^1.3.1", - "spark-md5": "^3.0.2", - "tweetnacl": "^1.0.3", - "utf8": "^3.0.0" - } - }, - "@hashgraph/proto": { - "version": "2.15.0-beta.4", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", - "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", - "dev": true, - "requires": { - "long": "^4.0.0", - "protobufjs": "^7.2.5" - } - }, - "@hashgraph/sdk": { - "version": "2.51.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", - "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.8", - "@hashgraph/proto": "2.15.0-beta.4", - "axios": "^1.6.4", - "bignumber.js": "^9.1.1", - "bn.js": "^5.1.1", - "crypto-js": "^4.2.0", - "js-base64": "^3.7.4", - "long": "^4.0.0", - "pino": "^8.14.1", - "pino-pretty": "^10.0.0", - "protobufjs": "^7.2.5", - "rfc4648": "^1.5.3", - "utf8": "^3.0.0" - }, - "dependencies": { - "pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", - "dev": true, - "requires": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^3.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.6.0" - } - }, - "pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", - "dev": true, - "requires": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - } - } - }, "@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -24467,25 +23871,22 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "requires": { "balanced-match": "^1.0.0" } }, - "dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true - }, "diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==" + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true }, "ethers": { "version": "6.13.2", @@ -24514,6 +23915,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -24525,7 +23927,8 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "http-errors": { "version": "2.0.0", @@ -24553,6 +23956,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -24561,6 +23965,7 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, "requires": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", @@ -24587,12 +23992,7 @@ "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "path-to-regexp": { @@ -24600,98 +24000,15 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==" }, - "pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", - "dev": true, - "requires": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", - "dev": true - }, - "process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", - "dev": true - }, - "protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "dev": true, - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "dependencies": { - "long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "dev": true - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "dev": true - }, "serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dev": true, "requires": { - "atomic-sleep": "^1.0.0" + "randombytes": "^2.1.0" } }, - "split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true - }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -24701,17 +24018,9 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", "dev": true, "requires": { - "real-require": "^0.2.0" + "has-flag": "^4.0.0" } }, "tslib": { @@ -24723,7 +24032,8 @@ "workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==" + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true }, "ws": { "version": "8.17.1", @@ -24736,6 +24046,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -24749,14 +24060,14 @@ "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, "@hashgraph/json-rpc-ws-server": { "version": "file:packages/ws-server", "requires": { - "@hashgraph/hedera-local": "^2.29.2", "@hashgraph/json-rpc-relay": "file:../relay", "@hashgraph/json-rpc-server": "file:../server", "@hashgraph/sdk": "^2.50.0-beta.3", @@ -24783,111 +24094,12 @@ "mocha": "^10.1.0", "pino": "^7.11.0", "pino-pretty": "^7.6.1", - "pnpm": "8.15.9", "shelljs": "^0.8.5", "ts-mocha": "^9.0.2", "ts-node": "^10.8.1", "typescript": "^4.5.5" }, "dependencies": { - "@hashgraph/cryptography": { - "version": "1.4.8-beta.8", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", - "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", - "dev": true, - "requires": { - "asn1js": "^3.0.5", - "bignumber.js": "^9.1.1", - "bn.js": "^5.2.1", - "buffer": "^6.0.3", - "crypto-js": "^4.2.0", - "elliptic": "^6.5.4", - "js-base64": "^3.7.4", - "node-forge": "^1.3.1", - "spark-md5": "^3.0.2", - "tweetnacl": "^1.0.3", - "utf8": "^3.0.0" - } - }, - "@hashgraph/proto": { - "version": "2.15.0-beta.4", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", - "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", - "dev": true, - "requires": { - "long": "^4.0.0", - "protobufjs": "^7.2.5" - } - }, - "@hashgraph/sdk": { - "version": "2.51.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", - "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.8", - "@hashgraph/proto": "2.15.0-beta.4", - "axios": "^1.6.4", - "bignumber.js": "^9.1.1", - "bn.js": "^5.1.1", - "crypto-js": "^4.2.0", - "js-base64": "^3.7.4", - "long": "^4.0.0", - "pino": "^8.14.1", - "pino-pretty": "^10.0.0", - "protobufjs": "^7.2.5", - "rfc4648": "^1.5.3", - "utf8": "^3.0.0" - }, - "dependencies": { - "pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", - "dev": true, - "requires": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^3.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.6.0" - } - }, - "pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", - "dev": true, - "requires": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - } - } - }, "@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -24913,25 +24125,22 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "requires": { "balanced-match": "^1.0.0" } }, - "dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true - }, "diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==" + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true }, "ethers": { "version": "6.13.2", @@ -24960,6 +24169,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -24971,7 +24181,8 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "http-errors": { "version": "2.0.0", @@ -24999,6 +24210,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -25007,6 +24219,7 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, "requires": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", @@ -25033,12 +24246,7 @@ "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "path-to-regexp": { @@ -25046,98 +24254,15 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==" }, - "pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", - "dev": true, - "requires": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", - "dev": true - }, - "process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", - "dev": true - }, - "protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "dev": true, - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "dependencies": { - "long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "dev": true - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "dev": true - }, "serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dev": true, "requires": { - "atomic-sleep": "^1.0.0" + "randombytes": "^2.1.0" } }, - "split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true - }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -25147,17 +24272,9 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", "dev": true, "requires": { - "real-require": "^0.2.0" + "has-flag": "^4.0.0" } }, "tslib": { @@ -25169,7 +24286,8 @@ "workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==" + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true }, "ws": { "version": "8.17.1", @@ -25182,6 +24300,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -25195,31 +24314,32 @@ "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, "@hashgraph/proto": { - "version": "2.15.0-beta.3", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.3.tgz", - "integrity": "sha512-/95cydqBQRaO1gagBOenNpcfJIcnRx+vzefhuzSFNp4pfl0AM3oXau39hM2raKLhFHoKZysSjkXkMp24AaCE5w==", + "version": "2.15.0-beta.4", + "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", + "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", "requires": { "long": "^4.0.0", "protobufjs": "^7.2.4" } }, "@hashgraph/sdk": { - "version": "2.49.2", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.49.2.tgz", - "integrity": "sha512-HqESeH6gF/QEm69qmEyPZ40i9w2jBXsyXFqT/kRsrb7yEyCdVrG1Wnt88HxOSP9XyOsm3hj4OBTEiy2sI+kl+A==", + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", + "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", "requires": { "@ethersproject/abi": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/rlp": "^5.7.0", "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.5", - "@hashgraph/proto": "2.15.0-beta.3", + "@hashgraph/cryptography": "1.4.8-beta.8", + "@hashgraph/proto": "2.15.0-beta.4", "axios": "^1.6.4", "bignumber.js": "^9.1.1", "bn.js": "^5.1.1", @@ -25345,6 +24465,7 @@ "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -25354,12 +24475,14 @@ "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, "@hutson/parse-repository-url": { "version": "3.0.2", @@ -25540,7 +24663,8 @@ "@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true }, "@jridgewell/set-array": { "version": "1.1.2", @@ -25551,7 +24675,8 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, "@jridgewell/trace-mapping": { "version": "0.3.19", @@ -25566,12 +24691,14 @@ "@json-schema-spec/json-pointer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@json-schema-spec/json-pointer/-/json-pointer-0.1.2.tgz", - "integrity": "sha512-BYY7IavBjwsWWSmVcMz2A9mKiDD9RvacnsItgmy1xV8cmgbtxFfKmKMtkVpD7pYtkx4mIW4800yZBXueVFIWPw==" + "integrity": "sha512-BYY7IavBjwsWWSmVcMz2A9mKiDD9RvacnsItgmy1xV8cmgbtxFfKmKMtkVpD7pYtkx4mIW4800yZBXueVFIWPw==", + "dev": true }, "@json-schema-tools/dereferencer": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@json-schema-tools/dereferencer/-/dereferencer-1.5.4.tgz", "integrity": "sha512-4cmEdRPIG7WrcSWGRV6HBDCLXEOXGkaOZnopqBxoG24mKYuCHWg4M6N9nioTQyNfKqlPkOPvT4lStQqkPnhLgA==", + "dev": true, "requires": { "@json-schema-tools/reference-resolver": "^1.2.4", "@json-schema-tools/traverse": "^1.7.8", @@ -25581,12 +24708,14 @@ "@json-schema-tools/meta-schema": { "version": "1.6.19", "resolved": "https://registry.npmjs.org/@json-schema-tools/meta-schema/-/meta-schema-1.6.19.tgz", - "integrity": "sha512-55zuWFW7tr4tf/G5AYmybcPdGOkVAreQbt2JdnogX4I2r/zkxZiimYPJESDf5je9BI2oRveak2p296HzDppeaA==" + "integrity": "sha512-55zuWFW7tr4tf/G5AYmybcPdGOkVAreQbt2JdnogX4I2r/zkxZiimYPJESDf5je9BI2oRveak2p296HzDppeaA==", + "dev": true }, "@json-schema-tools/reference-resolver": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@json-schema-tools/reference-resolver/-/reference-resolver-1.2.4.tgz", "integrity": "sha512-Oag20zDuapO6nBQp00k8Rd5sDTb8Gfz9uH43Tf7dHKNx7nHDK/WdeTe7OxkOmLQCL6aS+mCJx1Zv+fZBCD+tzQ==", + "dev": true, "requires": { "@json-schema-spec/json-pointer": "^0.1.2", "isomorphic-fetch": "^3.0.0" @@ -25595,7 +24724,8 @@ "@json-schema-tools/traverse": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@json-schema-tools/traverse/-/traverse-1.10.1.tgz", - "integrity": "sha512-vYY5EIxCPzEXEWL/vTjdHy4g92tv1ApUQCjPJsj9gEoXLNNVwJlwwgRZisuvgFBZ3zeLzQygrbehERSpYdmFZA==" + "integrity": "sha512-vYY5EIxCPzEXEWL/vTjdHy4g92tv1ApUQCjPJsj9gEoXLNNVwJlwwgRZisuvgFBZ3zeLzQygrbehERSpYdmFZA==", + "dev": true }, "@keyvhq/core": { "version": "1.6.26", @@ -26515,12 +25645,14 @@ "@open-rpc/meta-schema": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/@open-rpc/meta-schema/-/meta-schema-1.14.2.tgz", - "integrity": "sha512-vD4Nbkrb7wYFRcSQf+j228LwOy1C6/KKpy5NADlpMElGrAWPRxhTa2yTi6xG+x88OHzg2+cydQ0GAD6o40KUcg==" + "integrity": "sha512-vD4Nbkrb7wYFRcSQf+j228LwOy1C6/KKpy5NADlpMElGrAWPRxhTa2yTi6xG+x88OHzg2+cydQ0GAD6o40KUcg==", + "dev": true }, "@open-rpc/schema-utils-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@open-rpc/schema-utils-js/-/schema-utils-js-1.16.1.tgz", "integrity": "sha512-8D4OgBnHDAv7JeaYZ5v7SL4yR0YLLO4WLTWtdR8vmqSqvX3SvPzSsGYv06zqm9z1Lhm563MAcuearrc8g9eJ4w==", + "dev": true, "requires": { "@json-schema-tools/dereferencer": "1.5.4", "@json-schema-tools/meta-schema": "1.6.19", @@ -26538,6 +25670,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -26548,7 +25681,8 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true } } }, @@ -26883,22 +26017,26 @@ "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true }, "@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true }, "@tufjs/canonical-json": { "version": "2.0.0", @@ -27072,7 +26210,8 @@ "@types/find-config": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/find-config/-/find-config-1.0.4.tgz", - "integrity": "sha512-BCXaKgzHK7KnfCQBRQBWGTA+QajOE9uFolXPt+9EktiiMS56D8oXF2ZCh9eCxuEyfqDmX/mYIcmWg9j9f659eg==" + "integrity": "sha512-BCXaKgzHK7KnfCQBRQBWGTA+QajOE9uFolXPt+9EktiiMS56D8oXF2ZCh9eCxuEyfqDmX/mYIcmWg9j9f659eg==", + "dev": true }, "@types/heapdump": { "version": "0.3.4", @@ -27095,12 +26234,14 @@ "@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true }, "@types/keygrip": { "version": "1.0.6", @@ -27301,7 +26442,8 @@ "@types/semver": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==" + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true }, "@types/send": { "version": "0.17.4", @@ -27328,6 +26470,7 @@ "version": "10.0.20", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", "integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==", + "dev": true, "requires": { "@types/sinonjs__fake-timers": "*" } @@ -27335,7 +26478,8 @@ "@types/sinonjs__fake-timers": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", - "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==" + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true }, "@types/uuid": { "version": "10.0.0", @@ -27357,6 +26501,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", + "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.5.0", @@ -27375,6 +26520,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, "requires": {} } } @@ -27383,6 +26529,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "dev": true, "requires": { "@typescript-eslint/scope-manager": "6.5.0", "@typescript-eslint/types": "6.5.0", @@ -27395,6 +26542,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "dev": true, "requires": { "@typescript-eslint/types": "6.5.0", "@typescript-eslint/visitor-keys": "6.5.0" @@ -27404,6 +26552,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", + "dev": true, "requires": { "@typescript-eslint/typescript-estree": "6.5.0", "@typescript-eslint/utils": "6.5.0", @@ -27415,6 +26564,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, "requires": {} } } @@ -27422,12 +26572,14 @@ "@typescript-eslint/types": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", - "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==" + "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "dev": true }, "@typescript-eslint/typescript-estree": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "dev": true, "requires": { "@typescript-eslint/types": "6.5.0", "@typescript-eslint/visitor-keys": "6.5.0", @@ -27442,6 +26594,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, "requires": {} } } @@ -27450,6 +26603,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", + "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", @@ -27464,6 +26618,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "dev": true, "requires": { "@typescript-eslint/types": "6.5.0", "eslint-visitor-keys": "^3.4.1" @@ -27473,6 +26628,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true, "peer": true }, "@yarnpkg/lockfile": { @@ -27519,7 +26675,8 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "abort-controller": { "version": "3.0.0", @@ -27566,18 +26723,21 @@ "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "requires": {} }, "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true }, "add-stream": { "version": "1.0.0", @@ -27587,7 +26747,8 @@ "address": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==" + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true }, "aes-js": { "version": "3.0.0", @@ -27615,6 +26776,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -27625,7 +26787,8 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true } } }, @@ -27633,6 +26796,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, "requires": { "ajv": "^8.0.0" } @@ -27674,6 +26838,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/ansi-term/-/ansi-term-0.0.2.tgz", "integrity": "sha512-jLnGE+n8uAjksTJxiWZf/kcUmXq+cRWSl550B9NmQ8YiqaTM+lILcSe5dHdp8QkJPhaOghDjnMKwyYSMjosgAA==", + "dev": true, "requires": { "x256": ">=0.0.1" } @@ -27681,12 +26846,14 @@ "ansicolors": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -27715,7 +26882,8 @@ "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true }, "argparse": { "version": "2.0.1", @@ -27750,6 +26918,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, "requires": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -27769,6 +26938,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -27786,6 +26956,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -27798,6 +26969,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -27809,6 +26981,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -27820,6 +26993,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, "requires": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -27838,6 +27012,7 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -27861,7 +27036,8 @@ "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true }, "async": { "version": "2.6.4", @@ -27889,7 +27065,8 @@ "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true }, "atomic-sleep": { "version": "1.0.0", @@ -27899,7 +27076,8 @@ "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true }, "aws-sign2": { "version": "0.7.0", @@ -27965,6 +27143,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, "requires": { "tweetnacl": "^0.14.3" }, @@ -27972,7 +27151,8 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true } } }, @@ -28013,7 +27193,8 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true }, "bintrees": { "version": "1.0.2", @@ -28070,12 +27251,14 @@ "blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", - "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==" + "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", + "dev": true }, "blessed-terminal": { "version": "0.1.22", "resolved": "https://registry.npmjs.org/blessed-terminal/-/blessed-terminal-0.1.22.tgz", "integrity": "sha512-R8Ej+yzsaey7gW5DSmPhIC28gNLYQad6lMODuEw0X4KzudWWxZQ632Z+BRJk2EHN5dsFWRWhvLbD+M5Vs5J+AA==", + "dev": true, "requires": { "ansi-term": ">=0.0.2", "chalk": "^2.4.2", @@ -28124,7 +27307,8 @@ "bresenham": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/bresenham/-/bresenham-0.0.3.tgz", - "integrity": "sha512-wbMxoJJM1p3+6G7xEFXYNCJ30h2qkwmVxebkbwIl4OcnWtno5R3UT9VuYLfStlVNAQCmRjkGwjPFdfaPd4iNXw==" + "integrity": "sha512-wbMxoJJM1p3+6G7xEFXYNCJ30h2qkwmVxebkbwIl4OcnWtno5R3UT9VuYLfStlVNAQCmRjkGwjPFdfaPd4iNXw==", + "dev": true }, "brorand": { "version": "1.1.0", @@ -28134,7 +27318,8 @@ "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true }, "browserify-aes": { "version": "1.2.0", @@ -28222,18 +27407,21 @@ "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==" + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true }, "buildcheck": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "dev": true, "optional": true }, "builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, "requires": { "semver": "^7.5.3" } @@ -28396,6 +27584,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, "requires": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" @@ -28411,6 +27600,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", @@ -28434,6 +27624,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/chai-exclude/-/chai-exclude-2.1.1.tgz", "integrity": "sha512-IHgNmgAFOkyRPnmOtZio9UsOHQ6RnzVr2LOs+5V9urYYqjhV/ERLQapC0Eq2DmID5eDWyngAcBxNUm0ZK0QbrQ==", + "dev": true, "requires": { "fclone": "^1.0.11" } @@ -28462,17 +27653,20 @@ "charm": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "dev": true }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==" + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -28523,9 +27717,10 @@ "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==" }, "cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, "requires": { "@colors/colors": "1.5.0", "string-width": "^4.2.0" @@ -28978,13 +28173,14 @@ } }, "cpu-features": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz", - "integrity": "sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ==", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, "optional": true, "requires": { "buildcheck": "~0.0.6", - "nan": "^2.17.0" + "nan": "^2.19.0" } }, "crc-32": { @@ -29023,7 +28219,8 @@ "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, "cross-spawn": { "version": "7.0.3", @@ -29055,6 +28252,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", + "dev": true, "requires": { "minimist": "^1.2.0" } @@ -29117,6 +28315,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, "requires": { "type-detect": "^4.0.0" } @@ -29135,7 +28334,8 @@ "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "default-require-extensions": { "version": "3.0.1", @@ -29198,6 +28398,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -29242,12 +28443,14 @@ "detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true }, "detect-port": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "dev": true, "requires": { "address": "^1.0.1", "debug": "4" @@ -29256,7 +28459,8 @@ "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true }, "diff-sequences": { "version": "29.6.3", @@ -29275,6 +28479,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", "integrity": "sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==", + "dev": true, "requires": { "debug": "^4.1.1", "readable-stream": "^3.5.0", @@ -29286,6 +28491,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz", "integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==", + "dev": true, "requires": { "@balena/dockerignore": "^1.0.2", "docker-modem": "^5.0.3", @@ -29296,6 +28502,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "requires": { "esutils": "^2.0.2" } @@ -29324,12 +28531,14 @@ "drawille-blessed-contrib": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/drawille-blessed-contrib/-/drawille-blessed-contrib-1.0.0.tgz", - "integrity": "sha512-WnHMgf5en/hVOsFhxLI8ZX0qTJmerOsVjIMQmn4cR1eI8nLGu+L7w5ENbul+lZ6w827A3JakCuernES5xbHLzQ==" + "integrity": "sha512-WnHMgf5en/hVOsFhxLI8ZX0qTJmerOsVjIMQmn4cR1eI8nLGu+L7w5ENbul+lZ6w827A3JakCuernES5xbHLzQ==", + "dev": true }, "drawille-canvas-blessed-contrib": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/drawille-canvas-blessed-contrib/-/drawille-canvas-blessed-contrib-0.1.3.tgz", "integrity": "sha512-bdDvVJOxlrEoPLifGDPaxIzFh3cD7QH05ePoQ4fwnqfi08ZSxzEhOUpI5Z0/SQMlWgcCQOEtuw0zrwezacXglw==", + "dev": true, "requires": { "ansi-term": ">=0.0.2", "bresenham": "0.0.3", @@ -29515,6 +28724,7 @@ "version": "1.22.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, "requires": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.1", @@ -29561,6 +28771,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, "requires": { "get-intrinsic": "^1.1.3", "has": "^1.0.3", @@ -29571,6 +28782,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, "requires": { "has": "^1.0.3" } @@ -29579,6 +28791,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -29610,6 +28823,7 @@ "version": "8.48.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -29654,6 +28868,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -29665,6 +28880,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -29673,6 +28889,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -29682,6 +28899,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -29689,17 +28907,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "requires": { "is-glob": "^4.0.3" } @@ -29707,17 +28928,20 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -29747,6 +28971,7 @@ "version": "43.0.1", "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-43.0.1.tgz", "integrity": "sha512-WfZ986+qzIzX6dcr4yGUyVb/l9N3Z8wPXCc5z/70fljs3UbWhhV+WxrfgsqMToRzuuyX9MqZ974pq2UPhDTOcA==", + "dev": true, "requires": { "@typescript-eslint/parser": "^6.4.0", "eslint-config-standard": "17.1.0" @@ -29756,6 +28981,7 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, "requires": {} } } @@ -29764,6 +28990,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, "requires": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -29774,6 +29001,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -29784,6 +29012,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, "requires": { "debug": "^3.2.7" }, @@ -29792,6 +29021,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -29802,6 +29032,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, "requires": { "eslint-utils": "^2.0.0", "regexpp": "^3.0.0" @@ -29811,6 +29042,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" } @@ -29818,7 +29050,8 @@ "eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true } } }, @@ -29826,6 +29059,7 @@ "version": "2.28.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "dev": true, "requires": { "array-includes": "^3.1.6", "array.prototype.findlastindex": "^1.2.2", @@ -29850,6 +29084,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -29858,6 +29093,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "requires": { "esutils": "^2.0.2" } @@ -29868,6 +29104,7 @@ "version": "15.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, "requires": { "builtins": "^5.0.1", "eslint-plugin-es": "^4.1.0", @@ -29883,6 +29120,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, "peer": true, "requires": {} }, @@ -29890,6 +29128,7 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -29899,6 +29138,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "requires": { "eslint-visitor-keys": "^2.0.0" }, @@ -29906,19 +29146,22 @@ "eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true } } }, "eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true }, "espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, "requires": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -29934,6 +29177,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, "requires": { "estraverse": "^5.1.0" } @@ -29942,6 +29186,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "requires": { "estraverse": "^5.2.0" } @@ -29949,12 +29194,14 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "eth-ens-namehash": { "version": "2.0.8", @@ -30231,6 +29478,7 @@ "version": "0.9.8", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-0.9.8.tgz", "integrity": "sha512-o5h0Mp1bkoR6B0i7pTCAzRy+VzdsRWH997KQD4Psb0EOPoKEIiaRx/EsOdUl7p1Ktjw7aIWvweI/OY1R9XrlUg==", + "dev": true, "requires": { "optimist": "0.2" }, @@ -30239,6 +29487,7 @@ "version": "0.2.8", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.2.8.tgz", "integrity": "sha512-Wy7E3cQDpqsTIFyW7m22hSevyTLxw850ahYv7FWsw4G6MIKVTZ8NSA95KBrQ95a4SMsMr1UGUUnwEFKhVaSzIg==", + "dev": true, "requires": { "wordwrap": ">=0.0.1 <0.1.0" } @@ -30246,7 +29495,8 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==" + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "dev": true } } }, @@ -30365,7 +29615,8 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "fast-glob": { "version": "3.3.1", @@ -30382,12 +29633,14 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "fast-redact": { "version": "3.3.0", @@ -30402,7 +29655,8 @@ "fast-uri": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true }, "fastq": { "version": "1.15.0", @@ -30415,7 +29669,8 @@ "fclone": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==" + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", + "dev": true }, "fd-slicer": { "version": "1.1.0", @@ -30438,6 +29693,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "requires": { "flat-cache": "^3.0.4" } @@ -30514,6 +29770,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -30528,6 +29785,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -30536,7 +29794,8 @@ "flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true }, "follow-redirects": { "version": "1.15.6", @@ -30547,6 +29806,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -30624,6 +29884,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -30655,6 +29916,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "optional": true }, "function-bind": { @@ -30666,6 +29928,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -30682,7 +29945,8 @@ "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true }, "ganache": { "version": "7.4.3", @@ -31065,7 +30329,8 @@ "get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==" + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true }, "get-intrinsic": { "version": "1.2.1", @@ -31141,6 +30406,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -31218,7 +30484,8 @@ "gl-matrix": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz", - "integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==" + "integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==", + "dev": true }, "glob": { "version": "7.2.0", @@ -31245,6 +30512,7 @@ "version": "13.21.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, "requires": { "type-fest": "^0.20.2" } @@ -31253,6 +30521,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, "requires": { "define-properties": "^1.1.3" } @@ -31274,6 +30543,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -31286,12 +30556,14 @@ "graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, "peer": true }, "handlebars": { @@ -31358,7 +30630,8 @@ "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true }, "has-flag": { "version": "3.0.0", @@ -31369,6 +30642,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -31437,7 +30711,8 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true }, "help-me": { "version": "5.0.0", @@ -31447,7 +30722,8 @@ "here": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/here/-/here-0.0.2.tgz", - "integrity": "sha512-U7VYImCTcPoY27TSmzoiFsmWLEqQFaYNdpsPb9K0dXJhE6kufUqycaz51oR09CW85dDU9iWyy7At8M+p7hb3NQ==" + "integrity": "sha512-U7VYImCTcPoY27TSmzoiFsmWLEqQFaYNdpsPb9K0dXJhE6kufUqycaz51oR09CW85dDU9iWyy7At8M+p7hb3NQ==", + "dev": true }, "hmac-drbg": { "version": "1.0.1", @@ -31751,6 +31027,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "requires": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -31760,7 +31037,8 @@ "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true }, "invert-kv": { "version": "1.0.0", @@ -31811,6 +31089,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -31826,6 +31105,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "requires": { "has-bigints": "^1.0.1" } @@ -31834,6 +31114,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -31842,6 +31123,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -31856,7 +31138,8 @@ "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true }, "is-ci": { "version": "3.0.1", @@ -31878,6 +31161,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -31932,7 +31216,8 @@ "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true }, "is-number": { "version": "7.0.0", @@ -31943,6 +31228,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -31955,7 +31241,8 @@ "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true }, "is-plain-obj": { "version": "1.1.0", @@ -31971,6 +31258,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -31985,6 +31273,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -32006,6 +31295,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -32014,6 +31304,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -32030,6 +31321,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, "requires": { "which-typed-array": "^1.1.11" } @@ -32048,7 +31340,8 @@ "is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true }, "is-utf8": { "version": "0.2.1", @@ -32061,6 +31354,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -32082,7 +31376,8 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "isexe": { "version": "2.0.0", @@ -32098,6 +31393,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dev": true, "requires": { "node-fetch": "^2.6.1", "whatwg-fetch": "^3.4.1" @@ -32429,12 +31725,14 @@ "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "json-stringify-nice": { "version": "1.1.4", @@ -33047,6 +32345,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -33296,6 +32595,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "requires": { "p-locate": "^5.0.0" } @@ -33364,7 +32664,8 @@ "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "log-symbols": { "version": "4.1.0", @@ -33555,6 +32856,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, "requires": { "get-func-name": "^2.0.0" } @@ -33582,7 +32884,8 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "make-fetch-happen": { "version": "13.0.1", @@ -33614,6 +32917,7 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/map-canvas/-/map-canvas-0.1.5.tgz", "integrity": "sha512-f7M3sOuL9+up0NCOZbb1rQpWDLZwR/ftCiNbyscjl9LUUEwrRaoumH4sz6swgs58lF21DQ0hsYOCw5C6Zz7hbg==", + "dev": true, "requires": { "drawille-canvas-blessed-contrib": ">=0.0.1", "xml2js": "^0.4.5" @@ -33627,12 +32931,14 @@ "marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==" + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true }, "marked-terminal": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.2.0.tgz", "integrity": "sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==", + "dev": true, "requires": { "ansi-escapes": "^6.2.0", "cardinal": "^2.1.1", @@ -33643,22 +32949,16 @@ }, "dependencies": { "ansi-escapes": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", - "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", - "requires": { - "type-fest": "^3.0.0" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==" - }, - "type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==" + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true } } }, @@ -33752,6 +33052,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/memory-streams/-/memory-streams-0.1.3.tgz", "integrity": "sha512-qVQ/CjkMyMInPaaRMrwWNDvf6boRZXaT/DbQeMYcCWuXPEBf1v8qChOc9OlEVQp2uOvRXa1Qu30fLmKhY6NipA==", + "dev": true, "requires": { "readable-stream": "~1.0.2" }, @@ -33759,12 +33060,14 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true }, "readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -33775,14 +33078,16 @@ "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true } } }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==" + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true }, "meow": { "version": "8.1.2", @@ -34179,6 +33484,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "requires": { "minimist": "^1.2.6" } @@ -34186,12 +33492,14 @@ "mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true }, "mocha": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, "peer": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -34224,12 +33532,14 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, "peer": true }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, "peer": true, "requires": { "ms": "2.1.2" @@ -34239,6 +33549,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, "peer": true } } @@ -34247,18 +33558,21 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "peer": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "peer": true }, "minimatch": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, "peer": true, "requires": { "brace-expansion": "^1.1.7" @@ -34268,12 +33582,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "peer": true }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "peer": true, "requires": { "has-flag": "^4.0.0" @@ -34283,6 +33599,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "peer": true, "requires": { "cliui": "^7.0.2", @@ -34367,21 +33684,24 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "dev": true, "optional": true }, "nanoid": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true, "peer": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "negotiator": { "version": "0.6.3", @@ -34435,6 +33755,7 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, "requires": { "lodash": "^4.17.21" } @@ -34629,7 +33950,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "npm-bundled": { "version": "3.0.1", @@ -35093,12 +34415,14 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -35121,6 +34445,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -35131,6 +34456,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -35142,6 +34468,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -35196,6 +34523,7 @@ "version": "0.3.7", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", "integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==", + "dev": true, "requires": { "wordwrap": "~0.0.2" }, @@ -35203,7 +34531,8 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==" + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "dev": true } } }, @@ -35211,6 +34540,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, "requires": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -35310,6 +34640,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "requires": { "yocto-queue": "^0.1.0" } @@ -35318,6 +34649,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "requires": { "p-limit": "^3.0.2" } @@ -35546,7 +34878,8 @@ "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true }, "pbkdf2": { "version": "3.1.2", @@ -35588,6 +34921,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/picture-tuber/-/picture-tuber-1.0.2.tgz", "integrity": "sha512-49/xq+wzbwDeI32aPvwQJldM8pr7dKDRuR76IjztrkmiCkAQDaWFJzkmfVqCHmt/iFoPFhHmI9L0oKhthrTOQw==", + "dev": true, "requires": { "buffers": "~0.1.1", "charm": "~0.1.0", @@ -35737,7 +35071,8 @@ "png-js": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/png-js/-/png-js-0.1.1.tgz", - "integrity": "sha512-NTtk2SyfjBm+xYl2/VZJBhFnTQ4kU5qWC7VC4/iGbrgiU4FuB4xC+74erxADYJIqZICOR1HCvRA7EBHkpjTg9g==" + "integrity": "sha512-NTtk2SyfjBm+xYl2/VZJBhFnTQ4kU5qWC7VC4/iGbrgiU4FuB4xC+74erxADYJIqZICOR1HCvRA7EBHkpjTg9g==", + "dev": true }, "pnpm": { "version": "8.15.9", @@ -35756,7 +35091,8 @@ "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true }, "prettier": { "version": "3.0.3", @@ -35921,7 +35257,8 @@ "punycode": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==" + "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", + "dev": true }, "pvtsutils": { "version": "1.3.5", @@ -35963,6 +35300,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -36160,6 +35498,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -36173,6 +35512,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, "requires": { "resolve": "^1.1.6" } @@ -36190,6 +35530,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, "requires": { "esprima": "~4.0.0" } @@ -36344,6 +35685,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -36353,7 +35695,8 @@ "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true }, "release-zalgo": { "version": "1.0.0", @@ -36660,6 +36003,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -36676,6 +36020,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -36693,9 +36038,10 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true }, "scrypt-js": { "version": "3.0.1", @@ -36752,6 +36098,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, "peer": true, "requires": { "randombytes": "^2.1.0" @@ -36808,6 +36155,7 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -36986,6 +36334,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -37000,6 +36349,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/sparkline/-/sparkline-0.1.2.tgz", "integrity": "sha512-t//aVOiWt9fi/e22ea1vXVWBDX+gp18y+Ch9sKqmHl828bRfvP2VtfTJVEcgWFBQHd0yDPNQRiHdqzCvbcYSDA==", + "dev": true, "requires": { "here": "0.0.2", "nopt": "~2.1.2" @@ -37009,6 +36359,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", "integrity": "sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==", + "dev": true, "requires": { "abbrev": "1" } @@ -37068,7 +36419,8 @@ "split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "dev": true }, "split2": { "version": "3.2.2", @@ -37084,14 +36436,15 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "ssh2": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", - "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", + "dev": true, "requires": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2", - "cpu-features": "~0.0.9", - "nan": "^2.18.0" + "cpu-features": "~0.0.10", + "nan": "^2.20.0" } }, "sshpk": { @@ -37194,6 +36547,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -37204,6 +36558,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -37214,6 +36569,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -37295,6 +36651,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, "requires": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -37303,12 +36660,14 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -37388,6 +36747,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "dev": true, "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -37398,7 +36758,8 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true } } }, @@ -37430,7 +36791,8 @@ "term-canvas": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/term-canvas/-/term-canvas-0.0.5.tgz", - "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==" + "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==", + "dev": true }, "test-exclude": { "version": "6.0.0", @@ -37458,7 +36820,8 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "thread-stream": { "version": "0.15.2", @@ -37710,6 +37073,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -37729,7 +37093,8 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true } } }, @@ -37737,6 +37102,7 @@ "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -37748,6 +37114,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "requires": { "minimist": "^1.2.0" } @@ -37755,7 +37122,8 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true } } }, @@ -37797,6 +37165,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "requires": { "prelude-ls": "^1.2.1" } @@ -37804,12 +37173,14 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true }, "type-is": { "version": "1.6.18", @@ -37896,6 +37267,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -37906,6 +37278,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -37917,6 +37290,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -37929,6 +37303,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, "requires": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -37970,6 +37345,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -38033,6 +37409,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -38081,7 +37458,8 @@ "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true }, "validate-npm-package-license": { "version": "3.0.4", @@ -38192,7 +37570,8 @@ "whatwg-fetch": { "version": "3.6.17", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==" + "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==", + "dev": true }, "whatwg-url": { "version": "5.0.0", @@ -38215,6 +37594,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "requires": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -38234,6 +37614,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -38284,6 +37665,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true, "peer": true }, "wrap-ansi": { @@ -38438,7 +37820,8 @@ "x256": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/x256/-/x256-0.0.2.tgz", - "integrity": "sha512-ZsIH+sheoF8YG9YG+QKEEIdtqpHRA9FYuD7MqhfyB1kayXU43RUNBFSxBEnF8ywSUxdg+8no4+bPr5qLbyxKgA==" + "integrity": "sha512-ZsIH+sheoF8YG9YG+QKEEIdtqpHRA9FYuD7MqhfyB1kayXU43RUNBFSxBEnF8ywSUxdg+8no4+bPr5qLbyxKgA==", + "dev": true }, "xml": { "version": "1.0.1", @@ -38450,6 +37833,7 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -38458,7 +37842,8 @@ "xmlbuilder": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true }, "xtend": { "version": "4.0.2", @@ -38552,6 +37937,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, "requires": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -38562,17 +37948,20 @@ "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true }, "decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true } } }, @@ -38594,12 +37983,14 @@ "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 1303d0a39f..59017208c4 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,26 @@ { "name": "root", "devDependencies": { + "@hashgraph/hedera-local": "^2.31.0", + "@open-rpc/schema-utils-js": "^1.16.1", "@types/chai-as-promised": "^7.1.5", "@types/co-body": "6.1.0", + "@types/find-config": "^1.0.4", "@types/koa-cors": "^0.0.6", + "@types/sinon": "^10.0.20", "@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/parser": "^6.5.0", + "ajv": "^8.16.0", + "ajv-formats": "^3.0.1", "axios-mock-adapter": "^1.20.0", "chai-as-promised": "^7.1.1", + "chai-exclude": "^2.1.1", "eslint": "^8.48.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.3.0", + "eslint-config-standard-with-typescript": "^43.0.1", "eslint-plugin-import": "^2.28.1", + "eslint-plugin-n": "^15.7.0", "ethereum-waffle": "^4.0.7", "husky": "^8.0.3", "ioredis": "^5.3.2", @@ -21,7 +30,9 @@ "nodemon": "^2.0.15", "nyc": "^15.1.0", "prettier": "^3.0.3", - "replace": "^1.2.2" + "replace": "^1.2.2", + "ts-node": "^10.9.2", + "typescript": "^4.6.3" }, "workspaces": { "packages": [ @@ -73,15 +84,6 @@ "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/trie": "^6.2.1", "@ethereumjs/util": "^9.1.0", - "@hashgraph/hedera-local": "^2.31.0", - "@open-rpc/schema-utils-js": "^1.16.1", - "@types/find-config": "^1.0.4", - "@types/sinon": "^10.0.20", - "ajv": "^8.16.0", - "ajv-formats": "^3.0.1", - "chai-exclude": "^2.1.1", - "eslint-config-standard-with-typescript": "^43.0.1", - "eslint-plugin-n": "^15.7.0", "keyv-file": "^0.3.3", "koa-cors": "^0.0.16", "koa-websocket": "^7.0.0", @@ -89,9 +91,8 @@ "pino": "^7.11.0", "pino-pretty": "^7.6.1", "prom-client": "^14.0.1", - "redis": "^4.7.0", - "ts-node": "^10.9.2", - "typescript": "^4.6.3" + "pnpm": "^8.7.1", + "redis": "^4.7.0" }, "overrides": { "protobufjs": "^7.2.4", diff --git a/packages/relay/package.json b/packages/relay/package.json index b69d7ea22b..5a5c8d4adb 100644 --- a/packages/relay/package.json +++ b/packages/relay/package.json @@ -64,7 +64,6 @@ "lodash": "^4.17.21", "lru-cache": "^7.14.0", "pino": "^7.11.0", - "pnpm": "^8.7.1", "redis": "^4.6.7", "rlp": "^3.0.0" }, diff --git a/packages/server/package.json b/packages/server/package.json index 1024448b0f..021a3eb9c5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -7,7 +7,6 @@ "author": "Hedera Smart Contracts Team", "dependencies": { "@hashgraph/json-rpc-relay": "file:../relay", - "axios": "^1.4.0", "co-body": "6.2.0", "dotenv": "^16.0.0", "koa": "^2.13.4", @@ -15,14 +14,11 @@ "koa-cors": "^0.0.16", "koa-logger": "^3.2.1", "koa-router": "^13.0.1", - "mocha": "^10.6.0", "pino": "^7.11.0", "pino-pretty": "^7.6.1", - "pnpm": "^8.7.1", "uuid": "^3.3.2" }, "devDependencies": { - "@hashgraph/hedera-local": "^2.29.2", "@hashgraph/sdk": "^2.50.0-beta.3", "@koa/cors": "^5.0.0", "@types/chai": "^4.3.0", @@ -34,10 +30,12 @@ "@types/mocha": "^9.1.0", "@types/node": "^17.0.31", "@types/uuid": "^10.0.0", + "axios": "^1.4.0", "axios-retry": "^3.5.1", "chai": "^4.3.6", "ethers": "^6.7.0", "execution-apis": "git://github.com/ethereum/execution-apis.git#7907424db935b93c2fe6a3c0faab943adebe8557", + "mocha": "^10.6.0", "shelljs": "^0.8.5", "ts-mocha": "^9.0.2", "ts-node": "^10.8.1", diff --git a/packages/ws-server/package.json b/packages/ws-server/package.json index 8acd7fe00c..65b09709ec 100644 --- a/packages/ws-server/package.json +++ b/packages/ws-server/package.json @@ -8,7 +8,6 @@ "dependencies": { "@hashgraph/json-rpc-relay": "file:../relay", "@hashgraph/json-rpc-server": "file:../server", - "axios": "^1.4.0", "co-body": "6.2.0", "dotenv": "^16.0.0", "koa": "^2.13.4", @@ -17,13 +16,10 @@ "koa-logger": "^3.2.1", "koa-router": "^13.0.1", "koa-websocket": "^7.0.0", - "mocha": "^10.1.0", "pino": "^7.11.0", - "pino-pretty": "^7.6.1", - "pnpm": "8.15.9" + "pino-pretty": "^7.6.1" }, "devDependencies": { - "@hashgraph/hedera-local": "^2.29.2", "@hashgraph/sdk": "^2.50.0-beta.3", "@koa/cors": "^5.0.0", "@types/chai": "^4.3.0", @@ -33,9 +29,11 @@ "@types/koa-router": "^7.4.4", "@types/mocha": "^9.1.0", "@types/node": "^17.0.31", + "axios": "^1.4.0", "axios-retry": "^3.5.1", "chai": "^4.3.6", "ethers": "^6.7.0", + "mocha": "^10.1.0", "shelljs": "^0.8.5", "ts-mocha": "^9.0.2", "ts-node": "^10.8.1", @@ -60,4 +58,3 @@ "report-dir": "coverage" } } - From 4cdf8aeae5800f0a087e6e7328d67a4317600440 Mon Sep 17 00:00:00 2001 From: Eric Badiere Date: Mon, 30 Sep 2024 10:04:44 -0600 Subject: [PATCH 06/38] feat: Added configuration option for allocating HBar for a test run. (#3013) * feat: Added configuration option for allocating HBar for a test run. Signed-off-by: ebadiere * Update docs/configuration.md Co-authored-by: Nana Essilfie-Conduah <56320167+Nana-EC@users.noreply.github.com> Signed-off-by: Eric Badiere * fix: Updated env var to reflect the configuration docs. Signed-off-by: ebadiere * feat: Remove privateKey logging. Signed-off-by: ebadiere --------- Signed-off-by: ebadiere Signed-off-by: Eric Badiere Co-authored-by: Nana Essilfie-Conduah <56320167+Nana-EC@users.noreply.github.com> --- docs/configuration.md | 1 + packages/server/tests/acceptance/index.spec.ts | 1 + packages/server/tests/clients/servicesClient.ts | 4 ++-- packages/server/tests/helpers/utils.ts | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c09426086b..8661f1c9eb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -71,6 +71,7 @@ Unless you need to set a non-default value, it is recommended to only populate o | `HAPI_CLIENT_DURATION_RESET` | "3600000" | Time until client reinitialization. (ms) | | `HAPI_CLIENT_ERROR_RESET` | [21, 50] | Array of status codes, which when encountered will trigger a reinitialization. Status codes are availble [here](https://github.com/hashgraph/hedera-protobufs/blob/main/services/response_code.proto). | | `HAPI_CLIENT_TRANSACTION_RESET` | "50" | Number of transaction executions, until client reinitialization. | +| `TEST_INITIAL_ACCOUNT_STARTING_BALANCE` | "2000" | The number of HBars to allocate to the initial account in acceptance test runs. This account is responsible for the gas payment of tests within the suite run session and needs to be adequately funded. | | `LIMIT_DURATION` | "60000" | The maximum duration in ms applied to IP-method based rate limits. | | `MIRROR_NODE_CONTRACT_RESULTS_PG_MAX` | "25" | The maximum number of pages to be requested for contract results from the mirror node. | | `MIRROR_NODE_CONTRACT_RESULTS_LOGS_PG_MAX` | "200" | The maximum number of pages to be requested for contract results logs from the mirror node. (each page will contain a max of 100 results) | diff --git a/packages/server/tests/acceptance/index.spec.ts b/packages/server/tests/acceptance/index.spec.ts index 861943f26b..51984031a6 100644 --- a/packages/server/tests/acceptance/index.spec.ts +++ b/packages/server/tests/acceptance/index.spec.ts @@ -131,6 +131,7 @@ describe('RPC Server Acceptance Tests', function () { RELAY_URL, CHAIN_ID, Utils.generateRequestId(), + Number(process.env.TEST_INITIAL_ACCOUNT_STARTING_BALANCE || 2000), ); global.accounts = new Array(initialAccount); diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index 6d56d983fa..11f7a53993 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -99,7 +99,7 @@ export default class ServicesClient { const address = wallet.address; this.logger.trace( - `${requestIdPrefix} Create new Eth compatible account w privateKey: ${privateKey}, alias: ${address} and balance ~${initialBalance} HBar`, + `${requestIdPrefix} Create new Eth compatible account w alias: ${address} and balance ~${initialBalance} HBar`, ); const aliasCreationResponse = await this.executeTransaction( @@ -424,7 +424,7 @@ export default class ServicesClient { const aliasAccountId = publicKey.toAccountId(0, 0); this.logger.trace( - `${requestIdPrefix} Create new Eth compatible account w privateKey: ${privateKey}, alias: ${aliasAccountId.toString()} and balance ~${initialBalance} hb`, + `${requestIdPrefix} Create new Eth compatible account w alias: ${aliasAccountId.toString()} and balance ~${initialBalance} hb`, ); const aliasCreationResponse = await this.executeTransaction( diff --git a/packages/server/tests/helpers/utils.ts b/packages/server/tests/helpers/utils.ts index 9b20e0f9ce..2dd9c71822 100644 --- a/packages/server/tests/helpers/utils.ts +++ b/packages/server/tests/helpers/utils.ts @@ -313,7 +313,7 @@ export class Utils { for (let i = 0; i < neededAccounts; i++) { const account = await Utils.createAliasAccount(mirrorNode, initialAccount, requestId, initialAmountInTinyBar); global.logger.trace( - `${requestIdPrefix} Create new Eth compatible account w privateKey: ${account.privateKey}, alias: ${account.address} and balance ~${initialAmountInTinyBar} wei`, + `${requestIdPrefix} Create new Eth compatible account w alias: ${account.address} and balance ~${initialAmountInTinyBar} wei`, ); accounts.push(account); } From 4377a60b453e78c28069f42ce5627c950be02f4f Mon Sep 17 00:00:00 2001 From: Swirlds Automation <52682028+swirlds-automation@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:22:45 -0500 Subject: [PATCH 07/38] build: Bump versions for v0.58.0-SNAPSHOT (#3046) Bump versions for v0.58.0-SNAPSHOT Signed-off-by: Swirlds Automation --- charts/hedera-json-rpc-relay-websocket/Chart.yaml | 4 ++-- charts/hedera-json-rpc-relay/Chart.yaml | 4 ++-- charts/hedera-json-rpc/Chart.yaml | 4 ++-- docs/openrpc.json | 2 +- package-lock.json | 6 +++--- packages/relay/package.json | 2 +- packages/server/package.json | 2 +- packages/ws-server/package.json | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/charts/hedera-json-rpc-relay-websocket/Chart.yaml b/charts/hedera-json-rpc-relay-websocket/Chart.yaml index 2a499dd679..f6fca3b479 100644 --- a/charts/hedera-json-rpc-relay-websocket/Chart.yaml +++ b/charts/hedera-json-rpc-relay-websocket/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: "0.57.0-SNAPSHOT" +appVersion: "0.58.0-SNAPSHOT" description: Helm chart deployment of the hashgraph/hedera-json-rpc-relay web socket server home: https://github.com/hashgraph/hedera-json-rpc-relay icon: https://camo.githubusercontent.com/cca6b767847bb8ca5c7059481ba13a5fc81c5938/68747470733a2f2f7777772e6865646572612e636f6d2f6c6f676f2d6361706974616c2d686261722d776f72646d61726b2e6a7067 @@ -22,4 +22,4 @@ name: hedera-json-rpc-relay-websocket sources: - https://github.com/hashgraph/hedera-json-rpc-relay type: application -version: 0.57.0-SNAPSHOT +version: 0.58.0-SNAPSHOT diff --git a/charts/hedera-json-rpc-relay/Chart.yaml b/charts/hedera-json-rpc-relay/Chart.yaml index 0841a93fe0..589610668b 100644 --- a/charts/hedera-json-rpc-relay/Chart.yaml +++ b/charts/hedera-json-rpc-relay/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: "0.57.0-SNAPSHOT" +appVersion: "0.58.0-SNAPSHOT" description: Helm chart deployment of the hashgraph/hedera-json-rpc-relay home: https://github.com/hashgraph/hedera-json-rpc-relay icon: https://camo.githubusercontent.com/cca6b767847bb8ca5c7059481ba13a5fc81c5938/68747470733a2f2f7777772e6865646572612e636f6d2f6c6f676f2d6361706974616c2d686261722d776f72646d61726b2e6a7067 @@ -21,4 +21,4 @@ name: hedera-json-rpc-relay sources: - https://github.com/hashgraph/hedera-json-rpc-relay type: application -version: 0.57.0-SNAPSHOT +version: 0.58.0-SNAPSHOT diff --git a/charts/hedera-json-rpc/Chart.yaml b/charts/hedera-json-rpc/Chart.yaml index 6c0db5b7d3..aacac5f9f1 100644 --- a/charts/hedera-json-rpc/Chart.yaml +++ b/charts/hedera-json-rpc/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: "0.57.0-SNAPSHOT" +appVersion: "0.58.0-SNAPSHOT" dependencies: - alias: relay condition: relay.enabled @@ -31,4 +31,4 @@ name: hedera-json-rpc sources: - https://github.com/hashgraph/hedera-json-rpc-relay type: application -version: 0.57.0-SNAPSHOT +version: 0.58.0-SNAPSHOT diff --git a/docs/openrpc.json b/docs/openrpc.json index 5c5700573d..98144f7bb3 100644 --- a/docs/openrpc.json +++ b/docs/openrpc.json @@ -3,7 +3,7 @@ "info": { "title": "Hedera JSON-RPC Specification", "description": "A specification of the implemented Ethereum JSON RPC APIs interface for Hedera clients and adheres to the Ethereum execution APIs schema.", - "version": "0.57.0-SNAPSHOT" + "version": "0.58.0-SNAPSHOT" }, "servers": [ { diff --git a/package-lock.json b/package-lock.json index e55ce72b9b..3fdf48714f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21097,7 +21097,7 @@ }, "packages/relay": { "name": "@hashgraph/json-rpc-relay", - "version": "0.57.0-SNAPSHOT", + "version": "0.58.0-SNAPSHOT", "dependencies": { "@ethersproject/asm": "^5.7.0", "@hashgraph/sdk": "^2.50.0-beta.3", @@ -21210,7 +21210,7 @@ }, "packages/server": { "name": "@hashgraph/json-rpc-server", - "version": "0.57.0-SNAPSHOT", + "version": "0.58.0-SNAPSHOT", "dependencies": { "@hashgraph/json-rpc-relay": "file:../relay", "co-body": "6.2.0", @@ -21548,7 +21548,7 @@ }, "packages/ws-server": { "name": "@hashgraph/json-rpc-ws-server", - "version": "0.57.0-SNAPSHOT", + "version": "0.58.0-SNAPSHOT", "dependencies": { "@hashgraph/json-rpc-relay": "file:../relay", "@hashgraph/json-rpc-server": "file:../server", diff --git a/packages/relay/package.json b/packages/relay/package.json index 5a5c8d4adb..a750f911d0 100644 --- a/packages/relay/package.json +++ b/packages/relay/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/json-rpc-relay", - "version": "0.57.0-SNAPSHOT", + "version": "0.58.0-SNAPSHOT", "description": "Hedera Hashgraph implementation of Ethereum JSON RPC APIs. Utilises both the Hedera Consensus Nodes and the Mirror Nodes for transaction management and information retrieval", "types": "dist/index.d.ts", "main": "dist/index.js", diff --git a/packages/server/package.json b/packages/server/package.json index 021a3eb9c5..8072450013 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/json-rpc-server", - "version": "0.57.0-SNAPSHOT", + "version": "0.58.0-SNAPSHOT", "description": "Hedera Hashgraph Ethereum JSON RPC server. Accepts requests for Ethereum JSON RPC 2.0 APIs", "main": "dist/index.js", "keywords": [], diff --git a/packages/ws-server/package.json b/packages/ws-server/package.json index 65b09709ec..ce4609163a 100644 --- a/packages/ws-server/package.json +++ b/packages/ws-server/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/json-rpc-ws-server", - "version": "0.57.0-SNAPSHOT", + "version": "0.58.0-SNAPSHOT", "description": "Hedera Hashgraph Ethereum JSON RPC socket server. Accepts socket connections for the purpose of subscribing to real-time data.", "main": "dist/index.js", "keywords": [], From da100624f2e5186fc4b6c6f3a7c5b0a1e1d7d970 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:34:32 -0400 Subject: [PATCH 08/38] build(deps): bump rollup from 2.79.1 to 2.79.2 in /dapp-example (#3037) Bumps [rollup](https://github.com/rollup/rollup) from 2.79.1 to 2.79.2. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.79.1...v2.79.2) --- updated-dependencies: - dependency-name: rollup dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dapp-example/package-lock.json | 107 ++------------------------------- 1 file changed, 6 insertions(+), 101 deletions(-) diff --git a/dapp-example/package-lock.json b/dapp-example/package-lock.json index 55781a3a39..8fdc05ff19 100644 --- a/dapp-example/package-lock.json +++ b/dapp-example/package-lock.json @@ -11799,14 +11799,6 @@ "ms": "2.0.0" } }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -22796,9 +22788,9 @@ } }, "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "bin": { "rollup": "dist/bin/rollup" }, @@ -23293,88 +23285,6 @@ "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -35682,11 +35592,6 @@ "ms": "2.0.0" } }, - "encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -43507,9 +43412,9 @@ } }, "rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "requires": { "fsevents": "~2.3.2" } From 7e2209445659d765b9b83a606dc907d3a3d1ce47 Mon Sep 17 00:00:00 2001 From: Nikolay Atanasow Date: Wed, 2 Oct 2024 09:16:13 +0300 Subject: [PATCH 09/38] feat: add `eth_call`, `eth_getLogs` and `eth_estimateGas` coverage to conformity tests (#2953) * chore: add tests Signed-off-by: nikolay * chore: resolve comments Signed-off-by: nikolay --------- Signed-off-by: nikolay --- .../tests/acceptance/conformityTests.spec.ts | 335 +++++++++++------- .../data/conformity-tests-batch-2.json | 62 ++++ .../data/conformity-tests-batch-3.json | 55 +++ .../data/conformity-tests-batch-4.json | 70 ++++ 4 files changed, 391 insertions(+), 131 deletions(-) create mode 100644 packages/server/tests/acceptance/data/conformity-tests-batch-2.json create mode 100644 packages/server/tests/acceptance/data/conformity-tests-batch-3.json create mode 100644 packages/server/tests/acceptance/data/conformity-tests-batch-4.json diff --git a/packages/server/tests/acceptance/conformityTests.spec.ts b/packages/server/tests/acceptance/conformityTests.spec.ts index 17d12dec9b..11ad52a32c 100644 --- a/packages/server/tests/acceptance/conformityTests.spec.ts +++ b/packages/server/tests/acceptance/conformityTests.spec.ts @@ -440,73 +440,7 @@ describe('@api-conformity @conformity-batch-2 Ethereum execution apis tests', as ).result; }); - const TEST_CASES = { - eth_submitHashrate: { - status: 400, - request: '{"jsonrpc":"2.0","id":1,"method":"eth_submitHashrate"}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32601}}', - }, - eth_sign: { - status: 400, - request: '{"jsonrpc":"2.0","id":1,"method":"eth_sign"}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32601}}', - }, - eth_signTransaction: { - status: 400, - request: '{"jsonrpc":"2.0","id":1,"method":"eth_signTransaction"}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32601}}', - }, - eth_sendTransaction: { - status: 400, - request: '{"jsonrpc":"2.0","id":1,"method":"eth_sendTransaction"}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32601}}', - }, - eth_protocolVersion: { - status: 400, - request: '{"jsonrpc":"2.0","id":1,"method":"eth_protocolVersion"}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32601}}', - }, - eth_newPendingTransactionFilter: { - status: 400, - request: '{"jsonrpc":"2.0","id":1,"method":"eth_newPendingTransactionFilter"}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32601}}', - }, - eth_newBlockFilter: { - request: '{"jsonrpc":"2.0","id":1,"method":"eth_newBlockFilter"}', - response: '{"jsonrpc":"2.0","id":1,"result":"0x33f5c9d2974eea142909a19906ef0548"}', - }, - 'eth_getFilterChanges - existing filter': { - request: - '{"jsonrpc":"2.0","id":1,"method":"eth_getFilterChanges","params":["0xb5c45fa0ece1ff79b115fc7cc490655b"]}', - response: - '{"jsonrpc":"2.0","id":1,"result":["0xc926f266c4a93e01cf6df220b5902b032adb38d70626835d8f53c9f8a648d747dd651af5e9c64201aad121a93c2304c4"]}', - }, - 'eth_getFilterChanges - no existing filter': { - status: 400, - request: - '{"jsonrpc":"2.0","id":1,"method":"eth_getFilterChanges","params":["0x275220eef57cfbc06e932e043535d492"]}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32001}}', - }, - eth_uninstallFilter: { - request: - '{"jsonrpc":"2.0","id":1,"method":"eth_uninstallFilter","params":["0x275220eef57cfbc06e932e043535d492"]}', - response: '{"jsonrpc":"2.0","id":1,"result":false}', - }, - eth_newFilter: { - request: - '{"jsonrpc":"2.0","id":1,"method":"eth_newFilter","params":[{"fromBlock": "0x1","toBlock": "0x160c","address": "0x281723C907113cbdEe8785F3480dD7496315312c"}]}', - response: '{"jsonrpc":"2.0","id":1,"result":"0xd569b8fad2873edebd3033831f790dee"}', - }, - 'eth_getFilterLogs - existing filter': { - request: '{"jsonrpc":"2.0","id":1,"method":"eth_getFilterLogs","params":["0x65d84e9904db339e6a85340b9f7c3d3e"]}', - response: '{"jsonrpc":"2.0","id":1,"result":[]}', - }, - 'eth_getFilterLogs - no existing filter': { - status: 400, - request: '{"jsonrpc":"2.0","id":1,"method":"eth_getFilterLogs","params":["0xb567d26e162027d80fd434e3bc3d6897"]}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32001}}', - }, - }; + const TEST_CASES_BATCH_2 = require('./data/conformity-tests-batch-2.json'); const updateParamIfNeeded = (testName, request) => { switch (testName) { @@ -521,7 +455,7 @@ describe('@api-conformity @conformity-batch-2 Ethereum execution apis tests', as return request; }; - synthesizeTestCases(TEST_CASES, updateParamIfNeeded); + synthesizeTestCases(TEST_CASES_BATCH_2, updateParamIfNeeded); }); describe('@api-conformity @conformity-batch-3 Ethereum execution apis tests', async function () { @@ -533,37 +467,7 @@ describe('@api-conformity @conformity-batch-3 Ethereum execution apis tests', as txHash = (await signAndSendRawTransaction(transaction1559)).transactionHash; }); - const TEST_CASES = { - eth_submitWork: { - request: - '{"jsonrpc":"2.0", "method":"eth_submitWork","params":["0x0000000000000001","0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000"],"id":1}', - response: '{"jsonrpc":"2.0","id":1,"result":false}', - }, - net_listening: { - request: '{"jsonrpc":"2.0","id":1,"method":"net_listening","params":[]}', - response: '{"jsonrpc":"2.0","id":1,"result":"false"}', - }, - net_version: { - request: '{"jsonrpc":"2.0","id":1,"method":"net_version","params":[]}', - response: '{"jsonrpc":"2.0","id":1,"result":"298"}', - }, - web3_clientVersion: { - request: '{"jsonrpc":"2.0","id":1,"method":"web3_clientVersion","params":[]}', - response: '{"jsonrpc":"2.0","id":1,"result":"relay/0.55.0-SNAPSHOT"}', - }, - 'debug_traceTransaction - existing tx': { - request: - '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0x75a7d81c08d33daf327635bd62b7ecaf33c6d3c8cc17d8b19224e7f3e6811cb8",{"tracer":"callTracer","tracerConfig":{"onlyTopCall":true}}],"id":1}', - response: - '{"result":{"type":"CALL","from":"0xc37f417fa09933335240fca72dd257bfbde9c275","to":"0x67d8d32e9bf1a9968a5ff53b87d777aa8ebbee69","value":"0x14","gas":"0x3d090","gasUsed":"0x30d40","input":"0x","output":"0x"},"jsonrpc":"2.0","id":1}', - }, - 'debug_traceTransaction - no existing tx': { - status: 400, - request: - '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0x75a7d81c08d33daf327635bd62b7ecaf33c6d3c8cc17d8b19224e7f3e6811cb8",{"tracer":"callTracer","tracerConfig":{"onlyTopCall":true}}],"id":1}', - response: '{"jsonrpc":"2.0","id":1,"error":{"code":-32001}}', - }, - }; + const TEST_CASES_BATCH_3 = require('./data/conformity-tests-batch-3.json'); const updateParamIfNeeded = (testName, request) => { switch (testName) { @@ -583,7 +487,7 @@ describe('@api-conformity @conformity-batch-3 Ethereum execution apis tests', as return request; }; - synthesizeTestCases(TEST_CASES, updateParamIfNeeded); + synthesizeTestCases(TEST_CASES_BATCH_3['server'], updateParamIfNeeded); describe('ws related rpc methods', async function () { let webSocket: WebSocket; @@ -631,36 +535,6 @@ describe('@api-conformity @conformity-batch-3 Ethereum execution apis tests', as webSocket.close(); }); - const TEST_CASES = { - eth_newFilter: { - request: - '{"jsonrpc":"2.0","id":1,"method":"eth_newFilter","params":[{"fromBlock": "0x2","toBlock": "0x5644","address": "0x68c281b97b214deae198043c15a92e7096ca2546"}]}', - response: '{"result":"0x5cd8adcbc637551d4b5959c732d0ad67","jsonrpc":"2.0","id":1}', - }, - 'eth_subscribe - newPendingTransactions': { - request: '{"jsonrpc":"2.0","method":"eth_subscribe","params":["newPendingTransactions"],"id":1}', - response: '{"error":{"code":-32601,"message":"Unsupported JSON-RPC method"},"jsonrpc":"2.0","id":1}', - }, - 'eth_subscribe - non existing contract': { - request: - '{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["logs",{"address":"0x678d3e4c7b6b8e9617e9b3487352ec63c54dbf81"}]}', - response: '{"error":{"code":-32602},"jsonrpc":"2.0","id":1}', - }, - 'eth_subscribe - existing contract': { - request: - '{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["logs",{"address":"0x12833e4c7a6b1e9512e9a32873321c13cb4dbfef"}]}', - response: '{"result":"0xa4e1803ab025341ed7668eb13ca71f3c","jsonrpc":"2.0","id":1}', - }, - 'eth_unsubscribe - non existing filter': { - request: '{"jsonrpc":"2.0","method":"eth_unsubscribe","params":["0x2c9c38d1200d30208fcdad52ed71fbff"],"id":1}', - response: '{"result":false,"jsonrpc":"2.0","id":1}', - }, - 'eth_unsubscribe - existing filter': { - request: '{"jsonrpc":"2.0","method":"eth_unsubscribe","params":["0x2c9c38d1200d30208fcdad52ed71fbff"],"id":1}', - response: '{"result":true,"jsonrpc":"2.0","id":1}', - }, - }; - const updateParamIfNeeded = (testName, request) => { switch (testName) { case 'eth_subscribe - existing contract': @@ -699,6 +573,205 @@ describe('@api-conformity @conformity-batch-3 Ethereum execution apis tests', as } }; - synthesizeWsTestCases(TEST_CASES, updateParamIfNeeded); + synthesizeWsTestCases(TEST_CASES_BATCH_3['ws-server'], updateParamIfNeeded); + }); +}); + +describe('@api-conformity @conformity-batch-4 Ethereum execution apis tests', async function () { + this.timeout(240 * 1000); + + let existingCallerContractAddress: string; + let existingLogsContractAddress: string; + let fromBlockForLogs: string; + + before(async () => { + const deployCallerContractTx = await signAndSendRawTransaction({ + chainId: 0x12a, + to: null, + from: sendAccountAddress, + maxPriorityFeePerGas: gasPrice, + maxFeePerGas: gasPrice, + gasLimit: gasLimit, + type: 2, + data: CallerContract.bytecode, + }); + + const deployLogsContractTx = await signAndSendRawTransaction({ + chainId: 0x12a, + to: null, + from: sendAccountAddress, + maxPriorityFeePerGas: gasPrice, + maxFeePerGas: gasPrice, + gasLimit: gasLimit, + type: 2, + data: LogsContract.bytecode, + }); + + existingCallerContractAddress = deployCallerContractTx.contractAddress; + existingLogsContractAddress = deployLogsContractTx.contractAddress; + + const log0ContractCall = await signAndSendRawTransaction({ + chainId: 0x12a, + to: existingLogsContractAddress, + from: sendAccountAddress, + maxPriorityFeePerGas: gasPrice, + maxFeePerGas: gasPrice, + gasLimit: gasLimit, + type: 2, + data: '0xd05285d4000000000000000000000000000000000000000000000000000000000000160c', + }); + + fromBlockForLogs = log0ContractCall.blockNumber; }); + + const TEST_CASES_BATCH_4 = require('./data/conformity-tests-batch-4.json'); + + const updateParamIfNeeded = (testName, request) => { + switch (testName) { + case 'eth_call - existing contract view function and existing from': + request.params = [ + { + from: sendAccountAddress, + to: existingCallerContractAddress, + data: '0x0ec1551d', + }, + 'latest', + ]; + break; + case 'eth_call - existing contract tx and existing from': + request.params = [ + { + from: sendAccountAddress, + to: existingCallerContractAddress, + data: '0xddf363d7', + }, + 'latest', + ]; + break; + case 'eth_call - existing contract tx, existing from and positive value': + request.params = [ + { + from: sendAccountAddress, + to: existingCallerContractAddress, + data: '0xddf363d7', + value: '0x2540be400', + }, + 'latest', + ]; + break; + case 'eth_call - existing contract view function and non-existing from': + request.params = [ + { + from: '0x6b175474e89094c44da98b954eedeac495271d0f', + to: existingCallerContractAddress, + data: '0x0ec1551d', + }, + 'latest', + ]; + break; + case 'eth_call - existing contract tx and non-existing from': + request.params = [ + { + from: '0x6b175474e89094c44da98b954eedeac495271d0f', + to: existingCallerContractAddress, + data: '0xddf363d7', + }, + 'latest', + ]; + break; + case 'eth_call - existing contract tx, non-existing from and positive value': + request.params = [ + { + from: '0x6b175474e89094c44da98b954eedeac495271d0f', + to: existingCallerContractAddress, + data: '0xddf363d7', + value: '0x2540be400', + }, + 'latest', + ]; + break; + case 'eth_estimateGas - existing contract view function and existing from': + request.params = [ + { + from: sendAccountAddress, + to: existingCallerContractAddress, + data: '0x0ec1551d', + }, + 'latest', + ]; + break; + case 'eth_estimateGas - existing contract tx and existing from': + request.params = [ + { + from: sendAccountAddress, + to: existingCallerContractAddress, + data: '0xddf363d7', + }, + 'latest', + ]; + break; + case 'eth_estimateGas - existing contract tx, existing from and positive value': + request.params = [ + { + from: sendAccountAddress, + to: existingCallerContractAddress, + data: '0xddf363d7', + value: '0x2540be400', + }, + 'latest', + ]; + break; + case 'eth_estimateGas - existing contract view function and non-existing from': + request.params = [ + { + from: '0x6b175474e89094c44da98b954eedeac495271d0f', + to: existingCallerContractAddress, + data: '0x0ec1551d', + }, + 'latest', + ]; + break; + case 'eth_estimateGas - existing contract tx and non-existing from': + request.params = [ + { + from: '0x6b175474e89094c44da98b954eedeac495271d0f', + to: existingCallerContractAddress, + data: '0xddf363d7', + }, + 'latest', + ]; + break; + case 'eth_estimateGas - existing contract tx, non-existing from and positive value': + request.params = [ + { + from: '0x6b175474e89094c44da98b954eedeac495271d0f', + to: existingCallerContractAddress, + data: '0xddf363d7', + value: '0x2540be400', + }, + 'latest', + ]; + break; + case 'eth_getLogs - existing contract': + request.params = [ + { + address: existingLogsContractAddress, + }, + ]; + break; + case 'eth_getLogs - existing contract and from/to block': + request.params = [ + { + fromBlock: fromBlockForLogs, + toBlock: 'latest', + address: existingLogsContractAddress, + }, + ]; + break; + } + + return request; + }; + + synthesizeTestCases(TEST_CASES_BATCH_4, updateParamIfNeeded); }); diff --git a/packages/server/tests/acceptance/data/conformity-tests-batch-2.json b/packages/server/tests/acceptance/data/conformity-tests-batch-2.json new file mode 100644 index 0000000000..d0ef5c046d --- /dev/null +++ b/packages/server/tests/acceptance/data/conformity-tests-batch-2.json @@ -0,0 +1,62 @@ +{ + "eth_submitHashrate": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_submitHashrate\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601}}" + }, + "eth_sign": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_sign\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601}}" + }, + "eth_signTransaction": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_signTransaction\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601}}" + }, + "eth_sendTransaction": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_sendTransaction\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601}}" + }, + "eth_protocolVersion": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_protocolVersion\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601}}" + }, + "eth_newPendingTransactionFilter": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_newPendingTransactionFilter\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601}}" + }, + "eth_newBlockFilter": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_newBlockFilter\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x33f5c9d2974eea142909a19906ef0548\"}" + }, + "eth_getFilterChanges - existing filter": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getFilterChanges\",\"params\":[\"0xb5c45fa0ece1ff79b115fc7cc490655b\"]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[\"0xc926f266c4a93e01cf6df220b5902b032adb38d70626835d8f53c9f8a648d747dd651af5e9c64201aad121a93c2304c4\"]}" + }, + "eth_getFilterChanges - no existing filter": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getFilterChanges\",\"params\":[\"0x275220eef57cfbc06e932e043535d492\"]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32001}}" + }, + "eth_uninstallFilter": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_uninstallFilter\",\"params\":[\"0x275220eef57cfbc06e932e043535d492\"]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":false}" + }, + "eth_newFilter": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_newFilter\",\"params\":[{\"fromBlock\": \"0x1\",\"toBlock\": \"0x160c\",\"address\": \"0x281723C907113cbdEe8785F3480dD7496315312c\"}]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xd569b8fad2873edebd3033831f790dee\"}" + }, + "eth_getFilterLogs - existing filter": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getFilterLogs\",\"params\":[\"0x65d84e9904db339e6a85340b9f7c3d3e\"]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}" + }, + "eth_getFilterLogs - no existing filter": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getFilterLogs\",\"params\":[\"0xb567d26e162027d80fd434e3bc3d6897\"]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32001}}" + } +} diff --git a/packages/server/tests/acceptance/data/conformity-tests-batch-3.json b/packages/server/tests/acceptance/data/conformity-tests-batch-3.json new file mode 100644 index 0000000000..21c7aed996 --- /dev/null +++ b/packages/server/tests/acceptance/data/conformity-tests-batch-3.json @@ -0,0 +1,55 @@ +{ + "server": { + "eth_submitWork": { + "request": "{\"jsonrpc\":\"2.0\", \"method\":\"eth_submitWork\",\"params\":[\"0x0000000000000001\",\"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\",\"0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000\"],\"id\":1}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":false}" + }, + "net_listening": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"net_listening\",\"params\":[]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"false\"}" + }, + "net_version": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"net_version\",\"params\":[]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"298\"}" + }, + "web3_clientVersion": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"web3_clientVersion\",\"params\":[]}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"relay/0.55.0-SNAPSHOT\"}" + }, + "debug_traceTransaction - existing tx": { + "request": "{\"jsonrpc\":\"2.0\",\"method\":\"debug_traceTransaction\",\"params\":[\"0x75a7d81c08d33daf327635bd62b7ecaf33c6d3c8cc17d8b19224e7f3e6811cb8\",{\"tracer\":\"callTracer\",\"tracerConfig\":{\"onlyTopCall\":true}}],\"id\":1}", + "response": "{\"result\":{\"type\":\"CALL\",\"from\":\"0xc37f417fa09933335240fca72dd257bfbde9c275\",\"to\":\"0x67d8d32e9bf1a9968a5ff53b87d777aa8ebbee69\",\"value\":\"0x14\",\"gas\":\"0x3d090\",\"gasUsed\":\"0x30d40\",\"input\":\"0x\",\"output\":\"0x\"},\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "debug_traceTransaction - no existing tx": { + "status": 400, + "request": "{\"jsonrpc\":\"2.0\",\"method\":\"debug_traceTransaction\",\"params\":[\"0x75a7d81c08d33daf327635bd62b7ecaf33c6d3c8cc17d8b19224e7f3e6811cb8\",{\"tracer\":\"callTracer\",\"tracerConfig\":{\"onlyTopCall\":true}}],\"id\":1}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32001}}" + } + }, + "ws-server": { + "eth_newFilter": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_newFilter\",\"params\":[{\"fromBlock\": \"0x2\",\"toBlock\": \"0x5644\",\"address\": \"0x68c281b97b214deae198043c15a92e7096ca2546\"}]}", + "response": "{\"result\":\"0x5cd8adcbc637551d4b5959c732d0ad67\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_subscribe - newPendingTransactions": { + "request": "{\"jsonrpc\":\"2.0\",\"method\":\"eth_subscribe\",\"params\":[\"newPendingTransactions\"],\"id\":1}", + "response": "{\"error\":{\"code\":-32601,\"message\":\"Unsupported JSON-RPC method\"},\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_subscribe - non existing contract": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_subscribe\",\"params\":[\"logs\",{\"address\":\"0x678d3e4c7b6b8e9617e9b3487352ec63c54dbf81\"}]}", + "response": "{\"error\":{\"code\":-32602},\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_subscribe - existing contract": { + "request": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_subscribe\",\"params\":[\"logs\",{\"address\":\"0x12833e4c7a6b1e9512e9a32873321c13cb4dbfef\"}]}", + "response": "{\"result\":\"0xa4e1803ab025341ed7668eb13ca71f3c\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_unsubscribe - non existing filter": { + "request": "{\"jsonrpc\":\"2.0\",\"method\":\"eth_unsubscribe\",\"params\":[\"0x2c9c38d1200d30208fcdad52ed71fbff\"],\"id\":1}", + "response": "{\"result\":false,\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_unsubscribe - existing filter": { + "request": "{\"jsonrpc\":\"2.0\",\"method\":\"eth_unsubscribe\",\"params\":[\"0x2c9c38d1200d30208fcdad52ed71fbff\"],\"id\":1}", + "response": "{\"result\":true,\"jsonrpc\":\"2.0\",\"id\":1}" + } + } +} diff --git a/packages/server/tests/acceptance/data/conformity-tests-batch-4.json b/packages/server/tests/acceptance/data/conformity-tests-batch-4.json new file mode 100644 index 0000000000..df592db761 --- /dev/null +++ b/packages/server/tests/acceptance/data/conformity-tests-batch-4.json @@ -0,0 +1,70 @@ +{ + "eth_call - non existing contract": { + "request": "{\"method\":\"eth_call\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_call - existing contract view function and existing from": { + "request": "{\"method\":\"eth_call\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_call - existing contract tx and existing from": { + "request": "{\"method\":\"eth_call\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_call - existing contract tx, existing from and positive value": { + "request": "{\"method\":\"eth_call\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\",\"value\":\"0x2540be400\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_call - existing contract view function and non-existing from": { + "request": "{\"method\":\"eth_call\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_call - existing contract tx and non-existing from": { + "request": "{\"method\":\"eth_call\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_call - existing contract tx, non-existing from and positive value": { + "request": "{\"method\":\"eth_call\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\",\"value\":\"0x2540be400\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}" + }, + "eth_estimateGas - non existing contract": { + "request": "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x5adc\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_estimateGas - existing contract view function and existing from": { + "request": "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x5aa7\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_estimateGas - existing contract tx and existing from": { + "request": "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x5ad0\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_estimateGas - existing contract tx, existing from and positive value": { + "request": "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\",\"value\":\"0x2540be400\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x5ad0\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_estimateGas - existing contract view function and non-existing from": { + "request": "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x5ad0\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_estimateGas - existing contract tx and non-existing from": { + "request": "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}" + }, + "eth_estimateGas - existing contract tx, non-existing from and positive value": { + "request": "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"to\":\"0x6b175474e89094c44da98b954eedeac495271d0f\",\"data\":\"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE\",\"value\":\"0x2540be400\"},\"latest\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":\"0x7a120\",\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_getLogs - non-existing contract": { + "request": "{\"method\":\"eth_getLogs\",\"params\":[{\"address\":\"0x6b175474e89094c44da98b954eedeac495271d0f\"}],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":[],\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_getLogs - existing contract": { + "request": "{\"method\":\"eth_getLogs\",\"params\":[{\"address\":\"0x6b175474e89094c44da98b954eedeac495271d0f\"}],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":[],\"jsonrpc\":\"2.0\",\"id\":1}" + }, + "eth_getLogs - existing contract and from/to block": { + "request": "{\"method\":\"eth_getLogs\",\"params\":[{\"fromBlock\":\"0x1\",\"toBlock\":\"latest\",\"address\":\"0x6b175474e89094c44da98b954eedeac495271d0f\"}],\"id\":1,\"jsonrpc\":\"2.0\"}", + "response": "{\"result\":[{\"address\":\"0x7402f907cb2f494acdf7080cffa4f70c939486a1\",\"blockHash\":\"0xd0c2b09c0c60f1e70cbbd7b09df931286b332db705725d2f073e0e46530d5b4d\",\"blockNumber\":\"0x39c\",\"data\":\"0x000000000000000000000000000000000000000000000000000000000000160c\",\"logIndex\":\"0x0\",\"removed\":false,\"topics\":[],\"transactionHash\":\"0xab2529089e736c8c3b6bf69bb2fd32d52bc3497412566d874cc692c5fe08c91d\",\"transactionIndex\":\"0x7\"}],\"jsonrpc\":\"2.0\",\"id\":1}" + } +} From f4a39e30b227482340bbce70c061c309fd8c43b9 Mon Sep 17 00:00:00 2001 From: konstantinabl Date: Thu, 3 Oct 2024 14:32:37 +0300 Subject: [PATCH 10/38] feat: makes ip address of request available in application layer (#2939) * Makes ip address of request available in application layer Signed-off-by: Konstantina Blazhukova * Fixes failing unit tests Signed-off-by: Konstantina Blazhukova * Fixes conflicts Signed-off-by: Konstantina Blazhukova * Adds requestDetails to estimate gas Signed-off-by: Konstantina Blazhukova * Adds request details param to estimate gas unit tests Signed-off-by: Konstantina Blazhukova * Adds request detail to eth_call unit tests Signed-off-by: Konstantina Blazhukova * passes requestDetail wherever needed Signed-off-by: Konstantina Blazhukova * Makes requestIdPrefix required Signed-off-by: Konstantina Blazhukova * Fixes tests Signed-off-by: Konstantina Blazhukova * Fixes unit tests Signed-off-by: Konstantina Blazhukova * Fixes ws batch1 tests Signed-off-by: Konstantina Blazhukova * Makes RequestDetails object available to all methods in the server Signed-off-by: Konstantina Blazhukova * Fixes unit tests after passing requestDetails to all methods in server Signed-off-by: Konstantina Blazhukova * adds request details to ws server Signed-off-by: Konstantina Blazhukova * Adds request details to filter service Signed-off-by: Konstantina Blazhukova * Makes requestId required in all methods of cacheService Signed-off-by: Konstantina Blazhukova * Fixes polling tests Signed-off-by: Konstantina Blazhukova * Removes unecessary 4th parameter in test for getStorageAt Signed-off-by: Konstantina Blazhukova * Improves parameters handling in ws server Signed-off-by: Konstantina Blazhukova * Adds requestId to localLRUCache test Signed-off-by: Konstantina Blazhukova * Fixes redisCache test Signed-off-by: Konstantina Blazhukova * Adds requestDetails to poller with generated request id Signed-off-by: Konstantina Blazhukova * chore: draft changes Signed-off-by: Victor Yanev * chore: draft changes Signed-off-by: Victor Yanev * chore: fix localLRUCache.spec.ts Signed-off-by: Victor Yanev * chore: fix debug.spec.ts Signed-off-by: Victor Yanev * chore: fix cacheService.spec.ts Signed-off-by: Victor Yanev * chore: fix hbarLimiter.spec.ts Signed-off-by: Victor Yanev * chore: fix build Signed-off-by: Victor Yanev * chore: fix ethAddressHbarSpendingPlanRepository.spec.ts and hbarSpendingPlanRepository.spec.ts Signed-off-by: Victor Yanev * Fixes acceptance tests using request details Signed-off-by: Konstantina Blazhukova * Fixes ws-server acceptance tests Signed-off-by: Konstantina Blazhukova * Adds better typing in accpetance tests as well as fixing them Signed-off-by: Konstantina Blazhukova * temporary fix Signed-off-by: Konstantina Blazhukova * fix: build error Signed-off-by: Victor Yanev * fix: ws errors Signed-off-by: Victor Yanev * fix: metricService.spec.ts Signed-off-by: Victor Yanev * fix: mirrorNodeClient Signed-off-by: Victor Yanev * fix: validators.ts Signed-off-by: Victor Yanev * fix: hapiService.spec.ts Signed-off-by: Victor Yanev * fix: precheck.spec.ts and hbarSpendingPlanRepository.spec.ts Signed-off-by: Victor Yanev * fixes hbar calculation in rpc batch1 test Signed-off-by: Konstantina Blazhukova * Fixes precompile test Signed-off-by: Konstantina Blazhukova * Fixes unit tests after adding requestDetails as required Signed-off-by: Konstantina Blazhukova * Fixes failing tests in filter accetance test Signed-off-by: Konstantina Blazhukova * Fixes build after main merge Signed-off-by: Konstantina Blazhukova * merge main and fix tests Signed-off-by: Konstantina Blazhukova * Addresses PR comments Signed-off-by: Konstantina Blazhukova * fix: ws-server Signed-off-by: Victor Yanev * chore: fix copyright comment Signed-off-by: Victor Yanev * chore: remove unused methods in `KoaJsonRpc` class Signed-off-by: Victor Yanev * fix: filter.spec.ts Signed-off-by: Victor Yanev * fix: utils.spec.ts Signed-off-by: Victor Yanev * fix: validations.spec.ts Signed-off-by: Victor Yanev * chore: fix year in copyright Signed-off-by: Victor Yanev * fix: ws-server Signed-off-by: Victor Yanev * fix: index.spec.ts of ws-server Signed-off-by: Victor Yanev * fix: precompileCalls.spec.ts Signed-off-by: Victor Yanev * fix: subscribe.spec.ts Signed-off-by: Victor Yanev * Removes logging of ip address Signed-off-by: Konstantina Blazhukova * Adds JSDoc to RequestDetails class Signed-off-by: Konstantina Blazhukova * Improves logging and fixes corresponding tests Signed-off-by: Konstantina Blazhukova * Update variable name in delete method in ipAddress repository Co-authored-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Signed-off-by: konstantinabl * Improves variable naming in delete method in ipAddress repository Signed-off-by: Konstantina Blazhukova * Improves logging in ipAddress spending plan repository Signed-off-by: Konstantina Blazhukova --------- Signed-off-by: Konstantina Blazhukova Signed-off-by: Victor Yanev Signed-off-by: konstantinabl Co-authored-by: Victor Yanev Co-authored-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> --- docs/design/distributed-cache.md | 26 +- package-lock.json | 14 + package.json | 1 + packages/relay/src/formatters.ts | 2 - packages/relay/src/index.ts | 101 +-- .../src/lib/clients/cache/ICacheClient.ts | 14 +- .../lib/clients/cache/IRedisCacheClient.ts | 13 +- .../src/lib/clients/cache/localLRUCache.ts | 51 +- .../relay/src/lib/clients/cache/redisCache.ts | 77 +- .../relay/src/lib/clients/mirrorNodeClient.ts | 268 +++---- packages/relay/src/lib/clients/sdkClient.ts | 229 +++--- .../ethAddressHbarSpendingPlanRepository.ts | 16 +- .../hbarLimiter/hbarSpendingPlanRepository.ts | 121 ++-- .../ipAddressHbarSpendingPlanRepository.ts | 23 +- .../src/lib/db/types/hbarLimiter/errors.ts | 2 +- packages/relay/src/lib/eth.ts | 681 +++++++++--------- packages/relay/src/lib/hbarlimiter/index.ts | 34 +- packages/relay/src/lib/poller.ts | 27 +- packages/relay/src/lib/precheck.ts | 77 +- packages/relay/src/lib/relay.ts | 9 +- .../lib/services/cacheService/cacheService.ts | 125 ++-- .../services/debugService/IDebugService.ts | 6 +- .../src/lib/services/debugService/index.ts | 61 +- .../ethCommonService/ICommonService.ts | 19 +- .../ethService/ethCommonService/index.ts | 66 +- .../ethFilterService/IFilterService.ts | 13 +- .../ethService/ethFilterService/index.ts | 67 +- .../hbarLimitService/IHbarLimitService.ts | 9 +- .../lib/services/hbarLimitService/index.ts | 141 ++-- .../services/metricService/metricService.ts | 55 +- .../relay/src/lib/subscriptionController.ts | 3 +- .../relay/src/lib/types/RequestDetails.ts | 101 +++ packages/relay/src/lib/types/events.ts | 6 +- packages/relay/src/lib/types/index.ts | 3 + packages/relay/src/utils.ts | 10 + packages/relay/tests/assertions.ts | 25 +- packages/relay/tests/helpers.ts | 4 +- .../tests/lib/clients/localLRUCache.spec.ts | 99 +-- .../tests/lib/clients/redisCache.spec.ts | 137 ++-- packages/relay/tests/lib/eth/eth_call.spec.ts | 234 +++--- .../relay/tests/lib/eth/eth_common.spec.ts | 36 +- .../tests/lib/eth/eth_estimateGas.spec.ts | 240 +++--- .../tests/lib/eth/eth_feeHistory.spec.ts | 52 +- .../relay/tests/lib/eth/eth_gasPrice.spec.ts | 40 +- .../tests/lib/eth/eth_getBalance.spec.ts | 71 +- .../tests/lib/eth/eth_getBlockByHash.spec.ts | 37 +- .../lib/eth/eth_getBlockByNumber.spec.ts | 101 ++- ...eth_getBlockTransactionCountByHash.spec.ts | 18 +- ...h_getBlockTransactionCountByNumber.spec.ts | 35 +- .../relay/tests/lib/eth/eth_getCode.spec.ts | 31 +- .../relay/tests/lib/eth/eth_getLogs.spec.ts | 82 ++- .../tests/lib/eth/eth_getStorageAt.spec.ts | 55 +- ..._getTransactionByBlockHashAndIndex.spec.ts | 45 +- ...etTransactionByBlockNumberAndIndex.spec.ts | 63 +- .../lib/eth/eth_getTransactionByHash.spec.ts | 43 +- .../lib/eth/eth_getTransactionCount.spec.ts | 74 +- .../lib/eth/eth_getTransactionReceipt.spec.ts | 29 +- .../lib/eth/eth_sendRawTransaction.spec.ts | 48 +- packages/relay/tests/lib/hapiService.spec.ts | 4 +- packages/relay/tests/lib/hbarLimiter.spec.ts | 60 +- .../relay/tests/lib/mirrorNodeClient.spec.ts | 292 +++++--- packages/relay/tests/lib/openrpc.spec.ts | 110 +-- packages/relay/tests/lib/precheck.spec.ts | 50 +- ...hAddressHbarSpendingPlanRepository.spec.ts | 26 +- .../hbarSpendingPlanRepository.spec.ts | 149 ++-- ...pAddressHbarSpendingPlanRepository.spec.ts | 25 +- packages/relay/tests/lib/sdkClient.spec.ts | 73 +- .../cacheService/cacheService.spec.ts | 149 ++-- .../lib/services/debugService/debug.spec.ts | 22 +- .../tests/lib/services/eth/filter.spec.ts | 289 +++----- .../hbarLimitService/hbarLimitService.spec.ts | 120 ++- .../metricService/metricService.spec.ts | 40 +- packages/server/src/koaJsonRpc/index.ts | 37 +- .../src/koaJsonRpc/lib/IJsonRpcRequest.ts | 4 +- .../src/koaJsonRpc/lib/IJsonRpcResponse.ts} | 19 +- .../server/src/koaJsonRpc/lib/RpcResponse.ts | 11 +- packages/server/src/server.ts | 182 ++--- .../tests/acceptance/cacheService.spec.ts | 47 +- .../tests/acceptance/equivalence.spec.ts | 11 +- .../server/tests/acceptance/erc20.spec.ts | 14 +- .../acceptance/estimateGasContract.spec.ts | 2 +- .../acceptance/estimateGasPrecompile.spec.ts | 13 +- .../tests/acceptance/hbarLimiter.spec.ts | 66 +- .../htsPrecompile/precompileCalls.spec.ts | 4 +- .../tests/acceptance/htsPrecompile_v1.spec.ts | 36 +- .../server/tests/acceptance/index.spec.ts | 10 +- .../tests/acceptance/rateLimiter.spec.ts | 3 +- .../tests/acceptance/rpc_batch1.spec.ts | 258 ++++--- .../tests/acceptance/rpc_batch2.spec.ts | 50 +- .../tests/acceptance/rpc_batch3.spec.ts | 18 +- .../tests/acceptance/serverConfig.spec.ts | 2 +- packages/server/tests/clients/mirrorClient.ts | 1 + packages/server/tests/clients/relayClient.ts | 14 +- .../server/tests/clients/servicesClient.ts | 248 +++---- packages/server/tests/helpers/utils.ts | 91 ++- .../src/controllers/eth_subscribe.ts | 149 ++-- packages/ws-server/src/controllers/index.ts | 100 ++- packages/ws-server/src/utils/utils.ts | 58 +- packages/ws-server/src/utils/validators.ts | 19 +- packages/ws-server/src/webSocketServer.ts | 59 +- .../ws-server/tests/acceptance/call.spec.ts | 9 +- .../tests/acceptance/estimateGas.spec.ts | 11 +- .../tests/acceptance/getBalance.spec.ts | 11 +- .../tests/acceptance/getLogs.spec.ts | 12 +- .../tests/acceptance/getStorageAt.spec.ts | 8 +- .../acceptance/getTransactionByHash.spec.ts | 20 +- .../acceptance/getTransactionCount.spec.ts | 19 +- .../acceptance/getTransactionReceipt.spec.ts | 19 +- .../ws-server/tests/acceptance/index.spec.ts | 17 +- .../acceptance/sendRawTransaction.spec.ts | 26 +- .../acceptance/subscribeNewHeads.spec.ts | 5 +- packages/ws-server/tests/unit/utils.spec.ts | 16 +- .../ws-server/tests/unit/validations.spec.ts | 16 +- 113 files changed, 4059 insertions(+), 3135 deletions(-) create mode 100644 packages/relay/src/lib/types/RequestDetails.ts rename packages/{ws-server/src/utils/formatters.ts => server/src/koaJsonRpc/lib/IJsonRpcResponse.ts} (57%) diff --git a/docs/design/distributed-cache.md b/docs/design/distributed-cache.md index 0a323695d7..07b51090c5 100644 --- a/docs/design/distributed-cache.md +++ b/docs/design/distributed-cache.md @@ -19,9 +19,9 @@ Important details is that, if an operator does not want to use Redis or it went ```javascript interface ICacheClient { - get(key: string, callingMethod: string, requestIdPrefix?: string): any; - set(key: string, value: any, callingMethod: string, ttl?: number, requestIdPrefix?: string): void; - delete(key: string, callingMethod: string, requestIdPrefix?: string): void; + get(key: string, callingMethod: string, requestDetails: RequestDetails): any; + set(key: string, value: any, callingMethod: string, ttl?: number, requestDetails: RequestDetails): void; + delete(key: string, callingMethod: string, requestDetails: RequestDetails): void; clear(): void; } ``` @@ -40,15 +40,15 @@ class LocalLRUCache implements ICacheClient{ public constructor() { this.cache = new LRU(this.options); } - get(key: string, callingMethod: string, requestIdPrefix?: string) { + get(key: string, callingMethod: string, requestDetails: RequestDetails) { // Get item from internal cache implementation } - set(key: string, value: any, callingMethod: string, ttl?: number, requestIdPrefix?: string) { + set(key: string, value: any, callingMethod: string, requestDetails: RequestDetails, ttl?: number) { // Set item to internal cache implementation } - delete(key: string, callingMethod: string, requestIdPrefix?: string) { + delete(key: string, callingMethod: string, requestDetails: RequestDetails) { // Delete item from internal cache implementation } @@ -81,15 +81,15 @@ class RedisCache implements ICacheClient{ this.cache = client; } - get(key: string, callingMethod: string, requestIdPrefix?: string) { + get(key: string, callingMethod: string, requestDetails: RequestDetails) { // Get item from shared cache implementation } - set(key: string, value: any, callingMethod: string, ttl?: number, requestIdPrefix?: string) { + set(key: string, value: any, callingMethod: string, requestDetails: RequestDetails, ttl?: number) { // Set item to shared cache implementation } - delete(key: string, callingMethod: string, requestIdPrefix?: string) { + delete(key: string, callingMethod: string, requestDetails: RequestDetails) { // Delete item from shared cache implementation } @@ -120,25 +120,25 @@ class CacheClientService{ const sharedCache = new RedisCache(); } - get(key: string, callingMethod: string, requestIdPrefix?: string, shared: boolean = false) { + get(key: string, callingMethod: string, requestDetails: RequestDetails, shared: boolean = false) { // Depending on the shared boolean, this method decide from where it should request the data. // Fallbacks to internalCache in case of error from the shared cache. // Getting from shared cache depends on REDIS_ENABLED env. variable } - set(key: string, value: any, callingMethod: string, ttl?: number, requestIdPrefix?: string, shared: boolean = false) { + set(key: string, value: any, callingMethod: string, requestDetails: RequestDetails, ttl?: number, shared: boolean = false) { // Depending on the shared boolean, this method decide where it should save the data. // Fallbacks to internalCache in case of error from the shared cache. // Setting to shared cache depends on REDIS_ENABLED env. variable } - delete(key: string, callingMethod: string, requestIdPrefix?: string, shared: boolean = false) { + delete(key: string, callingMethod: string, requestDetails: RequestDetails, shared: boolean = false) { // Depending on the shared boolean, this method decide from where it should delete the data. // Fallbacks to internalCache in case of error from the shared cache. // Deleting from shared cache depends on REDIS_ENABLED env. variable } - clear(shared: boolean = false) { + clear(requestDetails: RequestDetails, shared: boolean = false) { // In case of error does NOT fallback to shared cache. // Clearing from shared cache depends on REDIS_ENABLED env. variable } diff --git a/package-lock.json b/package-lock.json index 3fdf48714f..008c850bc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@types/find-config": "^1.0.4", "@types/koa-cors": "^0.0.6", "@types/sinon": "^10.0.20", + "@types/lodash": "^4.17.7", "@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/parser": "^6.5.0", "ajv": "^8.16.0", @@ -5243,6 +5244,13 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -26318,6 +26326,12 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", + "dev": true + }, "@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", diff --git a/package.json b/package.json index 59017208c4..8e7a08ec58 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@types/find-config": "^1.0.4", "@types/koa-cors": "^0.0.6", "@types/sinon": "^10.0.20", + "@types/lodash": "^4.17.7", "@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/parser": "^6.5.0", "ajv": "^8.16.0", diff --git a/packages/relay/src/formatters.ts b/packages/relay/src/formatters.ts index 872787b4e5..3087c9fe5d 100644 --- a/packages/relay/src/formatters.ts +++ b/packages/relay/src/formatters.ts @@ -21,9 +21,7 @@ import crypto from 'crypto'; import constants from './lib/constants'; import { BigNumber as BN } from 'bignumber.js'; -import { TransactionRecord } from '@hashgraph/sdk'; import { BigNumber } from '@hashgraph/sdk/lib/Transfer'; -import { MirrorNodeTransactionRecord } from './lib/types/mirrorNode'; import { Transaction, Transaction1559, Transaction2930 } from './lib/model'; const EMPTY_HEX = '0x'; diff --git a/packages/relay/src/index.ts b/packages/relay/src/index.ts index 3c95d53ce5..1e16a7db9f 100644 --- a/packages/relay/src/index.ts +++ b/packages/relay/src/index.ts @@ -19,7 +19,7 @@ */ import { Block, Log, Receipt, Transaction } from './lib/model'; -import { IContractCallRequest } from './lib/types'; +import { IContractCallRequest, RequestDetails } from './lib/types'; import { JsonRpcError, predefined } from './lib/errors/JsonRpcError'; import WebSocketError from './lib/errors/WebSocketError'; import { MirrorNodeClientError } from './lib/errors/MirrorNodeClientError'; @@ -62,33 +62,33 @@ export interface Net { } export interface Eth { - blockNumber(requestId?: string): Promise; + blockNumber(requestDetails: RequestDetails): Promise; - call(call: any, blockParam: string | object | null, requestId?: string): Promise; + call(call: any, blockParam: string | object | null, requestDetails: RequestDetails): Promise; - coinbase(requestId?: string): JsonRpcError; + coinbase(requestDetails: RequestDetails): JsonRpcError; estimateGas( transaction: IContractCallRequest, blockParam: string | null, - requestId?: string, + requestDetails: RequestDetails, ): Promise; - gasPrice(requestId?: string): Promise; + gasPrice(requestDetails: RequestDetails): Promise; - getBalance(account: string, blockNumber: string | null, requestId?: string): Promise; + getBalance(account: string, blockNumber: string | null, requestDetails: RequestDetails): Promise; - getBlockByHash(hash: string, showDetails: boolean, requestId?: string): Promise; + getBlockByHash(hash: string, showDetails: boolean, requestDetails: RequestDetails): Promise; - getBlockByNumber(blockNum: string, showDetails: boolean, requestId?: string): Promise; + getBlockByNumber(blockNum: string, showDetails: boolean, requestDetails: RequestDetails): Promise; - getBlockTransactionCountByHash(hash: string, requestId?: string): Promise; + getBlockTransactionCountByHash(hash: string, requestDetails: RequestDetails): Promise; - getBlockTransactionCountByNumber(blockNum: string, requestId?: string): Promise; + getBlockTransactionCountByNumber(blockNum: string, requestDetails: RequestDetails): Promise; - getCode(address: string, blockNumber: string | null, requestId?: string): Promise; + getCode(address: string, blockNumber: string | null, requestDetails: RequestDetails): Promise; - chainId(requestId?: string): string; + chainId(requestDetails: RequestDetails): string; getLogs( blockHash: string | null, @@ -96,61 +96,78 @@ export interface Eth { toBlock: string | null, address: string | string[] | null, topics: any[] | null, - requestId?: string, + requestDetails: RequestDetails, ): Promise; - getStorageAt(address: string, slot: string, blockNumber: string | null, requestId?: string): Promise; - - getTransactionByBlockHashAndIndex(hash: string, index: string, requestId?: string): Promise; - - getTransactionByBlockNumberAndIndex(blockNum: string, index: string, requestId?: string): Promise; - - getTransactionByHash(hash: string, requestId?: string): Promise; - - getTransactionCount(address: string, blockNum: string, requestId?: string): Promise; + getStorageAt( + address: string, + slot: string, + requestDetails: RequestDetails, + blockNumber: string | null, + ): Promise; + + getTransactionByBlockHashAndIndex( + hash: string, + index: string, + requestDetails: RequestDetails, + ): Promise; + + getTransactionByBlockNumberAndIndex( + blockNum: string, + index: string, + requestDetails: RequestDetails, + ): Promise; + + getTransactionByHash(hash: string, requestDetails: RequestDetails): Promise; + + getTransactionCount( + address: string, + blockNum: string, + requestDetails: RequestDetails, + ): Promise; - getTransactionReceipt(hash: string, requestId?: string): Promise; + getTransactionReceipt(hash: string, requestDetails: RequestDetails): Promise; - getUncleByBlockHashAndIndex(requestId?: string): Promise; + getUncleByBlockHashAndIndex(requestDetails: RequestDetails): Promise; - getUncleByBlockNumberAndIndex(requestId?: string): Promise; + getUncleByBlockNumberAndIndex(requestDetails: RequestDetails): Promise; - getUncleCountByBlockHash(requestId?: string): Promise; + getUncleCountByBlockHash(requestDetails: RequestDetails): Promise; - getUncleCountByBlockNumber(requestId?: string): Promise; + getUncleCountByBlockNumber(requestDetails: RequestDetails): Promise; - getWork(requestId?: string): JsonRpcError; + getWork(requestDetails: RequestDetails): JsonRpcError; feeHistory( blockCount: number, newestBlock: string, rewardPercentiles: Array | null, - requestId?: string, + requestDetails: RequestDetails, ): Promise; - hashrate(requestId?: string): Promise; + hashrate(requestDetails: RequestDetails): Promise; - maxPriorityFeePerGas(requestId?: string): Promise; + maxPriorityFeePerGas(requestDetails: RequestDetails): Promise; - mining(requestId?: string): Promise; + mining(requestDetails: RequestDetails): Promise; - protocolVersion(requestId?: string): JsonRpcError; + protocolVersion(requestDetails: RequestDetails): JsonRpcError; - sendRawTransaction(transaction: string, requestId: string): Promise; + sendRawTransaction(transaction: string, requestDetails: RequestDetails): Promise; - sendTransaction(requestId?: string): JsonRpcError; + sendTransaction(requestDetails: RequestDetails): JsonRpcError; - sign(requestId?: string): JsonRpcError; + sign(requestDetails: RequestDetails): JsonRpcError; - signTransaction(requestId?: string): JsonRpcError; + signTransaction(requestDetails: RequestDetails): JsonRpcError; - submitHashrate(requestId?: string): JsonRpcError; + submitHashrate(requestDetails: RequestDetails): JsonRpcError; - submitWork(requestId?: string): Promise; + submitWork(requestDetails: RequestDetails): Promise; - syncing(requestId?: string): Promise; + syncing(requestDetails: RequestDetails): Promise; - accounts(requestId?: string): Array; + accounts(requestDetails: RequestDetails): Array; filterService(): IFilterService; diff --git a/packages/relay/src/lib/clients/cache/ICacheClient.ts b/packages/relay/src/lib/clients/cache/ICacheClient.ts index b0a2241a17..4ca11b1fa0 100644 --- a/packages/relay/src/lib/clients/cache/ICacheClient.ts +++ b/packages/relay/src/lib/clients/cache/ICacheClient.ts @@ -18,17 +18,19 @@ * */ +import { RequestDetails } from '../../types'; + export interface ICacheClient { - keys(pattern: string, callingMethod: string, requestIdPrefix?: string): Promise; - get(key: string, callingMethod: string, requestIdPrefix?: string): Promise; - set(key: string, value: any, callingMethod: string, ttl?: number, requestIdPrefix?: string): Promise; - multiSet(keyValuePairs: Record, callingMethod: string, requestIdPrefix?: string): Promise; + keys(pattern: string, callingMethod: string, requestDetails: RequestDetails): Promise; + get(key: string, callingMethod: string, requestDetails: RequestDetails): Promise; + set(key: string, value: any, callingMethod: string, requestDetails: RequestDetails, ttl?: number): Promise; + multiSet(keyValuePairs: Record, callingMethod: string, requestDetails: RequestDetails): Promise; pipelineSet( keyValuePairs: Record, callingMethod: string, + requestDetails: RequestDetails, ttl?: number | undefined, - requestIdPrefix?: string, ): Promise; - delete(key: string, callingMethod: string, requestIdPrefix?: string): Promise; + delete(key: string, callingMethod: string, requestDetails: RequestDetails): Promise; clear(): Promise; } diff --git a/packages/relay/src/lib/clients/cache/IRedisCacheClient.ts b/packages/relay/src/lib/clients/cache/IRedisCacheClient.ts index 704c174335..944b02c60b 100644 --- a/packages/relay/src/lib/clients/cache/IRedisCacheClient.ts +++ b/packages/relay/src/lib/clients/cache/IRedisCacheClient.ts @@ -19,10 +19,17 @@ */ import type { ICacheClient } from './ICacheClient'; +import { RequestDetails } from '../../types'; export interface IRedisCacheClient extends ICacheClient { disconnect: () => Promise; - incrBy(key: string, amount: number, callingMethod: string, requestIdPrefix?: string): Promise; - rPush(key: string, value: any, callingMethod: string, requestIdPrefix?: string): Promise; - lRange(key: string, start: number, end: number, callingMethod: string, requestIdPrefix?: string): Promise; + incrBy(key: string, amount: number, callingMethod: string, requestDetails: RequestDetails): Promise; + rPush(key: string, value: any, callingMethod: string, requestDetails: RequestDetails): Promise; + lRange( + key: string, + start: number, + end: number, + callingMethod: string, + requestDetails: RequestDetails, + ): Promise; } diff --git a/packages/relay/src/lib/clients/cache/localLRUCache.ts b/packages/relay/src/lib/clients/cache/localLRUCache.ts index 0280e0319d..c366b8897d 100644 --- a/packages/relay/src/lib/clients/cache/localLRUCache.ts +++ b/packages/relay/src/lib/clients/cache/localLRUCache.ts @@ -23,6 +23,7 @@ import { Gauge, Registry } from 'prom-client'; import { ICacheClient } from './ICacheClient'; import constants from '../../constants'; import LRUCache, { LimitedByCount, LimitedByTTL } from 'lru-cache'; +import { RequestDetails } from '../../types'; /** * Represents a LocalLRUCache instance that uses an LRU (Least Recently Used) caching strategy @@ -98,14 +99,16 @@ export class LocalLRUCache implements ICacheClient { * If the value exists in the cache, updates metrics and logs the retrieval. * @param {string} key - The key associated with the cached value. * @param {string} callingMethod - The name of the method calling the cache. - * @param {string} requestIdPrefix - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {*} The cached value if found, otherwise null. */ - public async get(key: string, callingMethod: string, requestIdPrefix?: string): Promise { + public async get(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { const value = this.cache.get(key); if (value !== undefined) { this.logger.trace( - `${requestIdPrefix} returning cached value ${key}:${JSON.stringify(value)} on ${callingMethod} call`, + `${requestDetails.formattedRequestId} returning cached value ${key}:${JSON.stringify( + value, + )} on ${callingMethod} call`, ); return value; } @@ -117,12 +120,14 @@ export class LocalLRUCache implements ICacheClient { * The remaining TTL of the specified key in the cache. * @param {string} key - The key to check the remaining TTL for. * @param {string} callingMethod - The name of the method calling the cache. - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The remaining TTL in milliseconds. */ - public async getRemainingTtl(key: string, callingMethod: string, requestIdPrefix?: string): Promise { + public async getRemainingTtl(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { const remainingTtl = this.cache.getRemainingTTL(key); // in milliseconds - this.logger.trace(`${requestIdPrefix} returning remaining TTL ${key}:${remainingTtl} on ${callingMethod} call`); + this.logger.trace( + `${requestDetails.formattedRequestId} returning remaining TTL ${key}:${remainingTtl} on ${callingMethod} call`, + ); return remainingTtl; } @@ -133,17 +138,19 @@ export class LocalLRUCache implements ICacheClient { * @param {*} value - The value to cache. * @param {string} callingMethod - The name of the method calling the cache. * @param {number} ttl - Time to live for the cached value in milliseconds (optional). - * @param {string} requestIdPrefix - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ public async set( key: string, value: any, callingMethod: string, + requestDetails: RequestDetails, ttl?: number, - requestIdPrefix?: string, ): Promise { const resolvedTtl = ttl ?? this.options.ttl; - this.logger.trace(`${requestIdPrefix} caching ${key}:${JSON.stringify(value)} for ${resolvedTtl} ms`); + this.logger.trace( + `${requestDetails.formattedRequestId} caching ${key}:${JSON.stringify(value)} for ${resolvedTtl} ms`, + ); this.cache.set(key, value, { ttl: resolvedTtl }); } @@ -152,17 +159,17 @@ export class LocalLRUCache implements ICacheClient { * * @param keyValuePairs - An object where each property is a key and its value is the value to be cached. * @param callingMethod - The name of the calling method. - * @param requestIdPrefix - Optional request ID prefix for logging. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves when the values are cached. */ public async multiSet( keyValuePairs: Record, callingMethod: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { // Iterate over each entry in the keyValuePairs object for (const [key, value] of Object.entries(keyValuePairs)) { - await this.set(key, value, callingMethod, undefined, requestIdPrefix); + await this.set(key, value, callingMethod, requestDetails); } } @@ -172,18 +179,18 @@ export class LocalLRUCache implements ICacheClient { * @param keyValuePairs - An object where each property is a key and its value is the value to be cached. * @param callingMethod - The name of the calling method. * @param ttl - Time to live on the set values - * @param requestIdPrefix - Optional request ID prefix for logging. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {void} A Promise that resolves when the values are cached. */ public async pipelineSet( keyValuePairs: Record, callingMethod: string, + requestDetails: RequestDetails, ttl?: number, - requestIdPrefix?: string, ): Promise { // Iterate over each entry in the keyValuePairs object for (const [key, value] of Object.entries(keyValuePairs)) { - await this.set(key, value, callingMethod, ttl, requestIdPrefix); + await this.set(key, value, callingMethod, requestDetails, ttl); } } @@ -192,10 +199,10 @@ export class LocalLRUCache implements ICacheClient { * Logs the deletion of the cache entry. * @param {string} key - The key associated with the cached value to delete. * @param {string} callingMethod - The name of the method calling the cache. - * @param {string} requestIdPrefix - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - public async delete(key: string, callingMethod: string, requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} delete cache for ${key}`); + public async delete(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} delete cache for ${key}`); this.cache.delete(key); } @@ -219,10 +226,10 @@ export class LocalLRUCache implements ICacheClient { * Retrieves all keys in the cache that match the given pattern. * @param {string} pattern - The pattern to match keys against. * @param {string} callingMethod - The name of the method calling the cache. - * @param {string} requestIdPrefix - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} An array of keys that match the pattern. */ - public async keys(pattern: string, callingMethod: string, requestIdPrefix?: string): Promise { + public async keys(pattern: string, callingMethod: string, requestDetails: RequestDetails): Promise { const keys = Array.from(this.cache.rkeys()); // Replace escaped special characters with placeholders @@ -251,7 +258,9 @@ export class LocalLRUCache implements ICacheClient { const matchingKeys = keys.filter((key) => regex.test(key)); - this.logger.trace(`${requestIdPrefix} retrieving keys matching ${pattern} on ${callingMethod} call`); + this.logger.trace( + `${requestDetails.formattedRequestId} retrieving keys matching ${pattern} on ${callingMethod} call`, + ); return matchingKeys; } } diff --git a/packages/relay/src/lib/clients/cache/redisCache.ts b/packages/relay/src/lib/clients/cache/redisCache.ts index a2ed6867b5..2c9263d239 100644 --- a/packages/relay/src/lib/clients/cache/redisCache.ts +++ b/packages/relay/src/lib/clients/cache/redisCache.ts @@ -25,6 +25,7 @@ import { Registry } from 'prom-client'; import { RedisCacheError } from '../../errors/RedisCacheError'; import constants from '../../constants'; import { IRedisCacheClient } from './IRedisCacheClient'; +import { RequestDetails } from '../../types'; /** * A class that provides caching functionality using Redis. @@ -125,15 +126,17 @@ export class RedisCache implements IRedisCacheClient { * * @param {string} key - The cache key. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - The optional request ID prefix. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The cached value or null if not found. */ - async get(key: string, callingMethod: string, requestIdPrefix?: string | undefined): Promise { + async get(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { const client = await this.getConnectedClient(); const result = await client.get(key); if (result) { this.logger.trace( - `${requestIdPrefix} returning cached value ${key}:${JSON.stringify(result)} on ${callingMethod} call`, + `${requestDetails.formattedRequestId} returning cached value ${key}:${JSON.stringify( + result, + )} on ${callingMethod} call`, ); // TODO: add metrics return JSON.parse(result); @@ -148,22 +151,24 @@ export class RedisCache implements IRedisCacheClient { * @param {*} value - The value to be cached. * @param {string} callingMethod - The name of the calling method. * @param {number} [ttl] - The time-to-live (expiration) of the cache item in milliseconds. - * @param {string} [requestIdPrefix] - The optional request ID prefix. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves when the value is cached. */ async set( key: string, value: any, callingMethod: string, - ttl?: number | undefined, - requestIdPrefix?: string | undefined, + requestDetails: RequestDetails, + ttl?: number, ): Promise { const client = await this.getConnectedClient(); const serializedValue = JSON.stringify(value); const resolvedTtl = ttl ?? this.options.ttl; // in milliseconds await client.set(key, serializedValue, { PX: resolvedTtl }); - this.logger.trace(`${requestIdPrefix} caching ${key}: ${serializedValue} on ${callingMethod} for ${resolvedTtl} s`); + this.logger.trace( + `${requestDetails.formattedRequestId} caching ${key}: ${serializedValue} on ${callingMethod} for ${resolvedTtl} s`, + ); // TODO: add metrics } @@ -172,10 +177,14 @@ export class RedisCache implements IRedisCacheClient { * * @param {Record} keyValuePairs - An object where each property is a key and its value is the value to be cached. * @param {string} callingMethod - The name of the calling method. - * @param {string} requestIdPrefix - Optional request ID prefix for logging. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves when the values are cached. */ - async multiSet(keyValuePairs: Record, callingMethod: string, requestIdPrefix?: string): Promise { + async multiSet( + keyValuePairs: Record, + callingMethod: string, + requestDetails: RequestDetails, + ): Promise { const client = await this.getConnectedClient(); // Serialize values const serializedKeyValuePairs: Record = {}; @@ -192,7 +201,9 @@ export class RedisCache implements IRedisCacheClient { // Log the operation const entriesLength = Object.keys(keyValuePairs).length; - this.logger.trace(`${requestIdPrefix} caching multiple keys via ${callingMethod}, total keys: ${entriesLength}`); + this.logger.trace( + `${requestDetails.formattedRequestId} caching multiple keys via ${callingMethod}, total keys: ${entriesLength}`, + ); } /** @@ -201,14 +212,14 @@ export class RedisCache implements IRedisCacheClient { * @param {Record} keyValuePairs - An object where each property is a key and its value is the value to be cached. * @param {string} callingMethod - The name of the calling method. * @param {number} [ttl] - The time-to-live (expiration) of the cache item in milliseconds. - * @param {string} requestIdPrefix - Optional request ID prefix for logging. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves when the values are cached. */ async pipelineSet( keyValuePairs: Record, callingMethod: string, - ttl?: number | undefined, - requestIdPrefix?: string, + requestDetails: RequestDetails, + ttl?: number, ): Promise { const client = await this.getConnectedClient(); const resolvedTtl = ttl ?? this.options.ttl; // in milliseconds @@ -225,7 +236,9 @@ export class RedisCache implements IRedisCacheClient { // Log the operation const entriesLength = Object.keys(keyValuePairs).length; - this.logger.trace(`${requestIdPrefix} caching multiple keys via ${callingMethod}, total keys: ${entriesLength}`); + this.logger.trace( + `${requestDetails.formattedRequestId} caching multiple keys via ${callingMethod}, total keys: ${entriesLength}`, + ); } /** @@ -233,13 +246,13 @@ export class RedisCache implements IRedisCacheClient { * * @param {string} key - The cache key. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - The optional request ID prefix. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves when the value is deleted from the cache. */ - async delete(key: string, callingMethod: string, requestIdPrefix?: string | undefined): Promise { + async delete(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { const client = await this.getConnectedClient(); await client.del(key); - this.logger.trace(`${requestIdPrefix} delete cache for ${key} on ${callingMethod} call`); + this.logger.trace(`${requestDetails.formattedRequestId} delete cache for ${key} on ${callingMethod} call`); // TODO: add metrics } @@ -301,13 +314,13 @@ export class RedisCache implements IRedisCacheClient { * @param {string} key The key to increment * @param {number} amount The amount to increment by * @param {string} callingMethod The name of the calling method - * @param {string} [requestIdPrefix] The optional request ID prefix + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The value of the key after incrementing */ - async incrBy(key: string, amount: number, callingMethod: string, requestIdPrefix?: string): Promise { + async incrBy(key: string, amount: number, callingMethod: string, requestDetails: RequestDetails): Promise { const client = await this.getConnectedClient(); const result = await client.incrBy(key, amount); - this.logger.trace(`${requestIdPrefix} incrementing ${key} by ${amount} on ${callingMethod} call`); + this.logger.trace(`${requestDetails.formattedRequestId} incrementing ${key} by ${amount} on ${callingMethod} call`); return result; } @@ -318,7 +331,7 @@ export class RedisCache implements IRedisCacheClient { * @param {number} start The start index * @param {number} end The end index * @param {string} callingMethod The name of the calling method - * @param {string} [requestIdPrefix] The optional request ID prefix + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The list of elements in the range */ async lRange( @@ -326,11 +339,13 @@ export class RedisCache implements IRedisCacheClient { start: number, end: number, callingMethod: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { const client = await this.getConnectedClient(); const result = await client.lRange(key, start, end); - this.logger.trace(`${requestIdPrefix} retrieving range [${start}:${end}] from ${key} on ${callingMethod} call`); + this.logger.trace( + `${requestDetails.formattedRequestId} retrieving range [${start}:${end}] from ${key} on ${callingMethod} call`, + ); return result.map((item) => JSON.parse(item)); } @@ -340,14 +355,16 @@ export class RedisCache implements IRedisCacheClient { * @param {string} key The key of the list * @param {*} value The value to push * @param {string} callingMethod The name of the calling method - * @param {string} [requestIdPrefix] The optional request ID prefix + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The length of the list after pushing */ - async rPush(key: string, value: any, callingMethod: string, requestIdPrefix?: string): Promise { + async rPush(key: string, value: any, callingMethod: string, requestDetails: RequestDetails): Promise { const client = await this.getConnectedClient(); const serializedValue = JSON.stringify(value); const result = await client.rPush(key, serializedValue); - this.logger.trace(`${requestIdPrefix} pushing ${serializedValue} to ${key} on ${callingMethod} call`); + this.logger.trace( + `${requestDetails.formattedRequestId} pushing ${serializedValue} to ${key} on ${callingMethod} call`, + ); return result; } @@ -355,13 +372,15 @@ export class RedisCache implements IRedisCacheClient { * Retrieves all keys matching a pattern. * @param {string} pattern The pattern to match * @param {string} callingMethod The name of the calling method - * @param {string} [requestIdPrefix] The optional request ID prefix + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The list of keys matching the pattern */ - async keys(pattern: string, callingMethod: string, requestIdPrefix?: string): Promise { + async keys(pattern: string, callingMethod: string, requestDetails: RequestDetails): Promise { const client = await this.getConnectedClient(); const result = await client.keys(pattern); - this.logger.trace(`${requestIdPrefix} retrieving keys matching ${pattern} on ${callingMethod} call`); + this.logger.trace( + `${requestDetails.formattedRequestId} retrieving keys matching ${pattern} on ${callingMethod} call`, + ); return result; } } diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index 244347b2b5..fdef1c5e9a 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -42,6 +42,7 @@ import { IContractLogsResultsParams, MirrorNodeTransactionRecord, IMirrorNodeTransactionRecord, + RequestDetails, } from '../types'; type REQUEST_METHODS = 'GET' | 'POST'; @@ -121,9 +122,6 @@ export class MirrorNodeClient { private static readonly FORWARD_SLASH = '/'; private static readonly HTTPS_PREFIX = 'https://'; private static readonly API_V1_POST_FIX = 'api/v1/'; - private static readonly EMPTY_STRING = ''; - private static readonly REQUEST_PREFIX_SEPARATOR = ': '; - private static readonly REQUEST_PREFIX_TRAILING_BRACKET = ']'; private static readonly HTTP_GET = 'GET'; private static readonly REQUESTID_LABEL = 'requestId'; @@ -166,7 +164,7 @@ export class MirrorNodeClient { // defualt values for axios clients to mirror node const mirrorNodeTimeout = parseInt(process.env.MIRROR_NODE_TIMEOUT || '10000'); const mirrorNodeMaxRedirects = parseInt(process.env.MIRROR_NODE_MAX_REDIRECTS || '5'); - const mirrorNodeHttpKeepAlive = process.env.MIRROR_NODE_HTTP_KEEP_ALIVE === 'false' ? false : true; + const mirrorNodeHttpKeepAlive = process.env.MIRROR_NODE_HTTP_KEEP_ALIVE !== 'false'; const mirrorNodeHttpKeepAliveMsecs = parseInt(process.env.MIRROR_NODE_HTTP_KEEP_ALIVE_MSECS || '1000'); const mirrorNodeHttpMaxSockets = parseInt(process.env.MIRROR_NODE_HTTP_MAX_SOCKETS || '300'); const mirrorNodeHttpMaxTotalSockets = parseInt(process.env.MIRROR_NODE_HTTP_MAX_TOTAL_SOCKETS || '300'); @@ -180,7 +178,7 @@ export class MirrorNodeClient { ? JSON.parse(process.env.MIRROR_NODE_RETRY_CODES) : []; // we are in the process of deprecating this feature // by default will be true, unless explicitly set to false. - const useCacheableDnsLookup: boolean = process.env.MIRROR_NODE_AGENT_CACHEABLE_DNS === 'false' ? false : true; + const useCacheableDnsLookup: boolean = process.env.MIRROR_NODE_AGENT_CACHEABLE_DNS !== 'false'; const httpAgent = new http.Agent({ keepAlive: mirrorNodeHttpKeepAlive, @@ -313,22 +311,16 @@ export class MirrorNodeClient { path: string, pathLabel: string, method: REQUEST_METHODS, + requestDetails: RequestDetails, data?: any, - requestIdPrefix?: string, retries?: number, ): Promise { const start = Date.now(); - // extract request id from prefix and remove trailing ']' character - const requestId = - requestIdPrefix - ?.split(MirrorNodeClient.REQUEST_PREFIX_SEPARATOR)[1] - .replace(MirrorNodeClient.REQUEST_PREFIX_TRAILING_BRACKET, MirrorNodeClient.EMPTY_STRING) || - MirrorNodeClient.EMPTY_STRING; const controller = new AbortController(); try { const axiosRequestConfig: AxiosRequestConfig = { headers: { - [MirrorNodeClient.REQUESTID_LABEL]: requestId, + [MirrorNodeClient.REQUESTID_LABEL]: requestDetails.requestId, }, signal: controller.signal, }; @@ -350,7 +342,7 @@ export class MirrorNodeClient { } const ms = Date.now() - start; - this.logger.debug(`${requestId} [${method}] ${path} ${response.status} ${ms} ms`); + this.logger.debug(`${requestDetails.formattedRequestId} [${method}] ${path} ${response.status} ${ms} ms`); this.mirrorResponseHistogram.labels(pathLabel, response.status?.toString()).observe(ms); return response.data; } catch (error: any) { @@ -364,25 +356,30 @@ export class MirrorNodeClient { // always abort the request on failure as the axios call can hang until the parent code/stack times out (might be a few minutes in a server-side applications) controller.abort(); - this.handleError(error, path, pathLabel, effectiveStatusCode, method, requestIdPrefix); + this.handleError(error, path, pathLabel, effectiveStatusCode, method, requestDetails); } return null; } - async get(path: string, pathLabel: string, requestIdPrefix?: string, retries?: number): Promise { - return this.request(path, pathLabel, 'GET', null, requestIdPrefix, retries); + async get( + path: string, + pathLabel: string, + requestDetails: RequestDetails, + retries?: number, + ): Promise { + return this.request(path, pathLabel, 'GET', requestDetails, null, retries); } async post( path: string, data: any, pathLabel: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, retries?: number, ): Promise { if (!data) data = {}; - return this.request(path, pathLabel, 'POST', data, requestIdPrefix, retries); + return this.request(path, pathLabel, 'POST', requestDetails, data, retries); } /** @@ -395,8 +392,9 @@ export class MirrorNodeClient { pathLabel: string, effectiveStatusCode: number, method: REQUEST_METHODS, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): null { + const requestIdPrefix = requestDetails.formattedRequestId; const mirrorError = new MirrorNodeClientError(error, effectiveStatusCode); const acceptedErrorResponses = MirrorNodeClient.acceptedErrorStatusesResponsePerRequestPathMap.get(pathLabel); @@ -426,12 +424,12 @@ export class MirrorNodeClient { url: string, pathLabel: string, resultProperty: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, results = [], page = 1, pageMax: number = constants.MAX_MIRROR_NODE_PAGINATION, ) { - const result = await this.get(url, pathLabel, requestIdPrefix); + const result = await this.get(url, pathLabel, requestDetails); if (result && result[resultProperty]) { results = results.concat(result[resultProperty]); @@ -439,24 +437,26 @@ export class MirrorNodeClient { if (page === pageMax) { // max page reached - this.logger.trace(`${requestIdPrefix} Max page reached ${pageMax} with ${results.length} results`); + this.logger.trace( + `${requestDetails.formattedRequestId} Max page reached ${pageMax} with ${results.length} results`, + ); throw predefined.PAGINATION_MAX(pageMax); } if (result?.links?.next && page < pageMax) { page++; const next = result.links.next.replace(constants.NEXT_LINK_PREFIX, ''); - return this.getPaginatedResults(next, pathLabel, resultProperty, requestIdPrefix, results, page, pageMax); + return this.getPaginatedResults(next, pathLabel, resultProperty, requestDetails, results, page, pageMax); } else { return results; } } - public async getAccount(idOrAliasOrEvmAddress: string, requestIdPrefix?: string, retries?: number) { + public async getAccount(idOrAliasOrEvmAddress: string, requestDetails: RequestDetails, retries?: number) { return this.get( `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}?transactions=false`, MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, - requestIdPrefix, + requestDetails, retries, ); } @@ -464,8 +464,8 @@ export class MirrorNodeClient { public async getAccountLatestEthereumTransactionsByTimestamp( idOrAliasOrEvmAddress: string, timestampTo: string, + requestDetails: RequestDetails, numberOfTransactions: number = 1, - requestIdPrefix?: string, ) { const queryParamObject = {}; this.setQueryParam( @@ -483,24 +483,24 @@ export class MirrorNodeClient { return this.get( `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}${queryParams}`, MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, - requestIdPrefix, + requestDetails, ); } - public async getAccountPageLimit(idOrAliasOrEvmAddress: string, requestIdPrefix?: string) { + public async getAccountPageLimit(idOrAliasOrEvmAddress: string, requestDetails: RequestDetails) { return this.get( `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}?limit=${constants.MIRROR_NODE_QUERY_LIMIT}`, MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, - requestIdPrefix, + requestDetails, ); } /******************************************************************************* * To be used to make paginated calls for the account information when the * transaction count exceeds the constant MIRROR_NODE_QUERY_LIMIT. *******************************************************************************/ - public async getAccountPaginated(url: string, requestIdPrefix?: string) { + public async getAccountPaginated(url: string, requestDetails: RequestDetails) { const queryParamObject = {}; - const accountId = this.extractAccountIdFromUrl(url, requestIdPrefix); + const accountId = this.extractAccountIdFromUrl(url, requestDetails); const params = new URLSearchParams(url.split('?')[1]); this.setQueryParam(queryParamObject, 'limit', constants.MIRROR_NODE_QUERY_LIMIT); @@ -511,11 +511,11 @@ export class MirrorNodeClient { `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${accountId}${queryParams}`, MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, 'transactions', - requestIdPrefix, + requestDetails, ); } - public extractAccountIdFromUrl(url: string, requestIdPrefix?: string): string | null { + public extractAccountIdFromUrl(url: string, requestDetails: RequestDetails): string | null { const substringStartIndex = url.indexOf('/accounts/') + '/accounts/'.length; if (url.startsWith('0x', substringStartIndex)) { // evm addresss @@ -523,7 +523,7 @@ export class MirrorNodeClient { const match = url.match(regex); const accountId = match ? match[1] : null; if (!accountId) { - this.logger.error(`${requestIdPrefix} Unable to extract evm address from url ${url}`); + this.logger.error(`${requestDetails.formattedRequestId} Unable to extract evm address from url ${url}`); } return String(accountId); } else { @@ -531,7 +531,7 @@ export class MirrorNodeClient { const match = url.match(MirrorNodeClient.EVM_ADDRESS_REGEX); const accountId = match ? match[1] : null; if (!accountId) { - this.logger.error(`${requestIdPrefix} Unable to extract account ID from url ${url}`); + this.logger.error(`${requestDetails.formattedRequestId} Unable to extract account ID from url ${url}`); } return String(accountId); } @@ -541,7 +541,7 @@ export class MirrorNodeClient { accountId: string, timestampFrom: string, timestampTo: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'account.id', accountId); @@ -553,11 +553,11 @@ export class MirrorNodeClient { `${MirrorNodeClient.GET_TRANSACTIONS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_TRANSACTIONS_ENDPOINT, 'transactions', - requestIdPrefix, + requestDetails, ); } - public async getBalanceAtTimestamp(accountId: string, timestamp?: string, requestIdPrefix?: string) { + public async getBalanceAtTimestamp(accountId: string, requestDetails: RequestDetails, timestamp?: string) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'account.id', accountId); this.setQueryParam(queryParamObject, 'timestamp', timestamp); @@ -565,16 +565,16 @@ export class MirrorNodeClient { return this.get( `${MirrorNodeClient.GET_BALANCE_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_BALANCE_ENDPOINT, - requestIdPrefix, + requestDetails, ); } - public async getBlock(hashOrBlockNumber: string | number, requestIdPrefix?: string) { + public async getBlock(hashOrBlockNumber: string | number, requestDetails: RequestDetails) { const cachedLabel = `${constants.CACHE_KEY.GET_BLOCK}.${hashOrBlockNumber}`; const cachedResponse: any = await this.cacheService.getAsync( cachedLabel, MirrorNodeClient.GET_BLOCK_ENDPOINT, - requestIdPrefix, + requestDetails, ); if (cachedResponse) { return cachedResponse; @@ -583,18 +583,18 @@ export class MirrorNodeClient { const block = await this.get( `${MirrorNodeClient.GET_BLOCK_ENDPOINT}${hashOrBlockNumber}`, MirrorNodeClient.GET_BLOCK_ENDPOINT, - requestIdPrefix, + requestDetails, ); - await this.cacheService.set(cachedLabel, block, MirrorNodeClient.GET_BLOCK_ENDPOINT, undefined, requestIdPrefix); + await this.cacheService.set(cachedLabel, block, MirrorNodeClient.GET_BLOCK_ENDPOINT, requestDetails); return block; } public async getBlocks( + requestDetails: RequestDetails, blockNumber?: number | string[], timestamp?: string, limitOrderParams?: ILimitOrderParams, - requestIdPrefix?: string, ) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'block.number', blockNumber); @@ -604,35 +604,35 @@ export class MirrorNodeClient { return this.get( `${MirrorNodeClient.GET_BLOCKS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_BLOCKS_ENDPOINT, - requestIdPrefix, + requestDetails, ); } - public async getContract(contractIdOrAddress: string, requestIdPrefix?: string, retries?: number) { + public async getContract(contractIdOrAddress: string, requestDetails: RequestDetails, retries?: number) { return this.get( `${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${contractIdOrAddress}`, MirrorNodeClient.GET_CONTRACT_ENDPOINT, - requestIdPrefix, + requestDetails, retries, ); } - public getIsValidContractCacheLabel(contractIdOrAddress): string { + public getIsValidContractCacheLabel(contractIdOrAddress: string): string { return `${constants.CACHE_KEY.GET_CONTRACT}.valid.${contractIdOrAddress}`; } - public async getIsValidContractCache(contractIdOrAddress, requestIdPrefix?: string): Promise { + public async getIsValidContractCache(contractIdOrAddress: string, requestDetails: RequestDetails): Promise { const cachedLabel = this.getIsValidContractCacheLabel(contractIdOrAddress); - return await this.cacheService.getAsync(cachedLabel, MirrorNodeClient.GET_CONTRACT_ENDPOINT, requestIdPrefix); + return await this.cacheService.getAsync(cachedLabel, MirrorNodeClient.GET_CONTRACT_ENDPOINT, requestDetails); } - public async isValidContract(contractIdOrAddress: string, requestIdPrefix?: string, retries?: number) { - const cachedResponse: any = await this.getIsValidContractCache(contractIdOrAddress, requestIdPrefix); + public async isValidContract(contractIdOrAddress: string, requestDetails: RequestDetails, retries?: number) { + const cachedResponse: any = await this.getIsValidContractCache(contractIdOrAddress, requestDetails); if (cachedResponse != undefined) { return cachedResponse; } - const contract = await this.getContractId(contractIdOrAddress, requestIdPrefix, retries); + const contract = await this.getContractId(contractIdOrAddress, requestDetails, retries); const valid = contract != null; const cachedLabel = this.getIsValidContractCacheLabel(contractIdOrAddress); @@ -640,18 +640,18 @@ export class MirrorNodeClient { cachedLabel, valid, MirrorNodeClient.GET_CONTRACT_ENDPOINT, + requestDetails, constants.CACHE_TTL.ONE_DAY, - requestIdPrefix, ); return valid; } - public async getContractId(contractIdOrAddress: string, requestIdPrefix?: string, retries?: number) { + public async getContractId(contractIdOrAddress: string, requestDetails: RequestDetails, retries?: number) { const cachedLabel = `${constants.CACHE_KEY.GET_CONTRACT}.id.${contractIdOrAddress}`; const cachedResponse: any = await this.cacheService.getAsync( cachedLabel, MirrorNodeClient.GET_CONTRACT_ENDPOINT, - requestIdPrefix, + requestDetails, ); if (cachedResponse != undefined) { return cachedResponse; @@ -660,7 +660,7 @@ export class MirrorNodeClient { const contract = await this.get( `${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${contractIdOrAddress}`, MirrorNodeClient.GET_CONTRACT_ENDPOINT, - requestIdPrefix, + requestDetails, retries, ); @@ -670,8 +670,8 @@ export class MirrorNodeClient { cachedLabel, id, MirrorNodeClient.GET_CONTRACT_ENDPOINT, + requestDetails, constants.CACHE_TTL.ONE_DAY, - requestIdPrefix, ); return id; } @@ -679,9 +679,13 @@ export class MirrorNodeClient { return null; } - public async getContractResult(transactionIdOrHash: string, requestIdPrefix?: string) { + public async getContractResult(transactionIdOrHash: string, requestDetails: RequestDetails) { const cacheKey = `${constants.CACHE_KEY.GET_CONTRACT_RESULT}.${transactionIdOrHash}`; - const cachedResponse = await this.cacheService.getAsync(cacheKey, MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT); + const cachedResponse = await this.cacheService.getAsync( + cacheKey, + MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT, + requestDetails, + ); if (cachedResponse) { return cachedResponse; @@ -690,7 +694,7 @@ export class MirrorNodeClient { const response = await this.get( `${MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT}${transactionIdOrHash}`, MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT, - requestIdPrefix, + requestDetails, ); if ( @@ -703,8 +707,8 @@ export class MirrorNodeClient { cacheKey, response, MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT, + requestDetails, constants.CACHE_TTL.ONE_HOUR, - requestIdPrefix, ); } @@ -715,21 +719,21 @@ export class MirrorNodeClient { * In some very rare cases the /contracts/results api is called before all the data is saved in * the mirror node DB and `transaction_index` or `block_number` is returned as `undefined`. A single re-fetch is sufficient to * resolve this problem. - * @param transactionIdOrHash - * @param requestIdPrefix + * @param {string} transactionIdOrHash - The transaction ID or hash + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - public async getContractResultWithRetry(transactionIdOrHash: string, requestIdPrefix?: string) { - const contractResult = await this.getContractResult(transactionIdOrHash, requestIdPrefix); + public async getContractResultWithRetry(transactionIdOrHash: string, requestDetails: RequestDetails) { + const contractResult = await this.getContractResult(transactionIdOrHash, requestDetails); if (contractResult && !(contractResult.transaction_index && contractResult.block_number)) { - return this.getContractResult(transactionIdOrHash, requestIdPrefix); + return this.getContractResult(transactionIdOrHash, requestDetails); } return contractResult; } public async getContractResults( + requestDetails: RequestDetails, contractResultsParams?: IContractResultsParams, limitOrderParams?: ILimitOrderParams, - requestIdPrefix?: string, ) { const queryParamObject = {}; this.setContractResultsParams(queryParamObject, contractResultsParams); @@ -739,47 +743,47 @@ export class MirrorNodeClient { `${MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT, 'results', - requestIdPrefix, + requestDetails, [], 1, MirrorNodeClient.mirrorNodeContractResultsPageMax, ); } - public async getContractResultsDetails(contractId: string, timestamp: string, requestIdPrefix?: string) { + public async getContractResultsDetails(contractId: string, timestamp: string, requestDetails: RequestDetails) { return this.get( `${this.getContractResultsDetailsByContractIdAndTimestamp(contractId, timestamp)}`, MirrorNodeClient.GET_CONTRACT_RESULTS_DETAILS_BY_CONTRACT_ID_ENDPOINT, - requestIdPrefix, + requestDetails, ); } - public async getContractsResultsActions(transactionIdOrHash: string, requestIdPrefix?: string): Promise { + public async getContractsResultsActions(transactionIdOrHash: string, requestDetails: RequestDetails): Promise { return this.get( `${this.getContractResultsActionsByTransactionIdPath(transactionIdOrHash)}`, MirrorNodeClient.GET_CONTRACTS_RESULTS_ACTIONS, - requestIdPrefix, + requestDetails, ); } public async getContractsResultsOpcodes( transactionIdOrHash: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, params?: { memory?: boolean; stack?: boolean; storage?: boolean }, ): Promise { const queryParams = params ? this.getQueryParams(params) : ''; return this.get( `${this.getContractResultsOpcodesByTransactionIdPath(transactionIdOrHash)}${queryParams}`, MirrorNodeClient.GET_CONTRACTS_RESULTS_OPCODES, - requestIdPrefix, + requestDetails, ); } public async getContractResultsByAddress( contractIdOrAddress: string, + requestDetails: RequestDetails, contractResultsParams?: IContractResultsParams, limitOrderParams?: ILimitOrderParams, - requestIdPrefix?: string, ) { const queryParamObject = {}; this.setContractResultsParams(queryParamObject, contractResultsParams); @@ -788,20 +792,20 @@ export class MirrorNodeClient { return this.get( `${MirrorNodeClient.getContractResultsByAddressPath(contractIdOrAddress)}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULTS_BY_ADDRESS_ENDPOINT, - requestIdPrefix, + requestDetails, ); } public async getContractResultsByAddressAndTimestamp( contractIdOrAddress: string, timestamp: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ) { const apiPath = MirrorNodeClient.getContractResultsByAddressAndTimestampPath(contractIdOrAddress, timestamp); return this.get( apiPath, MirrorNodeClient.GET_CONTRACT_RESULTS_DETAILS_BY_ADDRESS_AND_TIMESTAMP_ENDPOINT, - requestIdPrefix, + requestDetails, ); } @@ -825,9 +829,9 @@ export class MirrorNodeClient { } public async getContractResultsLogs( + requestDetails: RequestDetails, contractLogsResultsParams?: IContractLogsResultsParams, limitOrderParams?: ILimitOrderParams, - requestIdPrefix?: string, ) { const queryParams = this.prepareLogsParams(contractLogsResultsParams, limitOrderParams); @@ -835,7 +839,7 @@ export class MirrorNodeClient { `${MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT, MirrorNodeClient.CONTRACT_RESULT_LOGS_PROPERTY, - requestIdPrefix, + requestDetails, [], 1, MirrorNodeClient.mirrorNodeContractResultsLogsPageMax, @@ -844,9 +848,9 @@ export class MirrorNodeClient { public async getContractResultsLogsByAddress( address: string, + requestDetails: RequestDetails, contractLogsResultsParams?: IContractLogsResultsParams, limitOrderParams?: ILimitOrderParams, - requestIdPrefix?: string, ) { if (address === ethers.ZeroAddress) return []; @@ -860,29 +864,29 @@ export class MirrorNodeClient { `${apiEndpoint}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_BY_ADDRESS_ENDPOINT, MirrorNodeClient.CONTRACT_RESULT_LOGS_PROPERTY, - requestIdPrefix, + requestDetails, [], 1, MirrorNodeClient.mirrorNodeContractResultsLogsPageMax, ); } - public async getEarliestBlock(requestId?: string) { + public async getEarliestBlock(requestDetails: RequestDetails) { const cachedLabel = `${constants.CACHE_KEY.GET_BLOCK}.earliest`; const cachedResponse: any = await this.cacheService.getAsync( cachedLabel, MirrorNodeClient.GET_BLOCKS_ENDPOINT, - requestId, + requestDetails, ); if (cachedResponse != undefined) { return cachedResponse; } const blocks = await this.getBlocks( + requestDetails, undefined, undefined, this.getLimitOrderQueryParam(1, MirrorNodeClient.ORDER.ASC), - requestId, ); if (blocks && blocks.blocks.length > 0) { const block = blocks.blocks[0]; @@ -890,8 +894,8 @@ export class MirrorNodeClient { cachedLabel, block, MirrorNodeClient.GET_BLOCKS_ENDPOINT, + requestDetails, constants.CACHE_TTL.ONE_DAY, - requestId, ); return block; } @@ -899,12 +903,12 @@ export class MirrorNodeClient { return null; } - public async getLatestBlock(requestIdPrefix?: string) { + public async getLatestBlock(requestDetails: RequestDetails) { return this.getBlocks( + requestDetails, undefined, undefined, this.getLimitOrderQueryParam(1, MirrorNodeClient.ORDER.DESC), - requestIdPrefix, ); } @@ -912,19 +916,18 @@ export class MirrorNodeClient { return { limit: limit, order: order }; } - public async getNetworkExchangeRate(requestId: string, timestamp?: string) { - const formattedRequestId = formatRequestIdMessage(requestId); + public async getNetworkExchangeRate(requestDetails: RequestDetails, timestamp?: string) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'timestamp', timestamp); const queryParams = this.getQueryParams(queryParamObject); return this.get( `${MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT, - formattedRequestId, + requestDetails, ); } - public async getNetworkFees(timestamp?: string, order?: string, requestIdPrefix?: string) { + public async getNetworkFees(requestDetails: RequestDetails, timestamp?: string, order?: string) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'timestamp', timestamp); this.setQueryParam(queryParamObject, 'order', order); @@ -932,7 +935,7 @@ export class MirrorNodeClient { return this.get( `${MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT, - requestIdPrefix, + requestDetails, ); } @@ -971,11 +974,11 @@ export class MirrorNodeClient { ); } - public async getTokenById(tokenId: string, requestIdPrefix?: string, retries?: number) { + public async getTokenById(tokenId: string, requestDetails: RequestDetails, retries?: number) { return this.get( `${MirrorNodeClient.GET_TOKENS_ENDPOINT}/${tokenId}`, MirrorNodeClient.GET_TOKENS_ENDPOINT, - requestIdPrefix, + requestDetails, retries, ); } @@ -984,21 +987,21 @@ export class MirrorNodeClient { address: string, blockEndTimestamp: string | undefined, limit: number, - requestIdPrefix?: string, + requestDetails: RequestDetails, ) { // retrieve the timestamp of the contract const contractResultsParams: IContractResultsParams = blockEndTimestamp ? { timestamp: `lte:${blockEndTimestamp}` } : {}; const limitOrderParams: ILimitOrderParams = this.getLimitOrderQueryParam(limit, 'desc'); - return this.getContractResultsByAddress(address, contractResultsParams, limitOrderParams, requestIdPrefix); + return this.getContractResultsByAddress(address, requestDetails, contractResultsParams, limitOrderParams); } public async getContractStateByAddressAndSlot( address: string, slot: string, + requestDetails: RequestDetails, blockEndTimestamp?: string, - requestIdPrefix?: string, ) { const limitOrderParams: ILimitOrderParams = this.getLimitOrderQueryParam( constants.MIRROR_NODE_QUERY_LIMIT, @@ -1016,28 +1019,28 @@ export class MirrorNodeClient { MirrorNodeClient.ADDRESS_PLACEHOLDER, address, ); - return this.get(`${apiEndpoint}${queryParams}`, MirrorNodeClient.CONTRACT_ADDRESS_STATE_ENDPOINT, requestIdPrefix); + return this.get(`${apiEndpoint}${queryParams}`, MirrorNodeClient.CONTRACT_ADDRESS_STATE_ENDPOINT, requestDetails); } /** * Send a contract call request to mirror node * @param callData {IContractCallRequest} contract call request data - * @param requestIdPrefix {string} optional request id prefix + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ public async postContractCall( callData: IContractCallRequest, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { return this.post( MirrorNodeClient.CONTRACT_CALL_ENDPOINT, callData, MirrorNodeClient.CONTRACT_CALL_ENDPOINT, - requestIdPrefix, + requestDetails, 1, // historical blocks might need 1 retry due to possible timeout from mirror node ); } - public async getTransactionById(transactionId: string, nonce: number | undefined, requestIdPrefix?: string) { + public async getTransactionById(transactionId: string, requestDetails: RequestDetails, nonce?: number) { const formattedId = formatTransactionId(transactionId); if (formattedId == null) { return formattedId; @@ -1052,31 +1055,34 @@ export class MirrorNodeClient { return this.get( `${apiEndpoint}${queryParams}`, MirrorNodeClient.GET_TRANSACTIONS_ENDPOINT_TRANSACTION_ID, - requestIdPrefix, + requestDetails, ); } /** * Check if transaction fail is because of contract revert and try to fetch and log the reason. * - * @param e - * @param requestIdPrefix + * @param e - The error object. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - public async getContractRevertReasonFromTransaction(e: any, requestIdPrefix: string): Promise { + public async getContractRevertReasonFromTransaction( + e: any, + requestDetails: RequestDetails, + ): Promise { if (e instanceof SDKClientError && e.isContractRevertExecuted()) { const transactionId = e.message.match(constants.TRANSACTION_ID_REGEX); if (transactionId) { - const tx = await this.getTransactionById(transactionId[0], undefined, requestIdPrefix); + const tx = await this.getTransactionById(transactionId[0], requestDetails); if (tx === null) { - this.logger.error(`${requestIdPrefix} Transaction failed with null result`); + this.logger.error(`${requestDetails.formattedRequestId} Transaction failed with null result`); return null; } else if (tx.length === 0) { - this.logger.error(`${requestIdPrefix} Transaction failed with empty result`); + this.logger.error(`${requestDetails.formattedRequestId} Transaction failed with empty result`); return null; } else if (tx?.transactions.length > 1) { const result = tx.transactions[1].result; - this.logger.error(`${requestIdPrefix} Transaction failed with result: ${result}`); + this.logger.error(`${requestDetails.formattedRequestId} Transaction failed with result: ${result}`); return result; } } @@ -1136,22 +1142,22 @@ export class MirrorNodeClient { * @param entityIdentifier the address of the contract * @param searchableTypes the types to search for * @param callerName calling method name - * @param requestIdPrefix the request id prefix message + * @param requestDetails The request details for logging and tracking. * @param retries the number of retries * @returns entity object or null if not found */ public async resolveEntityType( entityIdentifier: string, - searchableTypes: any[] = [constants.TYPE_CONTRACT, constants.TYPE_ACCOUNT, constants.TYPE_TOKEN], callerName: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, + searchableTypes: any[] = [constants.TYPE_CONTRACT, constants.TYPE_ACCOUNT, constants.TYPE_TOKEN], retries?: number, ) { const cachedLabel = `${constants.CACHE_KEY.RESOLVE_ENTITY_TYPE}_${entityIdentifier}`; const cachedResponse: { type: string; entity: any } | undefined = await this.cacheService.getAsync( cachedLabel, callerName, - requestIdPrefix, + requestDetails, ); if (cachedResponse) { return cachedResponse; @@ -1166,7 +1172,7 @@ export class MirrorNodeClient { ); if (searchableTypes.find((t) => t === constants.TYPE_CONTRACT)) { - const contract = await this.getContract(entityIdentifier, requestIdPrefix, retries).catch(() => { + const contract = await this.getContract(entityIdentifier, requestDetails, retries).catch(() => { return null; }); if (contract) { @@ -1174,7 +1180,7 @@ export class MirrorNodeClient { type: constants.TYPE_CONTRACT, entity: contract, }; - await this.cacheService.set(cachedLabel, response, callerName, undefined, requestIdPrefix); + await this.cacheService.set(cachedLabel, response, callerName, requestDetails); return response; } } @@ -1184,7 +1190,7 @@ export class MirrorNodeClient { const promises = [ searchableTypes.find((t) => t === constants.TYPE_ACCOUNT) ? buildPromise( - this.getAccount(entityIdentifier, requestIdPrefix, retries).catch(() => { + this.getAccount(entityIdentifier, requestDetails, retries).catch(() => { return null; }), ) @@ -1196,7 +1202,7 @@ export class MirrorNodeClient { promises.push( searchableTypes.find((t) => t === constants.TYPE_TOKEN) ? buildPromise( - this.getTokenById(`0.0.${parseInt(entityIdentifier, 16)}`, requestIdPrefix, retries).catch(() => { + this.getTokenById(`0.0.${parseInt(entityIdentifier, 16)}`, requestDetails, retries).catch(() => { return null; }), ) @@ -1229,7 +1235,7 @@ export class MirrorNodeClient { type, entity: data.value, }; - await this.cacheService.set(cachedLabel, response, callerName, undefined, requestIdPrefix); + await this.cacheService.set(cachedLabel, response, callerName, requestDetails); return response; } @@ -1253,11 +1259,11 @@ export class MirrorNodeClient { * enough time for the expected data to be propagated to the Mirror node. * It provides a way to have an extended retry logic only in specific places */ - public async repeatedRequest(methodName: string, args: any[], repeatCount: number, requestId?: string) { + public async repeatedRequest(methodName: string, args: any[], repeatCount: number, requestDetails?: RequestDetails) { let result; for (let i = 0; i < repeatCount; i++) { try { - result = await this[methodName](...args, requestId); + result = await this[methodName](...args); } catch (e: any) { // note: for some methods, it will throw 404 not found error as the record is not yet recorded in mirror-node // if error is 404, `result` would be assigned as null for it to not break out the loop. @@ -1267,7 +1273,7 @@ export class MirrorNodeClient { } else { this.logger.warn( e, - `${requestId} Error raised during polling mirror node for updated records: method=${methodName}, args=${args}`, + `${requestDetails?.formattedRequestId} Error raised during polling mirror node for updated records: method=${methodName}, args=${args}`, ); } } @@ -1277,7 +1283,7 @@ export class MirrorNodeClient { } this.logger.trace( - `${requestId} Repeating request ${methodName} with args ${JSON.stringify( + `${requestDetails?.formattedRequestId} Repeating request ${methodName} with args ${JSON.stringify( args, )} retry count ${i} of ${repeatCount}. Waiting ${this.MIRROR_NODE_RETRY_DELAY} ms before repeating request`, ); @@ -1293,20 +1299,20 @@ export class MirrorNodeClient { * * @param {string} transactionId - The ID of the transaction for which the record is being retrieved. * @param {string} callerName - The name of the caller requesting the transaction record. - * @param {string} requestId - The unique identifier for the request, used for logging and tracking. * @param {string} txConstructorName - The name of the transaction constructor associated with the transaction. * @param {string} operatorAccountId - The account ID of the operator, used to calculate transaction fees. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise<{ITransactionRecordMetric}>} - An object containing the transaction fee if available, or `undefined` if the transaction record is not found. * @throws {MirrorNodeClientError} - Throws an error if no transaction record is retrieved. */ public async getTransactionRecordMetrics( transactionId: string, callerName: string, - requestId: string, txConstructorName: string, operatorAccountId: string, + requestDetails: RequestDetails, ): Promise { - const formattedRequestId = formatRequestIdMessage(requestId); + const formattedRequestId = requestDetails.formattedRequestId; this.logger.trace( `${formattedRequestId} Get transaction record via mirror node: transactionId=${transactionId}, txConstructorName=${txConstructorName}, callerName=${callerName}`, @@ -1314,9 +1320,9 @@ export class MirrorNodeClient { const transactionRecords = await this.repeatedRequest( this.getTransactionById.name, - [transactionId, 0], + [transactionId, requestDetails, 0], this.MIRROR_NODE_REQUEST_RETRY_COUNT, - formattedRequestId, + requestDetails, ); if (!transactionRecords) { diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 1478530eb4..dd4f714a9e 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -57,14 +57,14 @@ import { EventEmitter } from 'events'; import HbarLimit from '../hbarlimiter'; import constants from './../constants'; import { BigNumber } from '@hashgraph/sdk/lib/Transfer'; -import { SDKClientError } from './../errors/SDKClientError'; -import { JsonRpcError, predefined } from './../errors/JsonRpcError'; +import { SDKClientError } from '../errors/SDKClientError'; +import { JsonRpcError, predefined } from '../errors/JsonRpcError'; import { CacheService } from '../services/cacheService/cacheService'; import { formatRequestIdMessage, weibarHexToTinyBarInt } from '../../formatters'; import { ITransactionRecordMetric, IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from '../types'; +import { RequestDetails } from '../types'; const _ = require('lodash'); -const LRU = require('lru-cache'); export class SDKClient { /** @@ -159,16 +159,20 @@ export class SDKClient { * * @param {string} account - The account ID to retrieve the balance for. * @param {string} callerName - The name of the caller requesting the account balance. - * @param {string} [requestId] - Optional request ID for tracking the request. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves to the account balance. */ - async getAccountBalance(account: string, callerName: string, requestId?: string): Promise { + async getAccountBalance( + account: string, + callerName: string, + requestDetails: RequestDetails, + ): Promise { return this.executeQuery( new AccountBalanceQuery().setAccountId(AccountId.fromString(account)), this.clientMain, callerName, account, - requestId, + requestDetails, ); } @@ -176,11 +180,16 @@ export class SDKClient { * Retrieves the balance of an account in tinybars. * @param {string} account - The account ID to query. * @param {string} callerName - The name of the caller for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The balance of the account in tinybars. * @throws {SDKClientError} Throws an SDK client error if the balance retrieval fails. */ - async getAccountBalanceInTinyBar(account: string, callerName: string, requestId?: string): Promise { - const balance = await this.getAccountBalance(account, callerName, requestId); + async getAccountBalanceInTinyBar( + account: string, + callerName: string, + requestDetails: RequestDetails, + ): Promise { + const balance = await this.getAccountBalance(account, callerName, requestDetails); return balance.hbars.to(HbarUnit.Tinybar); } @@ -188,12 +197,16 @@ export class SDKClient { * Retrieves the balance of an account in weiBars. * @param {string} account - The account ID to query. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The balance of the account in weiBars. * @throws {SDKClientError} Throws an SDK client error if the balance retrieval fails. */ - async getAccountBalanceInWeiBar(account: string, callerName: string, requestId?: string): Promise { - const balance = await this.getAccountBalance(account, callerName, requestId); + async getAccountBalanceInWeiBar( + account: string, + callerName: string, + requestDetails: RequestDetails, + ): Promise { + const balance = await this.getAccountBalance(account, callerName, requestDetails); return SDKClient.HbarToWeiBar(balance); } @@ -201,17 +214,17 @@ export class SDKClient { * Retrieves information about an account. * @param {string} address - The account ID to query. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The information about the account. * @throws {SDKClientError} Throws an SDK client error if the account info retrieval fails. */ - async getAccountInfo(address: string, callerName: string, requestId?: string): Promise { + async getAccountInfo(address: string, callerName: string, requestDetails: RequestDetails): Promise { return this.executeQuery( new AccountInfoQuery().setAccountId(AccountId.fromString(address)), this.clientMain, callerName, address, - requestId, + requestDetails, ); } @@ -221,7 +234,7 @@ export class SDKClient { * @param {number | Long} realm - The realm number of the contract. * @param {string} address - The address of the contract. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The bytecode of the contract. * @throws {SDKClientError} Throws an SDK client error if the bytecode retrieval fails. */ @@ -230,7 +243,7 @@ export class SDKClient { realm: number | Long, address: string, callerName: string, - requestId?: string, + requestDetails: RequestDetails, ): Promise { const contractByteCodeQuery = new ContractByteCodeQuery().setContractId( ContractId.fromEvmAddress(shard, realm, address), @@ -242,7 +255,7 @@ export class SDKClient { this.clientMain, callerName, address, - requestId, + requestDetails, ); } @@ -250,17 +263,21 @@ export class SDKClient { * Retrieves the balance of a contract. * @param {string} contract - The contract ID to query. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The balance of the contract. * @throws {SDKClientError} Throws an SDK client error if the balance retrieval fails. */ - async getContractBalance(contract: string, callerName: string, requestId?: string): Promise { + async getContractBalance( + contract: string, + callerName: string, + requestDetails: RequestDetails, + ): Promise { return this.executeQuery( new AccountBalanceQuery().setContractId(ContractId.fromString(contract)), this.clientMain, callerName, contract, - requestId, + requestDetails, ); } @@ -269,24 +286,28 @@ export class SDKClient { * Converts the balance from Hbar to weiBars using the `HbarToWeiBar` method. * @param {string} account - The account address of the contract. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The contract balance in weiBars. * @throws {SDKClientError} Throws an SDK client error if the balance retrieval fails. */ - async getContractBalanceInWeiBar(account: string, callerName: string, requestId?: string): Promise { - const balance = await this.getContractBalance(account, callerName, requestId); + async getContractBalanceInWeiBar( + account: string, + callerName: string, + requestDetails: RequestDetails, + ): Promise { + const balance = await this.getContractBalance(account, callerName, requestDetails); return SDKClient.HbarToWeiBar(balance); } /** * Retrieves the current exchange rates from a file. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The exchange rates. * @throws {SDKClientError} Throws an SDK client error if the exchange rates file retrieval or parsing fails. */ - async getExchangeRate(callerName: string, requestId?: string): Promise { - const exchangeFileBytes = await this.getFileIdBytes(constants.EXCHANGE_RATE_FILE_ID, callerName, requestId); + async getExchangeRate(callerName: string, requestDetails: RequestDetails): Promise { + const exchangeFileBytes = await this.getFileIdBytes(constants.EXCHANGE_RATE_FILE_ID, callerName, requestDetails); return ExchangeRates.fromBytes(exchangeFileBytes); } @@ -294,12 +315,12 @@ export class SDKClient { /** * Retrieves the fee schedule from a file. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The fee schedules. * @throws {SDKClientError} Throws an SDK client error if the fee schedule file retrieval or parsing fails. */ - async getFeeSchedule(callerName: string, requestId?: string): Promise { - const feeSchedulesFileBytes = await this.getFileIdBytes(constants.FEE_SCHEDULE_FILE_ID, callerName, requestId); + async getFeeSchedule(callerName: string, requestDetails: RequestDetails): Promise { + const feeSchedulesFileBytes = await this.getFileIdBytes(constants.FEE_SCHEDULE_FILE_ID, callerName, requestDetails); return FeeSchedules.fromBytes(feeSchedulesFileBytes); } @@ -310,21 +331,21 @@ export class SDKClient { * MAPI does not incur any fees, while HAPI will incur a query fee. * * @param {string} callerName - The name of the caller, used for logging purposes. - * @param {string} [requestId] - Optional request ID, used for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The gas fee in tinybars. * @throws {SDKClientError} Throws an SDK client error if the fee schedules or exchange rates are invalid. */ - async getTinyBarGasFee(callerName: string, requestId?: string): Promise { + async getTinyBarGasFee(callerName: string, requestDetails: RequestDetails): Promise { const cachedResponse: number | undefined = await this.cacheService.getAsync( constants.CACHE_KEY.GET_TINYBAR_GAS_FEE, callerName, - requestId, + requestDetails, ); if (cachedResponse) { return cachedResponse; } - const feeSchedules = await this.getFeeSchedule(callerName, requestId); + const feeSchedules = await this.getFeeSchedule(callerName, requestDetails); if (_.isNil(feeSchedules.current) || feeSchedules.current?.transactionFeeSchedule === undefined) { throw new SDKClientError({}, 'Invalid FeeSchedules proto format'); } @@ -332,16 +353,10 @@ export class SDKClient { for (const schedule of feeSchedules.current?.transactionFeeSchedule) { if (schedule.hederaFunctionality?._code === constants.ETH_FUNCTIONALITY_CODE && schedule.fees !== undefined) { // get exchange rate & convert to tiny bar - const exchangeRates = await this.getExchangeRate(callerName, requestId); + const exchangeRates = await this.getExchangeRate(callerName, requestDetails); const tinyBars = this.convertGasPriceToTinyBars(schedule.fees[0].servicedata, exchangeRates); - await this.cacheService.set( - constants.CACHE_KEY.GET_TINYBAR_GAS_FEE, - tinyBars, - callerName, - undefined, - requestId, - ); + await this.cacheService.set(constants.CACHE_KEY.GET_TINYBAR_GAS_FEE, tinyBars, callerName, requestDetails); return tinyBars; } } @@ -353,17 +368,17 @@ export class SDKClient { * Retrieves the contents of a file identified by its ID and returns them as a byte array. * @param {string} address - The file ID or address of the file to retrieve. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The contents of the file as a byte array. * @throws {SDKClientError} Throws an SDK client error if the file query fails. */ - async getFileIdBytes(address: string, callerName: string, requestId?: string): Promise { + async getFileIdBytes(address: string, callerName: string, requestDetails: RequestDetails): Promise { return this.executeQuery( new FileContentsQuery().setFileId(address), this.clientMain, callerName, address, - requestId, + requestDetails, ); } @@ -374,6 +389,7 @@ export class SDKClient { * * @param {Uint8Array} transactionBuffer - The transaction data in bytes. * @param {string} callerName - The name of the caller initiating the transaction. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @param {string} originalCallerAddress - The address of the original caller making the request. * @param {number} networkGasPriceInWeiBars - The predefined gas price of the network in weibar. * @param {number} currentNetworkExchangeRateInCents - The exchange rate in cents of the current network. @@ -384,16 +400,15 @@ export class SDKClient { async submitEthereumTransaction( transactionBuffer: Uint8Array, callerName: string, + requestDetails: RequestDetails, originalCallerAddress: string, networkGasPriceInWeiBars: number, currentNetworkExchangeRateInCents: number, - requestId: string, ): Promise<{ txResponse: TransactionResponse; fileId: FileId | null }> { const ethereumTransactionData: EthereumTransactionData = EthereumTransactionData.fromBytes(transactionBuffer); const ethereumTransaction = new EthereumTransaction(); const interactingEntity = ethereumTransactionData.toJSON()['to'].toString(); let fileId: FileId | null = null; - const requestIdPrefix = formatRequestIdMessage(requestId); // if callData's size is greater than `fileAppendChunkSize` => employ HFS to create new file to carry the rest of the contents of callData if (ethereumTransactionData.callData.length <= this.fileAppendChunkSize) { @@ -408,7 +423,7 @@ export class SDKClient { hexCallDataLength, this.fileAppendChunkSize, currentNetworkExchangeRateInCents, - requestId, + requestDetails, ); if (shouldPreemptivelyLimit) { @@ -419,13 +434,13 @@ export class SDKClient { fileId = await this.createFile( ethereumTransactionData.callData, this.clientMain, - requestId, + requestDetails, callerName, interactingEntity, originalCallerAddress, ); if (!fileId) { - throw new SDKClientError({}, `${requestIdPrefix} No fileId created for transaction. `); + throw new SDKClientError({}, `${requestDetails.formattedRequestId} No fileId created for transaction. `); } ethereumTransactionData.callData = new Uint8Array(); ethereumTransaction.setEthereumData(ethereumTransactionData.toBytes()).setCallDataFileId(fileId); @@ -442,7 +457,7 @@ export class SDKClient { ethereumTransaction, callerName, interactingEntity, - requestId, + requestDetails, true, originalCallerAddress, ), @@ -456,7 +471,7 @@ export class SDKClient { * @param {number} gas - The amount of gas to use for the contract call. * @param {string} from - The address of the sender in EVM format. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - Optional request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The result of the contract function call. * @throws {SDKClientError} Throws an SDK client error if the contract call query fails. */ @@ -466,7 +481,7 @@ export class SDKClient { gas: number, from: string, callerName: string, - requestId?: string, + requestDetails: RequestDetails, ): Promise { const contract = SDKClient.prune0x(to); const contractId = contract.startsWith('00000000000') @@ -488,7 +503,7 @@ export class SDKClient { contractCallQuery.setPaymentTransactionId(TransactionId.generate(this.clientMain.operatorAccountId)); } - return this.executeQuery(contractCallQuery, this.clientMain, callerName, to, requestId); + return this.executeQuery(contractCallQuery, this.clientMain, callerName, to, requestDetails); } /** @@ -498,7 +513,7 @@ export class SDKClient { * @param {number} gas - The amount of gas to use for the contract call. * @param {string} from - The address from which the contract call is made. * @param {string} callerName - The name of the caller for logging purposes. - * @param {string} [requestId] - The request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The result of the contract function call. * @throws {JsonRpcError} Throws an error if the error is a JSON-RPC error. * @throws {SDKClientError} Throws an SDK client error if the error is not a timeout error or if the retries are exhausted. @@ -509,21 +524,20 @@ export class SDKClient { gas: number, from: string, callerName: string, - requestId?: string, + requestDetails: RequestDetails, ): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); let retries = 0; let resp; while (parseInt(process.env.CONTRACT_QUERY_TIMEOUT_RETRIES || '1') > retries) { try { - resp = await this.submitContractCallQuery(to, data, gas, from, callerName, requestId); + resp = await this.submitContractCallQuery(to, data, gas, from, callerName, requestDetails); return resp; } catch (e: any) { const sdkClientError = new SDKClientError(e, e.message); if (sdkClientError.isTimeoutExceeded()) { const delay = retries * 1000; this.logger.trace( - `${requestIdPrefix} Contract call query failed with status ${sdkClientError.message}. Retrying again after ${delay} ms ...`, + `${requestDetails.formattedRequestId} Contract call query failed with status ${sdkClientError.message}. Retrying again after ${delay} ms ...`, ); retries++; await new Promise((r) => setTimeout(r, delay)); @@ -545,7 +559,7 @@ export class SDKClient { * @param {Client} client - The client to use for executing the query. * @param {number} maxRetries - The maximum number of retries allowed. * @param {number} currentRetry - The current retry attempt number. - * @param {string} [requestId] - The request ID for logging purposes. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise<{resp: any, cost: Hbar}>} The response of the query execution and the cost used. * @throws Will throw an error if the maximum number of retries is exceeded or if the error is not due to insufficient transaction fees. */ @@ -555,7 +569,7 @@ export class SDKClient { client: Client, maxRetries: number, currentRetry: number, - requestId?: string, + requestDetails: RequestDetails, ): Promise<{ resp: any; cost: Hbar }> { const baseMultiplier = constants.QUERY_COST_INCREMENTATION_STEP; const multiplier = Math.pow(baseMultiplier, currentRetry); @@ -569,8 +583,10 @@ export class SDKClient { const sdkClientError = new SDKClientError(e, e.message); if (maxRetries > currentRetry && sdkClientError.isInsufficientTxFee()) { const newRetry = currentRetry + 1; - this.logger.info(`${requestId} Retrying query execution with increased cost, retry number: ${newRetry}`); - return await this.increaseCostAndRetryExecution(query, baseCost, client, maxRetries, newRetry, requestId); + this.logger.info( + `${requestDetails.formattedRequestId} Retrying query execution with increased cost, retry number: ${newRetry}`, + ); + return await this.increaseCostAndRetryExecution(query, baseCost, client, maxRetries, newRetry, requestDetails); } throw e; @@ -583,19 +599,20 @@ export class SDKClient { * @param {Client} client - The Hedera client to use for the query. * @param {string} callerName - The name of the caller executing the query. * @param {string} interactingEntity - The entity interacting with the query. - * @param {string} [requestId] - The optional request ID for logging and tracking. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A promise resolving to the query response. * @throws {Error} Throws an error if the query fails or if rate limits are exceeded. + * @template T - The type of the query response. */ async executeQuery( query: Query, client: Client, callerName: string, interactingEntity: string, - requestId?: string, + requestDetails: RequestDetails, ): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); const queryConstructorName = query.constructor.name; + const requestIdPrefix = requestDetails.formattedRequestId; let queryResponse: any = null; let queryCost: number | undefined = undefined; let status: string = ''; @@ -605,7 +622,7 @@ export class SDKClient { try { if (query.paymentTransactionId) { const baseCost = await query.getCost(this.clientMain); - const res = await this.increaseCostAndRetryExecution(query, baseCost, client, 3, 0, requestId); + const res = await this.increaseCostAndRetryExecution(query, baseCost, client, 3, 0, requestDetails); queryResponse = res.resp; queryCost = res.cost.toTinybars().toNumber(); } else { @@ -648,7 +665,7 @@ export class SDKClient { gasUsed: 0, interactingEntity, status, - requestId: requestIdPrefix, + requestDetails, } as IExecuteQueryEventPayload); } } @@ -660,7 +677,7 @@ export class SDKClient { * @param {Transaction} transaction - The transaction to execute. * @param {string} callerName - The name of the caller requesting the transaction. * @param {string} interactingEntity - The entity interacting with the transaction. - * @param {string} requestId - The ID of the request. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @param {boolean} shouldThrowHbarLimit - Flag to indicate whether to check HBAR limits. * @param {string} originalCallerAddress - The address of the original caller making the request. * @returns {Promise} - A promise that resolves to the transaction response. @@ -670,11 +687,10 @@ export class SDKClient { transaction: Transaction, callerName: string, interactingEntity: string, - requestId: string, + requestDetails: RequestDetails, shouldThrowHbarLimit: boolean, originalCallerAddress: string, ): Promise { - const formattedRequestId = formatRequestIdMessage(requestId); const txConstructorName = transaction.constructor.name; let transactionId: string = ''; let transactionResponse: TransactionResponse | null = null; @@ -685,7 +701,7 @@ export class SDKClient { constants.EXECUTION_MODE.TRANSACTION, callerName, originalCallerAddress, - requestId, + requestDetails, ); if (shouldLimit) { throw predefined.HBAR_RATE_LIMIT_EXCEEDED; @@ -693,7 +709,7 @@ export class SDKClient { } try { - this.logger.info(`${formattedRequestId} Execute ${txConstructorName} transaction`); + this.logger.info(`${requestDetails.formattedRequestId} Execute ${txConstructorName} transaction`); transactionResponse = await transaction.execute(this.clientMain); transactionId = transactionResponse.transactionId.toString(); @@ -702,7 +718,7 @@ export class SDKClient { const transactionReceipt = await transactionResponse.getReceipt(this.clientMain); this.logger.info( - `${formattedRequestId} Successfully execute ${txConstructorName} transaction: transactionId=${transactionResponse.transactionId}, callerName=${callerName}, status=${transactionReceipt.status}(${transactionReceipt.status._code})`, + `${requestDetails.formattedRequestId} Successfully execute ${txConstructorName} transaction: transactionId=${transactionResponse.transactionId}, callerName=${callerName}, status=${transactionReceipt.status}(${transactionReceipt.status._code})`, ); return transactionResponse; } catch (e: any) { @@ -719,12 +735,12 @@ export class SDKClient { this.logger.warn( sdkClientError, - `${formattedRequestId} Fail to execute ${txConstructorName} transaction: transactionId=${transaction.transactionId}, callerName=${callerName}, status=${sdkClientError.status}(${sdkClientError.status._code})`, + `${requestDetails.formattedRequestId} Fail to execute ${txConstructorName} transaction: transactionId=${transaction.transactionId}, callerName=${callerName}, status=${sdkClientError.status}(${sdkClientError.status._code})`, ); if (!transactionResponse) { throw predefined.INTERNAL_ERROR( - `${formattedRequestId} Transaction execution returns a null value: transactionId=${transaction.transactionId}, callerName=${callerName}, txConstructorName=${txConstructorName}`, + `${requestDetails.formattedRequestId} Transaction execution returns a null value: transactionId=${transaction.transactionId}, callerName=${callerName}, txConstructorName=${txConstructorName}`, ); } return transactionResponse; @@ -733,7 +749,7 @@ export class SDKClient { this.eventEmitter.emit(constants.EVENTS.EXECUTE_TRANSACTION, { transactionId, callerName, - requestId, + requestDetails, txConstructorName, operatorAccountId: this.clientMain.operatorAccountId!.toString(), interactingEntity, @@ -748,7 +764,7 @@ export class SDKClient { * @param {FileAppendTransaction} transaction - The batch transaction to execute. * @param {string} callerName - The name of the caller requesting the transaction. * @param {string} interactingEntity - The entity interacting with the transaction. - * @param {string} requestId - The ID of the request. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @param {boolean} shouldThrowHbarLimit - Flag to indicate whether to check HBAR limits. * @param {string} originalCallerAddress - The address of the original caller making the request. * @returns {Promise} - A promise that resolves when the batch execution is complete. @@ -758,11 +774,10 @@ export class SDKClient { transaction: FileAppendTransaction, callerName: string, interactingEntity: string, - requestId: string, + requestDetails: RequestDetails, shouldThrowHbarLimit: boolean, originalCallerAddress: string, ): Promise { - const formattedRequestId = formatRequestIdMessage(requestId); const txConstructorName = transaction.constructor.name; let transactionResponses: TransactionResponse[] | null = null; @@ -772,7 +787,7 @@ export class SDKClient { constants.EXECUTION_MODE.TRANSACTION, callerName, originalCallerAddress, - requestId, + requestDetails, ); if (shouldLimit) { throw predefined.HBAR_RATE_LIMIT_EXCEEDED; @@ -780,17 +795,17 @@ export class SDKClient { } try { - this.logger.info(`${formattedRequestId} Execute ${txConstructorName} transaction`); + this.logger.info(`${requestDetails.formattedRequestId} Execute ${txConstructorName} transaction`); transactionResponses = await transaction.executeAll(this.clientMain); this.logger.info( - `${formattedRequestId} Successfully execute all ${transactionResponses.length} ${txConstructorName} transactions: callerName=${callerName}, status=${Status.Success}(${Status.Success._code})`, + `${requestDetails.formattedRequestId} Successfully execute all ${transactionResponses.length} ${txConstructorName} transactions: callerName=${callerName}, status=${Status.Success}(${Status.Success._code})`, ); } catch (e: any) { const sdkClientError = new SDKClientError(e, e.message); this.logger.warn( - `${formattedRequestId} Fail to executeAll for ${txConstructorName} transaction: transactionId=${transaction.transactionId}, callerName=${callerName}, transactionType=${txConstructorName}, status=${sdkClientError.status}(${sdkClientError.status._code})`, + `${requestDetails.formattedRequestId} Fail to executeAll for ${txConstructorName} transaction: transactionId=${transaction.transactionId}, callerName=${callerName}, transactionType=${txConstructorName}, status=${sdkClientError.status}(${sdkClientError.status._code})`, ); throw sdkClientError; } finally { @@ -800,7 +815,7 @@ export class SDKClient { this.eventEmitter.emit(constants.EVENTS.EXECUTE_TRANSACTION, { transactionId: transactionResponse.transactionId.toString(), callerName, - requestId, + requestDetails, txConstructorName, operatorAccountId: this.clientMain.operatorAccountId!.toString(), interactingEntity, @@ -815,7 +830,7 @@ export class SDKClient { * Creates a file on the Hedera network using the provided call data. * @param {Uint8Array} callData - The data to be written to the file. * @param {Client} client - The Hedera client to use for the transaction. - * @param {string} requestId - The request ID associated with the transaction. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @param {string} callerName - The name of the caller creating the file. * @param {string} interactingEntity - The entity interacting with the transaction. * @param {string} originalCallerAddress - The address of the original caller making the request. @@ -825,12 +840,11 @@ export class SDKClient { async createFile( callData: Uint8Array, client: Client, - requestId: string, + requestDetails: RequestDetails, callerName: string, interactingEntity: string, originalCallerAddress: string, ): Promise { - const formattedRequestId = formatRequestIdMessage(requestId); const hexedCallData = Buffer.from(callData).toString('hex'); const fileCreateTx = new FileCreateTransaction() @@ -841,7 +855,7 @@ export class SDKClient { fileCreateTx, callerName, interactingEntity, - formattedRequestId, + requestDetails, true, originalCallerAddress, ); @@ -859,7 +873,7 @@ export class SDKClient { fileAppendTx, callerName, interactingEntity, - formattedRequestId, + requestDetails, true, originalCallerAddress, ); @@ -871,14 +885,16 @@ export class SDKClient { this.clientMain, callerName, interactingEntity, - requestId, + requestDetails, ); if (fileInfo.size.isZero()) { - this.logger.warn(`${requestId} File ${fileId} is empty.`); - throw new SDKClientError({}, `${requestId} Created file is empty. `); + this.logger.warn(`${requestDetails.formattedRequestId} File ${fileId} is empty.`); + throw new SDKClientError({}, `${requestDetails.formattedRequestId} Created file is empty. `); } - this.logger.trace(`${formattedRequestId} Created file with fileId: ${fileId} and file size ${fileInfo.size}`); + this.logger.trace( + `${requestDetails.formattedRequestId} Created file with fileId: ${fileId} and file size ${fileInfo.size}`, + ); } return fileId; @@ -888,7 +904,7 @@ export class SDKClient { * Deletes a file on the Hedera network and verifies its deletion. * * @param {FileId} fileId - The ID of the file to be deleted. - * @param {string} requestId - A unique identifier for the request. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @param {string} callerName - The name of the entity initiating the request. * @param {string} interactingEntity - The name of the interacting entity. * @param {string} originalCallerAddress - The address of the original caller making the request. @@ -897,13 +913,11 @@ export class SDKClient { */ async deleteFile( fileId: FileId, - requestId: string, + requestDetails: RequestDetails, callerName: string, interactingEntity: string, originalCallerAddress: string, ): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); - try { const fileDeleteTx = new FileDeleteTransaction() .setFileId(fileId) @@ -914,7 +928,7 @@ export class SDKClient { fileDeleteTx, callerName, interactingEntity, - requestId, + requestDetails, false, originalCallerAddress, ); @@ -924,16 +938,16 @@ export class SDKClient { this.clientMain, callerName, interactingEntity, - requestId, + requestDetails, ); if (fileInfo.isDeleted) { - this.logger.trace(`${requestIdPrefix} Deleted file with fileId: ${fileId}`); + this.logger.trace(`${requestDetails.formattedRequestId} Deleted file with fileId: ${fileId}`); } else { - this.logger.warn(`${requestIdPrefix} Fail to delete file with fileId: ${fileId} `); + this.logger.warn(`${requestDetails.formattedRequestId} Fail to delete file with fileId: ${fileId} `); } } catch (error: any) { - this.logger.warn(`${requestIdPrefix} ${error['message']} `); + this.logger.warn(`${requestDetails.formattedRequestId} ${error['message']} `); } } @@ -966,26 +980,25 @@ export class SDKClient { * * @param {string} transactionId - The ID of the transaction to retrieve metrics for. * @param {string} callerName - The name of the caller requesting the transaction record. - * @param {string} requestId - The request ID for tracking the request. * @param {string} txConstructorName - The name of the transaction constructor. * @param {string} operatorAccountId - The account ID of the operator. - * @returns {Promise} - A promise that resolves to an object containing transaction metrics. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - A promise that resolves to an object containing transaction metrics. * @throws {SDKClientError} - Throws an error if an issue occurs during the transaction record query. */ public async getTransactionRecordMetrics( transactionId: string, callerName: string, - requestId: string, txConstructorName: string, operatorAccountId: string, + requestDetails: RequestDetails, ): Promise { let gasUsed: number = 0; let transactionFee: number = 0; let txRecordChargeAmount: number = 0; - const formattedRequestId = formatRequestIdMessage(requestId); try { this.logger.trace( - `${formattedRequestId} Get transaction record via consensus node: transactionId=${transactionId}, txConstructorName=${txConstructorName}, callerName=${callerName}`, + `${requestDetails.formattedRequestId} Get transaction record via consensus node: transactionId=${transactionId}, txConstructorName=${txConstructorName}, callerName=${callerName}`, ); const transactionRecord = await new TransactionRecordQuery() @@ -1006,7 +1019,7 @@ export class SDKClient { const sdkClientError = new SDKClientError(e, e.message); this.logger.warn( e, - `${formattedRequestId} Error raised during TransactionRecordQuery: transactionId=${transactionId}, txConstructorName=${txConstructorName}, callerName=${callerName}, recordStatus=${sdkClientError.status} (${sdkClientError.status._code}), cost=${transactionFee}, gasUsed=${gasUsed}`, + `${requestDetails.formattedRequestId} Error raised during TransactionRecordQuery: transactionId=${transactionId}, txConstructorName=${txConstructorName}, callerName=${callerName}, recordStatus=${sdkClientError.status} (${sdkClientError.status._code}), cost=${transactionFee}, gasUsed=${gasUsed}`, ); throw sdkClientError; } diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts index 90a90b918c..c7d39bac5e 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts @@ -23,6 +23,7 @@ import { Logger } from 'pino'; import { IEthAddressHbarSpendingPlan } from '../../types/hbarLimiter/ethAddressHbarSpendingPlan'; import { EthAddressHbarSpendingPlanNotFoundError } from '../../types/hbarLimiter/errors'; import { EthAddressHbarSpendingPlan } from '../../entities/hbarLimiter/ethAddressHbarSpendingPlan'; +import { RequestDetails } from '../../../types'; export class EthAddressHbarSpendingPlanRepository { private readonly collectionKey = 'ethAddressHbarSpendingPlan'; @@ -49,11 +50,12 @@ export class EthAddressHbarSpendingPlanRepository { * Finds an {@link EthAddressHbarSpendingPlan} for an ETH address. * * @param {string} ethAddress - The ETH address to search for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - The associated plan for the ETH address. */ - async findByAddress(ethAddress: string): Promise { + async findByAddress(ethAddress: string, requestDetails: RequestDetails): Promise { const key = this.getKey(ethAddress); - const addressPlan = await this.cache.getAsync(key, 'findByAddress'); + const addressPlan = await this.cache.getAsync(key, 'findByAddress', requestDetails); if (!addressPlan) { throw new EthAddressHbarSpendingPlanNotFoundError(ethAddress); } @@ -65,11 +67,12 @@ export class EthAddressHbarSpendingPlanRepository { * Saves an {@link EthAddressHbarSpendingPlan} to the cache, linking the plan to the ETH address. * * @param {IEthAddressHbarSpendingPlan} addressPlan - The plan to save. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves when the ETH address is linked to the plan. */ - async save(addressPlan: IEthAddressHbarSpendingPlan): Promise { + async save(addressPlan: IEthAddressHbarSpendingPlan, requestDetails: RequestDetails): Promise { const key = this.getKey(addressPlan.ethAddress); - await this.cache.set(key, addressPlan, 'save', this.threeMonthsInMillis); + await this.cache.set(key, addressPlan, 'save', requestDetails, this.threeMonthsInMillis); this.logger.trace(`Saved EthAddressHbarSpendingPlan with address ${addressPlan.ethAddress}`); } @@ -77,11 +80,12 @@ export class EthAddressHbarSpendingPlanRepository { * Deletes an {@link EthAddressHbarSpendingPlan} from the cache, unlinking the plan from the ETH address. * * @param {string} ethAddress - The ETH address to unlink the plan from. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves when the ETH address is unlinked from the plan. */ - async delete(ethAddress: string): Promise { + async delete(ethAddress: string, requestDetails: RequestDetails): Promise { const key = this.getKey(ethAddress); - await this.cache.delete(key, 'delete'); + await this.cache.delete(key, 'delete', requestDetails); this.logger.trace(`Deleted EthAddressHbarSpendingPlan with address ${ethAddress}`); } diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts index 3bfbac5d3c..13a9a46160 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts @@ -27,6 +27,7 @@ import { IDetailedHbarSpendingPlan, IHbarSpendingPlan } from '../../types/hbarLi import { HbarSpendingRecord } from '../../entities/hbarLimiter/hbarSpendingRecord'; import { SubscriptionType } from '../../types/hbarLimiter/subscriptionType'; import { HbarSpendingPlan } from '../../entities/hbarLimiter/hbarSpendingPlan'; +import { RequestDetails } from '../../../types'; export class HbarSpendingPlanRepository { private readonly collectionKey = 'hbarSpendingPlan'; @@ -51,13 +52,14 @@ export class HbarSpendingPlanRepository { } /** - * Gets a hbar spending plan by ID. - * @param id - The ID of the plan to get. - * @returns {Promise} - The hbar spending plan object. + * Gets an HBar spending plan by ID. + * @param {string} id - The ID of the plan to get. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - The HBar spending plan object. */ - async findById(id: string): Promise { + async findById(id: string, requestDetails: RequestDetails): Promise { const key = this.getKey(id); - const plan = await this.cache.getAsync(key, 'findById'); + const plan = await this.cache.getAsync(key, 'findById', requestDetails); if (!plan) { throw new HbarSpendingPlanNotFoundError(id); } @@ -69,25 +71,27 @@ export class HbarSpendingPlanRepository { } /** - * Gets a hbar spending plan by ID with detailed information (spendingHistory and spentToday). - * @param id - The ID of the plan. - * @returns {Promise} - The detailed hbar spending plan object. + * Gets an HBar spending plan by ID with detailed information (spendingHistory and spentToday). + * @param {string} id - The ID of the plan. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - The detailed HBar spending plan object. */ - async findByIdWithDetails(id: string): Promise { - const plan = await this.findById(id); + async findByIdWithDetails(id: string, requestDetails: RequestDetails): Promise { + const plan = await this.findById(id, requestDetails); return new HbarSpendingPlan({ ...plan, spendingHistory: [], - spentToday: await this.getSpentToday(id), + spentToday: await this.getSpentToday(id, requestDetails), }); } /** - * Creates a new hbar spending plan. - * @param subscriptionType - The subscription type of the plan to create. - * @returns {Promise} - The created hbar spending plan object. + * Creates a new HBar spending plan. + * @param {SubscriptionType} subscriptionType - The subscription type of the plan to create. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - The created HBar spending plan object. */ - async create(subscriptionType: SubscriptionType): Promise { + async create(subscriptionType: SubscriptionType, requestDetails: RequestDetails): Promise { const plan: IDetailedHbarSpendingPlan = { id: uuidV4(randomBytes(16)), subscriptionType, @@ -98,104 +102,121 @@ export class HbarSpendingPlanRepository { }; this.logger.trace(`Creating HbarSpendingPlan with ID ${plan.id}...`); const key = this.getKey(plan.id); - await this.cache.set(key, plan, 'create', this.threeMonthsInMillis); + await this.cache.set(key, plan, 'create', requestDetails, this.threeMonthsInMillis); return new HbarSpendingPlan(plan); } /** - * Verify that an hbar spending plan exists and is active. - * @param id - The ID of the plan. + * Verify that an HBar spending plan exists and is active. + * @param {string} id - The ID of the plan. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves if the plan exists and is active, or rejects if not. */ - async checkExistsAndActive(id: string): Promise { - const plan = await this.findById(id); + async checkExistsAndActive(id: string, requestDetails: RequestDetails): Promise { + const plan = await this.findById(id, requestDetails); if (!plan.active) { throw new HbarSpendingPlanNotActiveError(id); } } /** - * Gets the spending history for a hbar spending plan. - * @param id - The ID of the plan. + * Gets the spending history for an HBar spending plan. + * @param {string} id - The ID of the plan. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the spending history. */ - async getSpendingHistory(id: string): Promise { - await this.checkExistsAndActive(id); + async getSpendingHistory(id: string, requestDetails: RequestDetails): Promise { + await this.checkExistsAndActive(id, requestDetails); this.logger.trace(`Retrieving spending history for HbarSpendingPlan with ID ${id}...`); const key = this.getSpendingHistoryKey(id); - const spendingHistory = await this.cache.lRange(key, 0, -1, 'getSpendingHistory'); + const spendingHistory = await this.cache.lRange( + key, + 0, + -1, + 'getSpendingHistory', + requestDetails, + ); return spendingHistory.map((entry) => new HbarSpendingRecord(entry)); } /** * Adds spending to a plan's spending history. - * @param id - The ID of the plan. - * @param amount - The amount to add to the plan's spending. + * @param {string} id - The ID of the plan. + * @param {number} amount - The amount to add to the plan's spending. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the new length of the spending history. */ - async addAmountToSpendingHistory(id: string, amount: number): Promise { - await this.checkExistsAndActive(id); + async addAmountToSpendingHistory(id: string, amount: number, requestDetails: RequestDetails): Promise { + await this.checkExistsAndActive(id, requestDetails); this.logger.trace(`Adding ${amount} to spending history for HbarSpendingPlan with ID ${id}...`); const key = this.getSpendingHistoryKey(id); const entry: IHbarSpendingRecord = { amount, timestamp: new Date() }; - return this.cache.rPush(key, entry, 'addAmountToSpendingHistory'); + return this.cache.rPush(key, entry, 'addAmountToSpendingHistory', requestDetails); } /** - * Gets the amount spent today for an hbar spending plan. - * @param id - The ID of the plan. + * Gets the amount spent today for an HBar spending plan. + * @param {string} id - The ID of the plan. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the amount spent today. */ - async getSpentToday(id: string): Promise { - await this.checkExistsAndActive(id); + async getSpentToday(id: string, requestDetails: RequestDetails): Promise { + await this.checkExistsAndActive(id, requestDetails); this.logger.trace(`Retrieving spentToday for HbarSpendingPlan with ID ${id}...`); const key = this.getSpentTodayKey(id); - return this.cache.getAsync(key, 'getSpentToday').then((spentToday) => parseInt(spentToday ?? '0')); + return this.cache.getAsync(key, 'getSpentToday', requestDetails).then((spentToday) => parseInt(spentToday ?? '0')); } /** * Resets the amount spent today for all hbar spending plans. * @returns {Promise} - A promise that resolves when the operation is complete. */ - async resetAllSpentTodayEntries(): Promise { + async resetAllSpentTodayEntries(requestDetails: RequestDetails): Promise { this.logger.trace('Resetting the spentToday entries for all HbarSpendingPlans...'); const callerMethod = this.resetAllSpentTodayEntries.name; - const keys = await this.cache.keys(`${this.collectionKey}:*:spentToday`, callerMethod); - await Promise.all(keys.map((key) => this.cache.delete(key, callerMethod))); + const keys = await this.cache.keys(`${this.collectionKey}:*:spentToday`, callerMethod, requestDetails); + await Promise.all(keys.map((key) => this.cache.delete(key, callerMethod, requestDetails))); this.logger.trace(`Successfully reset ${keys.length} spentToday entries for HbarSpendingPlans.`); } /** * Adds an amount to the amount spent today for a plan. - * @param id - The ID of the plan. - * @param amount - The amount to add. + * @param {string} id - The ID of the plan. + * @param {number} amount - The amount to add. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves when the operation is complete. */ - async addAmountToSpentToday(id: string, amount: number): Promise { - await this.checkExistsAndActive(id); + async addAmountToSpentToday(id: string, amount: number, requestDetails: RequestDetails): Promise { + await this.checkExistsAndActive(id, requestDetails); const key = this.getSpentTodayKey(id); - if (!(await this.cache.getAsync(key, 'addAmountToSpentToday'))) { + if (!(await this.cache.getAsync(key, 'addAmountToSpentToday', requestDetails))) { this.logger.trace(`No spending yet for HbarSpendingPlan with ID ${id}, setting spentToday to ${amount}...`); - await this.cache.set(key, amount, 'addAmountToSpentToday', this.oneDayInMillis); + await this.cache.set(key, amount, 'addAmountToSpentToday', requestDetails, this.oneDayInMillis); } else { this.logger.trace(`Adding ${amount} to spentToday for HbarSpendingPlan with ID ${id}...`); - await this.cache.incrBy(key, amount, 'addAmountToSpentToday'); + await this.cache.incrBy(key, amount, 'addAmountToSpentToday', requestDetails); } } /** - * Finds all active hbar spending plans by subscription type. + * Finds all active HBar spending plans by subscription type. * @param {SubscriptionType} subscriptionType - The subscription type to filter by. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the active spending plans. */ - async findAllActiveBySubscriptionType(subscriptionType: SubscriptionType): Promise { + async findAllActiveBySubscriptionType( + subscriptionType: SubscriptionType, + requestDetails: RequestDetails, + ): Promise { const callerMethod = this.findAllActiveBySubscriptionType.name; - const keys = await this.cache.keys(`${this.collectionKey}:*`, callerMethod); - const plans = await Promise.all(keys.map((key) => this.cache.getAsync(key, callerMethod))); + const keys = await this.cache.keys(`${this.collectionKey}:*`, callerMethod, requestDetails); + const plans = await Promise.all( + keys.map((key) => this.cache.getAsync(key, callerMethod, requestDetails)), + ); return Promise.all( plans .filter((plan) => plan.subscriptionType === subscriptionType && plan.active) @@ -205,7 +226,7 @@ export class HbarSpendingPlanRepository { ...plan, createdAt: new Date(plan.createdAt), spendingHistory: [], - spentToday: await this.getSpentToday(plan.id), + spentToday: await this.getSpentToday(plan.id, requestDetails), }), ), ); diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts index 564cd301bf..82849ec2ed 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts @@ -23,6 +23,7 @@ import { Logger } from 'pino'; import { IIPAddressHbarSpendingPlan } from '../../types/hbarLimiter/ipAddressHbarSpendingPlan'; import { IPAddressHbarSpendingPlanNotFoundError } from '../../types/hbarLimiter/errors'; import { IPAddressHbarSpendingPlan } from '../../entities/hbarLimiter/ipAddressHbarSpendingPlan'; +import { RequestDetails } from '../../../types'; export class IPAddressHbarSpendingPlanRepository { private readonly collectionKey = 'ipAddressHbarSpendingPlan'; @@ -51,13 +52,13 @@ export class IPAddressHbarSpendingPlanRepository { * @param {string} ipAddress - The IP address to search for. * @returns {Promise} - The associated plan for the IP address. */ - async findByAddress(ipAddress: string): Promise { + async findByAddress(ipAddress: string, requestDetails: RequestDetails): Promise { const key = this.getKey(ipAddress); - const addressPlan = await this.cache.getAsync(key, 'findByAddress'); + const addressPlan = await this.cache.getAsync(key, 'findByAddress', requestDetails); if (!addressPlan) { throw new IPAddressHbarSpendingPlanNotFoundError(ipAddress); } - this.logger.trace(`Retrieved IPAddressHbarSpendingPlan with address ${ipAddress}`); + this.logger.trace(`Retrieved link between IP address and HbarSpendingPlan with ID ${addressPlan.planId}`); return new IPAddressHbarSpendingPlan(addressPlan); } @@ -67,10 +68,10 @@ export class IPAddressHbarSpendingPlanRepository { * @param {IIPAddressHbarSpendingPlan} addressPlan - The plan to save. * @returns {Promise} - A promise that resolves when the IP address is linked to the plan. */ - async save(addressPlan: IIPAddressHbarSpendingPlan): Promise { + async save(addressPlan: IIPAddressHbarSpendingPlan, requestDetails: RequestDetails): Promise { const key = this.getKey(addressPlan.ipAddress); - await this.cache.set(key, addressPlan, 'save', this.threeMonthsInMillis); - this.logger.trace(`Saved IPAddressHbarSpendingPlan with address ${addressPlan.ipAddress}`); + await this.cache.set(key, addressPlan, 'save', requestDetails, this.threeMonthsInMillis); + this.logger.trace(`Linked new IP address to HbarSpendingPlan with ID ${addressPlan.planId}`); } /** @@ -79,10 +80,14 @@ export class IPAddressHbarSpendingPlanRepository { * @param {string} ipAddress - The IP address to unlink the plan from. * @returns {Promise} - A promise that resolves when the IP address is unlinked from the plan. */ - async delete(ipAddress: string): Promise { + async delete(ipAddress: string, requestDetails: RequestDetails): Promise { const key = this.getKey(ipAddress); - await this.cache.delete(key, 'delete'); - this.logger.trace(`Deleted IPAddressHbarSpendingPlan with address ${ipAddress}`); + const ipAddressSpendingPlan = await this.cache.getAsync(key, 'delete', requestDetails); + await this.cache.delete(key, 'delete', requestDetails); + const errorMessage = ipAddressSpendingPlan + ? `Removed IP address from HbarSpendingPlan with ID ${ipAddressSpendingPlan.planId}` + : `Trying to remove an IP address, which is not linked to a spending plan`; + this.logger.trace(errorMessage); } /** diff --git a/packages/relay/src/lib/db/types/hbarLimiter/errors.ts b/packages/relay/src/lib/db/types/hbarLimiter/errors.ts index 1e089949c7..bacfd0562d 100644 --- a/packages/relay/src/lib/db/types/hbarLimiter/errors.ts +++ b/packages/relay/src/lib/db/types/hbarLimiter/errors.ts @@ -41,7 +41,7 @@ export class EthAddressHbarSpendingPlanNotFoundError extends Error { export class IPAddressHbarSpendingPlanNotFoundError extends Error { constructor(ipAddress: string) { - super(`IPAddressHbarSpendingPlan with address ${ipAddress} not found`); + super(`IPAddressHbarSpendingPlan not found`); this.name = 'IPAddressHbarSpendingPlanNotFoundError'; } } diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index 95f3ed8ae1..43a05abfe6 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -21,13 +21,13 @@ import crypto from 'crypto'; import { Logger } from 'pino'; import { Eth } from '../index'; -import { Utils } from './../utils'; +import { Utils } from '../utils'; import constants from './constants'; import { Precheck } from './precheck'; import { MirrorNodeClient } from './clients'; import { Counter, Registry } from 'prom-client'; import { IAccountInfo } from './types/mirrorNode'; -import { LogsBloomUtils } from './../logsBloomUtils'; +import { LogsBloomUtils } from '../logsBloomUtils'; import { DebugService } from './services/debugService'; import { SDKClientError } from './errors/SDKClientError'; import { Transaction as EthersTransaction } from 'ethers'; @@ -41,7 +41,7 @@ import { IDebugService } from './services/debugService/IDebugService'; import { MirrorNodeClientError } from './errors/MirrorNodeClientError'; import { IReceiptRootHash, ReceiptsRootUtils } from '../receiptsRootUtils'; import { IFilterService } from './services/ethService/ethFilterService/IFilterService'; -import { IFeeHistory, IContractCallRequest, IContractCallResponse, ITransactionReceipt } from './types'; +import { IFeeHistory, IContractCallRequest, IContractCallResponse, ITransactionReceipt, RequestDetails } from './types'; import { isHex, toHash32, @@ -233,17 +233,17 @@ export class EthImpl implements Eth { private readonly ethExecutionsCounter: Counter; /** - * The Common Service implemntation that contains logic shared by other services. + * The Common Service implementation that contains logic shared by other services. */ private readonly common: CommonService; /** - * The Filter Service implemntation that takes care of all filter API operations. + * The Filter Service implementation that takes care of all filter API operations. */ private readonly filterServiceImpl: FilterService; /** - * The Debug Service implemntation that takes care of all filter API operations. + * The Debug Service implementation that takes care of all filter API operations. */ private readonly debugServiceImpl: DebugService; @@ -306,8 +306,8 @@ export class EthImpl implements Eth { * This method is implemented to always return an empty array. This is in alignment * with the behavior of Infura. */ - accounts(requestIdPrefix?: string): never[] { - this.logger.trace(`${requestIdPrefix} accounts()`); + accounts(requestDetails: RequestDetails): never[] { + this.logger.trace(`${requestDetails.formattedRequestId} accounts()`); return EthImpl.accounts; } @@ -325,8 +325,9 @@ export class EthImpl implements Eth { blockCount: number, newestBlock: string, rewardPercentiles: Array | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; const maxResults = process.env.TEST === 'true' ? constants.DEFAULT_FEE_HISTORY_MAX_RESULTS @@ -337,11 +338,11 @@ export class EthImpl implements Eth { ); try { - const latestBlockNumber = await this.translateBlockTag(EthImpl.blockLatest, requestIdPrefix); + const latestBlockNumber = await this.translateBlockTag(EthImpl.blockLatest, requestDetails); const newestBlockNumber = newestBlock == EthImpl.blockLatest || newestBlock == EthImpl.blockPending ? latestBlockNumber - : await this.translateBlockTag(newestBlock, requestIdPrefix); + : await this.translateBlockTag(newestBlock, requestDetails); if (newestBlockNumber > latestBlockNumber) { return predefined.REQUEST_BEYOND_HEAD_BLOCK(newestBlockNumber, latestBlockNumber); @@ -359,14 +360,14 @@ export class EthImpl implements Eth { blockCount = 1; oldestBlock = 1; } - const gasPriceFee = await this.gasPrice(requestIdPrefix); + const gasPriceFee = await this.gasPrice(requestDetails); feeHistory = this.getRepeatedFeeHistory(blockCount, oldestBlock, rewardPercentiles, gasPriceFee); } else { // once we finish testing and refining Fixed Fee method, we can remove this else block to clean up code const cacheKey = `${constants.CACHE_KEY.FEE_HISTORY}_${blockCount}_${newestBlock}_${rewardPercentiles?.join( '', )}`; - const cachedFeeHistory = await this.cacheService.getAsync(cacheKey, EthImpl.ethFeeHistory, requestIdPrefix); + const cachedFeeHistory = await this.cacheService.getAsync(cacheKey, EthImpl.ethFeeHistory, requestDetails); if (cachedFeeHistory) { feeHistory = cachedFeeHistory; @@ -376,11 +377,11 @@ export class EthImpl implements Eth { newestBlockNumber, latestBlockNumber, rewardPercentiles, - requestIdPrefix, + requestDetails, ); } if (newestBlock != EthImpl.blockLatest && newestBlock != EthImpl.blockPending) { - await this.cacheService.set(cacheKey, feeHistory, EthImpl.ethFeeHistory, undefined, requestIdPrefix); + await this.cacheService.set(cacheKey, feeHistory, EthImpl.ethFeeHistory, requestDetails); } } @@ -391,15 +392,15 @@ export class EthImpl implements Eth { } } - private async getFeeByBlockNumber(blockNumber: number, requestIdPrefix?: string): Promise { + private async getFeeByBlockNumber(blockNumber: number, requestDetails: RequestDetails): Promise { let fee = 0; try { - const block = await this.mirrorNodeClient.getBlock(blockNumber, requestIdPrefix); - fee = await this.getFeeWeibars(EthImpl.ethFeeHistory, requestIdPrefix, `lte:${block.timestamp.to}`); + const block = await this.mirrorNodeClient.getBlock(blockNumber, requestDetails); + fee = await this.getFeeWeibars(EthImpl.ethFeeHistory, requestDetails, `lte:${block.timestamp.to}`); } catch (error) { this.logger.warn( error, - `${requestIdPrefix} Fee history cannot retrieve block or fee. Returning ${fee} fee for block ${blockNumber}`, + `${requestDetails.formattedRequestId} Fee history cannot retrieve block or fee. Returning ${fee} fee for block ${blockNumber}`, ); } @@ -436,9 +437,9 @@ export class EthImpl implements Eth { newestBlockNumber: number, latestBlockNumber: number, rewardPercentiles: Array | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { - // include newest block number in the total block count + // include the newest block number in the total block count const oldestBlockNumber = Math.max(0, newestBlockNumber - blockCount + 1); const shouldIncludeRewards = Array.isArray(rewardPercentiles) && rewardPercentiles.length > 0; const feeHistory: IFeeHistory = { @@ -449,18 +450,18 @@ export class EthImpl implements Eth { // get fees from oldest to newest blocks for (let blockNumber = oldestBlockNumber; blockNumber <= newestBlockNumber; blockNumber++) { - const fee = await this.getFeeByBlockNumber(blockNumber, requestIdPrefix); + const fee = await this.getFeeByBlockNumber(blockNumber, requestDetails); feeHistory.baseFeePerGas?.push(fee); feeHistory.gasUsedRatio?.push(EthImpl.defaultGasUsedRatio); } // get latest block fee - let nextBaseFeePerGas = _.last(feeHistory.baseFeePerGas); + let nextBaseFeePerGas: string = _.last(feeHistory.baseFeePerGas); if (latestBlockNumber > newestBlockNumber) { // get next block fee if the newest block is not the latest - nextBaseFeePerGas = await this.getFeeByBlockNumber(newestBlockNumber + 1, requestIdPrefix); + nextBaseFeePerGas = await this.getFeeByBlockNumber(newestBlockNumber + 1, requestDetails); } if (nextBaseFeePerGas) { @@ -474,24 +475,26 @@ export class EthImpl implements Eth { return feeHistory; } - private async getFeeWeibars(callerName: string, requestIdPrefix?: string, timestamp?: string): Promise { + private async getFeeWeibars(callerName: string, requestDetails: RequestDetails, timestamp?: string): Promise { let networkFees; try { - networkFees = await this.mirrorNodeClient.getNetworkFees(timestamp, undefined, requestIdPrefix); + networkFees = await this.mirrorNodeClient.getNetworkFees(requestDetails, timestamp, undefined); } catch (e: any) { this.logger.warn( e, - `${requestIdPrefix} Mirror Node threw an error while retrieving fees. Fallback to consensus node.`, + `${requestDetails.formattedRequestId} Mirror Node threw an error while retrieving fees. Fallback to consensus node.`, ); } if (_.isNil(networkFees)) { - this.logger.debug(`${requestIdPrefix} Mirror Node returned no network fees. Fallback to consensus node.`); + this.logger.debug( + `${requestDetails.formattedRequestId} Mirror Node returned no network fees. Fallback to consensus node.`, + ); networkFees = { fees: [ { - gas: await this.hapiService.getSDKClient().getTinyBarGasFee(callerName, requestIdPrefix), + gas: await this.hapiService.getSDKClient().getTinyBarGasFee(callerName, requestDetails), transaction_type: EthImpl.ethTxType, }, ], @@ -514,27 +517,27 @@ export class EthImpl implements Eth { /** * Gets the most recent block number. */ - async blockNumber(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} blockNumber()`); - return await this.common.getLatestBlockNumber(requestIdPrefix); + async blockNumber(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} blockNumber()`); + return await this.common.getLatestBlockNumber(requestDetails); } /** * Gets the most recent block number and timestamp.to which represents the block finality. */ - async blockNumberTimestamp(caller: string, requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} blockNumber()`); + async blockNumberTimestamp(caller: string, requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} blockNumber()`); const cacheKey = `${constants.CACHE_KEY.ETH_BLOCK_NUMBER}`; - const blocksResponse = await this.mirrorNodeClient.getLatestBlock(requestIdPrefix); + const blocksResponse = await this.mirrorNodeClient.getLatestBlock(requestDetails); const blocks = blocksResponse !== null ? blocksResponse.blocks : null; if (Array.isArray(blocks) && blocks.length > 0) { const currentBlock = numberTo0x(blocks[0].number); const timestamp = blocks[0].timestamp.to; const blockTimeStamp: LatestBlockNumberTimestamp = { blockNumber: currentBlock, timeStampTo: timestamp }; // save the latest block number in cache - await this.cacheService.set(cacheKey, currentBlock, caller, this.ethBlockNumberCacheTtlMs, requestIdPrefix); + await this.cacheService.set(cacheKey, currentBlock, caller, requestDetails, this.ethBlockNumberCacheTtlMs); return blockTimeStamp; } @@ -547,8 +550,8 @@ export class EthImpl implements Eth { * the same value. This can be specified via an environment variable * `CHAIN_ID`. */ - chainId(requestIdPrefix?: string): string { - this.logger.trace(`${requestIdPrefix} chainId()`); + chainId(requestDetails: RequestDetails): string { + this.logger.trace(`${requestDetails.formattedRequestId} chainId()`); return this.chain; } @@ -559,8 +562,9 @@ export class EthImpl implements Eth { async estimateGas( transaction: IContractCallRequest, _blockParam: string | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; const callData = transaction.data ? transaction.data : transaction.input; const callDataSize = callData ? callData.length : 0; @@ -575,13 +579,13 @@ export class EthImpl implements Eth { ); try { - const response = await this.estimateGasFromMirrorNode(transaction, requestIdPrefix); + const response = await this.estimateGasFromMirrorNode(transaction, requestDetails); if (response?.result) { this.logger.info(`${requestIdPrefix} Returning gas: ${response.result}`); return prepend0x(trimPrecedingZeros(response.result)); } else { this.logger.error(`${requestIdPrefix} No gas estimate returned from mirror-node: ${JSON.stringify(response)}`); - return this.predefinedGasForTransaction(transaction, requestIdPrefix); + return this.predefinedGasForTransaction(transaction, requestDetails); } } catch (e: any) { this.logger.error( @@ -591,7 +595,7 @@ export class EthImpl implements Eth { if (this.estimateGasThrows && e instanceof MirrorNodeClientError && e.isContractRevertOpcodeExecuted()) { return predefined.CONTRACT_REVERT(e.detail ?? e.message, e.data); } - return this.predefinedGasForTransaction(transaction, requestIdPrefix, e); + return this.predefinedGasForTransaction(transaction, requestDetails, e); } } @@ -599,16 +603,16 @@ export class EthImpl implements Eth { * Executes an estimate contract call gas request in the mirror node. * * @param {IContractCallRequest} transaction The transaction data for the contract call. - * @param requestIdPrefix The prefix for the request ID. + * @param {RequestDetails} requestDetails The request details for logging and tracking. * @returns {Promise} the response from the mirror node */ private async estimateGasFromMirrorNode( transaction: IContractCallRequest, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { - await this.contractCallFormat(transaction, requestIdPrefix); + await this.contractCallFormat(transaction, requestDetails); const callData = { ...transaction, estimate: true }; - return this.mirrorNodeClient.postContractCall(callData, requestIdPrefix); + return this.mirrorNodeClient.postContractCall(callData, requestDetails); } /** @@ -616,15 +620,16 @@ export class EthImpl implements Eth { * This method is used when the mirror node fails to return a gas estimate. * * @param {IContractCallRequest} transaction The transaction data for the contract call. - * @param {string} requestIdPrefix The prefix for the request ID. + * @param {RequestDetails} requestDetails The request details for logging and tracking. * @param error (Optional) received error from the mirror-node contract call request. * @returns {Promise} the calculated gas cost for the transaction */ private async predefinedGasForTransaction( transaction: IContractCallRequest, - requestIdPrefix?: string, + requestDetails: RequestDetails, error?: any, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; const isSimpleTransfer = !!transaction?.to && (!transaction.data || transaction.data === '0x'); const isContractCall = !!transaction?.to && transaction?.data && transaction.data.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH; @@ -640,7 +645,7 @@ export class EthImpl implements Eth { ); } // when account exists return default base gas - if (await this.getAccount(transaction.to!, requestIdPrefix)) { + if (await this.getAccount(transaction.to!, requestDetails)) { this.logger.warn(`${requestIdPrefix} Returning predefined gas for simple transfer: ${EthImpl.gasTxBaseCost}`); return EthImpl.gasTxBaseCost; } @@ -677,32 +682,32 @@ export class EthImpl implements Eth { * if not found, it fetches it from the mirror node. * * @param {string} address the address of the account - * @param {string} requestIdPrefix the prefix for the request ID + * @param {RequestDetails} requestDetails the request details for logging and tracking * @returns {Promise} the account (if such exists for the given address) */ - private async getAccount(address: string, requestIdPrefix?: string): Promise { + private async getAccount(address: string, requestDetails: RequestDetails): Promise { const key = `${constants.CACHE_KEY.ACCOUNT}_${address}`; - let account = await this.cacheService.getAsync(key, EthImpl.ethEstimateGas, requestIdPrefix); + let account = await this.cacheService.getAsync(key, EthImpl.ethEstimateGas, requestDetails); if (!account) { - account = await this.mirrorNodeClient.getAccount(address, requestIdPrefix); - await this.cacheService.set(key, account, EthImpl.ethEstimateGas, undefined, requestIdPrefix); + account = await this.mirrorNodeClient.getAccount(address, requestDetails); + await this.cacheService.set(key, account, EthImpl.ethEstimateGas, requestDetails); } return account; } /** * Perform value format precheck before making contract call towards the mirror node - * @param transaction - * @param requestIdPrefix + * @param {IContractCallRequest} transaction the transaction object + * @param {RequestDetails} requestDetails the request details for logging and tracking */ - async contractCallFormat(transaction: IContractCallRequest, requestIdPrefix?: string): Promise { + async contractCallFormat(transaction: IContractCallRequest, requestDetails: RequestDetails): Promise { if (transaction.value) { transaction.value = weibarHexToTinyBarInt(transaction.value); } if (transaction.gasPrice) { transaction.gasPrice = parseInt(transaction.gasPrice.toString()); } else { - transaction.gasPrice = await this.gasPrice(requestIdPrefix).then((gasPrice) => parseInt(gasPrice)); + transaction.gasPrice = await this.gasPrice(requestDetails).then((gasPrice) => parseInt(gasPrice)); } if (transaction.gas) { transaction.gas = parseInt(transaction.gas.toString()); @@ -712,7 +717,7 @@ export class EthImpl implements Eth { transaction.from = this.hapiService.getMainClientInstance().operatorPublicKey?.toEvmAddress(); } else { const operatorId = this.hapiService.getMainClientInstance().operatorAccountId!.toString(); - const operatorAccount = await this.getAccount(operatorId, requestIdPrefix); + const operatorAccount = await this.getAccount(operatorId, requestDetails); transaction.from = operatorAccount?.evm_address; } } @@ -737,159 +742,160 @@ export class EthImpl implements Eth { * @returns {Promise} The current gas price in weibars as a hexadecimal string. * @throws Will throw an error if unable to retrieve the gas price. */ - async gasPrice(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} eth_gasPrice`); + async gasPrice(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} eth_gasPrice`); try { let gasPrice: number | undefined = await this.cacheService.getAsync( constants.CACHE_KEY.GAS_PRICE, EthImpl.ethGasPrice, - requestIdPrefix, + requestDetails, ); if (!gasPrice) { - gasPrice = Utils.addPercentageBufferToGasPrice(await this.getFeeWeibars(EthImpl.ethGasPrice, requestIdPrefix)); + gasPrice = Utils.addPercentageBufferToGasPrice(await this.getFeeWeibars(EthImpl.ethGasPrice, requestDetails)); await this.cacheService.set( constants.CACHE_KEY.GAS_PRICE, gasPrice, EthImpl.ethGasPrice, + requestDetails, this.ethGasPRiceCacheTtlMs, - requestIdPrefix, ); } return numberTo0x(gasPrice); } catch (error) { - throw this.common.genericErrorHandler(error, `${requestIdPrefix} Failed to retrieve gasPrice`); + throw this.common.genericErrorHandler(error, `${requestDetails.formattedRequestId} Failed to retrieve gasPrice`); } } /** * Gets whether this "Ethereum client" is a miner. We don't mine, so this always returns false. */ - async mining(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} mining()`); + async mining(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} mining()`); return false; } /** * TODO Needs docs, or be removed? */ - async submitWork(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} submitWork()`); + async submitWork(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} submitWork()`); return false; } /** * TODO Needs docs, or be removed? */ - async syncing(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} syncing()`); + async syncing(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} syncing()`); return false; } /** * Always returns null. There are no uncles in Hedera. */ - async getUncleByBlockHashAndIndex(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} getUncleByBlockHashAndIndex()`); + async getUncleByBlockHashAndIndex(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} getUncleByBlockHashAndIndex()`); return null; } /** * Always returns null. There are no uncles in Hedera. */ - async getUncleByBlockNumberAndIndex(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} getUncleByBlockNumberAndIndex()`); + async getUncleByBlockNumberAndIndex(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} getUncleByBlockNumberAndIndex()`); return null; } /** * Always returns '0x0'. There are no uncles in Hedera. */ - async getUncleCountByBlockHash(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} getUncleCountByBlockHash()`); + async getUncleCountByBlockHash(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} getUncleCountByBlockHash()`); return EthImpl.zeroHex; } /** * Always returns '0x0'. There are no uncles in Hedera. */ - async getUncleCountByBlockNumber(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} getUncleCountByBlockNumber()`); + async getUncleCountByBlockNumber(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} getUncleCountByBlockNumber()`); return EthImpl.zeroHex; } /** * TODO Needs docs, or be removed? */ - async hashrate(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} hashrate()`); + async hashrate(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} hashrate()`); return EthImpl.zeroHex; } /** * Always returns UNSUPPORTED_METHOD error. */ - getWork(requestIdPrefix?: string): JsonRpcError { - this.logger.trace(`${requestIdPrefix} getWork()`); + getWork(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} getWork()`); return predefined.UNSUPPORTED_METHOD; } /** * Unsupported methods always return UNSUPPORTED_METHOD error. */ - submitHashrate(requestIdPrefix?: string): JsonRpcError { - this.logger.trace(`${requestIdPrefix} submitHashrate()`); + submitHashrate(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} submitHashrate()`); return predefined.UNSUPPORTED_METHOD; } - signTransaction(requestIdPrefix?: string): JsonRpcError { - this.logger.trace(`${requestIdPrefix} signTransaction()`); + signTransaction(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} signTransaction()`); return predefined.UNSUPPORTED_METHOD; } - sign(requestIdPrefix?: string): JsonRpcError { - this.logger.trace(`${requestIdPrefix} sign()`); + sign(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} sign()`); return predefined.UNSUPPORTED_METHOD; } - sendTransaction(requestIdPrefix?: string): JsonRpcError { - this.logger.trace(`${requestIdPrefix} sendTransaction()`); + sendTransaction(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} sendTransaction()`); return predefined.UNSUPPORTED_METHOD; } - protocolVersion(requestIdPrefix?: string): JsonRpcError { - this.logger.trace(`${requestIdPrefix} protocolVersion()`); + protocolVersion(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} protocolVersion()`); return predefined.UNSUPPORTED_METHOD; } - coinbase(requestIdPrefix?: string): JsonRpcError { - this.logger.trace(`${requestIdPrefix} coinbase()`); + coinbase(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} coinbase()`); return predefined.UNSUPPORTED_METHOD; } /** * Gets the value from a storage position at the given Ethereum address. * - * @param address - * @param slot - * @param blockNumberOrTagOrHash - * @param requestIdPrefix + * @param {string} address The Ethereum address to get the storage value from + * @param {string} slot The storage slot to get the value from + * @param {RequestDetails} requestDetails The request details for logging and tracking + * @param {string | null} blockNumberOrTagOrHash The block number or tag or hash to get the storage value from */ async getStorageAt( address: string, slot: string, + requestDetails: RequestDetails, blockNumberOrTagOrHash?: string | null, - requestIdPrefix?: string, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace( `${requestIdPrefix} getStorageAt(address=${address}, slot=${slot}, blockNumberOrOrHashTag=${blockNumberOrTagOrHash})`, ); let result = EthImpl.zeroHex32Byte; // if contract or slot not found then return 32 byte 0 - const blockResponse = await this.common.getHistoricalBlockResponse(blockNumberOrTagOrHash, false, requestIdPrefix); + const blockResponse = await this.common.getHistoricalBlockResponse(requestDetails, blockNumberOrTagOrHash, false); // To save a request to the mirror node for `latest` and `pending` blocks, we directly return null from `getHistoricalBlockResponse` // But if a block number or `earliest` tag is passed and the mirror node returns `null`, we should throw an error. if (!this.common.blockTagIsLatestOrPending(blockNumberOrTagOrHash) && blockResponse == null) { @@ -899,7 +905,7 @@ export class EthImpl implements Eth { const blockEndTimestamp = blockResponse?.timestamp?.to; await this.mirrorNodeClient - .getContractStateByAddressAndSlot(address, slot, blockEndTimestamp, requestIdPrefix) + .getContractStateByAddressAndSlot(address, slot, requestDetails, blockEndTimestamp) .then((response) => { if (response !== null && response.state.length > 0) { result = response.state[0].value; @@ -928,11 +934,16 @@ export class EthImpl implements Eth { * Gets the balance of an account as of the given block from the mirror node. * Current implementation does not yet utilize blockNumber * - * @param account - * @param blockNumberOrTagOrHash - * @param requestIdPrefix + * @param {string} account The account to get the balance from + * @param {string | null} blockNumberOrTagOrHash The block number or tag or hash to get the balance from + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async getBalance(account: string, blockNumberOrTagOrHash: string | null, requestIdPrefix?: string): Promise { + async getBalance( + account: string, + blockNumberOrTagOrHash: string | null, + requestDetails: RequestDetails, + ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; const latestBlockTolerance = 1; this.logger.trace(`${requestIdPrefix} getBalance(account=${account}, blockNumberOrTag=${blockNumberOrTagOrHash})`); @@ -942,18 +953,18 @@ export class EthImpl implements Eth { if (!this.common.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) { let blockHashNumber, isHash; const cacheKey = `${constants.CACHE_KEY.ETH_BLOCK_NUMBER}`; - const blockNumberCached = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBalance, requestIdPrefix); + const blockNumberCached = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBalance, requestDetails); if (blockNumberCached) { this.logger.trace(`${requestIdPrefix} returning cached value ${cacheKey}:${JSON.stringify(blockNumberCached)}`); latestBlock = { blockNumber: blockNumberCached, timeStampTo: '0' }; } else { - latestBlock = await this.blockNumberTimestamp(EthImpl.ethGetBalance, requestIdPrefix); + latestBlock = await this.blockNumberTimestamp(EthImpl.ethGetBalance, requestDetails); } if (blockNumberOrTagOrHash != null && blockNumberOrTagOrHash.length > 32) { isHash = true; - blockHashNumber = await this.mirrorNodeClient.getBlock(blockNumberOrTagOrHash); + blockHashNumber = await this.mirrorNodeClient.getBlock(blockNumberOrTagOrHash, requestDetails); } const currentBlockNumber = isHash ? Number(blockHashNumber.number) : Number(blockNumberOrTagOrHash); @@ -966,14 +977,14 @@ export class EthImpl implements Eth { // If ever we get the latest block from cache, and blockNumberOrTag is not latest, then we need to get the block timestamp // This should rarely happen. if (blockNumberOrTagOrHash !== EthImpl.blockLatest && latestBlock.timeStampTo === '0') { - latestBlock = await this.blockNumberTimestamp(EthImpl.ethGetBalance, requestIdPrefix); + latestBlock = await this.blockNumberTimestamp(EthImpl.ethGetBalance, requestDetails); } } // check cache first // create a key for the cache const cacheKey = `${constants.CACHE_KEY.ETH_GET_BALANCE}-${account}-${blockNumberOrTagOrHash}`; - let cachedBalance = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBalance, requestIdPrefix); + let cachedBalance = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBalance, requestDetails); if (cachedBalance && this.shouldUseCacheForBalance(blockNumberOrTagOrHash)) { this.logger.trace(`${requestIdPrefix} returning cached value ${cacheKey}:${JSON.stringify(cachedBalance)}`); return cachedBalance; @@ -986,7 +997,7 @@ export class EthImpl implements Eth { try { if (!this.common.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) { - const block = await this.common.getHistoricalBlockResponse(blockNumberOrTagOrHash, true, requestIdPrefix); + const block = await this.common.getHistoricalBlockResponse(requestDetails, blockNumberOrTagOrHash, true); if (block) { blockNumber = block.number; @@ -1000,8 +1011,8 @@ export class EthImpl implements Eth { if (timeDiff > constants.BALANCES_UPDATE_INTERVAL) { const balance = await this.mirrorNodeClient.getBalanceAtTimestamp( account, + requestDetails, block.timestamp.from, - requestIdPrefix, ); balanceFound = true; if (balance?.balances?.length) { @@ -1012,7 +1023,7 @@ export class EthImpl implements Eth { else { let currentBalance = 0; let balanceFromTxs = 0; - mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestIdPrefix); + mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestDetails); if (mirrorAccount) { if (mirrorAccount.balance) { currentBalance = mirrorAccount.balance.balance; @@ -1028,10 +1039,7 @@ export class EthImpl implements Eth { const nextPageTimeMarker = nextPageParams.get('timestamp'); // If nextPageTimeMarker is greater than the block.timestamp.to, then we need to paginate to get the transactions for the block.timestamp.to if (nextPageTimeMarker && nextPageTimeMarker?.split(':')[1] >= block.timestamp.to) { - const pagedTransactions = await this.mirrorNodeClient.getAccountPaginated( - nextPage, - requestIdPrefix, - ); + const pagedTransactions = await this.mirrorNodeClient.getAccountPaginated(nextPage, requestDetails); mirrorAccount.transactions = mirrorAccount.transactions.concat(pagedTransactions); } // If nextPageTimeMarker is less than the block.timestamp.to, then just run the getBalanceAtBlockTimestamp function in this case as well. @@ -1053,7 +1061,7 @@ export class EthImpl implements Eth { if (!balanceFound && !mirrorAccount) { // If no balance and no account, then we need to make a request to the mirror node for the account. - mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestIdPrefix); + mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestDetails); // Test if exists here if (mirrorAccount !== null && mirrorAccount !== undefined) { balanceFound = true; @@ -1072,12 +1080,13 @@ export class EthImpl implements Eth { // save in cache the current balance for the account and blockNumberOrTag cachedBalance = numberTo0x(weibars); + this.logger.trace(`${requestIdPrefix} Value cached balance ${cachedBalance}`); await this.cacheService.set( cacheKey, cachedBalance, EthImpl.ethGetBalance, + requestDetails, this.ethGetBalanceCacheTtlMs, - requestIdPrefix, ); return cachedBalance; @@ -1092,11 +1101,12 @@ export class EthImpl implements Eth { /** * Gets the smart contract code for the contract at the given Ethereum address. * - * @param address - * @param blockNumber - * @param requestIdPrefix + * @param {string} address The Ethereum address of the contract + * @param {string | null} blockNumber The block number to get the contract code from + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async getCode(address: string, blockNumber: string | null, requestIdPrefix?: string): Promise { + async getCode(address: string, blockNumber: string | null, requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; if (!EthImpl.isBlockParamValid(blockNumber)) { throw predefined.UNKNOWN_BLOCK( `The value passed is not a valid blockHash/blockNumber/blockTag value: ${blockNumber}`, @@ -1117,19 +1127,17 @@ export class EthImpl implements Eth { const cachedResponse: string | undefined = await this.cacheService.getAsync( cachedLabel, EthImpl.ethGetCode, - requestIdPrefix, + requestDetails, ); if (cachedResponse != undefined) { return cachedResponse; } try { - const result = await this.mirrorNodeClient.resolveEntityType( - address, - [constants.TYPE_CONTRACT, constants.TYPE_TOKEN], - EthImpl.ethGetCode, - requestIdPrefix, - ); + const result = await this.mirrorNodeClient.resolveEntityType(address, EthImpl.ethGetCode, requestDetails, [ + constants.TYPE_CONTRACT, + constants.TYPE_TOKEN, + ]); if (result) { if (result?.type === constants.TYPE_TOKEN) { this.logger.trace(`${requestIdPrefix} Token redirect case, return redirectBytecode`); @@ -1145,8 +1153,7 @@ export class EthImpl implements Eth { cachedLabel, result?.entity.runtime_bytecode, EthImpl.ethGetCode, - undefined, - requestIdPrefix, + requestDetails, ); return result?.entity.runtime_bytecode; } @@ -1156,7 +1163,7 @@ export class EthImpl implements Eth { const bytecode = await this.hapiService .getSDKClient() - .getContractByteCode(0, 0, address, EthImpl.ethGetCode, requestIdPrefix); + .getContractByteCode(0, 0, address, EthImpl.ethGetCode, requestDetails); return prepend0x(Buffer.from(bytecode).toString('hex')); } catch (e: any) { if (e instanceof SDKClientError) { @@ -1200,20 +1207,21 @@ export class EthImpl implements Eth { /** * Gets the block with the given hash. * - * @param hash - * @param showDetails - * @param requestIdPrefix + * @param {string} hash the block hash + * @param {boolean} showDetails whether to show the details of the block + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async getBlockByHash(hash: string, showDetails: boolean, requestIdPrefix?: string): Promise { + async getBlockByHash(hash: string, showDetails: boolean, requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`); const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_HASH}_${hash}_${showDetails}`; - let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByHash, requestIdPrefix); + let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByHash, requestDetails); if (!block) { - block = await this.getBlock(hash, showDetails, requestIdPrefix).catch((e: any) => { + block = await this.getBlock(hash, showDetails, requestDetails).catch((e: any) => { throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`); }); - await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByHash, undefined, requestIdPrefix); + await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByHash, requestDetails); } return block; @@ -1221,17 +1229,22 @@ export class EthImpl implements Eth { /** * Gets the block by its block number. - * @param blockNumOrTag Possible values are earliest/pending/latest or hex, and can't be null (validator check). - * @param showDetails - * @param requestIdPrefix + * @param {string} blockNumOrTag Possible values are earliest/pending/latest or hex, and can't be null (validator check). + * @param {boolean} showDetails whether to show the details of the block + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async getBlockByNumber(blockNumOrTag: string, showDetails: boolean, requestIdPrefix?: string): Promise { + async getBlockByNumber( + blockNumOrTag: string, + showDetails: boolean, + requestDetails: RequestDetails, + ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNum=${blockNumOrTag}, showDetails=${showDetails})`); const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumOrTag}_${showDetails}`; - let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestIdPrefix); + let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails); if (!block) { - block = await this.getBlock(blockNumOrTag, showDetails, requestIdPrefix).catch((e: any) => { + block = await this.getBlock(blockNumOrTag, showDetails, requestDetails).catch((e: any) => { throw this.common.genericErrorHandler( e, `${requestIdPrefix} Failed to retrieve block for blockNum ${blockNumOrTag}`, @@ -1239,7 +1252,7 @@ export class EthImpl implements Eth { }); if (!this.common.blockTagIsLatestOrPending(blockNumOrTag)) { - await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByNumber, undefined, requestIdPrefix); + await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByNumber, requestDetails); } } @@ -1249,17 +1262,18 @@ export class EthImpl implements Eth { /** * Gets the number of transaction in a block by its block hash. * - * @param hash - * @param requestIdPrefix + * @param {string} hash The block hash + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async getBlockTransactionCountByHash(hash: string, requestIdPrefix?: string): Promise { + async getBlockTransactionCountByHash(hash: string, requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByHash(hash=${hash}, showDetails=%o)`); const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_COUNT_BY_HASH}_${hash}`; const cachedResponse = await this.cacheService.getAsync( cacheKey, EthImpl.ethGetTransactionCountByHash, - requestIdPrefix, + requestDetails, ); if (cachedResponse) { this.logger.debug( @@ -1269,36 +1283,34 @@ export class EthImpl implements Eth { } const transactionCount = await this.mirrorNodeClient - .getBlock(hash, requestIdPrefix) + .getBlock(hash, requestDetails) .then((block) => EthImpl.getTransactionCountFromBlockResponse(block)) .catch((e: any) => { throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`); }); - await this.cacheService.set( - cacheKey, - transactionCount, - EthImpl.ethGetTransactionCountByHash, - undefined, - requestIdPrefix, - ); + await this.cacheService.set(cacheKey, transactionCount, EthImpl.ethGetTransactionCountByHash, requestDetails); return transactionCount; } /** * Gets the number of transaction in a block by its block number. - * @param blockNumOrTag - * @param requestIdPrefix + * @param {string} blockNumOrTag Possible values are earliest/pending/latest or hex + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async getBlockTransactionCountByNumber(blockNumOrTag: string, requestIdPrefix?: string): Promise { + async getBlockTransactionCountByNumber( + blockNumOrTag: string, + requestDetails: RequestDetails, + ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByNumber(blockNum=${blockNumOrTag}, showDetails=%o)`); - const blockNum = await this.translateBlockTag(blockNumOrTag, requestIdPrefix); + const blockNum = await this.translateBlockTag(blockNumOrTag, requestDetails); const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_COUNT_BY_NUMBER}_${blockNum}`; const cachedResponse = await this.cacheService.getAsync( cacheKey, EthImpl.ethGetTransactionCountByNumber, - requestIdPrefix, + requestDetails, ); if (cachedResponse) { this.logger.debug( @@ -1308,7 +1320,7 @@ export class EthImpl implements Eth { } const transactionCount = await this.mirrorNodeClient - .getBlock(blockNum, requestIdPrefix) + .getBlock(blockNum, requestDetails) .then((block) => EthImpl.getTransactionCountFromBlockResponse(block)) .catch((e: any) => { throw this.common.genericErrorHandler( @@ -1317,28 +1329,23 @@ export class EthImpl implements Eth { ); }); - await this.cacheService.set( - cacheKey, - transactionCount, - EthImpl.ethGetTransactionCountByNumber, - undefined, - requestIdPrefix, - ); + await this.cacheService.set(cacheKey, transactionCount, EthImpl.ethGetTransactionCountByNumber, requestDetails); return transactionCount; } /** * Gets the transaction in a block by its block hash and transactions index. * - * @param blockHash - * @param transactionIndex - * @param requestIdPrefix + * @param {string} blockHash The block hash + * @param {string} transactionIndex The transaction index + * @param {RequestDetails} requestDetails The request details for logging and tracking */ async getTransactionByBlockHashAndIndex( blockHash: string, transactionIndex: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace( `${requestIdPrefix} getTransactionByBlockHashAndIndex(hash=${blockHash}, index=${transactionIndex})`, ); @@ -1347,7 +1354,7 @@ export class EthImpl implements Eth { return await this.getTransactionByBlockHashOrBlockNumAndIndex( { title: 'blockHash', value: blockHash }, transactionIndex, - requestIdPrefix, + requestDetails, ); } catch (error) { throw this.common.genericErrorHandler( @@ -1360,25 +1367,26 @@ export class EthImpl implements Eth { /** * Gets the transaction in a block by its block hash and transactions index. * - * @param blockNumOrTag - * @param transactionIndex - * @param requestIdPrefix + * @param {string} blockNumOrTag Possible values are earliest/pending/latest or hex + * @param {string} transactionIndex The transaction index + * @param {RequestDetails} requestDetails The request details for logging and tracking */ async getTransactionByBlockNumberAndIndex( blockNumOrTag: string, transactionIndex: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace( `${requestIdPrefix} getTransactionByBlockNumberAndIndex(blockNum=${blockNumOrTag}, index=${transactionIndex})`, ); - const blockNum = await this.translateBlockTag(blockNumOrTag, requestIdPrefix); + const blockNum = await this.translateBlockTag(blockNumOrTag, requestDetails); try { return await this.getTransactionByBlockHashOrBlockNumAndIndex( { title: 'blockNumber', value: blockNum }, transactionIndex, - requestIdPrefix, + requestDetails, ); } catch (error) { throw this.common.genericErrorHandler( @@ -1392,22 +1400,23 @@ export class EthImpl implements Eth { * Gets the number of transactions that have been executed for the given address. * This goes to the consensus nodes to determine the ethereumNonce. * - * Queries mirror node for best effort and fallsback to consensus node for contracts until HIP 729 is implemented. + * Queries mirror node for best effort and falls back to consensus node for contracts until HIP 729 is implemented. * - * @param address - * @param blockNumOrTag - * @param requestIdPrefix + * @param {string} address The account address + * @param {string | null} blockNumOrTag Possible values are earliest/pending/latest or hex + * @param {RequestDetails} requestDetails The request details for logging and tracking */ async getTransactionCount( address: string, blockNumOrTag: string | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} getTransactionCount(address=${address}, blockNumOrTag=${blockNumOrTag})`); // cache considerations for high load const cacheKey = `eth_getTransactionCount_${address}_${blockNumOrTag}`; - let nonceCount = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetTransactionCount, requestIdPrefix); + let nonceCount = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetTransactionCount, requestDetails); if (nonceCount) { this.logger.trace(`${requestIdPrefix} returning cached value ${cacheKey}:${JSON.stringify(nonceCount)}`); return nonceCount; @@ -1420,35 +1429,35 @@ export class EthImpl implements Eth { return EthImpl.zeroHex; } else if (this.common.blockTagIsLatestOrPending(blockNumOrTag)) { // if latest or pending, get latest ethereumNonce from mirror node account API - nonceCount = await this.getAccountLatestEthereumNonce(address, requestIdPrefix); + nonceCount = await this.getAccountLatestEthereumNonce(address, requestDetails); } else if (blockNumOrTag === EthImpl.blockEarliest) { - nonceCount = await this.getAccountNonceForEarliestBlock(requestIdPrefix); + nonceCount = await this.getAccountNonceForEarliestBlock(requestDetails); } else if (!isNaN(blockNum) && blockNumOrTag.length != EthImpl.blockHashLength && blockNum > 0) { - nonceCount = await this.getAccountNonceForHistoricBlock(address, blockNum, requestIdPrefix); + nonceCount = await this.getAccountNonceForHistoricBlock(address, blockNum, requestDetails); } else if (blockNumOrTag.length == EthImpl.blockHashLength && blockNumOrTag.startsWith(EthImpl.emptyHex)) { - nonceCount = await this.getAccountNonceForHistoricBlock(address, blockNumOrTag, requestIdPrefix); + nonceCount = await this.getAccountNonceForHistoricBlock(address, blockNumOrTag, requestDetails); } else { // return a '-39001: Unknown block' error per api-spec throw predefined.UNKNOWN_BLOCK(); } } else { // if no block consideration, get latest ethereumNonce from mirror node if account or from consensus node is contract until HIP 729 is implemented - nonceCount = await this.getAccountLatestEthereumNonce(address, requestIdPrefix); + nonceCount = await this.getAccountLatestEthereumNonce(address, requestDetails); } const cacheTtl = blockNumOrTag === EthImpl.blockEarliest || !isNaN(blockNum) ? constants.CACHE_TTL.ONE_DAY : this.ethGetTransactionCountCacheTtl; // cache historical values longer as they don't change - await this.cacheService.set(cacheKey, nonceCount, EthImpl.ethGetTransactionCount, cacheTtl, requestIdPrefix); + await this.cacheService.set(cacheKey, nonceCount, EthImpl.ethGetTransactionCount, requestDetails, cacheTtl); return nonceCount; } async parseRawTxAndPrecheck( transaction: string, + requestDetails: RequestDetails, networkGasPriceInWeiBars: number, - requestIdPrefix?: string, ): Promise { let interactingEntity = ''; let originatingAddress = ''; @@ -1458,14 +1467,14 @@ export class EthImpl implements Eth { interactingEntity = parsedTx.to?.toString() || ''; originatingAddress = parsedTx.from?.toString() || ''; this.logger.trace( - `${requestIdPrefix} sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`, + `${requestDetails.formattedRequestId} sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`, ); - await this.precheck.sendRawTransactionCheck(parsedTx, networkGasPriceInWeiBars, requestIdPrefix); + await this.precheck.sendRawTransactionCheck(parsedTx, networkGasPriceInWeiBars, requestDetails); return parsedTx; } catch (e: any) { this.logger.warn( - `${requestIdPrefix} Error on precheck sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`, + `${requestDetails.formattedRequestId} Error on precheck sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`, ); throw this.common.genericErrorHandler(e); } @@ -1477,11 +1486,11 @@ export class EthImpl implements Eth { transactionBuffer, txSubmitted, parsedTx, - requestIdPrefix, + requestDetails: RequestDetails, ): Promise { this.logger.error( e, - `${requestIdPrefix} Failed to successfully submit sendRawTransaction for transaction ${transaction}`, + `${requestDetails.formattedRequestId} Failed to successfully submit sendRawTransaction for transaction ${transaction}`, ); if (e instanceof JsonRpcError) { return e; @@ -1496,21 +1505,23 @@ export class EthImpl implements Eth { // running a polling loop to give mirror node enough time to update account nonce let accountNonce: number | null = null; for (let i = 0; i < mirrorNodeGetContractResultRetries; i++) { - const accountInfo = await this.mirrorNodeClient.getAccount(parsedTx.from!, requestIdPrefix); + const accountInfo = await this.mirrorNodeClient.getAccount(parsedTx.from!, requestDetails); if (accountInfo.ethereum_nonce !== parsedTx.nonce) { accountNonce = accountInfo.ethereum_nonce; break; } this.logger.trace( - `${requestIdPrefix} Repeating retry to poll for updated account nonce. Count ${i} of ${mirrorNodeGetContractResultRetries}. Waiting ${this.mirrorNodeClient.getMirrorNodeRetryDelay()} ms before initiating a new request`, + `${ + requestDetails.formattedRequestId + } Repeating retry to poll for updated account nonce. Count ${i} of ${mirrorNodeGetContractResultRetries}. Waiting ${this.mirrorNodeClient.getMirrorNodeRetryDelay()} ms before initiating a new request`, ); await new Promise((r) => setTimeout(r, this.mirrorNodeClient.getMirrorNodeRetryDelay())); } if (!accountNonce) { - this.logger.warn(`${requestIdPrefix} Cannot find updated account nonce.`); - throw predefined.INTERNAL_ERROR(`Cannot find updated account nonce for WRONG_NONCE error.`); + this.logger.warn(`${requestDetails.formattedRequestId} Cannot find updated account nonce.`); + throw predefined.INTERNAL_ERROR(`Cannot find updated account nonce for WRONT_NONCE error.`); } if (parsedTx.nonce > accountNonce) { @@ -1525,11 +1536,11 @@ export class EthImpl implements Eth { return predefined.INTERNAL_ERROR(e.message.toString()); } - await this.mirrorNodeClient.getContractRevertReasonFromTransaction(e, requestIdPrefix); + await this.mirrorNodeClient.getContractRevertReasonFromTransaction(e, requestDetails); this.logger.error( e, - `${requestIdPrefix} Failed sendRawTransaction during record retrieval for transaction ${transaction}, returning computed hash`, + `${requestDetails.formattedRequestId} Failed sendRawTransaction during record retrieval for transaction ${transaction}, returning computed hash`, ); //Return computed hash if unable to retrieve EthereumHash from record due to error return prepend0x(createHash('keccak256').update(transactionBuffer).digest('hex')); @@ -1538,21 +1549,21 @@ export class EthImpl implements Eth { /** * Submits a transaction to the network for execution. * - * @param transaction - * @param requestId + * @param {string} transaction The raw transaction to submit + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async sendRawTransaction(transaction: string, requestId: string): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); + async sendRawTransaction(transaction: string, requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; if (transaction?.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) this.ethExecutionsCounter .labels(EthImpl.ethSendRawTransaction, transaction.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH)) .inc(); const networkGasPriceInWeiBars = Utils.addPercentageBufferToGasPrice( - await this.getFeeWeibars(EthImpl.ethGasPrice, requestIdPrefix), + await this.getFeeWeibars(EthImpl.ethGasPrice, requestDetails), ); - const parsedTx = await this.parseRawTxAndPrecheck(transaction, networkGasPriceInWeiBars, requestIdPrefix); + const parsedTx = await this.parseRawTxAndPrecheck(transaction, requestDetails, networkGasPriceInWeiBars); const originalCallerAddress = parsedTx.from?.toString() || ''; const transactionBuffer = Buffer.from(EthImpl.prune0x(transaction), 'hex'); let fileId: FileId | null = null; @@ -1563,10 +1574,10 @@ export class EthImpl implements Eth { .submitEthereumTransaction( transactionBuffer, EthImpl.ethSendRawTransaction, + requestDetails, originalCallerAddress, networkGasPriceInWeiBars, - await this.getCurrentNetworkExchangeRateInCents(requestIdPrefix), - requestIdPrefix, + await this.getCurrentNetworkExchangeRateInCents(requestDetails), ); txSubmitted = true; @@ -1583,9 +1594,9 @@ export class EthImpl implements Eth { const contractResult = await this.mirrorNodeClient.repeatedRequest( this.mirrorNodeClient.getContractResult.name, - [formattedId], + [formattedId, requestDetails], this.mirrorNodeClient.getMirrorNodeRequestRetryCount(), - requestIdPrefix, + requestDetails, ); if (!contractResult) { @@ -1608,7 +1619,7 @@ export class EthImpl implements Eth { transactionBuffer, txSubmitted, parsedTx, - requestIdPrefix, + requestDetails, ); } finally { /** @@ -1618,7 +1629,8 @@ export class EthImpl implements Eth { if (fileId) { this.hapiService .getSDKClient() - .deleteFile(fileId, requestIdPrefix, EthImpl.ethSendRawTransaction, fileId.toString(), originalCallerAddress); + .deleteFile(fileId, requestDetails, EthImpl.ethSendRawTransaction, fileId.toString(), originalCallerAddress) + .then(); } } } @@ -1626,15 +1638,16 @@ export class EthImpl implements Eth { /** * Execute a free contract call query. * - * @param call {IContractCallRequest} The contract call request data. - * @param blockParam either a string (blockNumber or blockTag) or an object (blockHash or blockNumber) - * @param requestIdPrefix optional request ID prefix for logging. + * @param {IContractCallRequest} call The contract call request data. + * @param {string | object | null} blockParam either a string (blockNumber or blockTag) or an object (blockHash or blockNumber) + * @param {RequestDetails} requestDetails The request details for logging and tracking */ async call( call: IContractCallRequest, blockParam: string | object | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; const callData = call.data ? call.data : call.input; // log request this.logger.info( @@ -1650,13 +1663,13 @@ export class EthImpl implements Eth { .inc(); } - const blockNumberOrTag = await this.extractBlockNumberOrTag(blockParam, requestIdPrefix); + const blockNumberOrTag = await this.extractBlockNumberOrTag(blockParam, requestDetails); await this.performCallChecks(call); // Get a reasonable value for "gas" if it is not specified. - const gas = this.getCappedBlockGasLimit(call.gas?.toString(), requestIdPrefix); + const gas = this.getCappedBlockGasLimit(call.gas?.toString(), requestDetails); - await this.contractCallFormat(call, requestIdPrefix); + await this.contractCallFormat(call, requestDetails); const selector = getFunctionSelector(call.data!); @@ -1673,11 +1686,11 @@ export class EthImpl implements Eth { let result: string | JsonRpcError = ''; try { if (shouldForceToConsensus || shouldDefaultToConsensus) { - result = await this.callConsensusNode(call, gas, requestIdPrefix); + result = await this.callConsensusNode(call, gas, requestDetails); } else { //temporary workaround until precompiles are implemented in Mirror node evm module // Execute the call and get the response - result = await this.callMirrorNode(call, gas, call.value, blockNumberOrTag, requestIdPrefix); + result = await this.callMirrorNode(call, gas, call.value, blockNumberOrTag, requestDetails); } this.logger.debug(`${requestIdPrefix} eth_call response: ${JSON.stringify(result)}`); @@ -1694,10 +1707,12 @@ export class EthImpl implements Eth { /** * Gets transactions by block hash or block number and index with resolved EVM addresses - * @param blockParam - * @param transactionIndex - * @param requestIdPrefix - * @returns Promise + * @param {object} blockParam The block parameter + * @param {string} blockParam.title Possible values are 'blockHash' and 'blockNumber' + * @param {string | number} blockParam.value The block hash or block number + * @param {string} transactionIndex + * @param {RequestDetails} requestDetails The request details for logging and tracking + * @returns {Promise} The transaction or null if not found */ private async getTransactionByBlockHashOrBlockNumAndIndex( blockParam: { @@ -1705,21 +1720,21 @@ export class EthImpl implements Eth { value: string | number; }, transactionIndex: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { const contractResults = await this.mirrorNodeClient.getContractResults( + requestDetails, { [blockParam.title]: blockParam.value, transactionIndex: Number(transactionIndex), }, undefined, - requestIdPrefix, ); if (!contractResults[0]) return null; - const resolvedToAddress = await this.resolveEvmAddress(contractResults[0].to, requestIdPrefix); - const resolvedFromAddress = await this.resolveEvmAddress(contractResults[0].from, requestIdPrefix, [ + const resolvedToAddress = await this.resolveEvmAddress(contractResults[0].to, requestDetails); + const resolvedFromAddress = await this.resolveEvmAddress(contractResults[0].from, requestDetails, [ constants.TYPE_ACCOUNT, ]); @@ -1729,7 +1744,7 @@ export class EthImpl implements Eth { // according to EIP-1898 (https://eips.ethereum.org/EIPS/eip-1898) block param can either be a string (blockNumber or Block Tag) or an object (blockHash or blockNumber) private async extractBlockNumberOrTag( blockParam: string | object | null, - requestIdPrefix: string | undefined, + requestDetails: RequestDetails, ): Promise { if (!blockParam) { return null; @@ -1743,7 +1758,7 @@ export class EthImpl implements Eth { } if (blockParam['blockHash'] != null) { - return await this.getBlockNumberFromHash(blockParam['blockHash'], requestIdPrefix); + return await this.getBlockNumberFromHash(blockParam['blockHash'], requestDetails); } // if is an object but doesn't have blockNumber or blockHash, then it's an invalid blockParam @@ -1751,10 +1766,10 @@ export class EthImpl implements Eth { } // if blockParam is a string, could be a blockNumber or blockTag or blockHash - if (typeof blockParam === 'string' && blockParam.length > 0) { + if (blockParam.length > 0) { // if string is a blockHash, we return its corresponding blockNumber if (EthImpl.isBlockHash(blockParam)) { - return await this.getBlockNumberFromHash(blockParam, requestIdPrefix); + return await this.getBlockNumberFromHash(blockParam, requestDetails); } else { return blockParam; } @@ -1763,8 +1778,8 @@ export class EthImpl implements Eth { return null; } - private async getBlockNumberFromHash(blockHash: string, requestIdPrefix: string | undefined): Promise { - const block = await this.getBlockByHash(blockHash, false, requestIdPrefix); + private async getBlockNumberFromHash(blockHash: string, requestDetails: RequestDetails): Promise { + const block = await this.getBlockByHash(blockHash, false, requestDetails); if (block != null) { return block.number; } else { @@ -1777,8 +1792,9 @@ export class EthImpl implements Eth { gas: number | null, value: number | string | null | undefined, block: string | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; let callData: IContractCallRequest = {}; try { this.logger.debug( @@ -1797,7 +1813,7 @@ export class EthImpl implements Eth { ...(block !== null ? { block } : {}), }; - const contractCallResponse = await this.mirrorNodeClient.postContractCall(callData, requestIdPrefix); + const contractCallResponse = await this.mirrorNodeClient.postContractCall(callData, requestDetails); return contractCallResponse?.result ? prepend0x(contractCallResponse.result) : EthImpl.emptyHex; } catch (e: any) { if (e instanceof JsonRpcError) { @@ -1831,7 +1847,7 @@ export class EthImpl implements Eth { callData, )} with error: "${e.message}"`, ); - return await this.callConsensusNode(call, gas, requestIdPrefix); + return await this.callConsensusNode(call, gas, requestDetails); } } @@ -1844,11 +1860,16 @@ export class EthImpl implements Eth { /** * Execute a contract call query to the consensus node * - * @param call - * @param gas - * @param requestIdPrefix + * @param call The contract call request data + * @param {number | null} gas The gas to pass for the call + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async callConsensusNode(call: any, gas: number | null, requestIdPrefix?: string): Promise { + async callConsensusNode( + call: any, + gas: number | null, + requestDetails: RequestDetails, + ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; // Execute the call and get the response if (!gas) { gas = Number.parseInt(this.defaultGas); @@ -1881,7 +1902,7 @@ export class EthImpl implements Eth { } const cacheKey = `${constants.CACHE_KEY.ETH_CALL}:${call.from || ''}.${call.to}.${data}`; - const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethCall, requestIdPrefix); + const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethCall, requestDetails); if (cachedResponse != undefined) { this.logger.debug(`${requestIdPrefix} eth_call returned cached response: ${cachedResponse}`); @@ -1890,7 +1911,7 @@ export class EthImpl implements Eth { const contractCallResponse = await this.hapiService .getSDKClient() - .submitContractCallQueryWithRetry(call.to, call.data, gas, call.from, EthImpl.ethCall, requestIdPrefix); + .submitContractCallQueryWithRetry(call.to, call.data, gas, call.from, EthImpl.ethCall, requestDetails); if (contractCallResponse) { const formattedCallReponse = prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex')); @@ -1898,8 +1919,8 @@ export class EthImpl implements Eth { cacheKey, formattedCallReponse, EthImpl.ethCall, + requestDetails, this.ethCallCacheTtl, - requestIdPrefix, ); return formattedCallReponse; } @@ -1934,16 +1955,16 @@ export class EthImpl implements Eth { async resolveEvmAddress( address: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, searchableTypes = [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT], ): Promise { if (!address) return address; const entity = await this.mirrorNodeClient.resolveEntityType( address, - searchableTypes, EthImpl.ethGetCode, - requestIdPrefix, + requestDetails, + searchableTypes, 0, ); let resolvedAddress = address; @@ -1962,12 +1983,13 @@ export class EthImpl implements Eth { * Gets a transaction by the provided hash * * @param hash - * @param requestIdPrefix + * @param requestDetails */ - async getTransactionByHash(hash: string, requestIdPrefix?: string): Promise { + async getTransactionByHash(hash: string, requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} getTransactionByHash(hash=${hash})`, hash); - const contractResult = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestIdPrefix); + const contractResult = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestDetails); if (contractResult === null || contractResult.hash === undefined) { // handle synthetic transactions const syntheticLogs = await this.common.getLogsWithParams( @@ -1975,7 +1997,7 @@ export class EthImpl implements Eth { { 'transaction.hash': hash, }, - requestIdPrefix, + requestDetails, ); // no tx found @@ -1993,8 +2015,8 @@ export class EthImpl implements Eth { ); } - const fromAddress = await this.resolveEvmAddress(contractResult.from, requestIdPrefix, [constants.TYPE_ACCOUNT]); - const toAddress = await this.resolveEvmAddress(contractResult.to, requestIdPrefix); + const fromAddress = await this.resolveEvmAddress(contractResult.from, requestDetails, [constants.TYPE_ACCOUNT]); + const toAddress = await this.resolveEvmAddress(contractResult.to, requestDetails); contractResult.chain_id = contractResult.chain_id || this.chain; return formatContractResult({ @@ -2007,18 +2029,15 @@ export class EthImpl implements Eth { /** * Gets a receipt for a transaction that has already executed. * - * @param hash - * @param requestIdPrefix + * @param {string} hash The transaction hash + * @param {RequestDetails} requestDetails The request details for logging and tracking */ - async getTransactionReceipt(hash: string, requestIdPrefix?: string): Promise { + async getTransactionReceipt(hash: string, requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} getTransactionReceipt(${hash})`); const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_RECEIPT}_${hash}`; - const cachedResponse = await this.cacheService.getAsync( - cacheKey, - EthImpl.ethGetTransactionReceipt, - requestIdPrefix, - ); + const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetTransactionReceipt, requestDetails); if (cachedResponse) { this.logger.debug( `${requestIdPrefix} getTransactionReceipt returned cached response: ${JSON.stringify(cachedResponse)}`, @@ -2026,7 +2045,7 @@ export class EthImpl implements Eth { return cachedResponse; } - const receiptResponse = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestIdPrefix); + const receiptResponse = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestDetails); if (receiptResponse === null || receiptResponse.hash === undefined) { // handle synthetic transactions const syntheticLogs = await this.common.getLogsWithParams( @@ -2034,7 +2053,7 @@ export class EthImpl implements Eth { { 'transaction.hash': hash, }, - requestIdPrefix, + requestDetails, ); // no tx found @@ -2043,7 +2062,7 @@ export class EthImpl implements Eth { return null; } - const gasPriceForTimestamp = await this.getCurrentGasPriceForBlock(syntheticLogs[0].blockHash); + const gasPriceForTimestamp = await this.getCurrentGasPriceForBlock(syntheticLogs[0].blockHash, requestDetails); const receipt: ITransactionReceipt = { blockHash: syntheticLogs[0].blockHash, blockNumber: syntheticLogs[0].blockNumber, @@ -2068,12 +2087,12 @@ export class EthImpl implements Eth { cacheKey, receipt, EthImpl.ethGetTransactionReceipt, + requestDetails, constants.CACHE_TTL.ONE_DAY, - requestIdPrefix, ); return receipt; } else { - const effectiveGas = await this.getCurrentGasPriceForBlock(receiptResponse.blockHash); + const effectiveGas = await this.getCurrentGasPriceForBlock(receiptResponse.blockHash, requestDetails); // support stricter go-eth client which requires the transaction hash property on logs const logs = receiptResponse.logs.map((log) => { return new Log({ @@ -2092,8 +2111,8 @@ export class EthImpl implements Eth { const receipt: ITransactionReceipt = { blockHash: toHash32(receiptResponse.block_hash), blockNumber: numberTo0x(receiptResponse.block_number), - from: await this.resolveEvmAddress(receiptResponse.from, requestIdPrefix), - to: await this.resolveEvmAddress(receiptResponse.to, requestIdPrefix), + from: await this.resolveEvmAddress(receiptResponse.from, requestDetails), + to: await this.resolveEvmAddress(receiptResponse.to, requestDetails), cumulativeGasUsed: numberTo0x(receiptResponse.block_gas_used), gasUsed: nanOrNumberTo0x(receiptResponse.gas_used), contractAddress: receiptResponse.address, @@ -2119,20 +2138,20 @@ export class EthImpl implements Eth { cacheKey, receipt, EthImpl.ethGetTransactionReceipt, + requestDetails, constants.CACHE_TTL.ONE_DAY, - requestIdPrefix, ); return receipt; } } - private async getCurrentGasPriceForBlock(blockHash: string, requestIdPrefix?: string): Promise { - const block = await this.getBlockByHash(blockHash, false); + private async getCurrentGasPriceForBlock(blockHash: string, requestDetails: RequestDetails): Promise { + const block = await this.getBlockByHash(blockHash, false, requestDetails); const timestampDecimal = parseInt(block ? block.timestamp : '0', 16); const timestampDecimalString = timestampDecimal > 0 ? timestampDecimal.toString() : ''; const gasPriceForTimestamp = await this.getFeeWeibars( EthImpl.ethGetTransactionReceipt, - requestIdPrefix, + requestDetails, timestampDecimalString, ); @@ -2177,12 +2196,12 @@ export class EthImpl implements Eth { * most recent block, 'earliest' is 0, numbers become numbers. * * @param tag null, a number, or 'latest', 'pending', or 'earliest' - * @param requestIdPrefix + * @param requestDetails * @private */ - private async translateBlockTag(tag: string | null, requestIdPrefix?: string): Promise { + private async translateBlockTag(tag: string | null, requestDetails: RequestDetails): Promise { if (this.common.blockTagIsLatestOrPending(tag)) { - return Number(await this.blockNumber(requestIdPrefix)); + return Number(await this.blockNumber(requestDetails)); } else if (tag === EthImpl.blockEarliest) { return 0; } else { @@ -2190,7 +2209,7 @@ export class EthImpl implements Eth { } } - private getCappedBlockGasLimit(gasString: string | undefined, requestIdPrefix?: string): number | null { + private getCappedBlockGasLimit(gasString: string | undefined, requestDetails: RequestDetails): number | null { if (!gasString) { // Return null and don't include in the mirror node call, as mirror is doing this estimation on the go. return null; @@ -2201,7 +2220,7 @@ export class EthImpl implements Eth { const gas = Number.parseInt(gasString); if (gas > constants.MAX_GAS_PER_SEC) { this.logger.trace( - `${requestIdPrefix} eth_call gas amount (${gas}) exceeds network limit, capping gas to ${constants.MAX_GAS_PER_SEC}`, + `${requestDetails.formattedRequestId} eth_call gas amount (${gas}) exceeds network limit, capping gas to ${constants.MAX_GAS_PER_SEC}`, ); return constants.MAX_GAS_PER_SEC; } @@ -2213,7 +2232,7 @@ export class EthImpl implements Eth { showDetails: boolean, logs: Log[], transactionsArray: Array, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Array { let filteredLogs: Log[]; if (showDetails) { @@ -2231,7 +2250,9 @@ export class EthImpl implements Eth { }); } - this.logger.trace(`${requestIdPrefix} Synthetic transaction hashes will be populated in the block response`); + this.logger.trace( + `${requestDetails.formattedRequestId} Synthetic transaction hashes will be populated in the block response`, + ); return transactionsArray; } @@ -2242,30 +2263,30 @@ export class EthImpl implements Eth { * Then using the block timerange get all contract results to get transaction details. * If showDetails is set to true subsequently call mirror node for additional transaction details * - * @param blockHashOrNumber - * @param showDetails - * @param requestIdPrefix + * @param {string} blockHashOrNumber The block hash or block number + * @param {boolean} showDetails Whether to show transaction details + * @param {RequestDetails} requestDetails The request details for logging and tracking */ private async getBlock( blockHashOrNumber: string, showDetails: boolean, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { - const blockResponse = await this.common.getHistoricalBlockResponse(blockHashOrNumber, true, requestIdPrefix); + const blockResponse = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrNumber, true); if (blockResponse == null) return null; const timestampRange = blockResponse.timestamp; const timestampRangeParams = [`gte:${timestampRange.from}`, `lte:${timestampRange.to}`]; const contractResults = await this.mirrorNodeClient.getContractResults( + requestDetails, { timestamp: timestampRangeParams }, undefined, - requestIdPrefix, ); const gasUsed = blockResponse.gas_used; const params = { timestamp: timestampRangeParams }; // get contract results logs using block timestamp range - const logs = await this.common.getLogsWithParams(null, params, requestIdPrefix); + const logs = await this.common.getLogsWithParams(null, params, requestDetails); if (contractResults == null && logs.length == 0) { // contract result not found @@ -2281,16 +2302,14 @@ export class EthImpl implements Eth { // prepare transactionArray let transactionArray: any[] = []; for (const contractResult of contractResults) { - contractResult.from = await this.resolveEvmAddress(contractResult.from, requestIdPrefix, [ - constants.TYPE_ACCOUNT, - ]); - contractResult.to = await this.resolveEvmAddress(contractResult.to, requestIdPrefix); + contractResult.from = await this.resolveEvmAddress(contractResult.from, requestDetails, [constants.TYPE_ACCOUNT]); + contractResult.to = await this.resolveEvmAddress(contractResult.to, requestDetails); contractResult.chain_id = contractResult.chain_id || this.chain; transactionArray.push(showDetails ? formatContractResult(contractResult) : contractResult.hash); } - transactionArray = this.populateSyntheticTransactions(showDetails, logs, transactionArray, requestIdPrefix); + transactionArray = this.populateSyntheticTransactions(showDetails, logs, transactionArray, requestDetails); transactionArray = showDetails ? _.uniqBy(transactionArray, 'hash') : _.uniq(transactionArray); const formattedReceipts: IReceiptRootHash[] = ReceiptsRootUtils.buildReceiptRootHashes( @@ -2301,7 +2320,7 @@ export class EthImpl implements Eth { const blockHash = toHash32(blockResponse.hash); return new Block({ - baseFeePerGas: await this.gasPrice(requestIdPrefix), + baseFeePerGas: await this.gasPrice(requestDetails), difficulty: EthImpl.zeroHex, extraData: EthImpl.emptyHex, gasLimit: numberTo0x(constants.BLOCK_GAS_LIMIT), @@ -2358,8 +2377,8 @@ export class EthImpl implements Eth { return numberTo0x(block.count); } - private async getAccountLatestEthereumNonce(address: string, requestId?: string): Promise { - const accountData = await this.mirrorNodeClient.getAccount(address, requestId); + private async getAccountLatestEthereumNonce(address: string, requestDetails: RequestDetails): Promise { + const accountData = await this.mirrorNodeClient.getAccount(address, requestDetails); if (accountData) { // with HIP 729 ethereum_nonce should always be 0+ and null. Historical contracts may have a null value as the nonce was not tracked, return default EVM compliant 0x1 in this case return accountData.ethereum_nonce !== null ? numberTo0x(accountData.ethereum_nonce) : EthImpl.oneHex; @@ -2372,18 +2391,19 @@ export class EthImpl implements Eth { * Returns the number of transactions sent from an address by searching for the ethereum transaction involving the address * Remove when https://github.com/hashgraph/hedera-mirror-node/issues/5862 is implemented * - * @param address string - * @param blockNumOrHash - * @param requestIdPrefix - * @returns string + * @param {string} address The account address + * @param {string | number} blockNumOrHash The block number or hash + * @param {RequestDetails} requestDetails The request details for logging and tracking + * @returns {Promise} The number of transactions sent from the address */ private async getAcccountNonceFromContractResult( address: string, - blockNumOrHash: any, - requestIdPrefix: string | undefined, + blockNumOrHash: string | number, + requestDetails: RequestDetails, ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; // get block timestamp for blockNum - const block = await this.mirrorNodeClient.getBlock(blockNumOrHash, requestIdPrefix); // consider caching error responses + const block = await this.mirrorNodeClient.getBlock(blockNumOrHash, requestDetails); // consider caching error responses if (block == null) { throw predefined.UNKNOWN_BLOCK(); } @@ -2392,8 +2412,8 @@ export class EthImpl implements Eth { const ethereumTransactions = await this.mirrorNodeClient.getAccountLatestEthereumTransactionsByTimestamp( address, block.timestamp.to, + requestDetails, 2, - requestIdPrefix, ); if (ethereumTransactions == null || ethereumTransactions.transactions.length === 0) { return EthImpl.zeroHex; @@ -2408,7 +2428,7 @@ export class EthImpl implements Eth { // get the transaction result for the latest transaction const transactionResult = await this.mirrorNodeClient.getContractResult( ethereumTransactions.transactions[0].transaction_id, - requestIdPrefix, + requestDetails, ); if (transactionResult == null) { throw predefined.RESOURCE_NOT_FOUND( @@ -2416,20 +2436,20 @@ export class EthImpl implements Eth { ); } - const accountResult = await this.mirrorNodeClient.getAccount(transactionResult.from); + const accountResult = await this.mirrorNodeClient.getAccount(transactionResult.from, requestDetails); if (accountResult.evm_address !== address.toLowerCase()) { this.logger.warn( `${requestIdPrefix} eth_transactionCount for a historical block was requested where address: ${address} was not sender: ${transactionResult.address}, returning latest value as best effort.`, ); - return await this.getAccountLatestEthereumNonce(address, requestIdPrefix); + return await this.getAccountLatestEthereumNonce(address, requestDetails); } return numberTo0x(transactionResult.nonce + 1); // nonce is 0 indexed } - private async getAccountNonceForEarliestBlock(requestIdPrefix?: string): Promise { - const block = await this.mirrorNodeClient.getEarliestBlock(requestIdPrefix); + private async getAccountNonceForEarliestBlock(requestDetails: RequestDetails): Promise { + const block = await this.mirrorNodeClient.getEarliestBlock(requestDetails); if (block == null) { throw predefined.INTERNAL_ERROR('No network blocks found'); } @@ -2446,33 +2466,33 @@ export class EthImpl implements Eth { private async getAccountNonceForHistoricBlock( address: string, blockNumOrHash: number | string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { let getBlock; - const isParamBlockNum = typeof blockNumOrHash === 'number' ? true : false; + const isParamBlockNum = typeof blockNumOrHash === 'number'; if (isParamBlockNum && (blockNumOrHash as number) < 0) { throw predefined.UNKNOWN_BLOCK(); } if (!isParamBlockNum) { - getBlock = await this.mirrorNodeClient.getBlock(blockNumOrHash); + getBlock = await this.mirrorNodeClient.getBlock(blockNumOrHash, requestDetails); } const blockNum = isParamBlockNum ? blockNumOrHash : getBlock.number; // check if on latest block, if so get latest ethereumNonce from mirror node account API - const blockResponse = await this.mirrorNodeClient.getLatestBlock(requestIdPrefix); // consider caching error responses + const blockResponse = await this.mirrorNodeClient.getLatestBlock(requestDetails); // consider caching error responses if (blockResponse == null || blockResponse.blocks.length === 0) { throw predefined.UNKNOWN_BLOCK(); } if (blockResponse.blocks[0].number - blockNum <= this.maxBlockRange) { - return this.getAccountLatestEthereumNonce(address, requestIdPrefix); + return this.getAccountLatestEthereumNonce(address, requestDetails); } // if valid block number, get block timestamp - return await this.getAcccountNonceFromContractResult(address, blockNum, requestIdPrefix); + return await this.getAcccountNonceFromContractResult(address, blockNum, requestDetails); } async getLogs( @@ -2481,13 +2501,13 @@ export class EthImpl implements Eth { toBlock: string | 'latest', address: string | string[] | null, topics: any[] | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { - return this.common.getLogs(blockHash, fromBlock, toBlock, address, topics, requestIdPrefix); + return this.common.getLogs(blockHash, fromBlock, toBlock, address, topics, requestDetails); } - async maxPriorityFeePerGas(requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} maxPriorityFeePerGas()`); + async maxPriorityFeePerGas(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} maxPriorityFeePerGas()`); return EthImpl.zeroHex; } @@ -2524,17 +2544,16 @@ export class EthImpl implements Eth { * @param {string} requestId - The unique identifier for the request. * @returns {Promise} - A promise that resolves to the current exchange rate in cents. */ - private async getCurrentNetworkExchangeRateInCents(requestId: string): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); + private async getCurrentNetworkExchangeRateInCents(requestDetails: RequestDetails): Promise { const cacheKey = constants.CACHE_KEY.CURRENT_NETWORK_EXCHANGE_RATE; const callingMethod = this.getCurrentNetworkExchangeRateInCents.name; const cacheTTL = 15 * 60 * 1000; // 15 minutes - let currentNetworkExchangeRate = await this.cacheService.getAsync(cacheKey, callingMethod, requestIdPrefix); + let currentNetworkExchangeRate = await this.cacheService.getAsync(cacheKey, callingMethod, requestDetails); if (!currentNetworkExchangeRate) { - currentNetworkExchangeRate = (await this.mirrorNodeClient.getNetworkExchangeRate(requestId)).current_rate; - await this.cacheService.set(cacheKey, currentNetworkExchangeRate, callingMethod, cacheTTL, requestIdPrefix); + currentNetworkExchangeRate = (await this.mirrorNodeClient.getNetworkExchangeRate(requestDetails)).current_rate; + await this.cacheService.set(cacheKey, currentNetworkExchangeRate, callingMethod, requestDetails, cacheTTL); } const exchangeRateInCents = currentNetworkExchangeRate.cent_equivalent / currentNetworkExchangeRate.hbar_equivalent; diff --git a/packages/relay/src/lib/hbarlimiter/index.ts b/packages/relay/src/lib/hbarlimiter/index.ts index 818d442e7b..2b25a657f5 100644 --- a/packages/relay/src/lib/hbarlimiter/index.ts +++ b/packages/relay/src/lib/hbarlimiter/index.ts @@ -20,9 +20,9 @@ import { Logger } from 'pino'; import constants from '../constants'; -import { predefined } from '../errors/JsonRpcError'; import { Registry, Counter, Gauge } from 'prom-client'; import { formatRequestIdMessage } from '../../formatters'; +import { RequestDetails } from '../types'; export default class HbarLimit { private enabled: boolean = false; @@ -79,7 +79,7 @@ export default class HbarLimit { * @param {string} mode - The mode of the transaction or request. * @param {string} methodName - The name of the method being invoked. * @param {string} originalCallerAddress - The address of the original caller making the request. - * @param {string} [requestId] - An optional unique request ID for tracking the request. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {boolean} - Returns `true` if the rate limit should be enforced, otherwise `false`. */ shouldLimit( @@ -87,13 +87,13 @@ export default class HbarLimit { mode: string, methodName: string, originalCallerAddress: string, - requestId?: string, + requestDetails: RequestDetails, ): boolean { if (!this.enabled) { return false; } - const requestIdPrefix = formatRequestIdMessage(requestId); + const requestIdPrefix = requestDetails.formattedRequestId; // check if the caller is a whitelisted caller if (this.isAccountWhiteListed(originalCallerAddress)) { @@ -104,7 +104,7 @@ export default class HbarLimit { } if (this.shouldResetLimiter(currentDateNow)) { - this.resetLimiter(currentDateNow, requestIdPrefix); + this.resetLimiter(currentDateNow, requestDetails); } if (this.remainingBudget <= 0) { @@ -139,13 +139,11 @@ export default class HbarLimit { callDataSize: number, fileChunkSize: number, currentNetworkExchangeRateInCents: number, - requestId: string, + requestDetails: RequestDetails, ): boolean { - const requestIdPrefix = formatRequestIdMessage(requestId); - if (this.isAccountWhiteListed(originalCallerAddress)) { this.logger.trace( - `${requestIdPrefix} Request bypasses the preemptive limit check - the caller is a whitelisted account: originalCallerAddress=${originalCallerAddress}`, + `${requestDetails.formattedRequestId} Request bypasses the preemptive limit check - the caller is a whitelisted account: originalCallerAddress=${originalCallerAddress}`, ); return false; } @@ -158,13 +156,13 @@ export default class HbarLimit { if (this.remainingBudget - estimatedTxFee < 0) { this.logger.warn( - `${requestIdPrefix} Request fails the preemptive limit check - the remaining HBAR budget was not enough to accommodate the estimated transaction fee: remainingBudget=${this.remainingBudget}, total=${this.total}, resetTimestamp=${this.reset}, callDataSize=${callDataSize}, estimatedTxFee=${estimatedTxFee}, exchangeRateInCents=${currentNetworkExchangeRateInCents}`, + `${requestDetails.formattedRequestId} Request fails the preemptive limit check - the remaining HBAR budget was not enough to accommodate the estimated transaction fee: remainingBudget=${this.remainingBudget}, total=${this.total}, resetTimestamp=${this.reset}, callDataSize=${callDataSize}, estimatedTxFee=${estimatedTxFee}, exchangeRateInCents=${currentNetworkExchangeRateInCents}`, ); return true; } this.logger.trace( - `${requestIdPrefix} Request passes the preemptive limit check - the remaining HBAR budget is enough to accommodate the estimated transaction fee: remainingBudget=${this.remainingBudget}, total=${this.total}, resetTimestamp=${this.reset}, callDataSize=${callDataSize}, estimatedTxFee=${estimatedTxFee}, exchangeRateInCents=${currentNetworkExchangeRateInCents}`, + `${requestDetails.formattedRequestId} Request passes the preemptive limit check - the remaining HBAR budget is enough to accommodate the estimated transaction fee: remainingBudget=${this.remainingBudget}, total=${this.total}, resetTimestamp=${this.reset}, callDataSize=${callDataSize}, estimatedTxFee=${estimatedTxFee}, exchangeRateInCents=${currentNetworkExchangeRateInCents}`, ); return false; } @@ -172,21 +170,19 @@ export default class HbarLimit { /** * Add expense to the remaining budget. */ - addExpense(cost: number, currentDateNow: number, requestId?: string) { - const requestIdPrefix = formatRequestIdMessage(requestId); - + addExpense(cost: number, currentDateNow: number, requestDetails: RequestDetails) { if (!this.enabled) { return; } if (this.shouldResetLimiter(currentDateNow)) { - this.resetLimiter(currentDateNow, requestIdPrefix); + this.resetLimiter(currentDateNow, requestDetails); } this.remainingBudget -= cost; this.hbarLimitRemainingGauge.set(this.remainingBudget); this.logger.trace( - `${requestIdPrefix} HBAR rate limit expense update: cost=${cost}, remainingBudget=${this.remainingBudget}, resetTimestamp=${this.reset}`, + `${requestDetails.formattedRequestId} HBAR rate limit expense update: cost=${cost}, remainingBudget=${this.remainingBudget}, resetTimestamp=${this.reset}`, ); } @@ -264,17 +260,17 @@ export default class HbarLimit { * Decides whether it should reset budget and timer. */ private shouldResetLimiter(currentDateNow: number): boolean { - return this.reset < currentDateNow ? true : false; + return this.reset < currentDateNow; } /** * Reset budget to the total allowed and reset timer to current time plus duration. */ - private resetLimiter(currentDateNow: number, requestIdPrefix: string) { + private resetLimiter(currentDateNow: number, requestDetails: RequestDetails) { this.reset = currentDateNow + this.duration; this.remainingBudget = this.total; this.logger.trace( - `${requestIdPrefix} HBAR Rate Limit reset: remainingBudget= ${this.remainingBudget}, newResetTimestamp= ${this.reset}`, + `${requestDetails.formattedRequestId} HBAR Rate Limit reset: remainingBudget= ${this.remainingBudget}, newResetTimestamp= ${this.reset}`, ); } } diff --git a/packages/relay/src/lib/poller.ts b/packages/relay/src/lib/poller.ts index 32cc23348f..ecfd3e65d6 100644 --- a/packages/relay/src/lib/poller.ts +++ b/packages/relay/src/lib/poller.ts @@ -21,6 +21,8 @@ import { Eth } from '../index'; import { Logger } from 'pino'; import { Registry, Gauge } from 'prom-client'; +import { RequestDetails } from './types'; +import { Utils } from '../utils'; export interface Poll { tag: string; @@ -31,15 +33,15 @@ export interface Poll { const LOGGER_PREFIX = 'Poller:'; export class Poller { - private eth: Eth; - private logger: Logger; + private readonly eth: Eth; + private readonly logger: Logger; private polls: Poll[]; private interval?: NodeJS.Timer; private latestBlock?: string; - private pollingInterval: number; - private newHeadsEnabled: boolean; - private activePollsGauge: Gauge; - private activeNewHeadsPollsGauge: Gauge; + private readonly pollingInterval: number; + private readonly newHeadsEnabled: boolean; + private readonly activePollsGauge: Gauge; + private readonly activeNewHeadsPollsGauge: Gauge; private NEW_HEADS_EVENT = 'newHeads'; @@ -83,11 +85,16 @@ export class Poller { 'latest', filters?.address || null, filters?.topics || null, + new RequestDetails({ requestId: Utils.generateRequestId(), ipAddress: '' }), ); poll.lastPolled = this.latestBlock; } else if (event === this.NEW_HEADS_EVENT && this.newHeadsEnabled) { - data = await this.eth.getBlockByNumber('latest', filters?.includeTransactions ?? false); + data = await this.eth.getBlockByNumber( + 'latest', + filters?.includeTransactions ?? false, + new RequestDetails({ requestId: Utils.generateRequestId(), ipAddress: '' }), + ); data.jsonrpc = '2.0'; poll.lastPolled = this.latestBlock; } else { @@ -114,7 +121,9 @@ export class Poller { start() { this.logger.info(`${LOGGER_PREFIX} Starting polling with interval=${this.pollingInterval}`); this.interval = setInterval(async () => { - this.latestBlock = await this.eth.blockNumber(); + this.latestBlock = await this.eth.blockNumber( + new RequestDetails({ requestId: Utils.generateRequestId(), ipAddress: '' }), + ); this.poll(); }, this.pollingInterval); } @@ -127,7 +136,7 @@ export class Poller { } } - async add(tag: string, callback: Function) { + add(tag: string, callback: Function) { if (!this.hasPoll(tag)) { this.logger.info(`${LOGGER_PREFIX} Tag ${tag} added to polling list`); this.polls.push({ diff --git a/packages/relay/src/lib/precheck.ts b/packages/relay/src/lib/precheck.ts index 1c09d1be85..f44c1d3d51 100644 --- a/packages/relay/src/lib/precheck.ts +++ b/packages/relay/src/lib/precheck.ts @@ -24,7 +24,8 @@ import { EthImpl } from './eth'; import { Logger } from 'pino'; import constants from './constants'; import { ethers, Transaction } from 'ethers'; -import { formatRequestIdMessage, prepend0x } from '../formatters'; +import { prepend0x } from '../formatters'; +import { RequestDetails } from './types'; /** * Precheck class for handling various prechecks before sending a raw transaction. @@ -69,36 +70,34 @@ export class Precheck { * Sends a raw transaction after performing various prechecks. * @param {ethers.Transaction} parsedTx - The parsed transaction. * @param {number} networkGasPriceInWeiBars - The predefined gas price of the network in weibar. - * @param {string} [requestId] - The request ID. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ async sendRawTransactionCheck( parsedTx: ethers.Transaction, networkGasPriceInWeiBars: number, - requestId?: string, + requestDetails: RequestDetails, ): Promise { - this.transactionType(parsedTx, requestId); - this.gasLimit(parsedTx, requestId); - const mirrorAccountInfo = await this.verifyAccount(parsedTx, requestId); - this.nonce(parsedTx, mirrorAccountInfo.ethereum_nonce, requestId); - this.chainId(parsedTx, requestId); + this.transactionType(parsedTx, requestDetails); + this.gasLimit(parsedTx, requestDetails); + const mirrorAccountInfo = await this.verifyAccount(parsedTx, requestDetails); + this.nonce(parsedTx, mirrorAccountInfo.ethereum_nonce, requestDetails); + this.chainId(parsedTx, requestDetails); this.value(parsedTx); - this.gasPrice(parsedTx, networkGasPriceInWeiBars, requestId); - this.balance(parsedTx, mirrorAccountInfo, requestId); + this.gasPrice(parsedTx, networkGasPriceInWeiBars, requestDetails); + this.balance(parsedTx, mirrorAccountInfo, requestDetails); } /** * Verifies the account. * @param {Transaction} tx - The transaction. - * @param {string} [requestId] - The request ID. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise. */ - async verifyAccount(tx: Transaction, requestId?: string): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); - // verify account - const accountInfo = await this.mirrorNodeClient.getAccount(tx.from!, requestId); + async verifyAccount(tx: Transaction, requestDetails: RequestDetails): Promise { + const accountInfo = await this.mirrorNodeClient.getAccount(tx.from!, requestDetails); if (accountInfo == null) { this.logger.trace( - `${requestIdPrefix} Failed to retrieve address '${ + `${requestDetails.formattedRequestId} Failed to retrieve address '${ tx.from }' account details from mirror node on verify account precheck for sendRawTransaction(transaction=${JSON.stringify( tx, @@ -114,12 +113,11 @@ export class Precheck { * Checks the nonce of the transaction. * @param {Transaction} tx - The transaction. * @param {number} accountInfoNonce - The nonce of the account. - * @param {string} [requestId] - The request ID. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - nonce(tx: Transaction, accountInfoNonce: number, requestId?: string): void { - const requestIdPrefix = formatRequestIdMessage(requestId); + nonce(tx: Transaction, accountInfoNonce: number, requestDetails: RequestDetails): void { this.logger.trace( - `${requestIdPrefix} Nonce precheck for sendRawTransaction(tx.nonce=${tx.nonce}, accountInfoNonce=${accountInfoNonce})`, + `${requestDetails.formattedRequestId} Nonce precheck for sendRawTransaction(tx.nonce=${tx.nonce}, accountInfoNonce=${accountInfoNonce})`, ); if (accountInfoNonce > tx.nonce) { @@ -130,15 +128,14 @@ export class Precheck { /** * Checks the chain ID of the transaction. * @param {Transaction} tx - The transaction. - * @param {string} [requestId] - The request ID. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - chainId(tx: Transaction, requestId?: string): void { - const requestIdPrefix = formatRequestIdMessage(requestId); + chainId(tx: Transaction, requestDetails: RequestDetails): void { const txChainId = prepend0x(Number(tx.chainId).toString(16)); const passes = this.isLegacyUnprotectedEtx(tx) || txChainId === this.chain; if (!passes) { this.logger.trace( - `${requestIdPrefix} Failed chainId precheck for sendRawTransaction(transaction=%s, chainId=%s)`, + `${requestDetails.formattedRequestId} Failed chainId precheck for sendRawTransaction(transaction=%s, chainId=%s)`, JSON.stringify(tx), txChainId, ); @@ -163,8 +160,7 @@ export class Precheck { * @param {number} networkGasPriceInWeiBars - The predefined gas price of the network in weibar. * @param {string} [requestId] - The request ID. */ - gasPrice(tx: Transaction, networkGasPriceInWeiBars: number, requestId?: string): void { - const requestIdPrefix = formatRequestIdMessage(requestId); + gasPrice(tx: Transaction, networkGasPriceInWeiBars: number, requestDetails: RequestDetails): void { const networkGasPrice = BigInt(networkGasPriceInWeiBars); const txGasPrice = tx.gasPrice || tx.maxFeePerGas! + tx.maxPriorityFeePerGas!; @@ -186,7 +182,7 @@ export class Precheck { } this.logger.trace( - `${requestIdPrefix} Failed gas price precheck for sendRawTransaction(transaction=%s, gasPrice=%s, requiredGasPrice=%s)`, + `${requestDetails.formattedRequestId} Failed gas price precheck for sendRawTransaction(transaction=%s, gasPrice=%s, requiredGasPrice=%s)`, JSON.stringify(tx), txGasPrice, networkGasPrice, @@ -208,10 +204,9 @@ export class Precheck { * Checks the balance of the sender account. * @param {Transaction} tx - The transaction. * @param {any} account - The account information. - * @param {string} [requestId] - The request ID. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - balance(tx: Transaction, account: any, requestId?: string): void { - const requestIdPrefix = formatRequestIdMessage(requestId); + balance(tx: Transaction, account: any, requestDetails: RequestDetails): void { const result = { passes: false, error: predefined.INSUFFICIENT_ACCOUNT_BALANCE, @@ -221,7 +216,9 @@ export class Precheck { if (account == null) { this.logger.trace( - `${requestIdPrefix} Failed to retrieve account details from mirror node on balance precheck for sendRawTransaction(transaction=${JSON.stringify( + `${ + requestDetails.formattedRequestId + } Failed to retrieve account details from mirror node on balance precheck for sendRawTransaction(transaction=${JSON.stringify( tx, )}, totalValue=${txTotalValue})`, ); @@ -234,7 +231,7 @@ export class Precheck { result.passes = tinybars >= txTotalValue; } catch (error: any) { this.logger.trace( - `${requestIdPrefix} Error on balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, error=%s)`, + `${requestDetails.formattedRequestId} Error on balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, error=%s)`, JSON.stringify(tx), txTotalValue, error.message, @@ -249,7 +246,7 @@ export class Precheck { if (!result.passes) { this.logger.trace( - `${requestIdPrefix} Failed balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, accountTinyBarBalance=%s)`, + `${requestDetails.formattedRequestId} Failed balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, accountTinyBarBalance=%s)`, JSON.stringify(tx), txTotalValue, tinybars, @@ -261,10 +258,9 @@ export class Precheck { /** * Checks the gas limit of the transaction. * @param {Transaction} tx - The transaction. - * @param {string} [requestId] - The request ID. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - gasLimit(tx: Transaction, requestId?: string): void { - const requestIdPrefix = formatRequestIdMessage(requestId); + gasLimit(tx: Transaction, requestDetails: RequestDetails): void { const gasLimit = Number(tx.gasLimit); const failBaseLog = 'Failed gasLimit precheck for sendRawTransaction(transaction=%s).'; @@ -272,7 +268,7 @@ export class Precheck { if (gasLimit > constants.MAX_GAS_PER_SEC) { this.logger.trace( - `${requestIdPrefix} ${failBaseLog} Gas Limit was too high: %s, block gas limit: %s`, + `${requestDetails.formattedRequestId} ${failBaseLog} Gas Limit was too high: %s, block gas limit: %s`, JSON.stringify(tx), gasLimit, constants.MAX_GAS_PER_SEC, @@ -280,7 +276,7 @@ export class Precheck { throw predefined.GAS_LIMIT_TOO_HIGH(gasLimit, constants.MAX_GAS_PER_SEC); } else if (gasLimit < intrinsicGasCost) { this.logger.trace( - `${requestIdPrefix} ${failBaseLog} Gas Limit was too low: %s, intrinsic gas cost: %s`, + `${requestDetails.formattedRequestId} ${failBaseLog} Gas Limit was too low: %s, intrinsic gas cost: %s`, JSON.stringify(tx), gasLimit, intrinsicGasCost, @@ -349,12 +345,11 @@ export class Precheck { } } - transactionType(tx: Transaction, requestId?: string) { + transactionType(tx: Transaction, requestDetails: RequestDetails) { // Blob transactions are not supported as per HIP 866 if (tx.type === 3) { - const requestIdPrefix = formatRequestIdMessage(requestId); this.logger.trace( - `${requestIdPrefix} Transaction with type=${ + `${requestDetails.formattedRequestId} Transaction with type=${ tx.type } is unsupported for sendRawTransaction(transaction=${JSON.stringify(tx)})`, ); diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index e30b0232ee..b366e541c4 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -39,6 +39,8 @@ import HAPIService from './services/hapiService/hapiService'; import { SubscriptionController } from './subscriptionController'; import MetricService from './services/metricService/metricService'; import { CacheService } from './services/cacheService/cacheService'; +import { RequestDetails } from './types'; +import { Utils } from '../utils'; export class RelayImpl implements Relay { /** @@ -181,7 +183,7 @@ export class RelayImpl implements Relay { mirrorNodeClient: MirrorNodeClient, logger: Logger, register: Registry, - ) { + ): Gauge { const metricGaugeName = 'rpc_relay_operator_balance'; register.removeSingleMetric(metricGaugeName); return new Gauge({ @@ -193,7 +195,10 @@ export class RelayImpl implements Relay { // Invoked when the registry collects its metrics' values. // Allows for updated account balance tracking try { - const account = await mirrorNodeClient.getAccount(clientMain.operatorAccountId!.toString()); + const account = await mirrorNodeClient.getAccount( + clientMain.operatorAccountId!.toString(), + new RequestDetails({ requestId: Utils.generateRequestId(), ipAddress: '' }), + ); const accountBalance = account.balance?.balance; this.labels({ accountId: clientMain.operatorAccountId?.toString() }).set(accountBalance); } catch (e: any) { diff --git a/packages/relay/src/lib/services/cacheService/cacheService.ts b/packages/relay/src/lib/services/cacheService/cacheService.ts index 1d21fcacca..e1a9135403 100644 --- a/packages/relay/src/lib/services/cacheService/cacheService.ts +++ b/packages/relay/src/lib/services/cacheService/cacheService.ts @@ -23,6 +23,7 @@ import { Counter, Registry } from 'prom-client'; import { ICacheClient } from '../../clients/cache/ICacheClient'; import { LocalLRUCache, RedisCache } from '../../clients'; import { RedisCacheError } from '../../errors/RedisCacheError'; +import { RequestDetails } from '../../types'; /** * A service that manages caching using different cache implementations based on configuration. @@ -199,24 +200,25 @@ export class CacheService { * * @param {string} key - The cache key. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - The optional request ID prefix. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves with the cached value or null if not found. */ - private async getFromSharedCache(key: string, callingMethod: string, requestIdPrefix?: string): Promise { + private async getFromSharedCache(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { try { this.cacheMethodsCounter .labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.GET_ASYNC) .inc(1); - return await this.sharedCache.get(key, callingMethod, requestIdPrefix); + return await this.sharedCache.get(key, callingMethod, requestDetails); } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while getting the cache from Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while getting the cache from Redis. Fallback to internal cache.`, ); // fallback to internal cache in case of Redis error - return await this.getFromInternalCache(key, callingMethod, requestIdPrefix); + return await this.getFromInternalCache(key, callingMethod, requestDetails); } } @@ -224,15 +226,15 @@ export class CacheService { * If SharedCacheEnabled will use shared, otherwise will fallback to internal cache. * @param {string} key - The cache key. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - The optional request ID prefix. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves with the cached value or null if not found. * @template T - The type of the cached value. */ - public async getAsync(key: string, callingMethod: string, requestIdPrefix?: string): Promise { + public async getAsync(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { if (this.isSharedCacheEnabled) { - return await this.getFromSharedCache(key, callingMethod, requestIdPrefix); + return await this.getFromSharedCache(key, callingMethod, requestDetails); } else { - return await this.getFromInternalCache(key, callingMethod, requestIdPrefix); + return await this.getFromInternalCache(key, callingMethod, requestDetails); } } @@ -241,13 +243,13 @@ export class CacheService { * * @param {string} key - The cache key. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - The optional request ID prefix. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves with the cached value or null if not found. */ - private async getFromInternalCache(key: string, callingMethod: string, requestIdPrefix?: string): Promise { + private async getFromInternalCache(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.GET).inc(1); - return await this.internalCache.get(key, callingMethod, requestIdPrefix); + return await this.internalCache.get(key, callingMethod, requestDetails); } /** @@ -258,32 +260,33 @@ export class CacheService { * @param {*} value - The value to cache. * @param {string} callingMethod - The name of the method calling the cache. * @param {number} ttl - Time to live for the cached value in milliseconds (optional). - * @param {string} requestIdPrefix - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ public async set( key: string, value: any, callingMethod: string, + requestDetails: RequestDetails, ttl?: number, - requestIdPrefix?: string, ): Promise { if (this.isSharedCacheEnabled) { try { this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.SET).inc(1); - await this.sharedCache.set(key, value, callingMethod, ttl, requestIdPrefix); + await this.sharedCache.set(key, value, callingMethod, requestDetails, ttl); return; } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while setting the cache to Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while setting the cache to Redis. Fallback to internal cache.`, ); } } // fallback to internal cache in case of Redis error this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); - await this.internalCache.set(key, value, callingMethod, ttl, requestIdPrefix); + await this.internalCache.set(key, value, callingMethod, requestDetails, ttl); } /** @@ -294,21 +297,21 @@ export class CacheService { * @param {Record} entries - An object containing key-value pairs to cache. * @param {string} callingMethod - The name of the method calling the cache. * @param {number} [ttl] - Time to live for the cached value in milliseconds (optional). - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ public async multiSet( entries: Record, callingMethod: string, + requestDetails: RequestDetails, ttl?: number, - requestIdPrefix?: string, ): Promise { if (this.isSharedCacheEnabled) { const metricsMethod = this.shouldMultiSet ? CacheService.methods.MSET : CacheService.methods.PIPELINE; try { if (this.shouldMultiSet) { - await this.sharedCache.multiSet(entries, callingMethod, requestIdPrefix); + await this.sharedCache.multiSet(entries, callingMethod, requestDetails); } else { - await this.sharedCache.pipelineSet(entries, callingMethod, ttl, requestIdPrefix); + await this.sharedCache.pipelineSet(entries, callingMethod, requestDetails, ttl); } this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.REDIS, metricsMethod).inc(1); @@ -316,13 +319,14 @@ export class CacheService { } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while setting the cache to Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while setting the cache to Redis. Fallback to internal cache.`, ); } } // fallback to internal cache, but use pipeline, because of it's TTL support - await this.internalCache.pipelineSet(entries, callingMethod, ttl, requestIdPrefix); + await this.internalCache.pipelineSet(entries, callingMethod, requestDetails, ttl); this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); } @@ -332,37 +336,38 @@ export class CacheService { * Else the internal cache deletion is attempted. * @param {string} key - The key associated with the cached value to delete. * @param {string} callingMethod - The name of the method calling the cache. - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - public async delete(key: string, callingMethod: string, requestIdPrefix?: string): Promise { + public async delete(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { if (this.isSharedCacheEnabled) { try { this.cacheMethodsCounter .labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.DELETE) .inc(1); - await this.sharedCache.delete(key, callingMethod, requestIdPrefix); + await this.sharedCache.delete(key, callingMethod, requestDetails); return; } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while deleting cache from Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while deleting cache from Redis.`, ); } } // fallback to internal cache in case of Redis error this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.DELETE).inc(1); - await this.internalCache.delete(key, callingMethod, requestIdPrefix); + await this.internalCache.delete(key, callingMethod, requestDetails); } /** * Clears the cache. * If the shared cache is enabled and an error occurs while clearing it, just logs the error. * Else the internal cache clearing is attempted. - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ - public async clear(requestIdPrefix?: string): Promise { + public async clear(requestDetails: RequestDetails): Promise { if (this.isSharedCacheEnabled) { try { await this.sharedCache.clear(); @@ -370,7 +375,8 @@ export class CacheService { } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while clearing Redis cache. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while clearing Redis cache.`, ); } } @@ -384,35 +390,41 @@ export class CacheService { * @param {string} key - The key to increment. * @param {number} amount - The amount to increment by. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves with the new value of the key after incrementing. */ - public async incrBy(key: string, amount: number, callingMethod: string, requestIdPrefix?: string): Promise { + public async incrBy( + key: string, + amount: number, + callingMethod: string, + requestDetails: RequestDetails, + ): Promise { if (this.isSharedCacheEnabled && this.sharedCache instanceof RedisCache) { try { this.cacheMethodsCounter .labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.INCR_BY) .inc(1); - return await this.sharedCache.incrBy(key, amount, callingMethod, requestIdPrefix); + return await this.sharedCache.incrBy(key, amount, callingMethod, requestDetails); } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while incrementing cache in Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while incrementing cache in Redis.`, ); } } // Fallback to internal cache - const value = await this.getFromInternalCache(key, callingMethod, requestIdPrefix); + const value = await this.getFromInternalCache(key, callingMethod, requestDetails); const newValue = value + amount; const remainingTtl = this.internalCache instanceof LocalLRUCache - ? await this.internalCache.getRemainingTtl(key, callingMethod, requestIdPrefix) + ? await this.internalCache.getRemainingTtl(key, callingMethod, requestDetails) : undefined; this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); - await this.internalCache.set(key, newValue, callingMethod, remainingTtl, requestIdPrefix); + await this.internalCache.set(key, newValue, callingMethod, requestDetails, remainingTtl); return newValue; } @@ -422,38 +434,39 @@ export class CacheService { * @param {string} key - The key of the list. * @param {*} value - The value to push. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves with the new length of the list after pushing. */ - public async rPush(key: string, value: any, callingMethod: string, requestIdPrefix?: string): Promise { + public async rPush(key: string, value: any, callingMethod: string, requestDetails: RequestDetails): Promise { if (this.isSharedCacheEnabled && this.sharedCache instanceof RedisCache) { try { this.cacheMethodsCounter .labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.RPUSH) .inc(1); - return await this.sharedCache.rPush(key, value, callingMethod, requestIdPrefix); + return await this.sharedCache.rPush(key, value, callingMethod, requestDetails); } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while pushing cache in Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while pushing cache in Redis.`, ); } } // Fallback to internal cache - const values = (await this.getFromInternalCache(key, callingMethod, requestIdPrefix)) ?? []; + const values = (await this.getFromInternalCache(key, callingMethod, requestDetails)) ?? []; if (!Array.isArray(values)) { throw new Error(`Value at key ${key} is not an array`); } values.push(value); const remainingTtl = this.internalCache instanceof LocalLRUCache - ? await this.internalCache.getRemainingTtl(key, callingMethod, requestIdPrefix) + ? await this.internalCache.getRemainingTtl(key, callingMethod, requestDetails) : undefined; this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); - await this.internalCache.set(key, values, callingMethod, remainingTtl, requestIdPrefix); + await this.internalCache.set(key, values, callingMethod, requestDetails, remainingTtl); return values.length; } @@ -464,7 +477,7 @@ export class CacheService { * @param {number} start - The start index of the range. * @param {number} end - The end index of the range. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves with the values in the range. * @template T - The type of the values in the list. */ @@ -473,7 +486,7 @@ export class CacheService { start: number, end: number, callingMethod: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { if (this.isSharedCacheEnabled && this.sharedCache instanceof RedisCache) { try { @@ -481,17 +494,18 @@ export class CacheService { .labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.LRANGE) .inc(1); - return await this.sharedCache.lRange(key, start, end, callingMethod, requestIdPrefix); + return await this.sharedCache.lRange(key, start, end, callingMethod, requestDetails); } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while getting cache in Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while getting cache in Redis.`, ); } } // Fallback to internal cache - const values = (await this.getFromInternalCache(key, callingMethod, requestIdPrefix)) ?? []; + const values = (await this.getFromInternalCache(key, callingMethod, requestDetails)) ?? []; if (!Array.isArray(values)) { throw new Error(`Value at key ${key} is not an array`); } @@ -505,22 +519,23 @@ export class CacheService { * Retrieves all keys matching the given pattern. * @param {string} pattern - The pattern to match keys against. * @param {string} callingMethod - The name of the calling method. - * @param {string} [requestIdPrefix] - A prefix to include in log messages (optional). + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A Promise that resolves with an array of keys that match the pattern. */ - async keys(pattern: string, callingMethod: string, requestIdPrefix?: string): Promise { + async keys(pattern: string, callingMethod: string, requestDetails: RequestDetails): Promise { if (this.isSharedCacheEnabled && this.sharedCache instanceof RedisCache) { try { - return await this.sharedCache.keys(pattern, callingMethod, requestIdPrefix); + return await this.sharedCache.keys(pattern, callingMethod, requestDetails); } catch (error) { const redisError = new RedisCacheError(error); this.logger.error( - `${requestIdPrefix} Error occurred while getting keys from Redis. Fallback to internal cache. ${redisError}`, + redisError, + `${requestDetails.formattedRequestId} Error occurred while getting keys from Redis.`, ); } } // Fallback to internal cache - return this.internalCache.keys(pattern, callingMethod, requestIdPrefix); + return this.internalCache.keys(pattern, callingMethod, requestDetails); } } diff --git a/packages/relay/src/lib/services/debugService/IDebugService.ts b/packages/relay/src/lib/services/debugService/IDebugService.ts index a57566ab49..0237eb5bf1 100644 --- a/packages/relay/src/lib/services/debugService/IDebugService.ts +++ b/packages/relay/src/lib/services/debugService/IDebugService.ts @@ -18,7 +18,7 @@ * */ -import { ITracerConfig } from '../../types'; +import { ITracerConfig, RequestDetails } from '../../types'; import type { TracerType } from '../../constants'; export interface IDebugService { @@ -26,7 +26,7 @@ export interface IDebugService { transactionIdOrHash: string, tracer: TracerType, tracerConfig: ITracerConfig, - requestIdPrefix?: string, + requestDetails: RequestDetails, ) => Promise; - resolveAddress: (address: string, types?: string[], requestIdPrefix?: string) => Promise; + resolveAddress: (address: string, requestDetails: RequestDetails, types?: string[]) => Promise; } diff --git a/packages/relay/src/lib/services/debugService/index.ts b/packages/relay/src/lib/services/debugService/index.ts index 6ee0b7ae51..6a57d47bbf 100644 --- a/packages/relay/src/lib/services/debugService/index.ts +++ b/packages/relay/src/lib/services/debugService/index.ts @@ -29,6 +29,7 @@ import { EthImpl } from '../../eth'; import { IOpcodesResponse } from '../../clients/models/IOpcodesResponse'; import { IOpcode } from '../../clients/models/IOpcode'; import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig } from '../../types'; +import { RequestDetails } from '../../types'; /** * Represents a DebugService for tracing and debugging transactions and debugging @@ -88,7 +89,7 @@ export class DebugService implements IDebugService { * @param {string} transactionIdOrHash - The ID or hash of the transaction to be traced. * @param {TracerType} tracer - The type of tracer to use (either 'CallTracer' or 'OpcodeLogger'). * @param {ITracerConfig} tracerConfig - The configuration object for the tracer. - * @param {string} [requestIdPrefix] - An optional request id. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @throws {Error} Throws an error if the specified tracer type is not supported or if an exception occurs during the trace. * @returns {Promise} A Promise that resolves to the result of the trace operation. * @@ -99,15 +100,15 @@ export class DebugService implements IDebugService { transactionIdOrHash: string, tracer: TracerType, tracerConfig: ITracerConfig, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { - this.logger.trace(`${requestIdPrefix} debug_traceTransaction(${transactionIdOrHash})`); + this.logger.trace(`${requestDetails.formattedRequestId} debug_traceTransaction(${transactionIdOrHash})`); try { DebugService.requireDebugAPIEnabled(); if (tracer === TracerType.CallTracer) { - return await this.callTracer(transactionIdOrHash, tracerConfig as ICallTracerConfig, requestIdPrefix); + return await this.callTracer(transactionIdOrHash, tracerConfig as ICallTracerConfig, requestDetails); } else if (tracer === TracerType.OpcodeLogger) { - return await this.callOpcodeLogger(transactionIdOrHash, tracerConfig as IOpcodeLoggerConfig, requestIdPrefix); + return await this.callOpcodeLogger(transactionIdOrHash, tracerConfig as IOpcodeLoggerConfig, requestDetails); } } catch (e) { throw this.common.genericErrorHandler(e); @@ -119,23 +120,23 @@ export class DebugService implements IDebugService { * * @async * @param {any} result - The response from the actions endpoint. - * @param {string} requestIdPrefix - The request prefix id. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise<[] | any>} The formatted actions response in an array. */ - async formatActionsResult(result: any, requestIdPrefix?: string): Promise<[] | any> { + async formatActionsResult(result: any, requestDetails: RequestDetails): Promise<[] | any> { return await Promise.all( result.map(async (action, index) => { const { resolvedFrom, resolvedTo } = await this.resolveMultipleAddresses( action.from, action.to, - requestIdPrefix, + requestDetails, ); // The actions endpoint does not return input and output for the calls so we get them from another endpoint // The first one is excluded because we take its input and output from the contracts/results/{transactionIdOrHash} endpoint const contract = index !== 0 && action.call_operation_type === CallType.CREATE - ? await this.mirrorNodeClient.getContract(action.to, requestIdPrefix) + ? await this.mirrorNodeClient.getContract(action.to, requestDetails) : undefined; return { @@ -201,22 +202,22 @@ export class DebugService implements IDebugService { * @async * @param {string} address - The address to be resolved. * @param {[string]} types - The possible types of the address. - * @param {string} requestIdPrefix - The request prefix id. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The address returned as an EVM address. */ async resolveAddress( address: string, - types = [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT], - requestIdPrefix?: string, + requestDetails: RequestDetails, + types: string[] = [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT], ): Promise { // if the address is null or undefined we return it as is if (!address) return address; const entity = await this.mirrorNodeClient.resolveEntityType( address, - types, EthImpl.debugTraceTransaction, - requestIdPrefix, + requestDetails, + types, ); if ( @@ -233,15 +234,15 @@ export class DebugService implements IDebugService { async resolveMultipleAddresses( from: string, to: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise<{ resolvedFrom: string; resolvedTo: string }> { const [resolvedFrom, resolvedTo] = await Promise.all([ - this.resolveAddress( - from, - [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT], - requestIdPrefix, - ), - this.resolveAddress(to, [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT], requestIdPrefix), + this.resolveAddress(from, requestDetails, [ + constants.TYPE_CONTRACT, + constants.TYPE_TOKEN, + constants.TYPE_ACCOUNT, + ]), + this.resolveAddress(to, requestDetails, [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT]), ]); return { resolvedFrom, resolvedTo }; @@ -255,13 +256,13 @@ export class DebugService implements IDebugService { * @param {boolean} tracerConfig.enableMemory - Whether to enable memory. * @param {boolean} tracerConfig.disableStack - Whether to disable stack. * @param {boolean} tracerConfig.disableStorage - Whether to disable storage. - * @param {string} requestIdPrefix - The request prefix id. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The formatted response. */ async callOpcodeLogger( transactionIdOrHash: string, tracerConfig: IOpcodeLoggerConfig, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { try { const options = { @@ -271,7 +272,7 @@ export class DebugService implements IDebugService { }; const response = await this.mirrorNodeClient.getContractsResultsOpcodes( transactionIdOrHash, - requestIdPrefix, + requestDetails, options, ); return await this.formatOpcodesResult(response, options); @@ -286,25 +287,25 @@ export class DebugService implements IDebugService { * @async * @param {string} transactionHash - The hash of the transaction to be debugged. * @param {ICallTracerConfig} tracerConfig - The tracer config to be used. - * @param {string} requestIdPrefix - The request prefix id. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} The formatted response. */ async callTracer( transactionHash: string, tracerConfig: ICallTracerConfig, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { try { const [actionsResponse, transactionsResponse] = await Promise.all([ - this.mirrorNodeClient.getContractsResultsActions(transactionHash, requestIdPrefix), - this.mirrorNodeClient.getContractResultWithRetry(transactionHash), + this.mirrorNodeClient.getContractsResultsActions(transactionHash, requestDetails), + this.mirrorNodeClient.getContractResultWithRetry(transactionHash, requestDetails), ]); if (!actionsResponse || !transactionsResponse) { throw predefined.RESOURCE_NOT_FOUND(`Failed to retrieve contract results for transaction ${transactionHash}`); } const { call_type: type } = actionsResponse.actions[0]; - const formattedActions = await this.formatActionsResult(actionsResponse.actions, requestIdPrefix); + const formattedActions = await this.formatActionsResult(actionsResponse.actions, requestDetails); const { from, @@ -318,7 +319,7 @@ export class DebugService implements IDebugService { result, } = transactionsResponse; - const { resolvedFrom, resolvedTo } = await this.resolveMultipleAddresses(from, to, requestIdPrefix); + const { resolvedFrom, resolvedTo } = await this.resolveMultipleAddresses(from, to, requestDetails); const value = amount === 0 ? EthImpl.zeroHex : numberTo0x(amount); const errorResult = result !== constants.SUCCESS ? result : undefined; diff --git a/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts b/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts index 6587c96fb7..f0873fc8e3 100644 --- a/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts +++ b/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts @@ -18,6 +18,7 @@ * */ import { Log } from '../../../model'; +import { RequestDetails } from '../../../types'; export interface ICommonService { blockTagIsLatestOrPending(tag: any): boolean; @@ -26,27 +27,31 @@ export interface ICommonService { params: any, fromBlock: string, toBlock: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, address?: string | string[] | null, ): Promise; getHistoricalBlockResponse( + requestDetails: RequestDetails, blockNumberOrTag?: string | null, returnLatest?: boolean, - requestIdPrefix?: string | undefined, ): Promise; - getLatestBlockNumber(requestIdPrefix?: string): Promise; + getLatestBlockNumber(requestDetails: RequestDetails): Promise; genericErrorHandler(error: any, logMessage?: string): void; - validateBlockHashAndAddTimestampToParams(params: any, blockHash: string, requestIdPrefix?: string): Promise; + validateBlockHashAndAddTimestampToParams( + params: any, + blockHash: string, + requestDetails: RequestDetails, + ): Promise; addTopicsToParams(params: any, topics: any[] | null): void; - getLogsByAddress(address: string | [string], params: any, requestIdPrefix): Promise; + getLogsByAddress(address: string | [string], params: any, requestDetails: RequestDetails): Promise; - getLogsWithParams(address: string | [string] | null, params, requestIdPrefix?: string): Promise; + getLogsWithParams(address: string | [string] | null, params: any, requestDetails: RequestDetails): Promise; getLogs( blockHash: string | null, @@ -54,6 +59,6 @@ export interface ICommonService { toBlock: string | 'latest', address: string | [string] | null, topics: any[] | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise; } diff --git a/packages/relay/src/lib/services/ethService/ethCommonService/index.ts b/packages/relay/src/lib/services/ethService/ethCommonService/index.ts index 3439c53f9b..3e8ff7d084 100644 --- a/packages/relay/src/lib/services/ethService/ethCommonService/index.ts +++ b/packages/relay/src/lib/services/ethService/ethCommonService/index.ts @@ -29,6 +29,7 @@ import { MirrorNodeClientError } from '../../../errors/MirrorNodeClientError'; import { Log } from '../../../model'; import * as _ from 'lodash'; import { CacheService } from '../../cacheService/cacheService'; +import { RequestDetails } from '../../../types'; /** * Create a new Common Service implementation. @@ -102,14 +103,14 @@ export class CommonService implements ICommonService { params: any, fromBlock: string, toBlock: string, - requestIdPrefix?: string, + requestDetails: RequestDetails, address?: string | string[] | null, ) { if (this.blockTagIsLatestOrPending(toBlock)) { toBlock = CommonService.blockLatest; } - const latestBlockNumber: string = await this.getLatestBlockNumber(requestIdPrefix); + const latestBlockNumber: string = await this.getLatestBlockNumber(requestDetails); // toBlock is a number and is less than the current block number and fromBlock is not defined if (Number(toBlock) < Number(latestBlockNumber) && !fromBlock) { @@ -124,7 +125,7 @@ export class CommonService implements ICommonService { let toBlockNum; params.timestamp = []; - const fromBlockResponse = await this.getHistoricalBlockResponse(fromBlock, true, requestIdPrefix); + const fromBlockResponse = await this.getHistoricalBlockResponse(requestDetails, fromBlock, true); if (!fromBlockResponse) { return false; } @@ -135,7 +136,7 @@ export class CommonService implements ICommonService { params.timestamp.push(`lte:${fromBlockResponse.timestamp.to}`); } else { fromBlockNum = parseInt(fromBlockResponse.number); - const toBlockResponse = await this.getHistoricalBlockResponse(toBlock, true, requestIdPrefix); + const toBlockResponse = await this.getHistoricalBlockResponse(requestDetails, toBlock, true); if (toBlockResponse != null) { params.timestamp.push(`lte:${toBlockResponse.timestamp.to}`); toBlockNum = parseInt(toBlockResponse.number); @@ -163,13 +164,14 @@ export class CommonService implements ICommonService { * returns the block response * otherwise return undefined. * - * @param blockNumberOrTag + * @param requestDetails + * @param blockNumberOrTagOrHash * @param returnLatest */ public async getHistoricalBlockResponse( + requestDetails: RequestDetails, blockNumberOrTagOrHash?: string | null, returnLatest?: boolean, - requestIdPrefix?: string | undefined, ): Promise { if (!returnLatest && this.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) { return null; @@ -177,7 +179,7 @@ export class CommonService implements ICommonService { const blockNumber = Number(blockNumberOrTagOrHash); if (blockNumberOrTagOrHash != null && blockNumberOrTagOrHash.length < 32 && !isNaN(blockNumber)) { - const latestBlockResponse = await this.mirrorNodeClient.getLatestBlock(requestIdPrefix); + const latestBlockResponse = await this.mirrorNodeClient.getLatestBlock(requestDetails); const latestBlock = latestBlockResponse.blocks[0]; if (blockNumber > latestBlock.number + this.maxBlockRange) { return null; @@ -185,39 +187,41 @@ export class CommonService implements ICommonService { } if (blockNumberOrTagOrHash == null || this.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) { - const latestBlockResponse = await this.mirrorNodeClient.getLatestBlock(requestIdPrefix); + const latestBlockResponse = await this.mirrorNodeClient.getLatestBlock(requestDetails); return latestBlockResponse.blocks[0]; } if (blockNumberOrTagOrHash == CommonService.blockEarliest) { - return await this.mirrorNodeClient.getBlock(0, requestIdPrefix); + return await this.mirrorNodeClient.getBlock(0, requestDetails); } if (blockNumberOrTagOrHash.length < 32) { - return await this.mirrorNodeClient.getBlock(Number(blockNumberOrTagOrHash), requestIdPrefix); + return await this.mirrorNodeClient.getBlock(Number(blockNumberOrTagOrHash), requestDetails); } - return await this.mirrorNodeClient.getBlock(blockNumberOrTagOrHash, requestIdPrefix); + return await this.mirrorNodeClient.getBlock(blockNumberOrTagOrHash, requestDetails); } /** * Gets the most recent block number. */ - public async getLatestBlockNumber(requestIdPrefix?: string): Promise { + public async getLatestBlockNumber(requestDetails: RequestDetails): Promise { // check for cached value const cacheKey = `${constants.CACHE_KEY.ETH_BLOCK_NUMBER}`; const blockNumberCached = await this.cacheService.getAsync( cacheKey, CommonService.latestBlockNumber, - requestIdPrefix, + requestDetails, ); if (blockNumberCached) { - this.logger.trace(`${requestIdPrefix} returning cached value ${cacheKey}:${JSON.stringify(blockNumberCached)}`); + this.logger.trace( + `${requestDetails.formattedRequestId} returning cached value ${cacheKey}:${JSON.stringify(blockNumberCached)}`, + ); return blockNumberCached; } - const blocksResponse = await this.mirrorNodeClient.getLatestBlock(requestIdPrefix); + const blocksResponse = await this.mirrorNodeClient.getLatestBlock(requestDetails); const blocks = blocksResponse !== null ? blocksResponse.blocks : null; if (Array.isArray(blocks) && blocks.length > 0) { const currentBlock = numberTo0x(blocks[0].number); @@ -226,8 +230,8 @@ export class CommonService implements ICommonService { cacheKey, currentBlock, CommonService.latestBlockNumber, + requestDetails, this.ethBlockNumberCacheTtlMs, - requestIdPrefix, ); return currentBlock; @@ -253,9 +257,13 @@ export class CommonService implements ICommonService { throw predefined.INTERNAL_ERROR(error.message.toString()); } - public async validateBlockHashAndAddTimestampToParams(params: any, blockHash: string, requestIdPrefix?: string) { + public async validateBlockHashAndAddTimestampToParams( + params: any, + blockHash: string, + requestDetails: RequestDetails, + ) { try { - const block = await this.mirrorNodeClient.getBlock(blockHash, requestIdPrefix); + const block = await this.mirrorNodeClient.getBlock(blockHash, requestDetails); if (block) { params.timestamp = [`gte:${block.timestamp.from}`, `lte:${block.timestamp.to}`]; } else { @@ -282,10 +290,10 @@ export class CommonService implements ICommonService { } } - public async getLogsByAddress(address: string | string[], params: any, requestIdPrefix) { + public async getLogsByAddress(address: string | string[], params: any, requestDetails: RequestDetails) { const addresses = Array.isArray(address) ? address : [address]; const logPromises = addresses.map((addr) => - this.mirrorNodeClient.getContractResultsLogsByAddress(addr, params, undefined, requestIdPrefix), + this.mirrorNodeClient.getContractResultsLogsByAddress(addr, requestDetails, params, undefined), ); const logResults = await Promise.all(logPromises); @@ -297,14 +305,18 @@ export class CommonService implements ICommonService { return logs; } - public async getLogsWithParams(address: string | string[] | null, params, requestIdPrefix?: string): Promise { + public async getLogsWithParams( + address: string | string[] | null, + params: any, + requestDetails: RequestDetails, + ): Promise { const EMPTY_RESPONSE = []; let logResults; if (address) { - logResults = await this.getLogsByAddress(address, params, requestIdPrefix); + logResults = await this.getLogsByAddress(address, params, requestDetails); } else { - logResults = await this.mirrorNodeClient.getContractResultsLogs(params, undefined, requestIdPrefix); + logResults = await this.mirrorNodeClient.getContractResultsLogs(requestDetails, params); } if (!logResults) { @@ -337,23 +349,23 @@ export class CommonService implements ICommonService { toBlock: string | 'latest', address: string | string[] | null, topics: any[] | null, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { const EMPTY_RESPONSE = []; const params: any = {}; if (blockHash) { - if (!(await this.validateBlockHashAndAddTimestampToParams(params, blockHash, requestIdPrefix))) { + if (!(await this.validateBlockHashAndAddTimestampToParams(params, blockHash, requestDetails))) { return EMPTY_RESPONSE; } } else if ( - !(await this.validateBlockRangeAndAddTimestampToParams(params, fromBlock, toBlock, requestIdPrefix, address)) + !(await this.validateBlockRangeAndAddTimestampToParams(params, fromBlock, toBlock, requestDetails, address)) ) { return EMPTY_RESPONSE; } this.addTopicsToParams(params, topics); - return this.getLogsWithParams(address, params, requestIdPrefix); + return this.getLogsWithParams(address, params, requestDetails); } } diff --git a/packages/relay/src/lib/services/ethService/ethFilterService/IFilterService.ts b/packages/relay/src/lib/services/ethService/ethFilterService/IFilterService.ts index efa42823d8..169d592e73 100644 --- a/packages/relay/src/lib/services/ethService/ethFilterService/IFilterService.ts +++ b/packages/relay/src/lib/services/ethService/ethFilterService/IFilterService.ts @@ -20,23 +20,24 @@ import { JsonRpcError } from '../../../errors/JsonRpcError'; import { Log } from '../../../model'; +import { RequestDetails } from '../../../types'; export interface IFilterService { newFilter( fromBlock: string, toBlock: string, + requestDetails: RequestDetails, address?: string, topics?: any[], - requestIdPrefix?: string, ): Promise; - newBlockFilter(requestIdPrefix?: string): Promise; + newBlockFilter(requestDetails: RequestDetails): Promise; - uninstallFilter(filterId: string, requestId?: string): Promise; + uninstallFilter(filterId: string, requestDetails: RequestDetails): Promise; - newPendingTransactionFilter(requestIdPrefix?: string): JsonRpcError; + newPendingTransactionFilter(requestDetails: RequestDetails): JsonRpcError; - getFilterLogs(filterId: string, requestId?: string): Promise; + getFilterLogs(filterId: string, requestDetails: RequestDetails): Promise; - getFilterChanges(filterId: string, requestIdPrefix?: string): Promise; + getFilterChanges(filterId: string, requestDetails: RequestDetails): Promise; } diff --git a/packages/relay/src/lib/services/ethService/ethFilterService/index.ts b/packages/relay/src/lib/services/ethService/ethFilterService/index.ts index 1cde8120ad..1e4bd60079 100644 --- a/packages/relay/src/lib/services/ethService/ethFilterService/index.ts +++ b/packages/relay/src/lib/services/ethService/ethFilterService/index.ts @@ -22,11 +22,12 @@ import { Logger } from 'pino'; import { MirrorNodeClient } from '../../../clients'; import constants from '../../../constants'; import { IFilterService } from './IFilterService'; -import { CommonService } from './../ethCommonService'; +import { CommonService } from '../ethCommonService'; import { generateRandomHex } from '../../../../formatters'; import { JsonRpcError, predefined } from '../../../errors/JsonRpcError'; import { Log } from '../../../model'; import { CacheService } from '../../cacheService/cacheService'; +import { RequestDetails } from '../../../types'; /** * Create a new Filter Service implementation. @@ -61,7 +62,7 @@ export class FilterService implements IFilterService { public readonly ethGetFilterChanges = 'eth_getFilterChanges'; private readonly common: CommonService; - private readonly supportedTypes; + private readonly supportedTypes: string[]; constructor(mirrorNodeClient: MirrorNodeClient, logger: Logger, cacheService: CacheService, common: CommonService) { this.mirrorNodeClient = mirrorNodeClient; @@ -76,9 +77,9 @@ export class FilterService implements IFilterService { * Creates a new filter with the specified type and parameters * @param type * @param params - * @param requestIdPrefix + * @param requestDetails */ - async createFilter(type: string, params: any, requestIdPrefix?: string): Promise { + async createFilter(type: string, params: any, requestDetails: RequestDetails): Promise { const filterId = generateRandomHex(); const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; await this.cacheService.set( @@ -89,10 +90,10 @@ export class FilterService implements IFilterService { lastQueried: null, }, this.ethNewFilter, + requestDetails, constants.FILTER.TTL, - requestIdPrefix, ); - this.logger.trace(`${requestIdPrefix} created filter with TYPE=${type}, params: ${params}`); + this.logger.trace(`${requestDetails.formattedRequestId} created filter with TYPE=${type}, params: ${params}`); return filterId; } @@ -111,15 +112,16 @@ export class FilterService implements IFilterService { * @param toBlock * @param address * @param topics - * @param requestIdPrefix + * @param requestDetails */ async newFilter( fromBlock: string = 'latest', toBlock: string = 'latest', + requestDetails: RequestDetails, address?: string, topics?: any[], - requestIdPrefix?: string, - ): Promise { + ): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace( `${requestIdPrefix} newFilter(fromBlock=${fromBlock}, toBlock=${toBlock}, address=${address}, topics=${topics})`, ); @@ -127,7 +129,7 @@ export class FilterService implements IFilterService { FilterService.requireFiltersEnabled(); if ( - !(await this.common.validateBlockRangeAndAddTimestampToParams({}, fromBlock, toBlock, requestIdPrefix, address)) + !(await this.common.validateBlockRangeAndAddTimestampToParams({}, fromBlock, toBlock, requestDetails, address)) ) { throw predefined.INVALID_BLOCK_RANGE; } @@ -135,60 +137,62 @@ export class FilterService implements IFilterService { return await this.createFilter( constants.FILTER.TYPE.LOG, { - fromBlock: fromBlock === 'latest' ? await this.common.getLatestBlockNumber(requestIdPrefix) : fromBlock, + fromBlock: fromBlock === 'latest' ? await this.common.getLatestBlockNumber(requestDetails) : fromBlock, toBlock, address, topics, }, - requestIdPrefix, + requestDetails, ); } catch (e) { throw this.common.genericErrorHandler(e); } } - async newBlockFilter(requestIdPrefix?: string): Promise { + async newBlockFilter(requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} newBlockFilter()`); try { FilterService.requireFiltersEnabled(); return await this.createFilter( constants.FILTER.TYPE.NEW_BLOCK, { - blockAtCreation: await this.common.getLatestBlockNumber(requestIdPrefix), + blockAtCreation: await this.common.getLatestBlockNumber(requestDetails), }, - requestIdPrefix, + requestDetails, ); } catch (e) { throw this.common.genericErrorHandler(e); } } - public async uninstallFilter(filterId: string, requestIdPrefix?: string | undefined): Promise { + public async uninstallFilter(filterId: string, requestDetails: RequestDetails): Promise { + const requestIdPrefix = requestDetails.formattedRequestId; this.logger.trace(`${requestIdPrefix} uninstallFilter(${filterId})`); FilterService.requireFiltersEnabled(); const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - const filter = await this.cacheService.getAsync(cacheKey, this.ethUninstallFilter, requestIdPrefix); + const filter = await this.cacheService.getAsync(cacheKey, this.ethUninstallFilter, requestDetails); if (filter) { - await this.cacheService.delete(cacheKey, this.ethUninstallFilter, requestIdPrefix); + await this.cacheService.delete(cacheKey, this.ethUninstallFilter, requestDetails); return true; } return false; } - public newPendingTransactionFilter(requestIdPrefix?: string | undefined): JsonRpcError { - this.logger.trace(`${requestIdPrefix} newPendingTransactionFilter()`); + public newPendingTransactionFilter(requestDetails: RequestDetails): JsonRpcError { + this.logger.trace(`${requestDetails.formattedRequestId} newPendingTransactionFilter()`); return predefined.UNSUPPORTED_METHOD; } - public async getFilterLogs(filterId: string, requestIdPrefix?: string | undefined): Promise { - this.logger.trace(`${requestIdPrefix} getFilterLogs(${filterId})`); + public async getFilterLogs(filterId: string, requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} getFilterLogs(${filterId})`); FilterService.requireFiltersEnabled(); const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - const filter = await this.cacheService.getAsync(cacheKey, this.ethGetFilterLogs, requestIdPrefix); + const filter = await this.cacheService.getAsync(cacheKey, this.ethGetFilterLogs, requestDetails); if (filter?.type != constants.FILTER.TYPE.LOG) { throw predefined.FILTER_NOT_FOUND; } @@ -199,16 +203,16 @@ export class FilterService implements IFilterService { filter?.params.toBlock, filter?.params.address, filter?.params.topics, - requestIdPrefix, + requestDetails, ); } - public async getFilterChanges(filterId: string, requestIdPrefix?: string): Promise { - this.logger.trace(`${requestIdPrefix} getFilterChanges(${filterId})`); + public async getFilterChanges(filterId: string, requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} getFilterChanges(${filterId})`); FilterService.requireFiltersEnabled(); const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - const filter = await this.cacheService.getAsync(cacheKey, this.ethGetFilterChanges, requestIdPrefix); + const filter = await this.cacheService.getAsync(cacheKey, this.ethGetFilterChanges, requestDetails); if (!filter) { throw predefined.FILTER_NOT_FOUND; @@ -222,7 +226,7 @@ export class FilterService implements IFilterService { filter?.params.toBlock, filter?.params.address, filter?.params.topics, - requestIdPrefix, + requestDetails, ); // get the latest block number and add 1 to exclude current results from the next response because @@ -231,10 +235,11 @@ export class FilterService implements IFilterService { Number( result.length ? result[result.length - 1].blockNumber - : await this.common.getLatestBlockNumber(requestIdPrefix), + : await this.common.getLatestBlockNumber(requestDetails), ) + 1; } else if (filter.type === constants.FILTER.TYPE.NEW_BLOCK) { result = await this.mirrorNodeClient.getBlocks( + requestDetails, [`gt:${filter.lastQueried || filter.params.blockAtCreation}`], undefined, { @@ -245,7 +250,7 @@ export class FilterService implements IFilterService { latestBlockNumber = Number( result?.blocks?.length ? result.blocks[result.blocks.length - 1].number - : await this.common.getLatestBlockNumber(requestIdPrefix), + : await this.common.getLatestBlockNumber(requestDetails), ); result = result?.blocks?.map((r) => r.hash) || []; @@ -262,8 +267,8 @@ export class FilterService implements IFilterService { lastQueried: latestBlockNumber, }, this.ethGetFilterChanges, + requestDetails, constants.FILTER.TTL, - requestIdPrefix, ); return result; diff --git a/packages/relay/src/lib/services/hbarLimitService/IHbarLimitService.ts b/packages/relay/src/lib/services/hbarLimitService/IHbarLimitService.ts index ef0da42e57..b8f1f49fe1 100644 --- a/packages/relay/src/lib/services/hbarLimitService/IHbarLimitService.ts +++ b/packages/relay/src/lib/services/hbarLimitService/IHbarLimitService.ts @@ -18,15 +18,16 @@ * */ +import { RequestDetails } from '../../types'; + export interface IHbarLimitService { - resetLimiter(): Promise; + resetLimiter(requestDetails: RequestDetails): Promise; shouldLimit( mode: string, methodName: string, ethAddress: string, - ipAddress?: string, - requestId?: string, + requestDetails: RequestDetails, estimatedTxFee?: number, ): Promise; - addExpense(cost: number, ethAddress: string, ipAddress?: string): Promise; + addExpense(cost: number, ethAddress: string, requestDetails: RequestDetails, ipAddress?: string): Promise; } diff --git a/packages/relay/src/lib/services/hbarLimitService/index.ts b/packages/relay/src/lib/services/hbarLimitService/index.ts index 8a0ec03b83..a791c75024 100644 --- a/packages/relay/src/lib/services/hbarLimitService/index.ts +++ b/packages/relay/src/lib/services/hbarLimitService/index.ts @@ -21,12 +21,12 @@ import { Logger } from 'pino'; import { Counter, Gauge, Registry } from 'prom-client'; import { IHbarLimitService } from './IHbarLimitService'; -import { formatRequestIdMessage } from '../../../formatters'; import { SubscriptionType } from '../../db/types/hbarLimiter/subscriptionType'; import { IDetailedHbarSpendingPlan } from '../../db/types/hbarLimiter/hbarSpendingPlan'; import { HbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/hbarSpendingPlanRepository'; import { EthAddressHbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; import { IPAddressHbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; +import { RequestDetails } from '../../types/RequestDetails'; export class HbarLimitService implements IHbarLimitService { static readonly ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000; @@ -143,15 +143,14 @@ export class HbarLimitService implements IHbarLimitService { * @param {string} [requestId] - An optional unique request ID for tracking the request. * @returns {Promise} - A promise that resolves when the operation is complete. */ - async resetLimiter(requestId?: string): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); - this.logger.trace(`${requestIdPrefix} Resetting HBAR rate limiter...`); - await this.hbarSpendingPlanRepository.resetAllSpentTodayEntries(); + async resetLimiter(requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} Resetting HBAR rate limiter...`); + await this.hbarSpendingPlanRepository.resetAllSpentTodayEntries(requestDetails); this.resetBudget(); this.resetMetrics(); this.reset = this.getResetTimestamp(); this.logger.trace( - `${requestIdPrefix} HBAR Rate Limit reset: remainingBudget=${this.remainingBudget}, newResetTimestamp=${this.reset}`, + `${requestDetails.formattedRequestId} HBAR Rate Limit reset: remainingBudget=${this.remainingBudget}, newResetTimestamp=${this.reset}`, ); } @@ -161,39 +160,38 @@ export class HbarLimitService implements IHbarLimitService { * @param {string} methodName - The name of the method being invoked. * @param {string} ethAddress - The eth address to check. * @param {string} [ipAddress] - The ip address to check. - * @param {string} [requestId] - A prefix to include in log messages (optional). * @param {number} [estimatedTxFee] - The total estimated transaction fee, default to 0. + * @param {RequestDetails} requestDetails The request details for logging and tracking. * @returns {Promise} - A promise that resolves with a boolean indicating if the address should be limited. */ async shouldLimit( mode: string, methodName: string, ethAddress: string, - ipAddress?: string, - requestId?: string, + requestDetails: RequestDetails, estimatedTxFee: number = 0, ): Promise { - const requestIdPrefix = formatRequestIdMessage(requestId); - if (await this.isDailyBudgetExceeded(mode, methodName, estimatedTxFee, requestIdPrefix)) { + const ipAddress = requestDetails.ipAddress; + if (await this.isDailyBudgetExceeded(mode, methodName, estimatedTxFee, requestDetails)) { return true; } if (!ethAddress && !ipAddress) { this.logger.warn('No eth address or ip address provided, cannot check if address should be limited'); return false; } - const user = `(ethAddress=${ethAddress}, ipAddress=${ipAddress})`; - this.logger.trace(`${requestIdPrefix} Checking if ${user} should be limited...`); - let spendingPlan = await this.getSpendingPlan(ethAddress, ipAddress); + const user = `(ethAddress=${ethAddress})`; + this.logger.trace(`${requestDetails.formattedRequestId} Checking if ${user} should be limited...`); + let spendingPlan = await this.getSpendingPlan(ethAddress, requestDetails); if (!spendingPlan) { // Create a basic spending plan if none exists for the eth address or ip address - spendingPlan = await this.createBasicSpendingPlan(ethAddress, ipAddress); + spendingPlan = await this.createBasicSpendingPlan(ethAddress, requestDetails); } const dailyLimit = HbarLimitService.DAILY_LIMITS[spendingPlan.subscriptionType]; const exceedsLimit = spendingPlan.spentToday >= dailyLimit || spendingPlan.spentToday + estimatedTxFee > dailyLimit; this.logger.trace( - `${requestIdPrefix} ${user} ${exceedsLimit ? 'should' : 'should not'} be limited: spentToday=${ + `${requestDetails.formattedRequestId} ${user} ${exceedsLimit ? 'should' : 'should not'} be limited, spentToday=${ spendingPlan.spentToday }, estimatedTxFee=${estimatedTxFee}, dailyLimit=${dailyLimit}`, ); @@ -204,26 +202,26 @@ export class HbarLimitService implements IHbarLimitService { * Add expense to the remaining budget. * @param {number} cost - The cost of the expense. * @param {string} ethAddress - The Ethereum address to add the expense to. - * @param {string} [ipAddress] - The optional IP address to add the expense to. - * @param {string} [requestId] - An optional unique request ID for tracking the request. + * @param {string} ipAddress - The optional IP address to add the expense to. + * @param {RequestDetails} requestDetails The request details for logging and tracking. * @returns {Promise} - A promise that resolves when the expense has been added. */ - async addExpense(cost: number, ethAddress: string, ipAddress?: string, requestId?: string): Promise { + async addExpense(cost: number, ethAddress: string, requestDetails: RequestDetails): Promise { + const ipAddress = requestDetails.ipAddress; if (!ethAddress && !ipAddress) { throw new Error('Cannot add expense without an eth address or ip address'); } - let spendingPlan = await this.getSpendingPlan(ethAddress, ipAddress); + let spendingPlan = await this.getSpendingPlan(ethAddress, requestDetails); if (!spendingPlan) { // Create a basic spending plan if none exists for the eth address or ip address - spendingPlan = await this.createBasicSpendingPlan(ethAddress, ipAddress); + spendingPlan = await this.createBasicSpendingPlan(ethAddress, requestDetails); } - const requestIdPrefix = formatRequestIdMessage(requestId); this.logger.trace( - `${requestIdPrefix} Adding expense of ${cost} to spending plan with ID ${spendingPlan.id}, new spentToday=${ - spendingPlan.spentToday + cost - }`, + `${requestDetails.formattedRequestId} Adding expense of ${cost} to spending plan with ID ${ + spendingPlan.id + }, new spentToday=${spendingPlan.spentToday + cost}`, ); // Check if the spending plan is being used for the first time today @@ -231,15 +229,15 @@ export class HbarLimitService implements IHbarLimitService { this.dailyUniqueSpendingPlansCounter[spendingPlan.subscriptionType].inc(1); } - await this.hbarSpendingPlanRepository.addAmountToSpentToday(spendingPlan.id, cost); + await this.hbarSpendingPlanRepository.addAmountToSpentToday(spendingPlan.id, cost, requestDetails); this.remainingBudget -= cost; this.hbarLimitRemainingGauge.set(this.remainingBudget); // Done asynchronously in the background - this.updateAverageDailyUsagePerSubscriptionType(spendingPlan.subscriptionType).then(); + this.updateAverageDailyUsagePerSubscriptionType(spendingPlan.subscriptionType, requestDetails).then(); this.logger.trace( - `${requestIdPrefix} HBAR rate limit expense update: cost=${cost}, remainingBudget=${this.remainingBudget}`, + `${requestDetails.formattedRequestId} HBAR rate limit expense update: cost=${cost}, remainingBudget=${this.remainingBudget}`, ); } @@ -248,7 +246,7 @@ export class HbarLimitService implements IHbarLimitService { * @param {string} mode - The mode of the transaction or request. * @param {string} methodName - The name of the method being invoked. * @param {number} estimatedTxFee - The total estimated transaction fee, default to 0. - * @param {string} [requestIdPrefix] - An optional prefix to include in log messages. + * @param {RequestDetails} requestDetails The request details for logging and tracking * @returns {Promise} - Resolves `true` if the daily budget has been exceeded, otherwise `false`. * @private */ @@ -256,20 +254,20 @@ export class HbarLimitService implements IHbarLimitService { mode: string, methodName: string, estimatedTxFee: number = 0, - requestIdPrefix?: string, + requestDetails: RequestDetails, ): Promise { if (this.shouldResetLimiter()) { - await this.resetLimiter(); + await this.resetLimiter(requestDetails); } if (this.remainingBudget <= 0 || this.remainingBudget - estimatedTxFee < 0) { this.hbarLimitCounter.labels(mode, methodName).inc(1); this.logger.warn( - `${requestIdPrefix} HBAR rate limit incoming call: remainingBudget=${this.remainingBudget}, totalBudget=${this.totalBudget}, estimatedTxFee=${estimatedTxFee}, resetTimestamp=${this.reset}`, + `${requestDetails.formattedRequestId} HBAR rate limit incoming call: remainingBudget=${this.remainingBudget}, totalBudget=${this.totalBudget}, resetTimestamp=${this.reset}`, ); return true; } else { this.logger.trace( - `${requestIdPrefix} HBAR rate limit not reached: remainingBudget=${this.remainingBudget}, totalBudget=${this.totalBudget}, estimatedTxFee=${estimatedTxFee} resetTimestamp=${this.reset}.`, + `${requestDetails.formattedRequestId} HBAR rate limit not reached. ${this.remainingBudget} out of ${this.totalBudget} tℏ left in relay budget until ${this.reset}.`, ); return false; } @@ -278,10 +276,17 @@ export class HbarLimitService implements IHbarLimitService { /** * Updates the average daily usage per subscription type. * @param {SubscriptionType} subscriptionType - The subscription type to update the average daily usage for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @private {Promise} - A promise that resolves when the average daily usage has been updated. */ - private async updateAverageDailyUsagePerSubscriptionType(subscriptionType: SubscriptionType): Promise { - const plans = await this.hbarSpendingPlanRepository.findAllActiveBySubscriptionType(subscriptionType); + private async updateAverageDailyUsagePerSubscriptionType( + subscriptionType: SubscriptionType, + requestDetails: RequestDetails, + ): Promise { + const plans = await this.hbarSpendingPlanRepository.findAllActiveBySubscriptionType( + subscriptionType, + requestDetails, + ); const totalUsage = plans.reduce((total, plan) => total + plan.spentToday, 0); const averageUsage = Math.round(totalUsage / plans.length); this.averageDailySpendingPlanUsagesGauge[subscriptionType].set(averageUsage); @@ -329,23 +334,31 @@ export class HbarLimitService implements IHbarLimitService { /** * Gets the spending plan for the given eth address or ip address. * @param {string} ethAddress - The eth address to get the spending plan for. - * @param {string} [ipAddress] - The ip address to get the spending plan for. + * @param {string} ipAddress - The ip address to get the spending plan for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the spending plan or null if none exists. * @private */ - private async getSpendingPlan(ethAddress: string, ipAddress?: string): Promise { + private async getSpendingPlan( + ethAddress: string, + requestDetails: RequestDetails, + ): Promise { + const ipAddress = requestDetails.ipAddress; if (ethAddress) { try { - return await this.getSpendingPlanByEthAddress(ethAddress); + return await this.getSpendingPlanByEthAddress(ethAddress, requestDetails); } catch (error) { - this.logger.warn(error, `Failed to get spending plan for eth address '${ethAddress}'`); + this.logger.warn( + error, + `${requestDetails.formattedRequestId} Failed to get spending plan for eth address '${ethAddress}'`, + ); } } if (ipAddress) { try { - return await this.getSpendingPlanByIPAddress(ipAddress); + return await this.getSpendingPlanByIPAddress(requestDetails); } catch (error) { - this.logger.warn(error, `Failed to get spending plan for IP address '${ipAddress}'`); + this.logger.warn(error, `${requestDetails.formattedRequestId} Failed to get spending plan`); } } return null; @@ -354,12 +367,19 @@ export class HbarLimitService implements IHbarLimitService { /** * Gets the spending plan for the given eth address. * @param {string} ethAddress - The eth address to get the spending plan for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the spending plan. * @private */ - private async getSpendingPlanByEthAddress(ethAddress: string): Promise { - const ethAddressHbarSpendingPlan = await this.ethAddressHbarSpendingPlanRepository.findByAddress(ethAddress); - return this.hbarSpendingPlanRepository.findByIdWithDetails(ethAddressHbarSpendingPlan.planId); + private async getSpendingPlanByEthAddress( + ethAddress: string, + requestDetails: RequestDetails, + ): Promise { + const ethAddressHbarSpendingPlan = await this.ethAddressHbarSpendingPlanRepository.findByAddress( + ethAddress, + requestDetails, + ); + return this.hbarSpendingPlanRepository.findByIdWithDetails(ethAddressHbarSpendingPlan.planId, requestDetails); } /** @@ -368,31 +388,44 @@ export class HbarLimitService implements IHbarLimitService { * @returns {Promise} - A promise that resolves with the spending plan. * @private */ - private async getSpendingPlanByIPAddress(ipAddress: string): Promise { - const ipAddressHbarSpendingPlan = await this.ipAddressHbarSpendingPlanRepository.findByAddress(ipAddress); - return this.hbarSpendingPlanRepository.findByIdWithDetails(ipAddressHbarSpendingPlan.planId); + private async getSpendingPlanByIPAddress(requestDetails: RequestDetails): Promise { + const ipAddress = requestDetails.ipAddress; + const ipAddressHbarSpendingPlan = await this.ipAddressHbarSpendingPlanRepository.findByAddress( + ipAddress, + requestDetails, + ); + return this.hbarSpendingPlanRepository.findByIdWithDetails(ipAddressHbarSpendingPlan.planId, requestDetails); } /** * Creates a basic spending plan for the given eth address. * @param {string} ethAddress - The eth address to create the spending plan for. - * @param {string} [ipAddress] - The ip address to create the spending plan for. + * @param {string} ipAddress - The ip address to create the spending plan for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the created spending plan. * @private */ - private async createBasicSpendingPlan(ethAddress: string, ipAddress?: string): Promise { + private async createBasicSpendingPlan( + ethAddress: string, + requestDetails: RequestDetails, + ): Promise { + const ipAddress = requestDetails.ipAddress; if (!ethAddress && !ipAddress) { throw new Error('Cannot create a spending plan without an associated eth address or ip address'); } - const spendingPlan = await this.hbarSpendingPlanRepository.create(SubscriptionType.BASIC); + const spendingPlan = await this.hbarSpendingPlanRepository.create(SubscriptionType.BASIC, requestDetails); if (ethAddress) { - this.logger.trace(`Linking spending plan with ID ${spendingPlan.id} to eth address ${ethAddress}`); - await this.ethAddressHbarSpendingPlanRepository.save({ ethAddress, planId: spendingPlan.id }); + this.logger.trace( + `${requestDetails.formattedRequestId} Linking spending plan with ID ${spendingPlan.id} to eth address ${ethAddress}`, + ); + await this.ethAddressHbarSpendingPlanRepository.save({ ethAddress, planId: spendingPlan.id }, requestDetails); } if (ipAddress) { - this.logger.trace(`Linking spending plan with ID ${spendingPlan.id} to ip address ${ipAddress}`); - await this.ipAddressHbarSpendingPlanRepository.save({ ipAddress, planId: spendingPlan.id }); + this.logger.trace( + `${requestDetails.formattedRequestId} Linking spending plan with ID ${spendingPlan.id} to ip address ${ipAddress}`, + ); + await this.ipAddressHbarSpendingPlanRepository.save({ ipAddress, planId: spendingPlan.id }, requestDetails); } return spendingPlan; } diff --git a/packages/relay/src/lib/services/metricService/metricService.ts b/packages/relay/src/lib/services/metricService/metricService.ts index cdd8764547..ff99a1c09c 100644 --- a/packages/relay/src/lib/services/metricService/metricService.ts +++ b/packages/relay/src/lib/services/metricService/metricService.ts @@ -24,8 +24,8 @@ import constants from '../../constants'; import HbarLimit from '../../hbarlimiter'; import { Histogram, Registry } from 'prom-client'; import { MirrorNodeClient, SDKClient } from '../../clients'; -import { formatRequestIdMessage } from '../../../formatters'; import { ITransactionRecordMetric, IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from '../../types'; +import { RequestDetails } from '../../types'; export default class MetricService { /** @@ -113,7 +113,7 @@ export default class MetricService { this.consensusNodeClientHistogramGasFee = this.initGasMetric(register); this.eventEmitter.on(constants.EVENTS.EXECUTE_TRANSACTION, (args: IExecuteTransactionEventPayload) => { - this.captureTransactionMetrics(args); + this.captureTransactionMetrics(args).then(); }); this.eventEmitter.on(constants.EVENTS.EXECUTE_QUERY, (args: IExecuteQueryEventPayload) => { @@ -126,28 +126,28 @@ export default class MetricService { * and recording the transaction fees, gas usage, and other relevant metrics. * * @param {IExecuteTransactionEventPayload} payload - The payload object containing transaction details. - * @param {string} payload.requestId - The unique identifier for the request. * @param {string} payload.callerName - The name of the entity calling the transaction. * @param {string} payload.transactionId - The unique identifier for the transaction. * @param {string} payload.txConstructorName - The name of the transaction constructor. * @param {string} payload.operatorAccountId - The account ID of the operator managing the transaction. * @param {string} payload.interactingEntity - The entity interacting with the transaction. + * @param {RequestDetails} payload.requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves when the transaction metrics have been captured. */ public async captureTransactionMetrics({ - requestId, callerName, transactionId, txConstructorName, operatorAccountId, interactingEntity, + requestDetails, }: IExecuteTransactionEventPayload): Promise { const transactionRecordMetrics = await this.getTransactionRecordMetrics( transactionId, callerName, - requestId, txConstructorName, operatorAccountId, + requestDetails, ); if (transactionRecordMetrics) { @@ -162,7 +162,7 @@ export default class MetricService { gasUsed, interactingEntity, status, - requestId, + requestDetails, } as IExecuteQueryEventPayload); } @@ -176,7 +176,7 @@ export default class MetricService { gasUsed: 0, interactingEntity, status, - requestId, + requestDetails, } as IExecuteQueryEventPayload); } } @@ -194,7 +194,7 @@ export default class MetricService { * @param {number} payload.gasUsed - The amount of gas used during the transaction. * @param {string} payload.interactingEntity - The entity interacting with the transaction. * @param {string} payload.status - The entity interacting with the transaction. - * @param {string} payload.requestId - The unique identifier for the request. + * @param {string} payload.requestDetails - The request details for logging and tracking. * @returns {void} - This method does not return a value. */ public addExpenseAndCaptureMetrics = ({ @@ -206,35 +206,16 @@ export default class MetricService { gasUsed, interactingEntity, status, - requestId, + requestDetails, }: IExecuteQueryEventPayload): void => { - const formattedRequestId = formatRequestIdMessage(requestId); this.logger.trace( - `${formattedRequestId} Capturing HBAR charged: executionMode=${executionMode} transactionId=${transactionId}, txConstructorName=${txConstructorName}, callerName=${callerName}, cost=${cost} tinybars`, + `${requestDetails.formattedRequestId} Capturing HBAR charged: executionMode=${executionMode} transactionId=${transactionId}, txConstructorName=${txConstructorName}, callerName=${callerName}, cost=${cost} tinybars`, ); - this.hbarLimiter.addExpense(cost, Date.now(), requestId); + this.hbarLimiter.addExpense(cost, Date.now(), requestDetails); this.captureMetrics(executionMode, txConstructorName, status, cost, gasUsed, callerName, interactingEntity); }; - /** - * Retrieves the cost metric for consensus node client operations. - * - * @returns {Histogram} - The histogram metric tracking the cost of consensus node client operations. - */ - private getCostMetric(): Histogram { - return this.consensusNodeClientHistogramCost; - } - - /** - * Retrieves the gas fee metric for consensus node client operations. - * - * @returns {Histogram} - The histogram metric tracking the gas fees of consensus node client operations. - */ - private getGasFeeMetric(): Histogram { - return this.consensusNodeClientHistogramGasFee; - } - /** * Initialize consensus node cost metrics * @param {Registry} register @@ -299,19 +280,18 @@ export default class MetricService { * * @param {string} transactionId - The ID of the transaction for which metrics are being retrieved. * @param {string} callerName - The name of the caller requesting the metrics. - * @param {string} requestId - The request ID for tracing the request flow. * @param {string} txConstructorName - The name of the transaction constructor. * @param {string} operatorAccountId - The account ID of the operator. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - The transaction record metrics or undefined if retrieval fails. */ private async getTransactionRecordMetrics( transactionId: string, callerName: string, - requestId: string, txConstructorName: string, operatorAccountId: string, + requestDetails: RequestDetails, ): Promise { - const formattedRequestId = formatRequestIdMessage(requestId); const defaultToConsensusNode = process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE === 'true'; // retrieve transaction metrics @@ -320,21 +300,24 @@ export default class MetricService { return await this.sdkClient.getTransactionRecordMetrics( transactionId, callerName, - requestId, txConstructorName, operatorAccountId, + requestDetails, ); } else { return await this.mirrorNodeClient.getTransactionRecordMetrics( transactionId, callerName, - requestId, txConstructorName, operatorAccountId, + requestDetails, ); } } catch (error: any) { - this.logger.warn(error, `${formattedRequestId} Could not fetch transaction record: error=${error.message}`); + this.logger.warn( + error, + `${requestDetails.formattedRequestId} Could not fetch transaction record: error=${error.message}`, + ); } } } diff --git a/packages/relay/src/lib/subscriptionController.ts b/packages/relay/src/lib/subscriptionController.ts index 5e082ecc9e..292e077ed4 100644 --- a/packages/relay/src/lib/subscriptionController.ts +++ b/packages/relay/src/lib/subscriptionController.ts @@ -25,6 +25,7 @@ import constants from './constants'; import { Poller } from './poller'; import { generateRandomHex } from '../formatters'; import { Registry, Histogram, Counter } from 'prom-client'; +import { Subs } from '../index'; export interface Subscriber { connection: any; @@ -34,7 +35,7 @@ export interface Subscriber { const CACHE_TTL = Number(process.env.WS_CACHE_TTL) || 20000; -export class SubscriptionController { +export class SubscriptionController implements Subs { private poller: Poller; private logger: Logger; private subscriptions: { [key: string]: Subscriber[] }; diff --git a/packages/relay/src/lib/types/RequestDetails.ts b/packages/relay/src/lib/types/RequestDetails.ts new file mode 100644 index 0000000000..5d557d2151 --- /dev/null +++ b/packages/relay/src/lib/types/RequestDetails.ts @@ -0,0 +1,101 @@ +/* + * + * Hedera JSON RPC Relay + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Interface representing the details of a request. + */ +export interface IRequestDetails { + /** + * The unique identifier for the request. + * @type {string} + */ + requestId: string; + + /** + * The IP address from which the request originated. + * @type {string} + */ + ipAddress: string; + + /** + * The connection ID associated with the request (optional). + * @type {string | undefined} + */ + connectionId?: string; +} + +/** + * Represents the details of a request. + */ +export class RequestDetails { + /** + * The unique identifier for the request. + */ + requestId: string; + + /** + * The IP address from which the request originated. + */ + ipAddress: string; + + /** + * The connection ID associated with the request (optional). + */ + connectionId?: string; + + /** + * Creates an instance of RequestDetails. + * @param {IRequestDetails} details - The details of the request. + */ + constructor(details: IRequestDetails) { + this.requestId = details.requestId; + this.ipAddress = details.ipAddress; + this.connectionId = details.connectionId; + } + + /** + * Gets the formatted request ID. + * @returns {string} The formatted request ID, or an empty string if requestId is not set. + */ + get formattedRequestId(): string { + return this.requestId ? `[Request ID: ${this.requestId}]` : ''; + } + + /** + * Gets the formatted connection ID. + * @returns {string | undefined} The formatted connection ID, or an empty string if connectionId is not set. + */ + get formattedConnectionId(): string | undefined { + return this.connectionId ? `[Connection ID: ${this.connectionId}]` : ''; + } + + /** + * Gets the formatted log prefix. + * @returns {string} The formatted log prefix, combining connection ID and request ID if both are set. + */ + get formattedLogPrefix(): string { + const connectionId = this.formattedConnectionId; + const requestId = this.formattedRequestId; + if (connectionId && requestId) { + return `${connectionId} ${requestId}`; + } + return connectionId || requestId; + } +} diff --git a/packages/relay/src/lib/types/events.ts b/packages/relay/src/lib/types/events.ts index 96987ea912..cdf110552b 100644 --- a/packages/relay/src/lib/types/events.ts +++ b/packages/relay/src/lib/types/events.ts @@ -18,13 +18,15 @@ * */ +import { RequestDetails } from './RequestDetails'; + export interface IExecuteTransactionEventPayload { transactionId: string; callerName: string; - requestId: string; txConstructorName: string; operatorAccountId: string; interactingEntity: string; + requestDetails: RequestDetails; } export interface IExecuteQueryEventPayload { @@ -36,5 +38,5 @@ export interface IExecuteQueryEventPayload { gasUsed: number; interactingEntity: string; status: string; - requestId: string; + requestDetails: RequestDetails; } diff --git a/packages/relay/src/lib/types/index.ts b/packages/relay/src/lib/types/index.ts index 267439a689..c50a13d891 100644 --- a/packages/relay/src/lib/types/index.ts +++ b/packages/relay/src/lib/types/index.ts @@ -38,6 +38,7 @@ import { MirrorNodeTransactionRecord, IMirrorNodeTransactionRecord, } from './mirrorNode'; +import { IRequestDetails, RequestDetails } from './RequestDetails'; export { ITransfer, @@ -61,4 +62,6 @@ export { MirrorNodeTransactionRecord, IMirrorNodeTransactionRecord, IExecuteTransactionEventPayload, + IRequestDetails, + RequestDetails, }; diff --git a/packages/relay/src/utils.ts b/packages/relay/src/utils.ts index 463dd3793a..369b347d70 100644 --- a/packages/relay/src/utils.ts +++ b/packages/relay/src/utils.ts @@ -20,6 +20,7 @@ import { PrivateKey } from '@hashgraph/sdk'; import constants from './lib/constants'; +import crypto from 'crypto'; export class Utils { public static readonly addPercentageBufferToGasPrice = (gasPrice: number): number => { @@ -55,4 +56,13 @@ export class Utils { throw new Error(`Invalid OPERATOR_KEY_FORMAT provided: ${process.env.OPERATOR_KEY_FORMAT}`); } } + + /** + * Generates a random trace ID for requests. + * + * @returns {string} The generated random trace ID. + */ + public static generateRequestId = (): string => { + return crypto.randomUUID(); + }; } diff --git a/packages/relay/tests/assertions.ts b/packages/relay/tests/assertions.ts index 489260d587..01712e8569 100644 --- a/packages/relay/tests/assertions.ts +++ b/packages/relay/tests/assertions.ts @@ -1,4 +1,25 @@ -import { expect } from 'chai'; +/* + * + * Hedera JSON RPC Relay + * + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import { JsonRpcError } from '../src'; import { EthImpl } from '../src/lib/eth'; import { Block, Transaction } from '../src/lib/model'; @@ -6,6 +27,8 @@ import { numberTo0x } from '../src/formatters'; import constants from '../src/lib/constants'; import { BASE_FEE_PER_GAS_DEFAULT } from './lib/eth/eth-config'; +chai.use(chaiAsPromised); + export default class RelayAssertions { static assertRejection = async ( error: JsonRpcError, diff --git a/packages/relay/tests/helpers.ts b/packages/relay/tests/helpers.ts index 7aa3d6ec23..cedce4c6e0 100644 --- a/packages/relay/tests/helpers.ts +++ b/packages/relay/tests/helpers.ts @@ -73,10 +73,10 @@ const getRequestId = () => { return formatRequestIdMessage(uuid()); }; -export const ethCallFailing = async (ethImpl, args, block, assertFunc) => { +export const ethCallFailing = async (ethImpl, args, block, requestDetails, assertFunc) => { let hasError = false; try { - await ethImpl.call(args, block); + await ethImpl.call(args, block, requestDetails); } catch (error: any) { hasError = true; assertFunc(error); diff --git a/packages/relay/tests/lib/clients/localLRUCache.spec.ts b/packages/relay/tests/lib/clients/localLRUCache.spec.ts index 446f9dcafb..f2a27284d0 100644 --- a/packages/relay/tests/lib/clients/localLRUCache.spec.ts +++ b/packages/relay/tests/lib/clients/localLRUCache.spec.ts @@ -24,6 +24,7 @@ import { Registry } from 'prom-client'; import pino from 'pino'; import { LocalLRUCache } from '../../../src/lib/clients'; import constants from '../../../src/lib/constants'; +import { RequestDetails } from '../../../src/lib/types'; const logger = pino(); const registry = new Registry(); @@ -36,6 +37,8 @@ chai.use(chaiAsPromised); describe('LocalLRUCache Test Suite', async function () { this.timeout(10000); + const requestDetails = new RequestDetails({ requestId: 'localLRUCacheTest', ipAddress: '0.0.0.0' }); + this.beforeAll(() => { localLRUCache = new LocalLRUCache(logger.child({ name: `cache` }), registry); }); @@ -46,65 +49,65 @@ describe('LocalLRUCache Test Suite', async function () { describe('verify simple cache', async function () { it('get on empty cache return null', async function () { - const cacheValue = await localLRUCache.get('test', callingMethod); + const cacheValue = await localLRUCache.get('test', callingMethod, requestDetails); expect(cacheValue).to.be.null; }); it('get on valid string cache returns non null', async function () { const key = 'key'; const expectedValue = 'value'; - await localLRUCache.set(key, expectedValue, callingMethod); - const cacheValue = await localLRUCache.get(key, callingMethod); + await localLRUCache.set(key, expectedValue, callingMethod, requestDetails); + const cacheValue = await localLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.equal(expectedValue); }); it('get on valid int cache returns non null', async function () { const key = 'key'; const expectedValue = 1; - await localLRUCache.set(key, expectedValue, callingMethod); - const cacheValue = await localLRUCache.get(key, callingMethod); + await localLRUCache.set(key, expectedValue, callingMethod, requestDetails); + const cacheValue = await localLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.equal(expectedValue); }); it('get on valid false boolean cache returns non null', async function () { const key = 'key'; const expectedValue = false; - await localLRUCache.set(key, expectedValue, callingMethod); - const cacheValue = await localLRUCache.get(key, callingMethod); + await localLRUCache.set(key, expectedValue, callingMethod, requestDetails); + const cacheValue = await localLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.equal(expectedValue); }); it('get on valid true boolean cache returns non null', async function () { const key = 'key'; const expectedValue = true; - await localLRUCache.set(key, expectedValue, callingMethod); - const cacheValue = await localLRUCache.get(key, callingMethod); + await localLRUCache.set(key, expectedValue, callingMethod, requestDetails); + const cacheValue = await localLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.equal(expectedValue); }); it('get on valid object cache returns non null', async function () { const key = 'key'; const expectedValue = { key: 'value' }; - await localLRUCache.set(key, expectedValue, callingMethod); - const cacheValue = await localLRUCache.get(key, callingMethod); + await localLRUCache.set(key, expectedValue, callingMethod, requestDetails); + const cacheValue = await localLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.equal(expectedValue); }); it('delete a valid object', async function () { const key = 'key'; const expectedValue = { key: 'value' }; - await localLRUCache.set(key, expectedValue, callingMethod); - const cacheValueBeforeDelete = await localLRUCache.get(key, callingMethod); - localLRUCache.delete(key, callingMethod); + await localLRUCache.set(key, expectedValue, callingMethod, requestDetails); + const cacheValueBeforeDelete = await localLRUCache.get(key, callingMethod, requestDetails); + await localLRUCache.delete(key, callingMethod, requestDetails); - const cacheValueAfterDelete = await localLRUCache.get(key, callingMethod); + const cacheValueAfterDelete = await localLRUCache.get(key, callingMethod, requestDetails); expect(cacheValueBeforeDelete).to.not.be.null; expect(cacheValueAfterDelete).to.be.null; }); }); describe('verify cache management', async function () { - this.beforeEach(() => { + beforeEach(() => { process.env.CACHE_MAX = constants.CACHE_MAX.toString(); }); @@ -119,12 +122,12 @@ describe('LocalLRUCache Test Suite', async function () { }; Object.entries(keyValuePairs).forEach(([key, value]) => { - customLocalLRUCache.set(key, value, callingMethod); + customLocalLRUCache.set(key, value, callingMethod, requestDetails); }); - const key1 = await customLocalLRUCache.get('key1', callingMethod); - const key2 = await customLocalLRUCache.get('key2', callingMethod); - const key3 = await customLocalLRUCache.get('key3', callingMethod); + const key1 = await customLocalLRUCache.get('key1', callingMethod, requestDetails); + const key2 = await customLocalLRUCache.get('key2', callingMethod, requestDetails); + const key3 = await customLocalLRUCache.get('key3', callingMethod, requestDetails); // expect cache to have capped at max size expect(key1).to.be.null; // key1 should have been evicted expect(key2).to.be.equal(keyValuePairs.key2); @@ -135,20 +138,20 @@ describe('LocalLRUCache Test Suite', async function () { const customLocalLRUCache = new LocalLRUCache(logger.child({ name: `cache` }), registry); const key = 'key'; let valueCount = 0; // keep track of values sets - await customLocalLRUCache.set(key, ++valueCount, callingMethod); - await customLocalLRUCache.set(key, ++valueCount, callingMethod); - await customLocalLRUCache.set(key, ++valueCount, callingMethod); - const cacheValue = await customLocalLRUCache.get(key, callingMethod); - // expect cache to have latest value for key + await customLocalLRUCache.set(key, ++valueCount, callingMethod, requestDetails); + await customLocalLRUCache.set(key, ++valueCount, callingMethod, requestDetails); + await customLocalLRUCache.set(key, ++valueCount, callingMethod, requestDetails); + const cacheValue = await customLocalLRUCache.get(key, callingMethod, requestDetails); + // expect cache to have the latest value for key expect(cacheValue).to.be.equal(valueCount); }); it('verify cache ttl nature', async function () { const customLocalLRUCache = new LocalLRUCache(logger.child({ name: `cache` }), registry); const key = 'key'; - await customLocalLRUCache.set(key, 'value', callingMethod, 100); // set ttl to 1 ms + await customLocalLRUCache.set(key, 'value', callingMethod, requestDetails, 100); // set ttl to 1 ms await new Promise((r) => setTimeout(r, 500)); // wait for ttl to expire - const cacheValue = await customLocalLRUCache.get(key, callingMethod); + const cacheValue = await customLocalLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.null; }); }); @@ -157,17 +160,17 @@ describe('LocalLRUCache Test Suite', async function () { it('should retrieve keys matching a glob-style pattern with *', async function () { const keys = ['hello', 'hallo', 'hxllo']; for (let i = 0; i < keys.length; i++) { - await localLRUCache.set(keys[i], `value${i}`, callingMethod); + await localLRUCache.set(keys[i], `value${i}`, callingMethod, requestDetails); } - await expect(localLRUCache.keys('h*llo', callingMethod)).to.eventually.have.members(keys); + await expect(localLRUCache.keys('h*llo', callingMethod, requestDetails)).to.eventually.have.members(keys); }); it('should retrieve keys matching a glob-style pattern with ?', async function () { const keys = ['hello', 'hallo', 'hxllo']; for (let i = 0; i < keys.length; i++) { - await localLRUCache.set(keys[i], `value${i}`, callingMethod); + await localLRUCache.set(keys[i], `value${i}`, callingMethod, requestDetails); } - await expect(localLRUCache.keys('h?llo', callingMethod)).to.eventually.have.members(keys); + await expect(localLRUCache.keys('h?llo', callingMethod, requestDetails)).to.eventually.have.members(keys); }); it('should retrieve keys matching a glob-style pattern with []', async function () { @@ -175,10 +178,10 @@ describe('LocalLRUCache Test Suite', async function () { const key2 = 'hallo'; const pattern = 'h[ae]llo'; - await localLRUCache.set(key1, 'value1', callingMethod); - await localLRUCache.set(key2, 'value2', callingMethod); + await localLRUCache.set(key1, 'value1', callingMethod, requestDetails); + await localLRUCache.set(key2, 'value2', callingMethod, requestDetails); - const keys = await localLRUCache.keys(pattern, callingMethod); + const keys = await localLRUCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2]); }); @@ -187,10 +190,10 @@ describe('LocalLRUCache Test Suite', async function () { const key2 = 'hbllo'; const pattern = 'h[^e]llo'; - await localLRUCache.set(key1, 'value1', callingMethod); - await localLRUCache.set(key2, 'value2', callingMethod); + await localLRUCache.set(key1, 'value1', callingMethod, requestDetails); + await localLRUCache.set(key2, 'value2', callingMethod, requestDetails); - const keys = await localLRUCache.keys(pattern, callingMethod); + const keys = await localLRUCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2]); }); @@ -199,22 +202,22 @@ describe('LocalLRUCache Test Suite', async function () { const key2 = 'hbllo'; const pattern = 'h[a-b]llo'; - await localLRUCache.set(key1, 'value1', callingMethod); - await localLRUCache.set(key2, 'value2', callingMethod); + await localLRUCache.set(key1, 'value1', callingMethod, requestDetails); + await localLRUCache.set(key2, 'value2', callingMethod, requestDetails); - const keys = await localLRUCache.keys(pattern, callingMethod); + const keys = await localLRUCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2]); }); it('should retrieve keys matching a pattern with escaped special characters', async function () { const keys = ['h*llo', 'h?llo', 'h[llo', 'h]llo']; for (let i = 0; i < keys.length; i++) { - await localLRUCache.set(keys[i], `value${i}`, callingMethod); + await localLRUCache.set(keys[i], `value${i}`, callingMethod, requestDetails); } for (const key of keys) { - await expect(localLRUCache.keys(key.replace(/([*?[\]])/g, '\\$1'), callingMethod)).eventually.has.members([ - key, - ]); + await expect( + localLRUCache.keys(key.replace(/([*?[\]])/g, '\\$1'), callingMethod, requestDetails), + ).eventually.has.members([key]); } }); @@ -224,11 +227,11 @@ describe('LocalLRUCache Test Suite', async function () { const key3 = 'age'; const pattern = '*'; - await localLRUCache.set(key1, 'Jack', callingMethod); - await localLRUCache.set(key2, 'Stuntman', callingMethod); - await localLRUCache.set(key3, '35', callingMethod); + await localLRUCache.set(key1, 'Jack', callingMethod, requestDetails); + await localLRUCache.set(key2, 'Stuntman', callingMethod, requestDetails); + await localLRUCache.set(key3, '35', callingMethod, requestDetails); - const keys = await localLRUCache.keys(pattern, callingMethod); + const keys = await localLRUCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2, key3]); }); }); diff --git a/packages/relay/tests/lib/clients/redisCache.spec.ts b/packages/relay/tests/lib/clients/redisCache.spec.ts index 89d5a6a485..6c6b966a34 100644 --- a/packages/relay/tests/lib/clients/redisCache.spec.ts +++ b/packages/relay/tests/lib/clients/redisCache.spec.ts @@ -24,6 +24,7 @@ import chaiAsPromised from 'chai-as-promised'; import { RedisCache } from '../../../src/lib/clients'; import { Registry } from 'prom-client'; import { useInMemoryRedisServer } from '../../helpers'; +import { RequestDetails } from '../../../dist/lib/types'; chai.use(chaiAsPromised); @@ -33,6 +34,8 @@ describe('RedisCache Test Suite', async function () { const logger = pino(); const registry = new Registry(); const callingMethod = 'RedisCacheTest'; + const requestDetails = new RequestDetails({ requestId: 'localLRUCacheTest', ipAddress: '0.0.0.0' }); + let redisCache: RedisCache; @@ -57,7 +60,7 @@ describe('RedisCache Test Suite', async function () { describe('Get and Set Test Suite', async function () { it('should get null on empty cache', async function () { - const cacheValue = await redisCache.get('test', callingMethod); + const cacheValue = await redisCache.get('test', callingMethod, requestDetails); expect(cacheValue).to.be.null; }); @@ -65,9 +68,9 @@ describe('RedisCache Test Suite', async function () { const key = 'int'; const value = 1; - await redisCache.set(key, value, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).equal(value); }); @@ -75,9 +78,9 @@ describe('RedisCache Test Suite', async function () { const key = 'boolean'; const value = false; - await redisCache.set(key, value, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).equal(value); }); @@ -85,9 +88,9 @@ describe('RedisCache Test Suite', async function () { const key = 'array'; const value = ['false']; - await redisCache.set(key, value, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).deep.equal(value); }); @@ -95,9 +98,9 @@ describe('RedisCache Test Suite', async function () { const key = 'object'; const value = { result: true }; - await redisCache.set(key, value, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).deep.equal(value); }); @@ -106,14 +109,14 @@ describe('RedisCache Test Suite', async function () { const value = 1; const ttl = 500; - await redisCache.set(key, value, callingMethod, ttl); + await redisCache.set(key, value, callingMethod, requestDetails, ttl); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).equal(value); await new Promise((resolve) => setTimeout(resolve, ttl + 100)); - const expiredValue = await redisCache.get(key, callingMethod); + const expiredValue = await redisCache.get(key, callingMethod, requestDetails); expect(expiredValue).to.be.null; }); @@ -122,14 +125,14 @@ describe('RedisCache Test Suite', async function () { const value = 1; const ttl = 1500; - await redisCache.set(key, value, callingMethod, ttl); + await redisCache.set(key, value, callingMethod, requestDetails, ttl); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).equal(value); await new Promise((resolve) => setTimeout(resolve, ttl + 100)); - const expiredValue = await redisCache.get(key, callingMethod); + const expiredValue = await redisCache.get(key, callingMethod, requestDetails); expect(expiredValue).to.be.null; }); }); @@ -144,10 +147,10 @@ describe('RedisCache Test Suite', async function () { object: { result: true }, }; - await redisCache.multiSet(keyValuePairs, callingMethod); + await redisCache.multiSet(keyValuePairs, callingMethod, requestDetails); for (const key in keyValuePairs) { - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).deep.equal(keyValuePairs[key]); } }); @@ -163,10 +166,10 @@ describe('RedisCache Test Suite', async function () { object: { result: true }, }; - await redisCache.pipelineSet(keyValuePairs, callingMethod); + await redisCache.pipelineSet(keyValuePairs, callingMethod, requestDetails); for (const key in keyValuePairs) { - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).deep.equal(keyValuePairs[key]); } }); @@ -180,17 +183,17 @@ describe('RedisCache Test Suite', async function () { object: { result: true }, }; - await redisCache.pipelineSet(keyValuePairs, callingMethod, 500); + await redisCache.pipelineSet(keyValuePairs, callingMethod, requestDetails, 500); for (const key in keyValuePairs) { - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).deep.equal(keyValuePairs[key]); } await new Promise((resolve) => setTimeout(resolve, 500)); for (const key in keyValuePairs) { - const expiredValue = await redisCache.get(key, callingMethod); + const expiredValue = await redisCache.get(key, callingMethod, requestDetails); expect(expiredValue).to.be.null; } }); @@ -201,10 +204,10 @@ describe('RedisCache Test Suite', async function () { const key = 'int'; const value = 1; - await redisCache.set(key, value, callingMethod); - await redisCache.delete(key, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); + await redisCache.delete(key, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).to.be.null; }); @@ -212,10 +215,10 @@ describe('RedisCache Test Suite', async function () { const key = 'boolean'; const value = false; - await redisCache.set(key, value, callingMethod); - await redisCache.delete(key, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); + await redisCache.delete(key, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).to.be.null; }); @@ -223,10 +226,10 @@ describe('RedisCache Test Suite', async function () { const key = 'array'; const value = ['false']; - await redisCache.set(key, value, callingMethod); - await redisCache.delete(key, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); + await redisCache.delete(key, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).to.be.null; }); @@ -234,10 +237,10 @@ describe('RedisCache Test Suite', async function () { const key = 'object'; const value = { result: true }; - await redisCache.set(key, value, callingMethod); - await redisCache.delete(key, callingMethod); + await redisCache.set(key, value, callingMethod, requestDetails); + await redisCache.delete(key, callingMethod, requestDetails); - const cachedValue = await redisCache.get(key, callingMethod); + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).to.be.null; }); }); @@ -247,7 +250,7 @@ describe('RedisCache Test Suite', async function () { const key = 'non-existing'; const amount = 1; - const newValue = await redisCache.incrBy(key, amount, callingMethod); + const newValue = await redisCache.incrBy(key, amount, callingMethod, requestDetails); expect(newValue).equal(amount); }); @@ -256,8 +259,8 @@ describe('RedisCache Test Suite', async function () { const initialValue = 5; const amount = 3; - await redisCache.set(key, initialValue, callingMethod); - const newValue = await redisCache.incrBy(key, amount, callingMethod); + await redisCache.set(key, initialValue, callingMethod, requestDetails); + const newValue = await redisCache.incrBy(key, amount, callingMethod, requestDetails); expect(newValue).equal(initialValue + amount); }); @@ -266,8 +269,8 @@ describe('RedisCache Test Suite', async function () { const initialValue = 5; const amount = -2; - await redisCache.set(key, initialValue, callingMethod); - const newValue = await redisCache.incrBy(key, amount, callingMethod); + await redisCache.set(key, initialValue, callingMethod, requestDetails); + const newValue = await redisCache.incrBy(key, amount, callingMethod, requestDetails); expect(newValue).equal(initialValue + amount); }); }); @@ -277,10 +280,10 @@ describe('RedisCache Test Suite', async function () { const key = 'non-existing-list'; const value = 'item1'; - const length = await redisCache.rPush(key, value, callingMethod); + const length = await redisCache.rPush(key, value, callingMethod, requestDetails); expect(length).equal(1); - const list = await redisCache.lRange(key, 0, -1, callingMethod); + const list = await redisCache.lRange(key, 0, -1, callingMethod, requestDetails); expect(list).deep.equal([value]); }); @@ -289,11 +292,11 @@ describe('RedisCache Test Suite', async function () { const initialList = ['item1']; const newValue = 'item2'; - await redisCache.rPush(key, initialList[0], callingMethod); - const length = await redisCache.rPush(key, newValue, callingMethod); + await redisCache.rPush(key, initialList[0], callingMethod, requestDetails); + const length = await redisCache.rPush(key, newValue, callingMethod, requestDetails); expect(length).equal(2); - const list = await redisCache.lRange(key, 0, -1, callingMethod); + const list = await redisCache.lRange(key, 0, -1, callingMethod, requestDetails); expect(list).deep.equal([...initialList, newValue]); }); }); @@ -304,7 +307,7 @@ describe('RedisCache Test Suite', async function () { const start = 0; const end = 1; - const list = await redisCache.lRange(key, start, end, callingMethod); + const list = await redisCache.lRange(key, start, end, callingMethod, requestDetails); expect(list).deep.equal([]); }); @@ -313,10 +316,10 @@ describe('RedisCache Test Suite', async function () { const list = ['item1', 'item2', 'item3']; for (const item of list) { - await redisCache.rPush(key, item, callingMethod); + await redisCache.rPush(key, item, callingMethod, requestDetails); } - const range = await redisCache.lRange(key, 0, 1, callingMethod); + const range = await redisCache.lRange(key, 0, 1, callingMethod, requestDetails); expect(range).deep.equal(['item1', 'item2']); }); }); @@ -325,17 +328,17 @@ describe('RedisCache Test Suite', async function () { it('should retrieve keys matching a glob-style pattern with *', async function () { const keys = ['hello', 'hallo', 'hxllo']; for (let i = 0; i < keys.length; i++) { - await redisCache.set(keys[i], `value${i}`, callingMethod); + await redisCache.set(keys[i], `value${i}`, callingMethod, requestDetails); } - await expect(redisCache.keys('h*llo', callingMethod)).to.eventually.have.members(keys); + await expect(redisCache.keys('h*llo', callingMethod, requestDetails)).to.eventually.have.members(keys); }); it('should retrieve keys matching a glob-style pattern with ?', async function () { const keys = ['hello', 'hallo', 'hxllo']; for (let i = 0; i < keys.length; i++) { - await redisCache.set(keys[i], `value${i}`, callingMethod); + await redisCache.set(keys[i], `value${i}`, callingMethod, requestDetails); } - await expect(redisCache.keys('h?llo', callingMethod)).to.eventually.have.members(keys); + await expect(redisCache.keys('h?llo', callingMethod, requestDetails)).to.eventually.have.members(keys); }); it('should retrieve keys matching a glob-style pattern with []', async function () { @@ -343,10 +346,10 @@ describe('RedisCache Test Suite', async function () { const key2 = 'hallo'; const pattern = 'h[ae]llo'; - await redisCache.set(key1, 'value1', callingMethod); - await redisCache.set(key2, 'value2', callingMethod); + await redisCache.set(key1, 'value1', callingMethod, requestDetails); + await redisCache.set(key2, 'value2', callingMethod, requestDetails); - const keys = await redisCache.keys(pattern, callingMethod); + const keys = await redisCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2]); }); @@ -355,10 +358,10 @@ describe('RedisCache Test Suite', async function () { const key2 = 'hbllo'; const pattern = 'h[^e]llo'; - await redisCache.set(key1, 'value1', callingMethod); - await redisCache.set(key2, 'value2', callingMethod); + await redisCache.set(key1, 'value1', callingMethod, requestDetails); + await redisCache.set(key2, 'value2', callingMethod, requestDetails); - const keys = await redisCache.keys(pattern, callingMethod); + const keys = await redisCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2]); }); @@ -367,20 +370,22 @@ describe('RedisCache Test Suite', async function () { const key2 = 'hbllo'; const pattern = 'h[a-b]llo'; - await redisCache.set(key1, 'value1', callingMethod); - await redisCache.set(key2, 'value2', callingMethod); + await redisCache.set(key1, 'value1', callingMethod, requestDetails); + await redisCache.set(key2, 'value2', callingMethod, requestDetails); - const keys = await redisCache.keys(pattern, callingMethod); + const keys = await redisCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2]); }); it('should retrieve keys matching a pattern with escaped special characters', async function () { const keys = ['h*llo', 'h?llo', 'h[llo', 'h]llo']; for (let i = 0; i < keys.length; i++) { - await redisCache.set(keys[i], `value${i}`, callingMethod); + await redisCache.set(keys[i], `value${i}`, callingMethod, requestDetails); } for (const key of keys) { - await expect(redisCache.keys(key.replace(/([*?[\]])/g, '\\$1'), callingMethod)).eventually.has.members([key]); + await expect( + redisCache.keys(key.replace(/([*?[\]])/g, '\\$1'), callingMethod, requestDetails), + ).eventually.has.members([key]); } }); @@ -390,11 +395,11 @@ describe('RedisCache Test Suite', async function () { const key3 = 'age'; const pattern = '*'; - await redisCache.set(key1, 'Jack', callingMethod); - await redisCache.set(key2, 'Stuntman', callingMethod); - await redisCache.set(key3, '35', callingMethod); + await redisCache.set(key1, 'Jack', callingMethod, requestDetails); + await redisCache.set(key2, 'Stuntman', callingMethod, requestDetails); + await redisCache.set(key3, '35', callingMethod, requestDetails); - const keys = await redisCache.keys(pattern, callingMethod); + const keys = await redisCache.keys(pattern, callingMethod, requestDetails); expect(keys).to.include.members([key1, key2, key3]); }); }); diff --git a/packages/relay/tests/lib/eth/eth_call.spec.ts b/packages/relay/tests/lib/eth/eth_call.spec.ts index 9381d21c13..8b3dd2860a 100644 --- a/packages/relay/tests/lib/eth/eth_call.spec.ts +++ b/packages/relay/tests/lib/eth/eth_call.spec.ts @@ -43,7 +43,7 @@ import { ONE_TINYBAR_IN_WEI_HEX, EXAMPLE_CONTRACT_BYTECODE, } from './eth-config'; -import { JsonRpcError, predefined } from '../../../src/lib/errors/JsonRpcError'; +import { JsonRpcError, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; import constants from '../../../src/lib/constants'; import { @@ -55,13 +55,14 @@ import { mockData, } from '../../helpers'; import { generateEthTestEnv } from './eth-helpers'; -import { IContractCallRequest, IContractCallResponse } from '../../../src/lib/types/IMirrorNode'; +import { IContractCallRequest, IContractCallResponse, RequestDetails } from '../../../src/lib/types'; +import { ContractFunctionResult } from '@hashgraph/sdk'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethCall Eth Call spec', async function () { @@ -75,11 +76,12 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT_HEX, }; - this.beforeEach(() => { + const requestDetails = new RequestDetails({ requestId: 'eth_callTest', ipAddress: '0.0.0.0' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(); + await cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); @@ -130,7 +132,8 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT_HEX, }, 'latest', - (error) => { + requestDetails, + (error: any) => { expect(error.message).to.equal( `Invalid Contract Address: ${EthImpl.zeroHex}. Expected length of 42 chars but was 3.`, ); @@ -149,7 +152,11 @@ describe('@ethCall Eth Call spec', async function () { evm_address: defaultCallData.from, }); restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - await ethImpl.call({ ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, 'latest'); + await ethImpl.call( + { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, + 'latest', + requestDetails, + ); assert(callMirrorNodeSpy.calledOnce); process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; @@ -166,7 +173,11 @@ describe('@ethCall Eth Call spec', async function () { evm_address: defaultCallData.from, }); restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - await ethImpl.call({ ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, 'latest'); + await ethImpl.call( + { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, + 'latest', + requestDetails, + ); assert(callMirrorNodeSpy.calledOnce); process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; @@ -182,7 +193,11 @@ describe('@ethCall Eth Call spec', async function () { evm_address: defaultCallData.from, }); restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - await ethImpl.call({ ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, 'latest'); + await ethImpl.call( + { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, + 'latest', + requestDetails, + ); assert(callConsensusNodeSpy.calledOnce); process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; @@ -203,6 +218,7 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT_HEX, }, 'latest', + requestDetails, ), ).to.eventually.be.fulfilled.and.equal('0x1'); }); @@ -223,32 +239,33 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT_HEX, }, 'latest', + requestDetails, ), ).to.eventually.be.fulfilled.and.equal('0x1'); }); }); describe('eth_call using consensus node', async function () { - let initialEthCallConesneusFF; + let initialEthCallDefaultsToConsensus: string | undefined; before(() => { - initialEthCallConesneusFF = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; + initialEthCallDefaultsToConsensus = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'true'; }); after(() => { - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; + process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallDefaultsToConsensus; }); it('eth_call with no gas', async function () { restMock.onGet(`contracts/${ACCOUNT_ADDRESS_1}`).reply(404); restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - sdkClientStub.submitContractCallQueryWithRetry.returns({ + sdkClientStub.submitContractCallQueryWithRetry.resolves({ asBytes: function () { return Uint8Array.of(0); }, - }); + } as unknown as ContractFunctionResult); const result = await ethImpl.call( { @@ -257,6 +274,7 @@ describe('@ethCall Eth Call spec', async function () { data: CONTRACT_CALL_DATA, }, 'latest', + requestDetails, ); sinon.assert.calledWith( @@ -272,11 +290,11 @@ describe('@ethCall Eth Call spec', async function () { it('eth_call with no data', async function () { restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - sdkClientStub.submitContractCallQueryWithRetry.returns({ + sdkClientStub.submitContractCallQueryWithRetry.resolves({ asBytes: function () { return Uint8Array.of(0); }, - }); + } as unknown as ContractFunctionResult); const result = await ethImpl.call( { @@ -285,6 +303,7 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT_HEX, }, 'latest', + requestDetails, ); sinon.assert.calledWith( @@ -308,13 +327,13 @@ describe('@ethCall Eth Call spec', async function () { }; restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - sdkClientStub.submitContractCallQueryWithRetry.returns({ + sdkClientStub.submitContractCallQueryWithRetry.resolves({ asBytes: function () { return Uint8Array.of(0); }, - }); + } as unknown as ContractFunctionResult); - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.equal('0x00'); }); @@ -327,7 +346,7 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT, }; - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect((result as JsonRpcError).code).to.equal(-32014); expect((result as JsonRpcError).message).to.equal( @@ -337,13 +356,13 @@ describe('@ethCall Eth Call spec', async function () { it('eth_call with all fields', async function () { restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - sdkClientStub.submitContractCallQueryWithRetry.returns({ + sdkClientStub.submitContractCallQueryWithRetry.resolves({ asBytes: function () { return Uint8Array.of(0); }, - }); + } as unknown as ContractFunctionResult); - const result = await ethImpl.call(ETH_CALL_REQ_ARGS, 'latest'); + const result = await ethImpl.call(ETH_CALL_REQ_ARGS, 'latest', requestDetails); sinon.assert.calledWith( sdkClientStub.submitContractCallQueryWithRetry, @@ -359,11 +378,11 @@ describe('@ethCall Eth Call spec', async function () { //Return once the value, then it's being fetched from cache. After the loop we reset the sdkClientStub, so that it returns nothing, if we get an error in the next request that means that the cache was cleared. it('eth_call should cache the response for 200ms', async function () { restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - sdkClientStub.submitContractCallQueryWithRetry.returns({ + sdkClientStub.submitContractCallQueryWithRetry.resolves({ asBytes: function () { return Uint8Array.of(0); }, - }); + } as unknown as ContractFunctionResult); for (let index = 0; index < 3; index++) { const result = await ethImpl.call( @@ -374,6 +393,7 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT_HEX, }, 'latest', + requestDetails, ); expect(result).to.equal('0x00'); await new Promise((r) => setTimeout(r, 50)); @@ -383,7 +403,7 @@ describe('@ethCall Eth Call spec', async function () { const expectedError = predefined.INVALID_CONTRACT_ADDRESS(CONTRACT_ADDRESS_2); sdkClientStub.submitContractCallQueryWithRetry.throws(expectedError); - const call: string | JsonRpcError = await ethImpl.call(ETH_CALL_REQ_ARGS, 'latest'); + const call: string | JsonRpcError = await ethImpl.call(ETH_CALL_REQ_ARGS, 'latest', requestDetails); expect((call as JsonRpcError).code).to.equal(expectedError.code); expect((call as JsonRpcError).message).to.equal(expectedError.message); @@ -395,7 +415,7 @@ describe('@ethCall Eth Call spec', async function () { predefined.CONTRACT_REVERT(defaultErrorMessageText, defaultErrorMessageHex), ); - const result = await ethImpl.call(ETH_CALL_REQ_ARGS, 'latest'); + const result = await ethImpl.call(ETH_CALL_REQ_ARGS, 'latest', requestDetails); expect(result).to.exist; expect((result as JsonRpcError).code).to.equal(3); @@ -412,6 +432,7 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT_HEX, }, 'latest', + requestDetails, ]; await RelayAssertions.assertRejection( @@ -426,7 +447,7 @@ describe('@ethCall Eth Call spec', async function () { it('eth_call throws internal error when consensus node times out and submitContractCallQueryWithRetry returns undefined', async function () { restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - sdkClientStub.submitContractCallQueryWithRetry.returns(undefined); + sdkClientStub.submitContractCallQueryWithRetry.resolves(undefined); const result = await ethImpl.call( { @@ -435,6 +456,7 @@ describe('@ethCall Eth Call spec', async function () { gas: 5_000_000, }, 'latest', + requestDetails, ); expect(result).to.exist; @@ -450,7 +472,7 @@ describe('@ethCall Eth Call spec', async function () { gas: 400000, value: null, }; - let initialEthCallConesneusFF; + let initialEthCallConesneusFF: string | undefined; before(() => { initialEthCallConesneusFF = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; @@ -478,7 +500,7 @@ describe('@ethCall Eth Call spec', async function () { restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_3_EMPTY_BYTECODE); web3Mock.onPost(`contracts/call`).replyOnce(200, {}); - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.equal('0x'); }); @@ -490,11 +512,11 @@ describe('@ethCall Eth Call spec', async function () { }; restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }); + await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }, requestDetails); web3Mock.history.post = []; - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(web3Mock.history.post.length).to.gte(1); expect(web3Mock.history.post[0].data).to.equal(JSON.stringify({ ...callData, estimate: false, block: 'latest' })); @@ -510,9 +532,9 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT, }; restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }); + await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }, requestDetails); - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.equal('0x00'); }); @@ -523,8 +545,8 @@ describe('@ethCall Eth Call spec', async function () { data: CONTRACT_CALL_DATA, gas: MAX_GAS_LIMIT, }; - await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }, requestDetails); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.equal('0x00'); }); @@ -536,8 +558,8 @@ describe('@ethCall Eth Call spec', async function () { data: CONTRACT_CALL_DATA, gas: MAX_GAS_LIMIT, }; - await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }, requestDetails); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.equal('0x00'); }); @@ -546,10 +568,16 @@ describe('@ethCall Eth Call spec', async function () { ...defaultCallData, gas: 25_000_000, }; - await mockContractCall({ ...callData, gas: constants.MAX_GAS_PER_SEC, block: 'latest' }, false, 200, { - result: '0x00', - }); - const res = await ethImpl.call(callData, 'latest'); + await mockContractCall( + { ...callData, gas: constants.MAX_GAS_PER_SEC, block: 'latest' }, + false, + 200, + { + result: '0x00', + }, + requestDetails, + ); + const res = await ethImpl.call(callData, 'latest', requestDetails); expect(res).to.equal('0x00'); }); @@ -564,11 +592,11 @@ describe('@ethCall Eth Call spec', async function () { block: 'latest', }; - await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }); + await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: '0x00' }, requestDetails); restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); // Relay is called with value in Weibars - const result = await ethImpl.call({ ...callData, value: ONE_TINYBAR_IN_WEI_HEX }, 'latest'); + const result = await ethImpl.call({ ...callData, value: ONE_TINYBAR_IN_WEI_HEX }, 'latest', requestDetails); expect(result).to.equal('0x00'); }); @@ -580,8 +608,8 @@ describe('@ethCall Eth Call spec', async function () { data: CONTRACT_CALL_DATA, gas: MAX_GAS_LIMIT, }; - await mockContractCall({ ...callData, block: 'latest' }, false, 429, mockData.tooManyRequests); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall({ ...callData, block: 'latest' }, false, 429, mockData.tooManyRequests, requestDetails); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.be.not.null; expect((result as JsonRpcError).code).to.eq(-32605); }); @@ -595,8 +623,8 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT, }; restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.contractReverted); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.contractReverted, requestDetails); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.be.not.null; expect((result as JsonRpcError).code).to.eq(3); expect((result as JsonRpcError).message).to.contain(mockData.contractReverted._status.messages[0].message); @@ -612,15 +640,15 @@ describe('@ethCall Eth Call spec', async function () { }; restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - await mockContractCall({ ...callData, block: 'latest' }, false, 501, mockData.notSuported); + await mockContractCall({ ...callData, block: 'latest' }, false, 501, mockData.notSuported, requestDetails); - sdkClientStub.submitContractCallQueryWithRetry.returns({ + sdkClientStub.submitContractCallQueryWithRetry.resolves({ asBytes: function () { return Uint8Array.of(0); }, - }); + } as unknown as ContractFunctionResult); - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); sinon.assert.calledWith( sdkClientStub.submitContractCallQueryWithRetry, @@ -643,9 +671,9 @@ describe('@ethCall Eth Call spec', async function () { }; restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.contractReverted); + await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.contractReverted, requestDetails); sinon.reset(); - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); sinon.assert.notCalled(sdkClientStub.submitContractCallQueryWithRetry); expect(result).to.not.be.null; expect((result as JsonRpcError).code).to.eq(3); @@ -662,19 +690,25 @@ describe('@ethCall Eth Call spec', async function () { }; restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - await mockContractCall({ ...callData, block: 'latest' }, false, 400, { - _status: { - messages: [ - { - message: '', - detail: defaultErrorMessageText, - data: defaultErrorMessageHex, - }, - ], + await mockContractCall( + { ...callData, block: 'latest' }, + false, + 400, + { + _status: { + messages: [ + { + message: '', + detail: defaultErrorMessageText, + data: defaultErrorMessageHex, + }, + ], + }, }, - }); + requestDetails, + ); - const result = await ethImpl.call(callData, 'latest'); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.exist; expect((result as JsonRpcError).code).to.eq(3); @@ -692,6 +726,7 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT, }, 'latest', + requestDetails, ]; await RelayAssertions.assertRejection( @@ -712,8 +747,8 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT, }; - await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.invalidTransaction); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.invalidTransaction, requestDetails); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.be.not.null; expect(result).to.equal('0x'); }); @@ -727,8 +762,8 @@ describe('@ethCall Eth Call spec', async function () { gas: MAX_GAS_LIMIT, }; - await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.failInvalid); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall({ ...callData, block: 'latest' }, false, 400, mockData.failInvalid, requestDetails); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.be.not.null; expect(result).to.equal('0x'); }); @@ -740,8 +775,14 @@ describe('@ethCall Eth Call spec', async function () { from: ACCOUNT_ADDRESS_1, }; - await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: EXAMPLE_CONTRACT_BYTECODE }); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall( + { ...callData, block: 'latest' }, + false, + 200, + { result: EXAMPLE_CONTRACT_BYTECODE }, + requestDetails, + ); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.eq(EXAMPLE_CONTRACT_BYTECODE); }); @@ -751,8 +792,14 @@ describe('@ethCall Eth Call spec', async function () { from: ACCOUNT_ADDRESS_1, }; - await mockContractCall({ ...callData, block: 'latest' }, false, 200, { result: EXAMPLE_CONTRACT_BYTECODE }); - const result = await ethImpl.call(callData, 'latest'); + await mockContractCall( + { ...callData, block: 'latest' }, + false, + 200, + { result: EXAMPLE_CONTRACT_BYTECODE }, + requestDetails, + ); + const result = await ethImpl.call(callData, 'latest', requestDetails); expect(result).to.eq(EXAMPLE_CONTRACT_BYTECODE); }); @@ -761,9 +808,10 @@ describe('@ethCall Eth Call spec', async function () { estimate: boolean, statusCode: number, result: IContractCallResponse, + requestDetails: RequestDetails, ) { const formattedCallData = { ...callData, estimate }; - await ethImpl.contractCallFormat(formattedCallData); + await ethImpl.contractCallFormat(formattedCallData, requestDetails); return web3Mock.onPost('contracts/call', formattedCallData).reply(statusCode, result); } }); @@ -784,7 +832,7 @@ describe('@ethCall Eth Call spec', async function () { value: '0x2540BE400', }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.value).to.equal(1); }); @@ -793,7 +841,7 @@ describe('@ethCall Eth Call spec', async function () { gasPrice: '1000000000', }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.gasPrice).to.equal(1000000000); }); @@ -803,7 +851,7 @@ describe('@ethCall Eth Call spec', async function () { gas: '50000', }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.gas).to.equal(50000); }); @@ -815,7 +863,7 @@ describe('@ethCall Eth Call spec', async function () { input: inputValue, data: dataValue, }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.data).to.eq(inputValue); expect(transaction.data).to.not.eq(dataValue); expect(transaction.input).to.be.undefined; @@ -826,7 +874,7 @@ describe('@ethCall Eth Call spec', async function () { const transaction = { data: dataValue, }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.data).to.eq(dataValue); }); @@ -835,7 +883,7 @@ describe('@ethCall Eth Call spec', async function () { input: 'input data', }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); // @ts-ignore expect(transaction.data).to.equal('input data'); @@ -849,7 +897,7 @@ describe('@ethCall Eth Call spec', async function () { gas: '50000', }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.value).to.equal(1); expect(transaction.gasPrice).to.equal(1000000000); @@ -862,9 +910,9 @@ describe('@ethCall Eth Call spec', async function () { gasPrice: undefined, }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); - const expectedGasPrice = await ethImpl.gasPrice(); + const expectedGasPrice = await ethImpl.gasPrice(requestDetails); expect(transaction.gasPrice).to.equal(parseInt(expectedGasPrice)); }); @@ -875,14 +923,15 @@ describe('@ethCall Eth Call spec', async function () { from: undefined, }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.from).to.equal(operatorEvmAddress); }); }); describe('eth_call using consensus node because of redirect by selector', async function () { - let initialEthCallConesneusFF: any, initialEthCallSelectorsAlwaysToConsensus: any; + let initialForceToConsensusBySelector: string | undefined; + let initialEthCallDefaultsToConsensus: string | undefined; const REDIRECTED_SELECTOR = '0x4d8fdd6d'; const NON_REDIRECTED_SELECTOR = '0xaaaaaaaa'; let callConsensusNodeSpy: sinon.SinonSpy; @@ -890,14 +939,15 @@ describe('@ethCall Eth Call spec', async function () { let sandbox: sinon.SinonSandbox; before(() => { - initialEthCallConesneusFF = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; - initialEthCallSelectorsAlwaysToConsensus = process.env.ETH_CALL_CONSENSUS_SELECTORS; + initialForceToConsensusBySelector = process.env.ETH_CALL_FORCE_TO_CONSENSUS_BY_SELECTOR; + initialEthCallDefaultsToConsensus = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; + process.env.ETH_CALL_FORCE_TO_CONSENSUS_BY_SELECTOR = 'true'; process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'false'; }); after(() => { - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; - process.env.ETH_CALL_CONSENSUS_SELECTORS = initialEthCallSelectorsAlwaysToConsensus; + process.env.ETH_CALL_FORCE_TO_CONSENSUS_BY_SELECTOR = initialForceToConsensusBySelector; + process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallDefaultsToConsensus; }); beforeEach(() => { @@ -919,6 +969,7 @@ describe('@ethCall Eth Call spec', async function () { data: REDIRECTED_SELECTOR, }, 'latest', + requestDetails, ); assert(callConsensusNodeSpy.calledOnce); @@ -932,6 +983,7 @@ describe('@ethCall Eth Call spec', async function () { data: NON_REDIRECTED_SELECTOR, }, 'latest', + requestDetails, ); assert(callConsensusNodeSpy.notCalled); diff --git a/packages/relay/tests/lib/eth/eth_common.spec.ts b/packages/relay/tests/lib/eth/eth_common.spec.ts index ee598c0cf1..69050d3091 100644 --- a/packages/relay/tests/lib/eth/eth_common.spec.ts +++ b/packages/relay/tests/lib/eth/eth_common.spec.ts @@ -23,78 +23,78 @@ import { expect, use } from 'chai'; import { Registry } from 'prom-client'; import pino from 'pino'; import chaiAsPromised from 'chai-as-promised'; -import { RelayImpl } from '../../../src/lib/relay'; +import { RelayImpl } from '../../../src'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); describe('@ethCommon', async function () { - let Relay: any; - + let Relay: RelayImpl; this.timeout(10000); + const requestDetails = new RequestDetails({ requestId: 'eth_commonTest', ipAddress: '0.0.0.0' }); + this.beforeAll(() => { - const logger = pino(); - const registry = new Registry(); - Relay = new RelayImpl(logger, registry); + Relay = new RelayImpl(pino(), new Registry()); }); describe('@ethCommon', async function () { it('should execute "eth_chainId"', async function () { - const chainId = Relay.eth().chainId(); + const chainId = Relay.eth().chainId(requestDetails); expect(chainId).to.be.equal('0x' + Number(process.env.CHAIN_ID).toString(16)); }); it('should execute "eth_accounts"', async function () { - const accounts = Relay.eth().accounts(); + const accounts = Relay.eth().accounts(requestDetails); expect(accounts).to.be.an('Array'); expect(accounts.length).to.be.equal(0); }); it('should execute "eth_getUncleByBlockHashAndIndex"', async function () { - const result = await Relay.eth().getUncleByBlockHashAndIndex(); + const result = await Relay.eth().getUncleByBlockHashAndIndex(requestDetails); expect(result).to.be.null; }); it('should execute "eth_getUncleByBlockNumberAndIndex"', async function () { - const result = await Relay.eth().getUncleByBlockNumberAndIndex(); + const result = await Relay.eth().getUncleByBlockNumberAndIndex(requestDetails); expect(result).to.be.null; }); it('should execute "eth_getUncleCountByBlockHash"', async function () { - const result = await Relay.eth().getUncleCountByBlockHash(); + const result = await Relay.eth().getUncleCountByBlockHash(requestDetails); expect(result).to.eq('0x0'); }); it('should execute "eth_getUncleCountByBlockNumber"', async function () { - const result = await Relay.eth().getUncleCountByBlockNumber(); + const result = await Relay.eth().getUncleCountByBlockNumber(requestDetails); expect(result).to.eq('0x0'); }); it('should execute "eth_hashrate"', async function () { - const result = await Relay.eth().hashrate(); + const result = await Relay.eth().hashrate(requestDetails); expect(result).to.eq('0x0'); }); it('should execute "eth_mining"', async function () { - const result = await Relay.eth().mining(); + const result = await Relay.eth().mining(requestDetails); expect(result).to.eq(false); }); it('should execute "eth_submitWork"', async function () { - const result = await Relay.eth().submitWork(); + const result = await Relay.eth().submitWork(requestDetails); expect(result).to.eq(false); }); it('should execute "eth_syncing"', async function () { - const result = await Relay.eth().syncing(); + const result = await Relay.eth().syncing(requestDetails); expect(result).to.eq(false); }); it('should execute "eth_getWork"', async function () { - const result = Relay.eth().getWork(); + const result = Relay.eth().getWork(requestDetails); expect(result).to.have.property('code'); expect(result.code).to.be.equal(-32601); expect(result).to.have.property('message'); @@ -102,7 +102,7 @@ describe('@ethCommon', async function () { }); it('should execute "eth_maxPriorityFeePerGas"', async function () { - const result = await Relay.eth().maxPriorityFeePerGas(); + const result = await Relay.eth().maxPriorityFeePerGas(requestDetails); expect(result).to.eq('0x0'); }); }); diff --git a/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts b/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts index f26d878636..dd7f9ad23f 100644 --- a/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts +++ b/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts @@ -31,7 +31,7 @@ import { Precheck } from '../../../src/lib/precheck'; import { SDKClient } from '../../../src/lib/clients'; import { numberTo0x } from '../../../src/formatters'; import { createStubInstance, SinonStub, SinonStubbedInstance, stub } from 'sinon'; -import { IContractCallRequest, IContractCallResponse } from '../../../src/lib/types'; +import { IContractCallRequest, IContractCallResponse, RequestDetails } from '../../../src/lib/types'; import { ACCOUNT_ADDRESS_1, DEFAULT_NETWORK_FEES, @@ -50,17 +50,20 @@ let currentMaxBlockRange: number; describe('@ethEstimateGas Estimate Gas spec', async function () { this.timeout(10000); - let { restMock, web3Mock, hapiServiceInstance, ethImpl, cacheService, mirrorNodeInstance, logger, registry } = + const { restMock, web3Mock, hapiServiceInstance, ethImpl, cacheService, mirrorNodeInstance, logger, registry } = generateEthTestEnv(); + const requestDetails = new RequestDetails({ requestId: 'eth_estimateGasTest', ipAddress: '0.0.0.0' }); + async function mockContractCall( callData: IContractCallRequest, estimate: boolean, statusCode: number, result: IContractCallResponse, + requestDetails: RequestDetails, ) { const formattedData = { ...callData, estimate }; - await ethImpl.contractCallFormat(formattedData); + await ethImpl.contractCallFormat(formattedData, requestDetails); return web3Mock.onPost('contracts/call', formattedData).reply(statusCode, result); } @@ -78,9 +81,8 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = createStubInstance(SDKClient); getSdkClientStub = stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); ethImplOverridden = new EthImpl(hapiServiceInstance, mirrorNodeInstance, logger, '0x12a', registry, cacheService); @@ -108,9 +110,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { gasPrice: '0x0', data: null, }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); - const gas = await ethImpl.estimateGas(callData, null); + const gas = await ethImpl.estimateGas(callData, null, requestDetails); expect(gas).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); }); @@ -120,9 +122,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { from: '0x81cb089c285e5ee3a7353704fb114955037443af', to: RECEIVER_ADDRESS, }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); - const gas = await ethImpl.estimateGas(callData, null); + const gas = await ethImpl.estimateGas(callData, null, requestDetails); expect(gas).to.equal(numberTo0x(constants.TX_CONTRACT_CALL_AVERAGE_GAS)); }); @@ -131,9 +133,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { data: '0x608060405234801561001057600080fd5b506040516107893803806107898339818101604052810190610032919061015a565b806000908051906020019061004892919061004f565b50506102f6565b82805461005b90610224565b90600052602060002090601f01602090048101928261007d57600085556100c4565b82601f1061009657805160ff19168380011785556100c4565b828001600101855582156100c4579182015b828111156100c35782518255916020019190600101906100a8565b5b5090506100d191906100d5565b5090565b5b808211156100ee5760008160009055506001016100d6565b5090565b6000610105610100846101c0565b61019b565b90508281526020810184848401111561011d57600080fd5b6101288482856101f1565b509392505050565b600082601f83011261014157600080fd5b81516101518482602086016100f2565b91505092915050565b60006020828403121561016c57600080fd5b600082015167ffffffffffffffff81111561018657600080fd5b61019284828501610130565b91505092915050565b60006101a56101b6565b90506101b18282610256565b919050565b6000604051905090565b600067ffffffffffffffff8211156101db576101da6102b6565b5b6101e4826102e5565b9050602081019050919050565b60005b8381101561020f5780820151818401526020810190506101f4565b8381111561021e576000848401525b50505050565b6000600282049050600182168061023c57607f821691505b602082108114156102505761024f610287565b5b50919050565b61025f826102e5565b810181811067ffffffffffffffff8211171561027e5761027d6102b6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b610484806103056000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610057575b600080fd5b6100556004803603810190610050919061022c565b610075565b005b61005f61008f565b60405161006c91906102a6565b60405180910390f35b806000908051906020019061008b929190610121565b5050565b60606000805461009e9061037c565b80601f01602080910402602001604051908101604052809291908181526020018280546100ca9061037c565b80156101175780601f106100ec57610100808354040283529160200191610117565b820191906000526020600020905b8154815290600101906020018083116100fa57829003601f168201915b5050505050905090565b82805461012d9061037c565b90600052602060002090601f01602090048101928261014f5760008555610196565b82601f1061016857805160ff1916838001178555610196565b82800160010185558215610196579182015b8281111561019557825182559160200191906001019061017a565b5b5090506101a391906101a7565b5090565b5b808211156101c05760008160009055506001016101a8565b5090565b60006101d76101d2846102ed565b6102c8565b9050828152602081018484840111156101ef57600080fd5b6101fa84828561033a565b509392505050565b600082601f83011261021357600080fd5b81356102238482602086016101c4565b91505092915050565b60006020828403121561023e57600080fd5b600082013567ffffffffffffffff81111561025857600080fd5b61026484828501610202565b91505092915050565b60006102788261031e565b6102828185610329565b9350610292818560208601610349565b61029b8161043d565b840191505092915050565b600060208201905081810360008301526102c0818461026d565b905092915050565b60006102d26102e3565b90506102de82826103ae565b919050565b6000604051905090565b600067ffffffffffffffff8211156103085761030761040e565b5b6103118261043d565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b8381101561036757808201518184015260208101905061034c565b83811115610376576000848401525b50505050565b6000600282049050600182168061039457607f821691505b602082108114156103a8576103a76103df565b5b50919050565b6103b78261043d565b810181811067ffffffffffffffff821117156103d6576103d561040e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fea264697066735822122070d157c4efbb3fba4a1bde43cbba5b92b69f2fc455a650c0dfb61e9ed3d4bd6364736f6c634300080400330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b696e697469616c5f6d7367000000000000000000000000000000000000000000', from: '0x81cb089c285e5ee3a7353704fb114955037443af', }; - await mockContractCall(callData, true, 200, { result: `0x61A80` }); + await mockContractCall(callData, true, 200, { result: `0x61A80` }, requestDetails); - const gas = await ethImpl.estimateGas(callData, null); + const gas = await ethImpl.estimateGas(callData, null, requestDetails); expect((gas as string).toLowerCase()).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT).toLowerCase()); }); @@ -143,9 +145,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { from: '0x81cb089c285e5ee3a7353704fb114955037443af', value: 1, }; - await mockContractCall(callData, true, 200, { result: `0x61A80` }); + await mockContractCall(callData, true, 200, { result: `0x61A80` }, requestDetails); - const gas = await ethImpl.estimateGas({ ...callData, value: ONE_TINYBAR_IN_WEI_HEX }, null); + const gas = await ethImpl.estimateGas({ ...callData, value: ONE_TINYBAR_IN_WEI_HEX }, null, requestDetails); expect((gas as string).toLowerCase()).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT).toLowerCase()); }); @@ -153,9 +155,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { const callData: IContractCallRequest = { data: '0x01', }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); - const gas = await ethImpl.estimateGas({ data: '0x01' }, null); + const gas = await ethImpl.estimateGas({ data: '0x01' }, null, requestDetails); expect(gas).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(callData.data!))); }); @@ -166,10 +168,12 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { to: RECEIVER_ADDRESS, value: '0x2540BE400', }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); - restMock.onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`).reply(200, { address: RECEIVER_ADDRESS }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); + restMock + .onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`) + .reply(200, { address: RECEIVER_ADDRESS }, requestDetails); - const gas = await ethImpl.estimateGas(callData, null); + const gas = await ethImpl.estimateGas(callData, null, requestDetails); expect(gas).to.equal(numberTo0x(constants.TX_BASE_COST)); }); @@ -179,10 +183,12 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { from: '0x81cb089c285e5ee3a7353704fb114955037443af', to: RECEIVER_ADDRESS, }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); - restMock.onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`).reply(200, { address: RECEIVER_ADDRESS }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); + restMock + .onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`) + .reply(200, { address: RECEIVER_ADDRESS }, requestDetails); - const result = await ethImpl.estimateGas(callData, null); + const result = await ethImpl.estimateGas(callData, null, requestDetails); expect(result).to.not.be.null; expect((result as JsonRpcError).code).to.eq(-32602); }); @@ -192,7 +198,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { to: RECEIVER_ADDRESS, value: 10, //in tinybars }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); restMock.onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`).reply(200, { address: RECEIVER_ADDRESS }); const gas = await ethImpl.estimateGas( @@ -201,6 +207,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { value: 100_000_000_000, }, null, + requestDetails, ); expect(gas).to.equal(EthImpl.gasTxBaseCost); }); @@ -210,7 +217,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { to: RECEIVER_ADDRESS, value: 10, //in tinybars }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); restMock.onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`).reply(200, { address: RECEIVER_ADDRESS }); const gasBeforeCache = await ethImpl.estimateGas( @@ -219,6 +226,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { value: 100_000_000_000, }, null, + requestDetails, ); restMock.onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`).reply(404); @@ -228,6 +236,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { value: 100_000_000_000, }, null, + requestDetails, ); expect(gasBeforeCache).to.equal(EthImpl.gasTxBaseCost); @@ -239,7 +248,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { to: RECEIVER_ADDRESS, value: 10, //in tinybars }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); restMock.onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`).reply(404); const hollowAccountGasCreation = await ethImpl.estimateGas( @@ -248,6 +257,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { value: 100_000_000_000, }, null, + requestDetails, ); expect(Number(hollowAccountGasCreation)).to.be.greaterThanOrEqual(Number(EthImpl.minGasTxHollowAccountCreation)); @@ -258,13 +268,14 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { to: RECEIVER_ADDRESS, value: 0, //in tinybars }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); const result = await ethImpl.estimateGas( { to: RECEIVER_ADDRESS, value: 0, }, null, + requestDetails, ); expect(result).to.exist; @@ -284,9 +295,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { input: '0x81cb089c285e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af', }; - await mockContractCall(callData, true, 200, { result: `0x14b662` }); + await mockContractCall(callData, true, 200, { result: `0x14b662` }, requestDetails); - const gas = await ethImpl.estimateGas(callData, null); + const gas = await ethImpl.estimateGas(callData, null, requestDetails); expect((gas as string).toLowerCase()).to.equal(numberTo0x(gasEstimation).toLowerCase()); }); @@ -296,13 +307,14 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { to: RECEIVER_ADDRESS, value: null, //in tinybars }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); const result = await ethImpl.estimateGas( { to: RECEIVER_ADDRESS, value: null, }, null, + requestDetails, ); expect(result).to.exist; @@ -314,17 +326,17 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { it('should eth_estimateGas empty call returns transfer cost', async function () { const callData: IContractCallRequest = {}; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); - const gas = await ethImpl.estimateGas({}, null); + const gas = await ethImpl.estimateGas({}, null, requestDetails); expect(gas).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); }); it('should eth_estimateGas empty call returns transfer cost with overridden default gas', async function () { const callData: IContractCallRequest = {}; - await mockContractCall(callData, true, 200, { result: numberTo0x(defaultGasOverride) }); + await mockContractCall(callData, true, 200, { result: numberTo0x(defaultGasOverride) }, requestDetails); - const gas = await ethImplOverridden.estimateGas({}, null); + const gas = await ethImplOverridden.estimateGas({}, null, requestDetails); expect(gas).to.equal(numberTo0x(defaultGasOverride)); }); @@ -335,9 +347,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }; const contractsCallResponse: IContractCallResponse = { errorMessage: '', statusCode: 501 }; - await mockContractCall(callData, true, 501, contractsCallResponse); + await mockContractCall(callData, true, 501, contractsCallResponse, requestDetails); - const gas = await ethImpl.estimateGas({ data: '' }, null); + const gas = await ethImpl.estimateGas({ data: '' }, null, requestDetails); expect(gas).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); }); @@ -345,9 +357,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { const callData: IContractCallRequest = { data: '', }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); - const gas = await ethImplOverridden.estimateGas({ data: '' }, null); + const gas = await ethImplOverridden.estimateGas({ data: '' }, null, requestDetails); expect(gas).to.equal(numberTo0x(defaultGasOverride)); }); @@ -357,10 +369,10 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { to: RECEIVER_ADDRESS, value: '0x1', }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); mockGetAccount(RECEIVER_ADDRESS, 200, { account: '0.0.1234', evm_address: RECEIVER_ADDRESS }); - const gas = await ethImpl.estimateGas(callData, null); + const gas = await ethImpl.estimateGas(callData, null, requestDetails); expect(gas).to.equal(numberTo0x(constants.TX_BASE_COST)); }); @@ -368,9 +380,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { const callData: IContractCallRequest = { data: '0x0', }; - await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + await mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }, requestDetails); - const gas = await ethImplOverridden.estimateGas({ data: '0x' }, null); + const gas = await ethImplOverridden.estimateGas({ data: '0x' }, null, requestDetails); expect(gas).to.equal(numberTo0x(defaultGasOverride)); }); @@ -386,9 +398,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { ], }, }; - await mockContractCall(transaction, true, 400, contractCallResult); + await mockContractCall(transaction, true, 400, contractCallResult, requestDetails); - const estimatedGas = await ethImpl.estimateGas(transaction, id); + const estimatedGas = await ethImpl.estimateGas(transaction, id, requestDetails); expect(estimatedGas).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!))); }); @@ -396,38 +408,50 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { it('should eth_estimateGas with contract revert and message does not equal executionReverted and ESTIMATE_GAS_THROWS is set to false', async function () { const estimateGasThrows = process.env.ESTIMATE_GAS_THROWS; process.env.ESTIMATE_GAS_THROWS = 'false'; - await mockContractCall(transaction, true, 400, { - _status: { - messages: [ - { - message: 'data field invalid hexadecimal string', - detail: '', - data: '', - }, - ], + await mockContractCall( + transaction, + true, + 400, + { + _status: { + messages: [ + { + message: 'data field invalid hexadecimal string', + detail: '', + data: '', + }, + ], + }, }, - }); + requestDetails, + ); - const result: any = await ethImpl.estimateGas(transaction, id); + const result: any = await ethImpl.estimateGas(transaction, id, requestDetails); expect(result).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!))); process.env.ESTIMATE_GAS_THROWS = estimateGasThrows; }); it('should eth_estimateGas with contract revert and message equals "execution reverted: Invalid number of recipients"', async function () { - await mockContractCall(transaction, true, 400, { - _status: { - messages: [ - { - data: '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c496e76616c6964206e756d626572206f6620726563697069656e747300000000', - detail: 'Invalid number of recipients', - message: 'CONTRACT_REVERT_EXECUTED', - }, - ], + await mockContractCall( + transaction, + true, + 400, + { + _status: { + messages: [ + { + data: '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c496e76616c6964206e756d626572206f6620726563697069656e747300000000', + detail: 'Invalid number of recipients', + message: 'CONTRACT_REVERT_EXECUTED', + }, + ], + }, }, - }); + requestDetails, + ); - const result: any = await ethImpl.estimateGas(transaction, id); + const result: any = await ethImpl.estimateGas(transaction, id, requestDetails); expect(result.data).to.equal( '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c496e76616c6964206e756d626572206f6620726563697069656e747300000000', @@ -441,19 +465,25 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { const encodedMessage = new AbiCoder().encode(['string'], [decodedMessage]).replace('0x', ''); const encodedCustomError = customErrorSignature + encodedMessage; - await mockContractCall(transaction, true, 400, { - _status: { - messages: [ - { - message: 'CONTRACT_REVERT_EXECUTED', - detail: decodedMessage, - data: encodedCustomError, - }, - ], + await mockContractCall( + transaction, + true, + 400, + { + _status: { + messages: [ + { + message: 'CONTRACT_REVERT_EXECUTED', + detail: decodedMessage, + data: encodedCustomError, + }, + ], + }, }, - }); + requestDetails, + ); - const result: any = await ethImpl.estimateGas(transaction, id); + const result: any = await ethImpl.estimateGas(transaction, id, requestDetails); expect(result.data).to.equal(encodedCustomError); expect(result.message).to.equal(`execution reverted: ${decodedMessage}`); @@ -465,38 +495,50 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { const encodedMessage = new AbiCoder().encode(['string'], [decodedMessage]).replace('0x', ''); const encodedGenericError = defaultErrorSignature + encodedMessage; - await mockContractCall(transaction, true, 400, { - _status: { - messages: [ - { - message: 'CONTRACT_REVERT_EXECUTED', - detail: decodedMessage, - data: encodedGenericError, - }, - ], + await mockContractCall( + transaction, + true, + 400, + { + _status: { + messages: [ + { + message: 'CONTRACT_REVERT_EXECUTED', + detail: decodedMessage, + data: encodedGenericError, + }, + ], + }, }, - }); + requestDetails, + ); - const result: any = await ethImpl.estimateGas(transaction, id); + const result: any = await ethImpl.estimateGas(transaction, id, requestDetails); expect(result.data).to.equal(encodedGenericError); expect(result.message).to.equal(`execution reverted: ${decodedMessage}`); }); it('should eth_estimateGas handles a 501 unimplemented response from the mirror node correctly by returning default gas', async function () { - await mockContractCall(transaction, true, 501, { - _status: { - messages: [ - { - message: 'Auto account creation is not supported.', - detail: '', - data: '', - }, - ], + await mockContractCall( + transaction, + true, + 501, + { + _status: { + messages: [ + { + message: 'Auto account creation is not supported.', + detail: '', + data: '', + }, + ], + }, }, - }); + requestDetails, + ); - const result: any = await ethImpl.estimateGas({ ...transaction, data: '0x', value: '0x1' }, id); + const result: any = await ethImpl.estimateGas({ ...transaction, data: '0x', value: '0x1' }, id, requestDetails); expect(result).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); }); @@ -509,7 +551,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { gas: '0xd97010', }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.value).to.eq(1110); expect(transaction.gasPrice).to.eq(1000000); expect(transaction.gas).to.eq(14250000); @@ -527,7 +569,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { gas: '0xd97010', }; - await ethImpl.contractCallFormat(transaction); + await ethImpl.contractCallFormat(transaction, requestDetails); expect(transaction.data).to.eq(inputValue); expect(transaction.data).to.not.eq(dataValue); expect(transaction.input).to.be.undefined; diff --git a/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts b/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts index dca3b5f67e..be4b57b667 100644 --- a/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts +++ b/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts @@ -38,23 +38,25 @@ import { } from './eth-config'; import { numberTo0x } from '../../../src/formatters'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethFeeHistory using MirrorNode', async function () { this.timeout(10000); let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const requestDetails = new RequestDetails({ requestId: 'eth_feeHistoryTest', ipAddress: '0.0.0.0' }); + this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); @@ -95,7 +97,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { it('eth_feeHistory', async function () { previousFees.fees[2].gas += 1; - const feeHistory = await ethImpl.feeHistory(2, 'latest', [25, 75]); + const feeHistory = await ethImpl.feeHistory(2, 'latest', [25, 75], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['baseFeePerGas'].length).to.equal(3); @@ -111,38 +113,38 @@ describe('@ethFeeHistory using MirrorNode', async function () { }); it('eth_feeHistory with latest param', async function () { - const feeHistory = await ethImpl.feeHistory(1, 'latest', [25, 75]); + const feeHistory = await ethImpl.feeHistory(1, 'latest', [25, 75], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq('0x' + BLOCK_NUMBER_3); }); it('eth_feeHistory with pending param', async function () { - const feeHistory = await ethImpl.feeHistory(1, 'pending', [25, 75]); + const feeHistory = await ethImpl.feeHistory(1, 'pending', [25, 75], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq('0x' + BLOCK_NUMBER_3); }); it('eth_feeHistory with finalized param', async function () { - const feeHistory = await ethImpl.feeHistory(1, 'finalized', [25, 75]); + const feeHistory = await ethImpl.feeHistory(1, 'finalized', [25, 75], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq('0x' + BLOCK_NUMBER_3); }); it('eth_feeHistory with safe param', async function () { - const feeHistory = await ethImpl.feeHistory(1, 'safe', [25, 75]); + const feeHistory = await ethImpl.feeHistory(1, 'safe', [25, 75], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq('0x' + BLOCK_NUMBER_3); }); it('eth_feeHistory with earliest param', async function () { const firstBlockIndex = 0; - const feeHistory = await ethImpl.feeHistory(1, 'earliest', [25, 75]); + const feeHistory = await ethImpl.feeHistory(1, 'earliest', [25, 75], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq('0x' + firstBlockIndex); }); it('eth_feeHistory with number param', async function () { - const feeHistory = await ethImpl.feeHistory(1, '0x' + BLOCK_NUMBER_3, [25, 75]); + const feeHistory = await ethImpl.feeHistory(1, '0x' + BLOCK_NUMBER_3, [25, 75], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq('0x' + BLOCK_NUMBER_3); }); @@ -157,7 +159,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { restMock.onGet(`blocks/${blockNumber}`).reply(200, { ...DEFAULT_BLOCK, number: blockNumber }), ); - const feeHistory = await ethImpl.feeHistory(200, '0x9', [0]); + const feeHistory = await ethImpl.feeHistory(200, '0x9', [0], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.equal(`0x0`); @@ -175,8 +177,8 @@ describe('@ethFeeHistory using MirrorNode', async function () { restMock.onGet(`blocks/${latestBlock.number}`).reply(200, latestBlock); restMock.onGet(`network/fees?timestamp=lte:${latestBlock.timestamp.to}`).reply(200, latestFees); - const firstFeeHistory = await ethImpl.feeHistory(1, hexBlockNumber, null); - const secondFeeHistory = await ethImpl.feeHistory(1, hexBlockNumber, null); + const firstFeeHistory = await ethImpl.feeHistory(1, hexBlockNumber, null, requestDetails); + const secondFeeHistory = await ethImpl.feeHistory(1, hexBlockNumber, null, requestDetails); expect(firstFeeHistory).to.exist; expect(firstFeeHistory['baseFeePerGas'][0]).to.equal(BASE_FEE_PER_GAS_HEX); @@ -199,7 +201,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { } this.beforeEach(() => { - sdkClientStub.getTinyBarGasFee.returns(fauxGasTinyBars); + sdkClientStub.getTinyBarGasFee.resolves(fauxGasTinyBars); restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [latestBlock] }); restMock.onGet(`blocks/${latestBlock.number}`).reply(200, latestBlock); restMock.onGet(`network/fees?timestamp=lte:${latestBlock.timestamp.to}`).reply(404, NOT_FOUND_RES); @@ -207,7 +209,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { }); it('eth_feeHistory on mirror 404', async function () { - const feeHistory = await ethImpl.feeHistory(1, 'latest', [25, 75]); + const feeHistory = await ethImpl.feeHistory(1, 'latest', [25, 75], requestDetails); feeHistoryOnErrorExpect(feeHistory); const rewards = feeHistory['reward'][0]; expect(rewards[0]).to.equal('0x0'); @@ -215,7 +217,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { }); it('eth_feeHistory on mirror 500', async function () { - const feeHistory = await ethImpl.feeHistory(1, 'latest', null); + const feeHistory = await ethImpl.feeHistory(1, 'latest', null, requestDetails); feeHistoryOnErrorExpect(feeHistory); }); }); @@ -240,7 +242,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { }); this.beforeEach(function () { - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); restMock.onGet(`network/fees`).reply(200, DEFAULT_NETWORK_FEES); }); @@ -256,7 +258,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { const countBlocks = 2; - const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', [25, 75]); + const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', [25, 75], requestDetails); checkCommonFeeHistoryFields(feeHistory); expect(feeHistory['oldestBlock']).to.eq(numberTo0x(latestBlockNumber - countBlocks + 1)); @@ -270,7 +272,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { const countBlocks = 5; - const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', []); + const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', [], requestDetails); checkCommonFeeHistoryFields(feeHistory); expect(feeHistory['oldestBlock']).to.eq(numberTo0x(latestBlockNumber - countBlocks + 1)); @@ -284,7 +286,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { const countBlocks = 5; - const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', []); + const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', [], requestDetails); checkCommonFeeHistoryFields(feeHistory); expect(feeHistory['oldestBlock']).to.eq(numberTo0x(latestBlockNumber - countBlocks + 1)); @@ -298,7 +300,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { const countBlocks = 5; - const feeHistory = await ethImpl.feeHistory(countBlocks, 'pending', []); + const feeHistory = await ethImpl.feeHistory(countBlocks, 'pending', [], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq(numberTo0x(latestBlockNumber - countBlocks + 1)); @@ -312,7 +314,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { restMock.onGet(`blocks/1`).reply(200, latestBlock); const countBlocks = 1; - const feeHistory = await ethImpl.feeHistory(countBlocks, 'earliest', []); + const feeHistory = await ethImpl.feeHistory(countBlocks, 'earliest', [], requestDetails); expect(feeHistory).to.exist; expect(feeHistory['oldestBlock']).to.eq(numberTo0x(1)); @@ -328,7 +330,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { const countBlocks = 2; - const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', []); + const feeHistory = await ethImpl.feeHistory(countBlocks, 'latest', [], requestDetails); checkCommonFeeHistoryFields(feeHistory); expect(feeHistory['oldestBlock']).to.eq(numberTo0x(latestBlockNumber - countBlocks + 1)); @@ -337,7 +339,7 @@ describe('@ethFeeHistory using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(404, {}); restMock.onGet(`blocks/${latestBlock.number}`).reply(404, {}); - const feeHistoryUsingCache = await ethImpl.feeHistory(countBlocks, 'latest', []); + const feeHistoryUsingCache = await ethImpl.feeHistory(countBlocks, 'latest', [], requestDetails); checkCommonFeeHistoryFields(feeHistoryUsingCache); expect(feeHistoryUsingCache['oldestBlock']).to.eq(numberTo0x(latestBlockNumber - countBlocks + 1)); expect(feeHistoryUsingCache['baseFeePerGas'].length).to.eq(countBlocks + 1); diff --git a/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts b/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts index e9f9e456f2..17f836b25a 100644 --- a/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts +++ b/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts @@ -27,27 +27,29 @@ import constants from '../../../src/lib/constants'; import { SDKClient } from '../../../src/lib/clients'; import { numberTo0x } from '../../../dist/formatters'; import { DEFAULT_NETWORK_FEES, NOT_FOUND_RES } from './eth-config'; -import { predefined } from '../../../src/lib/errors/JsonRpcError'; +import { predefined } from '../../../src'; import RelayAssertions from '../../assertions'; import { generateEthTestEnv } from './eth-helpers'; import { toHex } from '../../helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethGasPrice Gas Price spec', async function () { this.timeout(10000); let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const requestDetails = new RequestDetails({ requestId: 'eth_getPriceTest', ipAddress: '0.0.0.0' }); + this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); @@ -63,20 +65,20 @@ describe('@ethGasPrice Gas Price spec', async function () { describe('@ethGasPrice', async function () { it('eth_gasPrice', async function () { - const weiBars = await ethImpl.gasPrice(); + const weiBars = await ethImpl.gasPrice(requestDetails); const expectedWeiBars = DEFAULT_NETWORK_FEES.fees[2].gas * constants.TINYBAR_TO_WEIBAR_COEF; expect(weiBars).to.equal(numberTo0x(expectedWeiBars)); }); it('eth_gasPrice with cached value', async function () { - const firstGasResult = await ethImpl.gasPrice(); + const firstGasResult = await ethImpl.gasPrice(requestDetails); const modifiedNetworkFees = { ...DEFAULT_NETWORK_FEES }; modifiedNetworkFees.fees[2].gas = DEFAULT_NETWORK_FEES.fees[2].gas * 100; restMock.onGet(`network/fees`).reply(200, modifiedNetworkFees); - const secondGasResult = await ethImpl.gasPrice(); + const secondGasResult = await ethImpl.gasPrice(requestDetails); expect(firstGasResult).to.equal(secondGasResult); }); @@ -88,7 +90,9 @@ describe('@ethGasPrice Gas Price spec', async function () { restMock.onGet(`network/fees`).reply(200, partialNetworkFees); - await RelayAssertions.assertRejection(predefined.COULD_NOT_ESTIMATE_GAS_PRICE, ethImpl.gasPrice, true, ethImpl); + await RelayAssertions.assertRejection(predefined.COULD_NOT_ESTIMATE_GAS_PRICE, ethImpl.gasPrice, true, ethImpl, [ + requestDetails, + ]); }); describe('@ethGasPrice different value for GAS_PRICE_PERCENTAGE_BUFFER env', async function () { @@ -101,12 +105,12 @@ describe('@ethGasPrice Gas Price spec', async function () { for (let testCaseName in GAS_PRICE_PERCENTAGE_BUFFER_TESTCASES) { it(testCaseName, async function () { const GAS_PRICE_PERCENTAGE_BUFFER = GAS_PRICE_PERCENTAGE_BUFFER_TESTCASES[testCaseName]; - const initialGasPrice = await ethImpl.gasPrice(); + const initialGasPrice = await ethImpl.gasPrice(requestDetails); process.env.GAS_PRICE_PERCENTAGE_BUFFER = GAS_PRICE_PERCENTAGE_BUFFER; - await cacheService.clear(); + await cacheService.clear(requestDetails); - const gasPriceWithBuffer = await ethImpl.gasPrice(); + const gasPriceWithBuffer = await ethImpl.gasPrice(requestDetails); process.env.GAS_PRICE_PERCENTAGE_BUFFER = '0'; const expectedInitialGasPrice = toHex(DEFAULT_NETWORK_FEES.fees[2].gas * constants.TINYBAR_TO_WEIBAR_COEF); @@ -135,14 +139,20 @@ describe('@ethGasPrice Gas Price spec', async function () { it('eth_gasPrice with mirror node return network fees found', async function () { const fauxGasTinyBars = 35_000; const fauxGasWeiBarHex = '0x13e52b9abe000'; - sdkClientStub.getTinyBarGasFee.returns(fauxGasTinyBars); + sdkClientStub.getTinyBarGasFee.resolves(fauxGasTinyBars); - const gas = await ethImpl.gasPrice(); + const gas = await ethImpl.gasPrice(requestDetails); expect(gas).to.equal(fauxGasWeiBarHex); }); it('eth_gasPrice with no network fees records found', async function () { - await RelayAssertions.assertRejection(predefined.COULD_NOT_ESTIMATE_GAS_PRICE, ethImpl.gasPrice, true, ethImpl); + await RelayAssertions.assertRejection( + predefined.COULD_NOT_ESTIMATE_GAS_PRICE, + ethImpl.gasPrice, + true, + ethImpl, + [requestDetails], + ); }); }); }); diff --git a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts index 274157f83d..3af1eb1b5a 100644 --- a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts @@ -24,7 +24,7 @@ import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; import { EthImpl } from '../../../src/lib/eth'; -import { buildCryptoTransferTransaction, getRequestId } from '../../helpers'; +import { buildCryptoTransferTransaction } from '../../helpers'; import { SDKClient } from '../../../src/lib/clients'; import { numberTo0x } from '../../../dist/formatters'; import { @@ -44,21 +44,24 @@ import { TINYBAR_TO_WEIBAR_COEF_BIGINT, } from './eth-config'; import { balancesByAccountIdByTimestampURL, generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethGetBalance using MirrorNode', async function () { this.timeout(10000); let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const requestDetails = new RequestDetails({ requestId: 'eth_getBalanceTest', ipAddress: '0.0.0.0' }); + this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); @@ -78,7 +81,7 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOCK_BLOCK_NUMBER_1000_RES); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, MOCK_BALANCE_RES); - const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); }); @@ -86,13 +89,13 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOCK_BLOCK_NUMBER_1000_RES); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, MOCK_BALANCE_RES); - const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); // next call should use cache restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(404, {}); - const resBalanceCached = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null); + const resBalanceCached = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, requestDetails); expect(resBalanceCached).to.equal(resBalance); // Third call should return new number using mirror node @@ -105,9 +108,9 @@ describe('@ethGetBalance using MirrorNode', async function () { }, }); // expire cache, instead of waiting for ttl we clear it to simulate expiry faster. - cacheService.clear(); + await cacheService.clear(requestDetails); - const resBalanceNew = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, getRequestId()); + const resBalanceNew = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, requestDetails); expect(newBalanceHex).to.equal(resBalanceNew); }); @@ -116,7 +119,7 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOCK_BLOCKS_FOR_BALANCE_RES); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, MOCK_BALANCE_RES); - const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); }); @@ -128,7 +131,7 @@ describe('@ethGetBalance using MirrorNode', async function () { }); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, MOCK_BALANCE_RES); - const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockHash, getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockHash, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); }); @@ -137,7 +140,7 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOCK_BLOCKS_FOR_BALANCE_RES); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, MOCK_BALANCE_RES); - const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); }); @@ -160,7 +163,7 @@ describe('@ethGetBalance using MirrorNode', async function () { ], }); - const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockHash, getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockHash, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); }); @@ -169,7 +172,7 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(`contracts/${CONTRACT_ADDRESS_1}`).reply(200, null); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(404, NOT_FOUND_RES); - const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, requestDetails); expect(resBalance).to.equal(EthImpl.zeroHex); }); @@ -177,11 +180,11 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOCK_BLOCK_NUMBER_1000_RES); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, MOCK_BALANCE_RES); - const resNoCache = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, getRequestId()); + const resNoCache = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, requestDetails); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(404, NOT_FOUND_RES); - const resCached = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, getRequestId()); + const resCached = await ethImpl.getBalance(CONTRACT_ADDRESS_1, null, requestDetails); expect(resNoCache).to.equal(DEF_HEX_BALANCE); expect(resCached).to.equal(DEF_HEX_BALANCE); }); @@ -217,11 +220,11 @@ describe('@ethGetBalance using MirrorNode', async function () { }, }); - const resNoCache = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, getRequestId()); + const resNoCache = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, requestDetails); restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(404, NOT_FOUND_RES); - const resCached = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, getRequestId()); + const resCached = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, requestDetails); expect(resNoCache).to.equal(DEF_HEX_BALANCE); expect(resCached).to.equal(DEF_HEX_BALANCE); }); @@ -333,27 +336,27 @@ describe('@ethGetBalance using MirrorNode', async function () { }); it('latest', async () => { - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'latest', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'latest', requestDetails); expect(resBalance).to.equal(hexBalance3); }); it('finalized', async () => { - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'finalized', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'finalized', requestDetails); expect(resBalance).to.equal(hexBalance3); }); it('safe', async () => { - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'safe', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'safe', requestDetails); expect(resBalance).to.equal(hexBalance3); }); it('earliest', async () => { - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'earliest', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'earliest', requestDetails); expect(resBalance).to.equal('0x0'); }); it('pending', async () => { - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'pending', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, 'pending', requestDetails); expect(resBalance).to.equal(hexBalance3); }); @@ -387,7 +390,7 @@ describe('@ethGetBalance using MirrorNode', async function () { }, }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', requestDetails); const historicalBalance = numberTo0x(BigInt(balance3) * TINYBAR_TO_WEIBAR_COEF_BIGINT); expect(resBalance).to.equal(historicalBalance); }); @@ -402,7 +405,7 @@ describe('@ethGetBalance using MirrorNode', async function () { transactions: [], }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', requestDetails); expect(resBalance).to.equal(hexBalance1); }); @@ -433,7 +436,7 @@ describe('@ethGetBalance using MirrorNode', async function () { }, }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', requestDetails); const historicalBalance = numberTo0x(BigInt(balance3 - 175) * TINYBAR_TO_WEIBAR_COEF_BIGINT); expect(resBalance).to.equal(historicalBalance); }); @@ -465,7 +468,7 @@ describe('@ethGetBalance using MirrorNode', async function () { }, }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', requestDetails); const historicalBalance = numberTo0x(BigInt(balance3 + 175) * TINYBAR_TO_WEIBAR_COEF_BIGINT); expect(resBalance).to.equal(historicalBalance); }); @@ -498,7 +501,7 @@ describe('@ethGetBalance using MirrorNode', async function () { }, }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '2', requestDetails); const historicalBalance = numberTo0x(BigInt(balance3 + 65) * TINYBAR_TO_WEIBAR_COEF_BIGINT); expect(resBalance).to.equal(historicalBalance); }); @@ -543,7 +546,7 @@ describe('@ethGetBalance using MirrorNode', async function () { ], }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', requestDetails); const historicalBalance = numberTo0x(BigInt(balance3 - 230) * TINYBAR_TO_WEIBAR_COEF_BIGINT); expect(resBalance).to.equal(historicalBalance); }); @@ -604,7 +607,7 @@ describe('@ethGetBalance using MirrorNode', async function () { blocks: [latestBlock], }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', requestDetails); const historicalBalance = numberTo0x(BigInt(balance3 - 480) * TINYBAR_TO_WEIBAR_COEF_BIGINT); expect(resBalance).to.equal(historicalBalance); }); @@ -664,13 +667,13 @@ describe('@ethGetBalance using MirrorNode', async function () { blocks: [latestBlock], }); - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '1', requestDetails); const historicalBalance = numberTo0x(BigInt(balance3 - 80) * TINYBAR_TO_WEIBAR_COEF_BIGINT); expect(resBalance).to.equal(historicalBalance); }); it('blockNumber is the same as the latest block', async () => { - const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '3', getRequestId()); + const resBalance = await ethImpl.getBalance(CONTRACT_ID_1, '3', requestDetails); expect(resBalance).to.equal(hexBalance3); }); @@ -681,7 +684,7 @@ describe('@ethGetBalance using MirrorNode', async function () { .onGet(balancesByAccountIdByTimestampURL(notFoundEvmAddress, '1651550386.060890949')) .reply(404, NOT_FOUND_RES); - const resBalance = await ethImpl.getBalance(notFoundEvmAddress, '1', getRequestId()); + const resBalance = await ethImpl.getBalance(notFoundEvmAddress, '1', requestDetails); expect(resBalance).to.equal(EthImpl.zeroHex); }); @@ -700,7 +703,7 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(`blocks/2`).reply(200, recentBlockWithinLastfifteen); restMock.onGet(`accounts/${notFoundEvmAddress}?limit=100`).reply(404, NOT_FOUND_RES); - const resBalance = await ethImpl.getBalance(notFoundEvmAddress, '2', getRequestId()); + const resBalance = await ethImpl.getBalance(notFoundEvmAddress, '2', requestDetails); expect(resBalance).to.equal(EthImpl.zeroHex); }); }); diff --git a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts index d2ea6e35e6..dcf2eb6074 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts @@ -23,7 +23,7 @@ import { expect, use } from 'chai'; import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; -import { predefined } from '../../../src/lib/errors/JsonRpcError'; +import { predefined } from '../../../src'; import { EthImpl } from '../../../src/lib/eth'; import { blockLogsBloom, defaultContractResults, defaultDetailedContractResults } from '../../helpers'; import { SDKClient } from '../../../src/lib/clients'; @@ -56,12 +56,13 @@ import { DEFAULT_BLOCK_RECEIPTS_ROOT_HASH, } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; let ethImplLowTransactionCount: EthImpl; @@ -72,11 +73,12 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { const results = defaultContractResults.results; const TOTAL_GAS_USED = numberTo0x(results[0].gas_used + results[1].gas_used); + const requestDetails = new RequestDetails({ requestId: 'eth_getBlockByHashTest', ipAddress: '0.0.0.0' }); + this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); @@ -113,7 +115,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, gasUsed: TOTAL_GAS_USED, @@ -133,7 +135,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const res = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const res = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); RelayAssertions.assertBlock(res, { transactions: [CONTRACT_HASH_1, CONTRACT_HASH_2], hash: BLOCK_HASH_TRIMMED, @@ -154,7 +156,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, gasUsed: TOTAL_GAS_USED, @@ -176,7 +178,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, gasUsed: TOTAL_GAS_USED, @@ -194,7 +196,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); for (let i = 0; i < 3; i++) { - const result = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); expect(result).to.exist; expect(result).to.not.be.null; if (result) { @@ -211,7 +213,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, true); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, true, requestDetails); RelayAssertions.assertBlock( result, { @@ -234,7 +236,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { restMock.onGet(CONTRACTS_RESULTS_NEXT_URL).reply(200, defaultContractResults); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, true); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, true, requestDetails); RelayAssertions.assertBlock( result, { @@ -251,7 +253,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { }); it('eth_getBlockByHash with block match and contract revert', async function () { - cacheService.clear(); + await cacheService.clear(requestDetails); const randomBlock = { ...DEFAULT_BLOCK, gas_used: 400000, @@ -268,7 +270,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { ) .reply(200, { logs: [] }); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, true); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, true, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, gasUsed: numberTo0x(randomBlock.gas_used), @@ -280,11 +282,11 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { }); it('eth_getBlockByHash with no match', async function () { - cacheService.clear(); + await cacheService.clear(requestDetails); // mirror node request mocks restMock.onGet(`blocks/${BLOCK_HASH}`).reply(404, NO_SUCH_BLOCK_EXISTS_RES); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); expect(result).to.equal(null); }); @@ -305,6 +307,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { await RelayAssertions.assertRejection(predefined.INTERNAL_ERROR(), ethImpl.getBlockByHash, false, ethImpl, [ BLOCK_HASH, false, + requestDetails, ]); }); @@ -320,7 +323,7 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { .reply(200, defaultDetailedContractResults); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const args = [BLOCK_HASH, true]; + const args = [BLOCK_HASH, true, requestDetails]; await RelayAssertions.assertRejection( predefined.MAX_BLOCK_SIZE(77), diff --git a/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts index ba3da3da1f..4b29f5272a 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts @@ -23,11 +23,11 @@ import { expect, use } from 'chai'; import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; -import { predefined } from '../../../src/lib/errors/JsonRpcError'; +import { Eth, predefined } from '../../../src'; import { EthImpl } from '../../../src/lib/eth'; import { blockLogsBloom, defaultContractResults, defaultDetailedContractResults } from '../../helpers'; import { Block, Transaction } from '../../../src/lib/model'; -import { SDKClient } from '../../../src/lib/clients'; +import { MirrorNodeClient, SDKClient } from '../../../src/lib/clients'; import RelayAssertions from '../../assertions'; import constants from '../../../src/lib/constants'; import { hashNumber, numberTo0x } from '../../../dist/formatters'; @@ -73,22 +73,44 @@ import { } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; import { fail } from 'assert'; +import { RequestDetails } from '../../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; +import HAPIService from '../../../src/lib/services/hapiService/hapiService'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; +import { Registry } from 'prom-client'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; let ethImplLowTransactionCount: EthImpl; describe('@ethGetBlockByNumber using MirrorNode', async function () { this.timeout(10000); - let { restMock, hapiServiceInstance, ethImpl, cacheService, mirrorNodeInstance, logger, registry } = - generateEthTestEnv(true); + const { + restMock, + hapiServiceInstance, + ethImpl, + cacheService, + mirrorNodeInstance, + logger, + registry, + }: { + restMock: MockAdapter; + hapiServiceInstance: HAPIService; + ethImpl: Eth; + cacheService: CacheService; + mirrorNodeInstance: MirrorNodeClient; + logger: any; + registry: Registry; + } = generateEthTestEnv(true); const results = defaultContractResults.results; const TOTAL_GAS_USED = numberTo0x(results[0].gas_used + results[1].gas_used); + const requestDetails = new RequestDetails({ requestId: 'eth_getBlockByNumberTest', ipAddress: '0.0.0.0' }); + const veriftAggregatedInfo = (result) => { // verify aggregated info expect(result).to.exist; @@ -106,9 +128,9 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { expect(transactions[1].gas).equal(hashNumber(GAS_USED_2)); } - this.beforeEach(() => { + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(); + await cacheService.clear(requestDetails); restMock.reset(); restMock.resetHandlers(); @@ -146,36 +168,36 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { it('"eth_blockNumber" should return the latest block number', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); - const blockNumber = await ethImpl.blockNumber(); + const blockNumber = await ethImpl.blockNumber(requestDetails); expect(blockNumber).to.be.eq(blockNumber); }); it('"eth_blockNumber" should return the latest block number using cache', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); - const blockNumber = await ethImpl.blockNumber(); + const blockNumber = await ethImpl.blockNumber(requestDetails); expect(numberTo0x(DEFAULT_BLOCK.number)).to.be.eq(blockNumber); // Second call should return the same block number using cache restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(400, DEFAULT_BLOCKS_RES); - const blockNumber2 = await ethImpl.blockNumber(); + const blockNumber2 = await ethImpl.blockNumber(requestDetails); expect(blockNumber2).to.be.eq(blockNumber); // expire cache, instead of waiting for ttl we clear it to simulate expiry faster. - cacheService.clear(); + await cacheService.clear(requestDetails); // Third call should return new number using mirror node const newBlockNumber = 7; restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [{ ...DEFAULT_BLOCK, number: newBlockNumber }], }); - const blockNumber3 = await ethImpl.blockNumber(); + const blockNumber3 = await ethImpl.blockNumber(requestDetails); expect(numberTo0x(newBlockNumber)).to.be.eq(blockNumber3); }); it('"eth_blockNumber" should throw an error if no blocks are found', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(404, BLOCK_NOT_FOUND_RES); const error = predefined.COULD_NOT_RETRIEVE_LATEST_BLOCK; - await RelayAssertions.assertRejection(error, ethImpl.blockNumber, true, ethImpl); + await RelayAssertions.assertRejection(error, ethImpl.blockNumber, true, ethImpl, [requestDetails]); }); it('"eth_blockNumber" return the latest block number on second try', async function () { @@ -186,9 +208,9 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { .replyOnce(200, DEFAULT_BLOCKS_RES); try { - await ethImpl.blockNumber(); + await ethImpl.blockNumber(requestDetails); } catch (error) {} - const blockNumber = await ethImpl.blockNumber(); + const blockNumber = await ethImpl.blockNumber(requestDetails); expect(blockNumber).to.be.eq(blockNumber); }); @@ -201,6 +223,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { ethImpl.blockNumber, true, ethImpl, + [requestDetails], ); }); @@ -216,7 +239,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { it('eth_getBlockByNumber with match', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, @@ -234,7 +257,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { results: [...defaultContractResults.results, ...defaultContractResults.results], }); - const res = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false); + const res = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false, requestDetails); RelayAssertions.assertBlock(res, { transactions: [CONTRACT_HASH_1, CONTRACT_HASH_2], hash: BLOCK_HASH_TRIMMED, @@ -252,7 +275,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { }); restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, @@ -270,7 +293,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { it('eth_getBlockByNumber with match paginated', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, LINKS_NEXT_RES); restMock.onGet(CONTRACTS_RESULTS_NEXT_URL).reply(200, defaultContractResults); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, @@ -285,10 +308,10 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { it('eth_getBlockByNumber should return cached result', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); - const resBeforeCache = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false); + const resBeforeCache = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false, requestDetails); restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(404); - const resAfterCache = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false); + const resAfterCache = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false, requestDetails); expect(resBeforeCache).to.eq(resAfterCache); }); @@ -296,7 +319,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { it('eth_getBlockByHash with match', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); RelayAssertions.assertBlock(result, { hash: BLOCK_HASH_TRIMMED, gasUsed: TOTAL_GAS_USED, @@ -311,7 +334,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, LINKS_NEXT_RES); restMock.onGet(CONTRACTS_RESULTS_NEXT_URL).reply(200, defaultContractResults); - const result = await ethImpl.getBlockByHash(BLOCK_HASH, false); + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); const toMatch = { hash: BLOCK_HASH_TRIMMED, gasUsed: TOTAL_GAS_USED, @@ -330,7 +353,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOST_RECENT_BLOCK); restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, { results: [] }); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, { logs: [] }); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), false, requestDetails); if (result) { // verify aggregated info veriftAggregatedInfo(result); @@ -349,7 +372,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOST_RECENT_BLOCK); restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), true); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), true, requestDetails); if (result) { veriftAggregatedInfo(result); expect(result.gasUsed).equal(TOTAL_GAS_USED); @@ -369,7 +392,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(CONTRACT_QUERY).reply(200, CONTRACT_RESPONSE_MOCK); restMock.onGet(LOG_QUERY).reply(200, LOGS_RESPONSE_MOCK); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER_WITH_SYN_TXN), true); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER_WITH_SYN_TXN), true, requestDetails); if (result) { result.transactions.forEach((txn) => { expect(txn.maxFeePerGas).to.exist; @@ -387,7 +410,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, LINKS_NEXT_RES); restMock.onGet(CONTRACTS_RESULTS_NEXT_URL).reply(200, defaultContractResults); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), true); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), true, requestDetails); if (result) { // verify aggregated info veriftAggregatedInfo(result); @@ -406,7 +429,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOST_RECENT_BLOCK); restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, DEFAULT_CONTRACT_RES_REVERT); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, { logs: [] }); - const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), true); + const result = await ethImpl.getBlockByNumber(numberTo0x(BLOCK_NUMBER), true, requestDetails); if (result) { veriftAggregatedInfo(result); expect(result.gasUsed).equal(numberTo0x(GAS_USED_1)); @@ -421,7 +444,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(404, NO_SUCH_BLOCK_EXISTS_RES); restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOST_RECENT_BLOCK); - const result = await ethImpl.getBlockByNumber(BLOCK_NUMBER.toString(), false); + const result = await ethImpl.getBlockByNumber(BLOCK_NUMBER.toString(), false, requestDetails); expect(result).to.equal(null); }); @@ -436,7 +459,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { } } - this.beforeEach(() => { + beforeEach(() => { restMock.resetHistory(); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); for (const result of defaultContractResults.results) { @@ -447,7 +470,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { }); it('eth_getBlockByNumber with latest tag', async function () { - const result = await ethImpl.getBlockByNumber('latest', false); + const result = await ethImpl.getBlockByNumber('latest', false, requestDetails); // check that we only made the expected number of requests with the expected urls expect(restMock.history.get.length).equal(TOTAL_GET_CALLS_EXECUTED); expect(restMock.history.get[0].url).equal(BLOCKS_LIMIT_ORDER_URL); @@ -465,24 +488,24 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, LINKS_NEXT_RES); restMock.onGet(CONTRACTS_RESULTS_NEXT_URL).reply(200, defaultContractResults); - const result = await ethImpl.getBlockByNumber('latest', false); + const result = await ethImpl.getBlockByNumber('latest', false, requestDetails); confirmResult(result); }); it('eth_getBlockByNumber with pending tag', async function () { - const result = await ethImpl.getBlockByNumber('pending', false); + const result = await ethImpl.getBlockByNumber('pending', false, requestDetails); confirmResult(result); }); it('eth_getBlockByNumber with earliest tag', async function () { restMock.onGet(`blocks/0`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockByNumber('earliest', false); + const result = await ethImpl.getBlockByNumber('earliest', false, requestDetails); confirmResult(result); }); it('eth_getBlockByNumber with finalized tag', async function () { - const result = await ethImpl.getBlockByNumber('finalized', false); + const result = await ethImpl.getBlockByNumber('finalized', false, requestDetails); // check that we only made the expected number of requests with the expected urls expect(restMock.history.get.length).equal(TOTAL_GET_CALLS_EXECUTED); expect(restMock.history.get[0].url).equal(BLOCKS_LIMIT_ORDER_URL); @@ -497,7 +520,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { }); it('eth_getBlockByNumber with safe tag', async function () { - const result = await ethImpl.getBlockByNumber('safe', false); + const result = await ethImpl.getBlockByNumber('safe', false, requestDetails); // check that we only made the expected number of requests with the expected urls expect(restMock.history.get.length).equal(TOTAL_GET_CALLS_EXECUTED); expect(restMock.history.get[0].url).equal(BLOCKS_LIMIT_ORDER_URL); @@ -515,7 +538,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { restMock.onGet(`blocks/3735929054`).reply(200, DEFAULT_BLOCK); restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, BLOCKS_RES); - const result = await ethImpl.getBlockByNumber('0xdeadc0de', false); + const result = await ethImpl.getBlockByNumber('0xdeadc0de', false, requestDetails); confirmResult(result); }); }); @@ -533,7 +556,7 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { .reply(200, defaultDetailedContractResults); restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, DEFAULT_ETH_GET_BLOCK_BY_LOGS); - const args = [numberTo0x(BLOCK_NUMBER), true]; + const args = [numberTo0x(BLOCK_NUMBER), true, requestDetails]; await RelayAssertions.assertRejection( predefined.MAX_BLOCK_SIZE(77), diff --git a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts index cf29a97be1..e626de8bbb 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts @@ -33,20 +33,26 @@ import { NO_SUCH_BLOCK_EXISTS_RES, } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; describe('@ethGetBlockTransactionCountByHash using MirrorNode', async function () { this.timeout(10000); let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const requestDetails = new RequestDetails({ + requestId: 'eth_getBlockTransactionCountByHashTest', + ipAddress: '0.0.0.0', + }); + this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); @@ -63,7 +69,7 @@ describe('@ethGetBlockTransactionCountByHash using MirrorNode', async function ( // mirror node request mocks restMock.onGet(`blocks/${BLOCK_HASH}`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByHash(BLOCK_HASH); + const result = await ethImpl.getBlockTransactionCountByHash(BLOCK_HASH, requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); @@ -71,7 +77,7 @@ describe('@ethGetBlockTransactionCountByHash using MirrorNode', async function ( restMock.onGet(`blocks/${BLOCK_HASH}`).replyOnce(200, DEFAULT_BLOCK); for (let i = 0; i < 3; i++) { - const result = await ethImpl.getBlockTransactionCountByHash(BLOCK_HASH); + const result = await ethImpl.getBlockTransactionCountByHash(BLOCK_HASH, requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); } }); @@ -80,7 +86,7 @@ describe('@ethGetBlockTransactionCountByHash using MirrorNode', async function ( // mirror node request mocks restMock.onGet(`blocks/${BLOCK_HASH}`).reply(404, NO_SUCH_BLOCK_EXISTS_RES); - const result = await ethImpl.getBlockTransactionCountByHash(BLOCK_HASH); + const result = await ethImpl.getBlockTransactionCountByHash(BLOCK_HASH, requestDetails); expect(result).to.equal(null); }); }); diff --git a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts index 188acf8dcb..27c579b42f 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts @@ -35,22 +35,27 @@ import { NO_SUCH_BLOCK_EXISTS_RES, } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function () { this.timeout(10000); let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); - this.beforeEach(() => { + const requestDetails = new RequestDetails({ + requestId: 'eth_getBlockTransactionCountByNumberTest', + ipAddress: '0.0.0.0', + }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(); + await cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); @@ -65,7 +70,7 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function // mirror node request mocks restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByNumber(BLOCK_NUMBER.toString()); + const result = await ethImpl.getBlockTransactionCountByNumber(BLOCK_NUMBER.toString(), requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); @@ -73,16 +78,16 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function restMock.onGet(`blocks/${BLOCK_NUMBER}`).replyOnce(200, DEFAULT_BLOCK); for (let i = 0; i < 3; i++) { - const result = await ethImpl.getBlockTransactionCountByNumber(BLOCK_NUMBER.toString()); + const result = await ethImpl.getBlockTransactionCountByNumber(BLOCK_NUMBER.toString(), requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); } }); it('eth_getBlockTransactionCountByNumber with no match', async function () { - cacheService.clear(); + await cacheService.clear(requestDetails); restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(404, NO_SUCH_BLOCK_EXISTS_RES); - const result = await ethImpl.getBlockTransactionCountByNumber(BLOCK_NUMBER.toString()); + const result = await ethImpl.getBlockTransactionCountByNumber(BLOCK_NUMBER.toString(), requestDetails); expect(result).to.equal(null); }); @@ -91,7 +96,7 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByNumber('latest'); + const result = await ethImpl.getBlockTransactionCountByNumber('latest', requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); @@ -100,7 +105,7 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByNumber('finalized'); + const result = await ethImpl.getBlockTransactionCountByNumber('finalized', requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); @@ -109,7 +114,7 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByNumber('safe'); + const result = await ethImpl.getBlockTransactionCountByNumber('safe', requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); @@ -118,7 +123,7 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByNumber('pending'); + const result = await ethImpl.getBlockTransactionCountByNumber('pending', requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); @@ -126,7 +131,7 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function // mirror node request mocks restMock.onGet(`blocks/0`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByNumber('earliest'); + const result = await ethImpl.getBlockTransactionCountByNumber('earliest', requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); @@ -134,7 +139,7 @@ describe('@ethGetBlockTransactionCountByNumber using MirrorNode', async function // mirror node request mocks restMock.onGet(`blocks/3735929054`).reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getBlockTransactionCountByNumber('0xdeadc0de'); + const result = await ethImpl.getBlockTransactionCountByNumber('0xdeadc0de', requestDetails); expect(result).equal(numberTo0x(BLOCK_TRANSACTION_COUNT)); }); }); diff --git a/packages/relay/tests/lib/eth/eth_getCode.spec.ts b/packages/relay/tests/lib/eth/eth_getCode.spec.ts index e475dcea8f..c5cd107218 100644 --- a/packages/relay/tests/lib/eth/eth_getCode.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getCode.spec.ts @@ -38,12 +38,13 @@ import { } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; import { JsonRpcError, predefined } from '../../../src'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethGetCode using MirrorNode', async function () { @@ -52,9 +53,11 @@ describe('@ethGetCode using MirrorNode', async function () { let validBlockParam = [null, 'earliest', 'latest', 'pending', 'finalized', 'safe', '0x0', '0x369ABF']; let invalidBlockParam = ['hedera', 'ethereum', '0xhbar', '0x369ABF369ABF369ABF369ABF']; - this.beforeEach(() => { + const requestDetails = new RequestDetails({ requestId: 'eth_getCodeTest', ipAddress: '0.0.0.0' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); @@ -66,7 +69,7 @@ describe('@ethGetCode using MirrorNode', async function () { restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(404, null); restMock.onGet(`tokens/0.0.${parseInt(CONTRACT_ADDRESS_1, 16)}`).reply(404, null); restMock.onGet(`contracts/${CONTRACT_ADDRESS_1}`).reply(200, DEFAULT_CONTRACT); - sdkClientStub.getContractByteCode.returns(Buffer.from(DEPLOYED_BYTECODE.replace('0x', ''), 'hex')); + sdkClientStub.getContractByteCode.resolves(Buffer.from(DEPLOYED_BYTECODE.replace('0x', ''), 'hex')); }); this.afterEach(() => { @@ -86,20 +89,20 @@ describe('@ethGetCode using MirrorNode', async function () { }), ); - const resNoCache = await ethImpl.getCode(CONTRACT_ADDRESS_1, null); - const resCached = await ethImpl.getCode(CONTRACT_ADDRESS_1, null); + const resNoCache = await ethImpl.getCode(CONTRACT_ADDRESS_1, null, requestDetails); + const resCached = await ethImpl.getCode(CONTRACT_ADDRESS_1, null, requestDetails); expect(resNoCache).to.equal(EthImpl.emptyHex); expect(resCached).to.equal(EthImpl.emptyHex); }); it('should return the runtime_bytecode from the mirror node', async () => { - const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, null); + const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, null, requestDetails); expect(res).to.equal(MIRROR_NODE_DEPLOYED_BYTECODE); }); it('should return the bytecode from SDK if Mirror Node returns 404', async () => { restMock.onGet(`contracts/${CONTRACT_ADDRESS_1}`).reply(404, DEFAULT_CONTRACT); - const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, null); + const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, null, requestDetails); expect(res).to.equal(DEPLOYED_BYTECODE); }); @@ -108,7 +111,7 @@ describe('@ethGetCode using MirrorNode', async function () { ...DEFAULT_CONTRACT, runtime_bytecode: EthImpl.emptyHex, }); - const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, null); + const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, null, requestDetails); expect(res).to.equal(DEPLOYED_BYTECODE); }); @@ -119,7 +122,7 @@ describe('@ethGetCode using MirrorNode', async function () { const redirectBytecode = `6080604052348015600f57600080fd5b506000610167905077618dc65e${HTS_TOKEN_ADDRESS.slice( 2, )}600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033`; - const res = await ethImpl.getCode(HTS_TOKEN_ADDRESS, null); + const res = await ethImpl.getCode(HTS_TOKEN_ADDRESS, null, requestDetails); expect(res).to.equal(redirectBytecode); }); @@ -127,13 +130,13 @@ describe('@ethGetCode using MirrorNode', async function () { restMock.onGet(`contracts/${EthImpl.iHTSAddress}`).reply(200, DEFAULT_CONTRACT); restMock.onGet(`accounts/${EthImpl.iHTSAddress}${NO_TRANSACTIONS}`).reply(404, null); - const res = await ethImpl.getCode(EthImpl.iHTSAddress, null); + const res = await ethImpl.getCode(EthImpl.iHTSAddress, null, requestDetails); expect(res).to.equal(EthImpl.invalidEVMInstruction); }); validBlockParam.forEach((blockParam) => { it(`should pass the validate param check with blockParam=${blockParam} and return the bytecode`, async () => { - const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, blockParam); + const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, blockParam, requestDetails); expect(res).to.equal(MIRROR_NODE_DEPLOYED_BYTECODE); }); }); @@ -141,7 +144,7 @@ describe('@ethGetCode using MirrorNode', async function () { invalidBlockParam.forEach((blockParam) => { it(`should throw INVALID_PARAMETER JsonRpcError with invalid blockParam=${blockParam}`, async () => { try { - await ethImpl.getCode(EthImpl.iHTSAddress, blockParam); + await ethImpl.getCode(EthImpl.iHTSAddress, blockParam, requestDetails); expect(true).to.eq(false); } catch (error: any) { const expectedError = predefined.UNKNOWN_BLOCK( diff --git a/packages/relay/tests/lib/eth/eth_getLogs.spec.ts b/packages/relay/tests/lib/eth/eth_getLogs.spec.ts index 27746b29e6..d2cd1e264c 100644 --- a/packages/relay/tests/lib/eth/eth_getLogs.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getLogs.spec.ts @@ -58,12 +58,17 @@ import { } from './eth-config'; import { ethers } from 'ethers'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; +import HAPIService from '../../../src/lib/services/hapiService/hapiService'; +import { Eth } from '../../../src'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; dotenv.config({ path: path.resolve(__dirname, '../../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethGetLogs using MirrorNode', async function () { @@ -76,14 +81,22 @@ describe('@ethGetLogs using MirrorNode', async function () { to: '1651560395.060890949', }, }; - let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const { + restMock, + hapiServiceInstance, + ethImpl, + cacheService, + }: { restMock: MockAdapter; hapiServiceInstance: HAPIService; ethImpl: Eth; cacheService: CacheService } = + generateEthTestEnv(); const filteredLogs = { logs: [DEFAULT_LOGS.logs[0], DEFAULT_LOGS.logs[1]], }; - this.beforeEach(() => { + const requestDetails = new RequestDetails({ requestId: 'eth_getLogsTest', ipAddress: '0.0.0.0' }); + + beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); @@ -93,13 +106,13 @@ describe('@ethGetLogs using MirrorNode', async function () { process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; }); - this.afterEach(() => { + afterEach(() => { getSdkClientStub.restore(); process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('timeout', async function () { - this.beforeEach(() => { + beforeEach(() => { restMock.onGet(`blocks/${BLOCK_HASH}`).timeout(); restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); restMock.onGet(CONTRACTS_LOGS_WITH_FILTER).timeout(); @@ -107,14 +120,14 @@ describe('@ethGetLogs using MirrorNode', async function () { }); it('BLOCK_HASH filter timeouts and throws the expected error', async () => { - await ethGetLogsFailing(ethImpl, [BLOCK_HASH, null, null, null, null], (error) => { + await ethGetLogsFailing(ethImpl, [BLOCK_HASH, null, null, null, null, requestDetails], (error: any) => { expect(error.statusCode).to.equal(504); expect(error.message).to.eq('timeout of 10000ms exceeded'); }); }); it('address filter timeouts and throws the expected error', async () => { - await ethGetLogsFailing(ethImpl, [null, null, null, CONTRACT_ADDRESS_1, null], (error) => { + await ethGetLogsFailing(ethImpl, [null, null, null, CONTRACT_ADDRESS_1, null, requestDetails], (error: any) => { expect(error.statusCode).to.equal(504); expect(error.message).to.eq('timeout of 10000ms exceeded'); }); @@ -129,8 +142,8 @@ describe('@ethGetLogs using MirrorNode', async function () { let errorReceived = false; try { - await ethImpl.getLogs(null, null, null, null, null); - } catch (error) { + await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); + } catch (error: any) { errorReceived = true; expect(error.statusCode).to.equal(400); expect(error.message).to.eq('Mocked error'); @@ -154,7 +167,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, { ...DEFAULT_CONTRACT, contract_id: `0.0.105${index}` }); }); - const result = await ethImpl.getLogs(null, null, null, null, null); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); expect(result).to.exist; expect(result.length).to.eq(4); @@ -179,11 +192,11 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, { ...DEFAULT_CONTRACT, contract_id: `0.0.105${index}` }); }); - const result = await ethImpl.getLogs(null, null, null, null, null); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); expect(result).to.exist; expect(result.length).to.eq(4); - result.forEach((log, index) => { + result.forEach((log, _index) => { expect(log.transactionIndex).to.be.null; }); }); @@ -227,7 +240,7 @@ describe('@ethGetLogs using MirrorNode', async function () { }); //setting mirror node limit to 2 for this test only process.env['MIRROR_NODE_LIMIT_PARAM'] = '2'; - const result = await ethImpl.getLogs(null, null, null, null, null); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); //resetting mirror node limit to 100 process.env['MIRROR_NODE_LIMIT_PARAM'] = '100'; expect(result).to.exist; @@ -255,7 +268,7 @@ describe('@ethGetLogs using MirrorNode', async function () { .onGet(`contracts/${filteredLogs.logs[0].address}`) .reply(200, { ...DEFAULT_CONTRACT, evm_address: defaultEvmAddress }); - const result = await ethImpl.getLogs(null, null, null, null, null); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); expect(result).to.exist; expect(result.length).to.eq(1); @@ -272,7 +285,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT); } - const result = await ethImpl.getLogs(null, null, null, CONTRACT_ADDRESS_1, null); + const result = await ethImpl.getLogs(null, 'latest', 'latest', CONTRACT_ADDRESS_1, null, requestDetails); expect(result).to.exist; @@ -310,7 +323,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet('blocks/1').reply(200, fromBlock); restMock.onGet('blocks/1003').reply(200, toBlock); - const result = await ethImpl.getLogs(null, '0x1', '0x3eb', address, null); + const result = await ethImpl.getLogs(null, '0x1', '0x3eb', address, null, requestDetails); expect(result).to.exist; @@ -340,7 +353,14 @@ describe('@ethGetLogs using MirrorNode', async function () { } restMock.onGet(`contracts/${CONTRACT_ADDRESS_2}`).reply(200, DEFAULT_CONTRACT_2); - const result = await ethImpl.getLogs(null, null, null, [CONTRACT_ADDRESS_1, CONTRACT_ADDRESS_2], null); + const result = await ethImpl.getLogs( + null, + 'latest', + 'latest', + [CONTRACT_ADDRESS_1, CONTRACT_ADDRESS_2], + null, + requestDetails, + ); expect(result).to.exist; @@ -362,7 +382,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT); } - const result = await ethImpl.getLogs(BLOCK_HASH, null, null, null, null); + const result = await ethImpl.getLogs(BLOCK_HASH, 'latest', 'latest', null, null, requestDetails); expect(result).to.exist; expectLogData1(result[0]); @@ -394,7 +414,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT); } - const result = await ethImpl.getLogs(null, '0x5', '0x10', null, null); + const result = await ethImpl.getLogs(null, '0x5', '0x10', null, null, requestDetails); expect(result).to.exist; expectLogData1(result[0]); @@ -407,7 +427,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet('blocks/5').reply(200, DEFAULT_BLOCK); restMock.onGet('blocks/16').reply(404, NOT_FOUND_RES); - const result = await ethImpl.getLogs(null, '0x10', '0x5', null, null); + const result = await ethImpl.getLogs(null, '0x10', '0x5', null, null, requestDetails); expect(result).to.exist; expect(result).to.be.empty; @@ -426,7 +446,7 @@ describe('@ethGetLogs using MirrorNode', async function () { .reply(200, filteredLogs); restMock.onGet(`contracts/${filteredLogs.logs[0].address}`).reply(200, DEFAULT_CONTRACT); - const result = await ethImpl.getLogs(null, '0x5', '0x10', null, null); + const result = await ethImpl.getLogs(null, '0x5', '0x10', null, null, requestDetails); expect(result).to.exist; expectLogData1(result[0]); @@ -445,7 +465,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [latestBlock] }); restMock.onGet('blocks/16').reply(200, fromBlock); restMock.onGet('blocks/5').reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getLogs(null, '0x10', '0x5', null, null); + const result = await ethImpl.getLogs(null, '0x10', '0x5', null, null, requestDetails); expect(result).to.exist; expect(result).to.be.empty; @@ -455,7 +475,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [latestBlock] }); restMock.onGet('blocks/5').reply(200, DEFAULT_BLOCKS_RES); - await ethGetLogsFailing(ethImpl, [null, null, '0x5', null, null], (error) => { + await ethGetLogsFailing(ethImpl, [null, null, '0x5', null, null, requestDetails], (error: any) => { expect(error.code).to.equal(-32011); expect(error.message).to.equal('Provided toBlock parameter without specifying fromBlock'); }); @@ -472,7 +492,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT); } - const result = await ethImpl.getLogs(null, 'latest', null, null, null); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); expect(result).to.exist; expectLogData1(result[0]); @@ -498,7 +518,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet('blocks/1').reply(200, fromBlock); restMock.onGet('blocks/1003').reply(200, toBlock); - await ethGetLogsFailing(ethImpl, [null, '0x1', '0x3eb', address, null], (error) => { + await ethGetLogsFailing(ethImpl, [null, '0x1', '0x3eb', address, null, requestDetails], (error: any) => { expect(error.message).to.equal('Exceeded maximum block range: 1000'); }); }); @@ -523,7 +543,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT); } - const result = await ethImpl.getLogs(null, null, null, null, DEFAULT_LOG_TOPICS); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, DEFAULT_LOG_TOPICS, requestDetails); expect(result).to.exist; expectLogData1(result[0]); @@ -547,7 +567,7 @@ describe('@ethGetLogs using MirrorNode', async function () { for (const log of filteredLogs.logs) { restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT); } - const result = await ethImpl.getLogs(null, null, null, null, DEFAULT_NULL_LOG_TOPICS); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, DEFAULT_NULL_LOG_TOPICS, requestDetails); expect(result).to.exist; expect(result[0].topics.length).to.eq(DEFAULT_LOGS_4[0].topics.length); @@ -577,7 +597,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT); } - const result = await ethImpl.getLogs(null, '0x5', '0x10', null, DEFAULT_LOG_TOPICS); + const result = await ethImpl.getLogs(null, '0x5', '0x10', null, DEFAULT_LOG_TOPICS, requestDetails); expectLogData1(result[0]); expectLogData2(result[1]); @@ -587,7 +607,7 @@ describe('@ethGetLogs using MirrorNode', async function () { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [latestBlock] }); restMock.onGet('blocks/0').reply(200, DEFAULT_BLOCK); restMock.onGet('blocks/latest').reply(200, DEFAULT_BLOCK); - const result = await ethImpl.getLogs(null, '0x0', 'latest', ethers.ZeroAddress, DEFAULT_LOG_TOPICS); + const result = await ethImpl.getLogs(null, '0x0', 'latest', ethers.ZeroAddress, DEFAULT_LOG_TOPICS, requestDetails); expect(result.length).to.eq(0); expect(result).to.deep.equal([]); }); diff --git a/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts b/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts index 9c552380e7..4b5d5033b3 100644 --- a/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts @@ -40,23 +40,33 @@ import { OLDER_BLOCK, BLOCK_HASH, } from './eth-config'; -import { predefined } from '../../../src/lib/errors/JsonRpcError'; +import { Eth, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; import { defaultDetailedContractResults } from '../../helpers'; import { numberTo0x } from '../../../src/formatters'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; +import HAPIService from '../../../src/lib/services/hapiService/hapiService'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethGetStorageAt eth_getStorageAt spec', async function () { this.timeout(10000); - let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); - + const { + restMock, + hapiServiceInstance, + ethImpl, + cacheService, + }: { restMock: MockAdapter; hapiServiceInstance: HAPIService; ethImpl: Eth; cacheService: CacheService } = + generateEthTestEnv(); + const requestDetails = new RequestDetails({ requestId: 'eth_getStorageAtTest', ipAddress: '0.0.0.0' }); function confirmResult(result: string) { expect(result).to.exist; expect(result).to.not.be.null; @@ -65,7 +75,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); @@ -93,7 +103,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { ) .reply(200, DEFAULT_CURRENT_CONTRACT_STATE); - const result = await ethImpl.getStorageAt(CONTRACT_ADDRESS_1, '0x101', numberTo0x(BLOCK_NUMBER)); + const result = await ethImpl.getStorageAt(CONTRACT_ADDRESS_1, '0x101', requestDetails, numberTo0x(BLOCK_NUMBER)); confirmResult(result); // verify slot value @@ -108,7 +118,12 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { ) .reply(200, DEFAULT_CURRENT_CONTRACT_STATE); - const result = await ethImpl.getStorageAt(CONTRACT_ADDRESS_1, '0x0000101', numberTo0x(BLOCK_NUMBER)); + const result = await ethImpl.getStorageAt( + CONTRACT_ADDRESS_1, + '0x0000101', + requestDetails, + numberTo0x(BLOCK_NUMBER), + ); confirmResult(result); // verify slot value @@ -126,6 +141,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, defaultDetailedContractResults.state_changes[0].slot, + requestDetails, numberTo0x(BLOCK_NUMBER), ); confirmResult(result); @@ -145,6 +161,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, defaultDetailedContractResults.state_changes[0].slot, + requestDetails, BLOCK_HASH, ); confirmResult(result); @@ -164,6 +181,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, DEFAULT_CURRENT_CONTRACT_STATE.state[0].slot, + requestDetails, 'latest', ); confirmResult(result); @@ -182,6 +200,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, DEFAULT_CURRENT_CONTRACT_STATE.state[0].slot, + requestDetails, 'finalized', ); confirmResult(result); @@ -200,6 +219,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, DEFAULT_CURRENT_CONTRACT_STATE.state[0].slot, + requestDetails, 'safe', ); confirmResult(result); @@ -208,7 +228,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { }); // Block number is a required param, this should not work and should be removed when/if validations are added. - // Instead the relay should return `missing value for required argument error`. + // Instead, the relay should return `missing value for required argument error`. it('eth_getStorageAt with match null block', async function () { // mirror node request mocks restMock @@ -220,6 +240,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, defaultDetailedContractResults.state_changes[0].slot, + requestDetails, ); confirmResult(result); @@ -229,7 +250,12 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { it('eth_getStorageAt should throw a predefined RESOURCE_NOT_FOUND when block not found', async function () { restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, null); - const args = [CONTRACT_ADDRESS_1, defaultDetailedContractResults.state_changes[0].slot, numberTo0x(BLOCK_NUMBER)]; + const args = [ + CONTRACT_ADDRESS_1, + defaultDetailedContractResults.state_changes[0].slot, + requestDetails, + numberTo0x(BLOCK_NUMBER), + ]; await RelayAssertions.assertRejection( predefined.RESOURCE_NOT_FOUND(), @@ -248,7 +274,12 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { ) .reply(200, DEFAULT_CONTRACT_STATE_EMPTY_ARRAY); - const result = await ethImpl.getStorageAt(CONTRACT_ADDRESS_1, wrongSlot, numberTo0x(BLOCK_NUMBER)); + const result = await ethImpl.getStorageAt( + CONTRACT_ADDRESS_1, + wrongSlot, + requestDetails, + numberTo0x(BLOCK_NUMBER), + ); expect(result).to.equal(EthImpl.zeroHex32Byte); }); @@ -263,6 +294,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, DEFAULT_OLDER_CONTRACT_STATE.state[0].slot, + requestDetails, numberTo0x(OLDER_BLOCK.number), ); expect(result).to.equal(DEFAULT_OLDER_CONTRACT_STATE.state[0].value); @@ -279,6 +311,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { const result = await ethImpl.getStorageAt( CONTRACT_ADDRESS_1, DEFAULT_OLDER_CONTRACT_STATE.state[0].slot, + requestDetails, numberTo0x(OLDER_BLOCK.number), ); expect(result).to.equal(ethers.ZeroHash); diff --git a/packages/relay/tests/lib/eth/eth_getTransactionByBlockHashAndIndex.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionByBlockHashAndIndex.spec.ts index 2b071c1194..cfb59c429f 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionByBlockHashAndIndex.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionByBlockHashAndIndex.spec.ts @@ -21,10 +21,10 @@ import path from 'path'; import dotenv from 'dotenv'; import { expect, use } from 'chai'; import sinon from 'sinon'; -import * as _ from 'lodash'; +import _ from 'lodash'; import chaiAsPromised from 'chai-as-promised'; -import { predefined } from '../../../src/lib/errors/JsonRpcError'; +import { Eth, predefined } from '../../../src'; import { defaultContractResults, defaultDetailedContractResults } from '../../helpers'; import { Transaction, Transaction1559, Transaction2930 } from '../../../src/lib/model'; import { SDKClient } from '../../../src/lib/clients'; @@ -41,17 +41,18 @@ import { DEFAULT_NETWORK_FEES, EMPTY_RES, NOT_FOUND_RES, - ACCOUNT_ADDRESS_1, - CONTRACT_ADDRESS_2, - CONTRACT_ID_2, } from './eth-config'; import { contractResultsByHashByIndexURL, generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; +import HAPIService from '../../../src/lib/services/hapiService/hapiService'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; function verifyAggregatedInfo(result: Transaction | null) { // verify aggregated info @@ -65,11 +66,22 @@ function verifyAggregatedInfo(result: Transaction | null) { describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async function () { this.timeout(10000); - let { restMock, web3Mock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const { + restMock, + hapiServiceInstance, + ethImpl, + cacheService, + }: { restMock: MockAdapter; hapiServiceInstance: HAPIService; ethImpl: Eth; cacheService: CacheService } = + generateEthTestEnv(); + + const requestDetails = new RequestDetails({ + requestId: 'eth_getTransactionByBlockHashAndIndexTest', + ipAddress: '0.0.0.0', + }); - this.beforeEach(() => { + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); @@ -100,7 +112,11 @@ describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async functio restMock .onGet(`contracts/${CONTRACT_ADDRESS_1}/results/${CONTRACT_TIMESTAMP_1}`) .reply(200, defaultDetailedContractResults); - const result = await ethImpl.getTransactionByBlockHashAndIndex(DEFAULT_BLOCK.hash, numberTo0x(DEFAULT_BLOCK.count)); + const result = await ethImpl.getTransactionByBlockHashAndIndex( + DEFAULT_BLOCK.hash, + numberTo0x(DEFAULT_BLOCK.count), + requestDetails, + ); expect(result).to.exist; expect(result).to.not.be.null; @@ -118,7 +134,7 @@ describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async functio .onGet(contractResultsByHashByIndexURL(randomBlock.hash, randomBlock.count)) .reply(200, defaultContractResultsWithNullableFrom); - const args = [randomBlock.hash, numberTo0x(randomBlock.count)]; + const args = [randomBlock.hash, numberTo0x(randomBlock.count), requestDetails]; const errMessage = "Cannot read properties of null (reading 'substring')"; await RelayAssertions.assertRejection( @@ -137,6 +153,7 @@ describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async functio const result = await ethImpl.getTransactionByBlockHashAndIndex( DEFAULT_BLOCK.hash.toString(), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); expect(result).to.equal(null); }); @@ -147,6 +164,7 @@ describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async functio const result = await ethImpl.getTransactionByBlockHashAndIndex( DEFAULT_BLOCK.hash.toString(), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); expect(result).to.equal(null); }); @@ -164,6 +182,7 @@ describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async functio const result = await ethImpl.getTransactionByBlockHashAndIndex( DEFAULT_BLOCK.hash.toString(), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); expect(result).to.be.an.instanceOf(Transaction); }); @@ -182,6 +201,7 @@ describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async functio const result = await ethImpl.getTransactionByBlockHashAndIndex( DEFAULT_BLOCK.hash.toString(), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); expect(result).to.be.an.instanceOf(Transaction2930); }); @@ -202,6 +222,7 @@ describe('@ethGetTransactionByBlockHashAndIndex using MirrorNode', async functio const result = await ethImpl.getTransactionByBlockHashAndIndex( DEFAULT_BLOCK.hash.toString(), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); expect(result).to.be.an.instanceOf(Transaction1559); }); diff --git a/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts index 6578717e47..82c274e719 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts @@ -24,7 +24,7 @@ import sinon from 'sinon'; import * as _ from 'lodash'; import chaiAsPromised from 'chai-as-promised'; -import { predefined } from '../../../src/lib/errors/JsonRpcError'; +import { Eth, predefined } from '../../../src'; import { defaultContractResults, defaultDetailedContractResults } from '../../helpers'; import { Transaction } from '../../../src/lib/model'; import { SDKClient } from '../../../src/lib/clients'; @@ -43,12 +43,16 @@ import { NOT_FOUND_RES, } from './eth-config'; import { contractResultsByNumberByIndexURL, generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; +import HAPIService from '../../../src/lib/services/hapiService/hapiService'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; function verifyAggregatedInfo(result: Transaction | null) { // verify aggregated info @@ -64,13 +68,23 @@ function verifyAggregatedInfo(result: Transaction | null) { describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async function () { this.timeout(10000); - let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const { + restMock, + hapiServiceInstance, + ethImpl, + cacheService, + }: { restMock: MockAdapter; hapiServiceInstance: HAPIService; ethImpl: Eth; cacheService: CacheService } = + generateEthTestEnv(); + + const requestDetails = new RequestDetails({ + requestId: 'eth_getTransactionByBlockNumberAndIndexTest', + ipAddress: '0.0.0.0', + }); this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); @@ -103,6 +117,7 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct const result = await ethImpl.getTransactionByBlockNumberAndIndex( numberTo0x(DEFAULT_BLOCK.number), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); verifyAggregatedInfo(result); @@ -123,6 +138,7 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct const result = await ethImpl.getTransactionByBlockNumberAndIndex( numberTo0x(randomBlock.number), numberTo0x(randomBlock.count), + requestDetails, ); expect(result).to.exist; expect(result).to.not.be.null; @@ -141,6 +157,7 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct const result = await ethImpl.getTransactionByBlockNumberAndIndex( numberTo0x(DEFAULT_BLOCK.number), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); expect(result).to.equal(null); }); @@ -156,7 +173,7 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct .onGet(contractResultsByNumberByIndexURL(randomBlock.number, randomBlock.count)) .reply(200, defaultContractResultsWithNullableFrom); - const args = [numberTo0x(randomBlock.number), numberTo0x(randomBlock.count)]; + const args = [numberTo0x(randomBlock.number), numberTo0x(randomBlock.count), requestDetails]; const errMessage = "Cannot read properties of null (reading 'substring')"; await RelayAssertions.assertRejection( @@ -176,6 +193,7 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct const result = await ethImpl.getTransactionByBlockNumberAndIndex( numberTo0x(DEFAULT_BLOCK.number), numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); expect(result).to.equal(null); }); @@ -187,7 +205,11 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct .onGet(contractResultsByNumberByIndexURL(DEFAULT_BLOCK.number, DEFAULT_BLOCK.count)) .reply(200, defaultContractResults); - const result = await ethImpl.getTransactionByBlockNumberAndIndex('latest', numberTo0x(DEFAULT_BLOCK.count)); + const result = await ethImpl.getTransactionByBlockNumberAndIndex( + 'latest', + numberTo0x(DEFAULT_BLOCK.count), + requestDetails, + ); verifyAggregatedInfo(result); }); @@ -198,7 +220,11 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct .onGet(contractResultsByNumberByIndexURL(DEFAULT_BLOCK.number, DEFAULT_BLOCK.count)) .reply(200, defaultContractResults); - const result = await ethImpl.getTransactionByBlockNumberAndIndex('finalized', numberTo0x(DEFAULT_BLOCK.count)); + const result = await ethImpl.getTransactionByBlockNumberAndIndex( + 'finalized', + numberTo0x(DEFAULT_BLOCK.count), + requestDetails, + ); verifyAggregatedInfo(result); }); @@ -209,7 +235,11 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct .onGet(contractResultsByNumberByIndexURL(DEFAULT_BLOCK.number, DEFAULT_BLOCK.count)) .reply(200, defaultContractResults); - const result = await ethImpl.getTransactionByBlockNumberAndIndex('safe', numberTo0x(DEFAULT_BLOCK.count)); + const result = await ethImpl.getTransactionByBlockNumberAndIndex( + 'safe', + numberTo0x(DEFAULT_BLOCK.count), + requestDetails, + ); verifyAggregatedInfo(result); }); @@ -220,7 +250,11 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct .onGet(contractResultsByNumberByIndexURL(DEFAULT_BLOCK.number, DEFAULT_BLOCK.count)) .reply(200, defaultContractResults); - const result = await ethImpl.getTransactionByBlockNumberAndIndex('pending', numberTo0x(DEFAULT_BLOCK.count)); + const result = await ethImpl.getTransactionByBlockNumberAndIndex( + 'pending', + numberTo0x(DEFAULT_BLOCK.count), + requestDetails, + ); verifyAggregatedInfo(result); }); @@ -228,7 +262,11 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct // mirror node request mocks restMock.onGet(contractResultsByNumberByIndexURL(0, DEFAULT_BLOCK.count)).reply(200, defaultContractResults); - const result = await ethImpl.getTransactionByBlockNumberAndIndex('earliest', numberTo0x(DEFAULT_BLOCK.count)); + const result = await ethImpl.getTransactionByBlockNumberAndIndex( + 'earliest', + numberTo0x(DEFAULT_BLOCK.count), + requestDetails, + ); verifyAggregatedInfo(result); }); @@ -240,6 +278,7 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct const result = await ethImpl.getTransactionByBlockNumberAndIndex( '0xdeadc0de' + '', numberTo0x(DEFAULT_BLOCK.count), + requestDetails, ); verifyAggregatedInfo(result); }); diff --git a/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts index 4b4106a225..3b3b93d942 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts @@ -22,11 +22,8 @@ import dotenv from 'dotenv'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { EthImpl } from '../../../src/lib/eth'; -import { Log, Transaction, Transaction2930, Transaction1559 } from '../../../src/lib/model'; -import constants from '../../../src/lib/constants'; +import { Transaction, Transaction2930, Transaction1559 } from '../../../src/lib/model'; import RelayAssertions from '../../assertions'; -import { nullableNumberTo0x, numberTo0x, toHash32 } from '../../../src/formatters'; import { DEFAULT_DETAILED_CONTRACT_RESULT_BY_HASH_REVERTED, DEFAULT_TRANSACTION, @@ -35,15 +32,15 @@ import { EMPTY_LOGS_RESPONSE, NO_TRANSACTIONS, } from './eth-config'; -import { defaultDetailedContractResultByHash, defaultFromLongZeroAddress, defaultLogs1 } from '../../helpers'; -import { predefined } from '../../../src'; +import { defaultDetailedContractResultByHash, defaultFromLongZeroAddress } from '../../helpers'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async function () { - let { restMock, ethImpl, cacheService } = generateEthTestEnv(); + let { restMock, ethImpl } = generateEthTestEnv(); const from = '0x00000000000000000000000000000000000003f7'; const evm_address = '0xc37f417fa09933335240fca72dd257bfbde9c275'; const contractResultMock = { @@ -79,6 +76,8 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi nonce: 9, }; + const requestDetails = new RequestDetails({ requestId: 'eth_getTransactionByHashTest', ipAddress: '0.0.0.0' }); + this.beforeEach(function () { restMock.reset(); restMock.onGet(`accounts/${defaultFromLongZeroAddress}${NO_TRANSACTIONS}`).reply(200, { @@ -96,7 +95,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi type: 0, }); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.be.an.instanceOf(Transaction); }); @@ -108,7 +107,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi access_list: [], }); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.be.an.instanceOf(Transaction2930); }); @@ -122,7 +121,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi max_priority_fee_per_gas: '0x47', }); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.be.an.instanceOf(Transaction1559); }); @@ -133,21 +132,21 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi .onGet(`contracts/results/logs?transaction.hash=${uniqueTxHash}&limit=100&order=asc`) .reply(200, EMPTY_LOGS_RESPONSE); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.equal(null); }); it('account should be cached', async function () { restMock.onGet(`contracts/results/${DEFAULT_TX_HASH}`).reply(200, defaultDetailedContractResultByHash); - const resBeforeCache = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH); + const resBeforeCache = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH, requestDetails); restMock.onGet(`accounts/${defaultFromLongZeroAddress}${NO_TRANSACTIONS}`).reply(404); - const resAfterCache = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH); + const resAfterCache = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH, requestDetails); expect(resBeforeCache).to.deep.equal(resAfterCache); }); it('returns correct transaction for existing hash', async function () { restMock.onGet(`contracts/results/${DEFAULT_TX_HASH}`).reply(200, defaultDetailedContractResultByHash); - const result = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH); + const result = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH, requestDetails); RelayAssertions.assertTransaction(result, { ...DEFAULT_TRANSACTION, maxFeePerGas: '0x55', @@ -165,7 +164,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi const uniqueTxHash = '0x97cad7b827375d12d73af57b6a3f84353645fd31305ea58ff52dda53ec640533'; restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, detailedResultsWithZeroXZeroValues); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); RelayAssertions.assertTransaction(result, { ...DEFAULT_TRANSACTION, maxFeePerGas: '0x55', @@ -183,7 +182,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi const uniqueTxHash = '0x14aad7b827375d12d73af57b6a3e84353645fd31305ea58ff52dda53ec640533'; restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, detailedResultsWithNullNullableValues); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.not.be.null; expect(result).to.exist; @@ -198,7 +197,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi const uniqueTxHash = '0x0aaad7b827375d12d73af57b6a3e84353645fd31305ea58ff52dda53ec640533'; restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, detailedResultsWithNullNullableValues); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.not.be.null; expect(result).to.exist; @@ -214,7 +213,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi const uniqueTxHash = '0xb4cad7b827375d12d73af57b6a3e84353645fd31305ea58ff52dda53ec640533'; restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, detailedResultsWithNullNullableValues); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.not.be.null; expect(result).to.exist; @@ -229,7 +228,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi const uniqueTxHash = '0x14aad7b827375d12d73af57b6a3e84353645fd31305ea58ff52dda53ec640534'; restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, detailedResultsWithNullNullableValues); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.not.be.null; expect(result).to.exist; @@ -244,7 +243,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi const uniqueTxHash = '0x14aad7b827375d12d73af57b6a3e84353645fd31305ea58ff52dda53ec640511'; restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, detailedResultsWithNullNullableValues); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.not.be.null; expect(result).to.exist; @@ -261,7 +260,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi const uniqueTxHash = '0x14aad7b827375d12d73af57b6a3e84353645fd31305ea58ff52d1a53ec640511'; restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, detailedResultsWithNullNullableValues); - const result = await ethImpl.getTransactionByHash(uniqueTxHash); + const result = await ethImpl.getTransactionByHash(uniqueTxHash, requestDetails); expect(result).to.not.be.null; expect(result).to.exist; @@ -276,7 +275,7 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi .onGet(`contracts/results/${DEFAULT_TX_HASH}`) .reply(200, DEFAULT_DETAILED_CONTRACT_RESULT_BY_HASH_REVERTED); - const result = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH); + const result = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH, requestDetails); RelayAssertions.assertTransaction(result, { ...DEFAULT_TRANSACTION, maxFeePerGas: '0x55', diff --git a/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts index 7687a92249..8beeda137a 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts @@ -27,22 +27,34 @@ import { EthImpl } from '../../../src/lib/eth'; import constants from '../../../src/lib/constants'; import { SDKClient } from '../../../src/lib/clients'; import { DEFAULT_NETWORK_FEES, NO_TRANSACTIONS } from './eth-config'; -import { predefined } from '../../../src/lib/errors/JsonRpcError'; +import { Eth, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; import { defaultDetailedContractResults, defaultEthereumTransactions, mockData } from '../../helpers'; import { numberTo0x } from '../../../src/formatters'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; +import HAPIService from '../../../src/lib/services/hapiService/hapiService'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethGetTransactionCount eth_getTransactionCount spec', async function () { this.timeout(10000); - let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const { + restMock, + hapiServiceInstance, + ethImpl, + cacheService, + }: { restMock: MockAdapter; hapiServiceInstance: HAPIService; ethImpl: Eth; cacheService: CacheService } = + generateEthTestEnv(); + + const requestDetails = new RequestDetails({ requestId: 'eth_getTransactionCountTest', ipAddress: '0.0.0.0' }); const blockNumber = mockData.blocks.blocks[2].number; const blockNumberHex = numberTo0x(blockNumber); const transactionId = '0.0.1078@1686183420.196506746'; @@ -56,8 +68,8 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function const blockPath = `blocks/${blockNumber}`; const latestBlockPath = `blocks?limit=1&order=desc`; - function transactionPath(addresss, num) { - return `accounts/${addresss}?transactiontype=ETHEREUMTRANSACTION×tamp=lte:${mockData.blocks.blocks[2].timestamp.to}&limit=${num}&order=desc`; + function transactionPath(address: string, num: number) { + return `accounts/${address}?transactiontype=ETHEREUMTRANSACTION×tamp=lte:${mockData.blocks.blocks[2].timestamp.to}&limit=${num}&order=desc`; } this.beforeEach(() => { @@ -84,7 +96,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function restMock.resetHandlers(); process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); }); @@ -96,7 +108,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should return 0x0 nonce for no block consideration with not found acoount', async () => { restMock.onGet(contractPath).reply(404, mockData.notFound); restMock.onGet(accountPath).reply(404, mockData.notFound); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, null); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, null, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.zeroHex); }); @@ -104,63 +116,63 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should return latest nonce for no block consideration but valid account', async () => { restMock.onGet(contractPath).reply(404, mockData.notFound); restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, null); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, null, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); }); it('should return 0x0 nonce for block 0 consideration', async () => { restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, '0'); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, '0', requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.zeroHex); }); it('should return 0x0 nonce for block 1 consideration', async () => { restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, '1'); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, '1', requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.zeroHex); }); it('should return latest nonce for latest block', async () => { restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockLatest); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockLatest, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); }); it('should return latest nonce for finalized block', async () => { restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockFinalized); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockFinalized, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); }); it('should return latest nonce for latest block', async () => { restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockSafe); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockSafe, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); }); it('should return latest nonce for pending block', async () => { restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockPending); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockPending, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); }); it('should return 0x0 nonce for earliest block with valid block', async () => { restMock.onGet(earliestBlockPath).reply(200, { blocks: [mockData.blocks.blocks[0]] }); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockEarliest); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockEarliest, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.zeroHex); }); it('should throw error for earliest block with invalid block', async () => { restMock.onGet(earliestBlockPath).reply(200, { blocks: [] }); - const args = [MOCK_ACCOUNT_ADDR, EthImpl.blockEarliest]; + const args = [MOCK_ACCOUNT_ADDR, EthImpl.blockEarliest, requestDetails]; await RelayAssertions.assertRejection( predefined.INTERNAL_ERROR('No network blocks found'), @@ -174,7 +186,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should throw error for earliest block with non 0 or 1 block', async () => { restMock.onGet(earliestBlockPath).reply(200, { blocks: [mockData.blocks.blocks[2]] }); - const args = [MOCK_ACCOUNT_ADDR, EthImpl.blockEarliest]; + const args = [MOCK_ACCOUNT_ADDR, EthImpl.blockEarliest, requestDetails]; const errMessage = `Partial mirror node encountered, earliest block number is ${mockData.blocks.blocks[2].number}`; @@ -199,7 +211,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function .onGet(accountPathContractResultsAddress) .reply(200, { ...mockData.account, transactions: [defaultEthereumTransactions[0]] }); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(`0x${defaultDetailedContractResults.nonce + 1}`); }); @@ -207,7 +219,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should throw error for account historical numerical block tag with missing block', async () => { restMock.onGet(blockPath).reply(404, mockData.notFound); - const args = [MOCK_ACCOUNT_ADDR, blockNumberHex]; + const args = [MOCK_ACCOUNT_ADDR, blockNumberHex, requestDetails]; await RelayAssertions.assertRejection(predefined.UNKNOWN_BLOCK(), ethImpl.getTransactionCount, true, ethImpl, args); }); @@ -216,7 +228,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function restMock.onGet(blockPath).reply(404, mockData.notFound); restMock.onGet(latestBlockPath).reply(404, mockData.notFound); - const args = [MOCK_ACCOUNT_ADDR, blockNumberHex]; + const args = [MOCK_ACCOUNT_ADDR, blockNumberHex, requestDetails]; await RelayAssertions.assertRejection(predefined.UNKNOWN_BLOCK(), ethImpl.getTransactionCount, true, ethImpl, args); }); @@ -232,7 +244,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function }); restMock.onGet(accountPath).reply(200, mockData.account); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); }); @@ -240,7 +252,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should return 0x0 nonce for historical numerical block with no ethereum transactions found', async () => { restMock.onGet(transactionPath(MOCK_ACCOUNT_ADDR, 2)).reply(200, { transactions: [] }); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.zeroHex); }); @@ -248,7 +260,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should return 0x1 nonce for historical numerical block with a single ethereum transactions found', async () => { restMock.onGet(transactionPath(MOCK_ACCOUNT_ADDR, 2)).reply(200, { transactions: [{}] }); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockNumberHex, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.oneHex); }); @@ -259,7 +271,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function .reply(200, { transactions: [{ transaction_id: transactionId }, {}] }); restMock.onGet(contractResultsPath).reply(404, mockData.notFound); - const args = [MOCK_ACCOUNT_ADDR, blockNumberHex]; + const args = [MOCK_ACCOUNT_ADDR, blockNumberHex, requestDetails]; const errMessage = `Failed to retrieve contract results for transaction ${transactionId}`; await RelayAssertions.assertRejection( @@ -279,7 +291,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function .onGet(accountPathContractResultsAddress) .reply(200, { ...mockData.account, transactions: [defaultEthereumTransactions[0]] }); - const nonce = await ethImpl.getTransactionCount(mockData.account.evm_address, blockNumberHex); + const nonce = await ethImpl.getTransactionCount(mockData.account.evm_address, blockNumberHex, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(3)); }); @@ -292,26 +304,26 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function restMock .onGet(accountPathContractResultsAddress) .reply(200, { ...mockData.account, transactions: [defaultEthereumTransactions[0]] }); - const nonce = await ethImpl.getTransactionCount(mockData.account.evm_address, blockNumberHex); + const nonce = await ethImpl.getTransactionCount(mockData.account.evm_address, blockNumberHex, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); }); it('should throw for -1 invalid block tag', async () => { - const args = [MOCK_ACCOUNT_ADDR, '-1']; + const args = [MOCK_ACCOUNT_ADDR, '-1', requestDetails]; await RelayAssertions.assertRejection(predefined.UNKNOWN_BLOCK(), ethImpl.getTransactionCount, true, ethImpl, args); }); it('should throw for invalid block tag', async () => { - const args = [MOCK_ACCOUNT_ADDR, 'notablock']; + const args = [MOCK_ACCOUNT_ADDR, 'notablock', requestDetails]; await RelayAssertions.assertRejection(predefined.UNKNOWN_BLOCK(), ethImpl.getTransactionCount, true, ethImpl, args); }); it('should return 0x1 for pre-hip-729 contracts with nonce=null', async () => { restMock.onGet(accountPath).reply(200, { ...mockData.account, ethereum_nonce: null }); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockLatest); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, EthImpl.blockLatest, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.oneHex); }); @@ -325,7 +337,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function restMock .onGet(accountPathContractResultsAddress) .reply(200, { ...mockData.account, transactions: [defaultEthereumTransactions[0]] }); - const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockHash); + const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, blockHash, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(2)); }); diff --git a/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts index 3df66deddc..4676593171 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts @@ -29,6 +29,7 @@ import RelayAssertions from '../../assertions'; import { DEFAULT_BLOCK, EMPTY_LOGS_RESPONSE } from './eth-config'; import { defaultErrorMessageHex } from '../../helpers'; import { generateEthTestEnv } from './eth-helpers'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); @@ -38,6 +39,8 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func let { restMock, ethImpl, cacheService } = generateEthTestEnv(); let sandbox: sinon.SinonSandbox; + const requestDetails = new RequestDetails({ requestId: 'eth_getTransactionReceiptTest', ipAddress: '0.0.0.0' }); + this.beforeAll(() => { // @ts-ignore sandbox = createSandbox(); @@ -140,7 +143,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func this.afterEach(() => { restMock.resetHandlers(); sandbox.restore(); - cacheService.clear(); + cacheService.clear(requestDetails); }); it('returns `null` for non-existent hash', async function () { @@ -157,7 +160,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func restMock .onGet(`contracts/results/logs?transaction.hash=${txHash}&limit=100&order=asc`) .reply(200, EMPTY_LOGS_RESPONSE); - const receipt = await ethImpl.getTransactionReceipt(txHash); + const receipt = await ethImpl.getTransactionReceipt(txHash, requestDetails); expect(receipt).to.be.null; }); @@ -174,9 +177,9 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func restMock.onGet(`contracts/results/${defaultTxHash}`).reply(200, defaultDetailedContractResultByHash); restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404); stubBlockAndFeesFunc(sandbox); - const receipt = await ethImpl.getTransactionReceipt(defaultTxHash); + const receipt = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails); - const currentGasPrice = await ethImpl.gasPrice('valid receipt on match TEST'); + const currentGasPrice = await ethImpl.gasPrice(requestDetails); // Assert the data format RelayAssertions.assertTransactionReceipt(receipt, defaultReceipt, { @@ -189,7 +192,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).replyOnce(404); stubBlockAndFeesFunc(sandbox); for (let i = 0; i < 3; i++) { - const receipt = await ethImpl.getTransactionReceipt(defaultTxHash); + const receipt = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails); expect(receipt).to.exist; if (receipt == null) return; expect(RelayAssertions.validateHash(receipt.transactionHash, 64)).to.eq(true); @@ -207,7 +210,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func evm_address: contractEvmAddress, }); stubBlockAndFeesFunc(sandbox); - const receipt = await ethImpl.getTransactionReceipt(defaultTxHash); + const receipt = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails); expect(receipt).to.exist; if (receipt == null) return; @@ -230,7 +233,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, contractResult); restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404); stubBlockAndFeesFunc(sandbox); - const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash); + const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash, requestDetails); expect(receipt).to.exist; if (receipt == null) return; @@ -247,7 +250,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func restMock.onGet(`contracts/results/${defaultTxHash}`).reply(200, receiptWith0xBloom); restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404); stubBlockAndFeesFunc(sandbox); - const receipt = await ethImpl.getTransactionReceipt(defaultTxHash); + const receipt = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails); expect(receipt).to.exist; if (receipt == null) return; @@ -267,7 +270,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, receiptWithErrorMessage); restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404); stubBlockAndFeesFunc(sandbox); - const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash); + const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash, requestDetails); expect(receipt).to.exist; expect(receipt.revertReason).to.eq(defaultErrorMessageHex); @@ -284,7 +287,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, receiptWithNullGasUsed); restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404); stubBlockAndFeesFunc(sandbox); - const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash); + const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash, requestDetails); expect(receipt).to.exist; if (receipt == null) return; @@ -306,7 +309,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func evm_address: contractEvmAddress, }); stubBlockAndFeesFunc(sandbox); - const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash); + const receipt = await ethImpl.getTransactionReceipt(uniqueTxHash, requestDetails); expect(receipt).to.exist; @@ -332,10 +335,10 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func type: defaultDetailedContractResultByHash.type, }; - cacheService.set(cacheKey, cacheReceipt, EthImpl.ethGetTransactionReceipt); + await cacheService.set(cacheKey, cacheReceipt, EthImpl.ethGetTransactionReceipt, requestDetails); // w no mirror node requests - const receipt = await ethImpl.getTransactionReceipt(defaultTxHash); + const receipt = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails); // Assert the matching reciept expect(receipt.blockHash).to.eq(cacheReceipt.blockHash); diff --git a/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts b/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts index 7a0ac92fdc..6b84daed42 100644 --- a/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts +++ b/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts @@ -22,32 +22,34 @@ import dotenv from 'dotenv'; import { expect, use } from 'chai'; import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; -import { Hbar, HbarUnit, TransactionId } from '@hashgraph/sdk'; +import { Hbar, HbarUnit, TransactionId, TransactionResponse } from '@hashgraph/sdk'; import { SDKClient } from '../../../src/lib/clients'; import { ACCOUNT_ADDRESS_1, DEFAULT_NETWORK_FEES, MAX_GAS_LIMIT_HEX, NO_TRANSACTIONS } from './eth-config'; -import { JsonRpcError, predefined } from '../../../src/lib/errors/JsonRpcError'; +import { JsonRpcError, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; import { getRequestId, mockData, signTransaction } from '../../helpers'; import { generateEthTestEnv } from './eth-helpers'; import { SDKClientError } from '../../../src/lib/errors/SDKClientError'; +import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub; +let sdkClientStub: sinon.SinonStubbedInstance; +let getSdkClientStub: sinon.SinonStub; let currentMaxBlockRange: number; describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () { this.timeout(10000); let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); + const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); + this.beforeEach(() => { // reset cache and restMock - cacheService.clear(); + cacheService.clear(requestDetails); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); @@ -93,7 +95,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () }, }; - this.beforeEach(() => { + beforeEach(() => { sinon.restore(); sdkClientStub = sinon.createStubInstance(SDKClient); sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); @@ -101,7 +103,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () restMock.onGet(networkExchangeRateEndpoint).reply(200, mockedExchangeRate); }); - this.afterEach(() => { + afterEach(() => { sinon.restore(); }); @@ -114,7 +116,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () ethImpl.sendRawTransaction, false, ethImpl, - [txHash, getRequestId()], + [txHash, requestDetails], ); }); @@ -123,7 +125,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () restMock.onGet(`transactions/${transactionId}`).reply(200, null); - const resultingHash = await ethImpl.sendRawTransaction(signed, getRequestId()); + const resultingHash = await ethImpl.sendRawTransaction(signed, requestDetails); expect(resultingHash).to.equal(ethereumHash); }); @@ -133,14 +135,14 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () restMock.onGet(contractResultEndpoint).reply(404, mockData.notFound); restMock.onGet(`transactions/${transactionId}?nonce=0`).reply(200, null); - sdkClientStub.submitEthereumTransaction.returns({ + sdkClientStub.submitEthereumTransaction.resolves({ txResponse: { transactionId: '', - }, + } as unknown as TransactionResponse, fileId: null, }); - const response = (await ethImpl.sendRawTransaction(signed, getRequestId())) as JsonRpcError; + const response = (await ethImpl.sendRawTransaction(signed, requestDetails)) as JsonRpcError; expect(response.code).to.equal(predefined.INTERNAL_ERROR().code); expect(`Error invoking RPC: ${response.message}`).to.equal(predefined.INTERNAL_ERROR(response.message).message); @@ -151,14 +153,14 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () restMock.onGet(contractResultEndpoint).reply(200, { hash: ethereumHash }); - sdkClientStub.submitEthereumTransaction.returns({ + sdkClientStub.submitEthereumTransaction.resolves({ txResponse: { transactionId: '', - }, + } as unknown as TransactionResponse, fileId: null, }); - const response = (await ethImpl.sendRawTransaction(signed, getRequestId())) as JsonRpcError; + const response = (await ethImpl.sendRawTransaction(signed, requestDetails)) as JsonRpcError; expect(response.code).to.equal(predefined.INTERNAL_ERROR().code); expect(`Error invoking RPC: ${response.message}`).to.equal(predefined.INTERNAL_ERROR(response.message).message); @@ -167,31 +169,31 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () it('should return hash from ContractResult mirror node api', async function () { restMock.onGet(contractResultEndpoint).reply(200, { hash: ethereumHash }); - sdkClientStub.submitEthereumTransaction.returns({ + sdkClientStub.submitEthereumTransaction.resolves({ txResponse: { transactionId: TransactionId.fromString(transactionIdServicesFormat), - }, + } as unknown as TransactionResponse, fileId: null, }); const signed = await signTransaction(transaction); - const resultingHash = await ethImpl.sendRawTransaction(signed, getRequestId()); + const resultingHash = await ethImpl.sendRawTransaction(signed, requestDetails); expect(resultingHash).to.equal(ethereumHash); }); it('should not send second transaction upon succession', async function () { restMock.onGet(contractResultEndpoint).reply(200, { hash: ethereumHash }); - sdkClientStub.submitEthereumTransaction.returns({ + sdkClientStub.submitEthereumTransaction.resolves({ txResponse: { transactionId: TransactionId.fromString(transactionIdServicesFormat), - }, + } as unknown as TransactionResponse, fileId: null, }); const signed = await signTransaction(transaction); - const resultingHash = await ethImpl.sendRawTransaction(signed, getRequestId()); + const resultingHash = await ethImpl.sendRawTransaction(signed, requestDetails); expect(resultingHash).to.equal(ethereumHash); sinon.assert.calledOnce(sdkClientStub.submitEthereumTransaction); }); @@ -203,7 +205,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () const signed = await signTransaction(transaction); - const response = (await ethImpl.sendRawTransaction(signed, getRequestId())) as JsonRpcError; + const response = (await ethImpl.sendRawTransaction(signed, requestDetails)) as JsonRpcError; expect(response.code).to.equal(predefined.INTERNAL_ERROR().code); expect(`Error invoking RPC: ${response.message}`).to.equal(predefined.INTERNAL_ERROR(response.message).message); sinon.assert.calledOnce(sdkClientStub.submitEthereumTransaction); diff --git a/packages/relay/tests/lib/hapiService.spec.ts b/packages/relay/tests/lib/hapiService.spec.ts index 1aeed76d36..856d26d04e 100644 --- a/packages/relay/tests/lib/hapiService.spec.ts +++ b/packages/relay/tests/lib/hapiService.spec.ts @@ -29,6 +29,7 @@ import { SDKClient } from '../../src/lib/clients'; import HbarLimit from '../../src/lib/hbarlimiter'; import HAPIService from '../../src/lib/services/hapiService/hapiService'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; +import { RequestDetails } from '../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -43,6 +44,7 @@ describe('HAPI Service', async function () { let hapiService: HAPIService; const errorStatus = 50; + const requestDetails = new RequestDetails({ requestId: 'hapiService.spec.ts', ipAddress: '0.0.0.0' }); this.beforeAll(() => { const duration: number = 60000; @@ -147,7 +149,7 @@ describe('HAPI Service', async function () { const oldClientInstance = hapiService.getMainClientInstance(); const oldSDKInstance = hapiService.getSDKClient(); - hbarLimiter.addExpense(costAmount, Date.now()); + hbarLimiter.addExpense(costAmount, Date.now(), requestDetails); hapiService.decrementErrorCounter(errorStatus); const newSDKInstance = hapiService.getSDKClient(); diff --git a/packages/relay/tests/lib/hbarLimiter.spec.ts b/packages/relay/tests/lib/hbarLimiter.spec.ts index 4e02e478ae..2d1e45f35c 100644 --- a/packages/relay/tests/lib/hbarLimiter.spec.ts +++ b/packages/relay/tests/lib/hbarLimiter.spec.ts @@ -23,6 +23,7 @@ import { expect } from 'chai'; import { Registry } from 'prom-client'; import HbarLimit from '../../src/lib/hbarlimiter'; import { estimateFileTransactionsFee, getRequestId, random20BytesAddress } from '../helpers'; +import { RequestDetails } from '../../src/lib/types'; const registry = new Registry(); const logger = pino(); @@ -42,6 +43,7 @@ describe('HBAR Rate Limiter', async function () { const randomAccountAddress = random20BytesAddress(); const randomWhiteListedAccountAddress = random20BytesAddress(); const fileChunkSize = Number(process.env.FILE_APPEND_CHUNK_SIZE) || 5120; + const requestDetails = new RequestDetails({ requestId: getRequestId(), ipAddress: '0.0.0.0' }); this.beforeEach(() => { currentDateNow = Date.now(); @@ -67,8 +69,9 @@ describe('HBAR Rate Limiter', async function () { 'QUERY', 'eth_call', randomAccountAddress, + requestDetails, ); - rateLimiterWithEmptyBudget.addExpense(validTotal, currentDateNow); + rateLimiterWithEmptyBudget.addExpense(validTotal, currentDateNow, requestDetails); expect(isEnabled).to.equal(false); expect(shouldRateLimit).to.equal(false); @@ -82,8 +85,14 @@ describe('HBAR Rate Limiter', async function () { const isEnabled = rateLimiter.isEnabled(); const limiterResetTime = rateLimiter.getResetTime(); const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - const shouldRateLimit = rateLimiter.shouldLimit(currentDateNow, 'QUERY', 'eth_call', randomAccountAddress); - rateLimiter.addExpense(validTotal, currentDateNow); + const shouldRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'QUERY', + 'eth_call', + randomAccountAddress, + requestDetails, + ); + rateLimiter.addExpense(validTotal, currentDateNow, requestDetails); expect(isEnabled).to.equal(false); expect(shouldRateLimit).to.equal(false); @@ -97,8 +106,14 @@ describe('HBAR Rate Limiter', async function () { const isEnabled = invalidRateLimiter.isEnabled(); const limiterResetTime = invalidRateLimiter.getResetTime(); const limiterRemainingBudget = invalidRateLimiter.getRemainingBudget(); - const shouldRateLimit = invalidRateLimiter.shouldLimit(currentDateNow, 'QUERY', 'eth_call', randomAccountAddress); - invalidRateLimiter.addExpense(validTotal, currentDateNow); + const shouldRateLimit = invalidRateLimiter.shouldLimit( + currentDateNow, + 'QUERY', + 'eth_call', + randomAccountAddress, + requestDetails, + ); + invalidRateLimiter.addExpense(validTotal, currentDateNow, requestDetails); expect(isEnabled).to.equal(false); expect(shouldRateLimit).to.equal(false); @@ -110,7 +125,13 @@ describe('HBAR Rate Limiter', async function () { const isEnabled = rateLimiter.isEnabled(); const limiterResetTime = rateLimiter.getResetTime(); const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - const shouldRateLimit = rateLimiter.shouldLimit(currentDateNow, 'QUERY', 'eth_call', randomAccountAddress); + const shouldRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'QUERY', + 'eth_call', + randomAccountAddress, + requestDetails, + ); expect(isEnabled).to.equal(true); expect(shouldRateLimit).to.equal(false); @@ -121,7 +142,7 @@ describe('HBAR Rate Limiter', async function () { it('should not rate limit', async function () { const cost = 10000000; - rateLimiter.addExpense(cost, currentDateNow); + rateLimiter.addExpense(cost, currentDateNow, requestDetails); const isEnabled = rateLimiter.isEnabled(); const limiterResetTime = rateLimiter.getResetTime(); @@ -131,6 +152,7 @@ describe('HBAR Rate Limiter', async function () { 'TRANSACTION', 'eth_sendRawTransaction', randomAccountAddress, + requestDetails, ); expect(isEnabled).to.equal(true); @@ -142,7 +164,7 @@ describe('HBAR Rate Limiter', async function () { it('should rate limit', async function () { const cost = 1000000000; - rateLimiter.addExpense(cost, currentDateNow); + rateLimiter.addExpense(cost, currentDateNow, requestDetails); const isEnabled = rateLimiter.isEnabled(); const limiterResetTime = rateLimiter.getResetTime(); @@ -152,6 +174,7 @@ describe('HBAR Rate Limiter', async function () { 'TRANSACTION', 'eth_sendRawTransaction', randomAccountAddress, + requestDetails, ); expect(isEnabled).to.equal(true); @@ -163,7 +186,7 @@ describe('HBAR Rate Limiter', async function () { it('should reset budget, while checking if we should rate limit', async function () { const cost = 1000000000; - rateLimiter.addExpense(cost, currentDateNow); + rateLimiter.addExpense(cost, currentDateNow, requestDetails); const isEnabled = rateLimiter.isEnabled(); const futureDate = currentDateNow + validDuration * 2; @@ -172,6 +195,7 @@ describe('HBAR Rate Limiter', async function () { 'TRANSACTION', 'eth_sendRawTransaction', randomAccountAddress, + requestDetails, ); const limiterResetTime = rateLimiter.getResetTime(); const limiterRemainingBudget = rateLimiter.getRemainingBudget(); @@ -185,21 +209,23 @@ describe('HBAR Rate Limiter', async function () { it('should reset budget, while adding expense', async function () { const cost = 1000000000; - rateLimiter.addExpense(cost, currentDateNow); + rateLimiter.addExpense(cost, currentDateNow, requestDetails); const shouldRateLimitBefore = rateLimiter.shouldLimit( currentDateNow, 'TRANSACTION', 'eth_sendRawTransaction', randomAccountAddress, + requestDetails, ); const futureDate = currentDateNow + validDuration * 2; - rateLimiter.addExpense(100, futureDate); + rateLimiter.addExpense(100, futureDate, requestDetails); const shouldRateLimitAfter = rateLimiter.shouldLimit( futureDate, 'TRANSACTION', 'eth_sendRawTransaction', randomAccountAddress, + requestDetails, ); const isEnabled = rateLimiter.isEnabled(); @@ -219,7 +245,7 @@ describe('HBAR Rate Limiter', async function () { callDataSize, fileChunkSize, mockedExchangeRateInCents, - getRequestId(), + requestDetails, ); expect(result).to.be.true; }); @@ -230,7 +256,7 @@ describe('HBAR Rate Limiter', async function () { callDataSize, fileChunkSize, mockedExchangeRateInCents, - getRequestId(), + requestDetails, ); expect(result).to.be.false; }); @@ -245,7 +271,7 @@ describe('HBAR Rate Limiter', async function () { it('should bypass rate limit if original caller is a white listed account', async function () { // add expense to rate limit throttle - rateLimiter.addExpense(validTotal, currentDateNow); + rateLimiter.addExpense(validTotal, currentDateNow, requestDetails); // should return true as `randomAccountAddress` is not white listed const shouldNOTByPassRateLimit = rateLimiter.shouldLimit( @@ -253,6 +279,7 @@ describe('HBAR Rate Limiter', async function () { 'TRANSACTION', 'eth_sendRawTransaction', randomAccountAddress, + requestDetails, ); // should return false as `randomWhiteListedAccountAddress` is white listed @@ -261,6 +288,7 @@ describe('HBAR Rate Limiter', async function () { 'TRANSACTION', 'eth_sendRawTransaction', randomWhiteListedAccountAddress, + requestDetails, ); expect(shouldByPassRateLimit).to.equal(false); @@ -273,7 +301,7 @@ describe('HBAR Rate Limiter', async function () { callDataSize, fileChunkSize, mockedExchangeRateInCents, - getRequestId(), + requestDetails, ); expect(result).to.be.false; }); @@ -284,7 +312,7 @@ describe('HBAR Rate Limiter', async function () { callDataSize, fileChunkSize, mockedExchangeRateInCents, - getRequestId(), + requestDetails, ); expect(result).to.be.true; }); diff --git a/packages/relay/tests/lib/mirrorNodeClient.spec.ts b/packages/relay/tests/lib/mirrorNodeClient.spec.ts index 88172e44b8..f016084f4e 100644 --- a/packages/relay/tests/lib/mirrorNodeClient.spec.ts +++ b/packages/relay/tests/lib/mirrorNodeClient.spec.ts @@ -23,26 +23,30 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; import { Registry } from 'prom-client'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); -import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient'; +import { MirrorNodeClient } from '../../src/lib/clients'; import constants from '../../src/lib/constants'; -import axios from 'axios'; +import axios, { AxiosInstance } from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { getRequestId, mockData, random20BytesAddress } from './../helpers'; +import { getRequestId, mockData, random20BytesAddress } from '../helpers'; + const registry = new Registry(); import pino from 'pino'; import { ethers } from 'ethers'; -import { predefined } from '../../src/lib/errors/JsonRpcError'; -import { SDKClientError } from '../../src/lib/errors/SDKClientError'; +import { predefined, MirrorNodeClientError } from '../../src'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; -import { MirrorNodeClientError } from '../../src/lib/errors/MirrorNodeClientError'; +import { MirrorNodeTransactionRecord, RequestDetails } from '../../src/lib/types'; +import { SDKClientError } from '../../src/lib/errors/SDKClientError'; +import { BigNumber } from 'bignumber.js'; + const logger = pino(); const noTransactions = '?transactions=false'; +const requestDetails = new RequestDetails({ requestId: getRequestId(), ipAddress: '0.0.0.0' }); describe('MirrorNodeClient', async function () { this.timeout(20000); - let instance, mock, mirrorNodeInstance, cacheService; + let instance: AxiosInstance, mock: MockAdapter, mirrorNodeInstance: MirrorNodeClient, cacheService: CacheService; before(() => { // mock axios @@ -66,7 +70,7 @@ describe('MirrorNodeClient', async function () { beforeEach(() => { mock = new MockAdapter(instance); - mirrorNodeInstance.cacheService.clear(); + cacheService.clear(requestDetails); }); describe('handleError', async () => { @@ -79,12 +83,13 @@ describe('MirrorNodeClient', async function () { let error = new Error('test error'); error['response'] = 'test error'; - const result = await mirrorNodeInstance.handleError( + const result = mirrorNodeInstance.handleError( error, CONTRACT_CALL_ENDPOINT, CONTRACT_CALL_ENDPOINT, code, 'POST', + requestDetails, ); expect(result).to.equal(null); }); @@ -95,9 +100,16 @@ describe('MirrorNodeClient', async function () { try { let error = new Error('test error'); error['response'] = 'test error'; - await mirrorNodeInstance.handleError(error, CONTRACT_CALL_ENDPOINT, CONTRACT_CALL_ENDPOINT, code, 'POST'); + mirrorNodeInstance.handleError( + error, + CONTRACT_CALL_ENDPOINT, + CONTRACT_CALL_ENDPOINT, + code, + 'POST', + requestDetails, + ); expect.fail('should have thrown an error'); - } catch (e) { + } catch (e: any) { expect(e.message).to.equal('test error'); } }); @@ -107,20 +119,20 @@ describe('MirrorNodeClient', async function () { it('Can extract the account number out of an account pagination next link url', async () => { const accountId = '0.0.123'; const url = `/api/v1/accounts/${accountId}?limit=100×tamp=lt:1682455406.562695326`; - const extractedAccountId = mirrorNodeInstance.extractAccountIdFromUrl(url); + const extractedAccountId = mirrorNodeInstance.extractAccountIdFromUrl(url, requestDetails); expect(extractedAccountId).to.eq(accountId); }); it('Can extract the evm address out of an account pagination next link url', async () => { const evmAddress = '0x583031d1113ad414f02576bd6afa5bbdf935b7d9'; const url = `/api/v1/accounts/${evmAddress}?limit=100×tamp=lt:1682455406.562695326`; - const extractedEvmAddress = mirrorNodeInstance.extractAccountIdFromUrl(url); + const extractedEvmAddress = mirrorNodeInstance.extractAccountIdFromUrl(url, requestDetails); expect(extractedEvmAddress).to.eq(evmAddress); }); it('it should have a `request` method ', async () => { expect(mirrorNodeInstance).to.exist; - expect(mirrorNodeInstance.request).to.exist; + expect(mirrorNodeInstance['request']).to.exist; }); it('`restUrl` is exposed and correct', async () => { @@ -137,14 +149,14 @@ describe('MirrorNodeClient', async function () { it('Can extract the account number out of an account pagination next link url', async () => { const accountId = '0.0.123'; const url = `/api/v1/accounts/${accountId}?limit=100×tamp=lt:1682455406.562695326`; - const extractedAccountId = mirrorNodeInstance.extractAccountIdFromUrl(url); + const extractedAccountId = mirrorNodeInstance.extractAccountIdFromUrl(url, requestDetails); expect(extractedAccountId).to.eq(accountId); }); it('Can extract the evm address out of an account pagination next link url', async () => { const evmAddress = '0x583031d1113ad414f02576bd6afa5bbdf935b7d9'; const url = `/api/v1/accounts/${evmAddress}?limit=100×tamp=lt:1682455406.562695326`; - const extractedEvmAddress = mirrorNodeInstance.extractAccountIdFromUrl(url); + const extractedEvmAddress = mirrorNodeInstance.extractAccountIdFromUrl(url, requestDetails); expect(extractedEvmAddress).to.eq(evmAddress); }); @@ -227,7 +239,7 @@ describe('MirrorNodeClient', async function () { }, }); - const result = await mirrorNodeInstance.get('accounts'); + const result = await mirrorNodeInstance.get('accounts', 'accounts', requestDetails); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.exist; @@ -247,7 +259,7 @@ describe('MirrorNodeClient', async function () { }; mock.onPost('contracts/call', { foo: 'bar' }).reply(200, mockResult); - const result = await mirrorNodeInstance.post('contracts/call', { foo: 'bar' }); + const result = await mirrorNodeInstance.post('contracts/call', { foo: 'bar' }, 'contracts/call', requestDetails); expect(result).to.exist; expect(result.result).to.exist; expect(result.result).to.eq(mockResult.result); @@ -255,7 +267,7 @@ describe('MirrorNodeClient', async function () { it('call to non-existing REST route returns 404', async () => { try { - expect(await mirrorNodeInstance.get('non-existing-route')).to.throw(); + expect(await mirrorNodeInstance.get('non-existing-route', 'non-existing-route', requestDetails)).to.throw; } catch (err: any) { expect(err.statusCode).to.eq(404); } @@ -274,7 +286,7 @@ describe('MirrorNodeClient', async function () { }, }); - const result = await mirrorNodeInstance.getAccount(alias); + const result = await mirrorNodeInstance.getAccount(alias, requestDetails); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.equal(null); @@ -299,7 +311,7 @@ describe('MirrorNodeClient', async function () { }, }); - const result = await mirrorNodeInstance.getBlock(hash); + const result = await mirrorNodeInstance.getBlock(hash, requestDetails); expect(result).to.exist; expect(result.count).equal(3); expect(result.number).equal(77); @@ -322,7 +334,7 @@ describe('MirrorNodeClient', async function () { }, }); - const result = await mirrorNodeInstance.getBlock(number); + const result = await mirrorNodeInstance.getBlock(number, requestDetails); expect(result).to.exist; expect(result.count).equal(3); expect(result.number).equal(77); @@ -347,7 +359,7 @@ describe('MirrorNodeClient', async function () { .onGet(`blocks?block.number=${number}&limit=100&order=asc`) .reply(200, { blocks: [block], links: { next: null } }); - const result = await mirrorNodeInstance.getBlocks(number); + const result = await mirrorNodeInstance.getBlocks(requestDetails, number); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.equal(null); @@ -363,7 +375,7 @@ describe('MirrorNodeClient', async function () { .onGet(`blocks?timestamp=${timestamp}&limit=100&order=asc`) .reply(200, { blocks: [block], links: { next: null } }); - const result = await mirrorNodeInstance.getBlocks(undefined, timestamp); + const result = await mirrorNodeInstance.getBlocks(requestDetails, undefined, timestamp); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.equal(null); @@ -375,21 +387,21 @@ describe('MirrorNodeClient', async function () { it('`getContract`', async () => { mock.onGet(`contracts/${mockData.contractEvmAddress}`).reply(200, mockData.contract); - const result = await mirrorNodeInstance.getContract(mockData.contractEvmAddress); + const result = await mirrorNodeInstance.getContract(mockData.contractEvmAddress, requestDetails); expect(result).to.exist; expect(result.contract_id).equal('0.0.2000'); }); it('`getContract` not found', async () => { mock.onGet(`contracts/${mockData.contractEvmAddress}`).reply(404, mockData.notFound); - const result = await mirrorNodeInstance.getContract(mockData.contractEvmAddress); + const result = await mirrorNodeInstance.getContract(mockData.contractEvmAddress, requestDetails); expect(result).to.be.null; }); it('`getAccount`', async () => { mock.onGet(`accounts/${mockData.accountEvmAddress}${noTransactions}`).reply(200, mockData.account); - const result = await mirrorNodeInstance.getAccount(mockData.accountEvmAddress); + const result = await mirrorNodeInstance.getAccount(mockData.accountEvmAddress, requestDetails); expect(result).to.exist; expect(result.account).equal('0.0.1014'); }); @@ -398,7 +410,7 @@ describe('MirrorNodeClient', async function () { const evmAddress = '0x00000000000000000000000000000000000003f6'; mock.onGet(`accounts/${evmAddress}${noTransactions}`).reply(404, mockData.notFound); - const result = await mirrorNodeInstance.getAccount(evmAddress); + const result = await mirrorNodeInstance.getAccount(evmAddress, requestDetails); expect(result).to.be.null; }); @@ -407,7 +419,7 @@ describe('MirrorNodeClient', async function () { mock.onGet(`accounts/${evmAddress}${noTransactions}`).reply(500, { error: 'unexpected error' }); let errorRaised = false; try { - await mirrorNodeInstance.getAccount(evmAddress); + await mirrorNodeInstance.getAccount(evmAddress, requestDetails); } catch (error: any) { errorRaised = true; expect(error.message).to.equal(`Request failed with status code 500`); @@ -420,7 +432,7 @@ describe('MirrorNodeClient', async function () { mock.onGet(`accounts/${invalidAddress}${noTransactions}`).reply(400); let errorRaised = false; try { - await mirrorNodeInstance.getAccount(invalidAddress); + await mirrorNodeInstance.getAccount(invalidAddress, requestDetails); } catch (error: any) { errorRaised = true; expect(error.message).to.equal(`Request failed with status code 400`); @@ -431,7 +443,7 @@ describe('MirrorNodeClient', async function () { it('`getTokenById`', async () => { mock.onGet(`tokens/${mockData.tokenId}`).reply(200, mockData.token); - const result = await mirrorNodeInstance.getTokenById(mockData.tokenId); + const result = await mirrorNodeInstance.getTokenById(mockData.tokenId, requestDetails); expect(result).to.exist; expect(result.token_id).equal('0.0.13312'); }); @@ -440,7 +452,7 @@ describe('MirrorNodeClient', async function () { const tokenId = '0.0.132'; mock.onGet(`accounts/${tokenId}${noTransactions}`).reply(404, mockData.notFound); - const result = await mirrorNodeInstance.getTokenById(tokenId); + const result = await mirrorNodeInstance.getTokenById(tokenId, requestDetails); expect(result).to.be.null; }); @@ -519,7 +531,7 @@ describe('MirrorNodeClient', async function () { const transactionId = '0.0.10-167654-000123456'; mock.onGet(`contracts/results/${transactionId}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResult(transactionId); + const result = await mirrorNodeInstance.getContractResult(transactionId, requestDetails); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -530,7 +542,7 @@ describe('MirrorNodeClient', async function () { const hash = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6391'; mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResult(hash); + const result = await mirrorNodeInstance.getContractResult(hash, requestDetails); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -540,10 +552,10 @@ describe('MirrorNodeClient', async function () { it('`getContractResults` by hash using cache', async () => { const hash = '0x07cad7b827375d10d73af57b6a3e84353645fdb1305ea58ff52dda53ec640533'; mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const resultBeforeCached = await mirrorNodeInstance.getContractResult(hash); + const resultBeforeCached = await mirrorNodeInstance.getContractResult(hash, requestDetails); mock.onGet(`contracts/results/${hash}`).reply(400, null); - const resultAfterCached = await mirrorNodeInstance.getContractResult(hash); + const resultAfterCached = await mirrorNodeInstance.getContractResult(hash, requestDetails); expect(resultBeforeCached).to.eq(resultAfterCached); }); @@ -552,7 +564,7 @@ describe('MirrorNodeClient', async function () { const hash = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6399'; mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash); + const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -566,7 +578,7 @@ describe('MirrorNodeClient', async function () { mock.onGet(`contracts/results/${hash}`).replyOnce(200, { ...detailedContractResult, transaction_index: undefined }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash); + const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -582,7 +594,7 @@ describe('MirrorNodeClient', async function () { .replyOnce(200, { ...detailedContractResult, transaction_index: undefined, block_number: undefined }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash); + const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -597,7 +609,7 @@ describe('MirrorNodeClient', async function () { mock.onGet(`contracts/results/${hash}`).replyOnce(200, { ...detailedContractResult, block_number: undefined }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash); + const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -611,7 +623,7 @@ describe('MirrorNodeClient', async function () { .onGet(`contracts/results?limit=100&order=asc`) .reply(200, { results: [detailedContractResult], links: { next: null } }); - const result = await mirrorNodeInstance.getContractResults(); + const result = await mirrorNodeInstance.getContractResults(requestDetails); expect(result).to.exist; expect(result.links).to.not.exist; expect(result.length).to.gt(0); @@ -630,8 +642,8 @@ describe('MirrorNodeClient', async function () { error_message: null, from: '0x0000000000000000000000000000000000001f41', function_parameters: '0x0707', - gas_limit: 9223372036854775807, - gas_used: 9223372036854775806, + gas_limit: BigNumber('9223372036854775807'), + gas_used: BigNumber('9223372036854775806'), timestamp: '987654.000123456', to: '0x0000000000000000000000000000000000001389', }; @@ -641,7 +653,7 @@ describe('MirrorNodeClient', async function () { .onGet(`contracts/${contractId}/results?limit=100&order=asc`) .reply(200, { results: [contractResult], links: { next: null } }); - const result = await mirrorNodeInstance.getContractResultsByAddress(contractId); + const result = await mirrorNodeInstance.getContractResultsByAddress(contractId, requestDetails); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.equal(null); @@ -658,7 +670,7 @@ describe('MirrorNodeClient', async function () { .onGet(`contracts/${address}/results?limit=100&order=asc`) .reply(200, { results: [contractResult], links: { next: null } }); - const result = await mirrorNodeInstance.getContractResultsByAddress(address); + const result = await mirrorNodeInstance.getContractResultsByAddress(address, requestDetails); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.equal(null); @@ -675,7 +687,7 @@ describe('MirrorNodeClient', async function () { .onGet(`contracts/${address}/results?limit=1&order=desc`) .reply(200, { results: [contractResult], links: { next: null } }); - const result = await mirrorNodeInstance.getLatestContractResultsByAddress(address, undefined, 1); + const result = await mirrorNodeInstance.getLatestContractResultsByAddress(address, undefined, 1, requestDetails); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.equal(null); @@ -692,7 +704,12 @@ describe('MirrorNodeClient', async function () { .onGet(`contracts/${address}/results?timestamp=lte:987654.000123456&limit=2&order=desc`) .reply(200, { results: [contractResult], links: { next: null } }); - const result = await mirrorNodeInstance.getLatestContractResultsByAddress(address, '987654.000123456', 2); + const result = await mirrorNodeInstance.getLatestContractResultsByAddress( + address, + '987654.000123456', + 2, + requestDetails, + ); expect(result).to.exist; expect(result.links).to.exist; expect(result.links.next).to.equal(null); @@ -716,7 +733,7 @@ describe('MirrorNodeClient', async function () { it('`getContractResultsLogs` ', async () => { mock.onGet(`contracts/results/logs?limit=100&order=asc`).reply(200, { logs: [log] }); - const results = await mirrorNodeInstance.getContractResultsLogs(); + const results = await mirrorNodeInstance.getContractResultsLogs(requestDetails); expect(results).to.exist; expect(results.length).to.gt(0); const firstResult = results[0]; @@ -728,7 +745,7 @@ describe('MirrorNodeClient', async function () { it('`getContractResultsLogsByAddress` ', async () => { mock.onGet(`contracts/${log.address}/results/logs?limit=100&order=asc`).reply(200, { logs: [log] }); - const results = await mirrorNodeInstance.getContractResultsLogsByAddress(log.address); + const results = await mirrorNodeInstance.getContractResultsLogsByAddress(log.address, requestDetails); expect(results).to.exist; expect(results.length).to.gt(0); const firstResult = results[0]; @@ -737,7 +754,7 @@ describe('MirrorNodeClient', async function () { expect(firstResult.index).equal(log.index); }); it('`getContractResultsLogsByAddress` with ZeroAddress ', async () => { - const results = await mirrorNodeInstance.getContractResultsLogsByAddress(ethers.ZeroAddress); + const results = await mirrorNodeInstance.getContractResultsLogsByAddress(ethers.ZeroAddress, requestDetails); expect(results).to.exist; expect(results.length).to.eq(0); expect(results).to.deep.equal([]); @@ -752,6 +769,7 @@ describe('MirrorNodeClient', async function () { const result = await mirrorNodeInstance.getContractStateByAddressAndSlot( contractAddress, defaultCurrentContractState.state[0].slot, + requestDetails, ); expect(result).to.exist; @@ -770,6 +788,7 @@ describe('MirrorNodeClient', async function () { await mirrorNodeInstance.getContractStateByAddressAndSlot( contractAddress + '1', defaultCurrentContractState.state[0].slot, + requestDetails, ), ).to.throw(); } catch (error) { @@ -788,6 +807,7 @@ describe('MirrorNodeClient', async function () { await mirrorNodeInstance.getContractStateByAddressAndSlot( contractAddress, defaultCurrentContractState.state[0].slot + '1', + requestDetails, ), ).to.throw(); } catch (error) { @@ -800,7 +820,7 @@ describe('MirrorNodeClient', async function () { const incorrectAddress = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ed'; try { - expect(await mirrorNodeInstance.getContractResultsLogsByAddress(incorrectAddress)).to.throw(); + expect(await mirrorNodeInstance.getContractResultsLogsByAddress(incorrectAddress, requestDetails)).to.throw; } catch (err: any) { expect(err).to.exist; } @@ -809,7 +829,7 @@ describe('MirrorNodeClient', async function () { it('`getBlocks` by number', async () => { mock.onGet(`blocks?limit=1&order=desc`).reply(200, block); - const result = await mirrorNodeInstance.getLatestBlock(); + const result = await mirrorNodeInstance.getLatestBlock(requestDetails); expect(result).to.exist; expect(result.count).equal(block.count); expect(result.number).equal(block.number); @@ -823,7 +843,7 @@ describe('MirrorNodeClient', async function () { }); for (let i = 0; i < 3; i++) { - const result = await mirrorNodeInstance.getBlock(hash); + const result = await mirrorNodeInstance.getBlock(hash, requestDetails); expect(result).to.exist; expect(result.hash).equal(hash); expect(result.number).equal(77); @@ -847,7 +867,7 @@ describe('MirrorNodeClient', async function () { mock.onGet(`network/exchangerate`).reply(200, exchangerate); - const result = await mirrorNodeInstance.getNetworkExchangeRate(getRequestId()); + const result = await mirrorNodeInstance.getNetworkExchangeRate(requestDetails); expect(result).to.exist; expect(result.current_rate).to.exist; expect(result.next_rate).to.exist; @@ -864,13 +884,17 @@ describe('MirrorNodeClient', async function () { mock.onGet(`accounts/${mockData.contractEvmAddress}${noTransactions}`).reply(200, mockData.account); mock.onGet(`tokens/${mockData.contractEvmAddress}`).reply(404, mockData.notFound); - const entityType = await mirrorNodeInstance.resolveEntityType(mockData.contractEvmAddress); + const entityType = await mirrorNodeInstance.resolveEntityType( + mockData.contractEvmAddress, + 'mirrorNodeClientTest', + requestDetails, + ); expect(entityType).to.exist; expect(entityType).to.have.property('type'); expect(entityType).to.have.property('entity'); - expect(entityType.type).to.eq('contract'); - expect(entityType.entity).to.have.property('contract_id'); - expect(entityType.entity.contract_id).to.eq(mockData.contract.contract_id); + expect(entityType!.type).to.eq('contract'); + expect(entityType!.entity).to.have.property('contract_id'); + expect(entityType!.entity.contract_id).to.eq(mockData.contract.contract_id); }); it('returns `account` when CONTRACTS and TOKENS endpoint returns 404 and ACCOUNTS endpoint returns a result', async () => { @@ -878,13 +902,17 @@ describe('MirrorNodeClient', async function () { mock.onGet(`accounts/${mockData.accountEvmAddress}${noTransactions}`).reply(200, mockData.account); mock.onGet(`tokens/${mockData.tokenId}`).reply(404, mockData.notFound); - const entityType = await mirrorNodeInstance.resolveEntityType(mockData.accountEvmAddress); + const entityType = await mirrorNodeInstance.resolveEntityType( + mockData.accountEvmAddress, + 'mirrorNodeClientTest', + requestDetails, + ); expect(entityType).to.exist; expect(entityType).to.have.property('type'); expect(entityType).to.have.property('entity'); - expect(entityType.type).to.eq('account'); - expect(entityType.entity).to.have.property('account'); - expect(entityType.entity.account).to.eq(mockData.account.account); + expect(entityType!.type).to.eq('account'); + expect(entityType!.entity).to.have.property('account'); + expect(entityType!.entity.account).to.eq(mockData.account.account); }); it('returns `token` when CONTRACTS and ACCOUNTS endpoints returns 404 and TOKEN endpoint returns a result', async () => { @@ -892,12 +920,16 @@ describe('MirrorNodeClient', async function () { mock.onGet(`accounts/${notFoundAddress}${noTransactions}`).reply(404, mockData.notFound); mock.onGet(`tokens/${mockData.tokenId}`).reply(200, mockData.token); - const entityType = await mirrorNodeInstance.resolveEntityType(mockData.tokenLongZero); + const entityType = await mirrorNodeInstance.resolveEntityType( + mockData.tokenLongZero, + 'mirrorNodeClientTest', + requestDetails, + ); expect(entityType).to.exist; expect(entityType).to.have.property('type'); expect(entityType).to.have.property('entity'); - expect(entityType.type).to.eq('token'); - expect(entityType.entity.token_id).to.eq(mockData.tokenId); + expect(entityType!.type).to.eq('token'); + expect(entityType!.entity.token_id).to.eq(mockData.tokenId); }); it('returns null when CONTRACTS and ACCOUNTS endpoints return 404', async () => { @@ -905,7 +937,11 @@ describe('MirrorNodeClient', async function () { mock.onGet(`accounts/${notFoundAddress}${noTransactions}`).reply(404, mockData.notFound); mock.onGet(`tokens/${notFoundAddress}`).reply(404, mockData.notFound); - const entityType = await mirrorNodeInstance.resolveEntityType(notFoundAddress); + const entityType = await mirrorNodeInstance.resolveEntityType( + notFoundAddress, + 'mirrorNodeClientTest', + requestDetails, + ); expect(entityType).to.be.null; }); @@ -913,31 +949,35 @@ describe('MirrorNodeClient', async function () { mock.onGet(`contracts/${mockData.tokenId}`).reply(404, mockData.notFound); mock.onGet(`tokens/${mockData.tokenId}`).reply(200, mockData.token); - const entityType = await mirrorNodeInstance.resolveEntityType(mockData.tokenLongZero, [ - constants.TYPE_CONTRACT, - constants.TYPE_TOKEN, - ]); + const entityType = await mirrorNodeInstance.resolveEntityType( + mockData.tokenLongZero, + 'mirrorNodeClientTest', + requestDetails, + [constants.TYPE_CONTRACT, constants.TYPE_TOKEN], + ); expect(entityType).to.exist; expect(entityType).to.have.property('type'); expect(entityType).to.have.property('entity'); - expect(entityType.type).to.eq('token'); - expect(entityType.entity.token_id).to.eq(mockData.tokenId); + expect(entityType!.type).to.eq('token'); + expect(entityType!.entity.token_id).to.eq(mockData.tokenId); }); it('does not call mirror node tokens API when token is not long zero type', async () => { mock.onGet(`contracts/${mockData.contractEvmAddress}`).reply(200, mockData.contract); mock.onGet(`tokens/${mockData.tokenId}`).reply(404, mockData.notFound); - const entityType = await mirrorNodeInstance.resolveEntityType(mockData.contractEvmAddress, [ - constants.TYPE_CONTRACT, - constants.TYPE_TOKEN, - ]); + const entityType = await mirrorNodeInstance.resolveEntityType( + mockData.contractEvmAddress, + 'mirrorNodeClientTest', + requestDetails, + [constants.TYPE_CONTRACT, constants.TYPE_TOKEN], + ); expect(entityType).to.exist; expect(entityType).to.have.property('type'); expect(entityType).to.have.property('entity'); - expect(entityType.type).to.eq('contract'); - expect(entityType.entity).to.have.property('contract_id'); - expect(entityType.entity.contract_id).to.eq(mockData.contract.contract_id); + expect(entityType!.type).to.eq('contract'); + expect(entityType!.entity).to.have.property('contract_id'); + expect(entityType!.entity.contract_id).to.eq(mockData.contract.contract_id); }); }); @@ -994,7 +1034,7 @@ describe('MirrorNodeClient', async function () { it('should be able to fetch transaction by transaction id', async () => { mock.onGet(`transactions/${defaultTransactionIdFormatted}`).reply(200, defaultTransaction); - const transaction = await mirrorNodeInstance.getTransactionById(defaultTransactionId); + const transaction = await mirrorNodeInstance.getTransactionById(defaultTransactionId, requestDetails); expect(transaction).to.exist; expect(transaction.transactions.length).to.equal(defaultTransaction.transactions.length); }); @@ -1003,7 +1043,7 @@ describe('MirrorNodeClient', async function () { mock .onGet(`transactions/${defaultTransactionIdFormatted}?nonce=1`) .reply(200, defaultTransaction.transactions[1]); - const transaction = await mirrorNodeInstance.getTransactionById(defaultTransactionId, 1); + const transaction = await mirrorNodeInstance.getTransactionById(defaultTransactionId, requestDetails, 1); expect(transaction).to.exist; expect(transaction.transaction_id).to.equal(defaultTransaction.transactions[1].transaction_id); expect(transaction.result).to.equal(defaultTransaction.transactions[1].result); @@ -1011,7 +1051,7 @@ describe('MirrorNodeClient', async function () { it('should fail to fetch transaction by wrong transaction id', async () => { mock.onGet(`transactions/${invalidTransactionId}`).reply(404, mockData.notFound); - const transaction = await mirrorNodeInstance.getTransactionById(invalidTransactionId); + const transaction = await mirrorNodeInstance.getTransactionById(invalidTransactionId, requestDetails); expect(transaction).to.be.null; }); @@ -1022,7 +1062,7 @@ describe('MirrorNodeClient', async function () { }); mock.onGet(`transactions/${transactionId}`).reply(200, null); - const result = await mirrorNodeInstance.getContractRevertReasonFromTransaction(error, getRequestId()); + const result = await mirrorNodeInstance.getContractRevertReasonFromTransaction(error, requestDetails); expect(result).to.be.null; }); @@ -1033,7 +1073,7 @@ describe('MirrorNodeClient', async function () { }); mock.onGet(`transactions/${transactionId}`).reply(200, []); - const result = await mirrorNodeInstance.getContractRevertReasonFromTransaction(error, getRequestId()); + const result = await mirrorNodeInstance.getContractRevertReasonFromTransaction(error, requestDetails); expect(result).to.be.null; }); @@ -1044,7 +1084,7 @@ describe('MirrorNodeClient', async function () { }); mock.onGet(`transactions/${transactionId}`).reply(200, defaultTransaction); - const result = await mirrorNodeInstance.getContractRevertReasonFromTransaction(error, getRequestId()); + const result = await mirrorNodeInstance.getContractRevertReasonFromTransaction(error, requestDetails); expect(result).to.eq('INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE'); }); }); @@ -1081,7 +1121,12 @@ describe('MirrorNodeClient', async function () { }, }); - const results = await mirrorNodeInstance.getPaginatedResults('results', 'results', 'genericResults'); + const results = await mirrorNodeInstance.getPaginatedResults( + 'results', + 'results', + 'genericResults', + requestDetails, + ); expect(results).to.exist; expect(results).to.deep.equal(mockedResults); @@ -1091,7 +1136,12 @@ describe('MirrorNodeClient', async function () { const pages = 5; const mockedResults = mockPages(pages); - const results = await mirrorNodeInstance.getPaginatedResults('results?page=0', 'results', 'genericResults'); + const results = await mirrorNodeInstance.getPaginatedResults( + 'results?page=0', + 'results', + 'genericResults', + requestDetails, + ); expect(results).to.exist; expect(results.length).to.eq(pages); @@ -1103,9 +1153,9 @@ describe('MirrorNodeClient', async function () { mockPages(pages); try { - await mirrorNodeInstance.getPaginatedResults('results?page=0', 'results', 'genericResults'); + await mirrorNodeInstance.getPaginatedResults('results?page=0', 'results', 'genericResults', requestDetails); expect.fail('should have thrown an error'); - } catch (e) { + } catch (e: any) { const errorRef = predefined.PAGINATION_MAX(0); // reference error for all properties except message expect(e.message).to.equal( `Exceeded maximum mirror node pagination count: ${constants.MAX_MIRROR_NODE_PAGINATION}`, @@ -1121,7 +1171,11 @@ describe('MirrorNodeClient', async function () { it('if the method returns an immediate result it is called only once', async () => { mock.onGet(uri).reply(200, mockData.account); - const result = await mirrorNodeInstance.repeatedRequest('getAccount', [mockData.accountEvmAddress], 3); + const result = await mirrorNodeInstance.repeatedRequest( + 'getAccount', + [mockData.accountEvmAddress, requestDetails], + 3, + ); expect(result).to.exist; expect(result.account).equal('0.0.1014'); @@ -1132,7 +1186,11 @@ describe('MirrorNodeClient', async function () { // Return data on the second call mock.onGet(uri).replyOnce(404, mockData.notFound).onGet(uri).reply(200, mockData.account); - const result = await mirrorNodeInstance.repeatedRequest('getAccount', [mockData.accountEvmAddress], 3); + const result = await mirrorNodeInstance.repeatedRequest( + 'getAccount', + [mockData.accountEvmAddress, requestDetails], + 3, + ); expect(result).to.exist; expect(result.account).equal('0.0.1014'); @@ -1140,7 +1198,11 @@ describe('MirrorNodeClient', async function () { }); it('method is repeated the specified number of times if no result is found', async () => { - const result = await mirrorNodeInstance.repeatedRequest('getAccount', [mockData.accountEvmAddress], 3); + const result = await mirrorNodeInstance.repeatedRequest( + 'getAccount', + [mockData.accountEvmAddress, requestDetails], + 3, + ); expect(result).to.be.null; expect(mock.history.get.length).to.eq(3); // is called three times }); @@ -1157,7 +1219,11 @@ describe('MirrorNodeClient', async function () { .onGet(uri) .reply(200, mockData.account); - const result = await mirrorNodeInstance.repeatedRequest('getAccount', [mockData.accountEvmAddress], 3); + const result = await mirrorNodeInstance.repeatedRequest( + 'getAccount', + [mockData.accountEvmAddress, requestDetails], + 3, + ); expect(result).to.be.null; expect(mock.history.get.length).to.eq(3); // is called three times }); @@ -1196,9 +1262,9 @@ describe('MirrorNodeClient', async function () { const transactionRecordMetrics = await mirrorNodeInstance.getTransactionRecordMetrics( mockedTransactionId, mockedCallerName, - getRequestId(), mockedConstructorName, operatorAcocuntId, + requestDetails, ); expect(transactionRecordMetrics.transactionFee).to.eq(mockedTxFee); @@ -1211,9 +1277,9 @@ describe('MirrorNodeClient', async function () { await mirrorNodeInstance.getTransactionRecordMetrics( mockedTransactionId, mockedCallerName, - getRequestId(), mockedConstructorName, operatorAcocuntId, + requestDetails, ); expect.fail('should have thrown an error'); @@ -1266,7 +1332,7 @@ describe('MirrorNodeClient', async function () { }; const transactionFee = mirrorNodeInstance.getTransferAmountSumForAccount( - mockedMirrorNodeTransactionRecord.transactions[0], + mockedMirrorNodeTransactionRecord.transactions[0] as MirrorNodeTransactionRecord, accountIdA, ); expect(transactionFee).to.eq(expectedTxFeeForAccountIdA); @@ -1328,6 +1394,7 @@ describe('MirrorNodeClient', async function () { const transactions = await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp( evmAddress, timestamp, + requestDetails, ); expect(transactions).to.be.null; }); @@ -1337,6 +1404,7 @@ describe('MirrorNodeClient', async function () { const transactions = await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp( evmAddress, timestamp, + requestDetails, ); expect(transactions).to.exist; expect(transactions.transactions.length).to.equal(0); @@ -1347,6 +1415,7 @@ describe('MirrorNodeClient', async function () { const transactions = await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp( evmAddress, timestamp, + requestDetails, ); expect(transactions).to.exist; expect(transactions.transactions.length).to.equal(1); @@ -1357,6 +1426,7 @@ describe('MirrorNodeClient', async function () { const transactions = await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp( evmAddress, timestamp, + requestDetails, 2, ); expect(transactions).to.exist; @@ -1368,7 +1438,7 @@ describe('MirrorNodeClient', async function () { mock.onGet(transactionPath(address, 1)).reply(500, { error: 'unexpected error' }); let errorRaised = false; try { - await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp(address, timestamp); + await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp(address, timestamp, requestDetails); } catch (error: any) { errorRaised = true; expect(error.message).to.equal(`Request failed with status code 500`); @@ -1381,7 +1451,11 @@ describe('MirrorNodeClient', async function () { mock.onGet(transactionPath(invalidAddress, 1)).reply(400, null); let errorRaised = false; try { - await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp(invalidAddress, timestamp); + await mirrorNodeInstance.getAccountLatestEthereumTransactionsByTimestamp( + invalidAddress, + timestamp, + requestDetails, + ); } catch (error: any) { errorRaised = true; expect(error.message).to.equal(`Request failed with status code 400`); @@ -1396,24 +1470,24 @@ describe('MirrorNodeClient', async function () { it('should return false for contract for non existing contract', async () => { mock.onGet(contractPath).reply(404, mockData.notFound); - const isValid = await mirrorNodeInstance.isValidContract(evmAddress); + const isValid = await mirrorNodeInstance.isValidContract(evmAddress, requestDetails); expect(isValid).to.be.false; }); it('should return valid for contract for existing contract', async () => { mock.onGet(contractPath).reply(200, mockData.contract); - const isValid = await mirrorNodeInstance.isValidContract(evmAddress); + const isValid = await mirrorNodeInstance.isValidContract(evmAddress, requestDetails); expect(isValid).to.be.true; }); it('should return valid for contract from cache on additional calls', async () => { mock.onGet(contractPath).reply(200, mockData.contract); - let isValid = await mirrorNodeInstance.isValidContract(evmAddress); + let isValid = await mirrorNodeInstance.isValidContract(evmAddress, requestDetails); expect(isValid).to.be.true; // verify that the cache is used mock.onGet(contractPath).reply(404, mockData.notFound); - isValid = await mirrorNodeInstance.isValidContract(evmAddress); + isValid = await mirrorNodeInstance.isValidContract(evmAddress, requestDetails); expect(isValid).to.be.true; }); }); @@ -1424,20 +1498,20 @@ describe('MirrorNodeClient', async function () { it('should fail to fetch contract for non existing contract', async () => { mock.onGet(contractPath).reply(404, mockData.notFound); - const id = await mirrorNodeInstance.getContractId(evmAddress); + const id = await mirrorNodeInstance.getContractId(evmAddress, requestDetails); expect(id).to.not.exist; }); it('should fetch id for existing contract', async () => { mock.onGet(contractPath).reply(200, mockData.contract); - const id = await mirrorNodeInstance.getContractId(evmAddress); + const id = await mirrorNodeInstance.getContractId(evmAddress, requestDetails); expect(id).to.exist; expect(id).to.be.equal(mockData.contract.contract_id); }); it('should fetch contract for existing contract from cache on additional calls', async () => { mock.onGet(contractPath).reply(200, mockData.contract); - let id = await mirrorNodeInstance.getContractId(evmAddress); + let id = await mirrorNodeInstance.getContractId(evmAddress, requestDetails); expect(id).to.exist; expect(id).to.be.equal(mockData.contract.contract_id); @@ -1453,26 +1527,26 @@ describe('MirrorNodeClient', async function () { it('should fail to fetch blocks for empty network', async () => { mock.onGet(blockPath).reply(404, mockData.notFound); - const earlierBlock = await mirrorNodeInstance.getEarliestBlock(); + const earlierBlock = await mirrorNodeInstance.getEarliestBlock(requestDetails); expect(earlierBlock).to.not.exist; }); it('should fetch block for existing valid network', async () => { mock.onGet(blockPath).reply(200, { blocks: [mockData.blocks.blocks[0]] }); - const earlierBlock = await mirrorNodeInstance.getEarliestBlock(); + const earlierBlock = await mirrorNodeInstance.getEarliestBlock(requestDetails); expect(earlierBlock).to.exist; expect(earlierBlock.name).to.be.equal(mockData.blocks.blocks[0].name); }); it('should fetch block for valid network from cache on additional calls', async () => { mock.onGet(blockPath).reply(200, { blocks: [mockData.blocks.blocks[0]] }); - let earlierBlock = await mirrorNodeInstance.getEarliestBlock(); + let earlierBlock = await mirrorNodeInstance.getEarliestBlock(requestDetails); expect(earlierBlock).to.exist; expect(earlierBlock.name).to.be.equal(mockData.blocks.blocks[0].name); // verify that the cache is used mock.onGet(blockPath).reply(404, mockData.notFound); - earlierBlock = await mirrorNodeInstance.getEarliestBlock(); + earlierBlock = await mirrorNodeInstance.getEarliestBlock(requestDetails); expect(earlierBlock).to.exist; expect(earlierBlock.name).to.be.equal(mockData.blocks.blocks[0].name); }); diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts index a245b4a806..b264a40044 100644 --- a/packages/relay/tests/lib/openrpc.spec.ts +++ b/packages/relay/tests/lib/openrpc.spec.ts @@ -29,12 +29,13 @@ import axios from 'axios'; import sinon from 'sinon'; import dotenv from 'dotenv'; import MockAdapter from 'axios-mock-adapter'; -import { RelayImpl } from '../../src/lib/relay'; import { Registry } from 'prom-client'; +import { BigNumber } from 'bignumber.js'; +import { RelayImpl } from '../../src'; import { EthImpl } from '../../src/lib/eth'; -import { SDKClient } from '../../src/lib/clients'; -import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient'; +import { SDKClient, MirrorNodeClient } from '../../src/lib/clients'; +import { RequestDetails } from '../../src/lib/types'; import openRpcSchema from '../../../../docs/openrpc.json'; import { @@ -63,19 +64,20 @@ import { defaultLogTopics, defaultNetworkFees, defaultTxHash, - getRequestId, signedTransactionHash, } from '../helpers'; import { NOT_FOUND_RES } from './eth/eth-config'; import ClientService from '../../src/lib/services/hapiService/hapiService'; import HbarLimit from '../../src/lib/hbarlimiter'; -import { numberTo0x } from '../../../../packages/relay/src/formatters'; +import { numberTo0x } from '../../src/formatters'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); import constants from '../../src/lib/constants'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import EventEmitter from 'events'; +import Long from 'long'; +import { AccountInfo } from '@hashgraph/sdk'; process.env.npm_package_version = 'relay/0.0.1-SNAPSHOT'; @@ -86,7 +88,7 @@ const Relay = new RelayImpl(logger, registry); let mock: MockAdapter; let mirrorNodeInstance: MirrorNodeClient; let clientServiceInstance: ClientService; -let sdkClientStub: any; +let sdkClientStub: sinon.SinonStubbedInstance; const noTransactions = '?transactions=false'; @@ -95,6 +97,8 @@ describe('Open RPC Specification', function () { let methodsResponseSchema: { [method: string]: any }; let ethImpl: EthImpl; + const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); + this.beforeAll(async () => { rpcDocument = await parseOpenRPCDocument(JSON.stringify(openRpcSchema)); methodsResponseSchema = rpcDocument.methods.reduce( @@ -206,11 +210,11 @@ describe('Open RPC Specification', function () { mock.onGet(`contracts/${log.address}`).reply(200, defaultContract); } mock.onPost(`contracts/call`, { ...defaultCallData, estimate: false }).reply(200, { result: '0x12' }); - sdkClientStub.getAccountBalanceInWeiBar.returns(1000); - sdkClientStub.getAccountBalanceInTinyBar.returns(100000000000); - sdkClientStub.getContractByteCode.returns(Buffer.from(bytecode.replace('0x', ''), 'hex')); - sdkClientStub.getAccountInfo.returns({ ethereumNonce: '0x1' }); - sdkClientStub.submitEthereumTransaction.returns({}); + sdkClientStub.getAccountBalanceInWeiBar.resolves(BigNumber(1000)); + sdkClientStub.getAccountBalanceInTinyBar.resolves(BigNumber(100000000000)); + sdkClientStub.getContractByteCode.resolves(Buffer.from(bytecode.replace('0x', ''), 'hex')); + sdkClientStub.getAccountInfo.resolves({ ethereumNonce: Long.ONE } as unknown as AccountInfo); + sdkClientStub.submitEthereumTransaction.resolves(); mock.onGet(`accounts/${defaultContractResults.results[0].from}?transactions=false`).reply(200); mock.onGet(`accounts/${defaultContractResults.results[1].from}?transactions=false`).reply(200); mock.onGet(`accounts/${defaultContractResults.results[0].to}?transactions=false`).reply(200); @@ -244,112 +248,112 @@ describe('Open RPC Specification', function () { }); it('should execute "eth_accounts"', function () { - const response = ethImpl.accounts(); + const response = ethImpl.accounts(requestDetails); validateResponseSchema(methodsResponseSchema.eth_accounts, response); }); it('should execute "eth_blockNumber"', async function () { - const response = await ethImpl.blockNumber(); + const response = await ethImpl.blockNumber(requestDetails); validateResponseSchema(methodsResponseSchema.eth_blockNumber, response); }); it('should execute "eth_chainId"', function () { - const response = ethImpl.chainId(); + const response = ethImpl.chainId(requestDetails); validateResponseSchema(methodsResponseSchema.eth_chainId, response); }); it('should execute "eth_coinbase"', function () { - const response = ethImpl.coinbase(); + const response = ethImpl.coinbase(requestDetails); validateResponseSchema(methodsResponseSchema.eth_coinbase, response); }); it('should execute "eth_estimateGas"', async function () { mock.onGet(`accounts/undefined${noTransactions}`).reply(404); - const response = await ethImpl.estimateGas({}, null); + const response = await ethImpl.estimateGas({}, null, requestDetails); validateResponseSchema(methodsResponseSchema.eth_estimateGas, response); }); it('should execute "eth_feeHistory"', async function () { - const response = await ethImpl.feeHistory(1, 'latest', [0]); + const response = await ethImpl.feeHistory(1, 'latest', [0], requestDetails); validateResponseSchema(methodsResponseSchema.eth_feeHistory, response); }); it('should execute "eth_gasPrice"', async function () { - const response = await ethImpl.gasPrice(); + const response = await ethImpl.gasPrice(requestDetails); validateResponseSchema(methodsResponseSchema.eth_gasPrice, response); }); it('should execute "eth_getBalance"', async function () { - const response = await ethImpl.getBalance(contractAddress1, 'latest', getRequestId()); + const response = await ethImpl.getBalance(contractAddress1, 'latest', requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBalance, response); }); it('should execute "eth_getBlockByHash" with hydrated = true', async function () { - const response = await ethImpl.getBlockByHash(blockHash, true); + const response = await ethImpl.getBlockByHash(blockHash, true, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBlockByHash, response); }); it('should execute "eth_getBlockByHash" with hydrated = false', async function () { - const response = await ethImpl.getBlockByHash(blockHash, true); + const response = await ethImpl.getBlockByHash(blockHash, true, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBlockByHash, response); }); it('should execute "eth_getBlockByNumber" with hydrated = true', async function () { - const response = await ethImpl.getBlockByNumber(numberTo0x(blockNumber), true); + const response = await ethImpl.getBlockByNumber(numberTo0x(blockNumber), true, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBlockByNumber, response); }); it('should execute "eth_getBlockByNumber" with hydrated = false', async function () { - const response = await ethImpl.getBlockByNumber(numberTo0x(blockNumber), false); + const response = await ethImpl.getBlockByNumber(numberTo0x(blockNumber), false, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBlockByNumber, response); }); it('should execute "eth_getBlockTransactionCountByHash"', async function () { - const response = await ethImpl.getBlockTransactionCountByHash(blockHash); + const response = await ethImpl.getBlockTransactionCountByHash(blockHash, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBlockTransactionCountByHash, response); }); it('should execute "eth_getBlockTransactionCountByNumber" with block tag', async function () { - const response = await ethImpl.getBlockTransactionCountByNumber('latest'); + const response = await ethImpl.getBlockTransactionCountByNumber('latest', requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBlockTransactionCountByNumber, response); }); it('should execute "eth_getBlockTransactionCountByNumber" with block number', async function () { - const response = await ethImpl.getBlockTransactionCountByNumber('0x3'); + const response = await ethImpl.getBlockTransactionCountByNumber('0x3', requestDetails); validateResponseSchema(methodsResponseSchema.eth_getBlockTransactionCountByNumber, response); }); it('should execute "eth_getCode" with block tag', async function () { mock.onGet(`tokens/${defaultContractResults.results[0].contract_id}`).reply(404); - const response = await ethImpl.getCode(contractAddress1, 'latest'); + const response = await ethImpl.getCode(contractAddress1, 'latest', requestDetails); validateResponseSchema(methodsResponseSchema.eth_getCode, response); }); it('should execute "eth_getCode" with block number', async function () { mock.onGet(`tokens/${defaultContractResults.results[0].contract_id}`).reply(404); - const response = await ethImpl.getCode(contractAddress1, '0x3'); + const response = await ethImpl.getCode(contractAddress1, '0x3', requestDetails); validateResponseSchema(methodsResponseSchema.eth_getCode, response); }); it('should execute "eth_getLogs" with no filters', async function () { - const response = await ethImpl.getLogs(null, 'latest', 'latest', null, null); + const response = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getLogs, response); }); @@ -374,13 +378,17 @@ describe('Open RPC Specification', function () { mock.onGet(`contracts/${log.address}`).reply(200, defaultContract); } - const response = await ethImpl.getLogs(null, 'latest', 'latest', null, defaultLogTopics); + const response = await ethImpl.getLogs(null, 'latest', 'latest', null, defaultLogTopics, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getLogs, response); }); it('should execute "eth_getTransactionByBlockHashAndIndex"', async function () { - const response = await ethImpl.getTransactionByBlockHashAndIndex(defaultBlock.hash, numberTo0x(defaultBlock.count)); + const response = await ethImpl.getTransactionByBlockHashAndIndex( + defaultBlock.hash, + numberTo0x(defaultBlock.count), + requestDetails, + ); validateResponseSchema(methodsResponseSchema.eth_getTransactionByBlockHashAndIndex, response); }); @@ -389,13 +397,14 @@ describe('Open RPC Specification', function () { const response = await ethImpl.getTransactionByBlockNumberAndIndex( numberTo0x(defaultBlock.number), numberTo0x(defaultBlock.count), + requestDetails, ); validateResponseSchema(methodsResponseSchema.eth_getTransactionByBlockNumberAndIndex, response); }); it('should execute "eth_getTransactionByHash"', async function () { - const response = await ethImpl.getTransactionByHash(defaultTxHash); + const response = await ethImpl.getTransactionByHash(defaultTxHash, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getTransactionByHash, response); }); @@ -405,7 +414,7 @@ describe('Open RPC Specification', function () { .onGet(`accounts/${contractAddress1}${noTransactions}`) .reply(200, { account: contractAddress1, ethereum_nonce: 5 }); mock.onGet(`contracts/${contractAddress1}${noTransactions}`).reply(404); - const response = await ethImpl.getTransactionCount(contractAddress1, 'latest'); + const response = await ethImpl.getTransactionCount(contractAddress1, 'latest', requestDetails); validateResponseSchema(methodsResponseSchema.eth_getTransactionCount, response); }); @@ -414,102 +423,103 @@ describe('Open RPC Specification', function () { mock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404); sinon.stub(ethImpl, 'getCurrentGasPriceForBlock').resolves('0xad78ebc5ac620000'); - const response = await ethImpl.getTransactionReceipt(defaultTxHash); + const response = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails); validateResponseSchema(methodsResponseSchema.eth_getTransactionReceipt, response); }); it('should execute "eth_getUncleByBlockHashAndIndex"', async function () { - const response = await ethImpl.getUncleByBlockHashAndIndex(); + const response = await ethImpl.getUncleByBlockHashAndIndex(requestDetails); validateResponseSchema(methodsResponseSchema.eth_getUncleByBlockHashAndIndex, response); }); it('should execute "eth_getUncleByBlockNumberAndIndex"', async function () { - const response = await ethImpl.getUncleByBlockNumberAndIndex(); + const response = await ethImpl.getUncleByBlockNumberAndIndex(requestDetails); validateResponseSchema(methodsResponseSchema.eth_getUncleByBlockNumberAndIndex, response); }); it('should execute "eth_getUncleByBlockNumberAndIndex"', async function () { - const response = await ethImpl.getUncleByBlockNumberAndIndex(); + const response = await ethImpl.getUncleByBlockNumberAndIndex(requestDetails); validateResponseSchema(methodsResponseSchema.eth_getUncleByBlockNumberAndIndex, response); }); it('should execute "eth_getUncleCountByBlockHash"', async function () { - const response = await ethImpl.getUncleCountByBlockHash(); + const response = await ethImpl.getUncleCountByBlockHash(requestDetails); validateResponseSchema(methodsResponseSchema.eth_getUncleCountByBlockHash, response); }); it('should execute "eth_getUncleCountByBlockNumber"', async function () { - const response = await ethImpl.getUncleCountByBlockNumber(); + const response = await ethImpl.getUncleCountByBlockNumber(requestDetails); validateResponseSchema(methodsResponseSchema.eth_getUncleCountByBlockNumber, response); }); it('should execute "eth_getWork"', async function () { - const response = ethImpl.getWork(); + const response = ethImpl.getWork(requestDetails); validateResponseSchema(methodsResponseSchema.eth_getWork, response); }); it('should execute "eth_hashrate"', async function () { - const response = await ethImpl.hashrate(); + const response = await ethImpl.hashrate(requestDetails); validateResponseSchema(methodsResponseSchema.eth_hashrate, response); }); it('should execute "eth_mining"', async function () { - const response = await ethImpl.mining(); + const response = await ethImpl.mining(requestDetails); validateResponseSchema(methodsResponseSchema.eth_mining, response); }); it('should execute "eth_protocolVersion"', async function () { - const response = ethImpl.protocolVersion(); + const response = ethImpl.protocolVersion(requestDetails); validateResponseSchema(methodsResponseSchema.eth_protocolVersion, response); }); it('should execute "eth_sendRawTransaction"', async function () { - const response = await ethImpl.sendRawTransaction(signedTransactionHash, getRequestId()); + const response = await ethImpl.sendRawTransaction(signedTransactionHash, requestDetails); + validateResponseSchema(methodsResponseSchema.eth_sendRawTransaction, response); }); it('should execute "eth_sendTransaction"', async function () { - const response = ethImpl.sendTransaction(); + const response = ethImpl.sendTransaction(requestDetails); validateResponseSchema(methodsResponseSchema.eth_sendTransaction, response); }); it('should execute "eth_signTransaction"', async function () { - const response = ethImpl.signTransaction(); + const response = ethImpl.signTransaction(requestDetails); validateResponseSchema(methodsResponseSchema.eth_signTransaction, response); }); it('should execute "eth_sign"', async function () { - const response = ethImpl.sign(); + const response = ethImpl.sign(requestDetails); validateResponseSchema(methodsResponseSchema.eth_sign, response); }); it('should execute "eth_submitHashrate"', async function () { - const response = ethImpl.submitHashrate(); + const response = ethImpl.submitHashrate(requestDetails); validateResponseSchema(methodsResponseSchema.eth_submitHashrate, response); }); it('should execute "eth_submitWork"', async function () { - const response = await ethImpl.submitWork(); + const response = await ethImpl.submitWork(requestDetails); validateResponseSchema(methodsResponseSchema.eth_submitWork, response); }); it('should execute "eth_syncing"', async function () { - const response = await ethImpl.syncing(); + const response = await ethImpl.syncing(requestDetails); validateResponseSchema(methodsResponseSchema.eth_syncing, response); }); diff --git a/packages/relay/tests/lib/precheck.spec.ts b/packages/relay/tests/lib/precheck.spec.ts index ceadf43e47..f316531c22 100644 --- a/packages/relay/tests/lib/precheck.spec.ts +++ b/packages/relay/tests/lib/precheck.spec.ts @@ -34,12 +34,14 @@ import constants from '../../src/lib/constants'; import { JsonRpcError, predefined } from '../../src'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { ONE_TINYBAR_IN_WEI_HEX } from './eth/eth-config'; +import { RequestDetails } from '../../src/lib/types'; const logger = pino(); const limitOrderPostFix = '?order=desc&limit=1'; const transactionsPostFix = '?transactions=false'; describe('Precheck', async function () { + const requestDetails = new RequestDetails({ requestId: 'precheckTest', ipAddress: '0.0.0.0' }); const txWithMatchingChainId = '0x02f87482012a0485a7a358200085a7a3582000832dc6c09400000000000000000000000000000000000003f78502540be40080c001a006f4cd8e6f84b76a05a5c1542a08682c928108ef7163d9c1bf1f3b636b1cd1fba032097cbf2dda17a2dcc40f62c97964d9d930cdce2e8a9df9a8ba023cda28e4ad'; const parsedTxWithMatchingChainId = ethers.Transaction.from(txWithMatchingChainId); @@ -141,7 +143,7 @@ describe('Precheck', async function () { describe('chainId', async function () { it('should pass for matching chainId', async function () { try { - precheck.chainId(parsedTxWithMatchingChainId); + precheck.chainId(parsedTxWithMatchingChainId, requestDetails); } catch (e: any) { expect(e).to.not.exist; } @@ -149,7 +151,7 @@ describe('Precheck', async function () { it('should pass when chainId=0x0', async function () { try { - precheck.chainId(parsedtxWithChainId0x0); + precheck.chainId(parsedtxWithChainId0x0, requestDetails); } catch (e: any) { expect(e).to.not.exist; } @@ -157,7 +159,7 @@ describe('Precheck', async function () { it('should not pass for non-matching chainId', async function () { try { - precheck.chainId(parsedTxWithNonMatchingChainId); + precheck.chainId(parsedTxWithNonMatchingChainId, requestDetails); expectedError(); } catch (e: any) { expect(e).to.exist; @@ -191,7 +193,7 @@ describe('Precheck', async function () { ? `Transaction gas limit '${gasLimit}' exceeds max gas per sec limit '${constants.MAX_GAS_PER_SEC}'` : `Transaction gas limit provided '${gasLimit}' is insufficient of intrinsic gas required `; try { - await precheck.gasLimit(parsedTx); + await precheck.gasLimit(parsedTx, requestDetails); expectedError(); } catch (e: any) { console.log(e); @@ -214,7 +216,7 @@ describe('Precheck', async function () { const parsedTx = ethers.Transaction.from(signed); try { - await precheck.gasLimit(parsedTx); + precheck.gasLimit(parsedTx, requestDetails); } catch (e: any) { expect(e).to.not.exist; } @@ -243,12 +245,12 @@ describe('Precheck', async function () { }); it('should pass for gas price gt to required gas price', async function () { - const result = precheck.gasPrice(parsedTxWithMatchingChainId, 10); + const result = precheck.gasPrice(parsedTxWithMatchingChainId, 10, requestDetails); expect(result).to.not.exist; }); it('should pass for gas price equal to required gas price', async function () { - const result = precheck.gasPrice(parsedTxWithMatchingChainId, defaultGasPrice); + const result = precheck.gasPrice(parsedTxWithMatchingChainId, defaultGasPrice, requestDetails); expect(result).to.not.exist; }); @@ -273,6 +275,7 @@ describe('Precheck', async function () { const result = precheck.gasPrice( parsedDeterministicDeploymentTransaction, 100 * constants.TINYBAR_TO_WEIBAR_COEF, + requestDetails, ); expect(result).to.not.exist; }); @@ -280,7 +283,7 @@ describe('Precheck', async function () { it('should not pass for gas price not enough', async function () { const minGasPrice = 1000 * constants.TINYBAR_TO_WEIBAR_COEF; try { - precheck.gasPrice(parsedTxWithMatchingChainId, minGasPrice); + precheck.gasPrice(parsedTxWithMatchingChainId, minGasPrice, requestDetails); expectedError(); } catch (e: any) { expect(e).to.exist; @@ -292,7 +295,7 @@ describe('Precheck', async function () { it('should pass for gas price not enough but within buffer', async function () { const adjustedGasPrice = parsedTxGasPrice + Number(constants.GAS_PRICE_TINY_BAR_BUFFER); - precheck.gasPrice(parsedTxWithMatchingChainId, adjustedGasPrice); + precheck.gasPrice(parsedTxWithMatchingChainId, adjustedGasPrice, requestDetails); }); }); @@ -312,7 +315,7 @@ describe('Precheck', async function () { }; try { - await precheck.balance(parsedTransaction, account); + precheck.balance(parsedTransaction, account, requestDetails); expectedError(); } catch (e: any) { expect(e).to.exist; @@ -325,7 +328,7 @@ describe('Precheck', async function () { const account = null; try { - await precheck.balance(parsedTransaction, account); + precheck.balance(parsedTransaction, account, requestDetails); expectedError(); } catch (e: any) { expect(e).to.exist; @@ -342,7 +345,7 @@ describe('Precheck', async function () { }, }; - const result = await precheck.balance(parsedTransaction, account); + const result = precheck.balance(parsedTransaction, account, requestDetails); expect(result).to.not.exist; }); @@ -354,7 +357,7 @@ describe('Precheck', async function () { }, }; - const result = await precheck.balance(parsedTransaction, account); + const result = precheck.balance(parsedTransaction, account, requestDetails); expect(result).to.not.exist; }); @@ -366,7 +369,7 @@ describe('Precheck', async function () { }, }; - const result = await precheck.balance(parsedTransaction, account); + const result = precheck.balance(parsedTransaction, account, requestDetails); expect(result).to.not.exist; }); @@ -378,7 +381,7 @@ describe('Precheck', async function () { }, }; - const result = await precheck.balance(parsedTransaction, account); + const result = precheck.balance(parsedTransaction, account, requestDetails); expect(result).to.not.exist; }); @@ -390,7 +393,7 @@ describe('Precheck', async function () { }, }; - const result = await precheck.balance(parsedTransaction, account); + const result = precheck.balance(parsedTransaction, account, requestDetails); expect(result).to.not.exist; }); }); @@ -412,7 +415,7 @@ describe('Precheck', async function () { mock.onGet(`accounts/${parsedTx.from}${limitOrderPostFix}`).reply(200, mirrorAccount); try { - await precheck.nonce(parsedTx, mirrorAccount.ethereum_nonce); + precheck.nonce(parsedTx, mirrorAccount.ethereum_nonce, requestDetails); expectedError(); } catch (e: any) { expect(e).to.eql(predefined.NONCE_TOO_LOW(parsedTx.nonce, mirrorAccount.ethereum_nonce)); @@ -429,7 +432,7 @@ describe('Precheck', async function () { mock.onGet(`accounts/${parsedTx.from}${limitOrderPostFix}`).reply(200, mirrorAccount); - await precheck.nonce(parsedTx, mirrorAccount.ethereum_nonce); + precheck.nonce(parsedTx, mirrorAccount.ethereum_nonce, requestDetails); }); }); @@ -450,9 +453,8 @@ describe('Precheck', async function () { it(`should fail for missing account`, async function () { mock.onGet(`accounts/${parsedTx.from}${transactionsPostFix}`).reply(404, mockData.notFound); - try { - await precheck.verifyAccount(parsedTx); + await precheck.verifyAccount(parsedTx, requestDetails); expectedError(); } catch (e: any) { expect(e).to.exist; @@ -463,7 +465,7 @@ describe('Precheck', async function () { it(`should not fail for matched account`, async function () { mock.onGet(`accounts/${parsedTx.from}${transactionsPostFix}`).reply(200, mirrorAccount); - const account = await precheck.verifyAccount(parsedTx); + const account = await precheck.verifyAccount(parsedTx, requestDetails); expect(account.ethereum_nonce).to.eq(defaultNonce); }); @@ -573,7 +575,7 @@ describe('Precheck', async function () { it('should accept legacy transactions', async () => { const signedLegacy = await signTransaction(defaultTx); - expect(precheck.transactionType(ethers.Transaction.from(signedLegacy))).not.to.throw; + expect(() => precheck.transactionType(ethers.Transaction.from(signedLegacy), requestDetails)).not.to.throw; }); it('should accept London transactions', async () => { @@ -583,7 +585,7 @@ describe('Precheck', async function () { maxPriorityFeePerGas: defaultGasPrice, maxFeePerGas: defaultGasPrice, }); - expect(precheck.transactionType(ethers.Transaction.from(signedLondon))).not.to.throw; + expect(() => precheck.transactionType(ethers.Transaction.from(signedLondon), requestDetails)).not.to.throw; }); it('should reject Cancun transactions', async () => { @@ -595,7 +597,7 @@ describe('Precheck', async function () { maxFeePerBlobGas: defaultGasPrice, blobVersionedHashes: [blobVersionedHash], }); - precheck.transactionType(ethers.Transaction.from(signedCancun)); + precheck.transactionType(ethers.Transaction.from(signedCancun), requestDetails); } catch (e) { error = e; } diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts index 29d8cffc04..5b128ece92 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts @@ -28,12 +28,17 @@ import { EthAddressHbarSpendingPlanNotFoundError } from '../../../../src/lib/db/ import { randomBytes, uuidV4 } from 'ethers'; import { Registry } from 'prom-client'; import { useInMemoryRedisServer } from '../../../helpers'; +import { RequestDetails } from '../../../../dist/lib/types'; chai.use(chaiAsPromised); describe('EthAddressHbarSpendingPlanRepository', function () { const logger = pino(); const registry = new Registry(); + const requestDetails = new RequestDetails({ + requestId: 'ethAddressHbarSpendingPlanRepositoryTest', + ipAddress: '0.0.0.0', + }); const tests = (isSharedCacheEnabled: boolean) => { let cacheService: CacheService; @@ -69,15 +74,15 @@ describe('EthAddressHbarSpendingPlanRepository', function () { it('retrieves an address plan by address', async () => { const ethAddress = '0x123'; const addressPlan: IEthAddressHbarSpendingPlan = { ethAddress, planId: uuidV4(randomBytes(16)) }; - await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test'); + await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test', requestDetails); - const result = await repository.findByAddress(ethAddress); + const result = await repository.findByAddress(ethAddress, requestDetails); expect(result).to.deep.equal(addressPlan); }); it('throws an error if address plan is not found', async () => { const ethAddress = '0xnonexistent'; - await expect(repository.findByAddress(ethAddress)).to.be.eventually.rejectedWith( + await expect(repository.findByAddress(ethAddress, requestDetails)).to.be.eventually.rejectedWith( EthAddressHbarSpendingPlanNotFoundError, `EthAddressHbarSpendingPlan with address ${ethAddress} not found`, ); @@ -89,10 +94,11 @@ describe('EthAddressHbarSpendingPlanRepository', function () { const ethAddress = '0x123'; const addressPlan: IEthAddressHbarSpendingPlan = { ethAddress, planId: uuidV4(randomBytes(16)) }; - await repository.save(addressPlan); + await repository.save(addressPlan, requestDetails); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ethAddress}`, 'test', + requestDetails, ); expect(result).to.deep.equal(addressPlan); }); @@ -100,14 +106,15 @@ describe('EthAddressHbarSpendingPlanRepository', function () { it('overwrites an existing address plan', async () => { const ethAddress = '0x123'; const addressPlan: IEthAddressHbarSpendingPlan = { ethAddress, planId: uuidV4(randomBytes(16)) }; - await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test'); + await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test', requestDetails); const newPlanId = uuidV4(randomBytes(16)); const newAddressPlan: IEthAddressHbarSpendingPlan = { ethAddress, planId: newPlanId }; - await repository.save(newAddressPlan); + await repository.save(newAddressPlan, requestDetails); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ethAddress}`, 'test', + requestDetails, ); expect(result).to.deep.equal(newAddressPlan); }); @@ -117,19 +124,20 @@ describe('EthAddressHbarSpendingPlanRepository', function () { it('deletes an address plan successfully', async () => { const ethAddress = '0x123'; const addressPlan: IEthAddressHbarSpendingPlan = { ethAddress, planId: uuidV4(randomBytes(16)) }; - await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test'); + await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test', requestDetails); - await repository.delete(ethAddress); + await repository.delete(ethAddress, requestDetails); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ethAddress}`, 'test', + requestDetails, ); expect(result).to.be.null; }); it('does not throw an error if address plan to delete does not exist', async () => { const ethAddress = '0xnonexistent'; - await expect(repository.delete(ethAddress)).to.be.fulfilled; + await expect(repository.delete(ethAddress, requestDetails)).to.be.fulfilled; }); }); }; diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts index df230cfa13..3daa0883a0 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts @@ -33,12 +33,14 @@ import { IHbarSpendingRecord } from '../../../../src/lib/db/types/hbarLimiter/hb import { SubscriptionType } from '../../../../src/lib/db/types/hbarLimiter/subscriptionType'; import { IDetailedHbarSpendingPlan } from '../../../../src/lib/db/types/hbarLimiter/hbarSpendingPlan'; import { useInMemoryRedisServer } from '../../../helpers'; +import { RequestDetails } from '../../../../src/lib/types'; chai.use(chaiAsPromised); describe('HbarSpendingPlanRepository', function () { const logger = pino(); const registry = new Registry(); + const requestDetails = new RequestDetails({ requestId: 'hbarSpendingPlanRepositoryTest', ipAddress: '0.0.0.0' }); const tests = (isSharedCacheEnabled: boolean) => { let cacheService: CacheService; @@ -65,20 +67,22 @@ describe('HbarSpendingPlanRepository', function () { } afterEach(async () => { - await cacheService.clear(); + await cacheService.clear(requestDetails); }); describe('create', () => { it('creates a plan successfully', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); - await expect(repository.findByIdWithDetails(createdPlan.id)).to.be.eventually.deep.equal(createdPlan); + const createdPlan = await repository.create(subscriptionType, requestDetails); + await expect(repository.findByIdWithDetails(createdPlan.id, requestDetails)).to.be.eventually.deep.equal( + createdPlan, + ); }); }); describe('findById', () => { it('throws an error if plan is not found by ID', async () => { - await expect(repository.findById('non-existent-id')).to.be.eventually.rejectedWith( + await expect(repository.findById('non-existent-id', requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotFoundError, `HbarSpendingPlan with ID non-existent-id not found`, ); @@ -86,14 +90,14 @@ describe('HbarSpendingPlanRepository', function () { it('returns a plan by ID', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); - await expect(repository.findById(createdPlan.id)).to.be.eventually.deep.equal(createdPlan); + const createdPlan = await repository.create(subscriptionType, requestDetails); + await expect(repository.findById(createdPlan.id, requestDetails)).to.be.eventually.deep.equal(createdPlan); }); }); describe('findByIdWithDetails', () => { it('throws an error if plan is not found by ID', async () => { - await expect(repository.findByIdWithDetails('non-existent-id')).to.be.eventually.rejectedWith( + await expect(repository.findByIdWithDetails('non-existent-id', requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotFoundError, `HbarSpendingPlan with ID non-existent-id not found`, ); @@ -101,14 +105,16 @@ describe('HbarSpendingPlanRepository', function () { it('returns a plan by ID', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); - await expect(repository.findByIdWithDetails(createdPlan.id)).to.be.eventually.deep.equal(createdPlan); + const createdPlan = await repository.create(subscriptionType, requestDetails); + await expect(repository.findByIdWithDetails(createdPlan.id, requestDetails)).to.be.eventually.deep.equal( + createdPlan, + ); }); }); describe('getSpendingHistory', () => { it('throws an error if plan not found by ID', async () => { - await expect(repository.getSpendingHistory('non-existent-id')).to.be.eventually.rejectedWith( + await expect(repository.getSpendingHistory('non-existent-id', requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotFoundError, `HbarSpendingPlan with ID non-existent-id not found`, ); @@ -116,20 +122,20 @@ describe('HbarSpendingPlanRepository', function () { it('returns an empty array if spending history is empty', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); - const spendingHistory = await repository.getSpendingHistory(createdPlan.id); + const createdPlan = await repository.create(subscriptionType, requestDetails); + const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.deep.equal([]); }); it('retrieves spending history for a plan', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); const key = `${repository['collectionKey']}:${createdPlan.id}:spendingHistory`; const hbarSpending = { amount: 100, timestamp: new Date() } as IHbarSpendingRecord; - await cacheService.rPush(key, hbarSpending, 'test'); + await cacheService.rPush(key, hbarSpending, 'test', requestDetails); - const spendingHistory = await repository.getSpendingHistory(createdPlan.id); + const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.have.lengthOf(1); expect(spendingHistory[0].amount).to.equal(hbarSpending.amount); expect(spendingHistory[0].timestamp).to.be.a('Date'); @@ -139,13 +145,13 @@ describe('HbarSpendingPlanRepository', function () { describe('addAmountToSpendingHistory', () => { it('adds amount to spending history', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); - await expect(repository.getSpendingHistory(createdPlan.id)).to.be.eventually.deep.equal([]); + const createdPlan = await repository.create(subscriptionType, requestDetails); + await expect(repository.getSpendingHistory(createdPlan.id, requestDetails)).to.be.eventually.deep.equal([]); const amount = 100; - await repository.addAmountToSpendingHistory(createdPlan.id, amount); + await repository.addAmountToSpendingHistory(createdPlan.id, amount, requestDetails); - const spendingHistory = await repository.getSpendingHistory(createdPlan.id); + const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.have.lengthOf(1); expect(spendingHistory[0].amount).to.equal(amount); expect(spendingHistory[0].timestamp).to.be.a('Date'); @@ -153,15 +159,15 @@ describe('HbarSpendingPlanRepository', function () { it('adds multiple amounts to spending history', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); - await expect(repository.getSpendingHistory(createdPlan.id)).to.be.eventually.deep.equal([]); + const createdPlan = await repository.create(subscriptionType, requestDetails); + await expect(repository.getSpendingHistory(createdPlan.id, requestDetails)).to.be.eventually.deep.equal([]); const amounts = [100, 200, 300]; for (const amount of amounts) { - await repository.addAmountToSpendingHistory(createdPlan.id, amount); + await repository.addAmountToSpendingHistory(createdPlan.id, amount, requestDetails); } - const spendingHistory = await repository.getSpendingHistory(createdPlan.id); + const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.have.lengthOf(3); expect(spendingHistory.map((entry) => entry.amount)).to.deep.equal(amounts); }); @@ -170,7 +176,7 @@ describe('HbarSpendingPlanRepository', function () { const id = 'non-existent-id'; const amount = 100; - await expect(repository.addAmountToSpendingHistory(id, amount)).to.be.eventually.rejectedWith( + await expect(repository.addAmountToSpendingHistory(id, amount, requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotFoundError, `HbarSpendingPlan with ID ${id} not found`, ); @@ -197,34 +203,34 @@ describe('HbarSpendingPlanRepository', function () { it('retrieves spent today for a plan', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); const amount = 50; - await repository.addAmountToSpentToday(createdPlan.id, amount); + await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); - const spentToday = await repository.getSpentToday(createdPlan.id); + const spentToday = await repository.getSpentToday(createdPlan.id, requestDetails); expect(spentToday).to.equal(amount); }); it('returns 0 if spent today key does not exist', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); - const spentToday = await repository.getSpentToday(createdPlan.id); + const spentToday = await repository.getSpentToday(createdPlan.id, requestDetails); expect(spentToday).to.equal(0); }); it('should expire spent today key at the end of the day', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); const amount = 50; - await repository.addAmountToSpentToday(createdPlan.id, amount); - await expect(repository.getSpentToday(createdPlan.id)).to.eventually.equal(amount); + await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); + await expect(repository.getSpentToday(createdPlan.id, requestDetails)).to.eventually.equal(amount); await new Promise((resolve) => setTimeout(resolve, mockedOneDayInMillis + 100)); - await expect(repository.getSpentToday(createdPlan.id)).to.eventually.equal(0); + await expect(repository.getSpentToday(createdPlan.id, requestDetails)).to.eventually.equal(0); }); }); @@ -232,42 +238,42 @@ describe('HbarSpendingPlanRepository', function () { it('resets all spent today entries', async () => { const plans: IDetailedHbarSpendingPlan[] = []; for (const subscriptionType of Object.values(SubscriptionType)) { - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); plans.push(createdPlan); const amount = 50 * plans.length; - await repository.addAmountToSpentToday(createdPlan.id, amount); - await expect(repository.getSpentToday(createdPlan.id)).to.eventually.equal(amount); + await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); + await expect(repository.getSpentToday(createdPlan.id, requestDetails)).to.eventually.equal(amount); } - await repository.resetAllSpentTodayEntries(); + await repository.resetAllSpentTodayEntries(requestDetails); for (const plan of plans) { - await expect(repository.getSpentToday(plan.id)).to.eventually.equal(0); + await expect(repository.getSpentToday(plan.id, requestDetails)).to.eventually.equal(0); } }); it('does not throw an error if no spent today keys exist', async () => { - await expect(repository.resetAllSpentTodayEntries()).to.not.be.rejected; + await expect(repository.resetAllSpentTodayEntries(requestDetails)).to.not.be.rejected; }); }); describe('addAmountToSpentToday', () => { it('adds amount to spent today', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); const amount = 50; - await repository.addAmountToSpentToday(createdPlan.id, amount); + await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); - const plan = await repository.findByIdWithDetails(createdPlan.id); + const plan = await repository.findByIdWithDetails(createdPlan.id, requestDetails); expect(plan).to.not.be.null; expect(plan!.spentToday).to.equal(amount); // Add more to spent today const newAmount = 100; - await repository.addAmountToSpentToday(createdPlan.id, newAmount); + await repository.addAmountToSpentToday(createdPlan.id, newAmount, requestDetails); - const updatedPlan = await repository.findByIdWithDetails(createdPlan.id); + const updatedPlan = await repository.findByIdWithDetails(createdPlan.id, requestDetails); expect(updatedPlan).to.not.be.null; expect(updatedPlan!.spentToday).to.equal(amount + newAmount); }); @@ -276,7 +282,7 @@ describe('HbarSpendingPlanRepository', function () { const id = 'non-existent-id'; const amount = 50; - await expect(repository.addAmountToSpentToday(id, amount)).to.be.eventually.rejectedWith( + await expect(repository.addAmountToSpentToday(id, amount, requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotFoundError, `HbarSpendingPlan with ID ${id} not found`, ); @@ -284,14 +290,16 @@ describe('HbarSpendingPlanRepository', function () { it('throws an error if plan is not active when adding to spent today', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${createdPlan.id}`; - await cacheService.set(key, { ...createdPlan, active: false }, 'test'); + await cacheService.set(key, { ...createdPlan, active: false }, 'test', requestDetails); const amount = 50; - await expect(repository.addAmountToSpentToday(createdPlan.id, amount)).to.be.eventually.rejectedWith( + await expect( + repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails), + ).to.be.eventually.rejectedWith( HbarSpendingPlanNotActiveError, `HbarSpendingPlan with ID ${createdPlan.id} is not active`, ); @@ -301,7 +309,7 @@ describe('HbarSpendingPlanRepository', function () { describe('checkExistsAndActive', () => { it('throws error if plan does not exist when checking if exists and active', async () => { const id = 'non-existent-id'; - await expect(repository.checkExistsAndActive(id)).to.be.eventually.rejectedWith( + await expect(repository.checkExistsAndActive(id, requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotFoundError, `HbarSpendingPlan with ID ${id} not found`, ); @@ -309,13 +317,13 @@ describe('HbarSpendingPlanRepository', function () { it('throws error if plan is not active when checking if exists and active', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType); + const createdPlan = await repository.create(subscriptionType, requestDetails); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${createdPlan.id}`; - await cacheService.set(key, { ...createdPlan, active: false }, 'test'); + await cacheService.set(key, { ...createdPlan, active: false }, 'test', requestDetails); - await expect(repository.checkExistsAndActive(createdPlan.id)).to.be.eventually.rejectedWith( + await expect(repository.checkExistsAndActive(createdPlan.id, requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotActiveError, `HbarSpendingPlan with ID ${createdPlan.id} is not active`, ); @@ -325,47 +333,56 @@ describe('HbarSpendingPlanRepository', function () { describe('findAllActiveBySubscriptionType', () => { it('returns an empty array if no active plans exist for the subscription type', async () => { const subscriptionType = SubscriptionType.BASIC; - const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType); + const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); expect(activePlans).to.deep.equal([]); }); it('returns all active plans for the subscription type', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan1 = await repository.create(subscriptionType); - const createdPlan2 = await repository.create(subscriptionType); + const createdPlan1 = await repository.create(subscriptionType, requestDetails); + const createdPlan2 = await repository.create(subscriptionType, requestDetails); - const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType); + const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); expect(activePlans).to.have.lengthOf(2); expect(activePlans.map((plan) => plan.id)).to.include.members([createdPlan1.id, createdPlan2.id]); }); it('does not return inactive plans for the subscription type', async () => { const subscriptionType = SubscriptionType.BASIC; - const activePlan = await repository.create(subscriptionType); - const inactivePlan = await repository.create(subscriptionType); + const activePlan = await repository.create(subscriptionType, requestDetails); + const inactivePlan = await repository.create(subscriptionType, requestDetails); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${inactivePlan.id}`; - await cacheService.set(key, { ...inactivePlan, active: false }, 'test'); + await cacheService.set(key, { ...inactivePlan, active: false }, 'test', requestDetails); - const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType); + const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); expect(activePlans).to.deep.equal([activePlan]); }); it('returns only active plans for the specified subscription type', async () => { - const basicPlan = await repository.create(SubscriptionType.BASIC); - const extendedPlan = await repository.create(SubscriptionType.EXTENDED); - const privilegedPlan = await repository.create(SubscriptionType.PRIVILEGED); + const basicPlan = await repository.create(SubscriptionType.BASIC, requestDetails); + const extendedPlan = await repository.create(SubscriptionType.EXTENDED, requestDetails); + const privilegedPlan = await repository.create(SubscriptionType.PRIVILEGED, requestDetails); - const activeBasicPlans = await repository.findAllActiveBySubscriptionType(SubscriptionType.BASIC); + const activeBasicPlans = await repository.findAllActiveBySubscriptionType( + SubscriptionType.BASIC, + requestDetails, + ); expect(activeBasicPlans).to.have.lengthOf(1); expect(activeBasicPlans[0].id).to.equal(basicPlan.id); - const activeExtendedPlans = await repository.findAllActiveBySubscriptionType(SubscriptionType.EXTENDED); + const activeExtendedPlans = await repository.findAllActiveBySubscriptionType( + SubscriptionType.EXTENDED, + requestDetails, + ); expect(activeExtendedPlans).to.have.lengthOf(1); expect(activeExtendedPlans[0].id).to.equal(extendedPlan.id); - const activePrivilegedPlans = await repository.findAllActiveBySubscriptionType(SubscriptionType.PRIVILEGED); + const activePrivilegedPlans = await repository.findAllActiveBySubscriptionType( + SubscriptionType.PRIVILEGED, + requestDetails, + ); expect(activePrivilegedPlans).to.have.lengthOf(1); expect(activePrivilegedPlans[0].id).to.equal(privilegedPlan.id); }); diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts index 2debf7cee8..e3d67295a6 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts @@ -28,12 +28,14 @@ import { IPAddressHbarSpendingPlanNotFoundError } from '../../../../src/lib/db/t import { randomBytes, uuidV4 } from 'ethers'; import { Registry } from 'prom-client'; import { useInMemoryRedisServer } from '../../../helpers'; +import { RequestDetails } from '../../../../src/lib/types'; chai.use(chaiAsPromised); describe('IPAddressHbarSpendingPlanRepository', function () { const logger = pino(); const registry = new Registry(); + const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); const tests = (isSharedCacheEnabled: boolean) => { let cacheService: CacheService; @@ -60,16 +62,16 @@ describe('IPAddressHbarSpendingPlanRepository', function () { describe('findByAddress', () => { it('retrieves an address plan by ip', async () => { const addressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: uuidV4(randomBytes(16)) }; - await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test'); + await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test', requestDetails); - const result = await repository.findByAddress(ipAddress); + const result = await repository.findByAddress(ipAddress, requestDetails); expect(result).to.deep.equal(addressPlan); }); it('throws an error if address plan is not found', async () => { - await expect(repository.findByAddress(nonExistingIpAddress)).to.be.eventually.rejectedWith( + await expect(repository.findByAddress(nonExistingIpAddress, requestDetails)).to.be.eventually.rejectedWith( IPAddressHbarSpendingPlanNotFoundError, - `IPAddressHbarSpendingPlan with address ${nonExistingIpAddress} not found`, + `IPAddressHbarSpendingPlan not found`, ); }); }); @@ -78,24 +80,26 @@ describe('IPAddressHbarSpendingPlanRepository', function () { it('saves an address plan successfully', async () => { const addressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: uuidV4(randomBytes(16)) }; - await repository.save(addressPlan); + await repository.save(addressPlan, requestDetails); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ipAddress}`, 'test', + requestDetails, ); expect(result).to.deep.equal(addressPlan); }); it('overwrites an existing address plan', async () => { const addressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: uuidV4(randomBytes(16)) }; - await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test'); + await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test', requestDetails); const newPlanId = uuidV4(randomBytes(16)); const newAddressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: newPlanId }; - await repository.save(newAddressPlan); + await repository.save(newAddressPlan, requestDetails); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ipAddress}`, 'test', + requestDetails, ); expect(result).to.deep.equal(newAddressPlan); }); @@ -104,18 +108,19 @@ describe('IPAddressHbarSpendingPlanRepository', function () { describe('delete', () => { it('deletes an address plan successfully', async () => { const addressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: uuidV4(randomBytes(16)) }; - await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test'); + await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test', requestDetails); - await repository.delete(ipAddress); + await repository.delete(ipAddress, requestDetails); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ipAddress}`, 'test', + requestDetails, ); expect(result).to.be.null; }); it('does not throw an error if address plan to delete does not exist', async () => { - await expect(repository.delete(nonExistingIpAddress)).to.be.fulfilled; + await expect(repository.delete(nonExistingIpAddress, requestDetails)).to.be.fulfilled; }); }); }; diff --git a/packages/relay/tests/lib/sdkClient.spec.ts b/packages/relay/tests/lib/sdkClient.spec.ts index 5fb374b0d3..5aac26123b 100644 --- a/packages/relay/tests/lib/sdkClient.spec.ts +++ b/packages/relay/tests/lib/sdkClient.spec.ts @@ -25,7 +25,6 @@ import { resolve } from 'path'; import * as sinon from 'sinon'; import { config } from 'dotenv'; import { Context } from 'mocha'; -import { v4 as uuid } from 'uuid'; import EventEmitter from 'events'; import { Registry } from 'prom-client'; import { Utils } from '../../src/utils'; @@ -59,6 +58,7 @@ import { FileDeleteTransaction, TransactionRecordQuery, } from '@hashgraph/sdk'; +import { RequestDetails } from '../../src/lib/types'; config({ path: resolve(__dirname, '../test.env') }); const registry = new Registry(); @@ -76,6 +76,7 @@ describe('SdkClient', async function () { let metricService: MetricService; let mirrorNodeClient: MirrorNodeClient; + const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); const feeSchedules = { current: { transactionFeeSchedule: [ @@ -159,7 +160,14 @@ describe('SdkClient', async function () { it('executes the query', async () => { queryStub.returns(successResponse); - let { resp, cost } = await sdkClient.increaseCostAndRetryExecution(contractCallQuery, baseCost, client, 3, 0); + let { resp, cost } = await sdkClient.increaseCostAndRetryExecution( + contractCallQuery, + baseCost, + client, + 3, + 0, + requestDetails, + ); expect(resp).to.eq(successResponse); expect(cost.toTinybars().toNumber()).to.eq(costTinybars); expect(queryStub.callCount).to.eq(1); @@ -171,7 +179,14 @@ describe('SdkClient', async function () { }); queryStub.onCall(1).returns(successResponse); - let { resp, cost } = await sdkClient.increaseCostAndRetryExecution(contractCallQuery, baseCost, client, 3, 0); + let { resp, cost } = await sdkClient.increaseCostAndRetryExecution( + contractCallQuery, + baseCost, + client, + 3, + 0, + requestDetails, + ); expect(resp).to.eq(successResponse); expect(cost.toTinybars().toNumber()).to.eq(costTinybars * constants.QUERY_COST_INCREMENTATION_STEP); expect(queryStub.callCount).to.eq(2); @@ -188,7 +203,14 @@ describe('SdkClient', async function () { queryStub.onCall(2).returns(successResponse); - let { resp, cost } = await sdkClient.increaseCostAndRetryExecution(contractCallQuery, baseCost, client, 3, 0); + let { resp, cost } = await sdkClient.increaseCostAndRetryExecution( + contractCallQuery, + baseCost, + client, + 3, + 0, + requestDetails, + ); expect(resp).to.eq(successResponse); expect(cost.toTinybars().toNumber()).to.eq( Math.floor(costTinybars * Math.pow(constants.QUERY_COST_INCREMENTATION_STEP, 2)), @@ -202,7 +224,7 @@ describe('SdkClient', async function () { status: Status.InsufficientTxFee, }); - await sdkClient.increaseCostAndRetryExecution(contractCallQuery, baseCost, client, 3, 0); + await sdkClient.increaseCostAndRetryExecution(contractCallQuery, baseCost, client, 3, 0, requestDetails); } catch (e: any) { expect(queryStub.callCount).to.eq(4); expect(e.status).to.eq(Status.InsufficientTxFee); @@ -219,7 +241,7 @@ describe('SdkClient', async function () { const convertGasPriceToTinyBarsStub = sinon.stub(sdkClient, 'convertGasPriceToTinyBars').callsFake(() => 0x160c); for (let i = 0; i < 5; i++) { - await sdkClient.getTinyBarGasFee(''); + await sdkClient.getTinyBarGasFee('', requestDetails); } sinon.assert.calledOnce(getFeeScheduleStub); @@ -2243,12 +2265,10 @@ describe('SdkClient', async function () { const mockedConstructorName = 'constructor_name'; const mockedInteractingEntity = 'interacting_entity'; - let requestId: string; let hbarLimitMock: sinon.SinonMock; let sdkClientMock: sinon.SinonMock; beforeEach(() => { - requestId = uuid(); hbarLimitMock = sinon.mock(hbarLimiter); sdkClientMock = sinon.mock(sdkClient); mock = new MockAdapter(instance); @@ -2279,10 +2299,10 @@ describe('SdkClient', async function () { await sdkClient.submitEthereumTransaction( transactionBuffer, mockedCallerName, + requestDetails, randomAccountAddress, mockedNetworkGasPrice, mockedExchangeRateIncents, - requestId, ); expect.fail(`Expected an error but nothing was thrown`); } catch (error: any) { @@ -2338,10 +2358,10 @@ describe('SdkClient', async function () { await sdkClient.submitEthereumTransaction( transactionBuffer, mockedCallerName, + requestDetails, randomAccountAddress, mockedNetworkGasPrice, mockedExchangeRateIncents, - requestId, ); expect(queryStub.called).to.be.true; @@ -2384,7 +2404,7 @@ describe('SdkClient', async function () { const response = await sdkClient.createFile( callData, client, - requestId, + requestDetails, mockedCallerName, mockedInteractingEntity, randomAccountAddress, @@ -2421,7 +2441,7 @@ describe('SdkClient', async function () { new FileAppendTransaction(), mockedCallerName, mockedInteractingEntity, - requestId, + requestDetails, true, randomAccountAddress, ); @@ -2447,7 +2467,7 @@ describe('SdkClient', async function () { new FileAppendTransaction(), mockedCallerName, mockedInteractingEntity, - requestId, + requestDetails, true, randomAccountAddress, ); @@ -2467,8 +2487,9 @@ describe('SdkClient', async function () { await sdkClient.submitEthereumTransaction( transactionBuffer, mockedCallerName, - requestId, + requestDetails, randomAccountAddress, + mockedNetworkGasPrice, mockedExchangeRateIncents, ); expect.fail(`Expected an error but nothing was thrown`); @@ -2500,7 +2521,7 @@ describe('SdkClient', async function () { const response = await sdkClient.createFile( callData, client, - requestId, + requestDetails, mockedCallerName, mockedInteractingEntity, randomAccountAddress, @@ -2525,7 +2546,13 @@ describe('SdkClient', async function () { hbarLimitMock.expects('addExpense').withArgs(mockedTransactionRecordFee).once(); hbarLimitMock.expects('shouldLimit').never(); - await sdkClient.deleteFile(fileId, requestId, mockedCallerName, mockedInteractingEntity, randomAccountAddress); + await sdkClient.deleteFile( + fileId, + requestDetails, + mockedCallerName, + mockedInteractingEntity, + randomAccountAddress, + ); expect(deleteFileStub.called).to.be.true; expect(fileInfoQueryStub.called).to.be.true; @@ -2543,7 +2570,7 @@ describe('SdkClient', async function () { client, mockedCallerName, mockedInteractingEntity, - requestId, + requestDetails, ); expect(result).to.equal(fileInfo); @@ -2562,7 +2589,7 @@ describe('SdkClient', async function () { client, mockedCallerName, mockedInteractingEntity, - requestId, + requestDetails, ); expect(result).to.equal(fileInfo); @@ -2590,7 +2617,7 @@ describe('SdkClient', async function () { new EthereumTransaction().setCallDataFileId(fileId).setEthereumData(transactionBuffer), mockedCallerName, mockedInteractingEntity, - requestId, + requestDetails, true, randomAccountAddress, ); @@ -2640,7 +2667,7 @@ describe('SdkClient', async function () { new EthereumTransaction().setCallDataFileId(fileId).setEthereumData(transactionBuffer), mockedCallerName, mockedInteractingEntity, - requestId, + requestDetails, true, randomAccountAddress, ); @@ -2672,9 +2699,9 @@ describe('SdkClient', async function () { const transactionRecordMetrics = await sdkClient.getTransactionRecordMetrics( transactionId.toString(), mockedCallerName, - requestId, mockedConstructorName, accountId.toString(), + requestDetails, ); expect(transactionRecordStub.called).to.be.true; @@ -2691,12 +2718,12 @@ describe('SdkClient', async function () { await sdkClient.getTransactionRecordMetrics( transactionId.toString(), mockedCallerName, - requestId, mockedConstructorName, accountId.toString(), + requestDetails, ); expect.fail('should have thrown an error'); - } catch (error) { + } catch (error: any) { expect(error.status).to.eq(expectedError.status); expect(error.message).to.eq(expectedError.message); } diff --git a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts index 95ad7b6deb..931e537f0a 100644 --- a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts +++ b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts @@ -27,6 +27,7 @@ import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import { useInMemoryRedisServer } from '../../../helpers'; +import { RequestDetails } from '../../../../dist/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); const logger = pino(); @@ -39,7 +40,7 @@ chai.use(chaiAsPromised); describe('CacheService Test Suite', async function () { this.timeout(10000); - + const requestDetails = new RequestDetails({ requestId: 'cacheServiceTest', ipAddress: '0.0.0.0' }); const describeKeysTestSuite = () => { describe('keys', async function () { it('should retrieve all keys', async function () { @@ -48,9 +49,9 @@ describe('CacheService Test Suite', async function () { entries['key2'] = 'value2'; entries['key3'] = 'value3'; - await cacheService.multiSet(entries, callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); - const keys = await cacheService.keys('*', callingMethod); + const keys = await cacheService.keys('*', callingMethod, requestDetails); expect(keys).to.have.members(Object.keys(entries)); }); @@ -60,9 +61,9 @@ describe('CacheService Test Suite', async function () { entries['key2'] = 'value2'; entries['key3'] = 'value3'; - await cacheService.multiSet(entries, callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); - const keys = await cacheService.keys('key*', callingMethod); + const keys = await cacheService.keys('key*', callingMethod, requestDetails); expect(keys).to.have.members(Object.keys(entries)); }); @@ -72,9 +73,9 @@ describe('CacheService Test Suite', async function () { entries['key2'] = 'value2'; entries['key3'] = 'value3'; - await cacheService.multiSet(entries, callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); - const keys = await cacheService.keys('key?', callingMethod); + const keys = await cacheService.keys('key?', callingMethod, requestDetails); expect(keys).to.have.members(Object.keys(entries)); }); @@ -84,9 +85,9 @@ describe('CacheService Test Suite', async function () { entries['key2'] = 'value2'; entries['key3'] = 'value3'; - await cacheService.multiSet(entries, callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); - const keys = await cacheService.keys('key[1-2]', callingMethod); + const keys = await cacheService.keys('key[1-2]', callingMethod, requestDetails); expect(keys).to.have.members(['key1', 'key2']); }); @@ -96,10 +97,10 @@ describe('CacheService Test Suite', async function () { entries['key2'] = 'value2'; entries['key3'] = 'value3'; - await cacheService.multiSet(entries, callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); // [^3] should match all keys except key3 - const keys = await cacheService.keys('key[^3]', callingMethod); + const keys = await cacheService.keys('key[^3]', callingMethod, requestDetails); expect(keys).to.have.members(['key1', 'key2']); }); @@ -109,9 +110,9 @@ describe('CacheService Test Suite', async function () { entries['keyb'] = 'value2'; entries['keyc'] = 'value3'; - await cacheService.multiSet(entries, callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); - const keys = await cacheService.keys('key[a-c]', callingMethod); + const keys = await cacheService.keys('key[a-c]', callingMethod, requestDetails); expect(keys).to.have.members(Object.keys(entries)); }); @@ -119,9 +120,9 @@ describe('CacheService Test Suite', async function () { const key = 'h*llo'; const value = 'value'; - await cacheService.set(key, value, callingMethod); + await cacheService.set(key, value, callingMethod, requestDetails); - const keys = await cacheService.keys('h*llo', callingMethod); + const keys = await cacheService.keys('h*llo', callingMethod, requestDetails); expect(keys).to.have.members([key]); }); @@ -132,8 +133,8 @@ describe('CacheService Test Suite', async function () { entries['key3'] = 'value3'; await cacheService.disconnectRedisClient(); - await cacheService.multiSet(entries, callingMethod); - const keys = await cacheService.keys('*', callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); + const keys = await cacheService.keys('*', callingMethod, requestDetails); expect(keys).to.have.members(Object.keys(entries)); }); }); @@ -146,15 +147,15 @@ describe('CacheService Test Suite', async function () { }); this.afterEach(async () => { - await cacheService.clear(); + await cacheService.clear(requestDetails); }); it('should be able to set and get from internal cache', async function () { const key = 'string'; const value = 'value'; - await cacheService.set(key, value, callingMethod); - const cachedValue = await cacheService.getAsync(key, callingMethod); + await cacheService.set(key, value, callingMethod, requestDetails); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).eq(value); }); @@ -163,9 +164,9 @@ describe('CacheService Test Suite', async function () { const key = 'string'; const value = 'value'; - await cacheService.set(key, value, callingMethod); - await cacheService.delete(key, callingMethod); - const cachedValue = await cacheService.getAsync(key, callingMethod); + await cacheService.set(key, value, callingMethod, requestDetails); + await cacheService.delete(key, callingMethod, requestDetails); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).to.be.null; }); @@ -174,8 +175,8 @@ describe('CacheService Test Suite', async function () { const key = 'string'; const value = 'value'; - await cacheService.set(key, value, callingMethod); - const cachedValue = await cacheService.getAsync(key, callingMethod); + await cacheService.set(key, value, callingMethod, requestDetails); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).eq(value); }); @@ -186,10 +187,10 @@ describe('CacheService Test Suite', async function () { entries['key2'] = 'value2'; entries['key3'] = 'value3'; - await cacheService.multiSet(entries, callingMethod); + await cacheService.multiSet(entries, callingMethod, requestDetails); for (const [key, value] of Object.entries(entries)) { - const valueFromCache = await cacheService.getAsync(key, callingMethod); + const valueFromCache = await cacheService.getAsync(key, callingMethod, requestDetails); expect(valueFromCache).eq(value); } }); @@ -199,8 +200,8 @@ describe('CacheService Test Suite', async function () { const key = 'counter'; const amount = 5; - await cacheService.set(key, 10, callingMethod); - const newValue = await cacheService.incrBy(key, amount, callingMethod); + await cacheService.set(key, 10, callingMethod, requestDetails); + const newValue = await cacheService.incrBy(key, amount, callingMethod, requestDetails); expect(newValue).to.equal(15); }); @@ -211,8 +212,8 @@ describe('CacheService Test Suite', async function () { const key = 'list'; const value = 'item'; - await cacheService.rPush(key, value, callingMethod); - const cachedValue = await cacheService.getAsync(key, callingMethod); + await cacheService.rPush(key, value, callingMethod, requestDetails); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).to.deep.equal([value]); }); @@ -223,8 +224,8 @@ describe('CacheService Test Suite', async function () { const key = 'list'; const values = ['item1', 'item2', 'item3']; - await cacheService.set(key, values, callingMethod); - const range = await cacheService.lRange(key, 0, 1, callingMethod); + await cacheService.set(key, values, callingMethod, requestDetails); + const range = await cacheService.lRange(key, 0, 1, callingMethod, requestDetails); expect(range).to.deep.equal(['item1', 'item2']); }); @@ -233,8 +234,8 @@ describe('CacheService Test Suite', async function () { const key = 'list'; const values = ['item1', 'item2', 'item3']; - await cacheService.set(key, values, callingMethod); - const range = await cacheService.lRange(key, -2, -1, callingMethod); + await cacheService.set(key, values, callingMethod, requestDetails); + const range = await cacheService.lRange(key, -2, -1, callingMethod, requestDetails); expect(range).to.deep.equal(['item2', 'item3']); }); @@ -294,16 +295,16 @@ describe('CacheService Test Suite', async function () { }); this.afterEach(async () => { - await cacheService.clear(); + await cacheService.clear(requestDetails); }); it('should be able to set and get from shared cache', async function () { const key = 'string'; const value = 'value'; - await cacheService.set(key, value, callingMethod); + await cacheService.set(key, value, callingMethod, requestDetails); - const cachedValue = await cacheService.getAsync(key, callingMethod); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).eq(value); }); @@ -311,11 +312,11 @@ describe('CacheService Test Suite', async function () { const key = 'string'; const value = 'value'; - await cacheService.set(key, value, callingMethod); + await cacheService.set(key, value, callingMethod, requestDetails); - await cacheService.delete(key, callingMethod); + await cacheService.delete(key, callingMethod, requestDetails); - const cachedValue = await cacheService.getAsync(key, callingMethod); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).to.be.null; }); @@ -323,17 +324,17 @@ describe('CacheService Test Suite', async function () { const key = 'string'; const value = 'value'; - await cacheService.set(key, value, callingMethod); + await cacheService.set(key, value, callingMethod, requestDetails); - const cachedValue = await cacheService.getAsync(key, callingMethod); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).eq(value); }); it('should be able to set using multiSet and get them separately using internal cache', async function () { - await cacheService.multiSet(multiSetEntries, callingMethod); + await cacheService.multiSet(multiSetEntries, callingMethod, requestDetails); for (const [key, value] of Object.entries(multiSetEntries)) { - const valueFromCache = await cacheService.getAsync(key, callingMethod); + const valueFromCache = await cacheService.getAsync(key, callingMethod, requestDetails); expect(valueFromCache).eq(value); } }); @@ -342,10 +343,10 @@ describe('CacheService Test Suite', async function () { // @ts-ignore cacheService['shouldMultiSet'] = false; - await cacheService.multiSet(multiSetEntries, callingMethod); + await cacheService.multiSet(multiSetEntries, callingMethod, requestDetails); for (const [key, value] of Object.entries(multiSetEntries)) { - const valueFromCache = await cacheService.getAsync(key, callingMethod); + const valueFromCache = await cacheService.getAsync(key, callingMethod, requestDetails); expect(valueFromCache).eq(value); } }); @@ -354,7 +355,7 @@ describe('CacheService Test Suite', async function () { const key = 'string'; await cacheService.disconnectRedisClient(); - const cachedValue = await cacheService.getAsync(key, callingMethod); + const cachedValue = await cacheService.getAsync(key, callingMethod, requestDetails); expect(cachedValue).eq(null); }); @@ -364,19 +365,19 @@ describe('CacheService Test Suite', async function () { await cacheService.disconnectRedisClient(); - await expect(cacheService.set(key, value, callingMethod)).to.eventually.not.be.rejected; + await expect(cacheService.set(key, value, callingMethod, requestDetails)).to.eventually.not.be.rejected; - const internalCacheRes = await cacheService.getAsync(key, callingMethod); + const internalCacheRes = await cacheService.getAsync(key, callingMethod, requestDetails); expect(internalCacheRes).to.eq(value); }); it('should be able to multiSet to internal cache in case of Redis error', async function () { await cacheService.disconnectRedisClient(); - await expect(cacheService.multiSet(multiSetEntries, callingMethod)).to.eventually.not.be.rejected; + await expect(cacheService.multiSet(multiSetEntries, callingMethod, requestDetails)).to.eventually.not.be.rejected; for (const [key, value] of Object.entries(multiSetEntries)) { - const internalCacheRes = await cacheService.getAsync(key, callingMethod); + const internalCacheRes = await cacheService.getAsync(key, callingMethod, requestDetails); expect(internalCacheRes).to.eq(value); } }); @@ -387,10 +388,10 @@ describe('CacheService Test Suite', async function () { await cacheService.disconnectRedisClient(); - await expect(cacheService.multiSet(multiSetEntries, callingMethod)).to.eventually.not.be.rejected; + await expect(cacheService.multiSet(multiSetEntries, callingMethod, requestDetails)).to.eventually.not.be.rejected; for (const [key, value] of Object.entries(multiSetEntries)) { - const internalCacheRes = await cacheService.getAsync(key, callingMethod); + const internalCacheRes = await cacheService.getAsync(key, callingMethod, requestDetails); expect(internalCacheRes).to.eq(value); } }); @@ -398,21 +399,21 @@ describe('CacheService Test Suite', async function () { it('should be able to clear from internal cache in case of Redis error', async function () { await cacheService.disconnectRedisClient(); - await expect(cacheService.clear()).to.eventually.not.be.rejected; + await expect(cacheService.clear(requestDetails)).to.eventually.not.be.rejected; }); it('should be able to delete from internal cache in case of Redis error', async function () { const key = 'string'; await cacheService.disconnectRedisClient(); - await expect(cacheService.delete(key, callingMethod)).to.eventually.not.be.rejected; + await expect(cacheService.delete(key, callingMethod, requestDetails)).to.eventually.not.be.rejected; }); it('should be able to set to shared cache', async function () { const key = 'string'; const value = 'value'; - await expect(cacheService.set(key, value, callingMethod)).to.eventually.not.be.rejected; + await expect(cacheService.set(key, value, callingMethod, requestDetails)).to.eventually.not.be.rejected; }); it('should be able to multiset to shared cache', async function () { @@ -420,13 +421,13 @@ describe('CacheService Test Suite', async function () { items['key1'] = 'value1'; items['key2'] = 'value2'; - await expect(cacheService.multiSet(items, callingMethod)).to.eventually.not.be.rejected; + await expect(cacheService.multiSet(items, callingMethod, requestDetails)).to.eventually.not.be.rejected; }); it('should be able to delete from shared cache', async function () { const key = 'string'; - await expect(cacheService.delete(key, callingMethod)).to.eventually.not.be.rejected; + await expect(cacheService.delete(key, callingMethod, requestDetails)).to.eventually.not.be.rejected; }); describe('incrBy', async function () { @@ -434,8 +435,8 @@ describe('CacheService Test Suite', async function () { const key = 'counter'; const amount = 5; - await cacheService.set(key, 10, callingMethod); - const newValue = await cacheService.incrBy(key, amount, callingMethod); + await cacheService.set(key, 10, callingMethod, requestDetails); + const newValue = await cacheService.incrBy(key, amount, callingMethod, requestDetails); expect(newValue).to.equal(15); }); @@ -444,8 +445,8 @@ describe('CacheService Test Suite', async function () { const key = 'counter'; const amount = 5; - await cacheService.set(key, 10, callingMethod); - const newValue = await cacheService.incrBy(key, amount, callingMethod); + await cacheService.set(key, 10, callingMethod, requestDetails); + const newValue = await cacheService.incrBy(key, amount, callingMethod, requestDetails); expect(newValue).to.equal(15); }); @@ -456,7 +457,7 @@ describe('CacheService Test Suite', async function () { await cacheService.disconnectRedisClient(); - const newValue = await cacheService.incrBy(key, amount, callingMethod); + const newValue = await cacheService.incrBy(key, amount, callingMethod, requestDetails); expect(newValue).to.equal(5); }); @@ -467,8 +468,8 @@ describe('CacheService Test Suite', async function () { const key = 'list'; const value = 'item'; - await cacheService.rPush(key, value, callingMethod); - const cachedValue = await cacheService.lRange(key, 0, -1, callingMethod); + await cacheService.rPush(key, value, callingMethod, requestDetails); + const cachedValue = await cacheService.lRange(key, 0, -1, callingMethod, requestDetails); expect(cachedValue).to.deep.equal([value]); }); @@ -479,8 +480,8 @@ describe('CacheService Test Suite', async function () { await cacheService.disconnectRedisClient(); - await cacheService.rPush(key, value, callingMethod); - const cachedValue = await cacheService.lRange(key, 0, -1, callingMethod); + await cacheService.rPush(key, value, callingMethod, requestDetails); + const cachedValue = await cacheService.lRange(key, 0, -1, callingMethod, requestDetails); expect(cachedValue).to.deep.equal([value]); }); @@ -491,10 +492,10 @@ describe('CacheService Test Suite', async function () { const key = 'list'; const values = ['item1', 'item2', 'item3']; for (const item of values) { - await cacheService.rPush(key, item, callingMethod); + await cacheService.rPush(key, item, callingMethod, requestDetails); } - const range = await cacheService.lRange(key, 0, 1, callingMethod); + const range = await cacheService.lRange(key, 0, 1, callingMethod, requestDetails); expect(range).to.deep.equal(['item1', 'item2']); }); @@ -503,10 +504,10 @@ describe('CacheService Test Suite', async function () { const key = 'list'; const values = ['item1', 'item2', 'item3']; for (const item of values) { - await cacheService.rPush(key, item, callingMethod); + await cacheService.rPush(key, item, callingMethod, requestDetails); } - const range = await cacheService.lRange(key, -2, -1, callingMethod); + const range = await cacheService.lRange(key, -2, -1, callingMethod, requestDetails); expect(range).to.deep.equal(['item2', 'item3']); }); @@ -517,10 +518,10 @@ describe('CacheService Test Suite', async function () { const key = 'list'; const values = ['item1', 'item2', 'item3']; for (const item of values) { - await cacheService.rPush(key, item, callingMethod); + await cacheService.rPush(key, item, callingMethod, requestDetails); } - const range = await cacheService.lRange(key, 0, 1, callingMethod); + const range = await cacheService.lRange(key, 0, 1, callingMethod, requestDetails); expect(range).to.deep.equal(['item1', 'item2']); }); diff --git a/packages/relay/tests/lib/services/debugService/debug.spec.ts b/packages/relay/tests/lib/services/debugService/debug.spec.ts index 0f7eb97523..41e7e36294 100644 --- a/packages/relay/tests/lib/services/debugService/debug.spec.ts +++ b/packages/relay/tests/lib/services/debugService/debug.spec.ts @@ -28,13 +28,14 @@ import { MirrorNodeClient } from '../../../../src/lib/clients'; import pino from 'pino'; import { TracerType } from '../../../../src/lib/constants'; import { DebugService } from '../../../../src/lib/services/debugService'; -import { getQueryParams, getRequestId } from '../../../helpers'; +import { getQueryParams } from '../../../helpers'; import RelayAssertions from '../../../assertions'; import { predefined } from '../../../../src'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import { CommonService } from '../../../../src/lib/services/ethService'; import { IOpcodesResponse } from '../../../../src/lib/clients/models/IOpcodesResponse'; import { strip0x } from '../../../../src/formatters'; +import { RequestDetails } from '../../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); chai.use(chaiAsPromised); @@ -51,6 +52,7 @@ let cacheService: CacheService; describe('Debug API Test Suite', async function () { this.timeout(10000); + const requestDetails = new RequestDetails({ requestId: 'debugTest', ipAddress: '0.0.0.0' }); const transactionHash = '0xb7a433b014684558d4154c73de3ed360bd5867725239938c2143acb7a76bca82'; const nonExistentTransactionHash = '0xb8a433b014684558d4154c73de3ed360bd5867725239938c2143acb7a76bca82'; const contractAddress = '0x0000000000000000000000000000000000000409'; @@ -337,7 +339,7 @@ describe('Debug API Test Suite', async function () { debugService.debug_traceTransaction, true, debugService, - [transactionHash, callTracer, tracerConfigFalse, getRequestId()], + [transactionHash, callTracer, tracerConfigFalse, requestDetails], ); }); @@ -348,7 +350,7 @@ describe('Debug API Test Suite', async function () { transactionHash, callTracer, tracerConfigFalse, - getRequestId(), + requestDetails, ); expect(traceTransaction).to.exist; }); @@ -360,7 +362,7 @@ describe('Debug API Test Suite', async function () { debugService.debug_traceTransaction, true, debugService, - [transactionHash, callTracer, tracerConfigFalse, getRequestId()], + [transactionHash, callTracer, tracerConfigFalse, requestDetails], ); }); }); @@ -398,7 +400,7 @@ describe('Debug API Test Suite', async function () { transactionHash, callTracer, tracerConfigFalse, - getRequestId(), + requestDetails, ); expect(result).to.deep.equal(expectedResult); @@ -420,7 +422,7 @@ describe('Debug API Test Suite', async function () { transactionHash, callTracer, tracerConfigTrue, - getRequestId(), + requestDetails, ); expect(result).to.deep.equal(expectedResult); @@ -467,7 +469,7 @@ describe('Debug API Test Suite', async function () { transactionHash, opcodeLogger, config, - getRequestId(), + requestDetails, ); expect(result).to.deep.equal(expectedResult); @@ -509,7 +511,7 @@ describe('Debug API Test Suite', async function () { nonExistentTransactionHash, callTracer, tracerConfigTrue, - getRequestId(), + requestDetails, ]); }); @@ -527,13 +529,13 @@ describe('Debug API Test Suite', async function () { describe('resolveAddress', async function () { it('should return null address with invalid parameters in resolveAddress', async function () { - const address = await debugService.resolveAddress(null!); + const address = await debugService.resolveAddress(null!, requestDetails); expect(address).to.be.null; }); it('should return passed address on notFound entity from the mirror node', async function () { restMock.onGet(ACCOUNT_BY_ADDRESS).reply(404, notFound); - const address = await debugService.resolveAddress(accountAddress); + const address = await debugService.resolveAddress(accountAddress, requestDetails); expect(address).to.eq(accountAddress); }); }); diff --git a/packages/relay/tests/lib/services/eth/filter.spec.ts b/packages/relay/tests/lib/services/eth/filter.spec.ts index 3196cf9041..cce06cdc28 100644 --- a/packages/relay/tests/lib/services/eth/filter.spec.ts +++ b/packages/relay/tests/lib/services/eth/filter.spec.ts @@ -23,15 +23,17 @@ import dotenv from 'dotenv'; import MockAdapter from 'axios-mock-adapter'; import { expect } from 'chai'; import { Registry } from 'prom-client'; -import { MirrorNodeClient } from '../../../../src/lib/clients/mirrorNodeClient'; +import { MirrorNodeClient } from '../../../../src/lib/clients'; import pino from 'pino'; import constants from '../../../../src/lib/constants'; import { FilterService, CommonService } from '../../../../src/lib/services/ethService'; -import { defaultEvmAddress, getRequestId, toHex, defaultBlock, defaultLogTopics, defaultLogs1 } from '../../../helpers'; +import { defaultEvmAddress, toHex, defaultBlock, defaultLogTopics, defaultLogs1 } from '../../../helpers'; import RelayAssertions from '../../../assertions'; import { predefined } from '../../../../src'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import * as sinon from 'sinon'; +import { RequestDetails } from '../../../../src/lib/types'; +import { v4 as uuid } from 'uuid'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); const logger = pino(); @@ -45,12 +47,12 @@ let cacheService: CacheService; describe('Filter API Test Suite', async function () { this.timeout(10000); + const requestDetails = new RequestDetails({ requestId: uuid(), ipAddress: '0.0.0.0' }); const filterObject = { toBlock: 'latest', }; let blockFilterObject; - let cacheMock; const existingFilterId = '0x1112233'; const nonExistingFilterId = '0x1112231'; const LATEST_BLOCK_QUERY = 'blocks?limit=1&order=desc'; @@ -59,7 +61,7 @@ describe('Filter API Test Suite', async function () { const validateFilterCache = async (filterId, expectedFilterType, expectedParams = {}) => { const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - const cachedFilter = await cacheService.getAsync(cacheKey, undefined); + const cachedFilter = await cacheService.getAsync(cacheKey, 'validateFilterCache', requestDetails); expect(cachedFilter).to.exist; expect(cachedFilter.type).to.exist; expect(cachedFilter.type).to.eq(expectedFilterType); @@ -69,8 +71,6 @@ describe('Filter API Test Suite', async function () { }; this.beforeAll(() => { - cacheMock = sinon.createSandbox(); - blockFilterObject = { type: constants.FILTER.TYPE.NEW_BLOCK, params: { @@ -90,7 +90,7 @@ describe('Filter API Test Suite', async function () { cacheService = new CacheService(logger.child({ name: `cache` }), registry); // @ts-ignore mirrorNodeInstance = new MirrorNodeClient( - process.env.MIRROR_NODE_URL, + process.env.MIRROR_NODE_URL ?? '', logger.child({ name: `mirror-node` }), registry, cacheService, @@ -103,22 +103,23 @@ describe('Filter API Test Suite', async function () { web3Mock = new MockAdapter(mirrorNodeInstance.getMirrorNodeWeb3Instance(), { onNoMatch: 'throwException' }); // @ts-ignore - const common = new CommonService(mirrorNodeInstance, logger, cacheService); - filterService = new FilterService(mirrorNodeInstance, logger, cacheService, common); + const common = new CommonService(mirrorNodeInstance, logger.child({ name: 'common-service' }), cacheService); + filterService = new FilterService( + mirrorNodeInstance, + logger.child({ name: 'filter-service' }), + cacheService, + common, + ); }); - this.beforeEach(() => { + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(); + await cacheService.clear(requestDetails); restMock.reset(); - - cacheMock.stub(cacheService, 'set').returns(true); - cacheMock.stub(cacheService, 'delete').returns(true); }); this.afterEach(() => { restMock.resetHandlers(); - cacheMock.restore(); }); describe('all methods require a filter flag', async function () { @@ -139,47 +140,41 @@ describe('Filter API Test Suite', async function () { filterService.newFilter, true, filterService, - {}, + [undefined, undefined, requestDetails], ); await RelayAssertions.assertRejection( predefined.UNSUPPORTED_METHOD, filterService.uninstallFilter, true, filterService, - [existingFilterId], + [existingFilterId, requestDetails], ); await RelayAssertions.assertRejection( predefined.UNSUPPORTED_METHOD, filterService.getFilterChanges, true, filterService, - [existingFilterId], + [existingFilterId, requestDetails], ); }); it('FILTER_API_ENABLED=true', async function () { process.env.FILTER_API_ENABLED = 'true'; restMock.onGet(LATEST_BLOCK_QUERY).reply(200, { blocks: [{ ...defaultBlock }] }); - const filterId = await filterService.newFilter(); + const filterId = await filterService.newFilter(undefined, undefined, requestDetails); expect(filterId).to.exist; expect(RelayAssertions.validateHash(filterId, 32)).to.eq(true, 'returns valid filterId'); - const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - cacheMock.stub(cacheService, 'getAsync').withArgs(cacheKey, 'eth_getFilterChanges').returns(logFilterObject); - restMock.onGet(`blocks/${defaultBlock.number}`).reply(200, defaultBlock); restMock .onGet( `contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}×tamp=lte:${defaultBlock.timestamp.to}&limit=100&order=asc`, ) .reply(200, defaultLogs1); - const filterChanges = await filterService.getFilterChanges(filterId); + const filterChanges = await filterService.getFilterChanges(filterId, requestDetails); expect(filterChanges).to.exist; - cacheMock.restore(); - cacheMock.stub(cacheService, 'getAsync').withArgs(cacheKey, 'eth_uninstallFilter').returns(logFilterObject); - - const isFilterUninstalled = await filterService.uninstallFilter(filterId); + const isFilterUninstalled = await filterService.uninstallFilter(filterId, requestDetails); expect(isFilterUninstalled).to.eq(true, 'executes correctly'); }); @@ -190,21 +185,21 @@ describe('Filter API Test Suite', async function () { filterService.newFilter, true, filterService, - [], + [undefined, undefined, requestDetails], ); await RelayAssertions.assertRejection( predefined.UNSUPPORTED_METHOD, filterService.uninstallFilter, true, filterService, - [existingFilterId], + [existingFilterId, requestDetails], ); await RelayAssertions.assertRejection( predefined.UNSUPPORTED_METHOD, filterService.getFilterChanges, true, filterService, - [existingFilterId], + [existingFilterId, requestDetails], ); }); }); @@ -231,27 +226,30 @@ describe('Filter API Test Suite', async function () { }); it('Returns a valid filterId', async function () { - expect(RelayAssertions.validateHash(await filterService.newFilter(), 32)).to.eq( - true, - 'with default param values', - ); - expect(RelayAssertions.validateHash(await filterService.newFilter(numberHex), 32)).to.eq(true, 'with fromBlock'); - expect(RelayAssertions.validateHash(await filterService.newFilter(numberHex, 'latest'), 32)).to.eq( - true, - 'with fromBlock, toBlock', - ); expect( - RelayAssertions.validateHash(await filterService.newFilter(numberHex, 'latest', defaultEvmAddress), 32), + RelayAssertions.validateHash(await filterService.newFilter(undefined, undefined, requestDetails), 32), + ).to.eq(true, 'with default param values'); + expect( + RelayAssertions.validateHash(await filterService.newFilter(numberHex, undefined, requestDetails), 32), + ).to.eq(true, 'with fromBlock'); + expect( + RelayAssertions.validateHash(await filterService.newFilter(numberHex, 'latest', requestDetails), 32), + ).to.eq(true, 'with fromBlock, toBlock'); + expect( + RelayAssertions.validateHash( + await filterService.newFilter(numberHex, 'latest', requestDetails, defaultEvmAddress), + 32, + ), ).to.eq(true, 'with fromBlock, toBlock, address'); expect( RelayAssertions.validateHash( - await filterService.newFilter(numberHex, 'latest', defaultEvmAddress, defaultLogTopics), + await filterService.newFilter(numberHex, 'latest', requestDetails, defaultEvmAddress, defaultLogTopics), 32, ), ).to.eq(true, 'with fromBlock, toBlock, address, topics'); expect( RelayAssertions.validateHash( - await filterService.newFilter(numberHex, 'latest', defaultEvmAddress, defaultLogTopics, getRequestId()), + await filterService.newFilter(numberHex, 'latest', requestDetails, defaultEvmAddress, defaultLogTopics), 32, ), ).to.eq(true, 'with all parameters'); @@ -261,11 +259,11 @@ describe('Filter API Test Suite', async function () { const filterId = await filterService.newFilter( numberHex, 'latest', + requestDetails, defaultEvmAddress, defaultLogTopics, - getRequestId(), ); - validateFilterCache(filterId, constants.FILTER.TYPE.LOG, { + await validateFilterCache(filterId, constants.FILTER.TYPE.LOG, { fromBlock: numberHex, toBlock: 'latest', address: defaultEvmAddress, @@ -280,14 +278,14 @@ describe('Filter API Test Suite', async function () { filterService.newFilter, true, filterService, - [blockNumberHexes[1500], blockNumberHexes[1400]], + [blockNumberHexes[1500], blockNumberHexes[1400], requestDetails], ); await RelayAssertions.assertRejection( predefined.INVALID_BLOCK_RANGE, filterService.newFilter, true, filterService, - ['latest', blockNumberHexes[1400]], + ['latest', blockNumberHexes[1400], requestDetails], ); // block range is too large @@ -296,39 +294,45 @@ describe('Filter API Test Suite', async function () { filterService.newFilter, true, filterService, - [blockNumberHexes[5], blockNumberHexes[2000]], + [blockNumberHexes[5], blockNumberHexes[2000], requestDetails], ); // block range is valid expect( - RelayAssertions.validateHash(await filterService.newFilter(blockNumberHexes[1400], blockNumberHexes[1500]), 32), + RelayAssertions.validateHash( + await filterService.newFilter(blockNumberHexes[1400], blockNumberHexes[1500], requestDetails), + 32, + ), + ).to.eq(true); + expect( + RelayAssertions.validateHash( + await filterService.newFilter(blockNumberHexes[1400], 'latest', requestDetails), + 32, + ), ).to.eq(true); - expect(RelayAssertions.validateHash(await filterService.newFilter(blockNumberHexes[1400], 'latest'), 32)).to.eq( - true, - ); }); }); describe('eth_uninstallFilter', async function () { it('should return true if filter is deleted', async function () { const cacheKey = `${constants.CACHE_KEY.FILTERID}_${existingFilterId}`; + await cacheService.set( + cacheKey, + filterObject, + filterService.ethUninstallFilter, + requestDetails, + constants.FILTER.TTL, + ); - cacheMock.stub(cacheService, 'getAsync').onFirstCall().returns(filterObject).onSecondCall().returns(undefined); - - cacheService.set(cacheKey, filterObject, filterService.ethUninstallFilter, constants.FILTER.TTL, undefined, true); - - const result = await filterService.uninstallFilter(existingFilterId); + const result = await filterService.uninstallFilter(existingFilterId, requestDetails); - const isDeleted = (await cacheService.getAsync(cacheKey, filterService.ethUninstallFilter, undefined)) - ? false - : true; + const isDeleted = !(await cacheService.getAsync(cacheKey, filterService.ethUninstallFilter, requestDetails)); expect(result).to.eq(true); expect(isDeleted).to.eq(true); }); it('should return false if filter does not exist, therefore is not deleted', async function () { - cacheMock.stub(cacheService, 'getAsync').returns(undefined); - const result = await filterService.uninstallFilter(nonExistingFilterId); + const result = await filterService.uninstallFilter(nonExistingFilterId, requestDetails); expect(result).to.eq(false); }); }); @@ -339,12 +343,12 @@ describe('Filter API Test Suite', async function () { }); it('Returns a valid filterId', async function () { - expect(RelayAssertions.validateHash(await filterService.newBlockFilter(), 32)).to.eq(true); + expect(RelayAssertions.validateHash(await filterService.newBlockFilter(requestDetails), 32)).to.eq(true); }); it('Creates a filter with type=new_block', async function () { - const filterId = await filterService.newBlockFilter(getRequestId()); - validateFilterCache(filterId, constants.FILTER.TYPE.NEW_BLOCK, { + const filterId = await filterService.newBlockFilter(requestDetails); + await validateFilterCache(filterId, constants.FILTER.TYPE.NEW_BLOCK, { blockAtCreation: toHex(defaultBlock.number), }); }); @@ -352,29 +356,32 @@ describe('Filter API Test Suite', async function () { describe('eth_getFilterLogs', async function () { it('should throw FILTER_NOT_FOUND for type=newBlock', async function () { - cacheMock.stub(cacheService, 'getAsync').returns(undefined); - const filterIdBlockType = await filterService.createFilter(constants.FILTER.TYPE.NEW_BLOCK, filterObject); + const filterIdBlockType = await filterService.createFilter( + constants.FILTER.TYPE.NEW_BLOCK, + filterObject, + requestDetails, + ); await RelayAssertions.assertRejection( predefined.FILTER_NOT_FOUND, filterService.getFilterLogs, true, filterService, - [filterIdBlockType], + [filterIdBlockType, requestDetails], ); }); it('should throw FILTER_NOT_FOUND for type=pendingTransaction', async function () { - cacheMock.stub(cacheService, 'getAsync').returns(undefined); const filterIdBlockType = await filterService.createFilter( constants.FILTER.TYPE.PENDING_TRANSACTION, filterObject, + requestDetails, ); await RelayAssertions.assertRejection( predefined.FILTER_NOT_FOUND, filterService.getFilterLogs, true, filterService, - [filterIdBlockType], + [filterIdBlockType, requestDetails], ); }); @@ -400,19 +407,9 @@ describe('Filter API Test Suite', async function () { ) .reply(200, filteredLogs); - const filterId = await filterService.newFilter('0x1'); - const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - cacheMock - .stub(cacheService, 'getAsync') - .withArgs(cacheKey, 'eth_getFilterLogs') - .returns({ - ...logFilterObject, - params: { - fromBlock: 1, - }, - }); + const filterId = await filterService.newFilter('0x1', undefined, requestDetails); - const logs = await filterService.getFilterLogs(filterId); + const logs = await filterService.getFilterLogs(filterId, requestDetails); expect(logs).to.not.be.empty; logs.every((log) => expect(Number(log.blockNumber)).to.be.greaterThan(1)); @@ -437,20 +434,9 @@ describe('Filter API Test Suite', async function () { ) .reply(200, filteredLogs); - const filterId = await filterService.newFilter(null, '0x3'); - const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - cacheMock - .stub(cacheService, 'getAsync') - .withArgs(cacheKey, 'eth_getFilterLogs') - .returns({ - ...logFilterObject, - params: { - fromBlock: 3, - toBlock: 3, - }, - }); - - const logs = await filterService.getFilterLogs(filterId); + const filterId = await filterService.newFilter(undefined, '0x3', requestDetails); + + const logs = await filterService.getFilterLogs(filterId, requestDetails); expect(logs).to.not.be.empty; logs.every((log) => expect(Number(log.blockNumber)).to.be.lessThan(3)); @@ -471,21 +457,9 @@ describe('Filter API Test Suite', async function () { ) .reply(200, filteredLogs); - const filterId = await filterService.newFilter(null, null, defaultEvmAddress); - const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - - cacheMock - .stub(cacheService, 'getAsync') - .withArgs(cacheKey, 'eth_getFilterLogs') - .returns({ - ...logFilterObject, - params: { - fromBlock: defaultBlock.number, - address: defaultEvmAddress, - }, - }); + const filterId = await filterService.newFilter(undefined, undefined, requestDetails, defaultEvmAddress); - const logs = await filterService.getFilterLogs(filterId); + const logs = await filterService.getFilterLogs(filterId, requestDetails); expect(logs).to.not.be.empty; logs.every((log) => expect(log.address).to.equal(defaultEvmAddress)); @@ -511,20 +485,9 @@ describe('Filter API Test Suite', async function () { ) .reply(200, filteredLogs); - const filterId = await filterService.newFilter(null, null, null, customTopic); - const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - cacheMock - .stub(cacheService, 'getAsync') - .withArgs(cacheKey, 'eth_getFilterLogs') - .returns({ - ...logFilterObject, - params: { - fromBlock: defaultBlock.number, - topics: customTopic, - }, - }); + const filterId = await filterService.newFilter(undefined, undefined, requestDetails, undefined, customTopic); - const logs = await filterService.getFilterLogs(filterId); + const logs = await filterService.getFilterLogs(filterId, requestDetails); expect(logs).to.not.be.empty; logs.every((log) => expect(log.topics).to.deep.equal(customTopic)); @@ -533,24 +496,22 @@ describe('Filter API Test Suite', async function () { describe('eth_getFilterChanges', async function () { it('should throw error for non-existing filters', async function () { - cacheMock.stub(cacheService, 'getAsync').returns(undefined); await RelayAssertions.assertRejection( predefined.FILTER_NOT_FOUND, filterService.getFilterChanges, true, filterService, - [nonExistingFilterId], + [nonExistingFilterId, requestDetails], ); }); it('should throw error for invalid filter type', async function () { - cacheMock.stub(cacheService, 'getAsync').returns({ type: 'UnsupportedType' }); await RelayAssertions.assertRejection( - predefined.UNSUPPORTED_METHOD, + predefined.FILTER_NOT_FOUND, filterService.getFilterChanges, true, filterService, - [nonExistingFilterId], + [nonExistingFilterId, requestDetails], ); }); @@ -568,24 +529,15 @@ describe('Filter API Test Suite', async function () { .reply(200, { blocks: [] }); const cacheKey = `${constants.CACHE_KEY.FILTERID}_${existingFilterId}`; - cacheService.set( + await cacheService.set( cacheKey, blockFilterObject, filterService.ethGetFilterChanges, + requestDetails, constants.FILTER.TTL, - undefined, - true, ); - cacheMock - .stub(cacheService, 'getAsync') - .onFirstCall() - .returns(blockFilterObject) - .onSecondCall() - .returns({ ...blockFilterObject, lastQueried: defaultBlock.number + 3 }) - .onThirdCall() - .returns({ ...blockFilterObject, lastQueried: defaultBlock.number + 4 }); - const result = await filterService.getFilterChanges(existingFilterId); + const result = await filterService.getFilterChanges(existingFilterId, requestDetails); expect(result).to.exist; expect(result.length).to.eq(3, 'returns correct number of blocks'); @@ -593,7 +545,7 @@ describe('Filter API Test Suite', async function () { expect(result[1]).to.eq('0x2'); expect(result[2]).to.eq('0x3'); - const secondResult = await filterService.getFilterChanges(existingFilterId); + const secondResult = await filterService.getFilterChanges(existingFilterId, requestDetails); expect(secondResult).to.exist; expect(secondResult.length).to.eq(0, 'second call returns no block hashes'); }); @@ -609,25 +561,18 @@ describe('Filter API Test Suite', async function () { .reply(200, { blocks: [] }); const cacheKey = `${constants.CACHE_KEY.FILTERID}_${existingFilterId}`; - cacheService.set( + await cacheService.set( cacheKey, blockFilterObject, filterService.ethGetFilterChanges, + requestDetails, constants.FILTER.TTL, - undefined, - true, ); - cacheMock - .stub(cacheService, 'getAsync') - .onFirstCall() - .returns(blockFilterObject) - .onSecondCall() - .returns({ ...blockFilterObject, lastQueried: defaultBlock.number + 1 }); - - const resultCurrentBlock = await filterService.getFilterChanges(existingFilterId); + + const resultCurrentBlock = await filterService.getFilterChanges(existingFilterId, requestDetails); expect(resultCurrentBlock).to.not.be.empty; - const resultSameBlock = await filterService.getFilterChanges(existingFilterId); + const resultSameBlock = await filterService.getFilterChanges(existingFilterId, requestDetails); expect(resultSameBlock).to.be.empty; }); @@ -653,21 +598,11 @@ describe('Filter API Test Suite', async function () { .reply(200, filteredLogs); restMock.onGet('blocks/1').reply(200, { ...defaultBlock, block_number: 1 }); - const filterId = await filterService.newFilter('0x1'); - const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - cacheMock - .stub(cacheService, 'getAsync') - .withArgs(cacheKey, 'eth_getFilterChanges') - .returns({ - ...logFilterObject, - params: { - fromBlock: 1, - }, - }); - - const logs = await filterService.getFilterChanges(filterId); + const filterId = await filterService.newFilter('0x1', undefined, requestDetails); + + const logs = await filterService.getFilterChanges(filterId, requestDetails); expect(logs).to.not.be.empty; - logs.every((log) => expect(Number(log.blockNumber)).to.equal(9)); + logs.forEach((log) => expect(Number(log.blockNumber)).to.equal(9)); }); it('should return an empty set if there are no logs', async function () { @@ -679,18 +614,8 @@ describe('Filter API Test Suite', async function () { .reply(200, []); restMock.onGet('blocks/1').reply(200, { ...defaultBlock, block_number: 1 }); - const filterId = await filterService.newFilter('0x1'); - const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; - cacheMock - .stub(cacheService, 'getAsync') - .withArgs(cacheKey, 'eth_getFilterChanges') - .returns({ - ...logFilterObject, - params: { - fromBlock: 1, - }, - }); - const logs = await filterService.getFilterChanges(filterId); + const filterId = await filterService.newFilter('0x1', undefined, requestDetails); + const logs = await filterService.getFilterChanges(filterId, requestDetails); expect(logs).to.be.empty; }); @@ -701,17 +626,15 @@ describe('Filter API Test Suite', async function () { }); const cacheKey = `${constants.CACHE_KEY.FILTERID}_${existingFilterId}`; - cacheService.set( + await cacheService.set( cacheKey, blockFilterObject, filterService.ethGetFilterChanges, + requestDetails, constants.FILTER.TTL, - undefined, - true, ); - cacheMock.stub(cacheService, 'getAsync').returns(blockFilterObject); - const blocks = await filterService.getFilterChanges(existingFilterId); + const blocks = await filterService.getFilterChanges(existingFilterId, requestDetails); expect(blocks).to.be.empty; }); }); diff --git a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts index e5dace01b4..f0df675798 100644 --- a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts +++ b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts @@ -38,6 +38,7 @@ import { EthAddressHbarSpendingPlanNotFoundError, IPAddressHbarSpendingPlanNotFoundError, } from '../../../../src/lib/db/types/hbarLimiter/errors'; +import { RequestDetails } from '../../../../src/lib/types'; chai.use(chaiAsPromised); @@ -53,6 +54,8 @@ describe('HbarLimitService', function () { const mockRequestId = getRequestId(); const mockPlanId = uuidV4(randomBytes(16)); + const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: mockIpAddress }); + let hbarLimitService: HbarLimitService; let hbarSpendingPlanRepositoryStub: sinon.SinonStubbedInstance; let ethAddressHbarSpendingPlanRepositoryStub: sinon.SinonStubbedInstance; @@ -113,7 +116,7 @@ describe('HbarLimitService', function () { }); it('should reset the spentToday field of all spending plans', async function () { - await hbarLimitService.resetLimiter(); + await hbarLimitService.resetLimiter(requestDetails); expect(hbarSpendingPlanRepositoryStub.resetAllSpentTodayEntries.called).to.be.true; }); @@ -121,27 +124,27 @@ describe('HbarLimitService', function () { // @ts-ignore hbarLimitService.remainingBudget = 1000; const setSpy = sinon.spy(hbarLimitService['hbarLimitRemainingGauge'], 'set'); - await hbarLimitService.resetLimiter(); + await hbarLimitService.resetLimiter(requestDetails); expect(hbarLimitService['remainingBudget']).to.equal(totalBudget); expect(setSpy.calledOnceWith(totalBudget)).to.be.true; }); it('should reset the daily unique spending plans counter', async function () { const spies = createSpiesForMetricsReset('dailyUniqueSpendingPlansCounter'); - await hbarLimitService.resetLimiter(); + await hbarLimitService.resetLimiter(requestDetails); spies.forEach((spy) => sinon.assert.calledOnce(spy)); }); it('should reset the average daily spending plan usages gauge', async function () { const spies = createSpiesForMetricsReset('averageDailySpendingPlanUsagesGauge'); - await hbarLimitService.resetLimiter(); + await hbarLimitService.resetLimiter(requestDetails); spies.forEach((spy) => sinon.assert.calledOnce(spy)); }); it('should set the reset date to the next day at midnight', async function () { const tomorrow = new Date(Date.now() + HbarLimitService.ONE_DAY_IN_MILLIS); const expectedResetDate = new Date(tomorrow.setHours(0, 0, 0, 0)); - await hbarLimitService.resetLimiter(); + await hbarLimitService.resetLimiter(requestDetails); expect(hbarLimitService['reset']).to.deep.equal(expectedResetDate); }); }); @@ -151,7 +154,7 @@ describe('HbarLimitService', function () { it('should return true if the total daily budget is exceeded', async function () { // @ts-ignore hbarLimitService.remainingBudget = 0; - const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.true; }); @@ -162,8 +165,7 @@ describe('HbarLimitService', function () { mode, methodName, mockEthAddress, - mockRequestId, - mockIpAddress, + requestDetails, mockEstimatedTxFee, ); expect(result).to.be.true; @@ -176,7 +178,7 @@ describe('HbarLimitService', function () { hbarSpendingPlanRepositoryStub.create.resolves(newSpendingPlan); ethAddressHbarSpendingPlanRepositoryStub.save.resolves(); - const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.false; expect(hbarSpendingPlanRepositoryStub.create.calledOnce).to.be.true; @@ -189,8 +191,9 @@ describe('HbarLimitService', function () { ).to.be.true; }); - it('should return false if ethAddress is null or empty', async function () { - const result = await hbarLimitService.shouldLimit(mode, methodName, ''); + it('should return false if ethAddress and ipAddress is empty string', async function () { + const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: '' }); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.false; }); @@ -202,7 +205,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.true; }); @@ -215,7 +218,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.false; }); @@ -228,7 +231,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.true; }); @@ -248,8 +251,7 @@ describe('HbarLimitService', function () { mode, methodName, mockEthAddress, - mockRequestId, - mockIpAddress, + requestDetails, mockEstimatedTxFee, ); @@ -267,7 +269,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.false; }); @@ -283,7 +285,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.false; }); @@ -293,21 +295,14 @@ describe('HbarLimitService', function () { it('should return true if the total daily budget is exceeded', async function () { // @ts-ignore hbarLimitService.remainingBudget = 0; - const result = await hbarLimitService.shouldLimit(mode, methodName, '', mockIpAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.true; }); it('should return true when remainingBudget < estimatedTxFee ', async function () { // @ts-ignore hbarLimitService.remainingBudget = mockEstimatedTxFee - 1; - const result = await hbarLimitService.shouldLimit( - mode, - methodName, - '', - mockRequestId, - mockIpAddress, - mockEstimatedTxFee, - ); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails, mockEstimatedTxFee); expect(result).to.be.true; }); @@ -318,7 +313,8 @@ describe('HbarLimitService', function () { hbarSpendingPlanRepositoryStub.create.resolves(spendingPlan); ipAddressHbarSpendingPlanRepositoryStub.save.resolves(); - const result = await hbarLimitService.shouldLimit(mode, methodName, '', mockIpAddress); + const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: mockIpAddress }); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.false; expect(hbarSpendingPlanRepositoryStub.create.calledOnce).to.be.true; @@ -326,13 +322,14 @@ describe('HbarLimitService', function () { expect( loggerSpy.warn.calledWithMatch( sinon.match.instanceOf(IPAddressHbarSpendingPlanNotFoundError), - `Failed to get spending plan for IP address '${mockIpAddress}'`, + `${requestDetails.formattedRequestId} Failed to get spending plan`, ), ).to.be.true; }); it('should return false if ipAddress is null or empty', async function () { - const result = await hbarLimitService.shouldLimit(mode, methodName, '', ''); + const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: '' }); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.false; }); @@ -344,7 +341,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, '', mockIpAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.true; }); @@ -357,7 +354,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, '', mockIpAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.false; }); @@ -370,7 +367,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, '', mockIpAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.true; }); @@ -386,14 +383,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit( - mode, - methodName, - '', - mockRequestId, - mockIpAddress, - mockEstimatedTxFee, - ); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails, mockEstimatedTxFee); expect(result).to.be.true; }); @@ -409,7 +399,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, '', mockIpAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.false; }); @@ -425,7 +415,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService.shouldLimit(mode, methodName, '', mockIpAddress); + const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.false; }); @@ -434,14 +424,15 @@ describe('HbarLimitService', function () { describe('getSpendingPlan', function () { it(`should return null if neither ethAddress nor ipAddress is provided`, async function () { - const ipAddresses = ['', null, undefined]; - const ethAddresses = ['', null, undefined]; + const ipAddresses = ['']; + const ethAddresses = ['']; const testCases = ethAddresses.flatMap((ethAddress) => ipAddresses.map((ipAddress) => ({ ethAddress, ipAddress })), ); for (const { ethAddress, ipAddress } of testCases) { // @ts-ignore - const result = await hbarLimitService['getSpendingPlan'](ethAddress, ipAddress); + const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: ipAddress }); + const result = await hbarLimitService['getSpendingPlan'](ethAddress, requestDetails); expect(result).to.be.null; } }); @@ -454,7 +445,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService['getSpendingPlan'](mockEthAddress); + const result = await hbarLimitService['getSpendingPlan'](mockEthAddress, requestDetails); expect(result).to.deep.equal(spendingPlan); }); @@ -467,7 +458,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService['getSpendingPlan']('', mockIpAddress); + const result = await hbarLimitService['getSpendingPlan']('', requestDetails); expect(result).to.deep.equal(spendingPlan); }); @@ -476,7 +467,7 @@ describe('HbarLimitService', function () { const error = new EthAddressHbarSpendingPlanNotFoundError(mockEthAddress); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.rejects(error); - const result = await hbarLimitService['getSpendingPlan'](mockEthAddress); + const result = await hbarLimitService['getSpendingPlan'](mockEthAddress, requestDetails); expect(result).to.be.null; }); @@ -485,7 +476,7 @@ describe('HbarLimitService', function () { const error = new IPAddressHbarSpendingPlanNotFoundError(mockIpAddress); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.rejects(error); - const result = await hbarLimitService['getSpendingPlan']('', mockIpAddress); + const result = await hbarLimitService['getSpendingPlan']('', requestDetails); expect(result).to.be.null; }); @@ -493,7 +484,7 @@ describe('HbarLimitService', function () { describe('getSpendingPlanByEthAddress', function () { const testGetSpendingPlanByEthAddressError = async (error: Error, errorClass: any) => { - const result = hbarLimitService['getSpendingPlanByEthAddress'](mockEthAddress); + const result = hbarLimitService['getSpendingPlanByEthAddress'](mockEthAddress, requestDetails); await expect(result).to.be.eventually.rejectedWith(errorClass, error.message); }; @@ -531,7 +522,7 @@ describe('HbarLimitService', function () { }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan); - const result = await hbarLimitService['getSpendingPlanByEthAddress'](mockEthAddress); + const result = await hbarLimitService['getSpendingPlanByEthAddress'](mockEthAddress, requestDetails); expect(result).to.deep.equal(spendingPlan); }); @@ -539,11 +530,13 @@ describe('HbarLimitService', function () { describe('createBasicSpendingPlan', function () { const testCreateBasicSpendingPlan = async (ethAddress: string, ipAddress?: string) => { + const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: ipAddress ? ipAddress : '' }); + console.log('requestDetails', requestDetails); const newSpendingPlan = createSpendingPlan(mockPlanId); hbarSpendingPlanRepositoryStub.create.resolves(newSpendingPlan); ethAddressHbarSpendingPlanRepositoryStub.save.resolves(); - const result = await hbarLimitService['createBasicSpendingPlan'](ethAddress, ipAddress); + const result = await hbarLimitService['createBasicSpendingPlan'](ethAddress, requestDetails); expect(result).to.deep.equal(newSpendingPlan); expect(hbarSpendingPlanRepositoryStub.create.calledOnce).to.be.true; @@ -612,7 +605,7 @@ describe('HbarLimitService', function () { 'updateAverageDailyUsagePerSubscriptionType', ); - await hbarLimitService.addExpense(expense, ethAddress, ipAddress); + await hbarLimitService.addExpense(expense, ethAddress, requestDetails); expect(hbarSpendingPlanRepositoryStub.addAmountToSpentToday.calledOnceWith(mockPlanId, expense)).to.be.true; // @ts-ignore @@ -628,15 +621,16 @@ describe('HbarLimitService', function () { sinon.assert.calledOnceWithExactly(incDailyUniqueSpendingPlansCounterSpy, 1); }; - it('should throw an error if neither ethAddress nor ipAddress is provided', async function () { - const ipAddresses = ['', null, undefined]; - const ethAddresses = ['', null, undefined]; + it('should throw an error if empty ethAddress or ipAddress is provided', async function () { + const ipAddresses = ['']; + const ethAddresses = ['']; const testCases = ethAddresses.flatMap((ethAddress) => ipAddresses.map((ipAddress) => ({ ethAddress, ipAddress })), ); for (const { ethAddress, ipAddress } of testCases) { + const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: ipAddress }); // @ts-ignore - await expect(hbarLimitService.addExpense(100, ethAddress, ipAddress)).to.be.eventually.rejectedWith( + await expect(hbarLimitService.addExpense(100, ethAddress, requestDetails)).to.be.eventually.rejectedWith( 'Cannot add expense without an eth address or ip address', ); } @@ -650,7 +644,7 @@ describe('HbarLimitService', function () { ); ethAddressHbarSpendingPlanRepositoryStub.save.resolves(); - await hbarLimitService.addExpense(100, mockEthAddress); + await hbarLimitService.addExpense(100, mockEthAddress, requestDetails); expect(hbarSpendingPlanRepositoryStub.create.calledOnce).to.be.true; expect(ethAddressHbarSpendingPlanRepositoryStub.save.calledOnce).to.be.true; @@ -676,7 +670,7 @@ describe('HbarLimitService', function () { hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(createSpendingPlan(mockPlanId)); hbarSpendingPlanRepositoryStub.addAmountToSpentToday.rejects(new Error('Failed to add expense')); - await expect(hbarLimitService.addExpense(100, mockEthAddress)).to.be.eventually.rejectedWith( + await expect(hbarLimitService.addExpense(100, mockEthAddress, requestDetails)).to.be.eventually.rejectedWith( 'Failed to add expense', ); }); @@ -686,7 +680,9 @@ describe('HbarLimitService', function () { const testIsDailyBudgetExceeded = async (remainingBudget: number, expected: boolean) => { // @ts-ignore hbarLimitService.remainingBudget = remainingBudget; - await expect(hbarLimitService['isDailyBudgetExceeded'](mode, methodName)).to.eventually.equal(expected); + await expect( + hbarLimitService['isDailyBudgetExceeded'](mode, methodName, undefined, requestDetails), + ).to.eventually.equal(expected); }; it('should return true when the remaining budget is zero', async function () { @@ -705,7 +701,7 @@ describe('HbarLimitService', function () { hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(createSpendingPlan(mockPlanId)); hbarSpendingPlanRepositoryStub.addAmountToSpentToday.rejects(new Error('Failed to add expense')); - await expect(hbarLimitService.addExpense(100, mockEthAddress)).to.be.eventually.rejectedWith( + await expect(hbarLimitService.addExpense(100, mockEthAddress, requestDetails)).to.be.eventually.rejectedWith( 'Failed to add expense', ); }); diff --git a/packages/relay/tests/lib/services/metricService/metricService.spec.ts b/packages/relay/tests/lib/services/metricService/metricService.spec.ts index f90d8f96fa..2334dc0d5a 100644 --- a/packages/relay/tests/lib/services/metricService/metricService.spec.ts +++ b/packages/relay/tests/lib/services/metricService/metricService.spec.ts @@ -31,11 +31,12 @@ import { Utils } from '../../../../src/utils'; import constants from '../../../../src/lib/constants'; import HbarLimit from '../../../../src/lib/hbarlimiter'; import { MirrorNodeClient, SDKClient } from '../../../../src/lib/clients'; -import { calculateTxRecordChargeAmount, getRequestId } from '../../../helpers'; +import { calculateTxRecordChargeAmount } from '../../../helpers'; import MetricService from '../../../../src/lib/services/metricService/metricService'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; -import { IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from '../../../../src/lib/types/events'; +import { IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from '../../../../src/lib/types'; import { Hbar, Long, Status, Client, AccountId, TransactionRecord, TransactionRecordQuery } from '@hashgraph/sdk'; +import { RequestDetails } from '../../../../src/lib/types'; config({ path: resolve(__dirname, '../../../test.env') }); const registry = new Registry(); @@ -50,6 +51,7 @@ describe('Metric Service', function () { let metricService: MetricService; let mirrorNodeClient: MirrorNodeClient; + const requestDetails = new RequestDetails({ requestId: 'metricServiceTest', ipAddress: '0.0.0.0' }); const mockedTxFee = 36900000; const operatorAccountId = `0.0.1022`; const mockedCallerName = 'caller_name'; @@ -157,10 +159,10 @@ describe('Metric Service', function () { const mockedExecuteTransactionEventPayload: IExecuteTransactionEventPayload = { transactionId: mockedTransactionId, callerName: mockedCallerName, - requestId: getRequestId(), txConstructorName: mockedConstructorName, operatorAccountId, interactingEntity: mockedInteractingEntity, + requestDetails, }; it('Should execute captureTransactionMetrics() by retrieving transaction record from MIRROR NODE client', async () => { @@ -178,14 +180,14 @@ describe('Metric Service', function () { expect(originalBudget - updatedBudget).to.eq(mockedTxFee); // validate cost metrics - // @ts-ignore - const costMetricObject = (await metricService.getCostMetric().get()).values.find( + const costMetricObject = (await metricService['consensusNodeClientHistogramCost'].get()).values.find( (metric) => metric.metricName === metricHistogramCostSumTitle, - )!; - expect(costMetricObject.metricName).to.eq(metricHistogramCostSumTitle); - expect(costMetricObject.labels.caller).to.eq(mockedCallerName); - expect(costMetricObject.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(costMetricObject.value).to.eq(mockedTxFee); + ); + expect(costMetricObject).to.not.be.undefined; + expect(costMetricObject!.metricName).to.eq(metricHistogramCostSumTitle); + expect(costMetricObject!.labels.caller).to.eq(mockedCallerName); + expect(costMetricObject!.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(costMetricObject!.value).to.eq(mockedTxFee); }); it('Should execute captureTransactionMetrics() by retrieving transaction record from CONSENSUS NODE client', async () => { @@ -209,7 +211,7 @@ describe('Metric Service', function () { // validate cost metric // @ts-ignore - const metricObjects = await metricService.getCostMetric().get(); + const metricObjects = await metricService['consensusNodeClientHistogramCost'].get(); const txRecordFeeMetricObject = metricObjects.values.find((metric) => { return ( metric.labels.mode === constants.EXECUTION_MODE.RECORD && metric.metricName === metricHistogramCostSumTitle @@ -234,7 +236,7 @@ describe('Metric Service', function () { // validate gas metric // @ts-ignore - const gasMetricObject = (await metricService.getGasFeeMetric().get()).values.find( + const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( (metric) => metric.metricName === metricHistogramGasFeeSumTitle, )!; @@ -273,7 +275,7 @@ describe('Metric Service', function () { // validate cost metric // @ts-ignore - const metricObjects = await metricService.getCostMetric().get(); + const metricObjects = await metricService['consensusNodeClientHistogramCost'].get(); const txRecordFeeMetricObject = metricObjects.values.find((metric) => { return ( metric.labels.mode === constants.EXECUTION_MODE.RECORD && metric.metricName === metricHistogramCostSumTitle @@ -298,7 +300,7 @@ describe('Metric Service', function () { // validate gas metric // @ts-ignore - const gasMetricObject = (await metricService.getGasFeeMetric().get()).values.find( + const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( (metric) => metric.metricName === metricHistogramGasFeeSumTitle, )!; @@ -322,7 +324,7 @@ describe('Metric Service', function () { gasUsed: mockedGasUsed, interactingEntity: mockedInteractingEntity, status: 'SUCCESS', - requestId: getRequestId(), + requestDetails, }; it('should execute addExpenseAndCaptureMetrics() to capture metrics in HBAR limiter and metric registry', async () => { const originalBudget = hbarLimiter.getRemainingBudget(); @@ -336,7 +338,7 @@ describe('Metric Service', function () { // validate cost metrics // @ts-ignore - const costMetricObject = (await metricService.getCostMetric().get()).values.find( + const costMetricObject = (await metricService['consensusNodeClientHistogramCost'].get()).values.find( (metric) => metric.metricName === metricHistogramCostSumTitle, )!; expect(costMetricObject.metricName).to.eq(metricHistogramCostSumTitle); @@ -346,7 +348,7 @@ describe('Metric Service', function () { // validate gas metric // @ts-ignore - const gasMetricObject = (await metricService.getGasFeeMetric().get()).values.find( + const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( (metric) => metric.metricName === metricHistogramGasFeeSumTitle, )!; @@ -373,7 +375,7 @@ describe('Metric Service', function () { // validate cost metrics // @ts-ignore - const costMetricObject = (await metricService.getCostMetric().get()).values.find( + const costMetricObject = (await metricService['consensusNodeClientHistogramCost'].get()).values.find( (metric) => metric.metricName === metricHistogramCostSumTitle, )!; expect(costMetricObject.metricName).to.eq(metricHistogramCostSumTitle); @@ -383,7 +385,7 @@ describe('Metric Service', function () { // validate gas metric // @ts-ignore - const gasMetricObject = (await metricService.getGasFeeMetric().get()).values.find( + const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( (metric) => metric.metricName === metricHistogramGasFeeSumTitle, )!; diff --git a/packages/server/src/koaJsonRpc/index.ts b/packages/server/src/koaJsonRpc/index.ts index f32313dc30..a67172ba07 100644 --- a/packages/server/src/koaJsonRpc/index.ts +++ b/packages/server/src/koaJsonRpc/index.ts @@ -37,6 +37,7 @@ import { import Koa from 'koa'; import { Histogram, Registry } from 'prom-client'; import { JsonRpcError, predefined } from '@hashgraph/json-rpc-relay'; +import { IRequestDetails, RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import { RpcErrorCodeToStatusMap } from './lib/HttpStatusCodeAndMessage'; import { getBatchRequestsEnabled, @@ -47,6 +48,7 @@ import { hasOwnProperty, } from './lib/utils'; import { IJsonRpcRequest } from './lib/IJsonRpcRequest'; +import { IJsonRpcResponse } from './lib/IJsonRpcResponse'; dotenv.config({ path: path.resolve(__dirname, '../../../../../.env') }); @@ -72,10 +74,13 @@ export default class KoaJsonRpc { private readonly methodResponseHistogram: Histogram; private requestId: string; + private requestIpAddress: string; + private connectionId?: string; constructor(logger: Logger, register: Registry, opts?: { limit: string | null }) { this.koaApp = new Koa(); this.requestId = ''; + this.requestIpAddress = ''; this.registry = Object.create(null); this.registryTotal = Object.create(null); this.methodConfig = methodConfiguration; @@ -105,7 +110,7 @@ export default class KoaJsonRpc { rpcApp(): (ctx: Koa.Context, _next: Koa.Next) => Promise { return async (ctx: Koa.Context, _next: Koa.Next) => { - this.requestId = ctx.state.reqId; + this.updateRequestDetails({ requestId: ctx.state.reqId, ipAddress: ctx.request.ip }); ctx.set(REQUEST_ID_HEADER_NAME, this.requestId); if (ctx.request.method !== 'POST') { @@ -115,7 +120,7 @@ export default class KoaJsonRpc { return; } - let body: any; + let body: IJsonRpcRequest | IJsonRpcRequest[]; try { body = await parse.json(ctx, { limit: this.limit }); } catch (err) { @@ -131,7 +136,7 @@ export default class KoaJsonRpc { }; } - private async handleSingleRequest(ctx: Koa.Context, body: any): Promise { + private async handleSingleRequest(ctx: Koa.Context, body: IJsonRpcRequest): Promise { ctx.state.methodName = body.method; const response = await this.getRequestResult(body, ctx.ip); ctx.body = response; @@ -188,7 +193,7 @@ export default class KoaJsonRpc { ctx.state.status = responseSuccessStatusCode; } - async getRequestResult(request: any, ip: string): Promise { + async getRequestResult(request: IJsonRpcRequest, ip: string): Promise { try { const methodName = request.method; @@ -230,7 +235,7 @@ export default class KoaJsonRpc { !hasOwnProperty(body, 'id') ) { this.logger.warn( - `[${this.getRequestId()}] Invalid request, body.jsonrpc: ${body.jsonrpc}, body[method]: ${ + `${this.getFormattedLogPrefix()} Invalid request, body.jsonrpc: ${body.jsonrpc}, body[method]: ${ body.method }, body[id]: ${body.id}, ctx.request.method: ${body.method}`, ); @@ -245,7 +250,7 @@ export default class KoaJsonRpc { return true; } - this.logger.warn(`[${this.getRequestId()}] Method not found: ${methodName}`); + this.logger.warn(`${this.getFormattedLogPrefix()} Method not found: ${methodName}`); return false; } @@ -253,8 +258,18 @@ export default class KoaJsonRpc { return this.koaApp; } - getRequestId(): string { - return this.requestId; + getRequestDetails(): RequestDetails { + return new RequestDetails({ + requestId: this.requestId, + ipAddress: this.requestIpAddress, + connectionId: this.connectionId, + }); + } + + updateRequestDetails(details: IRequestDetails): void { + this.requestId = details.requestId; + this.requestIpAddress = details.ipAddress; + this.connectionId = details.connectionId; } hasInvalidRequestId(body: IJsonRpcRequest): boolean { @@ -263,11 +278,15 @@ export default class KoaJsonRpc { // If the request is invalid, we still want to return a valid JSON-RPC response, default id to 0 body.id = '0'; this.logger.warn( - `[${this.getRequestId()}] Optional JSON-RPC 2.0 request id encountered. Will continue and default id to 0 in response`, + `${this.getFormattedLogPrefix()} Optional JSON-RPC 2.0 request id encountered. Will continue and default id to 0 in response`, ); return false; } return !hasId; } + + private getFormattedLogPrefix(): string { + return this.getRequestDetails().formattedLogPrefix; + } } diff --git a/packages/server/src/koaJsonRpc/lib/IJsonRpcRequest.ts b/packages/server/src/koaJsonRpc/lib/IJsonRpcRequest.ts index cde463275a..7f78ec1b91 100644 --- a/packages/server/src/koaJsonRpc/lib/IJsonRpcRequest.ts +++ b/packages/server/src/koaJsonRpc/lib/IJsonRpcRequest.ts @@ -1,6 +1,6 @@ /*- * - * Hedera JSON RPC Relay - Hardhat Example + * Hedera JSON RPC Relay * * Copyright (C) 2024 Hedera Hashgraph, LLC * @@ -19,7 +19,7 @@ */ export interface IJsonRpcRequest { - id: string; + id: string | number; jsonrpc: string; method: string; params?: any[]; diff --git a/packages/ws-server/src/utils/formatters.ts b/packages/server/src/koaJsonRpc/lib/IJsonRpcResponse.ts similarity index 57% rename from packages/ws-server/src/utils/formatters.ts rename to packages/server/src/koaJsonRpc/lib/IJsonRpcResponse.ts index aa8080ec51..ba14478890 100644 --- a/packages/ws-server/src/utils/formatters.ts +++ b/packages/server/src/koaJsonRpc/lib/IJsonRpcResponse.ts @@ -1,8 +1,8 @@ -/* - +/*- * * Hedera JSON RPC Relay * - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,9 @@ * */ -/** - * Formats an ID message for logging purposes. - * @param {string } title - The title of the ID to be formatted. - * @param {string | undefined} id - The ID to be formatted. - * @returns {string} Returns a formatted ID message if an ID is provided, otherwise an empty string. - */ -export const formatIdMessage = (title: string, id?: string): string => { - return id ? `[${title}: ${id}]` : ''; -}; +export interface IJsonRpcResponse { + id: string | number | null; + jsonrpc: string; + result?: any; + error?: any; +} diff --git a/packages/server/src/koaJsonRpc/lib/RpcResponse.ts b/packages/server/src/koaJsonRpc/lib/RpcResponse.ts index c70d7ef34a..345973c182 100644 --- a/packages/server/src/koaJsonRpc/lib/RpcResponse.ts +++ b/packages/server/src/koaJsonRpc/lib/RpcResponse.ts @@ -18,8 +18,15 @@ * */ -export default function jsonResp(id, error, result) { - const response: any = {}; +import { JsonRpcError } from '@hashgraph/json-rpc-relay'; +import { IJsonRpcResponse } from './IJsonRpcResponse'; + +export default function jsonResp( + id: string | number | null, + error: JsonRpcError | null, + result: any, +): IJsonRpcResponse { + const response: IJsonRpcResponse = {} as IJsonRpcResponse; if (error && result) { throw new Error('Mutually exclusive error and result exist'); diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index d0a42a08fa..db557c0613 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -19,7 +19,7 @@ */ import { JsonRpcError, MirrorNodeClientError, predefined, Relay, RelayImpl } from '@hashgraph/json-rpc-relay'; -import { ITracerConfig } from '@hashgraph/json-rpc-relay/src/lib/types'; +import { ITracerConfig, RequestDetails } from '@hashgraph/json-rpc-relay/src/lib/types'; import { collectDefaultMetrics, Histogram, Registry } from 'prom-client'; import KoaJsonRpc from './koaJsonRpc'; import { TracerType, TYPES, Validator } from './validator'; @@ -114,7 +114,7 @@ app.getKoaApp().use(async (ctx, next) => { app.getKoaApp().use(async (ctx, next) => { if (ctx.url === '/health/readiness') { try { - const result = relay.eth().chainId(); + const result = relay.eth().chainId(app.getRequestDetails()); if (result.indexOf('0x12') >= 0) { ctx.status = 200; ctx.body = 'OK'; @@ -197,26 +197,26 @@ app.getKoaApp().use(async (ctx, next) => { return next(); }); -const logAndHandleResponse = async (methodName: any, methodParams: any, methodFunction: any) => { - const requestId = app.getRequestId(); - const requestIdPrefix = requestId ? formatRequestIdMessage(requestId) : ''; +const logAndHandleResponse = async (methodName: string, methodParams: any[], methodFunction: any) => { + const requestDetails = app.getRequestDetails(); try { const methodValidations = Validator.METHODS[methodName]; if (methodValidations) { logger.debug( - `${requestIdPrefix} Validating method parameters for ${methodName}, params: ${JSON.stringify(methodParams)}`, + `${requestDetails.formattedRequestId} Validating method parameters for ${methodName}, params: ${JSON.stringify( + methodParams, + )}`, ); Validator.validateParams(methodParams, methodValidations); } - - const response = await methodFunction(requestIdPrefix); + const response = await methodFunction(requestDetails); if (response instanceof JsonRpcError) { // log error only if it is not a contract revert, otherwise log it as debug if (response.code === predefined.CONTRACT_REVERT().code) { - logger.debug(`${requestIdPrefix} ${response.message}`); + logger.debug(`${requestDetails.formattedRequestId} ${response.message}`); } else { - logger.error(`${requestIdPrefix} ${response.message}`); + logger.error(`${requestDetails.formattedRequestId} ${response.message}`); } return new JsonRpcError( @@ -225,7 +225,7 @@ const logAndHandleResponse = async (methodName: any, methodParams: any, methodFu message: response.message, data: response.data, }, - requestId, + requestDetails.requestId, ); } return response; @@ -238,17 +238,17 @@ const logAndHandleResponse = async (methodName: any, methodParams: any, methodFu } else if (e instanceof JsonRpcError) { error = e; } else { - logger.error(`${requestIdPrefix} ${e.message}`); + logger.error(`${requestDetails.formattedRequestId} ${e.message}`); } - logger.error(`${requestIdPrefix} ${error.message}`); + logger.error(`${requestDetails.formattedRequestId} ${error.message}`); return new JsonRpcError( { code: error.code, message: error.message, data: error.data, }, - requestId, + requestDetails.requestId, ); } }; @@ -273,7 +273,7 @@ app.useRpc('net_version', async () => { * returns: Block number - hex encoded integer */ app.useRpc('eth_blockNumber', async () => { - return logAndHandleResponse('eth_blockNumber', [], (requestId) => relay.eth().blockNumber(requestId)); + return logAndHandleResponse('eth_blockNumber', [], (requestDetails) => relay.eth().blockNumber(requestDetails)); }); /** @@ -288,8 +288,8 @@ app.useRpc('eth_estimateGas', async (params: any) => { delete params[0].data; } - return logAndHandleResponse('eth_estimateGas', params, (requestId) => - relay.eth().estimateGas(params?.[0], params?.[1], requestId), + return logAndHandleResponse('eth_estimateGas', params, (requestDetails) => + relay.eth().estimateGas(params?.[0], params?.[1], requestDetails), ); }); @@ -301,8 +301,8 @@ app.useRpc('eth_estimateGas', async (params: any) => { * returns: Balance - hex encoded integer */ app.useRpc('eth_getBalance', async (params: any) => { - return logAndHandleResponse('eth_getBalance', params, (requestId) => - relay.eth().getBalance(params?.[0], params?.[1], requestId), + return logAndHandleResponse('eth_getBalance', params, (requestDetails) => + relay.eth().getBalance(params?.[0], params?.[1], requestDetails), ); }); @@ -314,8 +314,8 @@ app.useRpc('eth_getBalance', async (params: any) => { * returns: Bytecode - hex encoded bytes */ app.useRpc('eth_getCode', async (params: any) => { - return logAndHandleResponse('eth_getCode', params, (requestId) => - relay.eth().getCode(params?.[0], params?.[1], requestId), + return logAndHandleResponse('eth_getCode', params, (requestDetails) => + relay.eth().getCode(params?.[0], params?.[1], requestDetails), ); }); @@ -325,7 +325,7 @@ app.useRpc('eth_getCode', async (params: any) => { * returns: Chain ID - integer */ app.useRpc('eth_chainId', async () => { - return logAndHandleResponse('eth_chainId', [], (requestId) => relay.eth().chainId(requestId)); + return logAndHandleResponse('eth_chainId', [], (requestDetails) => relay.eth().chainId(requestDetails)); }); /** @@ -336,8 +336,8 @@ app.useRpc('eth_chainId', async () => { * returns: Block object */ app.useRpc('eth_getBlockByNumber', async (params: any) => { - return logAndHandleResponse('eth_getBlockByNumber', params, (requestId) => - relay.eth().getBlockByNumber(params?.[0], Boolean(params?.[1]), requestId), + return logAndHandleResponse('eth_getBlockByNumber', params, (requestDetails) => + relay.eth().getBlockByNumber(params?.[0], Boolean(params?.[1]), requestDetails), ); }); @@ -349,8 +349,8 @@ app.useRpc('eth_getBlockByNumber', async (params: any) => { * returns: Block object */ app.useRpc('eth_getBlockByHash', async (params: any) => { - return logAndHandleResponse('eth_getBlockByHash', params, (requestId) => - relay.eth().getBlockByHash(params?.[0], Boolean(params?.[1]), requestId), + return logAndHandleResponse('eth_getBlockByHash', params, (requestDetails) => + relay.eth().getBlockByHash(params?.[0], Boolean(params?.[1]), requestDetails), ); }); @@ -360,7 +360,7 @@ app.useRpc('eth_getBlockByHash', async (params: any) => { * returns: Gas price - hex encoded integer */ app.useRpc('eth_gasPrice', async () => { - return logAndHandleResponse('eth_gasPrice', [], (requestId) => relay.eth().gasPrice(requestId)); + return logAndHandleResponse('eth_gasPrice', [], (requestDetails) => relay.eth().gasPrice(requestDetails)); }); /** @@ -371,8 +371,8 @@ app.useRpc('eth_gasPrice', async () => { * returns: Transaction count - hex encoded integer */ app.useRpc('eth_getTransactionCount', async (params: any) => { - return logAndHandleResponse('eth_getTransactionCount', params, (requestId) => - relay.eth().getTransactionCount(params?.[0], params?.[1], requestId), + return logAndHandleResponse('eth_getTransactionCount', params, (requestDetails) => + relay.eth().getTransactionCount(params?.[0], params?.[1], requestDetails), ); }); @@ -383,7 +383,9 @@ app.useRpc('eth_getTransactionCount', async (params: any) => { * returns: Value - hex encoded bytes */ app.useRpc('eth_call', async (params: any) => { - return logAndHandleResponse('eth_call', params, (requestId) => relay.eth().call(params?.[0], params?.[1], requestId)); + return logAndHandleResponse('eth_call', params, (requestDetails) => + relay.eth().call(params?.[0], params?.[1], requestDetails), + ); }); /** @@ -393,8 +395,8 @@ app.useRpc('eth_call', async (params: any) => { * returns: Transaction hash - 32 byte hex value */ app.useRpc('eth_sendRawTransaction', async (params: any) => { - return logAndHandleResponse('eth_sendRawTransaction', params, (requestId) => - relay.eth().sendRawTransaction(params?.[0], requestId), + return logAndHandleResponse('eth_sendRawTransaction', params, (requestDetails) => + relay.eth().sendRawTransaction(params?.[0], requestDetails), ); }); @@ -405,8 +407,8 @@ app.useRpc('eth_sendRawTransaction', async (params: any) => { * returns: Transaction Receipt - object */ app.useRpc('eth_getTransactionReceipt', async (params: any) => { - return logAndHandleResponse('eth_getTransactionReceipt', params, (requestId) => - relay.eth().getTransactionReceipt(params?.[0], requestId), + return logAndHandleResponse('eth_getTransactionReceipt', params, (requestDetails) => + relay.eth().getTransactionReceipt(params?.[0], requestDetails), ); }); @@ -420,7 +422,7 @@ app.useRpc('web3_clientVersion', async () => { * returns: Accounts - hex encoded address */ app.useRpc('eth_accounts', async () => { - return logAndHandleResponse('eth_accounts', [], (requestId) => relay.eth().accounts(requestId)); + return logAndHandleResponse('eth_accounts', [], (requestDetails) => relay.eth().accounts(requestDetails)); }); /** @@ -430,8 +432,8 @@ app.useRpc('eth_accounts', async () => { * returns: Transaction Object */ app.useRpc('eth_getTransactionByHash', async (params: any) => { - return logAndHandleResponse('eth_getTransactionByHash', params, (requestId) => - relay.eth().getTransactionByHash(params[0], requestId), + return logAndHandleResponse('eth_getTransactionByHash', params, (requestDetails) => + relay.eth().getTransactionByHash(params[0], requestDetails), ); }); @@ -448,8 +450,8 @@ app.useRpc('eth_getTransactionByHash', async (params: any) => { * - reward - Array of effective priority fee per gas data. */ app.useRpc('eth_feeHistory', async (params: any) => { - return logAndHandleResponse('eth_feeHistory', params, (requestId) => - relay.eth().feeHistory(Number(params?.[0]), params?.[1], params?.[2], requestId), + return logAndHandleResponse('eth_feeHistory', params, (requestDetails) => + relay.eth().feeHistory(Number(params?.[0]), params?.[1], params?.[2], requestDetails), ); }); @@ -460,8 +462,8 @@ app.useRpc('eth_feeHistory', async (params: any) => { * returns: Block Transaction Count - Hex encoded integer */ app.useRpc('eth_getBlockTransactionCountByHash', async (params: any) => { - return logAndHandleResponse('eth_getBlockTransactionCountByHash', params, (requestId) => - relay.eth().getBlockTransactionCountByHash(params?.[0], requestId), + return logAndHandleResponse('eth_getBlockTransactionCountByHash', params, (requestDetails) => + relay.eth().getBlockTransactionCountByHash(params?.[0], requestDetails), ); }); @@ -472,8 +474,8 @@ app.useRpc('eth_getBlockTransactionCountByHash', async (params: any) => { * returns: Block Transaction Count - Hex encoded integer */ app.useRpc('eth_getBlockTransactionCountByNumber', async (params: any) => { - return logAndHandleResponse('eth_getBlockTransactionCountByNumber', params, (requestId) => - relay.eth().getBlockTransactionCountByNumber(params?.[0], requestId), + return logAndHandleResponse('eth_getBlockTransactionCountByNumber', params, (requestDetails) => + relay.eth().getBlockTransactionCountByNumber(params?.[0], requestDetails), ); }); @@ -486,8 +488,10 @@ app.useRpc('eth_getBlockTransactionCountByNumber', async (params: any) => { app.useRpc('eth_getLogs', async (params: any) => { const filter = params[0]; - return logAndHandleResponse('eth_getLogs', params, (requestId) => - relay.eth().getLogs(filter.blockHash, filter.fromBlock, filter.toBlock, filter.address, filter.topics, requestId), + return logAndHandleResponse('eth_getLogs', params, (requestDetails) => + relay + .eth() + .getLogs(filter.blockHash, filter.fromBlock, filter.toBlock, filter.address, filter.topics, requestDetails), ); }); @@ -500,8 +504,8 @@ app.useRpc('eth_getLogs', async (params: any) => { * returns: Value - The storage value */ app.useRpc('eth_getStorageAt', async (params: any) => { - return logAndHandleResponse('eth_getStorageAt', params, (requestId) => - relay.eth().getStorageAt(params?.[0], params?.[1], params?.[2], requestId), + return logAndHandleResponse('eth_getStorageAt', params, (requestDetails) => + relay.eth().getStorageAt(params?.[0], params?.[1], requestDetails, params?.[2]), ); }); @@ -513,8 +517,8 @@ app.useRpc('eth_getStorageAt', async (params: any) => { * returns: Transaction */ app.useRpc('eth_getTransactionByBlockHashAndIndex', async (params: any) => { - return logAndHandleResponse('eth_getTransactionByBlockHashAndIndex', params, (requestId) => - relay.eth().getTransactionByBlockHashAndIndex(params?.[0], params?.[1], requestId), + return logAndHandleResponse('eth_getTransactionByBlockHashAndIndex', params, (requestDetails) => + relay.eth().getTransactionByBlockHashAndIndex(params?.[0], params?.[1], requestDetails), ); }); @@ -526,8 +530,8 @@ app.useRpc('eth_getTransactionByBlockHashAndIndex', async (params: any) => { * returns: Transaction */ app.useRpc('eth_getTransactionByBlockNumberAndIndex', async (params: any) => { - return logAndHandleResponse('eth_getTransactionByBlockNumberAndIndex', params, (requestId) => - relay.eth().getTransactionByBlockNumberAndIndex(params?.[0], params?.[1], requestId), + return logAndHandleResponse('eth_getTransactionByBlockNumberAndIndex', params, (requestDetails) => + relay.eth().getTransactionByBlockNumberAndIndex(params?.[0], params?.[1], requestDetails), ); }); @@ -541,8 +545,8 @@ app.useRpc('eth_getTransactionByBlockNumberAndIndex', async (params: any) => { * returns: null */ app.useRpc('eth_getUncleByBlockHashAndIndex', async () => { - return logAndHandleResponse('eth_getUncleByBlockHashAndIndex', [], (requestId) => - relay.eth().getUncleByBlockHashAndIndex(requestId), + return logAndHandleResponse('eth_getUncleByBlockHashAndIndex', [], (requestDetails) => + relay.eth().getUncleByBlockHashAndIndex(requestDetails), ); }); @@ -555,8 +559,8 @@ app.useRpc('eth_getUncleByBlockHashAndIndex', async () => { * returns: null */ app.useRpc('eth_getUncleByBlockNumberAndIndex', async () => { - return logAndHandleResponse('eth_getUncleByBlockNumberAndIndex', [], (requestId) => - relay.eth().getUncleByBlockNumberAndIndex(requestId), + return logAndHandleResponse('eth_getUncleByBlockNumberAndIndex', [], (requestDetails) => + relay.eth().getUncleByBlockNumberAndIndex(requestDetails), ); }); @@ -568,8 +572,8 @@ app.useRpc('eth_getUncleByBlockNumberAndIndex', async () => { * returns: 0x0 */ app.useRpc('eth_getUncleCountByBlockHash', async () => { - return logAndHandleResponse('eth_getUncleCountByBlockHash', [], (requestId) => - relay.eth().getUncleCountByBlockHash(requestId), + return logAndHandleResponse('eth_getUncleCountByBlockHash', [], (requestDetails) => + relay.eth().getUncleCountByBlockHash(requestDetails), ); }); @@ -581,8 +585,8 @@ app.useRpc('eth_getUncleCountByBlockHash', async () => { * returns: 0x0 */ app.useRpc('eth_getUncleCountByBlockNumber', async () => { - return logAndHandleResponse('eth_getUncleCountByBlockNumber', [], (requestId) => - relay.eth().getUncleCountByBlockNumber(requestId), + return logAndHandleResponse('eth_getUncleCountByBlockNumber', [], (requestDetails) => + relay.eth().getUncleCountByBlockNumber(requestDetails), ); }); @@ -593,7 +597,7 @@ app.useRpc('eth_getUncleCountByBlockNumber', async () => { * returns: code: -32000 */ app.useRpc('eth_getWork', async () => { - return logAndHandleResponse('eth_getWork', [], (requestId) => relay.eth().getWork(requestId)); + return logAndHandleResponse('eth_getWork', [], (requestDetails) => relay.eth().getWork(requestDetails)); }); /** @@ -604,7 +608,7 @@ app.useRpc('eth_getWork', async () => { * returns: 0x0 */ app.useRpc('eth_hashrate', async () => { - return logAndHandleResponse('eth_hashrate', [], (requestId) => relay.eth().hashrate(requestId)); + return logAndHandleResponse('eth_hashrate', [], (requestDetails) => relay.eth().hashrate(requestDetails)); }); /** @@ -615,7 +619,7 @@ app.useRpc('eth_hashrate', async () => { * returns: false */ app.useRpc('eth_mining', async () => { - return logAndHandleResponse('eth_mining', [], (requestId) => relay.eth().mining(requestId)); + return logAndHandleResponse('eth_mining', [], (requestDetails) => relay.eth().mining(requestDetails)); }); /** @@ -626,7 +630,7 @@ app.useRpc('eth_mining', async () => { * returns: false */ app.useRpc('eth_submitWork', async () => { - return logAndHandleResponse('eth_submitWork', [], (requestId) => relay.eth().submitWork(requestId)); + return logAndHandleResponse('eth_submitWork', [], (requestDetails) => relay.eth().submitWork(requestDetails)); }); /** @@ -636,7 +640,7 @@ app.useRpc('eth_submitWork', async () => { * returns: false */ app.useRpc('eth_syncing', async () => { - return logAndHandleResponse('eth_syncing', [], (requestId) => relay.eth().syncing(requestId)); + return logAndHandleResponse('eth_syncing', [], (requestDetails) => relay.eth().syncing(requestDetails)); }); /** @@ -657,8 +661,8 @@ app.useRpc('web3_client_version', async () => { * returns: 0x0 */ app.useRpc('eth_maxPriorityFeePerGas', async () => { - return logAndHandleResponse('eth_maxPriorityFeePerGas', [], (requestId) => - relay.eth().maxPriorityFeePerGas(requestId), + return logAndHandleResponse('eth_maxPriorityFeePerGas', [], (requestDetails) => + relay.eth().maxPriorityFeePerGas(requestDetails), ); }); @@ -667,7 +671,7 @@ app.useRpc('eth_maxPriorityFeePerGas', async () => { */ app.useRpc('debug_traceTransaction', async (params: any) => { - return logAndHandleResponse('debug_traceTransaction', params, (requestId: string) => { + return logAndHandleResponse('debug_traceTransaction', params, (requestDetails: RequestDetails) => { const transactionIdOrHash = params[0]; let tracer: TracerType = TracerType.OpcodeLogger; let tracerConfig: ITracerConfig = {}; @@ -689,7 +693,7 @@ app.useRpc('debug_traceTransaction', async (params: any) => { } } - return relay.eth().debugService().debug_traceTransaction(transactionIdOrHash, tracer, tracerConfig, requestId); + return relay.eth().debugService().debug_traceTransaction(transactionIdOrHash, tracer, tracerConfig, requestDetails); }); }); @@ -704,27 +708,27 @@ app.useRpc('debug_traceTransaction', async (params: any) => { */ app.useRpc('eth_newFilter', async (params: any) => { const filter = params[0]; - return logAndHandleResponse('eth_newFilter', [], (requestId) => + return logAndHandleResponse('eth_newFilter', [], (requestDetails) => relay .eth() .filterService() - .newFilter(filter?.fromBlock, filter?.toBlock, filter?.address, filter?.topics, requestId), + .newFilter(filter?.fromBlock, filter?.toBlock, requestDetails, filter?.address, filter?.topics), ); }); app.useRpc('eth_getFilterLogs', async (params: any) => { - return logAndHandleResponse('eth_getFilterLogs', params, (requestId) => + return logAndHandleResponse('eth_getFilterLogs', params, (requestDetails) => relay .eth() .filterService() - .getFilterLogs(params?.[0], requestId), + .getFilterLogs(params?.[0], requestDetails), ); }); app.useRpc('eth_getFilterChanges', async (params: any) => { const filterId = params[0]; - return logAndHandleResponse('eth_getFilterChanges', [], (requestId) => - relay.eth().filterService().getFilterChanges(filterId, requestId), + return logAndHandleResponse('eth_getFilterChanges', [], (requestDetails) => + relay.eth().filterService().getFilterChanges(filterId, requestDetails), ); }); @@ -734,8 +738,8 @@ app.useRpc('eth_getFilterChanges', async (params: any) => { * returns: string */ app.useRpc('eth_newBlockFilter', async (params: any) => { - return logAndHandleResponse('eth_newBlockFilter', [], (requestId) => - relay.eth().filterService().newBlockFilter(requestId), + return logAndHandleResponse('eth_newBlockFilter', [], (requestDetails) => + relay.eth().filterService().newBlockFilter(requestDetails), ); }); @@ -743,8 +747,8 @@ app.useRpc('eth_newBlockFilter', async (params: any) => { * Not Supported */ app.useRpc('eth_newPendingTransactionFilter', async () => { - return logAndHandleResponse('eth_newPendingTransactionFilter', [], (requestId) => - relay.eth().filterService().newPendingTransactionFilter(requestId), + return logAndHandleResponse('eth_newPendingTransactionFilter', [], (requestDetails) => + relay.eth().filterService().newPendingTransactionFilter(requestDetails), ); }); @@ -755,11 +759,11 @@ app.useRpc('eth_newPendingTransactionFilter', async () => { * returns: boolean */ app.useRpc('eth_uninstallFilter', async (params: any) => { - return logAndHandleResponse('eth_uninstallFilter', params, (requestId) => + return logAndHandleResponse('eth_uninstallFilter', params, (requestDetails) => relay .eth() .filterService() - .uninstallFilter(params?.[0], requestId), + .uninstallFilter(params?.[0], requestDetails), ); }); @@ -767,27 +771,33 @@ app.useRpc('eth_uninstallFilter', async (params: any) => { * Not supported */ app.useRpc('eth_submitHashrate', async () => { - return logAndHandleResponse('eth_submitHashrate', [], (requestId) => relay.eth().submitHashrate(requestId)); + return logAndHandleResponse('eth_submitHashrate', [], (requestDetails) => relay.eth().submitHashrate(requestDetails)); }); app.useRpc('eth_signTransaction', async () => { - return logAndHandleResponse('eth_signTransaction', [], (requestId) => relay.eth().signTransaction(requestId)); + return logAndHandleResponse('eth_signTransaction', [], (requestDetails) => + relay.eth().signTransaction(requestDetails), + ); }); app.useRpc('eth_sign', async () => { - return logAndHandleResponse('eth_sign', [], (requestId) => relay.eth().sign(requestId)); + return logAndHandleResponse('eth_sign', [], (requestDetails) => relay.eth().sign(requestDetails)); }); app.useRpc('eth_sendTransaction', async () => { - return logAndHandleResponse('eth_sendTransaction', [], (requestId) => relay.eth().sendTransaction(requestId)); + return logAndHandleResponse('eth_sendTransaction', [], (requestDetails) => + relay.eth().sendTransaction(requestDetails), + ); }); app.useRpc('eth_protocolVersion', async () => { - return logAndHandleResponse('eth_protocolVersion', [], (requestId) => relay.eth().protocolVersion(requestId)); + return logAndHandleResponse('eth_protocolVersion', [], (requestDetails) => + relay.eth().protocolVersion(requestDetails), + ); }); app.useRpc('eth_coinbase', async () => { - return logAndHandleResponse('eth_coinbase', [], (requestId) => relay.eth().coinbase(requestId)); + return logAndHandleResponse('eth_coinbase', [], (requestDetails) => relay.eth().coinbase(requestDetails)); }); const rpcApp = app.rpcApp(); diff --git a/packages/server/tests/acceptance/cacheService.spec.ts b/packages/server/tests/acceptance/cacheService.spec.ts index c734a1dccd..fb148c59da 100644 --- a/packages/server/tests/acceptance/cacheService.spec.ts +++ b/packages/server/tests/acceptance/cacheService.spec.ts @@ -21,6 +21,7 @@ import { expect } from 'chai'; import { CacheService } from '../../../../packages/relay/src/lib/services/cacheService/cacheService'; import { Registry } from 'prom-client'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; const registry = new Registry(); const DATA_LABEL_PREFIX = 'acceptance-test-'; @@ -32,6 +33,8 @@ const CALLING_METHOD = 'AcceptanceTest'; describe('@cache-service Acceptance Tests for shared cache', function () { let cacheService: CacheService; + const requestDetails = new RequestDetails({ requestId: 'cacheServiceTest', ipAddress: '0.0.0.0' }); + before(async () => { cacheService = new CacheService(global.logger, registry); await new Promise((r) => setTimeout(r, 1000)); @@ -40,22 +43,22 @@ describe('@cache-service Acceptance Tests for shared cache', function () { it('Correctly performs set, get and delete operations', async () => { const dataLabel = `${DATA_LABEL_PREFIX}1`; - await cacheService.set(dataLabel, DATA, CALLING_METHOD, undefined, undefined, true); + await cacheService.set(dataLabel, DATA, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); - const cache = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const cache = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(cache).to.deep.eq(DATA, 'set method saves to shared cache'); - const cacheFromService = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const cacheFromService = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(cacheFromService).to.deep.eq(DATA, 'getAsync method reads correctly from shared cache'); - await cacheService.delete(dataLabel, CALLING_METHOD, undefined, true); + await cacheService.delete(dataLabel, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); - const deletedCache = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const deletedCache = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(deletedCache).to.eq(null, 'the delete method correctly deletes from shared cache'); - const deletedCacheFromService = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const deletedCacheFromService = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(deletedCacheFromService).to.eq(null, 'getAsync method cannot read deleted cache'); }); @@ -63,18 +66,18 @@ describe('@cache-service Acceptance Tests for shared cache', function () { const ttl = 1000; const dataLabel = `${DATA_LABEL_PREFIX}2`; - await cacheService.set(dataLabel, DATA, CALLING_METHOD, ttl, undefined, true); + await cacheService.set(dataLabel, DATA, CALLING_METHOD, requestDetails, ttl); await new Promise((r) => setTimeout(r, 200)); - const cache = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const cache = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(cache).to.deep.eq(DATA, 'data is stored with TTL'); await new Promise((r) => setTimeout(r, ttl)); - const expiredCache = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const expiredCache = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(expiredCache).to.eq(null, 'cache expires after TTL period'); - const deletedCacheFromService = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const deletedCacheFromService = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(deletedCacheFromService).to.eq(null, 'getAsync method cannot read expired cache'); }); @@ -85,10 +88,10 @@ describe('@cache-service Acceptance Tests for shared cache', function () { const serviceWithDisabledRedis = new CacheService(global.logger, registry); await new Promise((r) => setTimeout(r, 1000)); expect(serviceWithDisabledRedis.isRedisEnabled()).to.eq(false, 'redis is disabled'); - await serviceWithDisabledRedis.set(dataLabel, DATA, CALLING_METHOD, undefined, undefined, true); + await serviceWithDisabledRedis.set(dataLabel, DATA, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); - const dataInLRU = await serviceWithDisabledRedis.getAsync(dataLabel, CALLING_METHOD); + const dataInLRU = await serviceWithDisabledRedis.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(dataInLRU).to.deep.eq(DATA, 'data is stored in local cache'); process.env.REDIS_ENABLED = 'true'; @@ -97,10 +100,10 @@ describe('@cache-service Acceptance Tests for shared cache', function () { it('Cache set by one instance can be accessed by another', async () => { const dataLabel = `${DATA_LABEL_PREFIX}4`; const otherServiceInstance = new CacheService(global.logger, registry); - await cacheService.set(dataLabel, DATA, CALLING_METHOD, undefined, undefined, true); + await cacheService.set(dataLabel, DATA, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); - const cachedData = await otherServiceInstance.getAsync(dataLabel, CALLING_METHOD); + const cachedData = await otherServiceInstance.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(cachedData).to.deep.eq(DATA, 'cached data is read correctly by other service instance'); }); @@ -108,7 +111,7 @@ describe('@cache-service Acceptance Tests for shared cache', function () { const dataLabel = `${DATA_LABEL_PREFIX}_redis_error`; let currentRedisEnabledEnv; - let cacheService; + let cacheService: CacheService; before(async () => { currentRedisEnabledEnv = process.env.REDIS_ENABLED; @@ -126,10 +129,10 @@ describe('@cache-service Acceptance Tests for shared cache', function () { }); it('test getAsync operation', async () => { - await cacheService.set(dataLabel, DATA, CALLING_METHOD, undefined, undefined); + await cacheService.set(dataLabel, DATA, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); - const dataInLRU = await cacheService.getAsync(dataLabel, CALLING_METHOD); + const dataInLRU = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(dataInLRU).to.deep.eq(DATA, 'data is stored in local cache'); }); @@ -140,21 +143,21 @@ describe('@cache-service Acceptance Tests for shared cache', function () { string: '5644', }; - await cacheService.multiSet(pairs, CALLING_METHOD, undefined, undefined); + await cacheService.multiSet(pairs, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); for (const key in pairs) { - const cachedValue = await cacheService.getAsync(key, CALLING_METHOD); + const cachedValue = await cacheService.getAsync(key, CALLING_METHOD, requestDetails); expect(cachedValue).deep.equal(pairs[key]); } }); it('test delete operation', async () => { - await cacheService.set(dataLabel, DATA, CALLING_METHOD, undefined, undefined); + await cacheService.set(dataLabel, DATA, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); - await cacheService.delete(dataLabel, CALLING_METHOD); - const dataInLRU = await cacheService.getAsync(dataLabel, CALLING_METHOD); + await cacheService.delete(dataLabel, CALLING_METHOD, requestDetails); + const dataInLRU = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(dataInLRU).to.be.null; }); }); diff --git a/packages/server/tests/acceptance/equivalence.spec.ts b/packages/server/tests/acceptance/equivalence.spec.ts index 3991c0349c..5908f3946c 100644 --- a/packages/server/tests/acceptance/equivalence.spec.ts +++ b/packages/server/tests/acceptance/equivalence.spec.ts @@ -31,6 +31,7 @@ import pino from 'pino'; import { MirrorNodeClient } from '../../../relay/src/lib/clients'; import { hexToASCII } from '../../../relay/src/formatters'; import { AliasAccount } from '../types/AliasAccount'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; const logger = pino(); enum CallTypes { @@ -76,6 +77,7 @@ describe('Equivalence tests', async function () { const { servicesNode, mirrorNode, relay }: any = global; const servicesClient = servicesNode as ServicesClient; const mirrorNodeClient = mirrorNode as MirrorNodeClient; + const requestDetails = new RequestDetails({ requestId: 'rpc_batch1Test', ipAddress: '0.0.0.0' }); let precheck: Precheck; const SUCCESS = 'SUCCESS'; @@ -185,7 +187,10 @@ describe('Equivalence tests', async function () { equivalenceContractId = equivalenceContractReceipt.contractId.toString(); requestId = Utils.generateRequestId(); - const contractMirror = await mirrorNodeClient.get(`/contracts/${estimatePrecompileSolidityAddress}`, requestId); + const contractMirror = await mirrorNodeClient.get( + `/contracts/${estimatePrecompileSolidityAddress}`, + requestDetails, + ); accounts[0] = await servicesClient.createAccountWithContractIdKey( contractMirror.contract_id, @@ -355,7 +360,7 @@ describe('Equivalence tests', async function () { }; async function getResultByEntityIdAndTxTimestamp(entityId, txTimestamp) { - return await mirrorNode.get(`/contracts/${entityId}/results/${txTimestamp}`); + return await mirrorNode.get(`/contracts/${entityId}/results/${txTimestamp}`, requestDetails); } /** @@ -364,7 +369,7 @@ describe('Equivalence tests', async function () { * @returns list of ContractActions */ async function getContractActions(transactionIdOrHash: string) { - return await mirrorNodeClient.get(`/contracts/results/${transactionIdOrHash}/actions`, Utils.generateRequestId()); + return await mirrorNode.get(`/contracts/results/${transactionIdOrHash}/actions`, requestDetails); } async function createFungibleToken() { diff --git a/packages/server/tests/acceptance/erc20.spec.ts b/packages/server/tests/acceptance/erc20.spec.ts index be6a043b8c..4cd322d9de 100644 --- a/packages/server/tests/acceptance/erc20.spec.ts +++ b/packages/server/tests/acceptance/erc20.spec.ts @@ -32,6 +32,8 @@ import { EthImpl } from '@hashgraph/json-rpc-relay/src/lib/eth'; // Constants from local resources import Constants from '../../../server/tests/helpers/constants'; +import ServicesClient from '../clients/servicesClient'; +import RelayClient from '../clients/relayClient'; chai.use(solidity); @@ -42,7 +44,9 @@ const extractRevertReason = (errorReason: string) => { describe('@erc20 Acceptance Tests', async function () { this.timeout(240 * 1000); // 240 seconds - const { servicesNode, relay }: any = global; + + // @ts-ignore + const { servicesNode, relay }: { servicesNode: ServicesClient; relay: RelayClient } = global; // cached entities const accounts: AliasAccount[] = []; @@ -285,7 +289,7 @@ describe('@erc20 Acceptance Tests', async function () { await contract .connect(tokenOwnerWallet) .approve(spender, initialSupply, await Utils.gasOptions(requestId)); - await contract.transfer(to, 1, await Utils.gasOptions(1_500_000)); + await contract.transfer(to, 1, await Utils.gasOptions(requestId)); // 5 seconds sleep to propagate the changes to mirror node await new Promise((r) => setTimeout(r, 5000)); }); @@ -347,7 +351,7 @@ describe('@erc20 Acceptance Tests', async function () { }); beforeEach('reducing balance', async function () { - await contract.transfer(to, 2, await Utils.gasOptions(1_500_000)); + await contract.transfer(to, 2, await Utils.gasOptions(requestId)); }); it('reverts', async function () { @@ -374,9 +378,7 @@ describe('@erc20 Acceptance Tests', async function () { amount = initialSupply; to = ethers.ZeroAddress; tokenOwnerWallet = accounts[2].wallet; - await contract - .connect(tokenOwnerWallet) - .approve(spender, amount, await Utils.gasOptions(requestId, 1_500_000)); + await contract.connect(tokenOwnerWallet).approve(spender, amount, await Utils.gasOptions(requestId)); }); it('reverts', async function () { diff --git a/packages/server/tests/acceptance/estimateGasContract.spec.ts b/packages/server/tests/acceptance/estimateGasContract.spec.ts index 848df84caa..20d38b7a5b 100644 --- a/packages/server/tests/acceptance/estimateGasContract.spec.ts +++ b/packages/server/tests/acceptance/estimateGasContract.spec.ts @@ -168,7 +168,7 @@ describe('EstimateGasContract tests', function () { const estimateGasResponse = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_ESTIMATE_GAS, [ { data: '0x0ec1551d', - to: Utils.add0xPrefix(contract.target), + to: Utils.add0xPrefix(contract.target as string), from: randomAddress, }, ]); diff --git a/packages/server/tests/acceptance/estimateGasPrecompile.spec.ts b/packages/server/tests/acceptance/estimateGasPrecompile.spec.ts index 508bfc3007..828743b179 100644 --- a/packages/server/tests/acceptance/estimateGasPrecompile.spec.ts +++ b/packages/server/tests/acceptance/estimateGasPrecompile.spec.ts @@ -37,6 +37,9 @@ import RelayCalls from '../../../../packages/server/tests/helpers/constants'; // Other imports import { numberTo0x } from '../../../../packages/relay/src/formatters'; +import RelayClient from '../clients/relayClient'; +import ServicesClient from '../clients/servicesClient'; +import MirrorClient from '../clients/mirrorClient'; describe('EstimatePrecompileContract tests', function () { const signers: AliasAccount[] = []; @@ -72,7 +75,13 @@ describe('EstimatePrecompileContract tests', function () { const usdFee1 = 1; const usdFee2 = 2; const accounts: AliasAccount[] = []; - const { servicesNode, mirrorNode, relay }: any = global; + + // @ts-ignore + const { + servicesNode, + mirrorNode, + relay, + }: { servicesNode: ServicesClient; mirrorNode: MirrorClient; relay: RelayClient } = global; async function createFungibleToken() { estimateContract = new ethers.Contract( @@ -2369,7 +2378,7 @@ describe('EstimatePrecompileContract tests', function () { let estimateGasResponse; try { estimateGasResponse = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_ESTIMATE_GAS, [tx]); - } catch (e) { + } catch (e: any) { expect(e.code).to.eq(errorMessage); failed = true; } diff --git a/packages/server/tests/acceptance/hbarLimiter.spec.ts b/packages/server/tests/acceptance/hbarLimiter.spec.ts index 5cfc234113..6360ff64a0 100644 --- a/packages/server/tests/acceptance/hbarLimiter.spec.ts +++ b/packages/server/tests/acceptance/hbarLimiter.spec.ts @@ -37,15 +37,38 @@ import EstimateGasContract from '../contracts/EstimateGasContract.json'; import largeContractJson from '../contracts/hbarLimiterContracts/largeSizeContract.json'; import mediumSizeContract from '../contracts/hbarLimiterContracts/mediumSizeContract.json'; import fs from 'fs'; +import { resolve } from 'path'; +import { config } from 'dotenv'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import MirrorClient from '../clients/mirrorClient'; +import RelayClient from '../clients/relayClient'; +import { Logger } from 'pino'; +import MetricsClient from '../clients/metricsClient'; config({ path: resolve(__dirname, '../localAcceptance.env') }); const DOT_ENV = dotenv.parse(fs.readFileSync(resolve(__dirname, '../localAcceptance.env'))); describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { // @ts-ignore - const { mirrorNode, relay, logger, initialBalance, metrics, relayIsLocal } = global; + const { + mirrorNode, + relay, + logger, + initialBalance, + metrics, + relayIsLocal, + }: { + mirrorNode: MirrorClient; + relay: RelayClient; + logger: Logger; + initialBalance: string; + metrics: MetricsClient; + relayIsLocal: boolean; + } = global; const operatorAccount = process.env.OPERATOR_ID_MAIN || DOT_ENV.OPERATOR_ID_MAIN || ''; const fileAppendChunkSize = Number(process.env.FILE_APPEND_CHUNK_SIZE) || 5120; + const requestId = 'hbarLimiterTest'; + const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); // The following tests exhaust the hbar limit, so they should only be run against a local relay if (relayIsLocal) { @@ -105,7 +128,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { return fileAppendTxFee; }; - const getExpectedCostOfLastLargeTx = async (requestId: string, txData: string) => { + const getExpectedCostOfLastLargeTx = async (txData: string, requestDetails: RequestDetails) => { const ethereumTransaction = ( await mirrorNode.get( `/transactions?transactiontype=ETHEREUMTRANSACTION&order=desc&account.id=${operatorAccount}&limit=1`, @@ -113,8 +136,8 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { ) ).transactions[0]; const ethereumTxFee = sumAccountTransfers(ethereumTransaction.transfers, operatorAccount); - const { fileCreateTxFee, fileCreateTimestamp } = await getExpectedCostOfFileCreateTx(requestId); - const fileAppendTxFee = await getExpectedCostOfFileAppendTx(requestId, fileCreateTimestamp, txData); + const { fileCreateTxFee, fileCreateTimestamp } = await getExpectedCostOfFileCreateTx(requestDetails); + const fileAppendTxFee = await getExpectedCostOfFileAppendTx(requestDetails, fileCreateTimestamp, txData); const fileDeleteTx = ( await mirrorNode.get( @@ -151,19 +174,14 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { type: 2, }; - // cached entities - let requestId: string; - let requestIdPrefix: string; - before(async function () { // Restart the relay to reset the limits await global.restartLocalRelay(); - requestId = Utils.generateRequestId(); - requestIdPrefix = Utils.formatRequestIdMessage(requestId); - - logger.info(`${requestIdPrefix} Creating accounts`); - logger.info(`${requestIdPrefix} HBAR_RATE_LIMIT_TINYBAR: ${process.env.HBAR_RATE_LIMIT_TINYBAR}`); + logger.info(`${requestDetails.formattedRequestId} Creating accounts`); + logger.info( + `${requestDetails.formattedRequestId} HBAR_RATE_LIMIT_TINYBAR: ${process.env.HBAR_RATE_LIMIT_TINYBAR}`, + ); const initialAccount: AliasAccount = global.accounts[0]; @@ -174,15 +192,13 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { initialAccount, neededAccounts, initialBalance, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); }); beforeEach(async function () { - requestId = Utils.generateRequestId(); - requestIdPrefix = Utils.formatRequestIdMessage(requestId); await new Promise((r) => setTimeout(r, 3000)); }); @@ -194,7 +210,9 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { it('should execute "eth_sendRawTransaction" without triggering HBAR rate limit exceeded', async function () { const parentContract = await deployContract(parentContractJson, accounts[0].wallet); const parentContractAddress = parentContract.target as string; - global.logger.trace(`${requestIdPrefix} Deploy parent contract on address ${parentContractAddress}`); + global.logger.trace( + `${requestDetails.formattedRequestId} Deploy parent contract on address ${parentContractAddress}`, + ); const gasPrice = await relay.gasPrice(requestId); const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); @@ -224,7 +242,10 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { const contract = await deployContract(largeContractJson, accounts[0].wallet); const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - const expectedCost = await getExpectedCostOfLastLargeTx(requestId, contract.deploymentTransaction()!.data); + const expectedCost = await getExpectedCostOfLastLargeTx( + contract.deploymentTransaction()!.data, + requestDetails, + ); verifyRemainingLimit(expectedCost, remainingHbarsBefore, remainingHbarsAfter); }); @@ -249,7 +270,10 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { const contract = await deployContract(mediumSizeContract, accounts[0].wallet); const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - const expectedCost = await getExpectedCostOfLastLargeTx(requestId, contract.deploymentTransaction()!.data); + const expectedCost = await getExpectedCostOfLastLargeTx( + contract.deploymentTransaction()!.data, + requestDetails, + ); verifyRemainingLimit(expectedCost, remainingHbarsBefore, remainingHbarsAfter); }); @@ -304,8 +328,8 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { const amountPaidByOperator = operatorBalanceBefore - operatorBalanceAfter; const totalOperatorFees = await getExpectedCostOfLastLargeTx( - requestId, largeContract.deploymentTransaction()!.data, + requestDetails, ); const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); const hbarLimitReducedAmount = remainingHbarsBefore - remainingHbarsAfter; @@ -337,7 +361,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK = 'false'; const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - let lastRemainingHbars = remainingHbarsBefore; + const lastRemainingHbars = remainingHbarsBefore; expect(remainingHbarsBefore).to.be.gt(0); try { for (let i = 0; i < 50; i++) { diff --git a/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts b/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts index 5e7cb79676..f046474983 100644 --- a/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts +++ b/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts @@ -391,7 +391,7 @@ describe('@precompile-calls Tests for eth_call with HTS', async function () { expect(customFees.fixedFees).to.exist; expect(customFees.fixedFees.length).to.eq(1); expect(customFees.fixedFees[0].amount).to.exist; - expect(customFees.fixedFees[0].amount.toString()).to.eq(Hbar.from(1).toTinybars().toString()); + expect(customFees.fixedFees[0].amount.toString()).to.eq('1'); expect(customFees.fixedFees[0].tokenId).to.eq(ZERO_HEX); expect(customFees.fixedFees[0].feeCollector).to.exist; expect(customFees.fixedFees[0].feeCollector.toLowerCase()).to.eq(accounts[0].address.toLowerCase()); @@ -473,7 +473,7 @@ describe('@precompile-calls Tests for eth_call with HTS', async function () { expect(customFees.fixedFees.length).to.eq(2); expect(customFees.fixedFees[0].amount).to.exist; - expect(customFees.fixedFees[0].amount.toString()).to.eq(Hbar.from(1).toTinybars().toString()); + expect(customFees.fixedFees[0].amount.toString()).to.eq('1'); expect(customFees.fixedFees[0].tokenId).to.eq(ZERO_HEX); expect(customFees.fixedFees[0].feeCollector).to.exist; expect(customFees.fixedFees[0].feeCollector.toLowerCase()).to.eq(accounts[0].address.toLowerCase()); diff --git a/packages/server/tests/acceptance/htsPrecompile_v1.spec.ts b/packages/server/tests/acceptance/htsPrecompile_v1.spec.ts index d5676af6d7..7a53850269 100644 --- a/packages/server/tests/acceptance/htsPrecompile_v1.spec.ts +++ b/packages/server/tests/acceptance/htsPrecompile_v1.spec.ts @@ -30,12 +30,24 @@ import { AliasAccount } from '../types/AliasAccount'; import { ethers } from 'ethers'; import BaseHTSJson from '../contracts/contracts_v1/BaseHTS.json'; import { Utils } from '../helpers/utils'; +import ServicesClient from '../clients/servicesClient'; +import RelayClient from '../clients/relayClient'; +import MirrorClient from '../clients/mirrorClient'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; chai.use(solidity); describe('@htsprecompilev1 HTS Precompile V1 Acceptance Tests', async function () { this.timeout(240 * 1000); // 240 seconds - const { servicesNode, relay, mirrorNode }: any = global; + + // @ts-ignore + const { + servicesNode, + relay, + mirrorNode, + }: { servicesNode: ServicesClient; relay: RelayClient; mirrorNode: MirrorClient } = global; + + const requestDetails = new RequestDetails({ requestId: 'htsPrecompile_v1Test', ipAddress: '0.0.0.0' }); const TX_SUCCESS_CODE = BigInt(22); @@ -48,36 +60,38 @@ describe('@htsprecompilev1 HTS Precompile V1 Acceptance Tests', async function ( let baseHTSContractReceiverWalletFirst; let baseHTSContractReceiverWalletSecond; let HTSTokenWithCustomFeesContractAddress; - let requestId; this.beforeAll(async () => { - requestId = Utils.generateRequestId(); - const initialAccount: AliasAccount = global.accounts[0]; const initialAmount: string = '5000000000'; //50 Hbar - const contractDeployer = await Utils.createAliasAccount(mirrorNode, initialAccount, requestId, initialAmount); + const contractDeployer = await Utils.createAliasAccount( + mirrorNode, + initialAccount, + requestDetails.requestId, + initialAmount, + ); const BaseHTSContract = await Utils.deployContract(BaseHTSJson.abi, BaseHTSJson.bytecode, contractDeployer.wallet); BaseHTSContractAddress = BaseHTSContract.target; - const contractMirror = await mirrorNode.get(`/contracts/${BaseHTSContractAddress}`, requestId); + const contractMirror = await mirrorNode.get(`/contracts/${BaseHTSContractAddress}`, requestDetails.requestId); accounts[0] = await servicesNode.createAccountWithContractIdKey( contractMirror.contract_id, 70, relay.provider, - requestId, + requestDetails.requestId, ); accounts[1] = await servicesNode.createAccountWithContractIdKey( contractMirror.contract_id, 25, relay.provider, - requestId, + requestDetails.requestId, ); accounts[2] = await servicesNode.createAccountWithContractIdKey( contractMirror.contract_id, 25, relay.provider, - requestId, + requestDetails.requestId, ); // allow mirror node a 2 full record stream write windows (2 sec) and a buffer to persist setup details @@ -90,10 +104,6 @@ describe('@htsprecompilev1 HTS Precompile V1 Acceptance Tests', async function ( baseHTSContractReceiverWalletSecond = baseHTSContract.connect(accounts[2].wallet); }); - this.beforeEach(async () => { - requestId = Utils.generateRequestId(); - }); - async function createHTSToken() { const baseHTSContract = new ethers.Contract(BaseHTSContractAddress, BaseHTSJson.abi, accounts[0].wallet); const tx = await baseHTSContract.createFungibleTokenPublic(accounts[0].wallet.address, { diff --git a/packages/server/tests/acceptance/index.spec.ts b/packages/server/tests/acceptance/index.spec.ts index 51984031a6..f1f90f153a 100644 --- a/packages/server/tests/acceptance/index.spec.ts +++ b/packages/server/tests/acceptance/index.spec.ts @@ -48,6 +48,7 @@ import constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; import { Utils } from '../helpers/utils'; import { AliasAccount } from '../types/AliasAccount'; import { setServerTimeout } from '../../src/koaJsonRpc/lib/utils'; +import { Server, IncomingMessage, ServerResponse } from 'http'; chai.use(chaiAsPromised); dotenv.config({ path: path.resolve(__dirname, '../../../../.env') }); @@ -92,8 +93,6 @@ describe('RPC Server Acceptance Tests', function () { global.mirrorNode = new MirrorClient(MIRROR_NODE_URL, logger.child({ name: `mirror-node-test-client` })); global.metrics = new MetricsClient(RELAY_URL, logger.child({ name: `metrics-test-client` })); global.relay = new RelayClient(RELAY_URL, logger.child({ name: `relay-test-client` })); - global.relayServer = relayServer; - global.socketServer = socketServer; global.logger = logger; global.initialBalance = INITIAL_BALANCE; @@ -135,7 +134,7 @@ describe('RPC Server Acceptance Tests', function () { ); global.accounts = new Array(initialAccount); - await global.mirrorNode.get(`/accounts/${initialAccount.address}`); + await global.mirrorNode.get(`/accounts/${initialAccount.address}`, Utils.generateRequestId()); }); after(async function () { @@ -196,6 +195,8 @@ describe('RPC Server Acceptance Tests', function () { function stopRelay() { //stop relay logger.info('Stop relay'); + + const relayServer: Server = global.relayServer; if (relayServer !== undefined) { relayServer.close(); } @@ -209,7 +210,8 @@ describe('RPC Server Acceptance Tests', function () { // start local relay, relay instance in local should not be running logger.info(`Start relay on port ${constants.RELAY_PORT}`); - relayServer = app.listen({ port: constants.RELAY_PORT }); + const relayServer = app.listen({ port: constants.RELAY_PORT }); + global.relayServer = relayServer; setServerTimeout(relayServer); if (process.env.TEST_WS_SERVER === 'true') { diff --git a/packages/server/tests/acceptance/rateLimiter.spec.ts b/packages/server/tests/acceptance/rateLimiter.spec.ts index 62ff5521d0..a6ed98df71 100644 --- a/packages/server/tests/acceptance/rateLimiter.spec.ts +++ b/packages/server/tests/acceptance/rateLimiter.spec.ts @@ -22,12 +22,13 @@ import Assertions from '../helpers/assertions'; import testConstants from '../../tests/helpers/constants'; import relayConstants from '../../../../packages/relay/src/lib/constants'; +import RelayClient from '../clients/relayClient'; describe('@ratelimiter Rate Limiters Acceptance Tests', function () { this.timeout(480 * 1000); // 480 seconds // @ts-ignore - const { relay } = global; + const { relay }: { relay: RelayClient } = global; // cached entities let requestId: string; diff --git a/packages/server/tests/acceptance/rpc_batch1.spec.ts b/packages/server/tests/acceptance/rpc_batch1.spec.ts index 6680f884fd..833128fc0b 100644 --- a/packages/server/tests/acceptance/rpc_batch1.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch1.spec.ts @@ -23,7 +23,7 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; import { AliasAccount } from '../types/AliasAccount'; import { Utils } from '../helpers/utils'; -import { FileInfo, FileInfoQuery, TransferTransaction } from '@hashgraph/sdk'; +import { FileInfo, FileInfoQuery, Hbar, TransferTransaction } from '@hashgraph/sdk'; // Assertions from local resources import Assertions from '../helpers/assertions'; @@ -41,6 +41,11 @@ import RelayCalls from '../../tests/helpers/constants'; // Other imports import { numberTo0x, prepend0x } from '../../../../packages/relay/src/formatters'; import constants from '../../../relay/src/lib/constants'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import RelayClient from '../clients/relayClient'; +import ServicesClient from '../clients/servicesClient'; +import MirrorClient from '../clients/mirrorClient'; + const Address = RelayCalls; describe('@api-batch-1 RPC Server Acceptance Tests', function () { @@ -49,30 +54,40 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const accounts: AliasAccount[] = []; // @ts-ignore - const { servicesNode, mirrorNode, relay, initialBalance } = global; + const { + servicesNode, + mirrorNode, + relay, + initialBalance, + }: { servicesNode: ServicesClient; mirrorNode: MirrorClient; relay: RelayClient; initialBalance: string } = global; // cached entities let parentContractAddress: string; let mirrorContractDetails; - let requestId: string; let account2Address: string; let expectedGasPrice: string; + const requestId = 'rpc_batch1Test'; + const requestIdPrefix = Utils.formatRequestIdMessage(requestId); + const requestDetails = new RequestDetails({ requestId: 'rpc_batch1Test', ipAddress: '0.0.0.0' }); const CHAIN_ID = process.env.CHAIN_ID || '0x12a'; const INCORRECT_CHAIN_ID = 999; const GAS_PRICE_TOO_LOW = '0x1'; const GAS_PRICE_REF = '0x123456'; const ONE_TINYBAR = Utils.add0xPrefix(Utils.toHex(Constants.TINYBAR_TO_WEIBAR_COEF)); + const TEN_HBAR = Utils.add0xPrefix( + (BigInt(new Hbar(10).toTinybars().toString()) * BigInt(Constants.TINYBAR_TO_WEIBAR_COEF)).toString(16), + ); const sendRawTransaction = relay.sendRawTransaction; /** * resolves long zero addresses to EVM addresses by querying mirror node - * @param tx: any - supposedly a proper transaction that has `from` and `to` fields + * @param tx - supposedly a proper transaction that has `from` and `to` fields * @returns Promise<{from: any|null, to: any|null}> */ const resolveAccountEvmAddresses = async (tx: any) => { - const fromAccountInfo = await mirrorNode.get(`/accounts/${tx.from}`); - const toAccountInfo = await mirrorNode.get(`/accounts/${tx.to}`); + const fromAccountInfo = await mirrorNode.get(`/accounts/${tx.from}`, requestId); + const toAccountInfo = await mirrorNode.get(`/accounts/${tx.to}`, requestId); return { from: fromAccountInfo?.evm_address ?? tx.from, to: toAccountInfo?.evm_address ?? tx.to, @@ -83,9 +98,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { this.timeout(240 * 1000); // 240 seconds this.beforeAll(async () => { - requestId = Utils.generateRequestId(); - const requestIdPrefix = Utils.formatRequestIdMessage(requestId); - expectedGasPrice = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GAS_PRICE, [], requestId); + expectedGasPrice = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GAS_PRICE, [], requestIdPrefix); const initialAccount: AliasAccount = global.accounts[0]; const neededAccounts: number = 3; @@ -95,7 +108,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { initialAccount, neededAccounts, initialBalance, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); @@ -106,7 +119,9 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { accounts[0].wallet, ); parentContractAddress = parentContract.target as string; - global.logger.trace(`${requestIdPrefix} Deploy parent contract on address ${parentContractAddress}`); + global.logger.trace( + `${requestDetails.formattedRequestId} Deploy parent contract on address ${parentContractAddress}`, + ); await accounts[0].wallet.sendTransaction({ to: parentContractAddress, @@ -117,7 +132,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const createChildTx: ethers.ContractTransactionResponse = await parentContract.createChild(1); global.logger.trace( - `${requestIdPrefix} Contract call createChild on parentContract results in tx hash: ${createChildTx.hash}`, + `${requestDetails.formattedRequestId} Contract call createChild on parentContract results in tx hash: ${createChildTx.hash}`, ); // get contract result details mirrorContractDetails = await mirrorNode.get(`/contracts/results/${createChildTx.hash}`, requestId); @@ -126,10 +141,6 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { account2Address = accounts[2].address; }); - this.beforeEach(async () => { - requestId = Utils.generateRequestId(); - }); - describe('eth_getLogs', () => { let log0Block, log4Block, contractAddress: string, contractAddress2: string, latestBlock, previousBlock; @@ -147,7 +158,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { contractAddress = logsContract.target.toString(); contractAddress2 = logsContract2.target.toString(); - previousBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestId)); + previousBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestIdPrefix)); // @ts-ignore await (await logsContract.connect(accounts[1].wallet).log0(1)).wait(); @@ -162,7 +173,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { // @ts-ignore await (await logsContract2.connect(accounts[1].wallet).log4(1, 1, 1, 1)).wait(); - latestBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestId)); + latestBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestIdPrefix)); }); it('@release should deploy a contract', async () => { @@ -175,7 +186,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [contractAddress, contractAddress2], }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(0); @@ -195,7 +206,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { log0Block = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [logs[0].transactionHash], - requestId, + requestIdPrefix, ); const transactionCountLog0Block = await relay.provider.getTransactionCount( log0Block.from, @@ -205,7 +216,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { log4Block = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [logs[logs.length - 1].transactionHash], - requestId, + requestIdPrefix, ); const transactionCountLog4Block = await relay.provider.getTransactionCount( log4Block.from, @@ -234,7 +245,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [contractAddress, contractAddress2], }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(0); @@ -253,7 +264,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { }, ], predefined.MISSING_FROM_BLOCK_PARAM, - requestId, + requestIdPrefix, ); }); @@ -267,7 +278,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [contractAddress, contractAddress2], }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(0); @@ -289,7 +300,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: contractAddress, }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(0); @@ -311,7 +322,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: contractAddress, }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(0); @@ -332,7 +343,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [contractAddress, contractAddress2, Address.NON_EXISTING_ADDRESS], }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(0); expect(logs.length).to.be.eq(6); @@ -353,7 +364,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [contractAddress, contractAddress2], }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(0); @@ -371,20 +382,24 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [contractAddress, contractAddress2], }, ], - requestId, + requestIdPrefix, ); expect(logs).to.exist; expect(logs.length).to.be.eq(0); }); it('should be able to use `topics` param', async () => { - const logs = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_LOGS, [ - { - fromBlock: log0Block.blockNumber, - toBlock: log4Block.blockNumber, - address: [contractAddress, contractAddress2], - }, - ]); + const logs = await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_LOGS, + [ + { + fromBlock: log0Block.blockNumber, + toBlock: log4Block.blockNumber, + address: [contractAddress, contractAddress2], + }, + ], + requestIdPrefix, + ); expect(logs.length).to.be.greaterThan(0); //using second log in array, because the first doesn't contain any topics const topic = logs[1].topics[0]; @@ -398,7 +413,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { topics: [topic], }, ], - requestId, + requestIdPrefix, ); expect(logsWithTopic.length).to.be.greaterThan(0); @@ -414,7 +429,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { process.env['MIRROR_NODE_LIMIT_PARAM'] = '2'; // calculate blocks behind latest, so we can fetch logs from the past. // if current block is less than 10, we will fetch logs from the beginning otherwise we will fetch logs from 10 blocks behind latest - const currentBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestId)); + const currentBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestIdPrefix)); let blocksBehindLatest = 0; if (currentBlock > 10) { blocksBehindLatest = currentBlock - 10; @@ -428,7 +443,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [contractAddress, contractAddress2], }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.be.greaterThan(2); }); @@ -443,13 +458,13 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: ethers.ZeroAddress, }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.eq(0); }); it('should return only logs of non-zero addresses', async () => { - const currentBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestId)); + const currentBlock = Number(await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestIdPrefix)); let blocksBehindLatest = 0; if (currentBlock > 10) { blocksBehindLatest = currentBlock - 10; @@ -463,7 +478,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { address: [ethers.ZeroAddress, contractAddress2], }, ], - requestId, + requestIdPrefix, ); expect(logs.length).to.eq(1); }); @@ -499,7 +514,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_HASH, [mirrorBlock.hash.substring(0, 66), false], - requestId, + requestIdPrefix, ); Assertions.block(blockResult, mirrorBlock, mirrorTransactions, expectedGasPrice, false); }); @@ -508,7 +523,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_HASH, [mirrorBlock.hash.substring(0, 66), true], - requestId, + requestIdPrefix, ); // Remove synthetic transactions blockResult.transactions = blockResult.transactions.filter((transaction) => transaction.value !== '0x1234'); @@ -519,7 +534,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_HASH, [Address.NON_EXISTING_BLOCK_HASH, false], - requestId, + requestIdPrefix, ); expect(blockResult).to.be.null; }); @@ -528,7 +543,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_HASH, [Address.NON_EXISTING_BLOCK_HASH, true], - requestId, + requestIdPrefix, ); expect(blockResult).to.be.null; }); @@ -537,7 +552,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, [numberTo0x(mirrorBlock.number), false], - requestId, + requestIdPrefix, ); // Remove synthetic transactions blockResult.transactions = blockResult.transactions.filter((transaction) => transaction.value !== '0x1234'); @@ -548,14 +563,14 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['latest', false], - requestId, + requestIdPrefix, ); await Utils.wait(1000); const blockResult2 = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['latest', false], - requestId, + requestIdPrefix, ); expect(blockResult).to.not.deep.equal(blockResult2); }); @@ -564,14 +579,14 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['finalized', false], - requestId, + requestIdPrefix, ); await Utils.wait(1000); const blockResult2 = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['finalized', false], - requestId, + requestIdPrefix, ); expect(blockResult).to.not.deep.equal(blockResult2); }); @@ -580,14 +595,14 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['safe', false], - requestId, + requestIdPrefix, ); await Utils.wait(1000); const blockResult2 = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['safe', false], - requestId, + requestIdPrefix, ); expect(blockResult).to.not.deep.equal(blockResult2); }); @@ -596,14 +611,14 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['pending', false], - requestId, + requestIdPrefix, ); await Utils.wait(1000); const blockResult2 = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, ['pending', false], - requestId, + requestIdPrefix, ); expect(blockResult).to.not.deep.equal(blockResult2); }); @@ -612,7 +627,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, [numberTo0x(mirrorBlock.number), true], - requestId, + requestIdPrefix, ); // Remove synthetic transactions blockResult.transactions = blockResult.transactions.filter((transaction) => transaction.value !== '0x1234'); @@ -623,7 +638,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, [Address.NON_EXISTING_BLOCK_NUMBER, true], - requestId, + requestIdPrefix, ); expect(blockResult).to.be.null; }); @@ -632,7 +647,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const blockResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, [Address.NON_EXISTING_BLOCK_NUMBER, false], - requestId, + requestIdPrefix, ); expect(blockResult).to.be.null; }); @@ -641,7 +656,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER, [numberTo0x(mirrorBlock.number)], - requestId, + requestIdPrefix, ); expect(res).to.be.equal(ethers.toQuantity(mirrorBlock.count)); }); @@ -650,7 +665,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER, [Address.NON_EXISTING_BLOCK_NUMBER], - requestId, + requestIdPrefix, ); expect(res).to.be.null; }); @@ -659,7 +674,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_TRANSACTION_COUNT_BY_HASH, [mirrorBlock.hash.substring(0, 66)], - requestId, + requestIdPrefix, ); expect(res).to.be.equal(ethers.toQuantity(mirrorBlock.count)); }); @@ -668,7 +683,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_TRANSACTION_COUNT_BY_HASH, [Address.NON_EXISTING_BLOCK_HASH], - requestId, + requestIdPrefix, ); expect(res).to.be.null; }); @@ -680,7 +695,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { expect(mirrorBlocks.blocks.length).to.gt(0); const mirrorBlockNumber = mirrorBlocks.blocks[0].number; - const res = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestId); + const res = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_BLOCK_NUMBER, [], requestIdPrefix); const blockNumber = Number(res); expect(blockNumber).to.exist; @@ -728,7 +743,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const response = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_BLOCK_HASH_AND_INDEX, [mirrorContractDetails.block_hash.substring(0, 66), numberTo0x(mirrorContractDetails.transaction_index)], - requestId, + requestIdPrefix, ); Assertions.transaction(response, mirrorContractDetails); }); @@ -737,7 +752,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const response = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_BLOCK_HASH_AND_INDEX, [Address.NON_EXISTING_BLOCK_HASH, numberTo0x(mirrorContractDetails.transaction_index)], - requestId, + requestIdPrefix, ); expect(response).to.be.null; }); @@ -746,7 +761,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const response = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_BLOCK_HASH_AND_INDEX, [mirrorContractDetails.block_hash.substring(0, 66), Address.NON_EXISTING_INDEX], - requestId, + requestIdPrefix, ); expect(response).to.be.null; }); @@ -755,7 +770,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const response = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_BLOCK_NUMBER_AND_INDEX, [numberTo0x(mirrorContractDetails.block_number), numberTo0x(mirrorContractDetails.transaction_index)], - requestId, + requestIdPrefix, ); Assertions.transaction(response, mirrorContractDetails); }); @@ -764,7 +779,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const response = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_BLOCK_NUMBER_AND_INDEX, [numberTo0x(mirrorContractDetails.block_number), Address.NON_EXISTING_INDEX], - requestId, + requestIdPrefix, ); expect(response).to.be.null; }); @@ -773,7 +788,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const response = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_BLOCK_NUMBER_AND_INDEX, [Address.NON_EXISTING_BLOCK_NUMBER, numberTo0x(mirrorContractDetails.transaction_index)], - requestId, + requestIdPrefix, ); expect(response).to.be.null; }); @@ -795,14 +810,18 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { mirrorResult.from = accounts[2].wallet.address; mirrorResult.to = parentContractAddress; - const res = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [legacyTxHash], requestId); + const res = await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, + [legacyTxHash], + requestIdPrefix, + ); const currentPrice = await relay.gasPrice(requestId); Assertions.transactionReceipt(res, mirrorResult, currentPrice); }); it('@release-light, @release should execute "eth_getTransactionReceipt" for hash of London transaction', async function () { - const gasPrice = await relay.gasPrice(requestId); + const gasPrice = await relay.gasPrice(requestDetails); const transaction = { ...defaultLondonTransactionData, to: parentContractAddress, @@ -822,7 +841,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [transactionHash], - requestId, + requestIdPrefix, ); const currentPrice = await relay.gasPrice(requestId); @@ -848,7 +867,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [transactionHash], - requestId, + requestIdPrefix, ); const currentPrice = await relay.gasPrice(requestId); @@ -870,7 +889,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [ signedTx + '11', - requestId, + requestDetails, ]); }); @@ -895,11 +914,15 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { } // load the block in cache - await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, [formattedBlockNumber, true], requestId); + await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, + [formattedBlockNumber, true], + requestIdPrefix, + ); const receiptFromRelay = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [transactionHash], - requestId, + requestIdPrefix, ); // handle deviation in gas price @@ -913,11 +936,15 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const formattedBlockNumber = prepend0x(mirrorContractDetails.block_number.toString(16)); // load the block in cache - await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, [formattedBlockNumber, true], requestId); + await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_BLOCK_BY_NUMBER, + [formattedBlockNumber, true], + requestIdPrefix, + ); const receiptFromRelay = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [transactionHash], - requestId, + requestIdPrefix, ); // handle deviation in gas price @@ -929,7 +956,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [Address.NON_EXISTING_TX_HASH], - requestId, + requestIdPrefix, ); expect(res).to.be.null; }); @@ -944,14 +971,14 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.UNSUPPORTED_CHAIN_ID(ethers.toQuantity(INCORRECT_CHAIN_ID), CHAIN_ID); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('should execute "eth_sendRawTransaction" for deterministic deployment transaction', async function () { // send gas money to the proxy deployer const sendHbarTx = { ...defaultLegacyTransactionData, - value: (10 * ONE_TINYBAR * 10 ** 8).toString(), // 10hbar - the gasPrice to deploy the deterministic proxy contract + value: TEN_HBAR, // 10hbar - the gasPrice to deploy the deterministic proxy contract to: constants.DETERMINISTIC_DEPLOYMENT_SIGNER, nonce: await relay.getAccountNonce(accounts[0].address, requestId), gasPrice: await relay.gasPrice(requestId), @@ -983,8 +1010,8 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { ); const receipt = await mirrorNode.get(`/contracts/results/${deterministicDeployTransactionHash}`, requestId); - const fromAccountInfo = await global.mirrorNode.get(`/accounts/${receipt.from}`); - const toAccountInfo = await global.mirrorNode.get(`/accounts/${receipt.to}`); + const fromAccountInfo = await global.mirrorNode.get(`/accounts/${receipt.from}`, requestId); + const toAccountInfo = await global.mirrorNode.get(`/accounts/${receipt.to}`, requestId); expect(receipt).to.exist; expect(fromAccountInfo.evm_address).to.eq(constants.DETERMINISTIC_DEPLOYMENT_SIGNER); @@ -994,7 +1021,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { try { await relay.sendRawTransaction(constants.DETERMINISTIC_DEPLOYER_TRANSACTION, requestId); expect(true).to.be.false; - } catch (error) { + } catch (error: any) { const expectedNonceTooLowError = predefined.NONCE_TOO_LOW(0, signerNonce); const errObj = JSON.parse(error.info.responseBody).error; expect(errObj.code).to.eq(expectedNonceTooLowError.code); @@ -1004,7 +1031,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { }); it('@release-light, @release should execute "eth_sendRawTransaction" for legacy EIP 155 transactions', async function () { - const receiverInitialBalance = await relay.getBalance(parentContractAddress, 'latest', requestId); + const receiverInitialBalance = await relay.getBalance(parentContractAddress, 'latest', requestDetails); const transaction = { ...default155TransactionData, to: parentContractAddress, @@ -1035,7 +1062,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.INSUFFICIENT_ACCOUNT_BALANCE; - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('should execute "eth_sendRawTransaction" for legacy transactions (with no chainId i.e. chainId=0x0)', async function () { @@ -1071,7 +1098,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const transactionResult = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [transactionHash], - requestId, + requestIdPrefix, ); const result = Object.prototype.hasOwnProperty.call(transactionResult, 'chainId'); @@ -1089,7 +1116,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_PRICE_TOO_LOW(GAS_PRICE_TOO_LOW, GAS_PRICE_REF); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestDetails]); }); it('should not fail "eth_sendRawTransactxion" for Legacy 2930 transactions', async function () { @@ -1116,7 +1143,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_PRICE_TOO_LOW(GAS_PRICE_TOO_LOW, GAS_PRICE_REF); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestDetails]); }); it('should fail "eth_sendRawTransaction" for Legacy 2930 transactions (with insufficient balance)', async function () { @@ -1131,7 +1158,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.INSUFFICIENT_ACCOUNT_BALANCE; - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('should fail "eth_sendRawTransaction" for London transactions (with gas price too low)', async function () { @@ -1145,7 +1172,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_PRICE_TOO_LOW(GAS_PRICE_TOO_LOW, GAS_PRICE_REF); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestDetails]); }); it('should fail "eth_sendRawTransaction" for London transactions (with insufficient balance)', async function () { @@ -1163,7 +1190,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.INSUFFICIENT_ACCOUNT_BALANCE; - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('should execute "eth_sendRawTransaction" for London transactions', async function () { @@ -1258,7 +1285,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { await Utils.wait(1000); const txInfo = await mirrorNode.get(`/contracts/results/${transactionHash}`, requestId); - const contractResult = await mirrorNode.get(`/contracts/${txInfo.contract_id}`); + const contractResult = await mirrorNode.get(`/contracts/${txInfo.contract_id}`, requestId); const fileInfo = await new FileInfoQuery().setFileId(contractResult.file_id).execute(servicesNode.client); expect(fileInfo).to.exist; expect(fileInfo instanceof FileInfo).to.be.true; @@ -1281,7 +1308,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[1].wallet.signTransaction(transaction); const error = predefined.TRANSACTION_SIZE_TOO_BIG('132320', String(constants.SEND_RAW_TRANSACTION_SIZE_LIMIT)); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('should execute "eth_sendRawTransaction" of type 1 and deploy a real contract', async function () { @@ -1372,7 +1399,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.INTERNAL_ERROR(); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestDetails]); }); describe('Prechecks', async function () { @@ -1386,7 +1413,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.UNSUPPORTED_CHAIN_ID('0x3e7', CHAIN_ID); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('should fail "eth_sendRawTransaction" for EIP155 transaction with not enough gas', async function () { @@ -1402,7 +1429,10 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_LIMIT_TOO_LOW(gasLimit, Constants.MAX_GAS_PER_SEC); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [ + signedTx, + requestDetails, + ]); }); it('should fail "eth_sendRawTransaction" for EIP155 transaction with a too high gasLimit', async function () { @@ -1418,7 +1448,10 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_LIMIT_TOO_HIGH(gasLimit, Constants.MAX_GAS_PER_SEC); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [ + signedTx, + requestDetails, + ]); }); it('should fail "eth_sendRawTransaction" for London transaction with not enough gas', async function () { @@ -1432,7 +1465,10 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_LIMIT_TOO_LOW(gasLimit, Constants.MAX_GAS_PER_SEC); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [ + signedTx, + requestDetails, + ]); }); it('should fail "eth_sendRawTransaction" for London transaction with a too high gasLimit', async function () { @@ -1446,7 +1482,10 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_LIMIT_TOO_HIGH(gasLimit, Constants.MAX_GAS_PER_SEC); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [ + signedTx, + requestDetails, + ]); }); it('should fail "eth_sendRawTransaction" for legacy EIP 155 transactions (with gas price too low)', async function () { @@ -1459,7 +1498,10 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.GAS_PRICE_TOO_LOW(GAS_PRICE_TOO_LOW, GAS_PRICE_REF); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [ + signedTx, + requestDetails, + ]); }); it('@release fail "eth_getTransactionReceipt" on precheck with wrong nonce error when sending a tx with the same nonce twice', async function () { @@ -1477,12 +1519,16 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { mirrorResult.from = accounts[2].wallet.address; mirrorResult.to = parentContractAddress; - const res = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [txHash1], requestId); + const res = await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, + [txHash1], + requestIdPrefix, + ); const currentPrice = await relay.gasPrice(requestId); Assertions.transactionReceipt(res, mirrorResult, currentPrice); const error = predefined.NONCE_TOO_LOW(nonce, nonce + 1); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('@release fail "eth_getTransactionReceipt" on precheck with wrong nonce error when sending a tx with a higher nonce', async function () { @@ -1498,7 +1544,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[2].wallet.signTransaction(transaction); const error = predefined.NONCE_TOO_HIGH(nonce + 100, nonce); - await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); + await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); }); it('@release fail "eth_getTransactionReceipt" on submitting with wrong nonce error when sending a tx with the same nonce twice', async function () { @@ -1552,7 +1598,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [transactionHash], - requestId, + requestIdPrefix, ); const addressResult = await mirrorNode.get(`/accounts/${res.from}`, requestId); mirrorTransaction.from = addressResult.evm_address; @@ -1564,7 +1610,7 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [Address.NON_EXISTING_TX_HASH], - requestId, + requestIdPrefix, ); expect(res).to.be.null; }); diff --git a/packages/server/tests/acceptance/rpc_batch2.spec.ts b/packages/server/tests/acceptance/rpc_batch2.spec.ts index cdf59e07f3..8873211aeb 100644 --- a/packages/server/tests/acceptance/rpc_batch2.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch2.spec.ts @@ -43,6 +43,10 @@ import RelayCalls from '../../tests/helpers/constants'; import Helper from '../../tests/helpers/constants'; import Address from '../../tests/helpers/constants'; import constants from '../../tests/helpers/constants'; +import RelayClient from '../clients/relayClient'; +import ServicesClient from '../clients/servicesClient'; +import MirrorClient from '../clients/mirrorClient'; +import { Logger } from 'pino'; describe('@api-batch-2 RPC Server Acceptance Tests', function () { this.timeout(240 * 1000); // 240 seconds @@ -50,7 +54,19 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { const accounts: AliasAccount[] = []; // @ts-ignore - const { servicesNode, mirrorNode, relay, logger, initialBalance } = global; + const { + servicesNode, + mirrorNode, + relay, + logger, + initialBalance, + }: { + servicesNode: ServicesClient; + mirrorNode: MirrorClient; + relay: RelayClient; + logger: Logger; + initialBalance: string; + } = global; // cached entities let tokenId; @@ -84,14 +100,18 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { const signedTx = await accounts.wallet.signTransaction(transaction); const txHash = await relay.sendRawTransaction(signedTx, requestId); await mirrorNode.get(`/contracts/results/${txHash}`, requestId); - await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [txHash]); + await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, + [txHash], + Utils.formatRequestIdMessage(requestId), + ); await new Promise((r) => setTimeout(r, 2000)); }; this.beforeAll(async () => { requestId = Utils.generateRequestId(); const requestIdPrefix = Utils.formatRequestIdMessage(requestId); - expectedGasPrice = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GAS_PRICE, [], requestId); + expectedGasPrice = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GAS_PRICE, [], requestIdPrefix); const initialAccount: AliasAccount = global.accounts[0]; @@ -546,7 +566,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { requestId, ); const acc3Nonce = await relay.getAccountNonce(accounts[3].address); - const gasPrice = await relay.gasPrice(); + const gasPrice = await relay.gasPrice(requestId); const transaction = { value: ONE_TINYBAR, @@ -726,7 +746,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { async function createNftHTSToken(account) { const mainContract = new ethers.Contract(mainContractAddress, TokenCreateJson.abi, accounts[0].wallet); const tx = await mainContract.createNonFungibleTokenPublic(account.wallet.address, { - value: 30000000000000000000n, + value: BigInt('30000000000000000000'), ...Helper.GAS.LIMIT_5_000_000, }); const { tokenAddress } = (await tx.wait()).logs.filter( @@ -831,7 +851,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { ); expect(beginStorageVal).to.eq(BEGIN_EXPECTED_STORAGE_VAL); - const gasPrice = await relay.gasPrice(); + const gasPrice = await relay.gasPrice(requestId); const transaction = { value: 0, gasLimit: 50000, @@ -868,7 +888,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { requestId, ); - const gasPrice = await relay.gasPrice(); + const gasPrice = await relay.gasPrice(requestId); const transaction = { value: 0, gasLimit: 50000, @@ -925,7 +945,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { ); expect(beginStorageVal).to.eq(BEGIN_EXPECTED_STORAGE_VAL); - const gasPrice = await relay.gasPrice(); + const gasPrice = await relay.gasPrice(requestId); const transaction = { value: 0, gasLimit: 50000, @@ -956,7 +976,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { it('should execute "eth_getStorageAt" request to get current state changes with passing specific block', async function () { const EXPECTED_STORAGE_VAL = '0x0000000000000000000000000000000000000000000000000000000000000008'; - const gasPrice = await relay.gasPrice(); + const gasPrice = await relay.gasPrice(requestId); const transaction = { value: 0, gasLimit: 50000, @@ -1002,7 +1022,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { it('should execute "eth_getStorageAt" request to get current state changes with passing specific block hash', async function () { const EXPECTED_STORAGE_VAL = '0x0000000000000000000000000000000000000000000000000000000000000008'; - const gasPrice = await relay.gasPrice(); + const gasPrice = await relay.gasPrice(requestId); const transaction = { value: 0, gasLimit: 50000, @@ -1157,7 +1177,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { const getTxData = async (hash) => { const txByHash = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [hash], requestId); const receipt = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [hash], requestId); - let mirrorResult = await mirrorNode.get(`/contracts/results/${hash}`, requestId); + const mirrorResult = await mirrorNode.get(`/contracts/results/${hash}`, requestId); return { txByHash, receipt, mirrorResult }; }; @@ -1170,7 +1190,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { await tx.wait(); - let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); + const { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); mirrorResult.from = accounts[0].wallet.address; mirrorResult.to = accounts[1].wallet.address; @@ -1195,7 +1215,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { await tx.wait(); - let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); + const { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); mirrorResult.from = accounts[0].wallet.address; mirrorResult.to = relayContract.target; @@ -1219,7 +1239,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { await tx.wait(); - let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); + const { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); mirrorResult.from = accounts[0].wallet.address; mirrorResult.to = parentContractLongZeroAddress; @@ -1240,7 +1260,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { await tx.wait(); - let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); + const { txByHash, receipt, mirrorResult } = await getTxData(tx.hash); mirrorResult.from = accounts[0].wallet.address; diff --git a/packages/server/tests/acceptance/rpc_batch3.spec.ts b/packages/server/tests/acceptance/rpc_batch3.spec.ts index c5de6a340a..5bff80a411 100644 --- a/packages/server/tests/acceptance/rpc_batch3.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch3.spec.ts @@ -46,12 +46,15 @@ import DeployerContractJson from '../contracts/Deployer.json'; import HederaTokenServiceImplJson from '../contracts/HederaTokenServiceImpl.json'; import EstimateGasContract from '../contracts/EstimateGasContract.json'; import HRC719ContractJson from '../contracts/HRC719Contract.json'; -import TokenCreateJson from '../contracts/TokenCreateContract.json'; // Helper functions/constants from local resources import { EthImpl } from '@hashgraph/json-rpc-relay/src/lib/eth'; import { predefined } from '@hashgraph/json-rpc-relay'; import { TYPES } from '../../src/validator'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import RelayClient from '../clients/relayClient'; +import ServicesClient from '../clients/servicesClient'; +import MirrorClient from '../clients/mirrorClient'; chai.use(chaiExclude); @@ -59,9 +62,14 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () { this.timeout(240 * 1000); // 240 seconds const accounts: AliasAccount[] = []; + const requestDetails = new RequestDetails({ requestId: 'rpc_batch1Test', ipAddress: '0.0.0.0' }); // @ts-ignore - const { servicesNode, mirrorNode, relay } = global; + const { + servicesNode, + mirrorNode, + relay, + }: { servicesNode: ServicesClient; mirrorNode: MirrorClient; relay: RelayClient } = global; let mirrorPrimaryAccount: ethers.Wallet; let mirrorSecondaryAccount: ethers.Wallet; @@ -110,7 +118,7 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () { initialAccount, neededAccounts, initialBalance, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); @@ -757,7 +765,7 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () { adminPrivateKey: accounts[1].privateKey, }); - tokenAddress = Utils.idToEvmAddress(htsResult.receipt.tokenId.toString()); + tokenAddress = Utils.idToEvmAddress(htsResult.receipt.tokenId!.toString()); // Deploy a contract implementing HederaTokenService const HederaTokenServiceImplFactory = new ethers.ContractFactory( @@ -1245,7 +1253,7 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () { const signedTransaction = await accounts[0].wallet.signTransaction(transaction); const transactionHash = await relay.sendRawTransaction(signedTransaction, requestId); - estimateGasContractAddress = await mirrorNode.get(`/contracts/results/${transactionHash}`); + estimateGasContractAddress = await mirrorNode.get(`/contracts/results/${transactionHash}`, requestId); }); describe('Positive scenarios', async function () { diff --git a/packages/server/tests/acceptance/serverConfig.spec.ts b/packages/server/tests/acceptance/serverConfig.spec.ts index e46d2e0ae1..3edc034b42 100644 --- a/packages/server/tests/acceptance/serverConfig.spec.ts +++ b/packages/server/tests/acceptance/serverConfig.spec.ts @@ -32,7 +32,7 @@ describe('@server-config Server Configuration Options Coverage', function () { try { await Utils.sendJsonRpcRequestWithDelay(host, port, method, params, requestTimeoutMs + 1000); throw new Error('Request did not timeout as expected'); // Force the test to fail if the request does not time out - } catch (err) { + } catch (err: any) { expect(err.code).to.equal('ECONNRESET'); expect(err.message).to.equal('socket hang up'); } diff --git a/packages/server/tests/clients/mirrorClient.ts b/packages/server/tests/clients/mirrorClient.ts index 51565a03a9..f0aebccd2d 100644 --- a/packages/server/tests/clients/mirrorClient.ts +++ b/packages/server/tests/clients/mirrorClient.ts @@ -21,6 +21,7 @@ import Axios, { AxiosInstance } from 'axios'; import axiosRetry from 'axios-retry'; import { Logger } from 'pino'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import { Utils } from '../helpers/utils'; export default class MirrorClient { diff --git a/packages/server/tests/clients/relayClient.ts b/packages/server/tests/clients/relayClient.ts index 1427a6a64c..7b3bbbb6fe 100644 --- a/packages/server/tests/clients/relayClient.ts +++ b/packages/server/tests/clients/relayClient.ts @@ -18,14 +18,14 @@ * */ -import { ethers } from 'ethers'; +import { BlockTag, ethers } from 'ethers'; import { Logger } from 'pino'; import Assertions from '../helpers/assertions'; -import { predefined } from '../../../relay/src/lib/errors/JsonRpcError'; +import { predefined } from '@hashgraph/json-rpc-relay'; import { Utils } from '../helpers/utils'; export default class RelayClient { - private readonly provider: ethers.JsonRpcProvider; + readonly provider: ethers.JsonRpcProvider; private readonly logger: Logger; constructor(relayUrl: string, logger: Logger) { @@ -42,7 +42,6 @@ export default class RelayClient { */ async call(methodName: string, params: any[], requestId?: string) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); - const result = await this.provider.send(methodName, params); this.logger.trace( `${requestIdPrefix} [POST] to relay '${methodName}' with params [${JSON.stringify( @@ -61,7 +60,7 @@ export default class RelayClient { * @param payload * @returns */ - async callBatch(payload: { id: string; method: string; params: any[] }[]) { + async callBatch(payload: { id: number; method: string; params: any[] }[]) { const request = this.provider._getConnection(); request.setHeader('content-type', 'application/json'); request.body = JSON.stringify(payload.map((r) => ({ ...r, jsonrpc: '2.0' }))); @@ -75,6 +74,7 @@ export default class RelayClient { * Calls the specified methodName with the provided params and asserts that it fails * @param methodName * @param params + * @param expectedRpcError * @param requestId */ async callFailing( @@ -126,7 +126,7 @@ export default class RelayClient { * @param block * @param requestId */ - async getBalance(address, block = 'latest', requestId?: string) { + async getBalance(address: ethers.AddressLike, block: BlockTag = 'latest', requestId?: string) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); this.logger.debug(`${requestIdPrefix} [POST] to relay eth_getBalance for address ${address}]`); return this.provider.getBalance(address, block); @@ -137,7 +137,7 @@ export default class RelayClient { * @param requestId * Returns: The nonce of the account with the provided `evmAddress` */ - async getAccountNonce(evmAddress, requestId?: string): Promise { + async getAccountNonce(evmAddress: string, requestId?: string): Promise { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); this.logger.debug(`${requestIdPrefix} [POST] to relay for eth_getTransactionCount for address ${evmAddress}`); const nonce = await this.provider.send('eth_getTransactionCount', [evmAddress, 'latest']); diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index 11f7a53993..ea86c8612e 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -24,13 +24,10 @@ import { AccountId, AccountInfoQuery, Client, - ContractCreateTransaction, ContractExecuteTransaction, ContractFunctionParameters, ContractId, - FileCreateTransaction, Hbar, - Key, KeyList, PrivateKey, Query, @@ -43,7 +40,6 @@ import { FileUpdateTransaction, TransactionId, AccountAllowanceApproveTransaction, - AccountBalance, FileContentsQuery, TokenType, TokenSupplyType, @@ -53,19 +49,46 @@ import { CustomFractionalFee, CustomRoyaltyFee, EvmAddress, + CustomFee, + TokenId, + Key, } from '@hashgraph/sdk'; import { Logger } from 'pino'; -import { ethers } from 'ethers'; +import { ethers, JsonRpcProvider } from 'ethers'; import { Utils } from '../helpers/utils'; import { AliasAccount } from '../types/AliasAccount'; import { Utils as relayUtils } from '@hashgraph/json-rpc-relay/dist/utils'; +import { Long } from 'long'; const supportedEnvs = ['previewnet', 'testnet', 'mainnet']; +type CreateHTSParams = { + tokenName: string; + symbol: string; + treasuryAccountId: string; + initialSupply: number; + adminPrivateKey: PrivateKey; + kyc?: Key; + freeze?: Key; + customHbarFees?: number; + customTokenFees?: number; + customRoyaltyFees?: number; + customFractionalFees?: number; +}; + +type CreateNFTParams = { + tokenName: string; + symbol: string; + treasuryAccountId: string; + maxSupply: number; + adminPrivateKey: PrivateKey; + customRoyaltyFees?: number; +}; + export default class ServicesClient { static TINYBAR_TO_WEIBAR_COEF = 10_000_000_000; - private readonly DEFAULT_KEY = new Key(); + private readonly DEFAULT_KEY = PrivateKey.generateECDSA(); private readonly logger: Logger; private readonly network: string; @@ -89,7 +112,12 @@ export default class ServicesClient { return this.logger; } - async createInitialAliasAccount(providerUrl, chainId, requestId, initialBalance = 2000): Promise { + async createInitialAliasAccount( + providerUrl: string, + chainId: ethers.BigNumberish, + requestId?: string, + initialBalance: number = 2000, + ): Promise { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const privateKey = PrivateKey.generateECDSA(); const wallet = new ethers.Wallet( @@ -114,7 +142,7 @@ export default class ServicesClient { const accountId = receipt?.transfers[1].accountId!; accountId.evmAddress = EvmAddress.fromString(address); - const aliasAccount: AliasAccount = { + return { alias: accountId, accountId, address, @@ -123,11 +151,9 @@ export default class ServicesClient { wallet, keyList: KeyList.from([privateKey]), }; - - return aliasAccount; } - async executeQuery(query: Query, requestId?: string) { + async executeQuery(query: Query, requestId?: string) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); try { this.logger.info(`${requestIdPrefix} Execute ${query.constructor.name} query`); @@ -174,7 +200,7 @@ export default class ServicesClient { return { executedTimestamp, executedTransactionId }; } - async createToken(initialSupply = 1000, requestId?: string) { + async createToken(initialSupply: number = 1000, requestId?: string) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const symbol = Math.random().toString(36).slice(2, 6).toUpperCase(); this.logger.trace(`${requestIdPrefix} symbol = ${symbol}`); @@ -192,13 +218,13 @@ export default class ServicesClient { this.logger.trace(`${requestIdPrefix} get token id from receipt`); const tokenId = resp?.tokenId; this.logger.info(`${requestIdPrefix} token id = ${tokenId?.toString()}`); - return tokenId; + return tokenId!; } - async associateToken(tokenId, requestId?: string) { + async associateToken(tokenId: string | TokenId, requestId?: string) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); await this.executeAndGetTransactionReceipt( - await new TokenAssociateTransaction() + new TokenAssociateTransaction() .setAccountId(this._thisAccountId()) .setTokenIds([tokenId]) .setTransactionMemo('Relay test token association'), @@ -210,7 +236,7 @@ export default class ServicesClient { ); } - async transferToken(tokenId, recipient: AccountId, amount = 10, requestId?: string) { + async transferToken(tokenId: string | TokenId, recipient: AccountId, amount = 10, requestId?: string) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const receipt = await this.executeAndGetTransactionReceipt( new TransferTransaction() @@ -227,54 +253,17 @@ export default class ServicesClient { const balances = await this.executeQuery(new AccountBalanceQuery().setAccountId(recipient), requestId); this.logger.debug( - `${requestIdPrefix} Token balances for ${recipient.toString()} are ${balances.tokens.toString().toString()}`, + `${requestIdPrefix} Token balances for ${recipient.toString()} are ${balances?.tokens?.toString()}`, ); return receipt; } - async createParentContract(contractJson, requestId?: string) { - const requestIdPrefix = Utils.formatRequestIdMessage(requestId); - const contractByteCode = contractJson.deployedBytecode.replace('0x', ''); - - const fileReceipt = await this.executeAndGetTransactionReceipt( - new FileCreateTransaction() - .setKeys([this.client.operatorPublicKey || this.DEFAULT_KEY]) - .setContents(contractByteCode) - .setTransactionMemo('Relay test file create'), - requestId, - ); - - // Fetch the receipt for transaction that created the file - // The file ID is located on the transaction receipt - const fileId = fileReceipt?.fileId; - this.logger.info(`${requestIdPrefix} contract bytecode file: ${fileId?.toString()}`); - - // Create the contract - const contractReceipt = await this.executeAndGetTransactionReceipt( - new ContractCreateTransaction() - .setConstructorParameters(new ContractFunctionParameters()) - .setGas(75000) - .setInitialBalance(1) - .setBytecodeFileId(fileId || '') - .setAdminKey(this.client.operatorPublicKey || this.DEFAULT_KEY) - .setTransactionMemo('Relay test contract create'), - requestId, - ); - - // The contract ID is located on the transaction receipt - const contractId = contractReceipt?.contractId; - - this.logger.info(`${requestIdPrefix} new contract ID: ${contractId?.toString()}`); - - return contractId; - } - async executeContractCall( - contractId, + contractId: string | ContractId, functionName: string, params: ContractFunctionParameters, - gasLimit = 75000, + gasLimit: number | Long = 75000, requestId?: string, ) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); @@ -297,7 +286,7 @@ export default class ServicesClient { } async executeContractCallWithAmount( - contractId, + contractId: string | ContractId, functionName: string, params: ContractFunctionParameters, gasLimit = 500_000, @@ -316,7 +305,7 @@ export default class ServicesClient { if (amount > 0) { tx.setPayableAmount(Hbar.fromTinybars(amount)); } - let contractExecTransactionResponse; + let contractExecTransactionResponse: TransactionResponse; try { contractExecTransactionResponse = await this.executeTransaction(tx, requestId); @@ -333,20 +322,18 @@ export default class ServicesClient { } async getAliasAccountInfo( - accountId, + accountId: AccountId, privateKey: PrivateKey, - provider = null, + provider: JsonRpcProvider | null = null, requestId?: string, - keyList?: null | KeyList, + keyList?: KeyList, ): Promise { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); - //@ts-ignore - const balance = await this.executeQuery(new AccountBalanceQuery().setAccountId(accountId), requestId); + const balance = (await this.executeQuery(new AccountBalanceQuery().setAccountId(accountId), requestId))!; this.logger.info(`${requestIdPrefix} Balance of the new account: ${balance.toString()}`); - //@ts-ignore - const accountInfo = await this.executeQuery(new AccountInfoQuery().setAccountId(accountId), requestId); + const accountInfo = (await this.executeQuery(new AccountInfoQuery().setAccountId(accountId), requestId))!; this.logger.info( `${requestIdPrefix} New account Info - accountId: ${accountInfo.accountId.toString()}, evmAddress: ${ accountInfo.contractAccountId @@ -359,39 +346,40 @@ export default class ServicesClient { this.logger.child({ name: `services-client` }), ); - let wallet; + let wallet: ethers.Wallet; if (provider) { wallet = new ethers.Wallet(privateKey.toStringRaw(), provider); } else { wallet = new ethers.Wallet(privateKey.toStringRaw()); } - const account: AliasAccount = { + return { alias: accountId, accountId: accountInfo.accountId, - address: Utils.add0xPrefix(accountInfo.contractAccountId), + address: Utils.add0xPrefix(accountInfo.contractAccountId!), client: servicesClient, privateKey, wallet, keyList, }; - - return account; } // Creates an account that has 2 keys - ECDSA and a contractId. This is required for calling contract methods that create HTS tokens. // The contractId should be the id of the contract. // The account should be created after the contract has been deployed. async createAccountWithContractIdKey( - contractIdString: string, + contractId: string | ContractId, initialBalance = 10, - provider = null, + provider: JsonRpcProvider | null = null, requestId?: string, ) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const privateKey = PrivateKey.generateECDSA(); const publicKey = privateKey.publicKey; - const contractId = ContractId.fromString(contractIdString); + + if (typeof contractId === 'string') { + contractId = ContractId.fromString(contractId); + } const keys = [publicKey, contractId]; @@ -399,25 +387,28 @@ export default class ServicesClient { const keyList = new KeyList(keys, 1); this.logger.trace( - `${requestIdPrefix} Create new Eth compatible account w ContractId key: ${contractIdString}, privateKey: ${privateKey}, alias: ${publicKey.toEvmAddress()} and balance ~${initialBalance} hb`, + `${requestIdPrefix} Create new Eth compatible account w ContractId key: ${contractId}, privateKey: ${privateKey}, alias: ${publicKey.toEvmAddress()} and balance ~${initialBalance} hb`, ); - const accountCreateTx = await ( - await new AccountCreateTransaction() - .setInitialBalance(new Hbar(initialBalance)) - .setKey(keyList) - .setAlias(publicKey.toEvmAddress()) - .freezeWith(this.client) - ).sign(privateKey); + const accountCreateTx = await new AccountCreateTransaction() + .setInitialBalance(new Hbar(initialBalance)) + .setKey(keyList) + .setAlias(publicKey.toEvmAddress()) + .freezeWith(this.client) + .sign(privateKey); const txResult = await accountCreateTx.execute(this.client); const receipt = await txResult.getReceipt(this.client); - const accountId = receipt.accountId; + const accountId = receipt.accountId!; return this.getAliasAccountInfo(accountId, privateKey, provider, requestId, keyList); } - async createAliasAccount(initialBalance = 10, provider = null, requestId?: string): Promise { + async createAliasAccount( + initialBalance = 10, + provider: JsonRpcProvider | null = null, + requestId?: string, + ): Promise { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const privateKey = PrivateKey.generateECDSA(); const publicKey = privateKey.publicKey; @@ -442,7 +433,7 @@ export default class ServicesClient { } async deployContract( - contract, + contract: { bytecode: string | Uint8Array }, gas = 100_000, constructorParameters: Uint8Array = new Uint8Array(), initialBalance = 0, @@ -485,41 +476,23 @@ export default class ServicesClient { this.logger.info(`${requestIdPrefix} File ${fileId} updated with status: ${receipt.status.toString()}`); } - async getAccountBalance(account: string | AccountId, requestId?: string): Promise { - const accountId = typeof account === 'string' ? AccountId.fromString(account) : account; - return this.executeQuery(new AccountBalanceQuery().setAccountId(accountId), requestId); - } - - async getAccountBalanceInWeiBars(account: string | AccountId, requestId?: string) { - const balance = await this.getAccountBalance(account, requestId); - - return BigInt(balance.hbars.toTinybars().toString()) * BigInt(ServicesClient.TINYBAR_TO_WEIBAR_COEF); - } - getClient() { - let network = this.network; try { - network = JSON.parse(this.network); + const network = JSON.parse(this.network); + return Client.forNetwork(network); } catch (e) { // network config is a string and not a valid JSON + return Client.forName(this.network); } - - return Client.forNetwork(network); } async createHTS( - args = { + args: CreateHTSParams = { tokenName: 'Default Name', symbol: 'HTS', treasuryAccountId: '0.0.2', initialSupply: 5000, adminPrivateKey: this.DEFAULT_KEY, - kyc: null, - freeze: null, - customHbarFees: false, - customTokenFees: false, - customRoyaltyFees: false, - customFractionalFees: false, }, ) { const {} = args; @@ -549,11 +522,11 @@ export default class ServicesClient { transaction.setFreezeKey(args.freeze); } - const customFees = []; + const customFees: CustomFee[] = []; if (args.customHbarFees) { customFees.push( new CustomFixedFee() - .setHbarAmount(Hbar.from(args.customHbarFees)) + .setHbarAmount(Hbar.fromTinybars(args.customHbarFees)) .setFeeCollectorAccountId(AccountId.fromString(args.treasuryAccountId)), ); } @@ -579,7 +552,7 @@ export default class ServicesClient { transaction.setCustomFees(customFees); } - const tokenCreate = await (await transaction).execute(htsClient); + const tokenCreate = await transaction.execute(htsClient); const receipt = await tokenCreate.getReceipt(this.client); this.logger.info(`Created HTS token ${receipt.tokenId?.toString()}`); @@ -590,13 +563,12 @@ export default class ServicesClient { } async createNFT( - args = { + args: CreateNFTParams = { tokenName: 'Default Name', symbol: 'HTS', treasuryAccountId: '0.0.2', maxSupply: 5000, adminPrivateKey: this.DEFAULT_KEY, - customRoyaltyFees: false, }, ) { const {} = args; @@ -627,7 +599,7 @@ export default class ServicesClient { ]); } - let nftCreate = await (await transaction).execute(htsClient); + let nftCreate = await transaction.execute(htsClient); const receipt = await nftCreate.getReceipt(this.client); this.logger.info(`Created NFT token ${receipt.tokenId?.toString()}`); @@ -649,9 +621,10 @@ export default class ServicesClient { htsClient.setOperator(AccountId.fromString(args.treasuryAccountId), args.adminPrivateKey); // Mint new NFT - let mintTx = await ( - await new TokenMintTransaction().setTokenId(args.tokenId).setMetadata([Buffer.from(args.metadata)]) - ).execute(htsClient); + let mintTx = await new TokenMintTransaction() + .setTokenId(args.tokenId) + .setMetadata([Buffer.from(args.metadata)]) + .execute(htsClient); const receipt = await mintTx.getReceipt(this.client); return { @@ -672,9 +645,10 @@ export default class ServicesClient { htsClient.setOperator(AccountId.fromString(args.treasuryAccountId), args.adminPrivateKey); //Enable KYC flag on account and freeze the transaction for manual signing - const transaction = await ( - await new TokenGrantKycTransaction().setAccountId(args.accountId).setTokenId(args.tokenId) - ).execute(htsClient); + const transaction = await new TokenGrantKycTransaction() + .setAccountId(args.accountId) + .setTokenId(args.tokenId) + .execute(htsClient); //Request the receipt of the transaction const receipt = await transaction.getReceipt(htsClient); @@ -685,7 +659,13 @@ export default class ServicesClient { }; } - async associateHTSToken(accountId, tokenId, privateKey, htsClient, requestId?: string) { + async associateHTSToken( + accountId: string | AccountId, + tokenId: string | TokenId, + privateKey: PrivateKey, + htsClient: Client, + requestId?: string, + ) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const tokenAssociate = await ( await new TokenAssociateTransaction() @@ -699,7 +679,12 @@ export default class ServicesClient { this.logger.info(`${requestIdPrefix} HTS Token ${tokenId} associated to : ${accountId}`); } - async approveHTSToken(spenderId, tokenId, htsClient, requestId?: string) { + async approveHTSToken( + spenderId: string | AccountId, + tokenId: string | TokenId, + htsClient: Client, + requestId?: string, + ) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const amount = 10000; const tokenApprove = await new AccountAllowanceApproveTransaction() @@ -710,14 +695,19 @@ export default class ServicesClient { this.logger.info(`${requestIdPrefix} ${amount} of HTS Token ${tokenId} can be spent by ${spenderId}`); } - async transferHTSToken(accountId, tokenId, amount, fromId = this.client.operatorAccountId, requestId?: string) { + async transferHTSToken( + accountId: string | AccountId, + tokenId: string | TokenId, + amount: number | Long, + fromId: string | AccountId = this.client.operatorAccountId!, + requestId?: string, + ) { const requestIdPrefix = Utils.formatRequestIdMessage(requestId); try { - const tokenTransfer = await ( - await new TransferTransaction() - .addTokenTransfer(tokenId, fromId, -amount) - .addTokenTransfer(tokenId, accountId, amount) - ).execute(this.client); + const tokenTransfer = await new TransferTransaction() + .addTokenTransfer(tokenId, fromId, -amount) + .addTokenTransfer(tokenId, accountId, amount) + .execute(this.client); const rec = await tokenTransfer.getReceipt(this.client); this.logger.info(`${requestIdPrefix} ${amount} of HTS Token ${tokenId} can be spent by ${accountId}`); @@ -726,10 +716,4 @@ export default class ServicesClient { this.logger.error(e, `${requestIdPrefix} TransferTransaction failed`); } } - - async getAccountNonce(accountId) { - const query = new AccountInfoQuery().setAccountId(accountId); - const accountInfo = await query.execute(this.client); - return accountInfo.ethereumNonce; - } } diff --git a/packages/server/tests/helpers/utils.ts b/packages/server/tests/helpers/utils.ts index 2dd9c71822..91c33c42c1 100644 --- a/packages/server/tests/helpers/utils.ts +++ b/packages/server/tests/helpers/utils.ts @@ -34,6 +34,7 @@ import { Context } from 'mocha'; import { GitHubClient } from '../clients/githubClient'; import MirrorClient from '../clients/mirrorClient'; import { HeapDifferenceStatistics } from '../types/HeapDifferenceStatistics'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; export class Utils { static readonly HEAP_SIZE_DIFF_MEMORY_LEAK_THRESHOLD: number = 1e6; // 1 MB @@ -43,11 +44,11 @@ export class Utils { /** * Converts a number to its hexadecimal representation. * - * @param {number} num The number to convert to hexadecimal. + * @param {number | bigint | string} num The number to convert to hexadecimal. * @returns {string} The hexadecimal representation of the number. */ - static toHex = (num) => { - return parseInt(num).toString(16); + static toHex = (num: number | bigint | string): string => { + return Number(num).toString(16); }; /** @@ -56,7 +57,7 @@ export class Utils { * @param {string} id The Hedera account ID to convert. * @returns {string} The EVM compatible address. */ - static idToEvmAddress = (id): string => { + static idToEvmAddress = (id: string): string => { Assertions.assertId(id); const [shard, realm, num] = id.split('.'); @@ -71,10 +72,10 @@ export class Utils { /** * Converts a value from tinybars to weibars. * - * @param {number} value The value in tinybars to convert. - * @returns {ethers.BigNumber} The value converted to weibars. + * @param {number | bigint | string} value The value in tinybars to convert. + * @returns {bigint} The value converted to weibars. */ - static tinyBarsToWeibars = (value) => { + static tinyBarsToWeibars = (value: number | bigint | string): bigint => { return ethers.parseUnits(Number(value).toString(), 10); }; @@ -84,7 +85,7 @@ export class Utils { * @param {number} length The length of the random string to generate. * @returns {string} The generated random string. */ - static randomString(length) { + static randomString(length: number): string { let result = ''; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; for (let i = 0; i < length; i++) { @@ -109,21 +110,38 @@ export class Utils { return requestId ? `[Request ID: ${requestId}]` : ''; }; - static deployContractWithEthers = async (constructorArgs: any[] = [], contractJson, wallet, relay) => { + static deployContractWithEthers = async ( + constructorArgs: any[] = [], + contractJson: { abi: ethers.InterfaceAbi | ethers.Interface; bytecode: ethers.BytesLike | { object: string } }, + wallet: ethers.Wallet, + relay: RelayClient, + ) => { const factory = new ethers.ContractFactory(contractJson.abi, contractJson.bytecode, wallet); let contract = await factory.deploy(...constructorArgs); await contract.waitForDeployment(); // re-init the contract with the deployed address - const receipt = await relay.provider.getTransactionReceipt(contract.deploymentTransaction()?.hash); - contract = new ethers.Contract(receipt.to, contractJson.abi, wallet); + const receipt = await relay.provider.getTransactionReceipt(contract.deploymentTransaction()!.hash); - return contract; + let contractAddress: string | ethers.Addressable; + if (receipt?.to) { + // long-zero address + contractAddress = receipt.to; + } else { + // evm address + contractAddress = contract.target; + } + + return new ethers.Contract(contractAddress, contractJson.abi, wallet); }; // The main difference between this and deployContractWithEthers is that this does not re-init the contract with the deployed address // and that results in the contract address coming in EVM Format instead of LongZero format - static deployContractWithEthersV2 = async (constructorArgs: any[] = [], contractJson, wallet) => { + static deployContractWithEthersV2 = async ( + constructorArgs: any[] = [], + contractJson: { abi: ethers.Interface | ethers.InterfaceAbi; bytecode: ethers.BytesLike | { object: string } }, + wallet: ethers.Wallet, + ) => { const factory = new ethers.ContractFactory(contractJson.abi, contractJson.bytecode, wallet); const contract = await factory.deploy(...constructorArgs); await contract.waitForDeployment(); @@ -132,15 +150,15 @@ export class Utils { }; static createHTS = async ( - tokenName, - symbol, - adminAccount, - initialSupply, - abi, - associatedAccounts, - owner, - servicesNode, - requestId, + tokenName: string, + symbol: string, + adminAccount: AliasAccount, + initialSupply: number, + abi: ethers.InterfaceAbi | ethers.Interface, + associatedAccounts: AliasAccount[], + owner: AliasAccount, + servicesNode: ServicesClient, + requestId?: string, ) => { const htsResult = await servicesNode.createHTS({ tokenName, @@ -154,34 +172,35 @@ export class Utils { for (const account of associatedAccounts) { await servicesNode.associateHTSToken( account.accountId, - htsResult.receipt.tokenId, + htsResult.receipt.tokenId!, account.privateKey, htsResult.client, requestId, ); - await servicesNode.approveHTSToken(account.accountId, htsResult.receipt.tokenId, htsResult.client, requestId); + await servicesNode.approveHTSToken(account.accountId, htsResult.receipt.tokenId!, htsResult.client, requestId); } // Setup initial balance of token owner account await servicesNode.transferHTSToken( owner.accountId, - htsResult.receipt.tokenId, + htsResult.receipt.tokenId!, initialSupply, - htsResult.client, + htsResult.client.operatorAccountId!, requestId, ); - const evmAddress = Utils.idToEvmAddress(htsResult.receipt.tokenId.toString()); + const evmAddress = Utils.idToEvmAddress(htsResult.receipt.tokenId!.toString()); return new ethers.Contract(evmAddress, abi, owner.wallet); }; - static add0xPrefix = (num) => { + static add0xPrefix = (num: string) => { return num.startsWith('0x') ? num : '0x' + num; }; - static gasOptions = async (requestId, gasLimit = 1_500_000) => { + static gasOptions = async (requestId: string, gasLimit = 1_500_000) => { + const relay: RelayClient = global.relay; return { gasLimit: gasLimit, - gasPrice: await global.relay.gasPrice(requestId), + gasPrice: await relay.gasPrice(requestId), }; }; @@ -306,14 +325,18 @@ export class Utils { initialAccount: AliasAccount, neededAccounts: number, initialAmountInTinyBar: string, - requestId: string, + requestDetails: RequestDetails, ): Promise { - const requestIdPrefix = Utils.formatRequestIdMessage(requestId); const accounts: AliasAccount[] = []; for (let i = 0; i < neededAccounts; i++) { - const account = await Utils.createAliasAccount(mirrorNode, initialAccount, requestId, initialAmountInTinyBar); + const account = await Utils.createAliasAccount( + mirrorNode, + initialAccount, + requestDetails.requestId, + initialAmountInTinyBar, + ); global.logger.trace( - `${requestIdPrefix} Create new Eth compatible account w alias: ${account.address} and balance ~${initialAmountInTinyBar} wei`, + `${requestDetails.formattedRequestId} Create new Eth compatible account w alias: ${account.address} and balance ~${initialAmountInTinyBar} wei`, ); accounts.push(account); } diff --git a/packages/ws-server/src/controllers/eth_subscribe.ts b/packages/ws-server/src/controllers/eth_subscribe.ts index 00b6876708..82496b2456 100644 --- a/packages/ws-server/src/controllers/eth_subscribe.ts +++ b/packages/ws-server/src/controllers/eth_subscribe.ts @@ -24,69 +24,67 @@ import constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; import { MirrorNodeClient } from '@hashgraph/json-rpc-relay/dist/lib/clients'; import jsonResp from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcResponse'; import { constructValidLogSubscriptionFilter, getMultipleAddressesEnabled } from '../utils/utils'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import { ISharedParams } from './index'; +import { IJsonRpcRequest } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcRequest'; +import { IJsonRpcResponse } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcResponse'; +import { Context } from 'koa'; +import { Logger } from 'pino'; /** * Subscribes to new block headers (newHeads) events and returns the response and subscription ID. * @param {any} filters - The filters object specifying criteria for the subscription. - * @param {any} response - The response object to be sent to the client. - * @param {any} subscriptionId - The ID of the subscription. - * @param {any} ctx - The context object containing information about the WebSocket connection. + * @param {Context} ctx - The context object containing information about the WebSocket connection. * @param {string} event - The event name to subscribe to (e.g., "newHeads"). * @param {Relay} relay - The relay object used for managing WebSocket subscriptions. - * @param {any} logger - The logger object used for logging subscription information. - * @returns {{ response: any; subscriptionId: any }} Returns an object containing the response and subscription ID. + * @param {Logger} logger - The logger object used for logging subscription information. + * @returns {string | undefined} Returns the subscription ID. */ const subscribeToNewHeads = ( filters: any, - response: any, - subscriptionId: any, - ctx: any, + ctx: Context, event: string, relay: Relay, - logger: any, -): { response: any; subscriptionId: any } => { - subscriptionId = relay.subs()?.subscribe(ctx.websocket, event, filters); + logger: Logger, +): string | undefined => { + const subscriptionId = relay.subs()?.subscribe(ctx.websocket, event, filters); logger.info(`Subscribed to newHeads, subscriptionId: ${subscriptionId}`); - return { response, subscriptionId }; + return subscriptionId; }; /** * Handles the subscription request for newHeads events. * If newHeads subscription is enabled, subscribes to the event; otherwise, sends an unsupported method response. - * @param {any} response - The response object to be sent to the client. - * @param {any} subscriptionId - The ID of the subscription. * @param {any} filters - The filters object specifying criteria for the subscription. * @param {any} request - The request object received from the client. * @param {any} ctx - The context object containing information about the WebSocket connection. * @param {string} event - The event name to subscribe to (e.g., "newHeads"). * @param {Relay} relay - The relay object used for managing WebSocket subscriptions. * @param {any} logger - The logger object used for logging subscription information. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {{ response: any; subscriptionId: any }} Returns an object containing the response and subscription ID. */ const handleEthSubscribeNewHeads = ( - response: any, - subscriptionId: any, filters: any, - request: any, - ctx: any, + request: IJsonRpcRequest, + ctx: Context, event: string, relay: Relay, - logger: any, - connectionIdPrefix: string, - requestIdPrefix: string, -): { response: any; subscriptionId: any } => { + logger: Logger, + requestDetails: RequestDetails, +): IJsonRpcResponse => { const wsNewHeadsEnabled = typeof process.env.WS_NEW_HEADS_ENABLED !== 'undefined' ? process.env.WS_NEW_HEADS_ENABLED === 'true' : true; - if (wsNewHeadsEnabled) { - ({ response, subscriptionId } = subscribeToNewHeads(filters, response, subscriptionId, ctx, event, relay, logger)); - } else { + if (!wsNewHeadsEnabled) { logger.warn( - `${connectionIdPrefix} ${requestIdPrefix}: Unsupported JSON-RPC method due to the value of environment variable WS_NEW_HEADS_ENABLED`, + `${requestDetails.formattedLogPrefix}: Unsupported JSON-RPC method due to the value of environment variable WS_NEW_HEADS_ENABLED`, ); - response = jsonResp(request.id, predefined.UNSUPPORTED_METHOD, undefined); + throw predefined.UNSUPPORTED_METHOD; } - return { response, subscriptionId }; + + const subscriptionId = subscribeToNewHeads(filters, ctx, event, relay, logger); + return jsonResp(request.id, null, subscriptionId); }; /** @@ -94,115 +92,80 @@ const handleEthSubscribeNewHeads = ( * Validates the subscription parameters, checks if multiple addresses are enabled, * and subscribes to the event or sends an error response accordingly. * @param {any} filters - The filters object specifying criteria for the subscription. - * @param {string} requestIdPrefix - The prefix for the request ID. - * @param {any} response - The response object to be sent to the client. - * @param {any} request - The request object received from the client. - * @param {any} subscriptionId - The ID of the subscription. - * @param {any} ctx - The context object containing information about the WebSocket connection. - * @param {any} event - The event name to subscribe to. + * @param {IJsonRpcRequest} request - The request object received from the client. + * @param {Context} ctx - The context object containing information about the WebSocket connection. + * @param {string} event - The event name to subscribe to. * @param {Relay} relay - The relay object used for managing WebSocket subscriptions. * @param {MirrorNodeClient} mirrorNodeClient - The client for interacting with the MirrorNode API. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {{ response: any; subscriptionId: any }} Returns an object containing the response and subscription ID. */ const handleEthSubscribeLogs = async ( filters: any, - requestIdPrefix: string, - response: any, - request: any, - subscriptionId: any, - ctx: any, - event: any, + request: IJsonRpcRequest, + ctx: Context, + event: string, relay: Relay, mirrorNodeClient: MirrorNodeClient, -): Promise<{ response: any; subscriptionId: any }> => { + requestDetails: RequestDetails, +): Promise => { const validFiltersObject = constructValidLogSubscriptionFilter(filters); - await validateSubscribeEthLogsParams(validFiltersObject, requestIdPrefix, mirrorNodeClient); + await validateSubscribeEthLogsParams(validFiltersObject, mirrorNodeClient, requestDetails); if ( !getMultipleAddressesEnabled() && Array.isArray(validFiltersObject['address']) && validFiltersObject['address'].length > 1 ) { - response = jsonResp( - request.id, - predefined.INVALID_PARAMETER('filters.address', 'Only one contract address is allowed'), - undefined, - ); - } else { - subscriptionId = relay.subs()?.subscribe(ctx.websocket, event, validFiltersObject); + throw predefined.INVALID_PARAMETER('filters.address', 'Only one contract address is allowed'); } - return { response, subscriptionId }; + + const subscriptionId = relay.subs()?.subscribe(ctx.websocket, event, validFiltersObject); + return jsonResp(request.id, null, subscriptionId); }; /** * Handles subscription requests for on-chain events. * Subscribes to the specified event type and returns the response. * @param {object} args - An object containing the function parameters as properties. - * @param {any} args.ctx - The context object containing information about the WebSocket connection. + * @param {Context} args.ctx - The context object containing information about the WebSocket connection. * @param {any[]} args.params - The parameters of the method request, expecting an event and filters. - * @param {string} args.requestIdPrefix - The prefix for the request ID. - * @param {any} args.request - The request object received from the client. + * @param {IJsonRpcRequest} args.request - The request object received from the client. * @param {Relay} args.relay - The relay object for interacting with the Hedera network. * @param {MirrorNodeClient} args.mirrorNodeClient - The mirror node client for handling subscriptions. * @param {ConnectionLimiter} args.limiter - The limiter object for managing connection subscriptions. - * @param {any} args.logger - The logger object for logging messages and events. + * @param {Logger} args.logger - The logger object for logging messages and events. + * @param {RequestDetails} args.requestDetails - The request details for logging and tracking. * @returns {Promise} Returns a promise that resolves with the subscription response. */ -export const handleEthSubsribe = async ({ +export const handleEthSubscribe = async ({ ctx, params, - requestIdPrefix, request, relay, mirrorNodeClient, limiter, logger, - connectionIdPrefix, -}): Promise => { + requestDetails, +}: ISharedParams): Promise => { const event = params[0]; const filters = params[1]; - let response: any; - let subscriptionId: any; + let response: IJsonRpcResponse; switch (event) { case constants.SUBSCRIBE_EVENTS.LOGS: - ({ response, subscriptionId } = await handleEthSubscribeLogs( - filters, - requestIdPrefix, - response, - request, - subscriptionId, - ctx, - event, - relay, - mirrorNodeClient, - )); + response = await handleEthSubscribeLogs(filters, request, ctx, event, relay, mirrorNodeClient, requestDetails); break; case constants.SUBSCRIBE_EVENTS.NEW_HEADS: - ({ response, subscriptionId } = handleEthSubscribeNewHeads( - response, - subscriptionId, - filters, - request, - ctx, - event, - relay, - logger, - connectionIdPrefix, - requestIdPrefix, - )); - break; - case constants.SUBSCRIBE_EVENTS.NEW_PENDING_TRANSACTIONS: - response = jsonResp(request.id, predefined.UNSUPPORTED_METHOD, undefined); + response = handleEthSubscribeNewHeads(filters, request, ctx, event, relay, logger, requestDetails); break; default: - response = jsonResp(request.id, predefined.UNSUPPORTED_METHOD, undefined); + throw predefined.UNSUPPORTED_METHOD; } limiter.incrementSubs(ctx); - response = response ?? (subscriptionId ? jsonResp(request.id, null, subscriptionId) : undefined); return response; }; @@ -211,14 +174,14 @@ export const handleEthSubsribe = async ({ * Handles unsubscription requests for on-chain events. * Unsubscribes the WebSocket from the specified subscription ID and returns the response. * @param {object} args - An object containing the function parameters as properties. - * @param {any} args.ctx - The context object containing information about the WebSocket connection. - * @param {any} args.params - The parameters of the unsubscription request. - * @param {any} args.request - The request object received from the client. + * @param {Context} args.ctx - The context object containing information about the WebSocket connection. + * @param {any[]} args.params - The parameters of the unsubscription request. + * @param {IJsonRpcRequest} args.request - The request object received from the client. * @param {Relay} args.relay - The relay object used for managing WebSocket subscriptions. * @param {ConnectionLimiter} args.limiter - The limiter object used for rate limiting WebSocket connections. - * @returns {any} Returns the response to the unsubscription request. + * @returns {IJsonRpcResponse} Returns the response to the unsubscription request. */ -export const handleEthUnsubscribe = ({ ctx, params, request, relay, limiter }): any => { +export const handleEthUnsubscribe = ({ ctx, params, request, relay, limiter }: ISharedParams): IJsonRpcResponse => { const subId = params[0]; const unsubbedCount = relay.subs()?.unsubscribe(ctx.websocket, subId); limiter.decrementSubs(ctx, unsubbedCount); diff --git a/packages/ws-server/src/controllers/index.ts b/packages/ws-server/src/controllers/index.ts index f4b90a8a72..f40205f6e3 100644 --- a/packages/ws-server/src/controllers/index.ts +++ b/packages/ws-server/src/controllers/index.ts @@ -22,7 +22,7 @@ import { WS_CONSTANTS } from '../utils/constants'; import WsMetricRegistry from '../metrics/wsMetricRegistry'; import ConnectionLimiter from '../metrics/connectionLimiter'; import { Validator } from '@hashgraph/json-rpc-server/dist/validator'; -import { handleEthSubsribe, handleEthUnsubscribe } from './eth_subscribe'; +import { handleEthSubscribe, handleEthUnsubscribe } from './eth_subscribe'; import { JsonRpcError, predefined, Relay } from '@hashgraph/json-rpc-relay'; import { MirrorNodeClient } from '@hashgraph/json-rpc-relay/dist/lib/clients'; import jsonResp from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcResponse'; @@ -32,6 +32,23 @@ import { MethodNotFound, IPRateLimitExceeded, } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcError'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import { Logger } from 'pino'; +import { IJsonRpcRequest } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcRequest'; +import Koa from 'koa'; +import { IJsonRpcResponse } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcResponse'; + +export type ISharedParams = { + request: IJsonRpcRequest; + method: string; + params: any[]; + relay: Relay; + logger: Logger; + limiter: ConnectionLimiter; + mirrorNodeClient: MirrorNodeClient; + ctx: Koa.Context; + requestDetails: RequestDetails; +}; /** * Handles sending requests to a Relay by calling a specified method with given parameters. @@ -43,8 +60,7 @@ import { * @param {any} args.params - The parameters for the method call. * @param {Relay} args.relay - The relay object. * @param {any} args.logger - The logger object used for tracing. - * @param {string} args.requestIdPrefix - Prefix for request ID used for logging. - * @param {string} args.connectionIdPrefix - Prefix for connection ID used for logging. + * @param {RequestDetails} args.requestDetails - The request details for logging and tracking. * @returns {Promise} A promise that resolves to the result of the request. */ const handleSendingRequestsToRelay = async ({ @@ -53,15 +69,24 @@ const handleSendingRequestsToRelay = async ({ params, relay, logger, - requestIdPrefix, - connectionIdPrefix, -}): Promise => { - logger.trace(`${connectionIdPrefix} ${requestIdPrefix}: Submitting request=${JSON.stringify(request)} to relay.`); - + requestDetails, +}: ISharedParams): Promise => { + logger.trace(`${requestDetails.formattedLogPrefix}: Submitting request=${JSON.stringify(request)} to relay.`); try { const resolvedParams = resolveParams(method, params); const [service, methodName] = method.split('_'); + // Rearrange the parameters for certain methods, since not everywhere requestDetails is last aparameter + const paramRearrangementMap: { [key: string]: (params: any[], requestDetails: RequestDetails) => any[] } = { + chainId: (_, requestDetails) => [requestDetails], + estimateGas: (params, requestDetails) => [...params, null, requestDetails], + getStorageAt: (params, requestDetails) => [params[0], params[1], requestDetails, params[2]], + default: (params, requestDetails) => [...params, requestDetails], + }; + + const rearrangeParams = paramRearrangementMap[methodName] || paramRearrangementMap['default']; + const rearrangedParams = rearrangeParams(resolvedParams, requestDetails); + // Call the relay method with the resolved parameters. // Method will be validated by "verifySupportedMethod" before reaching this point. let txRes: any; @@ -69,14 +94,14 @@ const handleSendingRequestsToRelay = async ({ txRes = await relay .eth() .filterService() - [methodName](...resolvedParams, requestIdPrefix); + [methodName](...rearrangedParams); } else { - txRes = await relay[service]()[methodName](...resolvedParams, requestIdPrefix); + txRes = await relay[service]()[methodName](...rearrangedParams); } if (!txRes) { logger.trace( - `${connectionIdPrefix} ${requestIdPrefix}: Fail to retrieve result for request=${JSON.stringify( + `${requestDetails.formattedLogPrefix}: Fail to retrieve result for request=${JSON.stringify( request, )}. Result=${txRes}`, ); @@ -100,22 +125,20 @@ const handleSendingRequestsToRelay = async ({ * @param {any} logger - The logger object. * @param {any} request - The request object. * @param {ConnectionLimiter} limiter - The connection limiter object. - * @param {string} requestIdPrefix - Prefix for request ID. - * @param {string} connectionIdPrefix - Prefix for connection ID. * @param {MirrorNodeClient} mirrorNodeClient - The MirrorNodeClient object. * @param {WsMetricRegistry} wsMetricRegistry - The WsMetricRegistry object. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} A promise that resolves to the response of the request. */ export const getRequestResult = async ( - ctx: any, + ctx: Koa.Context, relay: Relay, - logger: any, - request: any, + logger: Logger, + request: IJsonRpcRequest, limiter: ConnectionLimiter, - requestIdPrefix: string, - connectionIdPrefix: string, mirrorNodeClient: MirrorNodeClient, wsMetricRegistry: WsMetricRegistry, + requestDetails: RequestDetails, ): Promise => { // Extract the method and parameters from the received request let { method, params } = request; @@ -128,13 +151,13 @@ export const getRequestResult = async ( wsMetricRegistry.getCounter('methodsCounterByIp').labels(ctx.request.ip, method).inc(); // validate request's jsonrpc object - if (!validateJsonRpcRequest(request, logger, requestIdPrefix, connectionIdPrefix)) { + if (!validateJsonRpcRequest(request, logger, requestDetails)) { return jsonResp(request.id || null, new InvalidRequest(), undefined); } // verify supported method if (!verifySupportedMethod(request.method)) { - logger.warn(`${connectionIdPrefix} ${requestIdPrefix}: Method not supported: ${request.method}`); + logger.warn(`${requestDetails.formattedLogPrefix}: Method not supported: ${request.method}`); return jsonResp(request.id || null, new MethodNotFound(request.method), undefined); } @@ -150,25 +173,33 @@ export const getRequestResult = async ( if (methodValidations) { Validator.validateParams(params, methodValidations); } - } catch (error) { + } catch (error: any) { logger.warn( error, - `${connectionIdPrefix} ${requestIdPrefix} Error in parameter validation. Method: ${method}, params: ${JSON.stringify( + `${requestDetails.formattedLogPrefix} Error in parameter validation. Method: ${method}, params: ${JSON.stringify( params, )}.`, ); - return jsonResp(request.id, error, undefined); + + let jsonRpcError: JsonRpcError; + if (error instanceof JsonRpcError) { + jsonRpcError = error; + } else { + jsonRpcError = predefined.INTERNAL_ERROR(JSON.stringify(error.message || error)); + } + + return jsonResp(request.id, jsonRpcError, undefined); } // Check if the subscription limit is exceeded for ETH_SUBSCRIBE method - let response: any; + let response: IJsonRpcResponse; if (method === WS_CONSTANTS.METHODS.ETH_SUBSCRIBE && !limiter.validateSubscriptionLimit(ctx)) { return jsonResp(request.id, predefined.MAX_SUBSCRIPTIONS, undefined); } // processing method try { - const sharedParams = { + const sharedParams: ISharedParams = { ctx, params, logger, @@ -176,14 +207,13 @@ export const getRequestResult = async ( request, method, limiter, - requestIdPrefix, mirrorNodeClient, - connectionIdPrefix, + requestDetails, }; switch (method) { case WS_CONSTANTS.METHODS.ETH_SUBSCRIBE: - response = await handleEthSubsribe({ ...sharedParams }); + response = await handleEthSubscribe({ ...sharedParams }); break; case WS_CONSTANTS.METHODS.ETH_UNSUBSCRIBE: response = handleEthUnsubscribe({ ...sharedParams }); @@ -192,14 +222,22 @@ export const getRequestResult = async ( // since unsupported methods have already been captured, the methods fall into this default block will always be valid and supported methods. response = await handleSendingRequestsToRelay({ ...sharedParams }); } - } catch (error) { + } catch (error: any) { logger.warn( error, - `${connectionIdPrefix} ${requestIdPrefix} Encountered error on connectionID: ${ + `${requestDetails.formattedLogPrefix} Encountered error on connectionID: ${ ctx.websocket.id }, method: ${method}, params: ${JSON.stringify(params)}`, ); - response = jsonResp(request.id, error, undefined); + + let jsonRpcError: JsonRpcError; + if (error instanceof JsonRpcError) { + jsonRpcError = error; + } else { + jsonRpcError = predefined.INTERNAL_ERROR(JSON.stringify(error.message || error)); + } + + response = jsonResp(request.id, jsonRpcError, undefined); } return response; diff --git a/packages/ws-server/src/utils/utils.ts b/packages/ws-server/src/utils/utils.ts index 125a129394..295ac93fff 100644 --- a/packages/ws-server/src/utils/utils.ts +++ b/packages/ws-server/src/utils/utils.ts @@ -22,6 +22,10 @@ import { WS_CONSTANTS } from './constants'; import WsMetricRegistry from '../metrics/wsMetricRegistry'; import ConnectionLimiter from '../metrics/connectionLimiter'; import { predefined, Relay } from '@hashgraph/json-rpc-relay'; +import { IJsonRpcRequest } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcRequest'; +import { IJsonRpcResponse } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcResponse'; +import { Logger } from 'pino'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; const hasOwnProperty = (obj: any, prop: any) => Object.prototype.hasOwnProperty.call(obj, prop); const getRequestIdIsOptional = () => { @@ -67,22 +71,20 @@ export const handleConnectionClose = async ( * Sends a JSON-RPC response message to the client WebSocket connection. * Resets the TTL timer for inactivity on the client connection. * @param {any} connection - The WebSocket connection object to the client. - * @param {any} request - The request object received from the client. - * @param {any} response - The response data to be sent back to the client. - * @param {any} logger - The logger object used for logging messages. - * @param {string} requestIdPrefix - The prefix added to the request ID for logging purposes. - * @param {string} connectionIdPrefix - The prefix added to the connection ID for logging purposes. + * @param {IJsonRpcRequest | IJsonRpcRequest[]} request - The request object received from the client. + * @param {IJsonRpcResponse | IJsonRpcResponse[]} response - The response data to be sent back to the client. + * @param {Logger} logger - The logger object used for logging messages. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ export const sendToClient = ( connection: any, - request: any, - response: any, - logger: any, - requestIdPrefix: string, - connectionIdPrefix: string, + request: IJsonRpcRequest | IJsonRpcRequest[], + response: IJsonRpcResponse | IJsonRpcResponse[], + logger: Logger, + requestDetails: RequestDetails, ) => { logger.trace( - `${connectionIdPrefix} ${requestIdPrefix}: Sending result=${JSON.stringify( + `${requestDetails.formattedLogPrefix}: Sending result=${JSON.stringify( response, )} to client for request=${JSON.stringify(request)}`, ); @@ -93,26 +95,24 @@ export const sendToClient = ( /** * Validates a JSON-RPC request to ensure it has the correct JSON-RPC version, method, and id. - * @param {any} request - The JSON-RPC request object. - * @param {any} logger - The logger instance used for logging. - * @param {string} requestIdPrefix - The prefix to use for the request ID. - * @param {string} connectionIdPrefix - The prefix to use for the connection ID. + * @param {IJsonRpcRequest} request - The JSON-RPC request object. + * @param {Logger} logger - The logger instance used for logging. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {boolean} A boolean indicating whether the request is valid. */ export const validateJsonRpcRequest = ( - request: any, - logger: any, - requestIdPrefix: string, - connectionIdPrefix: string, + request: IJsonRpcRequest, + logger: Logger, + requestDetails: RequestDetails, ): boolean => { if ( request.jsonrpc !== '2.0' || !hasOwnProperty(request, 'method') || - hasInvalidReqestId(request, logger, requestIdPrefix, connectionIdPrefix) || + hasInvalidRequestId(request, logger, requestDetails) || !hasOwnProperty(request, 'id') ) { logger.warn( - `${connectionIdPrefix} ${requestIdPrefix} Invalid request, request.jsonrpc: ${request.jsonrpc}, request.method: ${request.method}, request.id: ${request.id}, request.method: ${request.method}`, + `${requestDetails.formattedLogPrefix} Invalid request, request.jsonrpc: ${request.jsonrpc}, request.method: ${request.method}, request.id: ${request.id}, request.method: ${request.method}`, ); return false; } else { @@ -172,25 +172,19 @@ export const verifySupportedMethod = (method: string): boolean => { /** * Checks if the JSON-RPC request has an invalid ID. - * @param {any} request - The JSON-RPC request object. - * @param {any} logger - The logger instance used for logging. - * @param {string} requestIdPrefix - The prefix to use for the request ID. - * @param {string} connectionIdPrefix - The prefix to use for the connection ID. + * @param {IJsonRpcRequest} request - The JSON-RPC request object. + * @param {Logger} logger - The logger instance used for logging. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {boolean} A boolean indicating whether the request ID is invalid. */ -const hasInvalidReqestId = ( - request: any, - logger: any, - requestIdPrefix: string, - connectionIdPrefix: string, -): boolean => { +const hasInvalidRequestId = (request: IJsonRpcRequest, logger: Logger, requestDetails: RequestDetails): boolean => { const hasId = hasOwnProperty(request, 'id'); if (getRequestIdIsOptional() && !hasId) { // If the request is invalid, we still want to return a valid JSON-RPC response, default id to 0 request.id = '0'; logger.warn( - `${connectionIdPrefix} ${requestIdPrefix} Optional JSON-RPC 2.0 request id encountered. Will continue and default id to 0 in response`, + `${requestDetails.formattedLogPrefix} Optional JSON-RPC 2.0 request id encountered. Will continue and default id to 0 in response`, ); return false; } diff --git a/packages/ws-server/src/utils/validators.ts b/packages/ws-server/src/utils/validators.ts index f157bd2586..154bd956da 100644 --- a/packages/ws-server/src/utils/validators.ts +++ b/packages/ws-server/src/utils/validators.ts @@ -22,25 +22,26 @@ import constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; import { JsonRpcError, predefined } from '@hashgraph/json-rpc-relay'; import { MirrorNodeClient } from '@hashgraph/json-rpc-relay/dist/lib/clients'; import { EthSubscribeLogsParamsObject } from '@hashgraph/json-rpc-server/dist/validator'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; /** * Validates whether the provided address corresponds to a contract or token type. * Throws an error if the address is not a valid contract or token type or does not exist. * @param {string} address - The address to validate. - * @param {string} requestId - The unique identifier for the request. * @param {MirrorNodeClient} mirrorNodeClient - The client for interacting with the MirrorNode API. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @throws {JsonRpcError} Throws a JsonRpcError if the address is not a valid contract or token type or does not exist. */ const validateIsContractOrTokenAddress = async ( address: string, - requestId: string, mirrorNodeClient: MirrorNodeClient, + requestDetails: RequestDetails, ) => { const isContractOrToken = await mirrorNodeClient.resolveEntityType( address, - [constants.TYPE_CONTRACT, constants.TYPE_TOKEN], constants.METHODS.ETH_SUBSCRIBE, - requestId, + requestDetails, + [constants.TYPE_CONTRACT, constants.TYPE_TOKEN], ); if (!isContractOrToken) { throw new JsonRpcError( @@ -48,7 +49,7 @@ const validateIsContractOrTokenAddress = async ( 'filters.address', `${address} is not a valid contract or token type or does not exists`, ), - requestId, + requestDetails.formattedRequestId, ); } }; @@ -56,13 +57,13 @@ const validateIsContractOrTokenAddress = async ( /** * Validates the parameters for subscribing to ETH logs. * @param {any} filters - The filters object containing parameters for subscribing to ETH logs. - * @param {string} requestId - The unique identifier for the request. * @param {MirrorNodeClient} mirrorNodeClient - The client for interacting with the MirrorNode API. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. */ export const validateSubscribeEthLogsParams = async ( filters: any, - requestId: string, mirrorNodeClient: MirrorNodeClient, + requestDetails: RequestDetails, ) => { // validate address exists and is correct length and type // validate topics if exists and is array and each one is correct length and type @@ -73,10 +74,10 @@ export const validateSubscribeEthLogsParams = async ( if (ethSubscribeLogsParams.object.address) { if (Array.isArray(ethSubscribeLogsParams.object.address)) { for (const address of ethSubscribeLogsParams.object.address) { - await validateIsContractOrTokenAddress(address, requestId, mirrorNodeClient); + await validateIsContractOrTokenAddress(address, mirrorNodeClient, requestDetails); } } else { - await validateIsContractOrTokenAddress(ethSubscribeLogsParams.object.address, requestId, mirrorNodeClient); + await validateIsContractOrTokenAddress(ethSubscribeLogsParams.object.address, mirrorNodeClient, requestDetails); } } }; diff --git a/packages/ws-server/src/webSocketServer.ts b/packages/ws-server/src/webSocketServer.ts index a9ec9ca59f..bb0c9791f2 100644 --- a/packages/ws-server/src/webSocketServer.ts +++ b/packages/ws-server/src/webSocketServer.ts @@ -27,13 +27,14 @@ import websockify from 'koa-websocket'; import { collectDefaultMetrics, Registry } from 'prom-client'; import { getRequestResult } from './controllers'; import { WS_CONSTANTS } from './utils/constants'; -import { formatIdMessage } from './utils/formatters'; import WsMetricRegistry from './metrics/wsMetricRegistry'; import ConnectionLimiter from './metrics/connectionLimiter'; import KoaJsonRpc from '@hashgraph/json-rpc-server/dist/koaJsonRpc'; import jsonResp from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcResponse'; import { JsonRpcError, predefined, type Relay, RelayImpl } from '@hashgraph/json-rpc-relay'; import { getBatchRequestsMaxSize, getWsBatchRequestsEnabled, handleConnectionClose, sendToClient } from './utils/utils'; +import { IJsonRpcRequest } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcRequest'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../../../.env') }); @@ -59,7 +60,7 @@ const wsMetricRegistry = new WsMetricRegistry(register); const pingInterval = Number(process.env.WS_PING_INTERVAL || 100000); const app = websockify(new Koa()); -app.ws.use(async (ctx) => { +app.ws.use(async (ctx: Koa.Context) => { // Increment the total opened connections wsMetricRegistry.getCounter('totalOpenedConnections').inc(); @@ -68,19 +69,25 @@ app.ws.use(async (ctx) => { ctx.websocket.id = relay.subs()?.generateId(); ctx.websocket.requestId = uuid(); - ctx.websocket.limiter = limiter; ctx.websocket.wsMetricRegistry = wsMetricRegistry; - const connectionIdPrefix = formatIdMessage('Connection ID', ctx.websocket.id); - const requestIdPrefix = formatIdMessage('Request ID', ctx.websocket.requestId); + + koaJsonRpc.updateRequestDetails({ + requestId: ctx.websocket.requestId, + ipAddress: ctx.request.ip, + connectionId: ctx.websocket.id, + }); + const requestDetails = koaJsonRpc.getRequestDetails(); + logger.info( - `${connectionIdPrefix} ${requestIdPrefix} New connection established. Current active connections: ${ctx.app.server._connections}`, + // @ts-ignore + `${requestDetails.formattedLogPrefix} New connection established. Current active connections: ${ctx.app.server._connections}`, ); // Close event handle ctx.websocket.on('close', async (code, message) => { logger.info( - `${connectionIdPrefix} ${requestIdPrefix} Closing connection ${ctx.websocket.id} | code: ${code}, message: ${message}`, + `${requestDetails.formattedLogPrefix} Closing connection ${ctx.websocket.id} | code: ${code}, message: ${message}`, ); await handleConnectionClose(ctx, relay, limiter, wsMetricRegistry, startTime); }); @@ -103,13 +110,13 @@ app.ws.use(async (ctx) => { limiter.resetInactivityTTLTimer(ctx.websocket); // parse the received message from the client into a JSON object - let request; + let request: IJsonRpcRequest | IJsonRpcRequest[]; try { request = JSON.parse(msg.toString('ascii')); } catch (e) { // Log an error if the message cannot be decoded and send an invalid request error to the client logger.warn( - `${connectionIdPrefix} ${requestIdPrefix}: Could not decode message from connection, message: ${msg}, error: ${e}`, + `${requestDetails.formattedLogPrefix}: Could not decode message from connection, message: ${msg}, error: ${e}`, ); ctx.websocket.send(JSON.stringify(new JsonRpcError(predefined.INVALID_REQUEST, undefined))); return; @@ -117,7 +124,7 @@ app.ws.use(async (ctx) => { // check if request is a batch request (array) or a signle request (JSON) if (Array.isArray(request)) { - logger.trace(`${connectionIdPrefix} ${requestIdPrefix}: Receive batch request=${JSON.stringify(request)}`); + logger.trace(`${requestDetails.formattedLogPrefix}: Receive batch request=${JSON.stringify(request)}`); // Increment metrics for batch_requests wsMetricRegistry.getCounter('methodsCounter').labels(WS_CONSTANTS.BATCH_REQUEST_METHOD_NAME).inc(); @@ -129,7 +136,7 @@ app.ws.use(async (ctx) => { // send error if batch request feature is not enabled if (!getWsBatchRequestsEnabled()) { const batchRequestDisabledError = predefined.WS_BATCH_REQUESTS_DISABLED; - logger.warn(`${connectionIdPrefix} ${requestIdPrefix}: ${JSON.stringify(batchRequestDisabledError)}`); + logger.warn(`${requestDetails.formattedLogPrefix}: ${JSON.stringify(batchRequestDisabledError)}`); ctx.websocket.send(JSON.stringify([jsonResp(null, batchRequestDisabledError, undefined)])); return; } @@ -140,33 +147,23 @@ app.ws.use(async (ctx) => { request.length, getBatchRequestsMaxSize(), ); - logger.warn(`${connectionIdPrefix} ${requestIdPrefix}: ${JSON.stringify(batchRequestAmountMaxExceed)}`); + logger.warn(`${requestDetails.formattedLogPrefix}: ${JSON.stringify(batchRequestAmountMaxExceed)}`); ctx.websocket.send(JSON.stringify([jsonResp(null, batchRequestAmountMaxExceed, undefined)])); return; } // process requests const requestPromises = request.map((item: any) => { - return getRequestResult( - ctx, - relay, - logger, - item, - limiter, - requestIdPrefix, - connectionIdPrefix, - mirrorNodeClient, - wsMetricRegistry, - ); + return getRequestResult(ctx, relay, logger, item, limiter, mirrorNodeClient, wsMetricRegistry, requestDetails); }); // resolve all promises const responses = await Promise.all(requestPromises); // send to client - sendToClient(ctx.websocket, request, responses, logger, requestIdPrefix, connectionIdPrefix); + sendToClient(ctx.websocket, request, responses, logger, requestDetails); } else { - logger.trace(`${connectionIdPrefix} ${requestIdPrefix}: Receive single request=${JSON.stringify(request)}`); + logger.trace(`${requestDetails.formattedLogPrefix}: Receive single request=${JSON.stringify(request)}`); // process requests const response = await getRequestResult( @@ -175,14 +172,13 @@ app.ws.use(async (ctx) => { logger, request, limiter, - requestIdPrefix, - connectionIdPrefix, mirrorNodeClient, wsMetricRegistry, + requestDetails, ); // send to client - sendToClient(ctx.websocket, request, response, logger, requestIdPrefix, connectionIdPrefix); + sendToClient(ctx.websocket, request, response, logger, requestDetails); } // Calculate the duration of the connection @@ -201,10 +197,11 @@ app.ws.use(async (ctx) => { } }); -const httpApp = new KoaJsonRpc(logger, register).getKoaApp(); +const koaJsonRpc = new KoaJsonRpc(logger, register); +const httpApp = koaJsonRpc.getKoaApp(); collectDefaultMetrics({ register, prefix: 'rpc_relay_' }); -httpApp.use(async (ctx, next) => { +httpApp.use(async (ctx: Koa.Context, next: Koa.Next) => { // prometheus metrics exposure if (ctx.url === '/metrics') { ctx.status = 200; @@ -215,7 +212,7 @@ httpApp.use(async (ctx, next) => { } else if (ctx.url === '/health/readiness') { // readiness endpoint try { - const result = relay.eth().chainId(); + const result = relay.eth().chainId(new RequestDetails({ requestId: uuid(), ipAddress: ctx.request.ip })); if (result.includes('0x12')) { ctx.status = 200; ctx.body = 'OK'; diff --git a/packages/ws-server/tests/acceptance/call.spec.ts b/packages/ws-server/tests/acceptance/call.spec.ts index a00ae92d04..a1f6a17948 100644 --- a/packages/ws-server/tests/acceptance/call.spec.ts +++ b/packages/ws-server/tests/acceptance/call.spec.ts @@ -25,6 +25,7 @@ import { WsTestConstant, WsTestHelper } from '../helper'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import ERC20MockJson from '@hashgraph/json-rpc-server/tests/contracts/ERC20Mock.json'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-1 eth_call', async function () { const METHOD_NAME = 'eth_call'; @@ -60,14 +61,14 @@ describe('@web-socket-batch-1 eth_call', async function () { // @ts-ignore const { mirrorNode } = global; - let requestId: string, - erc20TokenAddr: string, + let erc20TokenAddr: string, accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider, erc20EtherInterface: ethers.Interface; + const requestDetails = new RequestDetails({ requestId: 'ws_callTest', ipAddress: '0.0.0.0' }); + before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const initialAmount: string = '2500000000'; //25 Hbar @@ -78,7 +79,7 @@ describe('@web-socket-batch-1 eth_call', async function () { initialAccount, neededAccounts, initialAmount, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); diff --git a/packages/ws-server/tests/acceptance/estimateGas.spec.ts b/packages/ws-server/tests/acceptance/estimateGas.spec.ts index 62cab70515..5da309f6ba 100644 --- a/packages/ws-server/tests/acceptance/estimateGas.spec.ts +++ b/packages/ws-server/tests/acceptance/estimateGas.spec.ts @@ -25,6 +25,7 @@ import { WsTestConstant, WsTestHelper } from '../helper'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import basicContractJson from '@hashgraph/json-rpc-server/tests/contracts/Basic.json'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-1 eth_estimateGas', async function () { const METHOD_NAME = 'eth_estimateGas'; @@ -38,22 +39,22 @@ describe('@web-socket-batch-1 eth_estimateGas', async function () { currentPrice: number, expectedGas: number, gasPriceDeviation: number, - ethersWsProvider: WebSocketProvider, - requestId = 'eth_estimateGas'; + ethersWsProvider: WebSocketProvider; + + const requestDetails = new RequestDetails({ requestId: 'ws_estimateGasTest', ipAddress: '0.0.0.0' }); before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const initialAmount: string = '2500000000'; //25 Hbar - const neededAccounts: number = 1; + accounts.push( ...(await Utils.createMultipleAliasAccounts( mirrorNode, initialAccount, neededAccounts, initialAmount, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); diff --git a/packages/ws-server/tests/acceptance/getBalance.spec.ts b/packages/ws-server/tests/acceptance/getBalance.spec.ts index d4647306eb..1019b2d733 100644 --- a/packages/ws-server/tests/acceptance/getBalance.spec.ts +++ b/packages/ws-server/tests/acceptance/getBalance.spec.ts @@ -24,6 +24,8 @@ import { ethers, WebSocketProvider } from 'ethers'; import { WsTestConstant, WsTestHelper } from '../helper'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; +import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-1 eth_getBalance', async function () { const METHOD_NAME = 'eth_getBalance'; @@ -38,13 +40,14 @@ describe('@web-socket-batch-1 eth_getBalance', async function () { [WsTestConstant.FAKE_TX_HASH, '0xhbar', 36], ]; // @ts-ignore - const { mirrorNode } = global; + const { mirrorNode }: { mirrorNode: MirrorClient } = global; + const requestId = 'getBalanceTest_ws-server'; + const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); + let accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider; - let requestId: string; before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const initialAmount: string = '2500000000'; //25 Hbar @@ -55,7 +58,7 @@ describe('@web-socket-batch-1 eth_getBalance', async function () { initialAccount, neededAccounts, initialAmount, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); diff --git a/packages/ws-server/tests/acceptance/getLogs.spec.ts b/packages/ws-server/tests/acceptance/getLogs.spec.ts index fea30b0a0c..1b130edff3 100644 --- a/packages/ws-server/tests/acceptance/getLogs.spec.ts +++ b/packages/ws-server/tests/acceptance/getLogs.spec.ts @@ -24,6 +24,8 @@ import { ethers, WebSocketProvider } from 'ethers'; import { WsTestConstant, WsTestHelper } from '../helper'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; +import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-2 eth_getLogs', async function () { const EXPECTED_VALUE = 7; @@ -77,13 +79,15 @@ describe('@web-socket-batch-2 eth_getLogs', async function () { const SIMPLE_CONTRACT_BYTECODE = '0x6080604052348015600f57600080fd5b507f4e7df42af9a017b7c655a28ef10cbc8f05b2b088f087ee02416cfa1a96ac3be26007604051603e91906091565b60405180910390a160aa565b6000819050919050565b6000819050919050565b6000819050919050565b6000607d6079607584604a565b605e565b6054565b9050919050565b608b816068565b82525050565b600060208201905060a460008301846084565b92915050565b603f8060b76000396000f3fe6080604052600080fdfea264697066735822122084db7fe76bde5c9c041d61bb40294c56dc6d339bdbc8e0cd285fc4008ccefc2c64736f6c63430008180033'; // @ts-ignore - const { mirrorNode } = global; + const { mirrorNode }: { mirrorNode: MirrorClient } = global; + const requestId = 'getLogsTest_ws-server'; + const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); + let wsFilterObj: any, accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider; - let requestId: string; + before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const initialAmount: string = '2500000000'; //25 Hbar @@ -94,7 +98,7 @@ describe('@web-socket-batch-2 eth_getLogs', async function () { initialAccount, neededAccounts, initialAmount, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); diff --git a/packages/ws-server/tests/acceptance/getStorageAt.spec.ts b/packages/ws-server/tests/acceptance/getStorageAt.spec.ts index 011dadfd92..dddcb0255e 100644 --- a/packages/ws-server/tests/acceptance/getStorageAt.spec.ts +++ b/packages/ws-server/tests/acceptance/getStorageAt.spec.ts @@ -24,6 +24,7 @@ import { ethers, WebSocketProvider } from 'ethers'; import { WsTestConstant, WsTestHelper } from '../helper'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-2 eth_getStorageAt', async function () { const METHOD_NAME = 'eth_getStorageAt'; @@ -36,7 +37,6 @@ describe('@web-socket-batch-2 eth_getStorageAt', async function () { [WsTestConstant.FAKE_TX_HASH, ''], [WsTestConstant.FAKE_TX_HASH, 36, 'latest'], [WsTestConstant.FAKE_TX_HASH, '0xhbar', 'latest'], - [WsTestConstant.FAKE_TX_HASH, '0x0', 'latest', '0xhedera'], ]; // @notice: The simple contract artifacts (ABI & bytecode) below simply has one state at position 0, which will be assigned to the number `7` within the consutrctor after deployment @@ -54,10 +54,10 @@ describe('@web-socket-batch-2 eth_getStorageAt', async function () { let params: any[], accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider; - let requestId: string; + + const requestDetails = new RequestDetails({ requestId: 'ws_getStorageAtTest', ipAddress: '0.0.0.0' }); before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const initialAmount: string = '2500000000'; //25 Hbar @@ -68,7 +68,7 @@ describe('@web-socket-batch-2 eth_getStorageAt', async function () { initialAccount, neededAccounts, initialAmount, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); diff --git a/packages/ws-server/tests/acceptance/getTransactionByHash.spec.ts b/packages/ws-server/tests/acceptance/getTransactionByHash.spec.ts index 3e681433df..c3dee35c12 100644 --- a/packages/ws-server/tests/acceptance/getTransactionByHash.spec.ts +++ b/packages/ws-server/tests/acceptance/getTransactionByHash.spec.ts @@ -26,6 +26,9 @@ import { numberTo0x } from '@hashgraph/json-rpc-relay/src/formatters'; import { ONE_TINYBAR_IN_WEI_HEX } from '@hashgraph/json-rpc-relay/tests/lib/eth/eth-config'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; +import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; +import RelayClient from '@hashgraph/json-rpc-server/tests/clients/relayClient'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-2 eth_getTransactionByHash', async function () { const METHOD_NAME = 'eth_getTransactionByHash'; @@ -45,15 +48,16 @@ describe('@web-socket-batch-2 eth_getTransactionByHash', async function () { ]; // @ts-ignore - const { mirrorNode, relay, initialBalance } = global; + const { mirrorNode, relay, initialBalance }: { mirrorNode: MirrorClient; relay: RelayClient } = global; + const requestId = 'getTransactionByHash_ws-server'; + const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); + let txHash: string, expectedTxReceipt: any, accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider; - let requestId: string; before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const neededAccounts: number = 2; @@ -63,7 +67,7 @@ describe('@web-socket-batch-2 eth_getTransactionByHash', async function () { initialAccount, neededAccounts, initialBalance, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); @@ -73,12 +77,12 @@ describe('@web-socket-batch-2 eth_getTransactionByHash', async function () { gasLimit: numberTo0x(30000), chainId: Number(CHAIN_ID), to: accounts[1].address, - nonce: await relay.getAccountNonce(accounts[0].address), - maxFeePerGas: await relay.gasPrice(), + nonce: await relay.getAccountNonce(accounts[0].address, requestId), + maxFeePerGas: await relay.gasPrice(requestId), }; const signedTx = await accounts[0].wallet.signTransaction(tx); - txHash = await relay.sendRawTransaction(signedTx); - expectedTxReceipt = await mirrorNode.get(`/contracts/results/${txHash}`); + txHash = await relay.sendRawTransaction(signedTx, requestId); + expectedTxReceipt = await mirrorNode.get(`/contracts/results/${txHash}`, requestDetails); }); beforeEach(async () => { diff --git a/packages/ws-server/tests/acceptance/getTransactionCount.spec.ts b/packages/ws-server/tests/acceptance/getTransactionCount.spec.ts index ac74249c2a..80e16363d1 100644 --- a/packages/ws-server/tests/acceptance/getTransactionCount.spec.ts +++ b/packages/ws-server/tests/acceptance/getTransactionCount.spec.ts @@ -25,6 +25,9 @@ import { WsTestConstant, WsTestHelper } from '../helper'; import { numberTo0x } from '@hashgraph/json-rpc-relay/src/formatters'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; +import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; +import RelayClient from '@hashgraph/json-rpc-server/tests/clients/relayClient'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@release @web-socket-batch-2 eth_getTransactionCount', async function () { const METHOD_NAME = 'eth_getTransactionCount'; @@ -32,14 +35,14 @@ describe('@release @web-socket-batch-2 eth_getTransactionCount', async function const ONE_TINYBAR = Utils.add0xPrefix(Utils.toHex(ethers.parseUnits('1', 10))); // @ts-ignore - const { mirrorNode, relay } = global; + const { mirrorNode, relay }: { mirrorNode: MirrorClient; relay: RelayClient } = global; + const requestId = 'getTransactionCount_ws-server'; + const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); - let requestId: string, - accounts: AliasAccount[] = [], + let accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider; before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const initialAmount: string = '100000000'; //1 Hbar @@ -50,7 +53,7 @@ describe('@release @web-socket-batch-2 eth_getTransactionCount', async function initialAccount, neededAccounts, initialAmount, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); @@ -85,7 +88,7 @@ describe('@release @web-socket-batch-2 eth_getTransactionCount', async function 'latest', ]); WsTestHelper.assertJsonRpcObject(beforeSendRawTransactionCountResponse); - const transactionCountBefore = await relay.getAccountNonce(accounts[0].address); + const transactionCountBefore = await relay.getAccountNonce(accounts[0].address, requestId); expect(Number(beforeSendRawTransactionCountResponse.result)).to.eq(transactionCountBefore); const transaction = { @@ -94,7 +97,7 @@ describe('@release @web-socket-batch-2 eth_getTransactionCount', async function chainId: Number(CHAIN_ID), to: accounts[1].address, maxFeePerGas: defaultGasPrice, - nonce: await relay.getAccountNonce(accounts[0].address), + nonce: await relay.getAccountNonce(accounts[0].address, requestId), }; const signedTx = await accounts[0].wallet.signTransaction(transaction); // @notice submit a transaction to increase transaction count @@ -105,7 +108,7 @@ describe('@release @web-socket-batch-2 eth_getTransactionCount', async function 'latest', ]); WsTestHelper.assertJsonRpcObject(afterSendRawTransactionCountResponse); - const transactionCountAfter = await relay.getAccountNonce(accounts[0].address); + const transactionCountAfter = await relay.getAccountNonce(accounts[0].address, requestId); expect(Number(afterSendRawTransactionCountResponse.result)).to.eq(transactionCountAfter); }); }); diff --git a/packages/ws-server/tests/acceptance/getTransactionReceipt.spec.ts b/packages/ws-server/tests/acceptance/getTransactionReceipt.spec.ts index 273512c5d9..12bed02289 100644 --- a/packages/ws-server/tests/acceptance/getTransactionReceipt.spec.ts +++ b/packages/ws-server/tests/acceptance/getTransactionReceipt.spec.ts @@ -26,6 +26,9 @@ import { numberTo0x } from '@hashgraph/json-rpc-relay/src/formatters'; import { ONE_TINYBAR_IN_WEI_HEX } from '@hashgraph/json-rpc-relay/tests/lib/eth/eth-config'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; +import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; +import RelayClient from '@hashgraph/json-rpc-server/tests/clients/relayClient'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-2 eth_getTransactionReceipt', async function () { const METHOD_NAME = 'eth_getTransactionReceipt'; @@ -45,16 +48,16 @@ describe('@web-socket-batch-2 eth_getTransactionReceipt', async function () { ]; // @ts-ignore - const { mirrorNode, relay, initialBalance } = global; + const { mirrorNode, relay, initialBalance }: { mirrorNode: MirrorClient; relay: RelayClient } = global; + const requestId = 'getTransactionReceiptTest_ws-server'; + const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); let txHash: string, expectedTxReceipt: any, accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider; - let requestId: string; before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const neededAccounts: number = 3; @@ -64,7 +67,7 @@ describe('@web-socket-batch-2 eth_getTransactionReceipt', async function () { initialAccount, neededAccounts, initialBalance, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); @@ -74,13 +77,13 @@ describe('@web-socket-batch-2 eth_getTransactionReceipt', async function () { gasLimit: numberTo0x(30000), chainId: Number(CHAIN_ID), to: accounts[1].address, - nonce: await relay.getAccountNonce(accounts[0].address), - maxFeePerGas: await relay.gasPrice(), + nonce: await relay.getAccountNonce(accounts[0].address, requestId), + maxFeePerGas: await relay.gasPrice(requestId), }; const signedTx = await accounts[0].wallet.signTransaction(tx); - txHash = await relay.sendRawTransaction(signedTx); - expectedTxReceipt = await mirrorNode.get(`/contracts/results/${txHash}`); + txHash = await relay.sendRawTransaction(signedTx, requestId); + expectedTxReceipt = await mirrorNode.get(`/contracts/results/${txHash}`, requestDetails); }); beforeEach(async () => { diff --git a/packages/ws-server/tests/acceptance/index.spec.ts b/packages/ws-server/tests/acceptance/index.spec.ts index 7fe5e20f63..025b9b21e4 100644 --- a/packages/ws-server/tests/acceptance/index.spec.ts +++ b/packages/ws-server/tests/acceptance/index.spec.ts @@ -35,6 +35,10 @@ import { app as wsApp } from '@hashgraph/json-rpc-ws-server/dist/webSocketServer import ServicesClient from '@hashgraph/json-rpc-server/tests/clients/servicesClient'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import { Server } from 'node:http'; +import { setServerTimeout } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/utils'; + dotenv.config({ path: path.resolve(__dirname, '../../../../.env') }); const DOT_ENV = dotenv.parse(fs.readFileSync(path.resolve(__dirname, '../../../../.env'))); @@ -64,8 +68,7 @@ global.relayIsLocal = RELAY_URL === LOCAL_RELAY_URL; describe('RPC Server Acceptance Tests', function () { this.timeout(240 * 1000); // 240 seconds - let relayServer; // Relay Server - let socketServer; + const requestDetails = new RequestDetails({ requestId: 'rpc_batch1Test', ipAddress: '0.0.0.0' }); global.servicesNode = new ServicesClient( NETWORK, OPERATOR_ID, @@ -74,8 +77,6 @@ describe('RPC Server Acceptance Tests', function () { ); global.mirrorNode = new MirrorClient(MIRROR_NODE_URL, logger.child({ name: `mirror-node-test-client` })); global.relay = new RelayClient(RELAY_URL, logger.child({ name: `relay-test-client` })); - global.relayServer = relayServer; - global.socketServer = socketServer; global.logger = logger; before(async () => { @@ -101,7 +102,7 @@ describe('RPC Server Acceptance Tests', function () { ); global.accounts = new Array(initialAccount); - await global.mirrorNode.get(`/accounts/${initialAccount.address}`); + await global.mirrorNode.get(`/accounts/${initialAccount.address}`, requestDetails); }); after(async function () { @@ -140,10 +141,12 @@ describe('RPC Server Acceptance Tests', function () { //stop relay logger.info('Stop relay'); + const relayServer: Server = global.relayServer; if (relayServer !== undefined) { relayServer.close(); } + const socketServer: Server = global.socketServer; if (process.env.TEST_WS_SERVER === 'true' && socketServer !== undefined) { socketServer.close(); } @@ -171,7 +174,9 @@ describe('RPC Server Acceptance Tests', function () { // start local relay, relay instance in local should not be running logger.info(`Start relay on port ${constants.RELAY_PORT}`); - relayServer = app.listen({ port: constants.RELAY_PORT }); + const relayServer = app.listen({ port: constants.RELAY_PORT }); + global.relayServer = relayServer; + setServerTimeout(relayServer); if (process.env.TEST_WS_SERVER === 'true') { logger.info(`Start ws-server on port ${constants.WEB_SOCKET_PORT}`); diff --git a/packages/ws-server/tests/acceptance/sendRawTransaction.spec.ts b/packages/ws-server/tests/acceptance/sendRawTransaction.spec.ts index fa8afd1c26..ac752f521b 100644 --- a/packages/ws-server/tests/acceptance/sendRawTransaction.spec.ts +++ b/packages/ws-server/tests/acceptance/sendRawTransaction.spec.ts @@ -28,6 +28,9 @@ import { numberTo0x } from '@hashgraph/json-rpc-relay/src/formatters'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import { ONE_TINYBAR_IN_WEI_HEX } from '@hashgraph/json-rpc-relay/tests/lib/eth/eth-config'; +import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; +import RelayClient from '@hashgraph/json-rpc-server/tests/clients/relayClient'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('@web-socket-batch-2 eth_sendRawTransaction', async function () { const METHOD_NAME = 'eth_sendRawTransaction'; @@ -47,16 +50,17 @@ describe('@web-socket-batch-2 eth_sendRawTransaction', async function () { ]; // @ts-ignore - const { mirrorNode, relay } = global; + const { mirrorNode, relay }: { mirrorNode: MirrorClient; relay: RelayClient } = global; const initialBalance = '5000000000'; // 50hbar + const requestId = 'sendRawTransactionTest_ws-server'; + const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); let tx: any, sendHbarToProxyContractDeployerTx: any, accounts: AliasAccount[] = [], ethersWsProvider: WebSocketProvider; - let requestId: string; + before(async () => { - requestId = Utils.generateRequestId(); const initialAccount: AliasAccount = global.accounts[0]; const neededAccounts: number = 3; @@ -66,7 +70,7 @@ describe('@web-socket-batch-2 eth_sendRawTransaction', async function () { initialAccount, neededAccounts, initialBalance, - requestId, + requestDetails, )), ); global.accounts.push(...accounts); @@ -76,7 +80,7 @@ describe('@web-socket-batch-2 eth_sendRawTransaction', async function () { gasLimit: numberTo0x(30000), chainId: Number(CHAIN_ID), to: accounts[2].address, - maxFeePerGas: await relay.gasPrice(), + maxFeePerGas: await relay.gasPrice(requestId), }; sendHbarToProxyContractDeployerTx = { @@ -110,15 +114,15 @@ describe('@web-socket-batch-2 eth_sendRawTransaction', async function () { } it(`@release Should execute eth_sendRawTransaction on Standard Web Socket and handle valid requests correctly`, async () => { - tx.nonce = await relay.getAccountNonce(accounts[0].address); + tx.nonce = await relay.getAccountNonce(accounts[0].address, requestId); const signedTx = await accounts[0].wallet.signTransaction(tx); const response = await WsTestHelper.sendRequestToStandardWebSocket(METHOD_NAME, [signedTx], 1000); WsTestHelper.assertJsonRpcObject(response); const txHash = response.result; - const txReceipt = await mirrorNode.get(`/contracts/results/${txHash}`); - const fromAccountInfo = await mirrorNode.get(`/accounts/${txReceipt.from}`); + const txReceipt = await mirrorNode.get(`/contracts/results/${txHash}`, requestDetails); + const fromAccountInfo = await mirrorNode.get(`/accounts/${txReceipt.from}`, requestDetails); expect(txReceipt.to).to.eq(accounts[2].address.toLowerCase()); expect(fromAccountInfo.evm_address).to.eq(accounts[0].address.toLowerCase()); @@ -178,13 +182,13 @@ describe('@web-socket-batch-2 eth_sendRawTransaction', async function () { } it(`@release Should execute eth_sendRawTransaction on Ethers Web Socket Provider and handle valid requests correctly`, async () => { - tx.nonce = await relay.getAccountNonce(accounts[1].address); + tx.nonce = await relay.getAccountNonce(accounts[1].address, requestId); const signedTx = await accounts[1].wallet.signTransaction(tx); // const signedTx = await accounts[0].wallet.signTransaction(tx); const txHash = await ethersWsProvider.send(METHOD_NAME, [signedTx]); - const txReceipt = await mirrorNode.get(`/contracts/results/${txHash}`); - const fromAccountInfo = await mirrorNode.get(`/accounts/${txReceipt.from}`); + const txReceipt = await mirrorNode.get(`/contracts/results/${txHash}`, requestDetails); + const fromAccountInfo = await mirrorNode.get(`/accounts/${txReceipt.from}`, requestDetails); expect(txReceipt.to).to.eq(accounts[2].address.toLowerCase()); expect(fromAccountInfo.evm_address).to.eq(accounts[1].address.toLowerCase()); diff --git a/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts b/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts index 5fc05c2862..8f4db50ab0 100644 --- a/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts +++ b/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts @@ -27,6 +27,8 @@ import { predefined } from '@hashgraph/json-rpc-relay'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import Assertions from '@hashgraph/json-rpc-server/tests/helpers/assertions'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; +import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; +import RelayClient from '@hashgraph/json-rpc-server/tests/clients/relayClient'; chai.use(solidity); const WS_RELAY_URL = `${process.env.WS_RELAY_URL}`; @@ -103,7 +105,8 @@ describe('@web-socket-batch-3 eth_subscribe newHeads', async function () { before(async () => { // @ts-ignore - const { socketServer, mirrorNode, relay } = global; + const { socketServer, mirrorNode, relay }: { socketServer: any; mirrorNode: MirrorClient; relay: RelayClient } = + global; mirrorNodeServer = mirrorNode; rpcServer = relay; wsServer = socketServer; diff --git a/packages/ws-server/tests/unit/utils.spec.ts b/packages/ws-server/tests/unit/utils.spec.ts index 8682e859a8..eae0293810 100644 --- a/packages/ws-server/tests/unit/utils.spec.ts +++ b/packages/ws-server/tests/unit/utils.spec.ts @@ -33,6 +33,7 @@ import { WS_CONSTANTS } from '../../src/utils/constants'; import { Relay } from '@hashgraph/json-rpc-relay/src'; import ConnectionLimiter from '../../src/metrics/connectionLimiter'; import WsMetricRegistry from '../../src/metrics/wsMetricRegistry'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('Utilities unit tests', async function () { describe('constructValidLogSubscriptionFilter tests', () => { @@ -86,8 +87,11 @@ describe('Utilities unit tests', async function () { let loggerMock: any; let request: any; let response: any; - const requestIdPrefix = 'req-123'; - const connectionIdPrefix = 'conn-456'; + const requestDetails = new RequestDetails({ + requestId: 'req-123', + ipAddress: '0.0.0.0', + connectionId: 'conn-456', + }); beforeEach(() => { connectionMock = { @@ -110,9 +114,9 @@ describe('Utilities unit tests', async function () { }); it('should log the response being sent to the client', () => { - sendToClient(connectionMock, request, response, loggerMock, requestIdPrefix, connectionIdPrefix); + sendToClient(connectionMock, request, response, loggerMock, requestDetails); - const expectedLogMessage = `${connectionIdPrefix} ${requestIdPrefix}: Sending result=${JSON.stringify( + const expectedLogMessage = `${requestDetails.formattedLogPrefix}: Sending result=${JSON.stringify( response, )} to client for request=${JSON.stringify(request)}`; @@ -121,14 +125,14 @@ describe('Utilities unit tests', async function () { }); it('should send the response to the client connection', () => { - sendToClient(connectionMock, request, response, loggerMock, requestIdPrefix, connectionIdPrefix); + sendToClient(connectionMock, request, response, loggerMock, requestDetails); expect(connectionMock.send.calledOnce).to.be.true; expect(connectionMock.send.calledWith(JSON.stringify(response))).to.be.true; }); it('should reset the inactivity TTL timer for the client connection', () => { - sendToClient(connectionMock, request, response, loggerMock, requestIdPrefix, connectionIdPrefix); + sendToClient(connectionMock, request, response, loggerMock, requestDetails); expect(connectionMock.limiter.resetInactivityTTLTimer.calledOnce).to.be.true; expect(connectionMock.limiter.resetInactivityTTLTimer.calledWith(connectionMock)).to.be.true; diff --git a/packages/ws-server/tests/unit/validations.spec.ts b/packages/ws-server/tests/unit/validations.spec.ts index 2ade475f75..feaf05c186 100644 --- a/packages/ws-server/tests/unit/validations.spec.ts +++ b/packages/ws-server/tests/unit/validations.spec.ts @@ -22,12 +22,18 @@ import pino from 'pino'; import { expect } from 'chai'; import { WS_CONSTANTS } from '../../src/utils/constants'; import { validateJsonRpcRequest, verifySupportedMethod } from '../../src/utils/utils'; +import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; const logger = pino(); describe('validations unit test', async function () { const FAKE_REQUEST_ID = '3'; const FAKE_CONNECTION_ID = '9'; + const requestDetails = new RequestDetails({ + requestId: FAKE_REQUEST_ID, + ipAddress: '0.0.0.0', + connectionId: FAKE_CONNECTION_ID, + }); it('Should execute validateJsonRpcRequest() to validate valid JSON RPC request and return true', () => { const VALID_REQEST = { @@ -37,7 +43,7 @@ describe('validations unit test', async function () { params: [], }; - expect(validateJsonRpcRequest(VALID_REQEST, logger, FAKE_REQUEST_ID, FAKE_CONNECTION_ID)).to.be.true; + expect(validateJsonRpcRequest(VALID_REQEST, logger, requestDetails)).to.be.true; }); it('Should execute validateJsonRpcRequest() to validate invalid JSON RPC requests and return false', () => { @@ -61,8 +67,8 @@ describe('validations unit test', async function () { INVALID_REQUESTS.forEach((request) => { console.log(request); - - expect(validateJsonRpcRequest(request, logger, FAKE_REQUEST_ID, FAKE_CONNECTION_ID)).to.be.false; + // @ts-ignore + expect(validateJsonRpcRequest(request, logger, requestDetails)).to.be.false; }); }); @@ -74,8 +80,8 @@ describe('validations unit test', async function () { method: 'eth_chainId', params: [], }; - - expect(validateJsonRpcRequest(REQUEST, logger, FAKE_REQUEST_ID, FAKE_CONNECTION_ID)).to.be.true; + // @ts-ignore + expect(validateJsonRpcRequest(REQUEST, logger, requestDetails)).to.be.true; delete process.env.REQUEST_ID_IS_OPTIONAL; }); From f1794d8b0aff6424bb04e19bc816213652d88e72 Mon Sep 17 00:00:00 2001 From: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:16:36 +0300 Subject: [PATCH 11/38] chore: Optimize imports (#3056) Signed-off-by: Victor Yanev --- .../relay/src/lib/clients/mirrorNodeClient.ts | 8 +-- packages/relay/src/lib/clients/sdkClient.ts | 56 ++++++++++--------- packages/relay/src/lib/eth.ts | 21 ++++--- packages/relay/src/lib/hbarlimiter/index.ts | 3 +- packages/relay/src/lib/poller.ts | 2 +- packages/relay/src/lib/relay.ts | 6 +- .../src/lib/services/debugService/index.ts | 3 +- .../lib/services/hapiService/hapiService.ts | 3 +- .../services/metricService/metricService.ts | 8 ++- .../relay/src/lib/subscriptionController.ts | 2 +- packages/relay/src/lib/types/index.ts | 16 +++--- packages/relay/tests/lib/eth/eth_call.spec.ts | 4 +- .../tests/lib/eth/eth_feeHistory.spec.ts | 2 +- .../tests/lib/eth/eth_getBalance.spec.ts | 8 +-- .../tests/lib/eth/eth_getBlockByHash.spec.ts | 6 +- ...h_getBlockTransactionCountByNumber.spec.ts | 2 +- .../relay/tests/lib/eth/eth_getLogs.spec.ts | 8 +-- .../tests/lib/eth/eth_getStorageAt.spec.ts | 4 +- .../lib/eth/eth_getTransactionByHash.spec.ts | 2 +- .../relay/tests/lib/ethGetBlockBy.spec.ts | 6 +- packages/relay/tests/lib/formatters.spec.ts | 12 ++-- .../relay/tests/lib/mirrorNodeClient.spec.ts | 10 ++-- packages/relay/tests/lib/openrpc.spec.ts | 9 ++- packages/relay/tests/lib/precheck.spec.ts | 7 ++- packages/relay/tests/lib/sdkClient.spec.ts | 20 +++---- .../tests/lib/services/eth/filter.spec.ts | 6 +- .../hbarLimitService/hbarLimitService.spec.ts | 4 +- .../metricService/metricService.spec.ts | 5 +- packages/relay/tests/lib/web3.spec.ts | 2 +- packages/relay/tests/redisInMemoryServer.ts | 1 - .../src/koaJsonRpc/lib/methodConfiguration.ts | 3 +- .../tests/acceptance/cacheService.spec.ts | 1 + .../tests/acceptance/conformityTests.spec.ts | 3 +- .../tests/acceptance/equivalence.spec.ts | 1 + .../tests/acceptance/hbarLimiter.spec.ts | 2 - .../htsPrecompile/precompileCalls.spec.ts | 4 +- .../htsPrecompile/tokenCreate.spec.ts | 5 +- .../htsPrecompile/tokenManagement.spec.ts | 5 +- .../server/tests/acceptance/index.spec.ts | 2 +- .../tests/acceptance/rpc_batch1.spec.ts | 2 +- packages/server/tests/clients/mirrorClient.ts | 1 - .../server/tests/clients/servicesClient.ts | 32 +++++------ .../integration/koaJsonRpc/rpcError.spec.ts | 14 ++--- .../server/tests/integration/server.spec.ts | 4 +- packages/ws-server/src/controllers/index.ts | 2 +- .../src/metrics/connectionLimiter.ts | 2 +- packages/ws-server/src/utils/utils.ts | 2 +- .../ws-server/tests/acceptance/index.spec.ts | 5 +- .../tests/acceptance/subscribe.spec.ts | 1 + .../acceptance/subscribeNewHeads.spec.ts | 1 + 50 files changed, 169 insertions(+), 169 deletions(-) diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index fdef1c5e9a..3ff0f0b9f5 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -32,16 +32,16 @@ import { install as betterLookupInstall } from 'better-lookup'; import { CacheService } from '../services/cacheService/cacheService'; import { MirrorNodeClientError } from '../errors/MirrorNodeClientError'; import Axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -import { parseNumericEnvVar, formatTransactionId, formatRequestIdMessage } from '../../formatters'; +import { formatRequestIdMessage, formatTransactionId, parseNumericEnvVar } from '../../formatters'; import { - ILimitOrderParams, IContractCallRequest, IContractCallResponse, + IContractLogsResultsParams, IContractResultsParams, + ILimitOrderParams, + IMirrorNodeTransactionRecord, ITransactionRecordMetric, - IContractLogsResultsParams, MirrorNodeTransactionRecord, - IMirrorNodeTransactionRecord, RequestDetails, } from '../types'; diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index dd4f714a9e..063e4bcf96 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -19,38 +19,38 @@ */ import { - Hbar, - Query, - Client, - Status, - FileId, - HbarUnit, + AccountBalance, + AccountBalanceQuery, AccountId, - ContractId, AccountInfo, - Transaction, - FeeSchedules, - ExchangeRate, - FileInfoQuery, - TransactionId, - ExchangeRates, - FeeComponents, - AccountBalance, AccountInfoQuery, + Client, + ContractByteCodeQuery, ContractCallQuery, - FileContentsQuery, - TransactionRecord, - AccountBalanceQuery, + ContractFunctionResult, + ContractId, EthereumTransaction, - PrecheckStatusError, - TransactionResponse, + EthereumTransactionData, + ExchangeRate, + ExchangeRates, + FeeComponents, + FeeSchedules, FileAppendTransaction, + FileContentsQuery, FileCreateTransaction, FileDeleteTransaction, - ContractByteCodeQuery, - ContractFunctionResult, + FileId, + FileInfoQuery, + Hbar, + HbarUnit, + PrecheckStatusError, + Query, + Status, + Transaction, + TransactionId, + TransactionRecord, TransactionRecordQuery, - EthereumTransactionData, + TransactionResponse, } from '@hashgraph/sdk'; import { Logger } from 'pino'; import { EventEmitter } from 'events'; @@ -60,9 +60,13 @@ import { BigNumber } from '@hashgraph/sdk/lib/Transfer'; import { SDKClientError } from '../errors/SDKClientError'; import { JsonRpcError, predefined } from '../errors/JsonRpcError'; import { CacheService } from '../services/cacheService/cacheService'; -import { formatRequestIdMessage, weibarHexToTinyBarInt } from '../../formatters'; -import { ITransactionRecordMetric, IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from '../types'; -import { RequestDetails } from '../types'; +import { weibarHexToTinyBarInt } from '../../formatters'; +import { + IExecuteQueryEventPayload, + IExecuteTransactionEventPayload, + ITransactionRecordMetric, + RequestDetails, +} from '../types'; const _ = require('lodash'); diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index 43a05abfe6..1735fa0b34 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -41,23 +41,22 @@ import { IDebugService } from './services/debugService/IDebugService'; import { MirrorNodeClientError } from './errors/MirrorNodeClientError'; import { IReceiptRootHash, ReceiptsRootUtils } from '../receiptsRootUtils'; import { IFilterService } from './services/ethService/ethFilterService/IFilterService'; -import { IFeeHistory, IContractCallRequest, IContractCallResponse, ITransactionReceipt, RequestDetails } from './types'; +import { IContractCallRequest, IContractCallResponse, IFeeHistory, ITransactionReceipt, RequestDetails } from './types'; import { - isHex, - toHash32, - prepend0x, ASCIIToHex, - numberTo0x, + formatContractResult, + formatTransactionIdWithoutQueryParams, + getFunctionSelector, + isHex, + isValidEthereumAddress, nanOrNumberTo0x, - parseNumericEnvVar, nullableNumberTo0x, + numberTo0x, + parseNumericEnvVar, + prepend0x, + toHash32, trimPrecedingZeros, - formatContractResult, weibarHexToTinyBarInt, - isValidEthereumAddress, - formatRequestIdMessage, - formatTransactionIdWithoutQueryParams, - getFunctionSelector, } from '../formatters'; const _ = require('lodash'); diff --git a/packages/relay/src/lib/hbarlimiter/index.ts b/packages/relay/src/lib/hbarlimiter/index.ts index 2b25a657f5..c4ae1a7d98 100644 --- a/packages/relay/src/lib/hbarlimiter/index.ts +++ b/packages/relay/src/lib/hbarlimiter/index.ts @@ -20,8 +20,7 @@ import { Logger } from 'pino'; import constants from '../constants'; -import { Registry, Counter, Gauge } from 'prom-client'; -import { formatRequestIdMessage } from '../../formatters'; +import { Counter, Gauge, Registry } from 'prom-client'; import { RequestDetails } from '../types'; export default class HbarLimit { diff --git a/packages/relay/src/lib/poller.ts b/packages/relay/src/lib/poller.ts index ecfd3e65d6..36439bc39d 100644 --- a/packages/relay/src/lib/poller.ts +++ b/packages/relay/src/lib/poller.ts @@ -20,7 +20,7 @@ import { Eth } from '../index'; import { Logger } from 'pino'; -import { Registry, Gauge } from 'prom-client'; +import { Gauge, Registry } from 'prom-client'; import { RequestDetails } from './types'; import { Utils } from '../utils'; diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index b366e541c4..92597e2cfe 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -20,8 +20,6 @@ import dotenv from 'dotenv'; import findConfig from 'find-config'; -dotenv.config({ path: findConfig('.env') || '' }); - import { Logger } from 'pino'; import { NetImpl } from './net'; import { EthImpl } from './eth'; @@ -34,7 +32,7 @@ import { Client } from '@hashgraph/sdk'; import { prepend0x } from '../formatters'; import { MirrorNodeClient } from './clients'; import { Gauge, Registry } from 'prom-client'; -import { Relay, Eth, Net, Web3, Subs } from '../index'; +import { Eth, Net, Relay, Subs, Web3 } from '../index'; import HAPIService from './services/hapiService/hapiService'; import { SubscriptionController } from './subscriptionController'; import MetricService from './services/metricService/metricService'; @@ -42,6 +40,8 @@ import { CacheService } from './services/cacheService/cacheService'; import { RequestDetails } from './types'; import { Utils } from '../utils'; +dotenv.config({ path: findConfig('.env') || '' }); + export class RelayImpl implements Relay { /** * @private diff --git a/packages/relay/src/lib/services/debugService/index.ts b/packages/relay/src/lib/services/debugService/index.ts index 6a57d47bbf..92a6db0ad8 100644 --- a/packages/relay/src/lib/services/debugService/index.ts +++ b/packages/relay/src/lib/services/debugService/index.ts @@ -28,8 +28,7 @@ import { predefined } from '../../errors/JsonRpcError'; import { EthImpl } from '../../eth'; import { IOpcodesResponse } from '../../clients/models/IOpcodesResponse'; import { IOpcode } from '../../clients/models/IOpcode'; -import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig } from '../../types'; -import { RequestDetails } from '../../types'; +import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig, RequestDetails } from '../../types'; /** * Represents a DebugService for tracing and debugging transactions and debugging diff --git a/packages/relay/src/lib/services/hapiService/hapiService.ts b/packages/relay/src/lib/services/hapiService/hapiService.ts index ab51682a08..1ca0e5bce1 100644 --- a/packages/relay/src/lib/services/hapiService/hapiService.ts +++ b/packages/relay/src/lib/services/hapiService/hapiService.ts @@ -25,12 +25,11 @@ import findConfig from 'find-config'; import constants from '../../constants'; import { Utils } from './../../../utils'; import HbarLimit from '../../hbarlimiter'; -import { Registry, Counter } from 'prom-client'; +import { Counter, Registry } from 'prom-client'; import { SDKClient } from '../../clients/sdkClient'; import { CacheService } from '../cacheService/cacheService'; import { AccountId, Client, PrivateKey } from '@hashgraph/sdk'; import fs from 'fs'; -import path from 'path'; export default class HAPIService { private transactionCount: number; diff --git a/packages/relay/src/lib/services/metricService/metricService.ts b/packages/relay/src/lib/services/metricService/metricService.ts index ff99a1c09c..0d5807c03f 100644 --- a/packages/relay/src/lib/services/metricService/metricService.ts +++ b/packages/relay/src/lib/services/metricService/metricService.ts @@ -24,8 +24,12 @@ import constants from '../../constants'; import HbarLimit from '../../hbarlimiter'; import { Histogram, Registry } from 'prom-client'; import { MirrorNodeClient, SDKClient } from '../../clients'; -import { ITransactionRecordMetric, IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from '../../types'; -import { RequestDetails } from '../../types'; +import { + IExecuteQueryEventPayload, + IExecuteTransactionEventPayload, + ITransactionRecordMetric, + RequestDetails, +} from '../../types'; export default class MetricService { /** diff --git a/packages/relay/src/lib/subscriptionController.ts b/packages/relay/src/lib/subscriptionController.ts index 292e077ed4..e306366357 100644 --- a/packages/relay/src/lib/subscriptionController.ts +++ b/packages/relay/src/lib/subscriptionController.ts @@ -24,7 +24,7 @@ import crypto from 'crypto'; import constants from './constants'; import { Poller } from './poller'; import { generateRandomHex } from '../formatters'; -import { Registry, Histogram, Counter } from 'prom-client'; +import { Counter, Histogram, Registry } from 'prom-client'; import { Subs } from '../index'; export interface Subscriber { diff --git a/packages/relay/src/lib/types/index.ts b/packages/relay/src/lib/types/index.ts index c50a13d891..2ca506dbf4 100644 --- a/packages/relay/src/lib/types/index.ts +++ b/packages/relay/src/lib/types/index.ts @@ -22,21 +22,21 @@ import { IFeeHistory } from './IFeeHistory'; import { ITransactionRecordMetric } from './metrics'; import { ITransactionReceipt } from './ITransactionReceipt'; import { ITracerConfigWrapper } from './ITracerConfigWrapper'; -import { IExecuteTransactionEventPayload, IExecuteQueryEventPayload } from './events'; +import { IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from './events'; import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig } from './ITracerConfig'; import { - ITransfer, - INftTransfer, - ITokenTransfer, - ILimitOrderParams, IAssessedCustomFee, IContractCallRequest, IContractCallResponse, - IStakingRewardTransfer, - IContractResultsParams, IContractLogsResultsParams, - MirrorNodeTransactionRecord, + IContractResultsParams, + ILimitOrderParams, IMirrorNodeTransactionRecord, + INftTransfer, + IStakingRewardTransfer, + ITokenTransfer, + ITransfer, + MirrorNodeTransactionRecord, } from './mirrorNode'; import { IRequestDetails, RequestDetails } from './RequestDetails'; diff --git a/packages/relay/tests/lib/eth/eth_call.spec.ts b/packages/relay/tests/lib/eth/eth_call.spec.ts index 8b3dd2860a..1a14846f20 100644 --- a/packages/relay/tests/lib/eth/eth_call.spec.ts +++ b/packages/relay/tests/lib/eth/eth_call.spec.ts @@ -35,13 +35,13 @@ import { DEFAULT_CONTRACT_2, DEFAULT_CONTRACT_3_EMPTY_BYTECODE, DEFAULT_NETWORK_FEES, + EXAMPLE_CONTRACT_BYTECODE, MAX_GAS_LIMIT, MAX_GAS_LIMIT_HEX, NO_TRANSACTIONS, NON_EXISTENT_CONTRACT_ADDRESS, - WRONG_CONTRACT_ADDRESS, ONE_TINYBAR_IN_WEI_HEX, - EXAMPLE_CONTRACT_BYTECODE, + WRONG_CONTRACT_ADDRESS, } from './eth-config'; import { JsonRpcError, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; diff --git a/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts b/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts index be4b57b667..afb930cda8 100644 --- a/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts +++ b/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts @@ -27,9 +27,9 @@ import constants from '../../../src/lib/constants'; import { SDKClient } from '../../../src/lib/clients'; import { BASE_FEE_PER_GAS_HEX, - BLOCKS_LIMIT_ORDER_URL, BLOCK_NUMBER_2, BLOCK_NUMBER_3, + BLOCKS_LIMIT_ORDER_URL, DEFAULT_BLOCK, DEFAULT_NETWORK_FEES, ETH_FEE_HISTORY_VALUE, diff --git a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts index 3af1eb1b5a..40b56bd3f2 100644 --- a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts @@ -28,18 +28,18 @@ import { buildCryptoTransferTransaction } from '../../helpers'; import { SDKClient } from '../../../src/lib/clients'; import { numberTo0x } from '../../../dist/formatters'; import { - BLOCKS_LIMIT_ORDER_URL, BLOCK_TIMESTAMP, BLOCK_ZERO, + BLOCKS_LIMIT_ORDER_URL, CONTRACT_ADDRESS_1, CONTRACT_ID_1, - DEFAULT_BLOCK, - DEFAULT_NETWORK_FEES, DEF_BALANCE, DEF_HEX_BALANCE, + DEFAULT_BLOCK, + DEFAULT_NETWORK_FEES, MOCK_BALANCE_RES, - MOCK_BLOCKS_FOR_BALANCE_RES, MOCK_BLOCK_NUMBER_1000_RES, + MOCK_BLOCKS_FOR_BALANCE_RES, NOT_FOUND_RES, TINYBAR_TO_WEIBAR_COEF_BIGINT, } from './eth-config'; diff --git a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts index dcf2eb6074..87a4940941 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts @@ -36,7 +36,6 @@ import { BLOCK_HASH_TRIMMED, BLOCK_NUMBER_HEX, BLOCK_TIMESTAMP_HEX, - CONTRACTS_RESULTS_NEXT_URL, CONTRACT_ADDRESS_1, CONTRACT_ADDRESS_2, CONTRACT_HASH_1, @@ -45,15 +44,16 @@ import { CONTRACT_RESULTS_WITH_FILTER_URL, CONTRACT_TIMESTAMP_1, CONTRACT_TIMESTAMP_2, + contractByEvmAddress, + CONTRACTS_RESULTS_NEXT_URL, DEFAULT_BLOCK, + DEFAULT_BLOCK_RECEIPTS_ROOT_HASH, DEFAULT_CONTRACT, DEFAULT_ETH_GET_BLOCK_BY_LOGS, DEFAULT_NETWORK_FEES, LINKS_NEXT_RES, MOCK_ACCOUNT_WITHOUT_TRANSACTIONS, NO_SUCH_BLOCK_EXISTS_RES, - contractByEvmAddress, - DEFAULT_BLOCK_RECEIPTS_ROOT_HASH, } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; import { RequestDetails } from '../../../src/lib/types'; diff --git a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts index 27c579b42f..8cef486271 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByNumber.spec.ts @@ -26,9 +26,9 @@ import chaiAsPromised from 'chai-as-promised'; import { SDKClient } from '../../../src/lib/clients'; import { numberTo0x } from '../../../dist/formatters'; import { - BLOCKS_LIMIT_ORDER_URL, BLOCK_NUMBER, BLOCK_TRANSACTION_COUNT, + BLOCKS_LIMIT_ORDER_URL, DEFAULT_BLOCK, DEFAULT_BLOCKS_RES, DEFAULT_NETWORK_FEES, diff --git a/packages/relay/tests/lib/eth/eth_getLogs.spec.ts b/packages/relay/tests/lib/eth/eth_getLogs.spec.ts index d2cd1e264c..744c44c9d2 100644 --- a/packages/relay/tests/lib/eth/eth_getLogs.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getLogs.spec.ts @@ -37,21 +37,21 @@ import { } from '../../helpers'; import { SDKClient } from '../../../src/lib/clients'; import { - BLOCKS_LIMIT_ORDER_URL, BLOCK_HASH, - CONTRACTS_LOGS_WITH_FILTER, + BLOCKS_LIMIT_ORDER_URL, CONTRACT_ADDRESS_1, CONTRACT_ADDRESS_2, CONTRACT_RESULTS_LOGS_WITH_FILTER_URL, + CONTRACTS_LOGS_WITH_FILTER, DEFAULT_BLOCK, DEFAULT_BLOCKS_RES, DEFAULT_CONTRACT, DEFAULT_CONTRACT_2, + DEFAULT_LOG_TOPICS, + DEFAULT_LOG_TOPICS_1, DEFAULT_LOGS, DEFAULT_LOGS_3, DEFAULT_LOGS_4, - DEFAULT_LOG_TOPICS, - DEFAULT_LOG_TOPICS_1, DEFAULT_NETWORK_FEES, DEFAULT_NULL_LOG_TOPICS, NOT_FOUND_RES, diff --git a/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts b/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts index 4b5d5033b3..d06dd51844 100644 --- a/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts @@ -27,8 +27,9 @@ import chaiAsPromised from 'chai-as-promised'; import { EthImpl } from '../../../src/lib/eth'; import { SDKClient } from '../../../src/lib/clients'; import { - BLOCKS_LIMIT_ORDER_URL, + BLOCK_HASH, BLOCK_NUMBER, + BLOCKS_LIMIT_ORDER_URL, CONTRACT_ADDRESS_1, DEFAULT_BLOCK, DEFAULT_CONTRACT_STATE_EMPTY_ARRAY, @@ -38,7 +39,6 @@ import { DETAILD_CONTRACT_RESULT_NOT_FOUND, MOST_RECENT_BLOCK, OLDER_BLOCK, - BLOCK_HASH, } from './eth-config'; import { Eth, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; diff --git a/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts index 3b3b93d942..5f5fd44225 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts @@ -22,7 +22,7 @@ import dotenv from 'dotenv'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { Transaction, Transaction2930, Transaction1559 } from '../../../src/lib/model'; +import { Transaction, Transaction1559, Transaction2930 } from '../../../src/lib/model'; import RelayAssertions from '../../assertions'; import { DEFAULT_DETAILED_CONTRACT_RESULT_BY_HASH_REVERTED, diff --git a/packages/relay/tests/lib/ethGetBlockBy.spec.ts b/packages/relay/tests/lib/ethGetBlockBy.spec.ts index ab3f498e0d..a1797bcb10 100644 --- a/packages/relay/tests/lib/ethGetBlockBy.spec.ts +++ b/packages/relay/tests/lib/ethGetBlockBy.spec.ts @@ -24,8 +24,6 @@ import MockAdapter from 'axios-mock-adapter'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { Registry } from 'prom-client'; - -dotenv.config({ path: path.resolve(__dirname, '../test.env') }); import { EthImpl } from '../../src/lib/eth'; import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient'; @@ -34,11 +32,13 @@ import constants from '../../src/lib/constants'; import HAPIService from '../../src/lib/services/hapiService/hapiService'; import HbarLimit from '../../src/lib/hbarlimiter'; import { Log, Transaction } from '../../src/lib/model'; -import { nullableNumberTo0x, numberTo0x, nanOrNumberTo0x, toHash32 } from '../../../../packages/relay/src/formatters'; +import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../../packages/relay/src/formatters'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { defaultDetailedContractResults, useInMemoryRedisServer } from '../helpers'; import { EventEmitter } from 'events'; +dotenv.config({ path: path.resolve(__dirname, '../test.env') }); + use(chaiAsPromised); const logger = pino(); diff --git a/packages/relay/tests/lib/formatters.spec.ts b/packages/relay/tests/lib/formatters.spec.ts index b9f6899341..bb21d85387 100644 --- a/packages/relay/tests/lib/formatters.spec.ts +++ b/packages/relay/tests/lib/formatters.spec.ts @@ -20,11 +20,15 @@ import { expect } from 'chai'; import { + ASCIIToHex, decodeErrorMessage, formatContractResult, + formatRequestIdMessage, formatTransactionId, formatTransactionIdWithoutQueryParams, + getFunctionSelector, hexToASCII, + isHex, isValidEthereumAddress, mapKeysAndValues, nanOrNumberTo0x, @@ -32,16 +36,12 @@ import { numberTo0x, parseNumericEnvVar, prepend0x, + strip0x, toHash32, + toHexString, toNullableBigNumber, toNullIfEmptyHex, trimPrecedingZeros, - isHex, - ASCIIToHex, - formatRequestIdMessage, - strip0x, - getFunctionSelector, - toHexString, weibarHexToTinyBarInt, } from '../../src/formatters'; import constants from '../../src/lib/constants'; diff --git a/packages/relay/tests/lib/mirrorNodeClient.spec.ts b/packages/relay/tests/lib/mirrorNodeClient.spec.ts index f016084f4e..4ac09984ad 100644 --- a/packages/relay/tests/lib/mirrorNodeClient.spec.ts +++ b/packages/relay/tests/lib/mirrorNodeClient.spec.ts @@ -22,23 +22,23 @@ import path from 'path'; import dotenv from 'dotenv'; import { expect } from 'chai'; import { Registry } from 'prom-client'; -dotenv.config({ path: path.resolve(__dirname, '../test.env') }); import { MirrorNodeClient } from '../../src/lib/clients'; import constants from '../../src/lib/constants'; import axios, { AxiosInstance } from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { getRequestId, mockData, random20BytesAddress } from '../helpers'; - -const registry = new Registry(); - import pino from 'pino'; import { ethers } from 'ethers'; -import { predefined, MirrorNodeClientError } from '../../src'; +import { MirrorNodeClientError, predefined } from '../../src'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { MirrorNodeTransactionRecord, RequestDetails } from '../../src/lib/types'; import { SDKClientError } from '../../src/lib/errors/SDKClientError'; import { BigNumber } from 'bignumber.js'; +dotenv.config({ path: path.resolve(__dirname, '../test.env') }); + +const registry = new Registry(); + const logger = pino(); const noTransactions = '?transactions=false'; const requestDetails = new RequestDetails({ requestId: getRequestId(), ipAddress: '0.0.0.0' }); diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts index b264a40044..3d755aa5e9 100644 --- a/packages/relay/tests/lib/openrpc.spec.ts +++ b/packages/relay/tests/lib/openrpc.spec.ts @@ -19,7 +19,7 @@ */ import { expect } from 'chai'; -import { validateOpenRPCDocument, parseOpenRPCDocument } from '@open-rpc/schema-utils-js'; +import { parseOpenRPCDocument, validateOpenRPCDocument } from '@open-rpc/schema-utils-js'; import Ajv from 'ajv'; @@ -34,7 +34,7 @@ import { BigNumber } from 'bignumber.js'; import { RelayImpl } from '../../src'; import { EthImpl } from '../../src/lib/eth'; -import { SDKClient, MirrorNodeClient } from '../../src/lib/clients'; +import { MirrorNodeClient, SDKClient } from '../../src/lib/clients'; import { RequestDetails } from '../../src/lib/types'; import openRpcSchema from '../../../../docs/openrpc.json'; @@ -70,15 +70,14 @@ import { NOT_FOUND_RES } from './eth/eth-config'; import ClientService from '../../src/lib/services/hapiService/hapiService'; import HbarLimit from '../../src/lib/hbarlimiter'; import { numberTo0x } from '../../src/formatters'; - -dotenv.config({ path: path.resolve(__dirname, '../test.env') }); - import constants from '../../src/lib/constants'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import EventEmitter from 'events'; import Long from 'long'; import { AccountInfo } from '@hashgraph/sdk'; +dotenv.config({ path: path.resolve(__dirname, '../test.env') }); + process.env.npm_package_version = 'relay/0.0.1-SNAPSHOT'; const logger = pino(); diff --git a/packages/relay/tests/lib/precheck.spec.ts b/packages/relay/tests/lib/precheck.spec.ts index f316531c22..f2e84b841f 100644 --- a/packages/relay/tests/lib/precheck.spec.ts +++ b/packages/relay/tests/lib/precheck.spec.ts @@ -21,20 +21,21 @@ import { expect } from 'chai'; import { Registry } from 'prom-client'; import { Hbar, HbarUnit } from '@hashgraph/sdk'; -const registry = new Registry(); - import pino from 'pino'; import { Precheck } from '../../src/lib/precheck'; import { blobVersionedHash, contractAddress1, expectedError, mockData, signTransaction } from '../helpers'; import { MirrorNodeClient } from '../../src/lib/clients'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { Transaction, ethers } from 'ethers'; +import { ethers, Transaction } from 'ethers'; import constants from '../../src/lib/constants'; import { JsonRpcError, predefined } from '../../src'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { ONE_TINYBAR_IN_WEI_HEX } from './eth/eth-config'; import { RequestDetails } from '../../src/lib/types'; + +const registry = new Registry(); + const logger = pino(); const limitOrderPostFix = '?order=desc&limit=1'; diff --git a/packages/relay/tests/lib/sdkClient.spec.ts b/packages/relay/tests/lib/sdkClient.spec.ts index 5aac26123b..93672a057a 100644 --- a/packages/relay/tests/lib/sdkClient.spec.ts +++ b/packages/relay/tests/lib/sdkClient.spec.ts @@ -40,23 +40,23 @@ import MetricService from '../../src/lib/services/metricService/metricService'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { calculateTxRecordChargeAmount, random20BytesAddress } from '../helpers'; import { - Hbar, - Query, - Status, - Client, - FileId, AccountId, - FeeSchedules, - ExchangeRate, - TransactionId, - FileInfoQuery, + Client, ContractCallQuery, EthereumTransaction, - TransactionResponse, + ExchangeRate, + FeeSchedules, FileAppendTransaction, FileCreateTransaction, FileDeleteTransaction, + FileId, + FileInfoQuery, + Hbar, + Query, + Status, + TransactionId, TransactionRecordQuery, + TransactionResponse, } from '@hashgraph/sdk'; import { RequestDetails } from '../../src/lib/types'; diff --git a/packages/relay/tests/lib/services/eth/filter.spec.ts b/packages/relay/tests/lib/services/eth/filter.spec.ts index cce06cdc28..cbfaaa8d90 100644 --- a/packages/relay/tests/lib/services/eth/filter.spec.ts +++ b/packages/relay/tests/lib/services/eth/filter.spec.ts @@ -26,14 +26,14 @@ import { Registry } from 'prom-client'; import { MirrorNodeClient } from '../../../../src/lib/clients'; import pino from 'pino'; import constants from '../../../../src/lib/constants'; -import { FilterService, CommonService } from '../../../../src/lib/services/ethService'; -import { defaultEvmAddress, toHex, defaultBlock, defaultLogTopics, defaultLogs1 } from '../../../helpers'; +import { CommonService, FilterService } from '../../../../src/lib/services/ethService'; +import { defaultBlock, defaultEvmAddress, defaultLogs1, defaultLogTopics, toHex } from '../../../helpers'; import RelayAssertions from '../../../assertions'; import { predefined } from '../../../../src'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; -import * as sinon from 'sinon'; import { RequestDetails } from '../../../../src/lib/types'; import { v4 as uuid } from 'uuid'; + dotenv.config({ path: path.resolve(__dirname, '../test.env') }); const logger = pino(); diff --git a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts index f0df675798..3b22fb313b 100644 --- a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts +++ b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts @@ -33,9 +33,9 @@ import { HbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/ import { EthAddressHbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; import { IPAddressHbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; import { - HbarSpendingPlanNotFoundError, - HbarSpendingPlanNotActiveError, EthAddressHbarSpendingPlanNotFoundError, + HbarSpendingPlanNotActiveError, + HbarSpendingPlanNotFoundError, IPAddressHbarSpendingPlanNotFoundError, } from '../../../../src/lib/db/types/hbarLimiter/errors'; import { RequestDetails } from '../../../../src/lib/types'; diff --git a/packages/relay/tests/lib/services/metricService/metricService.spec.ts b/packages/relay/tests/lib/services/metricService/metricService.spec.ts index 2334dc0d5a..354a791bb5 100644 --- a/packages/relay/tests/lib/services/metricService/metricService.spec.ts +++ b/packages/relay/tests/lib/services/metricService/metricService.spec.ts @@ -34,9 +34,8 @@ import { MirrorNodeClient, SDKClient } from '../../../../src/lib/clients'; import { calculateTxRecordChargeAmount } from '../../../helpers'; import MetricService from '../../../../src/lib/services/metricService/metricService'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; -import { IExecuteQueryEventPayload, IExecuteTransactionEventPayload } from '../../../../src/lib/types'; -import { Hbar, Long, Status, Client, AccountId, TransactionRecord, TransactionRecordQuery } from '@hashgraph/sdk'; -import { RequestDetails } from '../../../../src/lib/types'; +import { IExecuteQueryEventPayload, IExecuteTransactionEventPayload, RequestDetails } from '../../../../src/lib/types'; +import { AccountId, Client, Hbar, Long, Status, TransactionRecord, TransactionRecordQuery } from '@hashgraph/sdk'; config({ path: resolve(__dirname, '../../../test.env') }); const registry = new Registry(); diff --git a/packages/relay/tests/lib/web3.spec.ts b/packages/relay/tests/lib/web3.spec.ts index 3994063bd5..fbef5a873d 100644 --- a/packages/relay/tests/lib/web3.spec.ts +++ b/packages/relay/tests/lib/web3.spec.ts @@ -23,10 +23,10 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; import { Registry } from 'prom-client'; import { RelayImpl } from '../../src/lib/relay'; +import pino from 'pino'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); -import pino from 'pino'; const logger = pino(); const Relay = new RelayImpl(logger, new Registry()); diff --git a/packages/relay/tests/redisInMemoryServer.ts b/packages/relay/tests/redisInMemoryServer.ts index 2f938f6645..8520cc7206 100644 --- a/packages/relay/tests/redisInMemoryServer.ts +++ b/packages/relay/tests/redisInMemoryServer.ts @@ -20,7 +20,6 @@ import { RedisMemoryServer } from 'redis-memory-server'; import { Logger } from 'pino'; -import { RedisInstanceDataT } from 'redis-memory-server/lib/RedisMemoryServer'; export class RedisInMemoryServer { /** diff --git a/packages/server/src/koaJsonRpc/lib/methodConfiguration.ts b/packages/server/src/koaJsonRpc/lib/methodConfiguration.ts index 51f9393252..783730c00c 100644 --- a/packages/server/src/koaJsonRpc/lib/methodConfiguration.ts +++ b/packages/server/src/koaJsonRpc/lib/methodConfiguration.ts @@ -19,11 +19,10 @@ */ import dotenv from 'dotenv'; import path from 'path'; +import CONSTANTS from '../../../../relay/dist/lib/constants'; dotenv.config({ path: path.resolve(__dirname, '../../../../../.env') }); -import CONSTANTS from '../../../../relay/dist/lib/constants'; - const tier1rateLimit = parseInt(process.env.TIER_1_RATE_LIMIT ?? CONSTANTS.DEFAULT_RATE_LIMIT.TIER_1.toString()); const tier2rateLimit = parseInt(process.env.TIER_2_RATE_LIMIT ?? CONSTANTS.DEFAULT_RATE_LIMIT.TIER_2.toString()); const tier3rateLimit = parseInt(process.env.TIER_3_RATE_LIMIT ?? CONSTANTS.DEFAULT_RATE_LIMIT.TIER_3.toString()); diff --git a/packages/server/tests/acceptance/cacheService.spec.ts b/packages/server/tests/acceptance/cacheService.spec.ts index fb148c59da..d6beed0ff7 100644 --- a/packages/server/tests/acceptance/cacheService.spec.ts +++ b/packages/server/tests/acceptance/cacheService.spec.ts @@ -22,6 +22,7 @@ import { expect } from 'chai'; import { CacheService } from '../../../../packages/relay/src/lib/services/cacheService/cacheService'; import { Registry } from 'prom-client'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; + const registry = new Registry(); const DATA_LABEL_PREFIX = 'acceptance-test-'; diff --git a/packages/server/tests/acceptance/conformityTests.spec.ts b/packages/server/tests/acceptance/conformityTests.spec.ts index 11ad52a32c..a940942948 100644 --- a/packages/server/tests/acceptance/conformityTests.spec.ts +++ b/packages/server/tests/acceptance/conformityTests.spec.ts @@ -21,7 +21,6 @@ import fs from 'fs'; import { bytecode } from '../contracts/Basic.json'; import path from 'path'; -const directoryPath = path.resolve(__dirname, '../../../../node_modules/execution-apis/tests'); import axios from 'axios'; import openRpcData from '../../../../docs/openrpc.json'; import Ajv from 'ajv'; @@ -32,6 +31,8 @@ import { config } from 'dotenv'; import WebSocket from 'ws'; import LogsContract from '../contracts/Logs.json'; import CallerContract from '../contracts/Caller.json'; + +const directoryPath = path.resolve(__dirname, '../../../../node_modules/execution-apis/tests'); config(); let currentBlockHash; diff --git a/packages/server/tests/acceptance/equivalence.spec.ts b/packages/server/tests/acceptance/equivalence.spec.ts index 5908f3946c..453eba1de7 100644 --- a/packages/server/tests/acceptance/equivalence.spec.ts +++ b/packages/server/tests/acceptance/equivalence.spec.ts @@ -32,6 +32,7 @@ import { MirrorNodeClient } from '../../../relay/src/lib/clients'; import { hexToASCII } from '../../../relay/src/formatters'; import { AliasAccount } from '../types/AliasAccount'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; + const logger = pino(); enum CallTypes { diff --git a/packages/server/tests/acceptance/hbarLimiter.spec.ts b/packages/server/tests/acceptance/hbarLimiter.spec.ts index 6360ff64a0..ba8e4f0272 100644 --- a/packages/server/tests/acceptance/hbarLimiter.spec.ts +++ b/packages/server/tests/acceptance/hbarLimiter.spec.ts @@ -37,8 +37,6 @@ import EstimateGasContract from '../contracts/EstimateGasContract.json'; import largeContractJson from '../contracts/hbarLimiterContracts/largeSizeContract.json'; import mediumSizeContract from '../contracts/hbarLimiterContracts/mediumSizeContract.json'; import fs from 'fs'; -import { resolve } from 'path'; -import { config } from 'dotenv'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import MirrorClient from '../clients/mirrorClient'; import RelayClient from '../clients/relayClient'; diff --git a/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts b/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts index f046474983..faa16e230c 100644 --- a/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts +++ b/packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts @@ -21,9 +21,10 @@ // external resources import { solidity } from 'ethereum-waffle'; import chai, { expect } from 'chai'; -import { Hbar, ContractId } from '@hashgraph/sdk'; +import { ContractId } from '@hashgraph/sdk'; //Constants are imported with different definitions for better readability in the code. import Constants from '../../helpers/constants'; +import RelayCall from '../../helpers/constants'; import { AliasAccount } from '../../types/AliasAccount'; import { ethers } from 'ethers'; @@ -38,7 +39,6 @@ import TokenManagementContractJson from '../../contracts/TokenManagementContract import { predefined } from '../../../../relay/src/lib/errors/JsonRpcError'; import { Utils } from '../../helpers/utils'; -import RelayCall from '../../helpers/constants'; import { numberTo0x } from '../../../../../packages/relay/src/formatters'; chai.use(solidity); diff --git a/packages/server/tests/acceptance/htsPrecompile/tokenCreate.spec.ts b/packages/server/tests/acceptance/htsPrecompile/tokenCreate.spec.ts index 9d7de2c281..bdca265e11 100644 --- a/packages/server/tests/acceptance/htsPrecompile/tokenCreate.spec.ts +++ b/packages/server/tests/acceptance/htsPrecompile/tokenCreate.spec.ts @@ -22,9 +22,6 @@ import { solidity } from 'ethereum-waffle'; import chai, { expect } from 'chai'; import Constants from '../../helpers/constants'; - -chai.use(solidity); - import { ethers } from 'ethers'; import ERC20MockJson from '../../contracts/ERC20Mock.json'; import ERC721MockJson from '../../contracts/ERC721Mock.json'; @@ -34,6 +31,8 @@ import relayConstants from '@hashgraph/json-rpc-relay/dist/lib/constants'; import Assertions from '../../helpers/assertions'; import { AliasAccount } from '../../types/AliasAccount'; +chai.use(solidity); + /** * Tests for: * allowance diff --git a/packages/server/tests/acceptance/htsPrecompile/tokenManagement.spec.ts b/packages/server/tests/acceptance/htsPrecompile/tokenManagement.spec.ts index e6a879154d..1fd08250da 100644 --- a/packages/server/tests/acceptance/htsPrecompile/tokenManagement.spec.ts +++ b/packages/server/tests/acceptance/htsPrecompile/tokenManagement.spec.ts @@ -22,9 +22,6 @@ import { solidity } from 'ethereum-waffle'; import chai, { expect } from 'chai'; import Constants from '../../helpers/constants'; - -chai.use(solidity); - import Assertions from '../../helpers/assertions'; import { ethers } from 'ethers'; import ERC20MockJson from '../../contracts/ERC20Mock.json'; @@ -33,6 +30,8 @@ import { Utils } from '../../helpers/utils'; import relayConstants from '@hashgraph/json-rpc-relay/dist/lib/constants'; import { AliasAccount } from '../../types/AliasAccount'; +chai.use(solidity); + /** * Tests for: * wipeTokenAccount diff --git a/packages/server/tests/acceptance/index.spec.ts b/packages/server/tests/acceptance/index.spec.ts index f1f90f153a..fa539f7b04 100644 --- a/packages/server/tests/acceptance/index.spec.ts +++ b/packages/server/tests/acceptance/index.spec.ts @@ -48,7 +48,7 @@ import constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; import { Utils } from '../helpers/utils'; import { AliasAccount } from '../types/AliasAccount'; import { setServerTimeout } from '../../src/koaJsonRpc/lib/utils'; -import { Server, IncomingMessage, ServerResponse } from 'http'; +import { Server } from 'http'; chai.use(chaiAsPromised); dotenv.config({ path: path.resolve(__dirname, '../../../../.env') }); diff --git a/packages/server/tests/acceptance/rpc_batch1.spec.ts b/packages/server/tests/acceptance/rpc_batch1.spec.ts index 833128fc0b..b5ba54b44b 100644 --- a/packages/server/tests/acceptance/rpc_batch1.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch1.spec.ts @@ -36,11 +36,11 @@ import basicContract from '../../tests/contracts/Basic.json'; // Errors and constants from local resources import { predefined } from '../../../relay/src/lib/errors/JsonRpcError'; import Constants from '../../../relay/src/lib/constants'; +import constants from '../../../relay/src/lib/constants'; import RelayCalls from '../../tests/helpers/constants'; // Other imports import { numberTo0x, prepend0x } from '../../../../packages/relay/src/formatters'; -import constants from '../../../relay/src/lib/constants'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import RelayClient from '../clients/relayClient'; import ServicesClient from '../clients/servicesClient'; diff --git a/packages/server/tests/clients/mirrorClient.ts b/packages/server/tests/clients/mirrorClient.ts index f0aebccd2d..51565a03a9 100644 --- a/packages/server/tests/clients/mirrorClient.ts +++ b/packages/server/tests/clients/mirrorClient.ts @@ -21,7 +21,6 @@ import Axios, { AxiosInstance } from 'axios'; import axiosRetry from 'axios-retry'; import { Logger } from 'pino'; -import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import { Utils } from '../helpers/utils'; export default class MirrorClient { diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index ea86c8612e..90441ed638 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -19,39 +19,39 @@ */ import { + AccountAllowanceApproveTransaction, AccountBalanceQuery, AccountCreateTransaction, AccountId, AccountInfoQuery, Client, + ContractCreateFlow, ContractExecuteTransaction, ContractFunctionParameters, ContractId, + CustomFee, + CustomFixedFee, + CustomFractionalFee, + CustomRoyaltyFee, + EvmAddress, + FileContentsQuery, + FileUpdateTransaction, Hbar, + Key, KeyList, PrivateKey, Query, TokenAssociateTransaction, TokenCreateTransaction, + TokenGrantKycTransaction, + TokenId, + TokenMintTransaction, + TokenSupplyType, + TokenType, Transaction, + TransactionId, TransactionResponse, TransferTransaction, - ContractCreateFlow, - FileUpdateTransaction, - TransactionId, - AccountAllowanceApproveTransaction, - FileContentsQuery, - TokenType, - TokenSupplyType, - TokenMintTransaction, - TokenGrantKycTransaction, - CustomFixedFee, - CustomFractionalFee, - CustomRoyaltyFee, - EvmAddress, - CustomFee, - TokenId, - Key, } from '@hashgraph/sdk'; import { Logger } from 'pino'; import { ethers, JsonRpcProvider } from 'ethers'; diff --git a/packages/server/tests/integration/koaJsonRpc/rpcError.spec.ts b/packages/server/tests/integration/koaJsonRpc/rpcError.spec.ts index f30b4b127e..21529f4787 100644 --- a/packages/server/tests/integration/koaJsonRpc/rpcError.spec.ts +++ b/packages/server/tests/integration/koaJsonRpc/rpcError.spec.ts @@ -20,16 +20,16 @@ import { expect } from 'chai'; import { - JsonRpcError, - ParseError, + HBARRateLimitExceeded, + InternalError, + InvalidParams, InvalidRequest, + IPRateLimitExceeded, + JsonRpcError, MethodNotFound, - InvalidParams, - InternalError, - Unauthorized, + ParseError, ServerError, - IPRateLimitExceeded, - HBARRateLimitExceeded, + Unauthorized, } from '../../../src/koaJsonRpc/lib/RpcError'; describe('RpcErrors', () => { diff --git a/packages/server/tests/integration/server.spec.ts b/packages/server/tests/integration/server.spec.ts index aecb5235dc..e13f3cc6dd 100644 --- a/packages/server/tests/integration/server.spec.ts +++ b/packages/server/tests/integration/server.spec.ts @@ -25,8 +25,6 @@ import path from 'path'; import sinon from 'sinon'; import { Server } from 'http'; import { GCProfiler } from 'v8'; -dotenv.config({ path: path.resolve(__dirname, './test.env') }); - import Assertions from '../helpers/assertions'; import app from '../../src/server'; import { TracerType, Validator } from '../../src/validator'; @@ -37,6 +35,8 @@ import { predefined } from '@hashgraph/json-rpc-relay'; import { contractAddress1, contractAddress2, contractHash1, contractId1 } from '../../../relay/tests/helpers'; import { MirrorNodeClient } from '@hashgraph/json-rpc-relay/dist/lib/clients'; +dotenv.config({ path: path.resolve(__dirname, './test.env') }); + const MISSING_PARAM_ERROR = 'Missing value for required parameter'; describe('RPC Server', function () { diff --git a/packages/ws-server/src/controllers/index.ts b/packages/ws-server/src/controllers/index.ts index f40205f6e3..480628a25d 100644 --- a/packages/ws-server/src/controllers/index.ts +++ b/packages/ws-server/src/controllers/index.ts @@ -29,8 +29,8 @@ import jsonResp from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcResponse import { resolveParams, validateJsonRpcRequest, verifySupportedMethod } from '../utils/utils'; import { InvalidRequest, - MethodNotFound, IPRateLimitExceeded, + MethodNotFound, } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcError'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import { Logger } from 'pino'; diff --git a/packages/ws-server/src/metrics/connectionLimiter.ts b/packages/ws-server/src/metrics/connectionLimiter.ts index a9b81f875f..2daff2846e 100644 --- a/packages/ws-server/src/metrics/connectionLimiter.ts +++ b/packages/ws-server/src/metrics/connectionLimiter.ts @@ -20,7 +20,7 @@ import { Logger } from 'pino'; import { WS_CONSTANTS } from '../utils/constants'; -import { Gauge, Registry, Counter } from 'prom-client'; +import { Counter, Gauge, Registry } from 'prom-client'; import { WebSocketError } from '@hashgraph/json-rpc-relay'; import RateLimit from '@hashgraph/json-rpc-server/dist/rateLimit'; import constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; diff --git a/packages/ws-server/src/utils/utils.ts b/packages/ws-server/src/utils/utils.ts index 295ac93fff..32759ba090 100644 --- a/packages/ws-server/src/utils/utils.ts +++ b/packages/ws-server/src/utils/utils.ts @@ -21,7 +21,7 @@ import { WS_CONSTANTS } from './constants'; import WsMetricRegistry from '../metrics/wsMetricRegistry'; import ConnectionLimiter from '../metrics/connectionLimiter'; -import { predefined, Relay } from '@hashgraph/json-rpc-relay'; +import { Relay } from '@hashgraph/json-rpc-relay'; import { IJsonRpcRequest } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcRequest'; import { IJsonRpcResponse } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/IJsonRpcResponse'; import { Logger } from 'pino'; diff --git a/packages/ws-server/tests/acceptance/index.spec.ts b/packages/ws-server/tests/acceptance/index.spec.ts index 025b9b21e4..6db9b8abb1 100644 --- a/packages/ws-server/tests/acceptance/index.spec.ts +++ b/packages/ws-server/tests/acceptance/index.spec.ts @@ -22,9 +22,6 @@ import path from 'path'; import pino from 'pino'; import dotenv from 'dotenv'; import chaiAsPromised from 'chai-as-promised'; - -chai.use(chaiAsPromised); - import fs from 'fs'; import { AccountId, Hbar } from '@hashgraph/sdk'; import app from '@hashgraph/json-rpc-server/dist/server'; @@ -39,6 +36,8 @@ import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import { Server } from 'node:http'; import { setServerTimeout } from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/utils'; +chai.use(chaiAsPromised); + dotenv.config({ path: path.resolve(__dirname, '../../../../.env') }); const DOT_ENV = dotenv.parse(fs.readFileSync(path.resolve(__dirname, '../../../../.env'))); diff --git a/packages/ws-server/tests/acceptance/subscribe.spec.ts b/packages/ws-server/tests/acceptance/subscribe.spec.ts index 3c75277e57..b4a5161f20 100644 --- a/packages/ws-server/tests/acceptance/subscribe.spec.ts +++ b/packages/ws-server/tests/acceptance/subscribe.spec.ts @@ -32,6 +32,7 @@ import assertions from '@hashgraph/json-rpc-server/tests/helpers/assertions'; import LogContractJson from '@hashgraph/json-rpc-server/tests/contracts/Logs.json'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import IERC20Json from '@hashgraph/json-rpc-server/tests/contracts/openzeppelin/IERC20.json'; + chai.use(solidity); const WS_RELAY_URL = `${process.env.WS_RELAY_URL}`; diff --git a/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts b/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts index 8f4db50ab0..c14b467df9 100644 --- a/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts +++ b/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts @@ -29,6 +29,7 @@ import Assertions from '@hashgraph/json-rpc-server/tests/helpers/assertions'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; import RelayClient from '@hashgraph/json-rpc-server/tests/clients/relayClient'; + chai.use(solidity); const WS_RELAY_URL = `${process.env.WS_RELAY_URL}`; From b7ab2721458ab7e11b18798e0b6a780c58747fad Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Mon, 7 Oct 2024 11:01:45 -0400 Subject: [PATCH 12/38] feat: enabled configurable server host for the Relay server (#3073) Signed-off-by: Logan Nguyen --- docs/configuration.md | 72 ++++++++++--------- packages/relay/src/lib/constants.ts | 1 + packages/server/src/index.ts | 2 +- .../server/tests/acceptance/index.spec.ts | 1 + .../tests/acceptance/serverConfig.spec.ts | 2 +- .../server/tests/integration/server.spec.ts | 29 ++++++++ packages/ws-server/src/index.ts | 5 +- 7 files changed, 73 insertions(+), 39 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8661f1c9eb..547e58e3ae 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -24,23 +24,24 @@ These properties are noted below and should be custom set per deployment. The following table lists the available properties along with their default values for the [Server package](/packages/server/). Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`. -| Name | Default | Description | -| --------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests. | -| `BATCH_REQUESTS_MAX_SIZE` | "100" | Maximum number of requests allowed in a batch. | -| `CHAIN_ID` | "" | The network chain id. Local and previewnet envs should use `0x12a` (298). Previewnet, Testnet and Mainnet should use `0x129` (297), `0x128` (296) and `0x127` (295) respectively. | -| `HBAR_RATE_LIMIT_DURATION` | "80000" | hbar budget limit duration. This creates a timestamp, which resets all limits, when it's reached. Default is to 80000 (80 seconds). | -| `HBAR_RATE_LIMIT_TINYBAR` | "11_000_000_000" | total hbar budget in tinybars (110 hbars). | -| `HEDERA_NETWORK` | "" | Which network to connect to. Automatically populates the main node & mirror node endpoints. Can be `previewnet`, `testnet`, `mainnet` or a map of network IPs -> node accountIds e.g. `{"127.0.0.1:50211":"0.0.3"}` | -| `INPUT_SIZE_LIMIT` | "1mb" | The [koa-jsonrpc](https://github.com/Bitclimb/koa-jsonrpc) maximum size allowed for requests | -| `MAX_BLOCK_RANGE` | "5" | The maximum block number greater than the mirror node's latest block to query for | -| `OPERATOR_ID_MAIN` | "" | Operator account ID used to pay for transactions. In `S.R.N` format, e.g. `0.0.1001`. | -| `OPERATOR_KEY_FORMAT` | "DER" | Operator private key format. Valid types are `DER`, `HEX_ECDSA`, or `HEX_ED25519` | -| `OPERATOR_KEY_MAIN` | "" | Operator private key used to sign transactions in hex encoded DER format. This may be either an ED22519 private key or an ECDSA private key. The private key must be associated with/used to derive `OPERATOR_ID_MAIN`. | -| `RATE_LIMIT_DISABLED` | "false" | Flag to disable IP based rate limiting. | -| `REQUEST_ID_IS_OPTIONAL` | "" | Flag to set it the JSON RPC request id field in the body should be optional. Note, this breaks the API spec and is not advised and is provided for test purposes only where some wallets may be non compliant | -| `SERVER_PORT` | "7546" | The RPC server port number to listen for requests on. Currently a static value defaulting to 7546. See [#955](https://github.com/hashgraph/hedera-json-rpc-relay/issues/955) | -| `SERVER_REQUEST_TIMEOUT_MS` | "60000" | The time of inactivity allowed before a timeout is triggered and the socket is closed. See [NodeJs Server Timeout](https://nodejs.org/api/http.html#serversettimeoutmsecs-callback) | +| Name | Default | Description | +| --------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests. | +| `BATCH_REQUESTS_MAX_SIZE` | "100" | Maximum number of requests allowed in a batch. | +| `CHAIN_ID` | "" | The network chain id. Local and previewnet envs should use `0x12a` (298). Previewnet, Testnet and Mainnet should use `0x129` (297), `0x128` (296) and `0x127` (295) respectively. | +| `HBAR_RATE_LIMIT_DURATION` | "80000" | hbar budget limit duration. This creates a timestamp, which resets all limits, when it's reached. Default is to 80000 (80 seconds). | +| `HBAR_RATE_LIMIT_TINYBAR` | "11_000_000_000" | total hbar budget in tinybars (110 hbars). | +| `HEDERA_NETWORK` | "" | Which network to connect to. Automatically populates the main node & mirror node endpoints. Can be `previewnet`, `testnet`, `mainnet` or a map of network IPs -> node accountIds e.g. `{"127.0.0.1:50211":"0.0.3"}` | +| `INPUT_SIZE_LIMIT` | "1mb" | The [koa-jsonrpc](https://github.com/Bitclimb/koa-jsonrpc) maximum size allowed for requests | +| `MAX_BLOCK_RANGE` | "5" | The maximum block number greater than the mirror node's latest block to query for | +| `OPERATOR_ID_MAIN` | "" | Operator account ID used to pay for transactions. In `S.R.N` format, e.g. `0.0.1001`. | +| `OPERATOR_KEY_FORMAT` | "DER" | Operator private key format. Valid types are `DER`, `HEX_ECDSA`, or `HEX_ED25519` | +| `OPERATOR_KEY_MAIN` | "" | Operator private key used to sign transactions in hex encoded DER format. This may be either an ED22519 private key or an ECDSA private key. The private key must be associated with/used to derive `OPERATOR_ID_MAIN`. | +| `RATE_LIMIT_DISABLED` | "false" | Flag to disable IP based rate limiting. | +| `REQUEST_ID_IS_OPTIONAL` | "" | Flag to set it the JSON RPC request id field in the body should be optional. Note, this breaks the API spec and is not advised and is provided for test purposes only where some wallets may be non compliant | +| `SERVER_PORT` | "7546" | The RPC server port number to listen for requests on. Currently a static value defaulting to 7546. See [#955](https://github.com/hashgraph/hedera-json-rpc-relay/issues/955) | +| `SERVER_HOST` | undefined | The hostname or IP address on which the server listens for incoming connections. If `SERVER_HOST` is not configured or left undefined (same as `0.0.0.0`), it permits external connections by default, offering more flexibility. | +| `SERVER_REQUEST_TIMEOUT_MS` | "60000" | The time of inactivity allowed before a timeout is triggered and the socket is closed. See [NodeJs Server Timeout](https://nodejs.org/api/http.html#serversettimeoutmsecs-callback) | ## Relay @@ -71,7 +72,7 @@ Unless you need to set a non-default value, it is recommended to only populate o | `HAPI_CLIENT_DURATION_RESET` | "3600000" | Time until client reinitialization. (ms) | | `HAPI_CLIENT_ERROR_RESET` | [21, 50] | Array of status codes, which when encountered will trigger a reinitialization. Status codes are availble [here](https://github.com/hashgraph/hedera-protobufs/blob/main/services/response_code.proto). | | `HAPI_CLIENT_TRANSACTION_RESET` | "50" | Number of transaction executions, until client reinitialization. | -| `TEST_INITIAL_ACCOUNT_STARTING_BALANCE` | "2000" | The number of HBars to allocate to the initial account in acceptance test runs. This account is responsible for the gas payment of tests within the suite run session and needs to be adequately funded. | +| `TEST_INITIAL_ACCOUNT_STARTING_BALANCE` | "2000" | The number of HBars to allocate to the initial account in acceptance test runs. This account is responsible for the gas payment of tests within the suite run session and needs to be adequately funded. | | `LIMIT_DURATION` | "60000" | The maximum duration in ms applied to IP-method based rate limits. | | `MIRROR_NODE_CONTRACT_RESULTS_PG_MAX` | "25" | The maximum number of pages to be requested for contract results from the mirror node. | | `MIRROR_NODE_CONTRACT_RESULTS_LOGS_PG_MAX` | "200" | The maximum number of pages to be requested for contract results logs from the mirror node. (each page will contain a max of 100 results) | @@ -108,23 +109,24 @@ Unless you need to set a non-default value, it is recommended to only populate o The following table lists the available properties along with their default values for the [Ws-server package](/packages/ws-server/). Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`. -| Name | Default | Description | -| ------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `WS_BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests on the websocket server. | -| `WS_BATCH_REQUESTS_MAX_SIZE` | "20" | Maximum number of requests allowed in a batch on websocket server. | -| `SUBSCRIPTIONS_ENABLED` | "false" | If enabled eth_subscribe will be enabled using WebSockets. | -| `WS_MAX_INACTIVITY_TTL` | "300000" | Time in ms that the web socket connection is allowed to stay open without any messages sent or received, currently 5 minutes. | -| `WS_CONNECTION_LIMIT` | "10" | Maximum amount of concurrent web socket connections allowed. | -| `WS_POLLING_INTERVAL` | "500" | Time in ms in between each poll to mirror node while there are subscriptions. | -| `WEB_SOCKET_PORT` | "8546" | Port for the web socket connections | -| `WEB_SOCKET_HTTP_PORT` | "8547" | Port for standard http server, used for metrics and health status endpoints | -| `WS_SUBSCRIPTION_LIMIT` | "10" | Maximum amount of subscriptions per single connection | -| `WS_CONNECTION_LIMIT_PER_IP` | "10" | Maximum amount of connections from a single IP address | -| `WS_MULTIPLE_ADDRESSES_ENABLED` | "false" | If enabled eth_subscribe will allow subscription to multiple contract address. | -| `WS_CACHE_TTL` | "20000" | The time to live for cached entries. | -| `WS_NEW_HEADS_ENABLED`. | "true" | Enables subscriptions for the latest blocks, `newHeads`. | -| `WS_PING_INTERVAL` | "100000" | Interval between ping messages. Set to `0` to disable pinger. | -| `WS_SAME_SUB_FOR_SAME_EVENT` | "true" | The relay will return the same subscription ID when a client subscribes to the same event multiple times using a single connection. When set to false, the relay will always create a new subscription ID for each `eth_subscribe' request. | +| Name | Default | Description | +| ------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `WS_BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests on the websocket server. | +| `WS_BATCH_REQUESTS_MAX_SIZE` | "20" | Maximum number of requests allowed in a batch on websocket server. | +| `SUBSCRIPTIONS_ENABLED` | "false" | If enabled eth_subscribe will be enabled using WebSockets. | +| `WS_MAX_INACTIVITY_TTL` | "300000" | Time in ms that the web socket connection is allowed to stay open without any messages sent or received, currently 5 minutes. | +| `WS_CONNECTION_LIMIT` | "10" | Maximum amount of concurrent web socket connections allowed. | +| `WS_POLLING_INTERVAL` | "500" | Time in ms in between each poll to mirror node while there are subscriptions. | +| `WEB_SOCKET_PORT` | "8546" | Port for the web socket connections | +| `WEB_SOCKET_HOST` | "localhost" | The hostname or IP address on which the server will listen for incoming connections. | +| `WEB_SOCKET_HTTP_PORT` | "8547" | Port for standard http server, used for metrics and health status endpoints | +| `WS_SUBSCRIPTION_LIMIT` | "10" | Maximum amount of subscriptions per single connection | +| `WS_CONNECTION_LIMIT_PER_IP` | "10" | Maximum amount of connections from a single IP address | +| `WS_MULTIPLE_ADDRESSES_ENABLED` | "false" | If enabled eth_subscribe will allow subscription to multiple contract address. | +| `WS_CACHE_TTL` | "20000" | The time to live for cached entries. | +| `WS_NEW_HEADS_ENABLED`. | "true" | Enables subscriptions for the latest blocks, `newHeads`. | +| `WS_PING_INTERVAL` | "100000" | Interval between ping messages. Set to `0` to disable pinger. | +| `WS_SAME_SUB_FOR_SAME_EVENT` | "true" | The relay will return the same subscription ID when a client subscribes to the same event multiple times using a single connection. When set to false, the relay will always create a new subscription ID for each `eth_subscribe' request. | ## Sample for connecting to Hedera Environments diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index 95f0d9f4a2..d43fa286df 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -143,6 +143,7 @@ export default { WEB_SOCKET_HTTP_PORT: process.env.WEB_SOCKET_HTTP_PORT || 8547, RELAY_PORT: process.env.SERVER_PORT || 7546, + RELAY_HOST: process.env.SERVER_HOST || 'localhost', FUNCTION_SELECTOR_CHAR_LENGTH: 10, MIRROR_NODE_RETRY_DELAY_DEFAULT: 2000, diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index dd93cc2d06..55a18cc8be 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -22,7 +22,7 @@ import app from './server'; import { setServerTimeout } from './koaJsonRpc/lib/utils'; // Import the 'setServerTimeout' function from the correct location async function main() { - const server = await app.listen({ port: process.env.SERVER_PORT || 7546 }); + const server = app.listen({ port: process.env.SERVER_PORT || 7546, host: process.env.SERVER_HOST }); // set request timeout to ensure sockets are closed after specified time of inactivity setServerTimeout(server); diff --git a/packages/server/tests/acceptance/index.spec.ts b/packages/server/tests/acceptance/index.spec.ts index fa539f7b04..fe74a3edc0 100644 --- a/packages/server/tests/acceptance/index.spec.ts +++ b/packages/server/tests/acceptance/index.spec.ts @@ -210,6 +210,7 @@ describe('RPC Server Acceptance Tests', function () { // start local relay, relay instance in local should not be running logger.info(`Start relay on port ${constants.RELAY_PORT}`); + logger.info(`Start relay on host ${constants.RELAY_HOST}`); const relayServer = app.listen({ port: constants.RELAY_PORT }); global.relayServer = relayServer; setServerTimeout(relayServer); diff --git a/packages/server/tests/acceptance/serverConfig.spec.ts b/packages/server/tests/acceptance/serverConfig.spec.ts index 3edc034b42..ff7197d266 100644 --- a/packages/server/tests/acceptance/serverConfig.spec.ts +++ b/packages/server/tests/acceptance/serverConfig.spec.ts @@ -24,7 +24,7 @@ describe('@server-config Server Configuration Options Coverage', function () { describe('Koa Server Timeout', () => { it('should timeout a request after the specified time', async () => { const requestTimeoutMs: number = parseInt(process.env.SERVER_REQUEST_TIMEOUT_MS || '3000'); - const host = 'localhost'; + const host = process.env.SERVER_HOST || 'localhost'; const port = parseInt(process.env.SERVER_PORT || '7546'); const method = 'eth_blockNumber'; const params: any[] = []; diff --git a/packages/server/tests/integration/server.spec.ts b/packages/server/tests/integration/server.spec.ts index e13f3cc6dd..38e4894ff2 100644 --- a/packages/server/tests/integration/server.spec.ts +++ b/packages/server/tests/integration/server.spec.ts @@ -63,6 +63,35 @@ describe('RPC Server', function () { this.timeout(5000); + it('should verify that the server is running with the correct host and port', async function () { + const CUSTOMIZE_PORT = '7545'; + const CUSTOMIZE_HOST = '127.0.0.1'; + const configuredServer = app.listen({ port: CUSTOMIZE_PORT, host: CUSTOMIZE_HOST }); + + return new Promise((resolve, reject) => { + configuredServer.on('listening', () => { + const address = configuredServer.address(); + + try { + expect(address).to.not.be.null; + if (address && typeof address === 'object') { + expect(address.address).to.equal(CUSTOMIZE_HOST); + expect(address.port.toString()).to.equal(CUSTOMIZE_PORT); + } else { + throw new Error('Server address is not an object'); + } + configuredServer.close(() => resolve()); + } catch (error) { + configuredServer.close(() => reject(error)); + } + }); + + configuredServer.on('error', (error) => { + reject(error); + }); + }); + }); + it('should execute "eth_chainId"', async function () { const res = await testClient.post('/', { id: '2', diff --git a/packages/ws-server/src/index.ts b/packages/ws-server/src/index.ts index 6f5e70ab73..b4566e9692 100644 --- a/packages/ws-server/src/index.ts +++ b/packages/ws-server/src/index.ts @@ -22,8 +22,9 @@ import { app, httpApp } from './webSocketServer'; import constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; async function main() { - app.listen({ port: constants.WEB_SOCKET_PORT }); - httpApp.listen({ port: constants.WEB_SOCKET_HTTP_PORT }); + const host = process.env.SERVER_HOST; + app.listen({ port: constants.WEB_SOCKET_PORT, host }); + httpApp.listen({ port: constants.WEB_SOCKET_HTTP_PORT, host }); } (async () => { From 4a69be98b07beaf367bca97bfcaed740735c8c49 Mon Sep 17 00:00:00 2001 From: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:31:48 +0300 Subject: [PATCH 13/38] refactor: configurations of `HbarLimitService` (#3014) * chore: refactor configurations of `HbarLimitService` Signed-off-by: Victor Yanev * chore: fix ts errors Signed-off-by: Victor Yanev * chore: revert formatting of table Signed-off-by: Victor Yanev * refactor: configurations of `HbarLimitService` Signed-off-by: Victor Yanev * Merge branch 'main' into 2970-Note-what-configurations-are-used Signed-off-by: Victor Yanev # Conflicts: # packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts # packages/relay/src/lib/services/hbarLimitService/index.ts # packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts * docs: add configuration subsections to table of contents Signed-off-by: Victor Yanev * docs: make descriptions of configurations consistent Signed-off-by: Victor Yanev * chore: revert changes to localLRUCache.ts and redisCache.ts Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: moves TODO comment to constants.ts Signed-off-by: Victor Yanev * chore: optimize resetDate calculation Signed-off-by: Victor Yanev * chore: remove unused imports Signed-off-by: Victor Yanev * Merge branch 'main' into 2970-Note-what-configurations-are-used Signed-off-by: Victor Yanev # Conflicts: # packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts # packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts # packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts # packages/relay/src/lib/services/hbarLimitService/index.ts # packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts # packages/relay/tests/lib/sdkClient.spec.ts # packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts * docs: update docs Signed-off-by: Victor Yanev * docs: reformat table Signed-off-by: Victor Yanev * fix: hbarSpendingPlanRepository.spec.ts after resolving conflicts from main branch Signed-off-by: Victor Yanev * fix: metricService.spec.ts Signed-off-by: Victor Yanev * chore: fix wrong value for limit duration in `hbar-limiter.md` Co-authored-by: Logan Nguyen Signed-off-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> * chore: fix jsdocs Co-authored-by: Logan Nguyen Signed-off-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> * fix: hbarSpendingPlanRepository.ts Signed-off-by: Victor Yanev * fix: ethAddressHbarSpendingPlanRepository.ts Signed-off-by: Victor Yanev * fix: ipAddressHbarSpendingPlanRepository.ts Signed-off-by: Victor Yanev * fix: jsdocs in hbarLimitService/index.ts Signed-off-by: Victor Yanev * fix: do not reset metrics Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev --------- Signed-off-by: Victor Yanev Signed-off-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Co-authored-by: Logan Nguyen --- docs/configuration.md | 11 +- docs/design/hbar-limiter.md | 136 +++++++++- packages/relay/src/lib/clients/sdkClient.ts | 1 - packages/relay/src/lib/constants.ts | 10 +- .../entities/hbarLimiter/hbarSpendingPlan.ts | 4 +- .../ethAddressHbarSpendingPlanRepository.ts | 7 +- .../hbarLimiter/hbarSpendingPlanRepository.ts | 74 +++--- .../ipAddressHbarSpendingPlanRepository.ts | 8 +- .../db/types/hbarLimiter/hbarSpendingPlan.ts | 2 +- packages/relay/src/lib/relay.ts | 2 +- .../lib/services/hbarLimitService/index.ts | 128 +++++----- .../tests/lib/clients/redisCache.spec.ts | 1 - packages/relay/tests/lib/eth/eth-helpers.ts | 6 +- .../relay/tests/lib/ethGetBlockBy.spec.ts | 2 +- packages/relay/tests/lib/openrpc.spec.ts | 2 +- packages/relay/tests/lib/poller.spec.ts | 240 +++++++++--------- ...hAddressHbarSpendingPlanRepository.spec.ts | 60 +++-- .../hbarSpendingPlanRepository.spec.ts | 197 +++++++------- ...pAddressHbarSpendingPlanRepository.spec.ts | 40 ++- packages/relay/tests/lib/sdkClient.spec.ts | 2 +- .../hbarLimitService/hbarLimitService.spec.ts | 203 ++++++++------- .../metricService/metricService.spec.ts | 2 +- 22 files changed, 668 insertions(+), 470 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 547e58e3ae..8d5d655067 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -29,8 +29,6 @@ Unless you need to set a non-default value, it is recommended to only populate o | `BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests. | | `BATCH_REQUESTS_MAX_SIZE` | "100" | Maximum number of requests allowed in a batch. | | `CHAIN_ID` | "" | The network chain id. Local and previewnet envs should use `0x12a` (298). Previewnet, Testnet and Mainnet should use `0x129` (297), `0x128` (296) and `0x127` (295) respectively. | -| `HBAR_RATE_LIMIT_DURATION` | "80000" | hbar budget limit duration. This creates a timestamp, which resets all limits, when it's reached. Default is to 80000 (80 seconds). | -| `HBAR_RATE_LIMIT_TINYBAR` | "11_000_000_000" | total hbar budget in tinybars (110 hbars). | | `HEDERA_NETWORK` | "" | Which network to connect to. Automatically populates the main node & mirror node endpoints. Can be `previewnet`, `testnet`, `mainnet` or a map of network IPs -> node accountIds e.g. `{"127.0.0.1:50211":"0.0.3"}` | | `INPUT_SIZE_LIMIT` | "1mb" | The [koa-jsonrpc](https://github.com/Bitclimb/koa-jsonrpc) maximum size allowed for requests | | `MAX_BLOCK_RANGE` | "5" | The maximum block number greater than the mirror node's latest block to query for | @@ -49,7 +47,7 @@ The following table lists the available properties along with their default valu Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`. | Name | Default | Description | -| ------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +|---------------------------------------------|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `CACHE_MAX` | "1000" | The maximum number (or size) of items that remain in the cache (assuming no TTL pruning or explicit deletions). | | `CACHE_TTL` | "3_600_000" | Max time to live in ms, for items before they are considered stale. Default is one hour in milliseconds | | `CLIENT_TRANSPORT_SECURITY` | "false" | Flag to enable or disable TLS for both networks. | @@ -69,6 +67,11 @@ Unless you need to set a non-default value, it is recommended to only populate o | `ETH_FEE_HISTORY_FIXED` | "true" | Flag to set if eth_feeHistory should return a fixed fee for the set of results. | | `GAS_PRICE_PERCENTAGE_BUFFER` | "0" | The additional buffer that adds a percentage on top of the calculated network gasPrice. This may be used by operators to reduce the chances of `INSUFFICIENT_TX_FEE` errors experienced by users caused by minor fluctuations in the exchange rate. | | `GAS_PRICE_TINY_BAR_BUFFER` | "10000000000" | The additional buffer range to allow during a relay precheck of gas price. This supports slight fluctuations in network gasprice calculations. | +| `HBAR_RATE_LIMIT_DURATION` | "80000" | HBar budget limit duration. This creates a timestamp, which resets all limits, when it's reached. Default is to 80000 (80 seconds). | +| `HBAR_RATE_LIMIT_TINYBAR` | "11_000_000_000" | Total hbar budget in tinybars (110 hbars per 80 seconds). | +| `HBAR_RATE_LIMIT_BASIC` | "92_592_592" | Individual limit in tinybars for spending plans with a BASIC tier. (equivalent of 1_000 HBARs per day for a duration of 80 seconds) | +| `HBAR_RATE_LIMIT_EXTENDED` | "925_925_925" | Individual limit in tinybars for spending plans with a EXTENDED tier. (equivalent of 10_000 HBARs per day for a duration of 80 seconds) | +| `HBAR_RATE_LIMIT_PRIVILEGED` | "1_851_851_850" | Individual limit in tinybars for spending plans with a PRIVILEGED tier. (equivalent of 20_000 HBARs per day for a duration of 80 seconds) | | `HAPI_CLIENT_DURATION_RESET` | "3600000" | Time until client reinitialization. (ms) | | `HAPI_CLIENT_ERROR_RESET` | [21, 50] | Array of status codes, which when encountered will trigger a reinitialization. Status codes are availble [here](https://github.com/hashgraph/hedera-protobufs/blob/main/services/response_code.proto). | | `HAPI_CLIENT_TRANSACTION_RESET` | "50" | Number of transaction executions, until client reinitialization. | @@ -126,7 +129,7 @@ Unless you need to set a non-default value, it is recommended to only populate o | `WS_CACHE_TTL` | "20000" | The time to live for cached entries. | | `WS_NEW_HEADS_ENABLED`. | "true" | Enables subscriptions for the latest blocks, `newHeads`. | | `WS_PING_INTERVAL` | "100000" | Interval between ping messages. Set to `0` to disable pinger. | -| `WS_SAME_SUB_FOR_SAME_EVENT` | "true" | The relay will return the same subscription ID when a client subscribes to the same event multiple times using a single connection. When set to false, the relay will always create a new subscription ID for each `eth_subscribe' request. | +| `WS_SAME_SUB_FOR_SAME_EVENT` | "true" | The relay will return the same subscription ID when a client subscribes to the same event multiple times using a single connection. When set to false, the relay will always create a new subscription ID for each `eth_subscribe` request. | ## Sample for connecting to Hedera Environments diff --git a/docs/design/hbar-limiter.md b/docs/design/hbar-limiter.md index 44864ddbc2..9eedc48242 100644 --- a/docs/design/hbar-limiter.md +++ b/docs/design/hbar-limiter.md @@ -19,6 +19,10 @@ - [HBar Allocation Strategy](#hbar-allocation-strategy) - [Metrics to Track](#metrics-to-track) - [Allocation Algorithm](#allocation-algorithm) + - [Configurations](#configurations) + - [Pre-populating the Cache with Spending Plans for Supported Projects and Partner Projects](#pre-populating-the-cache-with-spending-plans-for-supported-projects-and-partner-projects) + - [Spending Limits of Different Tiers](#spending-limits-of-different-tiers) + - [Total Budget and Limit Duration](#total-budget-and-limit-duration) - [Additional Considerations](#additional-considerations) - [Performance](#performance) - [Monitoring and logging](#monitoring-and-logging) @@ -77,20 +81,43 @@ The purpose of the HBar Limiter is to track and control the spending of HBars in The HBar limiter will be implemented as a separate service, used by other services/classes that need it. It will have two main purposes - to capture the gas fees for different operation and to check if an operation needs to be paused, due to an exceeded HBar limit. +### General Users (BASIC tier): + +**NOTE:** Each general user will have a unique spending plan, linked both to their ETH and IP addresses. Each new user will be automatically assigned a BASIC spending plan when they send their first transaction and this plan will remain linked to them for any subsequent requests. + +```mermaid +flowchart TD + A[User] -->|sends transaction| B[JSON-RPC Relay] + B --> C[Estimate fees which will be paid by the relay operator] + C --> D{HBAR Limiter} + D -->|new user, i.e., who is not linked to a spending plan| E[Create a new BASIC spending plan] + E --> F[Link user's ETH & IP addresses to plan] + D -->|existing user, i.e., who is linked to a spending plan| G[Retrieve spending plan linked to user] + F --> H{Plan has enough balance to cover fees} + G --> H + H --> |no| I[Limit request] + H --> |yes| J[Execute transaction] + J --> K[Capture all fees the operator has been charged during execution] + K --> L[Update the spending plan's remaining balance] +``` + +### Supported Projects (EXTENDED tier) and Trusted Partners (PRIVILEGED tier): + +**NOTE:** There will be one spending plan per project/partner with a total spending limit, shared amongst a group of users (IP and ETH addresses) linked to that plan. This means that they will share a common total spending limit for the project/partner. + +All users associated with a project/partner will be pre-configured in the relay as shown in the + ```mermaid flowchart TD A[User] -->|sends transaction| B[JSON-RPC Relay] - B --> C{HbarLimitService} - C -->|new user, i.e., who is not linked to a spending plan| D[Create a BASIC HbarSpendingPlan] - D --> E[Link user's ETH & IP addresses to plan] - E --> F[Estimate fees of any additional HFS transactions which need to be executed by the operator] - C -->|existing user, i.e., who is linked to a spending plan| G[Retrieve HbarSpendingPlan linked to user] - G --> F - F --> H{The plan exceeds its daily HBar allowance?} - H --> |yes| I[Limit request] - H --> |no| J[Execute transaction] - J --> K[Capture fees the operator has been charged] - K --> L[Update spending plan] + B --> C[Estimate fees which will be paid by the relay operator] + C --> D{HBAR Limiter} + D --> E[Retrieve spending plan linked to user's ETH and/or IP address] + E --> F{Plan has enough balance to cover fees} + F --> |no| G[Limit request] + F --> |yes| H[Execute transaction] + H --> I[Capture all fees the operator has been charged during execution] + I --> J[Update the spending plan's remaining balance] ``` ### Class Diagram @@ -149,7 +176,7 @@ classDiagram -createdAt: Date -active: boolean -spendingHistory: HbarSpendingRecord[] - -spentToday: number + -amountSpent: number } class HbarSpendingRecord { @@ -188,8 +215,8 @@ classDiagram +checkExistsAndActive(id: string): Promise +getSpendingHistory(id: string): Promise +addAmountToSpendingHistory(id: string, amount: number): Promise - +getSpentToday(id: string): Promise - +addAmountToSpentToday(id: string, amount: number): Promise + +getAmountSpent(id: string): Promise + +addToAmountSpent(id: string, amount: number): Promise } class EthAddressHbarSpendingPlanRepository { @@ -271,6 +298,87 @@ c. Current day's usage (increase limits if overall usage is low) - Keep a small portion of the daily budget (e.g., 10%) as a reserve - Use this to accommodate unexpected spikes or high-priority users +## Configurations + +### Pre-populating the Cache with Spending Plans for Supported Projects and Partner Projects + +The following configurations will be used to automatically populate the cache with `HbarSpendingPlan`, `EthAddressHbarSpendingPlan`, and `IPAddressHbarSpendingPlan` entries for the outlined supported projects and partner projects on every start-up of the relay. + +All other users (ETH and IP addresses which are not specified in the configuration file) will be treated as "general users" and will be assigned a basic `HbarSpendingPlan` on their first request and their ETH address and IP address will be linked to that plan for all subsequent requests. + +```json +[ + { + "name": "partner name", + "ethAddresses": ["0x123", "0x124"], + "ipAddresses": ["127.0.0.1", "128.0.0.1"], + "subscriptionType": "PRIVILEGED" + }, + { + "name": "some other partner that has given us only eth addresses", + "ethAddresses": ["0x125", "0x126"], + "subscriptionType": "PRIVILEGED" + }, + { + "name": "supported project name", + "ethAddresses": ["0x127", "0x128"], + "ipAddresses": ["129.0.0.1", "130.0.0.1"], + "subscriptionType": "EXTENDED" + }, + { + "name": "some other supported project that has given us only ip addresses", + "ipAddresses": ["131.0.0.1", "132.0.0.1"], + "subscriptionType": "EXTENDED" + } +] +``` + +On every start-up, the relay will check if these entries are already populated in the cache. If not, it will populate them accordingly. + +The JSON file can also be updated over time to add new supported projects or partner projects, and it will populate only the new entries on the next start-up. + +```json +[ + ..., + { + "name": "new partner name", + "ethAddresses": ["0x129", "0x130"], + "ipAddresses": ["133.0.0.1"], + "subscriptionType": "PRIVILEGED" + } +] +``` + +### Spending Limits of Different Tiers + +The spending limits for different tiers are defined as environment variables: +- `HBAR_RATE_LIMIT_BASIC`: The spending limit (in tinybars) for general users (tier 3) +- `HBAR_RATE_LIMIT_EXTENDED`: The spending limit (in tinybars) for supported projects (tier 2) +- `HBAR_RATE_LIMIT_PRIVILEGED`: The spending limit (in tinybars) for trusted partners (tier 1) + +Example configuration for tiered spending limits: +```dotenv +HBAR_RATE_LIMIT_BASIC=92592592 +HBAR_RATE_LIMIT_EXTENDED=925925925 +HBAR_RATE_LIMIT_PRIVILEGED=1851851850 +``` + +### Total Budget and Limit Duration + +The total budget and the limit duration are defined as environment variables: +- `HBAR_RATE_LIMIT_DURATION`: The time window (in milliseconds) for which both the total budget and the spending limits are applicable. + - On initialization of `HbarLimitService`, a reset timestamp is calculated by adding the `HBAR_RATE_LIMIT_DURATION` to the current timestamp. + - The total budget and spending limits are reset when the current timestamp exceeds the reset timestamp. +- `HBAR_RATE_LIMIT_TINYBAR`: The ceiling (in tinybars) on the total amount of HBARs that can be spent in the limit duration. + - This is the largest bucket from which others pull from. + - If the total amount spent exceeds this limit, all spending is paused until the next reset. + +Example configuration for a total budget of 110 HBARs (11_000_000_000 tinybars) per 80 seconds: +```dotenv +HBAR_RATE_LIMIT_TINYBAR=11000000000 +HBAR_RATE_LIMIT_DURATION=80000 +``` + ## Additional Considerations ### Performance diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 063e4bcf96..cdbb3a7f21 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -397,7 +397,6 @@ export class SDKClient { * @param {string} originalCallerAddress - The address of the original caller making the request. * @param {number} networkGasPriceInWeiBars - The predefined gas price of the network in weibar. * @param {number} currentNetworkExchangeRateInCents - The exchange rate in cents of the current network. - * @param {string} requestId - The unique identifier for the request. * @returns {Promise<{ txResponse: TransactionResponse; fileId: FileId | null }>} * @throws {SDKClientError} Throws an error if no file ID is created or if the preemptive fee check fails. */ diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index d43fa286df..e86cf75739 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -18,6 +18,8 @@ * */ +import { BigNumber } from 'bignumber.js'; + enum CACHE_KEY { ACCOUNT = 'account', ETH_BLOCK_NUMBER = 'eth_block_number', @@ -136,8 +138,12 @@ export default { DURATION: 60000, }, - HBAR_RATE_LIMIT_DURATION: parseInt(process.env.HBAR_RATE_LIMIT_DURATION || '80000'), - HBAR_RATE_LIMIT_TINYBAR: parseInt(process.env.HBAR_RATE_LIMIT_TINYBAR || '11000000000'), + // TODO: Replace with actual values - https://github.com/hashgraph/hedera-json-rpc-relay/issues/2895 + HBAR_RATE_LIMIT_DURATION: parseInt(process.env.HBAR_RATE_LIMIT_DURATION || '80000'), // 80 seconds + HBAR_RATE_LIMIT_TOTAL: BigNumber(process.env.HBAR_RATE_LIMIT_TINYBAR || '11000000000'), // 110 HBARs per 80 seconds + HBAR_RATE_LIMIT_BASIC: BigNumber(process.env.HBAR_DAILY_LIMIT_BASIC || '92592592'), // Equivalent of 1000 HBARs per day + HBAR_RATE_LIMIT_EXTENDED: BigNumber(process.env.HBAR_DAILY_LIMIT_EXTENDED || '925925925'), // Equivalent of 10000 HBARs per day + HBAR_RATE_LIMIT_PRIVILEGED: BigNumber(process.env.HBAR_DAILY_LIMIT_PRIVILEGED || '1851851850'), // Equivalent of 20000 HBARs per day GAS_PRICE_TINY_BAR_BUFFER: parseInt(process.env.GAS_PRICE_TINY_BAR_BUFFER || '10000000000'), WEB_SOCKET_PORT: process.env.WEB_SOCKET_PORT || 8546, WEB_SOCKET_HTTP_PORT: process.env.WEB_SOCKET_HTTP_PORT || 8547, diff --git a/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts b/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts index 3ec2632e73..e1c72c5c60 100644 --- a/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts +++ b/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts @@ -28,7 +28,7 @@ export class HbarSpendingPlan implements IDetailedHbarSpendingPlan { createdAt: Date; active: boolean; spendingHistory: HbarSpendingRecord[]; - spentToday: number; + amountSpent: number; constructor(data: IDetailedHbarSpendingPlan) { this.id = data.id; @@ -36,6 +36,6 @@ export class HbarSpendingPlan implements IDetailedHbarSpendingPlan { this.createdAt = new Date(data.createdAt); this.active = data.active; this.spendingHistory = data.spendingHistory?.map((spending) => new HbarSpendingRecord(spending)) || []; - this.spentToday = data.spentToday ?? 0; + this.amountSpent = data.amountSpent ?? 0; } } diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts index c7d39bac5e..00022c1625 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts @@ -27,7 +27,7 @@ import { RequestDetails } from '../../../types'; export class EthAddressHbarSpendingPlanRepository { private readonly collectionKey = 'ethAddressHbarSpendingPlan'; - private readonly threeMonthsInMillis = 90 * 24 * 60 * 60 * 1000; + private readonly oneDayInMillis = 24 * 60 * 60 * 1000; /** * The cache service used for storing data. @@ -68,11 +68,12 @@ export class EthAddressHbarSpendingPlanRepository { * * @param {IEthAddressHbarSpendingPlan} addressPlan - The plan to save. * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @param {number} ttl - The time-to-live for the cache entry. * @returns {Promise} - A promise that resolves when the ETH address is linked to the plan. */ - async save(addressPlan: IEthAddressHbarSpendingPlan, requestDetails: RequestDetails): Promise { + async save(addressPlan: IEthAddressHbarSpendingPlan, requestDetails: RequestDetails, ttl: number): Promise { const key = this.getKey(addressPlan.ethAddress); - await this.cache.set(key, addressPlan, 'save', requestDetails, this.threeMonthsInMillis); + await this.cache.set(key, addressPlan, 'save', requestDetails, ttl); this.logger.trace(`Saved EthAddressHbarSpendingPlan with address ${addressPlan.ethAddress}`); } diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts index 13a9a46160..10b16f3cd2 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts @@ -31,8 +31,6 @@ import { RequestDetails } from '../../../types'; export class HbarSpendingPlanRepository { private readonly collectionKey = 'hbarSpendingPlan'; - private readonly oneDayInMillis = 24 * 60 * 60 * 1000; - private readonly threeMonthsInMillis = this.oneDayInMillis * 90; /** * The cache service used for storing data. @@ -71,7 +69,7 @@ export class HbarSpendingPlanRepository { } /** - * Gets an HBar spending plan by ID with detailed information (spendingHistory and spentToday). + * Gets an HBar spending plan by ID with detailed information (spendingHistory and amountSpent). * @param {string} id - The ID of the plan. * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - The detailed HBar spending plan object. @@ -81,7 +79,7 @@ export class HbarSpendingPlanRepository { return new HbarSpendingPlan({ ...plan, spendingHistory: [], - spentToday: await this.getSpentToday(id, requestDetails), + amountSpent: await this.getAmountSpent(id, requestDetails), }); } @@ -89,20 +87,25 @@ export class HbarSpendingPlanRepository { * Creates a new HBar spending plan. * @param {SubscriptionType} subscriptionType - The subscription type of the plan to create. * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @param {number} ttl - The time-to-live for the plan in milliseconds. * @returns {Promise} - The created HBar spending plan object. */ - async create(subscriptionType: SubscriptionType, requestDetails: RequestDetails): Promise { + async create( + subscriptionType: SubscriptionType, + requestDetails: RequestDetails, + ttl: number, + ): Promise { const plan: IDetailedHbarSpendingPlan = { id: uuidV4(randomBytes(16)), subscriptionType, createdAt: new Date(), active: true, spendingHistory: [], - spentToday: 0, + amountSpent: 0, }; this.logger.trace(`Creating HbarSpendingPlan with ID ${plan.id}...`); const key = this.getKey(plan.id); - await this.cache.set(key, plan, 'create', requestDetails, this.threeMonthsInMillis); + await this.cache.set(key, plan, 'create', requestDetails, ttl); return new HbarSpendingPlan(plan); } @@ -157,48 +160,51 @@ export class HbarSpendingPlanRepository { } /** - * Gets the amount spent today for an HBar spending plan. - * @param {string} id - The ID of the plan. - * @param {RequestDetails} requestDetails - The request details for logging and tracking. - * @returns {Promise} - A promise that resolves with the amount spent today. + * Gets the amount spent for an HBar spending plan. + * @param id - The ID of the plan. + @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - A promise that resolves with the amount spent. */ - async getSpentToday(id: string, requestDetails: RequestDetails): Promise { + async getAmountSpent(id: string, requestDetails: RequestDetails): Promise { await this.checkExistsAndActive(id, requestDetails); - this.logger.trace(`Retrieving spentToday for HbarSpendingPlan with ID ${id}...`); - const key = this.getSpentTodayKey(id); - return this.cache.getAsync(key, 'getSpentToday', requestDetails).then((spentToday) => parseInt(spentToday ?? '0')); + this.logger.trace(`Retrieving amountSpent for HbarSpendingPlan with ID ${id}...`); + const key = this.getAmountSpentKey(id); + return this.cache + .getAsync(key, 'getAmountSpent', requestDetails) + .then((amountSpent) => parseInt(amountSpent ?? '0')); } /** - * Resets the amount spent today for all hbar spending plans. + * Resets the amount spent for all hbar spending plans. * @returns {Promise} - A promise that resolves when the operation is complete. */ - async resetAllSpentTodayEntries(requestDetails: RequestDetails): Promise { - this.logger.trace('Resetting the spentToday entries for all HbarSpendingPlans...'); - const callerMethod = this.resetAllSpentTodayEntries.name; - const keys = await this.cache.keys(`${this.collectionKey}:*:spentToday`, callerMethod, requestDetails); + async resetAmountSpentOfAllPlans(requestDetails: RequestDetails): Promise { + this.logger.trace('Resetting the `amountSpent` entries for all HbarSpendingPlans...'); + const callerMethod = this.resetAmountSpentOfAllPlans.name; + const keys = await this.cache.keys(this.getAmountSpentKey('*'), callerMethod, requestDetails); await Promise.all(keys.map((key) => this.cache.delete(key, callerMethod, requestDetails))); - this.logger.trace(`Successfully reset ${keys.length} spentToday entries for HbarSpendingPlans.`); + this.logger.trace(`Successfully reset ${keys.length} "amountSpent" entries for HbarSpendingPlans.`); } /** - * Adds an amount to the amount spent today for a plan. + * Adds an amount to the amount spent for a plan. * @param {string} id - The ID of the plan. * @param {number} amount - The amount to add. * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @param {number} ttl - The time-to-live for the amountSpent entry in milliseconds. * @returns {Promise} - A promise that resolves when the operation is complete. */ - async addAmountToSpentToday(id: string, amount: number, requestDetails: RequestDetails): Promise { + async addToAmountSpent(id: string, amount: number, requestDetails: RequestDetails, ttl: number): Promise { await this.checkExistsAndActive(id, requestDetails); - const key = this.getSpentTodayKey(id); - if (!(await this.cache.getAsync(key, 'addAmountToSpentToday', requestDetails))) { - this.logger.trace(`No spending yet for HbarSpendingPlan with ID ${id}, setting spentToday to ${amount}...`); - await this.cache.set(key, amount, 'addAmountToSpentToday', requestDetails, this.oneDayInMillis); + const key = this.getAmountSpentKey(id); + if (!(await this.cache.getAsync(key, 'addToAmountSpent', requestDetails))) { + this.logger.trace(`No spending yet for HbarSpendingPlan with ID ${id}, setting amountSpent to ${amount}...`); + await this.cache.set(key, amount, 'addToAmountSpent', requestDetails, ttl); } else { - this.logger.trace(`Adding ${amount} to spentToday for HbarSpendingPlan with ID ${id}...`); - await this.cache.incrBy(key, amount, 'addAmountToSpentToday', requestDetails); + this.logger.trace(`Adding ${amount} to amountSpent for HbarSpendingPlan with ID ${id}...`); + await this.cache.incrBy(key, amount, 'addToAmountSpent', requestDetails); } } @@ -213,7 +219,7 @@ export class HbarSpendingPlanRepository { requestDetails: RequestDetails, ): Promise { const callerMethod = this.findAllActiveBySubscriptionType.name; - const keys = await this.cache.keys(`${this.collectionKey}:*`, callerMethod, requestDetails); + const keys = await this.cache.keys(this.getKey('*'), callerMethod, requestDetails); const plans = await Promise.all( keys.map((key) => this.cache.getAsync(key, callerMethod, requestDetails)), ); @@ -226,7 +232,7 @@ export class HbarSpendingPlanRepository { ...plan, createdAt: new Date(plan.createdAt), spendingHistory: [], - spentToday: await this.getSpentToday(plan.id, requestDetails), + amountSpent: await this.getAmountSpent(plan.id, requestDetails), }), ), ); @@ -242,12 +248,12 @@ export class HbarSpendingPlanRepository { } /** - * Gets the cache key for the amount spent today for an {@link IHbarSpendingPlan}. + * Gets the cache key for the amount spent for an {@link IHbarSpendingPlan}. * @param id - The ID of the plan to get the key for. * @private */ - private getSpentTodayKey(id: string): string { - return `${this.collectionKey}:${id}:spentToday`; + private getAmountSpentKey(id: string): string { + return `${this.collectionKey}:${id}:amountSpent`; } /** diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts index 82849ec2ed..75957defe1 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts @@ -27,7 +27,6 @@ import { RequestDetails } from '../../../types'; export class IPAddressHbarSpendingPlanRepository { private readonly collectionKey = 'ipAddressHbarSpendingPlan'; - private readonly threeMonthsInMillis = 90 * 24 * 60 * 60 * 1000; /** * The cache service used for storing data. @@ -50,6 +49,7 @@ export class IPAddressHbarSpendingPlanRepository { * Finds an {@link IPAddressHbarSpendingPlan} for an IP address. * * @param {string} ipAddress - The IP address to search for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - The associated plan for the IP address. */ async findByAddress(ipAddress: string, requestDetails: RequestDetails): Promise { @@ -66,11 +66,13 @@ export class IPAddressHbarSpendingPlanRepository { * Saves an {@link IPAddressHbarSpendingPlan} to the cache, linking the plan to the IP address. * * @param {IIPAddressHbarSpendingPlan} addressPlan - The plan to save. + * @param {RequestDetails} requestDetails - The request details used for logging and tracking. + * @param {number} ttl - The time-to-live for the cache entry. * @returns {Promise} - A promise that resolves when the IP address is linked to the plan. */ - async save(addressPlan: IIPAddressHbarSpendingPlan, requestDetails: RequestDetails): Promise { + async save(addressPlan: IIPAddressHbarSpendingPlan, requestDetails: RequestDetails, ttl: number): Promise { const key = this.getKey(addressPlan.ipAddress); - await this.cache.set(key, addressPlan, 'save', requestDetails, this.threeMonthsInMillis); + await this.cache.set(key, addressPlan, 'save', requestDetails, ttl); this.logger.trace(`Linked new IP address to HbarSpendingPlan with ID ${addressPlan.planId}`); } diff --git a/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts b/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts index 49821ca595..cdf4a5806d 100644 --- a/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts +++ b/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts @@ -30,5 +30,5 @@ export interface IHbarSpendingPlan { export interface IDetailedHbarSpendingPlan extends IHbarSpendingPlan { spendingHistory: IHbarSpendingRecord[]; - spentToday: number; + amountSpent: number; } diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index 92597e2cfe..48d603d9d5 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -123,7 +123,7 @@ export class RelayImpl implements Relay { const chainId = prepend0x(Number(configuredChainId).toString(16)); const duration = constants.HBAR_RATE_LIMIT_DURATION; - const total = constants.HBAR_RATE_LIMIT_TINYBAR; + const total = constants.HBAR_RATE_LIMIT_TOTAL.toNumber(); const hbarLimiter = new HbarLimit(logger.child({ name: 'hbar-rate-limit' }), Date.now(), total, duration, register); this.eventEmitter = new EventEmitter(); diff --git a/packages/relay/src/lib/services/hbarLimitService/index.ts b/packages/relay/src/lib/services/hbarLimitService/index.ts index a791c75024..1758796f22 100644 --- a/packages/relay/src/lib/services/hbarLimitService/index.ts +++ b/packages/relay/src/lib/services/hbarLimitService/index.ts @@ -26,15 +26,15 @@ import { IDetailedHbarSpendingPlan } from '../../db/types/hbarLimiter/hbarSpendi import { HbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/hbarSpendingPlanRepository'; import { EthAddressHbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; import { IPAddressHbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; -import { RequestDetails } from '../../types/RequestDetails'; +import { RequestDetails } from '../../types'; +import constants from '../../constants'; +import { Hbar } from '@hashgraph/sdk'; export class HbarLimitService implements IHbarLimitService { - static readonly ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000; - // TODO: Replace with actual values - static readonly DAILY_LIMITS: Record = { - BASIC: parseInt(process.env.HBAR_DAILY_LIMIT_BASIC ?? '1000'), - EXTENDED: parseInt(process.env.HBAR_DAILY_LIMIT_EXTENDED ?? '10000'), - PRIVILEGED: parseInt(process.env.HBAR_DAILY_LIMIT_PRIVILEGED ?? '100000'), + static readonly TIER_LIMITS: Record = { + BASIC: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_BASIC), + EXTENDED: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_EXTENDED), + PRIVILEGED: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_PRIVILEGED), }; /** @@ -70,7 +70,7 @@ export class HbarLimitService implements IHbarLimitService { * The remaining budget for the rate limiter. * @private */ - private remainingBudget: number; + private remainingBudget: Hbar; /** * The reset timestamp for the rate limiter. @@ -84,8 +84,12 @@ export class HbarLimitService implements IHbarLimitService { private readonly ipAddressHbarSpendingPlanRepository: IPAddressHbarSpendingPlanRepository, private readonly logger: Logger, private readonly register: Registry, - private readonly totalBudget: number, + private readonly totalBudget: Hbar, + private readonly limitDuration: number, ) { + this.reset = this.getResetTimestamp(); + this.remainingBudget = this.totalBudget; + const metricCounterName = 'rpc_relay_hbar_rate_limit'; this.register.removeSingleMetric(metricCounterName); this.hbarLimitCounter = new Counter({ @@ -103,8 +107,7 @@ export class HbarLimitService implements IHbarLimitService { help: 'Relay Hbar rate limit remaining budget', registers: [register], }); - this.hbarLimitRemainingGauge.set(this.totalBudget); - this.remainingBudget = this.totalBudget; + this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); this.dailyUniqueSpendingPlansCounter = Object.values(SubscriptionType).reduce( (acc, type) => { @@ -133,21 +136,17 @@ export class HbarLimitService implements IHbarLimitService { }, {} as Record, ); - - // Reset the rate limiter at the start of the next day - this.reset = this.getResetTimestamp(); } /** - * Resets the {@link HbarSpendingPlan#spentToday} field for all existing plans. - * @param {string} [requestId] - An optional unique request ID for tracking the request. + * Resets the {@link HbarSpendingPlan#amountSpent} field for all existing plans. + * @param {RequestDetails} requestDetails - The request details used for logging and tracking. * @returns {Promise} - A promise that resolves when the operation is complete. */ async resetLimiter(requestDetails: RequestDetails): Promise { this.logger.trace(`${requestDetails.formattedRequestId} Resetting HBAR rate limiter...`); - await this.hbarSpendingPlanRepository.resetAllSpentTodayEntries(requestDetails); + await this.hbarSpendingPlanRepository.resetAmountSpentOfAllPlans(requestDetails); this.resetBudget(); - this.resetMetrics(); this.reset = this.getResetTimestamp(); this.logger.trace( `${requestDetails.formattedRequestId} HBAR Rate Limit reset: remainingBudget=${this.remainingBudget}, newResetTimestamp=${this.reset}`, @@ -159,9 +158,8 @@ export class HbarLimitService implements IHbarLimitService { * @param {string} mode - The mode of the transaction or request. * @param {string} methodName - The name of the method being invoked. * @param {string} ethAddress - The eth address to check. - * @param {string} [ipAddress] - The ip address to check. - * @param {number} [estimatedTxFee] - The total estimated transaction fee, default to 0. * @param {RequestDetails} requestDetails The request details for logging and tracking. + * @param {number} [estimatedTxFee] - The total estimated transaction fee, default to 0. * @returns {Promise} - A promise that resolves with a boolean indicating if the address should be limited. */ async shouldLimit( @@ -187,13 +185,14 @@ export class HbarLimitService implements IHbarLimitService { spendingPlan = await this.createBasicSpendingPlan(ethAddress, requestDetails); } - const dailyLimit = HbarLimitService.DAILY_LIMITS[spendingPlan.subscriptionType]; + const spendingLimit = HbarLimitService.TIER_LIMITS[spendingPlan.subscriptionType].toTinybars(); - const exceedsLimit = spendingPlan.spentToday >= dailyLimit || spendingPlan.spentToday + estimatedTxFee > dailyLimit; + const exceedsLimit = + spendingLimit.lte(spendingPlan.amountSpent) || spendingLimit.lt(spendingPlan.amountSpent + estimatedTxFee); this.logger.trace( - `${requestDetails.formattedRequestId} ${user} ${exceedsLimit ? 'should' : 'should not'} be limited, spentToday=${ - spendingPlan.spentToday - }, estimatedTxFee=${estimatedTxFee}, dailyLimit=${dailyLimit}`, + `${requestDetails.formattedRequestId} ${user} ${exceedsLimit ? 'should' : 'should not'} be limited: amountSpent=${ + spendingPlan.amountSpent + }, estimatedTxFee=${estimatedTxFee} tℏ, spendingLimit=${spendingLimit.toString()} tℏ`, ); return exceedsLimit; } @@ -202,7 +201,6 @@ export class HbarLimitService implements IHbarLimitService { * Add expense to the remaining budget. * @param {number} cost - The cost of the expense. * @param {string} ethAddress - The Ethereum address to add the expense to. - * @param {string} ipAddress - The optional IP address to add the expense to. * @param {RequestDetails} requestDetails The request details for logging and tracking. * @returns {Promise} - A promise that resolves when the expense has been added. */ @@ -221,23 +219,23 @@ export class HbarLimitService implements IHbarLimitService { this.logger.trace( `${requestDetails.formattedRequestId} Adding expense of ${cost} to spending plan with ID ${ spendingPlan.id - }, new spentToday=${spendingPlan.spentToday + cost}`, + }, new amountSpent=${spendingPlan.amountSpent + cost}`, ); // Check if the spending plan is being used for the first time today - if (spendingPlan.spentToday === 0) { + if (spendingPlan.amountSpent === 0) { this.dailyUniqueSpendingPlansCounter[spendingPlan.subscriptionType].inc(1); } - await this.hbarSpendingPlanRepository.addAmountToSpentToday(spendingPlan.id, cost, requestDetails); - this.remainingBudget -= cost; - this.hbarLimitRemainingGauge.set(this.remainingBudget); + await this.hbarSpendingPlanRepository.addToAmountSpent(spendingPlan.id, cost, requestDetails, this.limitDuration); + this.remainingBudget = Hbar.fromTinybars(this.remainingBudget.toTinybars().sub(cost)); + this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); // Done asynchronously in the background this.updateAverageDailyUsagePerSubscriptionType(spendingPlan.subscriptionType, requestDetails).then(); this.logger.trace( - `${requestDetails.formattedRequestId} HBAR rate limit expense update: cost=${cost}, remainingBudget=${this.remainingBudget}`, + `${requestDetails.formattedRequestId} HBAR rate limit expense update: cost=${cost} tℏ, remainingBudget=${this.remainingBudget}`, ); } @@ -259,15 +257,15 @@ export class HbarLimitService implements IHbarLimitService { if (this.shouldResetLimiter()) { await this.resetLimiter(requestDetails); } - if (this.remainingBudget <= 0 || this.remainingBudget - estimatedTxFee < 0) { + if (this.remainingBudget.toTinybars().lte(0) || this.remainingBudget.toTinybars().sub(estimatedTxFee).lt(0)) { this.hbarLimitCounter.labels(mode, methodName).inc(1); this.logger.warn( - `${requestDetails.formattedRequestId} HBAR rate limit incoming call: remainingBudget=${this.remainingBudget}, totalBudget=${this.totalBudget}, resetTimestamp=${this.reset}`, + `${requestDetails.formattedRequestId} HBAR rate limit incoming call: remainingBudget=${this.remainingBudget}, totalBudget=${this.totalBudget}, estimatedTxFee=${estimatedTxFee} tℏ, resetTimestamp=${this.reset}`, ); return true; } else { this.logger.trace( - `${requestDetails.formattedRequestId} HBAR rate limit not reached. ${this.remainingBudget} out of ${this.totalBudget} tℏ left in relay budget until ${this.reset}.`, + `${requestDetails.formattedRequestId} HBAR rate limit not reached: remainingBudget=${this.remainingBudget}, totalBudget=${this.totalBudget}, estimatedTxFee=${estimatedTxFee} tℏ, resetTimestamp=${this.reset}.`, ); return false; } @@ -287,7 +285,7 @@ export class HbarLimitService implements IHbarLimitService { subscriptionType, requestDetails, ); - const totalUsage = plans.reduce((total, plan) => total + plan.spentToday, 0); + const totalUsage = plans.reduce((total, plan) => total + plan.amountSpent, 0); const averageUsage = Math.round(totalUsage / plans.length); this.averageDailySpendingPlanUsagesGauge[subscriptionType].set(averageUsage); } @@ -307,34 +305,37 @@ export class HbarLimitService implements IHbarLimitService { */ private resetBudget(): void { this.remainingBudget = this.totalBudget; - this.hbarLimitRemainingGauge.set(this.remainingBudget); + this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); } /** - * Resets the metrics that track daily unique spending plans and average daily spending plan usages. - * @private + * Calculates the next reset timestamp for the rate limiter. + * + * This method determines the next reset timestamp based on the current reset timestamp + * and the limit duration. If the current reset timestamp is not defined, it initializes + * the reset timestamp to midnight of the current day. It then iteratively adds the limit + * duration to the reset timestamp until it is in the future. + * + * @returns {Date} - The next reset timestamp. */ - private resetMetrics(): void { - for (const subscriptionType of Object.values(SubscriptionType)) { - this.dailyUniqueSpendingPlansCounter[subscriptionType].reset(); - this.averageDailySpendingPlanUsagesGauge[subscriptionType].reset(); + private getResetTimestamp(): Date { + const todayAtMidnight = new Date().setHours(0, 0, 0, 0); + + let resetDate = this.reset ? new Date(this.reset.getTime()) : new Date(todayAtMidnight); + while (resetDate.getTime() < Date.now()) { + // 1. Calculate the difference between the current time and the reset time. + // 2. Determine how many intervals of size `limitDuration` have passed since the last reset. + // 3. Calculate the new reset date by adding the required intervals to the original reset date. + const intervalsPassed = Math.ceil((Date.now() - resetDate.getTime()) / this.limitDuration); + resetDate = new Date(resetDate.getTime() + intervalsPassed * this.limitDuration); } - } - /** - * Gets a new reset timestamp for the rate limiter. - * @returns {Date} - The new reset timestamp. - * @private - */ - private getResetTimestamp(): Date { - const tomorrow = new Date(Date.now() + HbarLimitService.ONE_DAY_IN_MILLIS); - return new Date(tomorrow.setHours(0, 0, 0, 0)); + return resetDate; } /** * Gets the spending plan for the given eth address or ip address. * @param {string} ethAddress - The eth address to get the spending plan for. - * @param {string} ipAddress - The ip address to get the spending plan for. * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the spending plan or null if none exists. * @private @@ -384,7 +385,7 @@ export class HbarLimitService implements IHbarLimitService { /** * Gets the spending plan for the given IP address. - * @param {string} ipAddress - The IP address to get the spending plan for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the spending plan. * @private */ @@ -400,7 +401,6 @@ export class HbarLimitService implements IHbarLimitService { /** * Creates a basic spending plan for the given eth address. * @param {string} ethAddress - The eth address to create the spending plan for. - * @param {string} ipAddress - The ip address to create the spending plan for. * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the created spending plan. * @private @@ -414,18 +414,30 @@ export class HbarLimitService implements IHbarLimitService { throw new Error('Cannot create a spending plan without an associated eth address or ip address'); } - const spendingPlan = await this.hbarSpendingPlanRepository.create(SubscriptionType.BASIC, requestDetails); + const spendingPlan = await this.hbarSpendingPlanRepository.create( + SubscriptionType.BASIC, + requestDetails, + this.limitDuration, + ); if (ethAddress) { this.logger.trace( `${requestDetails.formattedRequestId} Linking spending plan with ID ${spendingPlan.id} to eth address ${ethAddress}`, ); - await this.ethAddressHbarSpendingPlanRepository.save({ ethAddress, planId: spendingPlan.id }, requestDetails); + await this.ethAddressHbarSpendingPlanRepository.save( + { ethAddress, planId: spendingPlan.id }, + requestDetails, + this.limitDuration, + ); } if (ipAddress) { this.logger.trace( `${requestDetails.formattedRequestId} Linking spending plan with ID ${spendingPlan.id} to ip address ${ipAddress}`, ); - await this.ipAddressHbarSpendingPlanRepository.save({ ipAddress, planId: spendingPlan.id }, requestDetails); + await this.ipAddressHbarSpendingPlanRepository.save( + { ipAddress, planId: spendingPlan.id }, + requestDetails, + this.limitDuration, + ); } return spendingPlan; } diff --git a/packages/relay/tests/lib/clients/redisCache.spec.ts b/packages/relay/tests/lib/clients/redisCache.spec.ts index 6c6b966a34..16d150e67d 100644 --- a/packages/relay/tests/lib/clients/redisCache.spec.ts +++ b/packages/relay/tests/lib/clients/redisCache.spec.ts @@ -36,7 +36,6 @@ describe('RedisCache Test Suite', async function () { const callingMethod = 'RedisCacheTest'; const requestDetails = new RequestDetails({ requestId: 'localLRUCacheTest', ipAddress: '0.0.0.0' }); - let redisCache: RedisCache; useInMemoryRedisServer(logger, 6379); diff --git a/packages/relay/tests/lib/eth/eth-helpers.ts b/packages/relay/tests/lib/eth/eth-helpers.ts index 68c8b99066..8301421bef 100644 --- a/packages/relay/tests/lib/eth/eth-helpers.ts +++ b/packages/relay/tests/lib/eth/eth-helpers.ts @@ -27,6 +27,7 @@ import HAPIService from '../../../src/lib/services/hapiService/hapiService'; import MockAdapter from 'axios-mock-adapter'; import { MirrorNodeClient } from '../../../src/lib/clients/mirrorNodeClient'; import { EthImpl } from '../../../src/lib/eth'; +import EventEmitter from 'events'; export function contractResultsByNumberByIndexURL(number: number, index: number): string { return `contracts/results?block.number=${number}&transaction.index=${index}&limit=100&order=asc`; @@ -60,10 +61,11 @@ export function generateEthTestEnv(fixedFeeHistory = false) { const web3Mock = new MockAdapter(mirrorNodeInstance.getMirrorNodeWeb3Instance(), { onNoMatch: 'throwException' }); const duration = constants.HBAR_RATE_LIMIT_DURATION; - const total = constants.HBAR_RATE_LIMIT_TINYBAR; + const total = constants.HBAR_RATE_LIMIT_TOTAL.toNumber(); const hbarLimiter = new HbarLimit(logger.child({ name: 'hbar-rate-limit' }), Date.now(), total, duration, registry); + const eventEmitter = new EventEmitter(); - const hapiServiceInstance = new HAPIService(logger, registry, hbarLimiter, cacheService); + const hapiServiceInstance = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); // @ts-ignore const ethImpl = new EthImpl(hapiServiceInstance, mirrorNodeInstance, logger, '0x12a', registry, cacheService); diff --git a/packages/relay/tests/lib/ethGetBlockBy.spec.ts b/packages/relay/tests/lib/ethGetBlockBy.spec.ts index a1797bcb10..567191b4ca 100644 --- a/packages/relay/tests/lib/ethGetBlockBy.spec.ts +++ b/packages/relay/tests/lib/ethGetBlockBy.spec.ts @@ -134,7 +134,7 @@ describe('eth_getBlockBy', async function () { restMock = new MockAdapter(mirrorNodeInstance.getMirrorNodeRestInstance(), { onNoMatch: 'throwException' }); const duration = constants.HBAR_RATE_LIMIT_DURATION; - const total = constants.HBAR_RATE_LIMIT_TINYBAR; + const total = constants.HBAR_RATE_LIMIT_TOTAL.toNumber(); const hbarLimiter = new HbarLimit(logger.child({ name: 'hbar-rate-limit' }), Date.now(), total, duration, registry); const eventEmitter = new EventEmitter(); hapiServiceInstance = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts index 3d755aa5e9..c96a0a1351 100644 --- a/packages/relay/tests/lib/openrpc.spec.ts +++ b/packages/relay/tests/lib/openrpc.spec.ts @@ -130,7 +130,7 @@ describe('Open RPC Specification', function () { instance, ); const duration = constants.HBAR_RATE_LIMIT_DURATION; - const total = constants.HBAR_RATE_LIMIT_TINYBAR; + const total = constants.HBAR_RATE_LIMIT_TOTAL.toNumber(); const hbarLimiter = new HbarLimit(logger.child({ name: 'hbar-rate-limit' }), Date.now(), total, duration, registry); const eventEmitter = new EventEmitter(); diff --git a/packages/relay/tests/lib/poller.spec.ts b/packages/relay/tests/lib/poller.spec.ts index 82681de4b4..8169ff9e1f 100644 --- a/packages/relay/tests/lib/poller.spec.ts +++ b/packages/relay/tests/lib/poller.spec.ts @@ -34,132 +34,128 @@ describe('Polling', async function () { 'Poller: Fetching data for tag: {"event":"logs","filters":{"address":"0x23f5e49569A835d7bf9AefD30e4f60CdD570f225","topics":["0xc8b501cbd8e69c98c535894661d25839eb035b096adfde2bba416f04cc7ce987"]}}'; const logs = '[{"address":"0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69","blockHash":"0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b","blockNumber":"0x3","data":"0x","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000000000000000000000000000000000000208fa13","0x0000000000000000000000000000000000000000000000000000000000000005"],"transactionHash":"0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392","transactionIndex":"0x1"},{"address":"0x0000000000000000000000000000000002131952","blockHash":"0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b","blockNumber":"0x3","data":"0x","logIndex":"0x1","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000000000000000000000000000000000000208fa13","0x0000000000000000000000000000000000000000000000000000000000000005"],"transactionHash":"0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392","transactionIndex":"0x1"},{"address":"0x0000000000000000000000000000000002131953","blockHash":"0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b","blockNumber":"0x4","data":"0x","logIndex":"0x0","removed":false,"topics":[],"transactionHash":"0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6393","transactionIndex":"0x1"},{"address":"0x0000000000000000000000000000000002131954","blockHash":"0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b","blockNumber":"0x5","data":"0x","logIndex":"0x0","removed":false,"topics":[],"transactionHash":"0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6394","transactionIndex":"0x1"}]'; - const logsArray = new Array([ - [ - { - address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x3', - data: '0x', - logIndex: '0x0', - removed: false, - topics: [ - '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x000000000000000000000000000000000000000000000000000000000208fa13', - '0x0000000000000000000000000000000000000000000000000000000000000005', - ], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', - transactionIndex: '0x1', - }, - { - address: '0x0000000000000000000000000000000002131952', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x3', - data: '0x', - logIndex: '0x1', - removed: false, - topics: [ - '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x000000000000000000000000000000000000000000000000000000000208fa13', - '0x0000000000000000000000000000000000000000000000000000000000000005', - ], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', - transactionIndex: '0x1', - }, - { - address: '0x0000000000000000000000000000000002131953', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x4', - data: '0x', - logIndex: '0x0', - removed: false, - topics: [], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6393', - transactionIndex: '0x1', - }, - { - address: '0x0000000000000000000000000000000002131954', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x5', - data: '0x', - logIndex: '0x0', - removed: false, - topics: [], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6394', - transactionIndex: '0x1', - }, - ], - [ - { - address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x3', - data: '0x', - logIndex: '0x0', - removed: false, - topics: [ - '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x000000000000000000000000000000000000000000000000000000000208fa13', - '0x0000000000000000000000000000000000000000000000000000000000000005', - ], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', - transactionIndex: '0x1', - }, - { - address: '0x0000000000000000000000000000000002131952', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x3', - data: '0x', - logIndex: '0x1', - removed: false, - topics: [ - '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x000000000000000000000000000000000000000000000000000000000208fa13', - '0x0000000000000000000000000000000000000000000000000000000000000005', - ], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', - transactionIndex: '0x1', - }, - { - address: '0x0000000000000000000000000000000002131953', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x4', - data: '0x', - logIndex: '0x0', - removed: false, - topics: [], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6393', - transactionIndex: '0x1', - }, - { - address: '0x0000000000000000000000000000000002131954', - blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', - blockNumber: '0x5', - data: '0x', - logIndex: '0x0', - removed: false, - topics: [], - transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6394', - transactionIndex: '0x1', - }, - ], - ]); + const logsArray = [ + { + address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x3', + data: '0x', + logIndex: '0x0', + removed: false, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x000000000000000000000000000000000000000000000000000000000208fa13', + '0x0000000000000000000000000000000000000000000000000000000000000005', + ], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', + transactionIndex: '0x1', + }, + { + address: '0x0000000000000000000000000000000002131952', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x3', + data: '0x', + logIndex: '0x1', + removed: false, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x000000000000000000000000000000000000000000000000000000000208fa13', + '0x0000000000000000000000000000000000000000000000000000000000000005', + ], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', + transactionIndex: '0x1', + }, + { + address: '0x0000000000000000000000000000000002131953', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x4', + data: '0x', + logIndex: '0x0', + removed: false, + topics: [], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6393', + transactionIndex: '0x1', + }, + { + address: '0x0000000000000000000000000000000002131954', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x5', + data: '0x', + logIndex: '0x0', + removed: false, + topics: [], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6394', + transactionIndex: '0x1', + }, + { + address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x3', + data: '0x', + logIndex: '0x0', + removed: false, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x000000000000000000000000000000000000000000000000000000000208fa13', + '0x0000000000000000000000000000000000000000000000000000000000000005', + ], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', + transactionIndex: '0x1', + }, + { + address: '0x0000000000000000000000000000000002131952', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x3', + data: '0x', + logIndex: '0x1', + removed: false, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x000000000000000000000000000000000000000000000000000000000208fa13', + '0x0000000000000000000000000000000000000000000000000000000000000005', + ], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392', + transactionIndex: '0x1', + }, + { + address: '0x0000000000000000000000000000000002131953', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x4', + data: '0x', + logIndex: '0x0', + removed: false, + topics: [], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6393', + transactionIndex: '0x1', + }, + { + address: '0x0000000000000000000000000000000002131954', + blockHash: '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b', + blockNumber: '0x5', + data: '0x', + logIndex: '0x0', + removed: false, + topics: [], + transactionHash: '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6394', + transactionIndex: '0x1', + }, + ]; const SINGLE_LINE = 'Called notifySubscriber with single line of log data!'; const tag = '{"event":"logs","filters":{"address":"0x23f5e49569A835d7bf9AefD30e4f60CdD570f225","topics":["0xc8b501cbd8e69c98c535894661d25839eb035b096adfde2bba416f04cc7ce987"]}}'; - let ethImplStub: EthImpl; + let ethImplStub: sinon.SinonStubbedInstance; let poller: Poller; - let sandbox; + let sandbox: sinon.SinonSandbox; this.beforeEach(() => { ethImplStub = sinon.createStubInstance(EthImpl); - ethImplStub.blockNumber.returns('0x1b177b'); - ethImplStub.getLogs.returns(logs); + ethImplStub.blockNumber.resolves('0x1b177b'); + ethImplStub.getLogs.resolves(JSON.parse(logs)); const registry = new Registry(); poller = new Poller(ethImplStub, logger, registry); @@ -172,10 +168,10 @@ describe('Polling', async function () { describe('Poller', () => { it('should start polling', async () => { - ethImplStub.blockNumber.returns('0x1b177b'); + ethImplStub.blockNumber.resolves('0x1b177b'); // eslint-disable-next-line @typescript-eslint/no-empty-function const notifySubscriber = (tag, logs) => {}; - ethImplStub.getLogs.returns(logs); + ethImplStub.getLogs.resolves(JSON.parse(logs)); const loggerSpy = sandbox.spy(logger, 'info'); expect(poller.hasPoll(tag)).to.be.false; @@ -214,7 +210,7 @@ describe('Polling', async function () { return; }; - ethImplStub.getLogs.returns(logs); + ethImplStub.getLogs.resolves(JSON.parse(logs)); poller.add(tag, notifySubscriber); const loggerSpy = sandbox.spy(logger, 'debug'); @@ -236,7 +232,7 @@ describe('Polling', async function () { return; }; - ethImplStub.getLogs.returns(logsArray); + ethImplStub.getLogs.resolves(logsArray); poller.add(tag, notifySubscriber); const loggerSpy = sandbox.spy(logger, 'debug'); diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts index 5b128ece92..4416db4139 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts @@ -20,6 +20,7 @@ import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import sinon from 'sinon'; import { EthAddressHbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import pino from 'pino'; @@ -39,36 +40,33 @@ describe('EthAddressHbarSpendingPlanRepository', function () { requestId: 'ethAddressHbarSpendingPlanRepositoryTest', ipAddress: '0.0.0.0', }); + const ttl = 86_400_000; // 1 day const tests = (isSharedCacheEnabled: boolean) => { let cacheService: CacheService; + let cacheServiceSpy: sinon.SinonSpiedInstance; let repository: EthAddressHbarSpendingPlanRepository; + before(async () => { + cacheService = new CacheService(logger.child({ name: 'CacheService' }), registry); + cacheServiceSpy = sinon.spy(cacheService); + repository = new EthAddressHbarSpendingPlanRepository( + cacheService, + logger.child({ name: 'EthAddressHbarSpendingPlanRepository' }), + ); + }); + if (isSharedCacheEnabled) { useInMemoryRedisServer(logger, 6382); + } - this.beforeAll(async () => { - cacheService = new CacheService(logger.child({ name: 'CacheService' }), new Registry()); - repository = new EthAddressHbarSpendingPlanRepository( - cacheService, - logger.child({ name: 'EthAddressHbarSpendingPlanRepository' }), - ); - }); + afterEach(async () => { + await cacheService.clear(requestDetails); + }); - this.afterAll(async () => { - await cacheService.disconnectRedisClient(); - }); - } else { - before(() => { - process.env.TEST = 'true'; - process.env.REDIS_ENABLED = 'false'; - cacheService = new CacheService(logger.child({ name: 'CacheService' }), registry); - repository = new EthAddressHbarSpendingPlanRepository( - cacheService, - logger.child({ name: 'EthAddressHbarSpendingPlanRepository' }), - ); - }); - } + after(async () => { + await cacheService.disconnectRedisClient(); + }); describe('findByAddress', () => { it('retrieves an address plan by address', async () => { @@ -94,13 +92,21 @@ describe('EthAddressHbarSpendingPlanRepository', function () { const ethAddress = '0x123'; const addressPlan: IEthAddressHbarSpendingPlan = { ethAddress, planId: uuidV4(randomBytes(16)) }; - await repository.save(addressPlan, requestDetails); + await repository.save(addressPlan, requestDetails, ttl); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ethAddress}`, 'test', requestDetails, ); expect(result).to.deep.equal(addressPlan); + sinon.assert.calledWith( + cacheServiceSpy.set, + `${repository['collectionKey']}:${ethAddress}`, + addressPlan, + 'save', + requestDetails, + ttl, + ); }); it('overwrites an existing address plan', async () => { @@ -110,13 +116,21 @@ describe('EthAddressHbarSpendingPlanRepository', function () { const newPlanId = uuidV4(randomBytes(16)); const newAddressPlan: IEthAddressHbarSpendingPlan = { ethAddress, planId: newPlanId }; - await repository.save(newAddressPlan, requestDetails); + await repository.save(newAddressPlan, requestDetails, ttl); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ethAddress}`, 'test', requestDetails, ); expect(result).to.deep.equal(newAddressPlan); + sinon.assert.calledWith( + cacheServiceSpy.set, + `${repository['collectionKey']}:${ethAddress}`, + newAddressPlan, + 'save', + requestDetails, + ttl, + ); }); }); diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts index 3daa0883a0..6ac974f99d 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts @@ -21,6 +21,7 @@ import { pino } from 'pino'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import sinon from 'sinon'; import { HbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import { Registry } from 'prom-client'; @@ -41,42 +42,45 @@ describe('HbarSpendingPlanRepository', function () { const logger = pino(); const registry = new Registry(); const requestDetails = new RequestDetails({ requestId: 'hbarSpendingPlanRepositoryTest', ipAddress: '0.0.0.0' }); + const ttl = 86_400_000; // 1 day const tests = (isSharedCacheEnabled: boolean) => { - let cacheService: CacheService; + let cacheServiceSpy: sinon.SinonSpiedInstance; let repository: HbarSpendingPlanRepository; + before(async () => { + const cacheService = new CacheService(logger.child({ name: `CacheService` }), registry); + cacheServiceSpy = sinon.spy(cacheService); + repository = new HbarSpendingPlanRepository(cacheService, logger.child({ name: `HbarSpendingPlanRepository` })); + }); + if (isSharedCacheEnabled) { useInMemoryRedisServer(logger, 6380); - - before(async () => { - cacheService = new CacheService(logger.child({ name: `CacheService` }), registry); - repository = new HbarSpendingPlanRepository(cacheService, logger.child({ name: `HbarSpendingPlanRepository` })); - }); - - after(async () => { - await cacheService.disconnectRedisClient(); - }); - } else { - before(async () => { - process.env.TEST = 'true'; - process.env.REDIS_ENABLED = 'false'; - cacheService = new CacheService(logger.child({ name: `CacheService` }), registry); - repository = new HbarSpendingPlanRepository(cacheService, logger.child({ name: `HbarSpendingPlanRepository` })); - }); } afterEach(async () => { - await cacheService.clear(requestDetails); + await cacheServiceSpy.clear(requestDetails); + }); + + after(async () => { + await cacheServiceSpy.disconnectRedisClient(); }); describe('create', () => { it('creates a plan successfully', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); await expect(repository.findByIdWithDetails(createdPlan.id, requestDetails)).to.be.eventually.deep.equal( createdPlan, ); + sinon.assert.calledWithMatch( + cacheServiceSpy.set, + `${repository['collectionKey']}:${createdPlan.id}`, + createdPlan, + 'create', + requestDetails, + ttl, + ); }); }); @@ -90,7 +94,7 @@ describe('HbarSpendingPlanRepository', function () { it('returns a plan by ID', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); await expect(repository.findById(createdPlan.id, requestDetails)).to.be.eventually.deep.equal(createdPlan); }); }); @@ -105,7 +109,7 @@ describe('HbarSpendingPlanRepository', function () { it('returns a plan by ID', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); await expect(repository.findByIdWithDetails(createdPlan.id, requestDetails)).to.be.eventually.deep.equal( createdPlan, ); @@ -122,18 +126,18 @@ describe('HbarSpendingPlanRepository', function () { it('returns an empty array if spending history is empty', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.deep.equal([]); }); it('retrieves spending history for a plan', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); const key = `${repository['collectionKey']}:${createdPlan.id}:spendingHistory`; const hbarSpending = { amount: 100, timestamp: new Date() } as IHbarSpendingRecord; - await cacheService.rPush(key, hbarSpending, 'test', requestDetails); + await cacheServiceSpy.rPush(key, hbarSpending, 'test', requestDetails); const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.have.lengthOf(1); @@ -145,7 +149,7 @@ describe('HbarSpendingPlanRepository', function () { describe('addAmountToSpendingHistory', () => { it('adds amount to spending history', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); await expect(repository.getSpendingHistory(createdPlan.id, requestDetails)).to.be.eventually.deep.equal([]); const amount = 100; @@ -155,11 +159,19 @@ describe('HbarSpendingPlanRepository', function () { expect(spendingHistory).to.have.lengthOf(1); expect(spendingHistory[0].amount).to.equal(amount); expect(spendingHistory[0].timestamp).to.be.a('Date'); + + sinon.assert.calledWithMatch( + cacheServiceSpy.rPush, + `${repository['collectionKey']}:${createdPlan.id}:spendingHistory`, + { amount, timestamp: spendingHistory[0].timestamp }, + 'addAmountToSpendingHistory', + requestDetails, + ); }); it('adds multiple amounts to spending history', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); await expect(repository.getSpendingHistory(createdPlan.id, requestDetails)).to.be.eventually.deep.equal([]); const amounts = [100, 200, 300]; @@ -183,122 +195,115 @@ describe('HbarSpendingPlanRepository', function () { }); }); - describe('getSpentToday', () => { - const mockedOneDayInMillis: number = 200; - let oneDayInMillis: number; - - beforeEach(() => { - // save the oneDayInMillis value - oneDayInMillis = repository['oneDayInMillis']; - // set oneDayInMillis to 1 second for testing - // @ts-ignore - repository['oneDayInMillis'] = mockedOneDayInMillis; - }); + describe('getAmountSpent', () => { + let createdPlan: IDetailedHbarSpendingPlan; - afterEach(() => { - // reset to the previous value of oneDayInMillis - // @ts-ignore - repository['oneDayInMillis'] = oneDayInMillis; + beforeEach(async () => { + createdPlan = await repository.create(SubscriptionType.BASIC, requestDetails, ttl); }); - it('retrieves spent today for a plan', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + it('retrieves amountSpent for a plan', async () => { const amount = 50; - - await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); - - const spentToday = await repository.getSpentToday(createdPlan.id, requestDetails); - expect(spentToday).to.equal(amount); + await repository.addToAmountSpent(createdPlan.id, amount, requestDetails, ttl); + await expect(repository.getAmountSpent(createdPlan.id, requestDetails)).to.eventually.equal(amount); }); - it('returns 0 if spent today key does not exist', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); - - const spentToday = await repository.getSpentToday(createdPlan.id, requestDetails); - expect(spentToday).to.equal(0); + it('returns 0 if amountSpent key does not exist', async () => { + await expect(repository.getAmountSpent(createdPlan.id, requestDetails)).to.eventually.equal(0); }); - it('should expire spent today key at the end of the day', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + it('should expire amountSpent key at the end of the day', async () => { const amount = 50; + const ttl = 100; + await repository.addToAmountSpent(createdPlan.id, amount, requestDetails, ttl); + await expect(repository.getAmountSpent(createdPlan.id, requestDetails)).to.eventually.equal(amount); - await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); - await expect(repository.getSpentToday(createdPlan.id, requestDetails)).to.eventually.equal(amount); + await new Promise((resolve) => setTimeout(resolve, ttl + 100)); - await new Promise((resolve) => setTimeout(resolve, mockedOneDayInMillis + 100)); - - await expect(repository.getSpentToday(createdPlan.id, requestDetails)).to.eventually.equal(0); + await expect(repository.getAmountSpent(createdPlan.id, requestDetails)).to.eventually.equal(0); }); }); - describe('resetAllSpentTodayEntries', () => { - it('resets all spent today entries', async () => { + describe('resetAllAmountSpentEntries', () => { + it('resets all amountSpent entries', async () => { const plans: IDetailedHbarSpendingPlan[] = []; for (const subscriptionType of Object.values(SubscriptionType)) { - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); plans.push(createdPlan); const amount = 50 * plans.length; - await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); - await expect(repository.getSpentToday(createdPlan.id, requestDetails)).to.eventually.equal(amount); + await repository.addToAmountSpent(createdPlan.id, amount, requestDetails, ttl); + await expect(repository.getAmountSpent(createdPlan.id, requestDetails)).to.eventually.equal(amount); } - await repository.resetAllSpentTodayEntries(requestDetails); + await repository.resetAmountSpentOfAllPlans(requestDetails); for (const plan of plans) { - await expect(repository.getSpentToday(plan.id, requestDetails)).to.eventually.equal(0); + await expect(repository.getAmountSpent(plan.id, requestDetails)).to.eventually.equal(0); } }); - it('does not throw an error if no spent today keys exist', async () => { - await expect(repository.resetAllSpentTodayEntries(requestDetails)).to.not.be.rejected; + it('does not throw an error if no amountSpent keys exist', async () => { + await expect(repository.resetAmountSpentOfAllPlans(requestDetails)).to.not.be.rejected; }); }); - describe('addAmountToSpentToday', () => { - it('adds amount to spent today', async () => { + describe('addToAmountSpent', () => { + it('adds amount to amountSpent', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); const amount = 50; - await repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails); + await repository.addToAmountSpent(createdPlan.id, amount, requestDetails, ttl); const plan = await repository.findByIdWithDetails(createdPlan.id, requestDetails); expect(plan).to.not.be.null; - expect(plan!.spentToday).to.equal(amount); + expect(plan!.amountSpent).to.equal(amount); + sinon.assert.calledWithMatch( + cacheServiceSpy.set, + `${repository['collectionKey']}:${createdPlan.id}:amountSpent`, + amount, + 'addToAmountSpent', + requestDetails, + ttl, + ); - // Add more to spent today + // Add more to amountSpent const newAmount = 100; - await repository.addAmountToSpentToday(createdPlan.id, newAmount, requestDetails); + await repository.addToAmountSpent(createdPlan.id, newAmount, requestDetails, ttl); + sinon.assert.calledWithMatch( + cacheServiceSpy.incrBy, + `${repository['collectionKey']}:${createdPlan.id}:amountSpent`, + newAmount, + 'addToAmountSpent', + requestDetails, + ); const updatedPlan = await repository.findByIdWithDetails(createdPlan.id, requestDetails); expect(updatedPlan).to.not.be.null; - expect(updatedPlan!.spentToday).to.equal(amount + newAmount); + expect(updatedPlan!.amountSpent).to.equal(amount + newAmount); }); - it('throws error if plan not found when adding to spent today', async () => { + it('throws error if plan not found when adding to amountSpent', async () => { const id = 'non-existent-id'; const amount = 50; - await expect(repository.addAmountToSpentToday(id, amount, requestDetails)).to.be.eventually.rejectedWith( + await expect(repository.addToAmountSpent(id, amount, requestDetails, ttl)).to.be.eventually.rejectedWith( HbarSpendingPlanNotFoundError, `HbarSpendingPlan with ID ${id} not found`, ); }); - it('throws an error if plan is not active when adding to spent today', async () => { + it('throws an error if plan is not active when adding to amountSpent', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${createdPlan.id}`; - await cacheService.set(key, { ...createdPlan, active: false }, 'test', requestDetails); + await cacheServiceSpy.set(key, { ...createdPlan, active: false }, 'test', requestDetails); const amount = 50; await expect( - repository.addAmountToSpentToday(createdPlan.id, amount, requestDetails), + repository.addToAmountSpent(createdPlan.id, amount, requestDetails, ttl), ).to.be.eventually.rejectedWith( HbarSpendingPlanNotActiveError, `HbarSpendingPlan with ID ${createdPlan.id} is not active`, @@ -317,11 +322,11 @@ describe('HbarSpendingPlanRepository', function () { it('throws error if plan is not active when checking if exists and active', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails); + const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${createdPlan.id}`; - await cacheService.set(key, { ...createdPlan, active: false }, 'test', requestDetails); + await cacheServiceSpy.set(key, { ...createdPlan, active: false }, 'test', requestDetails); await expect(repository.checkExistsAndActive(createdPlan.id, requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotActiveError, @@ -339,8 +344,8 @@ describe('HbarSpendingPlanRepository', function () { it('returns all active plans for the subscription type', async () => { const subscriptionType = SubscriptionType.BASIC; - const createdPlan1 = await repository.create(subscriptionType, requestDetails); - const createdPlan2 = await repository.create(subscriptionType, requestDetails); + const createdPlan1 = await repository.create(subscriptionType, requestDetails, ttl); + const createdPlan2 = await repository.create(subscriptionType, requestDetails, ttl); const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); expect(activePlans).to.have.lengthOf(2); @@ -349,21 +354,21 @@ describe('HbarSpendingPlanRepository', function () { it('does not return inactive plans for the subscription type', async () => { const subscriptionType = SubscriptionType.BASIC; - const activePlan = await repository.create(subscriptionType, requestDetails); - const inactivePlan = await repository.create(subscriptionType, requestDetails); + const activePlan = await repository.create(subscriptionType, requestDetails, ttl); + const inactivePlan = await repository.create(subscriptionType, requestDetails, ttl); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${inactivePlan.id}`; - await cacheService.set(key, { ...inactivePlan, active: false }, 'test', requestDetails); + await cacheServiceSpy.set(key, { ...inactivePlan, active: false }, 'test', requestDetails); const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); expect(activePlans).to.deep.equal([activePlan]); }); it('returns only active plans for the specified subscription type', async () => { - const basicPlan = await repository.create(SubscriptionType.BASIC, requestDetails); - const extendedPlan = await repository.create(SubscriptionType.EXTENDED, requestDetails); - const privilegedPlan = await repository.create(SubscriptionType.PRIVILEGED, requestDetails); + const basicPlan = await repository.create(SubscriptionType.BASIC, requestDetails, ttl); + const extendedPlan = await repository.create(SubscriptionType.EXTENDED, requestDetails, ttl); + const privilegedPlan = await repository.create(SubscriptionType.PRIVILEGED, requestDetails, ttl); const activeBasicPlans = await repository.findAllActiveBySubscriptionType( SubscriptionType.BASIC, diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts index e3d67295a6..24670fb02f 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts @@ -20,6 +20,7 @@ import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import sinon from 'sinon'; import { IPAddressHbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import pino from 'pino'; @@ -36,25 +37,32 @@ describe('IPAddressHbarSpendingPlanRepository', function () { const logger = pino(); const registry = new Registry(); const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); + const ttl = 86_400_000; // 1 day + const ipAddress = '555.555.555.555'; + const nonExistingIpAddress = 'xxx.xxx.xxx.xxx'; const tests = (isSharedCacheEnabled: boolean) => { let cacheService: CacheService; + let cacheServiceSpy: sinon.SinonSpiedInstance; let repository: IPAddressHbarSpendingPlanRepository; - const ipAddress = '555.555.555.555'; - const nonExistingIpAddress = 'xxx.xxx.xxx.xxx'; - - if (isSharedCacheEnabled) { - useInMemoryRedisServer(logger, 6383); - } before(() => { cacheService = new CacheService(logger.child({ name: 'CacheService' }), registry); + cacheServiceSpy = sinon.spy(cacheService); repository = new IPAddressHbarSpendingPlanRepository( cacheService, logger.child({ name: 'IPAddressHbarSpendingPlanRepository' }), ); }); + if (isSharedCacheEnabled) { + useInMemoryRedisServer(logger, 6383); + } + + afterEach(async () => { + await cacheService.clear(requestDetails); + }); + after(async () => { await cacheService.disconnectRedisClient(); }); @@ -80,13 +88,21 @@ describe('IPAddressHbarSpendingPlanRepository', function () { it('saves an address plan successfully', async () => { const addressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: uuidV4(randomBytes(16)) }; - await repository.save(addressPlan, requestDetails); + await repository.save(addressPlan, requestDetails, ttl); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ipAddress}`, 'test', requestDetails, ); expect(result).to.deep.equal(addressPlan); + sinon.assert.calledWith( + cacheServiceSpy.set, + `${repository['collectionKey']}:${ipAddress}`, + addressPlan, + 'save', + requestDetails, + ttl, + ); }); it('overwrites an existing address plan', async () => { @@ -95,13 +111,21 @@ describe('IPAddressHbarSpendingPlanRepository', function () { const newPlanId = uuidV4(randomBytes(16)); const newAddressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: newPlanId }; - await repository.save(newAddressPlan, requestDetails); + await repository.save(newAddressPlan, requestDetails, ttl); const result = await cacheService.getAsync( `${repository['collectionKey']}:${ipAddress}`, 'test', requestDetails, ); expect(result).to.deep.equal(newAddressPlan); + sinon.assert.calledWith( + cacheServiceSpy.set, + `${repository['collectionKey']}:${ipAddress}`, + newAddressPlan, + 'save', + requestDetails, + ttl, + ); }); }); diff --git a/packages/relay/tests/lib/sdkClient.spec.ts b/packages/relay/tests/lib/sdkClient.spec.ts index 93672a057a..c06e145482 100644 --- a/packages/relay/tests/lib/sdkClient.spec.ts +++ b/packages/relay/tests/lib/sdkClient.spec.ts @@ -107,7 +107,7 @@ describe('SdkClient', async function () { Utils.createPrivateKeyBasedOnFormat(process.env.OPERATOR_KEY_MAIN!), ); const duration = constants.HBAR_RATE_LIMIT_DURATION; - const total = constants.HBAR_RATE_LIMIT_TINYBAR; + const total = constants.HBAR_RATE_LIMIT_TOTAL.toNumber(); hbarLimiter = new HbarLimit(logger.child({ name: 'hbar-rate-limit' }), Date.now(), total, duration, registry); eventEmitter = new EventEmitter(); sdkClient = new SDKClient( diff --git a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts index 3b22fb313b..5bcbc1e403 100644 --- a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts +++ b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts @@ -23,7 +23,6 @@ import pino, { Logger } from 'pino'; import chai, { expect } from 'chai'; import { randomBytes, uuidV4 } from 'ethers'; import chaiAsPromised from 'chai-as-promised'; -import { getRequestId } from '../../../helpers'; import constants from '../../../../src/lib/constants'; import { Counter, Gauge, Registry } from 'prom-client'; import { HbarLimitService } from '../../../../src/lib/services/hbarLimitService'; @@ -39,22 +38,26 @@ import { IPAddressHbarSpendingPlanNotFoundError, } from '../../../../src/lib/db/types/hbarLimiter/errors'; import { RequestDetails } from '../../../../src/lib/types'; +import { Hbar } from '@hashgraph/sdk'; +import { Long } from 'long'; chai.use(chaiAsPromised); describe('HbarLimitService', function () { const logger = pino(); const register = new Registry(); - const totalBudget = 100_000; + const totalBudget = Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_TOTAL); + const totalBudgetInTinybars = constants.HBAR_RATE_LIMIT_TOTAL.toNumber(); + const limitDuration = constants.HBAR_RATE_LIMIT_DURATION; const mode = constants.EXECUTION_MODE.TRANSACTION; const methodName = 'testMethod'; const mockEthAddress = '0x123'; const mockIpAddress = 'x.x.x'; const mockEstimatedTxFee = 300; - const mockRequestId = getRequestId(); const mockPlanId = uuidV4(randomBytes(16)); + const todayAtMidnight = new Date().setHours(0, 0, 0, 0); - const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: mockIpAddress }); + const requestDetails = new RequestDetails({ requestId: 'hbarLimitServiceTest', ipAddress: mockIpAddress }); let hbarLimitService: HbarLimitService; let hbarSpendingPlanRepositoryStub: sinon.SinonStubbedInstance; @@ -74,6 +77,7 @@ describe('HbarLimitService', function () { logger, register, totalBudget, + limitDuration, ); }); @@ -81,14 +85,14 @@ describe('HbarLimitService', function () { sinon.restore(); }); - function createSpendingPlan(id: string, spentToday: number = 0) { + function createSpendingPlan(id: string, amountSpent: number | Long | Hbar = 0) { return new HbarSpendingPlan({ id, subscriptionType: SubscriptionType.BASIC, createdAt: new Date(), active: true, spendingHistory: [], - spentToday, + amountSpent: amountSpent instanceof Hbar ? Number(amountSpent.toTinybars()) : Number(amountSpent), }); } @@ -101,51 +105,71 @@ describe('HbarLimitService', function () { }); }); - describe('resetLimiter', function () { - const createSpiesForMetricsReset = (fieldName: string) => - Object.values(SubscriptionType).map((subscriptionType) => - sinon.spy(hbarLimitService[fieldName][subscriptionType], 'reset'), - ); + it('should set the reset date properly', () => { + const times = Math.ceil((Date.now() - todayAtMidnight) / limitDuration); + const expectedDate = new Date(todayAtMidnight + limitDuration * times); + const actualDate = hbarLimitService['reset']; + expect(new Date(actualDate)).to.deep.equal(new Date(expectedDate)); + }); + describe('getResetTimestamp', function () { + it('should return the current timestamp plus the limit duration', function () { + const times = Math.ceil((Date.now() - todayAtMidnight) / limitDuration); + const expectedDate = new Date(todayAtMidnight + limitDuration * times); + const actualDate = hbarLimitService['reset']; + expect(new Date(actualDate)).to.deep.equal(new Date(expectedDate)); + expect(hbarLimitService['getResetTimestamp']()).to.deep.equal(expectedDate); + }); + + describe('given a limit duration that is 1 day', function () { + const limitDuration = 24 * 60 * 60 * 1000; // one day + + it('should return tomorrow at midnight', function () { + const hbarLimitService = new HbarLimitService( + hbarSpendingPlanRepositoryStub, + ethAddressHbarSpendingPlanRepositoryStub, + ipAddressHbarSpendingPlanRepositoryStub, + logger, + register, + totalBudget, + limitDuration, + ); + const tomorrow = new Date(Date.now() + limitDuration); + const tomorrowAtMidnight = new Date(tomorrow.setHours(0, 0, 0, 0)); + expect(hbarLimitService['getResetTimestamp']()).to.deep.equal(tomorrowAtMidnight); + }); + }); + }); + + describe('resetLimiter', function () { beforeEach(() => { - hbarSpendingPlanRepositoryStub.resetAllSpentTodayEntries.resolves(); + hbarSpendingPlanRepositoryStub.resetAmountSpentOfAllPlans.resolves(); }); afterEach(() => { - hbarSpendingPlanRepositoryStub.resetAllSpentTodayEntries.restore(); + hbarSpendingPlanRepositoryStub.resetAmountSpentOfAllPlans.restore(); }); - it('should reset the spentToday field of all spending plans', async function () { + it('should reset the amountSpent field of all spending plans', async function () { await hbarLimitService.resetLimiter(requestDetails); - expect(hbarSpendingPlanRepositoryStub.resetAllSpentTodayEntries.called).to.be.true; + expect(hbarSpendingPlanRepositoryStub.resetAmountSpentOfAllPlans.called).to.be.true; }); it('should reset the remaining budget and update the gauge', async function () { // @ts-ignore - hbarLimitService.remainingBudget = 1000; + hbarLimitService.remainingBudget = Hbar.fromTinybars(1000); const setSpy = sinon.spy(hbarLimitService['hbarLimitRemainingGauge'], 'set'); await hbarLimitService.resetLimiter(requestDetails); - expect(hbarLimitService['remainingBudget']).to.equal(totalBudget); - expect(setSpy.calledOnceWith(totalBudget)).to.be.true; - }); - - it('should reset the daily unique spending plans counter', async function () { - const spies = createSpiesForMetricsReset('dailyUniqueSpendingPlansCounter'); - await hbarLimitService.resetLimiter(requestDetails); - spies.forEach((spy) => sinon.assert.calledOnce(spy)); - }); - - it('should reset the average daily spending plan usages gauge', async function () { - const spies = createSpiesForMetricsReset('averageDailySpendingPlanUsagesGauge'); - await hbarLimitService.resetLimiter(requestDetails); - spies.forEach((spy) => sinon.assert.calledOnce(spy)); + expect(hbarLimitService['remainingBudget'].toTinybars().toNumber()).to.eq(totalBudgetInTinybars); + expect(setSpy.calledOnceWith(totalBudgetInTinybars)).to.be.true; }); - it('should set the reset date to the next day at midnight', async function () { - const tomorrow = new Date(Date.now() + HbarLimitService.ONE_DAY_IN_MILLIS); - const expectedResetDate = new Date(tomorrow.setHours(0, 0, 0, 0)); + it('should set the reset date to the current timestamp plus the limit duration', async function () { + const times = Math.ceil((Date.now() - todayAtMidnight) / limitDuration); + const expectedDate = new Date(todayAtMidnight + limitDuration * times); await hbarLimitService.resetLimiter(requestDetails); - expect(hbarLimitService['reset']).to.deep.equal(expectedResetDate); + const resetDate = hbarLimitService['reset']; + expect(new Date(resetDate)).to.deep.equal(new Date(expectedDate)); }); }); @@ -153,14 +177,14 @@ describe('HbarLimitService', function () { describe('based on ethAddress', async function () { it('should return true if the total daily budget is exceeded', async function () { // @ts-ignore - hbarLimitService.remainingBudget = 0; + hbarLimitService.remainingBudget = Hbar.fromTinybars(0); const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); expect(result).to.be.true; }); it('should return true when remainingBudget < estimatedTxFee ', async function () { // @ts-ignore - hbarLimitService.remainingBudget = mockEstimatedTxFee - 1; + hbarLimitService.remainingBudget = Hbar.fromTinybars(mockEstimatedTxFee - 1); const result = await hbarLimitService.shouldLimit( mode, methodName, @@ -197,8 +221,8 @@ describe('HbarLimitService', function () { expect(result).to.be.false; }); - it('should return true if spentToday is exactly at the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC]); + it('should return true if amountSpent is exactly at the limit', async function () { + const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC]); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, planId: mockPlanId, @@ -210,8 +234,11 @@ describe('HbarLimitService', function () { expect(result).to.be.true; }); - it('should return false if spentToday is just below the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - 1); + it('should return false if amountSpent is just below the limit', async function () { + const spendingPlan = createSpendingPlan( + mockPlanId, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(1), + ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, planId: mockPlanId, @@ -223,8 +250,11 @@ describe('HbarLimitService', function () { expect(result).to.be.false; }); - it('should return true if spentToday is just above the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] + 1); + it('should return true if amountSpent is just above the limit', async function () { + const spendingPlan = createSpendingPlan( + mockPlanId, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().add(1), + ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, planId: mockPlanId, @@ -236,10 +266,10 @@ describe('HbarLimitService', function () { expect(result).to.be.true; }); - it('should return true if spentToday + estimatedTxFee is above the limit', async function () { + it('should return true if amountSpent + estimatedTxFee is above the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - mockEstimatedTxFee + 1, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).add(1), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -258,10 +288,10 @@ describe('HbarLimitService', function () { expect(result).to.be.true; }); - it('should return false if spentToday + estimatedTxFee is below the limit', async function () { + it('should return false if amountSpent + estimatedTxFee is below the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - mockEstimatedTxFee - 1, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).sub(1), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -274,10 +304,10 @@ describe('HbarLimitService', function () { expect(result).to.be.false; }); - it('should return false if spentToday + estimatedTxFee is at the limit', async function () { + it('should return false if amountSpent + estimatedTxFee is at the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - mockEstimatedTxFee, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -294,14 +324,14 @@ describe('HbarLimitService', function () { describe('based on ipAddress', async function () { it('should return true if the total daily budget is exceeded', async function () { // @ts-ignore - hbarLimitService.remainingBudget = 0; + hbarLimitService.remainingBudget = Hbar.fromTinybars(0); const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); expect(result).to.be.true; }); it('should return true when remainingBudget < estimatedTxFee ', async function () { // @ts-ignore - hbarLimitService.remainingBudget = mockEstimatedTxFee - 1; + hbarLimitService.remainingBudget = Hbar.fromTinybars(mockEstimatedTxFee - 1); const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails, mockEstimatedTxFee); expect(result).to.be.true; }); @@ -333,8 +363,8 @@ describe('HbarLimitService', function () { expect(result).to.be.false; }); - it('should return true if spentToday is exactly at the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC]); + it('should return true if amountSpent is exactly at the limit', async function () { + const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC]); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, planId: mockPlanId, @@ -346,8 +376,11 @@ describe('HbarLimitService', function () { expect(result).to.be.true; }); - it('should return false if spentToday is just below the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - 1); + it('should return false if amountSpent is just below the limit', async function () { + const spendingPlan = createSpendingPlan( + mockPlanId, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(1), + ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, planId: mockPlanId, @@ -359,8 +392,11 @@ describe('HbarLimitService', function () { expect(result).to.be.false; }); - it('should return true if spentToday is just above the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] + 1); + it('should return true if amountSpent is just above the limit', async function () { + const spendingPlan = createSpendingPlan( + mockPlanId, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().add(1), + ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, planId: mockPlanId, @@ -372,10 +408,10 @@ describe('HbarLimitService', function () { expect(result).to.be.true; }); - it('should return true if spentToday + estimatedTxFee is above the limit', async function () { + it('should return true if amountSpent + estimatedTxFee is above the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - mockEstimatedTxFee + 1, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).add(1), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -388,10 +424,10 @@ describe('HbarLimitService', function () { expect(result).to.be.true; }); - it('should return false if spentToday + estimatedTxFee is below the limit', async function () { + it('should return false if amountSpent + estimatedTxFee is below the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - mockEstimatedTxFee - 1, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).sub(1), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -404,10 +440,10 @@ describe('HbarLimitService', function () { expect(result).to.be.false; }); - it('should return false if spentToday + estimatedTxFee is at the limit', async function () { + it('should return false if amountSpent + estimatedTxFee is at the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.DAILY_LIMITS[SubscriptionType.BASIC] - mockEstimatedTxFee, + HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -530,8 +566,7 @@ describe('HbarLimitService', function () { describe('createBasicSpendingPlan', function () { const testCreateBasicSpendingPlan = async (ethAddress: string, ipAddress?: string) => { - const requestDetails = new RequestDetails({ requestId: 'hbarLimterTest', ipAddress: ipAddress ? ipAddress : '' }); - console.log('requestDetails', requestDetails); + const requestDetails = new RequestDetails({ requestId: 'hbarLimitServiceTest', ipAddress: ipAddress ?? '' }); const newSpendingPlan = createSpendingPlan(mockPlanId); hbarSpendingPlanRepositoryStub.create.resolves(newSpendingPlan); ethAddressHbarSpendingPlanRepositoryStub.save.resolves(); @@ -567,7 +602,7 @@ describe('HbarLimitService', function () { describe('addExpense', function () { const testAddExpense = async (ethAddress: string, ipAddress: string, expense: number = 100) => { - const otherPlanUsedToday = createSpendingPlan(uuidV4(randomBytes(16)), 200); + const otherPlanOfTheSameTier = createSpendingPlan(uuidV4(randomBytes(16)), 200); const existingSpendingPlan = createSpendingPlan(mockPlanId, 0); if (ethAddress) { ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ @@ -583,12 +618,12 @@ describe('HbarLimitService', function () { hbarSpendingPlanRepositoryStub.create.resolves(existingSpendingPlan); } hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(existingSpendingPlan); - hbarSpendingPlanRepositoryStub.addAmountToSpentToday.resolves(); + hbarSpendingPlanRepositoryStub.addToAmountSpent.resolves(); hbarSpendingPlanRepositoryStub.findAllActiveBySubscriptionType.resolves([ - otherPlanUsedToday, + otherPlanOfTheSameTier, { ...existingSpendingPlan, - spentToday: expense, + amountSpent: expense, spendingHistory: [{ amount: expense, timestamp: new Date() }], }, ]); @@ -607,16 +642,15 @@ describe('HbarLimitService', function () { await hbarLimitService.addExpense(expense, ethAddress, requestDetails); - expect(hbarSpendingPlanRepositoryStub.addAmountToSpentToday.calledOnceWith(mockPlanId, expense)).to.be.true; - // @ts-ignore - expect(hbarLimitService.remainingBudget).to.equal(hbarLimitService.totalBudget - expense); - // @ts-ignore - expect((await hbarLimitService.hbarLimitRemainingGauge.get()).values[0].value).to.equal( - // @ts-ignore - hbarLimitService.totalBudget - expense, + expect(hbarSpendingPlanRepositoryStub.addToAmountSpent.calledOnceWith(mockPlanId, expense)).to.be.true; + expect(hbarLimitService['remainingBudget'].toTinybars().toNumber()).to.eq( + hbarLimitService['totalBudget'].toTinybars().sub(expense).toNumber(), + ); + expect((await hbarLimitService['hbarLimitRemainingGauge'].get()).values[0].value).to.equal( + hbarLimitService['totalBudget'].toTinybars().sub(expense).toNumber(), ); await Promise.all(updateAverageDailyUsagePerSubscriptionTypeSpy.returnValues); - const expectedAverageUsage = Math.round((otherPlanUsedToday.spentToday + expense) / 2); + const expectedAverageUsage = Math.round((otherPlanOfTheSameTier.amountSpent + expense) / 2); sinon.assert.calledOnceWithExactly(setAverageDailySpendingPlanUsagesGaugeSpy, expectedAverageUsage); sinon.assert.calledOnceWithExactly(incDailyUniqueSpendingPlansCounterSpy, 1); }; @@ -668,7 +702,7 @@ describe('HbarLimitService', function () { planId: mockPlanId, }); hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(createSpendingPlan(mockPlanId)); - hbarSpendingPlanRepositoryStub.addAmountToSpentToday.rejects(new Error('Failed to add expense')); + hbarSpendingPlanRepositoryStub.addToAmountSpent.rejects(new Error('Failed to add expense')); await expect(hbarLimitService.addExpense(100, mockEthAddress, requestDetails)).to.be.eventually.rejectedWith( 'Failed to add expense', @@ -679,7 +713,7 @@ describe('HbarLimitService', function () { describe('isDailyBudgetExceeded', function () { const testIsDailyBudgetExceeded = async (remainingBudget: number, expected: boolean) => { // @ts-ignore - hbarLimitService.remainingBudget = remainingBudget; + hbarLimitService.remainingBudget = Hbar.fromTinybars(remainingBudget); await expect( hbarLimitService['isDailyBudgetExceeded'](mode, methodName, undefined, requestDetails), ).to.eventually.equal(expected); @@ -693,19 +727,6 @@ describe('HbarLimitService', function () { await testIsDailyBudgetExceeded(-1, true); }); - it('should handle errors when adding expense fails', async function () { - ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ - ethAddress: mockEthAddress, - planId: mockPlanId, - }); - hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(createSpendingPlan(mockPlanId)); - hbarSpendingPlanRepositoryStub.addAmountToSpentToday.rejects(new Error('Failed to add expense')); - - await expect(hbarLimitService.addExpense(100, mockEthAddress, requestDetails)).to.be.eventually.rejectedWith( - 'Failed to add expense', - ); - }); - it('should return false when the remaining budget is greater than zero', async function () { await testIsDailyBudgetExceeded(100, false); }); diff --git a/packages/relay/tests/lib/services/metricService/metricService.spec.ts b/packages/relay/tests/lib/services/metricService/metricService.spec.ts index 354a791bb5..e03b0b8ca4 100644 --- a/packages/relay/tests/lib/services/metricService/metricService.spec.ts +++ b/packages/relay/tests/lib/services/metricService/metricService.spec.ts @@ -133,7 +133,7 @@ describe('Metric Service', function () { mock = new MockAdapter(instance); const duration = constants.HBAR_RATE_LIMIT_DURATION; - const total = constants.HBAR_RATE_LIMIT_TINYBAR; + const total = constants.HBAR_RATE_LIMIT_TOTAL.toNumber(); hbarLimiter = new HbarLimit(logger.child({ name: 'hbar-rate-limit' }), Date.now(), total, duration, registry); eventEmitter = new EventEmitter(); From 43e3ddf8774e8e321ba295b8320ac53117f0eb22 Mon Sep 17 00:00:00 2001 From: Eric Badiere Date: Tue, 8 Oct 2024 09:33:20 -0600 Subject: [PATCH 14/38] =?UTF-8?q?fix:=20Skip=20issue=20matching=20check=20?= =?UTF-8?q?for=20thirdparty=20build=20dependency=20librar=E2=80=A6=20(#299?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Skip issue matching check for thirdparty build dependency libraries. We do not control them. Signed-off-by: ebadiere * fix: Now validates PRs listed in description by matching contributors in the repository and not matching PRs with issues from third party projects. Signed-off-by: ebadiere --------- Signed-off-by: ebadiere --- .github/scripts/check-pr.js | 271 +++++++++++++++++++++++++++++++----- 1 file changed, 236 insertions(+), 35 deletions(-) diff --git a/.github/scripts/check-pr.js b/.github/scripts/check-pr.js index 91c1455d99..6d82f9db44 100644 --- a/.github/scripts/check-pr.js +++ b/.github/scripts/check-pr.js @@ -5,28 +5,37 @@ const { GITHUB_REPOSITORY, GITHUB_PR_NUMBER } = process.env; const [owner, repo] = GITHUB_REPOSITORY.split('/'); -async function getPRDetails() { - const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${GITHUB_PR_NUMBER}`; - const response = await axios.get(url, { - headers: { - Authorization: `token ${githubToken}` +async function getPRDetails(prNumber) { + const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`; + try { + const response = await axios.get(url, { + headers: { + Authorization: `token ${githubToken}`, + }, + }); + return response.data; + } catch (error) { + if (error.response && error.response.status === 404) { + console.log(`PR #${prNumber} not found in repository ${owner}/${repo}, skipping...`); + return null; + } else { + throw error; } - }); - return response.data; + } } -async function getIssueDetails(issueNumber) { +async function getIssueDetails(issueOwner, issueRepo, issueNumber) { try { - const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`; + const url = `https://api.github.com/repos/${issueOwner}/${issueRepo}/issues/${issueNumber}`; const response = await axios.get(url, { headers: { - Authorization: `token ${githubToken}` - } + Authorization: `token ${githubToken}`, + }, }); return response.data; } catch (error) { if (error.response && error.response.status === 404) { - console.log(`Issue #${issueNumber} not found, skipping...`); + console.log(`Issue #${issueNumber} not found in repository ${issueOwner}/${issueRepo}, skipping...`); return null; } else { throw error; @@ -34,44 +43,236 @@ async function getIssueDetails(issueNumber) { } } -async function run() { - try { - const pr = await getPRDetails(); - const { labels: prLabels, milestone: prMilestone, body: prBody } = pr; +async function getContributors() { + const url = `https://api.github.com/repos/${owner}/${repo}/contributors`; + const response = await axios.get(url, { + headers: { + Authorization: `token ${githubToken}`, + }, + }); + return response.data; +} + +function stripHTMLTags(text) { + return text.replace(/<\/?[^>]+(>|$)/g, ''); +} + +function removeCodeBlocks(text) { + // Remove fenced code blocks (triple backticks or tildes) + text = text.replace(/```[\s\S]*?```/g, ''); + text = text.replace(/~~~[\s\S]*?~~~/g, ''); + // Remove inline code (single backticks) + text = text.replace(/`[^`]*`/g, ''); + return text; +} + +function extractPRReferences(text) { + // Regex to match PR references with any number of digits + const prRegex = + /(?:^|\s)(?:Fixes|Closes|Resolves|See|PR|Pull Request)?\s*(?:https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/(\d+)|([\w.-]+)\/([\w.-]+)#(\d+)|#(\d+))(?!\w)/gm; + const matches = []; + let match; + while ((match = prRegex.exec(text)) !== null) { + const refOwner = match[1] || match[4] || owner; + const refRepo = match[2] || match[5] || repo; + const prNumber = match[3] || match[6] || match[7]; + matches.push({ + owner: refOwner, + repo: refRepo, + prNumber, + }); + } + return matches; +} + +function extractIssueReferences(text) { + // Regex to match issue references with any number of digits + // Supports 'Fixes #123', 'owner/repo#123', 'https://github.com/owner/repo/issues/123' + const issueRegex = + /(?:^|\s)(?:Fixes|Closes|Resolves|See|Issue)?\s*(?:(?:https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/issues\/(\d+))|([\w.-]+)\/([\w.-]+)#(\d+)|#(\d+))(?!\w)/gm; + const issues = []; + let match; + while ((match = issueRegex.exec(text)) !== null) { + const issueOwner = match[1] || match[4] || owner; + const issueRepo = match[2] || match[5] || repo; + const issueNumber = match[3] || match[6] || match[7]; + issues.push({ + owner: issueOwner, + repo: issueRepo, + issueNumber, + }); + } + return issues; +} + +function cleanText(text) { + let cleanText = text; + cleanText = stripHTMLTags(cleanText); + cleanText = removeCodeBlocks(cleanText); + return cleanText; +} + +async function checkPRLabelsAndMilestone(pr) { + const { labels: prLabels, milestone: prMilestone } = pr; + + if (!prLabels || prLabels.length === 0) { + throw new Error('The PR has no labels.'); + } + if (!prMilestone) { + throw new Error('The PR has no milestone.'); + } +} - if (prLabels.length === 0) { - throw new Error('The PR has no labels.'); +function isDependabotPR(pr) { + return pr.user.login === 'dependabot[bot]'; +} + +async function processIssueReferencesInText(text) { + const issueReferences = extractIssueReferences(text); + + let hasValidIssueReference = false; + + if (issueReferences.length > 0) { + for (const issueRef of issueReferences) { + // Only process issues from the same repository + if (issueRef.owner === owner && issueRef.repo === repo) { + hasValidIssueReference = true; + const issue = await getIssueDetails(issueRef.owner, issueRef.repo, issueRef.issueNumber); + if (issue) { + const { labels: issueLabels, milestone: issueMilestone } = issue; + + if (!issueLabels || issueLabels.length === 0) { + throw new Error(`Associated issue #${issueRef.issueNumber} has no labels.`); + } + if (!issueMilestone) { + throw new Error(`Associated issue #${issueRef.issueNumber} has no milestone.`); + } + } + } else { + console.log( + `Issue #${issueRef.issueNumber} is from a different repository (${issueRef.owner}/${issueRef.repo}), skipping...` + ); + } } - if (!prMilestone) { - throw new Error('The PR has no milestone.'); + + if (!hasValidIssueReference) { + throw new Error('The PR description must reference at least one issue from the current repository.'); + } else { + console.log('All associated issues have labels and milestones.'); } + } else { + throw new Error('The PR description must reference at least one issue from the current repository.'); + } +} - const issueNumberMatches = prBody.match(/#(\d+)/g); +async function processPRReferencesInText(text, contributors) { + const prReferences = extractPRReferences(text); - if (!issueNumberMatches) { - console.log('No associated issues found in PR description.'); - } else { - for (const match of issueNumberMatches) { - const issueNumber = match.replace('#', ''); - const issue = await getIssueDetails(issueNumber); - if(issue) { - const {labels: issueLabels, milestone: issueMilestone} = issue; - - if (issueLabels.length === 0) { - throw new Error(`Associated issue #${issueNumber} has no labels.`); + if (prReferences.length === 0) { + console.log('No associated PRs found in PR description.'); + } else { + for (const prRef of prReferences) { + // Only process PRs from the same repository + if (prRef.owner === owner && prRef.repo === repo) { + await processReferencedPR(prRef, contributors); + } else { + console.log( + `PR #${prRef.prNumber} is from a different repository (${prRef.owner}/${prRef.repo}), skipping...` + ); + // Skip processing issue references from external PRs + } + } + } +} + +async function processReferencedPR(prRef, contributors) { + // Attempt to fetch the PR to validate its existence + const referencedPR = await getPRDetails(prRef.prNumber); + if (!referencedPR) { + console.log(`PR #${prRef.prNumber} does not exist, skipping...`); + return; // Skip if PR not found + } + + const authorLogin = referencedPR.user.login; + + const isContributor = contributors.some((contributor) => contributor.login === authorLogin); + + if (!isContributor) { + console.log( + `PR author ${authorLogin} is not a contributor, skipping issue matching for PR #${prRef.prNumber}.` + ); + return; + } + + // Clean the referenced PR body + const refPrBody = cleanText(referencedPR.body); + + // Extract issue references from the referenced PR description + const refIssueReferences = extractIssueReferences(refPrBody); + + if (refIssueReferences.length === 0) { + console.log(`No associated issues found in PR #${prRef.prNumber} description.`); + } else { + for (const issueRef of refIssueReferences) { + // Only process issues from the same repository + if (issueRef.owner === owner && issueRef.repo === repo) { + const issue = await getIssueDetails( + issueRef.owner, + issueRef.repo, + issueRef.issueNumber + ); + if (issue) { + const { labels: issueLabels, milestone: issueMilestone } = issue; + + if (!issueLabels || issueLabels.length === 0) { + throw new Error( + `Associated issue #${issueRef.issueNumber} has no labels.` + ); } if (!issueMilestone) { - throw new Error(`Associated issue #${issueNumber} has no milestone.`); + throw new Error( + `Associated issue #${issueRef.issueNumber} has no milestone.` + ); } } + } else { + console.log( + `Issue #${issueRef.issueNumber} is from a different repository (${issueRef.owner}/${issueRef.repo}), skipping...` + ); } } + console.log( + `PR #${prRef.prNumber} and all associated issues have labels and milestones.` + ); + } +} + +async function run() { + try { + const pr = await getPRDetails(GITHUB_PR_NUMBER); + if (!pr) { + throw new Error(`PR #${GITHUB_PR_NUMBER} not found.`); + } + + await checkPRLabelsAndMilestone(pr); + + if (isDependabotPR(pr)) { + console.log('Dependabot PR detected. Skipping issue reference requirement.'); + } else { + const cleanBody = cleanText(pr.body); + await processIssueReferencesInText(cleanBody); + } + + const contributors = await getContributors(); + + const cleanBody = cleanText(pr.body); + await processPRReferencesInText(cleanBody, contributors); - console.log('PR and all associated issues have labels and milestones.'); + console.log('All checks completed.'); } catch (error) { console.error(error.message); process.exit(1); } } -run(); +run(); \ No newline at end of file From 229ee756aad8a16bee2293ad525b31feec6740f1 Mon Sep 17 00:00:00 2001 From: Eric Badiere Date: Tue, 8 Oct 2024 09:34:03 -0600 Subject: [PATCH 15/38] fix: SDKClient timeouts are not getting logged with requestIds. (#3061) * fix: Enhanced logged warning to include SDKClient error message and bumped up SDK_REQUEST_TIMEOUT for CI. Signed-off-by: ebadiere * fix: Removed the increate in sdkClient timeout. Signed-off-by: ebadiere --------- Signed-off-by: ebadiere --- packages/relay/src/lib/clients/sdkClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index cdbb3a7f21..6a6e9f20f7 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -738,7 +738,7 @@ export class SDKClient { this.logger.warn( sdkClientError, - `${requestDetails.formattedRequestId} Fail to execute ${txConstructorName} transaction: transactionId=${transaction.transactionId}, callerName=${callerName}, status=${sdkClientError.status}(${sdkClientError.status._code})`, + `${requestDetails.formattedRequestId} Fail to execute ${txConstructorName} transaction: transactionId=${transaction.transactionId}, callerName=${callerName}, status=${sdkClientError.status}(${sdkClientError.status._code}) message=${sdkClientError.message}`, ); if (!transactionResponse) { From 9f86d43cb3bdd33eec918b9c32676cbb9d46625f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:53:33 -0600 Subject: [PATCH 16/38] build(deps): bump body-parser and express in /tools/truffle-example (#2958) Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `body-parser` from 1.20.2 to 1.20.3 - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3) Updates `express` from 4.19.2 to 4.20.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0) --- updated-dependencies: - dependency-name: body-parser dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: Eric Badiere Signed-off-by: ebadiere Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Eric Badiere From 0ba216213cb34345cfdd8e1c4fd907c793d9091a Mon Sep 17 00:00:00 2001 From: Eric Badiere Date: Tue, 8 Oct 2024 17:15:36 -0600 Subject: [PATCH 17/38] fix: pr check ignore issues check external projects (#3077) * fix: Skip issue matching check for thirdparty build dependency libraries. We do not control them. Signed-off-by: ebadiere * fix: Now validates PRs listed in description by matching contributors in the repository and not matching PRs with issues from third party projects. Signed-off-by: ebadiere * fix: Added issue check bypass for snyk PRs. Signed-off-by: ebadiere --------- Signed-off-by: ebadiere Signed-off-by: Eric Badiere --- .github/scripts/check-pr.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/scripts/check-pr.js b/.github/scripts/check-pr.js index 6d82f9db44..da90286940 100644 --- a/.github/scripts/check-pr.js +++ b/.github/scripts/check-pr.js @@ -123,8 +123,8 @@ async function checkPRLabelsAndMilestone(pr) { } } -function isDependabotPR(pr) { - return pr.user.login === 'dependabot[bot]'; +function isDependabotOrSnykPR(pr) { + return ((pr.user.login === 'dependabot[bot]') || (pr.user.login === 'swirlds-automation')); } async function processIssueReferencesInText(text) { @@ -256,8 +256,9 @@ async function run() { await checkPRLabelsAndMilestone(pr); - if (isDependabotPR(pr)) { - console.log('Dependabot PR detected. Skipping issue reference requirement.'); + if (isDependabotOrSnykPR(pr)) { + console.log('Dependabot or snyk PR detected. Skipping issue reference requirement.'); + return; } else { const cleanBody = cleanText(pr.body); await processIssueReferencesInText(cleanBody); From 670e5fa9cfd2938e8f88228a7c895ebb414658e1 Mon Sep 17 00:00:00 2001 From: Swirlds Automation <52682028+swirlds-automation@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:48:16 -0500 Subject: [PATCH 18/38] build(deps): [Snyk] Upgrade @hashgraph/sdk from 2.50.0 to 2.51.0 (#3069) fix: upgrade @hashgraph/sdk from 2.50.0 to 2.51.0 Snyk has created this PR to upgrade @hashgraph/sdk from 2.50.0 to 2.51.0. See this package in npm: @hashgraph/sdk See this project in Snyk: https://app.snyk.io/org/json-rpc-relay/project/baf29319-5e17-4aff-8e1f-4108d3e10c45?utm_source=github&utm_medium=referral&page=upgrade-pr Signed-off-by: ebadiere Co-authored-by: snyk-bot Co-authored-by: ebadiere --- dapp-example/package-lock.json | 47 +++++++++++++++++----------------- dapp-example/package.json | 2 +- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/dapp-example/package-lock.json b/dapp-example/package-lock.json index 8fdc05ff19..6a34238ffb 100644 --- a/dapp-example/package-lock.json +++ b/dapp-example/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@hashgraph/sdk": "^2.50.0", + "@hashgraph/sdk": "^2.51.0", "@mui/material": "^5.16.7", "ethers": "^6.13.2", "react": "^18.3.1", @@ -3263,9 +3263,9 @@ } }, "node_modules/@hashgraph/cryptography": { - "version": "1.4.8-beta.7", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.7.tgz", - "integrity": "sha512-FoffVo/UdhkqQUhbh/G+0lcmBYexvVs/RQqilyhzCuNzPB9lRWrJmKPT1pesFoWVC6j3kbbFcWezr9xbm9WrUA==", + "version": "1.4.8-beta.8", + "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", + "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", "license": "Apache-2.0", "dependencies": { "asn1js": "^3.0.5", @@ -3301,9 +3301,10 @@ } }, "node_modules/@hashgraph/proto": { - "version": "2.15.0-beta.3", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.3.tgz", - "integrity": "sha512-/95cydqBQRaO1gagBOenNpcfJIcnRx+vzefhuzSFNp4pfl0AM3oXau39hM2raKLhFHoKZysSjkXkMp24AaCE5w==", + "version": "2.15.0-beta.4", + "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", + "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", + "license": "Apache-2.0", "dependencies": { "long": "^4.0.0", "protobufjs": "^7.2.5" @@ -3313,9 +3314,9 @@ } }, "node_modules/@hashgraph/sdk": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.50.0.tgz", - "integrity": "sha512-TsklSXxXpyinVo4hOENOtm4e1jgYALeY8S8W+0SlMlbya/azzW0UZcJuriRElFGZhFzE1uqFTc7cN0ZMP059mQ==", + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", + "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", "license": "Apache-2.0", "dependencies": { "@ethersproject/abi": "^5.7.0", @@ -3323,8 +3324,8 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/rlp": "^5.7.0", "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.7", - "@hashgraph/proto": "2.15.0-beta.3", + "@hashgraph/cryptography": "1.4.8-beta.8", + "@hashgraph/proto": "2.15.0-beta.4", "axios": "^1.6.4", "bignumber.js": "^9.1.1", "bn.js": "^5.1.1", @@ -29257,9 +29258,9 @@ } }, "@hashgraph/cryptography": { - "version": "1.4.8-beta.7", - "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.7.tgz", - "integrity": "sha512-FoffVo/UdhkqQUhbh/G+0lcmBYexvVs/RQqilyhzCuNzPB9lRWrJmKPT1pesFoWVC6j3kbbFcWezr9xbm9WrUA==", + "version": "1.4.8-beta.8", + "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.8.tgz", + "integrity": "sha512-RK1SL5B6IGsYM4HyepC24rsMGr1qOvHFbNiJPlK+AGV5lApjxGpyNVWC80GusYqwRD9B1ljw43wJBSbHdaZIgw==", "requires": { "asn1js": "^3.0.5", "bignumber.js": "^9.1.1", @@ -29275,26 +29276,26 @@ } }, "@hashgraph/proto": { - "version": "2.15.0-beta.3", - "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.3.tgz", - "integrity": "sha512-/95cydqBQRaO1gagBOenNpcfJIcnRx+vzefhuzSFNp4pfl0AM3oXau39hM2raKLhFHoKZysSjkXkMp24AaCE5w==", + "version": "2.15.0-beta.4", + "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", + "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", "requires": { "long": "^4.0.0", "protobufjs": "^7.2.5" } }, "@hashgraph/sdk": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.50.0.tgz", - "integrity": "sha512-TsklSXxXpyinVo4hOENOtm4e1jgYALeY8S8W+0SlMlbya/azzW0UZcJuriRElFGZhFzE1uqFTc7cN0ZMP059mQ==", + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.51.0.tgz", + "integrity": "sha512-+RtBs8wmPr9g93fDSMCnQnAX27w+i5itw0bbYDFiAcFZ0F3Vb+TyxdPw7jfcHRgFDvwkyblEsPBzG7DO2lt5Ow==", "requires": { "@ethersproject/abi": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/rlp": "^5.7.0", "@grpc/grpc-js": "1.8.2", - "@hashgraph/cryptography": "1.4.8-beta.7", - "@hashgraph/proto": "2.15.0-beta.3", + "@hashgraph/cryptography": "1.4.8-beta.8", + "@hashgraph/proto": "2.15.0-beta.4", "axios": "^1.6.4", "bignumber.js": "^9.1.1", "bn.js": "^5.1.1", diff --git a/dapp-example/package.json b/dapp-example/package.json index cccd3a99aa..a1c8d95457 100644 --- a/dapp-example/package.json +++ b/dapp-example/package.json @@ -5,7 +5,7 @@ "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@hashgraph/sdk": "^2.50.0", + "@hashgraph/sdk": "^2.51.0", "@mui/material": "^5.16.7", "ethers": "^6.13.2", "react": "^18.3.1", From dbe5fc5a5893f22b94751c4b8b7b6d11542d9e5a Mon Sep 17 00:00:00 2001 From: Nadezhda Popova <48063261+nadezhdapopovaa@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:05:25 +0300 Subject: [PATCH 19/38] feat: change units of the value in eth gettransactionbyhash result (#3080) * fix: change unit of transaction value result Signed-off-by: Nadezhda Popova * chore: reusable tinybarsToWeibars method and tests Signed-off-by: Nadezhda Popova --------- Signed-off-by: Nadezhda Popova Co-authored-by: Nadezhda Popova --- packages/relay/src/formatters.ts | 11 +- packages/relay/src/lib/constants.ts | 1 + packages/relay/tests/lib/eth/eth-config.ts | 8 +- packages/relay/tests/lib/formatters.spec.ts | 26 ++++ packages/server/tests/helpers/assertions.ts | 7 +- scripts/signTransaction.js | 125 ++++++++++++++++++++ 6 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 scripts/signTransaction.js diff --git a/packages/relay/src/formatters.ts b/packages/relay/src/formatters.ts index 3087c9fe5d..55084ddf34 100644 --- a/packages/relay/src/formatters.ts +++ b/packages/relay/src/formatters.ts @@ -176,7 +176,7 @@ const formatContractResult = (cr: any) => { transactionIndex: nullableNumberTo0x(cr.transaction_index), type: cr.type === null ? '0x0' : nanOrNumberTo0x(cr.type), v: cr.v === null ? '0x0' : nanOrNumberTo0x(cr.v), - value: nanOrNumberTo0x(cr.amount), + value: nanOrNumberTo0x(tinybarsToWeibars(cr.amount)), // for legacy EIP155 with tx.chainId=0x0, mirror-node will return a '0x' (EMPTY_HEX) value for contract result's chain_id // which is incompatibile with certain tools (i.e. foundry). By setting this field, chainId, to undefined, the end jsonrpc // object will leave out this field, which is the proper behavior for other tools to be compatible with. @@ -301,6 +301,14 @@ const getFunctionSelector = (data?: string): string => { return data.replace(/^0x/, '').substring(0, 8); }; +const tinybarsToWeibars = (value: number | null) => { + if (value && value < 0) throw new Error('Invalid value - cannot pass negative number'); + if (value && value > constants.TOTAL_SUPPLY_TINYBARS) + throw new Error('Value cannot be more than the total supply of tinybars in the blockchain'); + + return value == null ? null : value * constants.TINYBAR_TO_WEIBAR_COEF; +}; + export { hashNumber, formatRequestIdMessage, @@ -328,4 +336,5 @@ export { ASCIIToHex, getFunctionSelector, mapKeysAndValues, + tinybarsToWeibars, }; diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index e86cf75739..abcbf5e6af 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -71,6 +71,7 @@ export enum CallType { export default { HBAR_TO_TINYBAR_COEF: 100_000_000, TINYBAR_TO_WEIBAR_COEF: 10_000_000_000, + TOTAL_SUPPLY_TINYBARS: 5_000_000_000_000_000_000, // 131072 bytes are 128kbytes SEND_RAW_TRANSACTION_SIZE_LIMIT: process.env.SEND_RAW_TRANSACTION_SIZE_LIMIT ? parseInt(process.env.SEND_RAW_TRANSACTION_SIZE_LIMIT) diff --git a/packages/relay/tests/lib/eth/eth-config.ts b/packages/relay/tests/lib/eth/eth-config.ts index 1eb936c576..f79693054c 100644 --- a/packages/relay/tests/lib/eth/eth-config.ts +++ b/packages/relay/tests/lib/eth/eth-config.ts @@ -18,6 +18,7 @@ * */ import { + defaultDetailedContractResultByHash, defaultEvmAddress, defaultLogs1, defaultLogs2, @@ -26,7 +27,7 @@ import { mockData, toHex, } from '../../helpers'; -import { numberTo0x } from '../../../dist/formatters'; +import { numberTo0x, nanOrNumberTo0x } from '../../../dist/formatters'; import constants from '../../../src/lib/constants'; export const BLOCK_TRANSACTION_COUNT = 77; @@ -601,6 +602,9 @@ export const BLOCK_BY_HASH_FROM_RELAY = { }; export const CONTRACT_EVM_ADDRESS = '0xd8db0b1dbf8ba6721ef5256ad5fe07d72d1d04b9'; export const DEFAULT_TX_HASH = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392'; +const DEFAULT_TRANSACTION_VALUE = nanOrNumberTo0x( + defaultDetailedContractResultByHash.amount * constants.TINYBAR_TO_WEIBAR_COEF, +); export const DEFAULT_TRANSACTION = { accessList: [], blockHash: '0xd693b532a80fed6392b428604171fb32fdbf953728a3a7ecc7d4062b1652c042', @@ -620,7 +624,7 @@ export const DEFAULT_TRANSACTION = { transactionIndex: '0x1', type: 2, v: 1, - value: '0x77359400', + value: DEFAULT_TRANSACTION_VALUE, }; export const DEFAULT_DETAILED_CONTRACT_RESULT_BY_HASH = { address: '0xd8db0b1dbf8ba6721ef5256ad5fe07d72d1d04b9', diff --git a/packages/relay/tests/lib/formatters.spec.ts b/packages/relay/tests/lib/formatters.spec.ts index bb21d85387..cd0c81d413 100644 --- a/packages/relay/tests/lib/formatters.spec.ts +++ b/packages/relay/tests/lib/formatters.spec.ts @@ -43,6 +43,7 @@ import { toNullIfEmptyHex, trimPrecedingZeros, weibarHexToTinyBarInt, + tinybarsToWeibars, } from '../../src/formatters'; import constants from '../../src/lib/constants'; import { BigNumber as BN } from 'bignumber.js'; @@ -737,4 +738,29 @@ describe('Formatters', () => { expect(result).to.deep.equal({ A: '1', B: '2', C: '3' }); }); }); + + describe('tinybarsToWeibars', () => { + it('should convert tinybars to weibars', () => { + expect(tinybarsToWeibars(10)).to.eql(100000000000); + }); + + it('should return null if null is passed', () => { + expect(tinybarsToWeibars(null)).to.eql(null); + }); + + it('should return 0 for 0 input', () => { + expect(tinybarsToWeibars(0)).to.eql(0); + }); + + it('should throw an error when value is smaller than 0', () => { + expect(() => tinybarsToWeibars(-10)).to.throw(Error, 'Invalid value - cannot pass negative number'); + }); + + it('should throw an error when value is larger than the total supply of tinybars', () => { + expect(() => tinybarsToWeibars(constants.TOTAL_SUPPLY_TINYBARS * 10)).to.throw( + Error, + 'Value cannot be more than the total supply of tinybars in the blockchain', + ); + }); + }); }); diff --git a/packages/server/tests/helpers/assertions.ts b/packages/server/tests/helpers/assertions.ts index 1a92dc1817..afa0a90326 100644 --- a/packages/server/tests/helpers/assertions.ts +++ b/packages/server/tests/helpers/assertions.ts @@ -190,9 +190,10 @@ export default class Assertions { relayResponse.transactionIndex, "Assert transaction: 'transactionIndex' should equal mirrorNode response", ).to.eq(ethers.toQuantity(mirrorNodeResponse.transaction_index)); - expect(relayResponse.value, "Assert transaction: 'value' should equal mirrorNode response").to.eq( - ethers.toQuantity(mirrorNodeResponse.amount), - ); + expect( + relayResponse.value, + "Assert transaction: 'value' should equal mirrorNode response converted in weibar", + ).to.eq(ethers.toQuantity(BigInt(mirrorNodeResponse.amount * constants.TINYBAR_TO_WEIBAR_COEF))); } static transactionReceipt = (transactionReceipt, mirrorResult, effectiveGas) => { diff --git a/scripts/signTransaction.js b/scripts/signTransaction.js new file mode 100644 index 0000000000..4b80659c6b --- /dev/null +++ b/scripts/signTransaction.js @@ -0,0 +1,125 @@ +require('ts-node/register'); +const helper = require('../packages/relay/tests/helpers.ts'); + +const gasPrice = '0x2C68AF0BB14000'; +const gasLimit = "0x493E0"; +const value = '0xDE0B6B3A7640000'; +const gasPrice1 = '0xa53f4c3c00'; +const defaultGasPrice = '0xA54F4C3C00'; +const bytecode ="0x6080604052348015600f57600080fd5b50609e8061001e6000396000f3fe608060405260043610602a5760003560e01c80635c36b18614603557806383197ef014605557600080fd5b36603057005b600080fd5b348015604057600080fd5b50600160405190815260200160405180910390f35b348015606057600080fd5b50606633ff5b00fea2646970667358221220886a6d6d6c88bcfc0063129ca2391a3d98aee75ad7fe3e870ec6679215456a3964736f6c63430008090033" +const estimateGassContractBytecode = "0x6080604052600160005534801561001557600080fd5b5060405161002290610064565b604051809103906000f08015801561003e573d6000803e3d6000fd5b50600180546001600160a01b0319166001600160a01b0392909216919091179055610070565b60938061105583390190565b610fd68061007f6000396000f3fe6080604052600436106101bb5760003560e01c806380f009b6116100ec578063ddf363d71161008a578063ec3e88cf11610064578063ec3e88cf14610461578063f96757d1146104a0578063fa5e414e146104b3578063ffaf0890146104d357600080fd5b8063ddf363d71461041b578063e080b4aa14610421578063e7df080e1461044157600080fd5b8063bbbfb986116100c6578063bbbfb986146103be578063c648049d146103d3578063d737d0c7146103f3578063dbb6f04a1461040657600080fd5b806380f009b61461036b57806383197ef01461038b578063bb376a961461039e57600080fd5b80635256b99d116101595780636e6662b9116101335780636e6662b914610301578063700799631461031657806374259795146103365780637df6ee271461034b57600080fd5b80635256b99d146102b65780635c929889146102d657806361bc221a146102eb57600080fd5b80633ec4de35116101955780633ec4de351461023957806341f32f0c146102615780634929af371461028157806351be4eaa146102a157600080fd5b80630c772ca5146101c75780630ec1551d146101f957806319a6e3d51461021757600080fd5b366101c257005b600080fd5b3480156101d357600080fd5b506101dc6104f3565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561020557600080fd5b5060045b6040519081526020016101f0565b34801561022357600080fd5b50610237610232366004610d7a565b610565565b005b34801561024557600080fd5b50610209610254366004610daa565b6001600160a01b03163190565b34801561026d57600080fd5b5061023761027c366004610daa565b610609565b34801561028d57600080fd5b5061023761029c366004610d7a565b61068d565b3480156102ad57600080fd5b50610209610742565b3480156102c257600080fd5b506102376102d1366004610dc7565b61074a565b3480156102e257600080fd5b506101dc610770565b3480156102f757600080fd5b5061020960005481565b34801561030d57600080fd5b506101dc6107d8565b34801561032257600080fd5b50610237610331366004610daa565b61080a565b34801561034257600080fd5b50610237610885565b34801561035757600080fd5b50610237610366366004610daa565b610939565b34801561037757600080fd5b50610237610386366004610d7a565b6109b2565b34801561039757600080fd5b5061023733ff5b3480156103aa57600080fd5b506102096103b9366004610de0565b610a65565b3480156103ca57600080fd5b506101dc610b42565b3480156103df57600080fd5b506102376103ee366004610dc7565b600055565b3480156103ff57600080fd5b50336101dc565b34801561041257600080fd5b506101dc610baa565b34610209565b34801561042d57600080fd5b5061023761043c366004610daa565b610bdf565b34801561044d57600080fd5b5061023761045c366004610e19565b610c2a565b34801561046d57600080fd5b506040517fffffffff000000000000000000000000000000000000000000000000000000006000351681526020016101f0565b3480156104ac57600080fd5b50326101dc565b3480156104bf57600080fd5b506102376104ce366004610d7a565b610c7f565b3480156104df57600080fd5b506102376104ee366004610e19565b610d20565b6001546040517f38cc48316aea9070a6b9a07b3cefc3f4db049e914955401a9d60fc9eb4c698d180825260009260609284926001600160a01b039092169190602081600481878761c350f2602082810160405282875290945061055c9186018101908601610e45565b94505050505090565b60005b828110156106045760408051600481526024810182526020810180516001600160e01b03166338cc483160e01b17905290516001600160a01b038416916105ae91610e62565b600060405180830381855af49150503d80600081146105e9576040519150601f19603f3d011682016040523d82523d6000602084013e6105ee565b606091505b50505080806105fc90610eb3565b915050610568565b505050565b60408051600481526024810182526020810180516001600160e01b0316632d3c86dd60e11b17905290516001600160a01b0383169161064791610e62565b600060405180830381855afa9150503d8060008114610682576040519150601f19603f3d011682016040523d82523d6000602084013e610687565b606091505b50505050565b60005b8281101561060457816001600160a01b0316816040516024016106b591815260200190565b60408051601f198184030181529181526020820180516001600160e01b031663c648049d60e01b179052516106ea9190610e62565b6000604051808303816000865af19150503d8060008114610727576040519150601f19603f3d011682016040523d82523d6000602084013e61072c565b606091505b505050808061073a90610eb3565b915050610690565b60005a905090565b60005b8181101561076c5760008190558061076481610eb3565b91505061074d565b5050565b6001546040517f38cc48316aea9070a6b9a07b3cefc3f4db049e914955401a9d60fc9eb4c698d180825260009260609284926001600160a01b0390921691906020816004818661c350f4602082810160405282875290945061055c9186018101908601610e45565b6000806040516107e790610d56565b604051809103906000f080158015610803573d6000803e3d6000fd5b5092915050565b60408051600481526024810182526020810180516001600160e01b0316632d3c86dd60e11b17905290516001600160a01b0383169161084891610e62565b6000604051808303816000865af19150503d8060008114610682576040519150601f19603f3d011682016040523d82523d6000602084013e610687565b608061160c8152602081a07fac3e966f295f2d5312f973dc6d42f30a6dc1c1f76ab8ee91cc8ca5dad1fa60fd80602083a17fae85c7887d510d629d8eb59ca412c0bf604c72c550fb0eec2734b12c76f2760b8082602085a261055160a0527ff4cd3854cb47c6b2f68a3a796635d026b9b412a93dfb80dd411c544cbc3c1817808284604087a37fe32ef46652011110f84325a4871007ee80018c1b6728ee04ffae74eb557e3fbf818385604088a450505050565b60408051600481526024810182526020810180516001600160e01b0316632d3c86dd60e11b17905290516001600160a01b0383169161097791610e62565b600060405180830381855af49150503d8060008114610682576040519150601f19603f3d011682016040523d82523d6000602084013e610687565b60005b8281101561060457816001600160a01b0316816040516024016109da91815260200190565b60408051601f198184030181529181526020820180516001600160e01b031663c648049d60e01b17905251610a0f9190610e62565b600060405180830381855af49150503d8060008114610a4a576040519150601f19603f3d011682016040523d82523d6000602084013e610a4f565b606091505b5050508080610a5d90610eb3565b9150506109b5565b600082841015610b385760006001600160a01b038316610a86866001610ece565b6040516024810191909152604481018690526001600160a01b038516606482015260840160408051601f198184030181529181526020820180516001600160e01b0316635d9bb54b60e11b17905251610adf9190610e62565b6000604051808303816000865af19150503d8060008114610b1c576040519150601f19603f3d011682016040523d82523d6000602084013e610b21565b606091505b5091505080610b2f90610ee6565b9150610b3b9050565b50825b9392505050565b6001546040517f38cc48316aea9070a6b9a07b3cefc3f4db049e914955401a9d60fc9eb4c698d180825260009260609284926001600160a01b0390921691906020816004818661c350fa602082810160405282875290945061055c9186018101908601610e45565b60008060005460001b604051610bbf90610d56565b8190604051809103906000f5905080158015610803573d6000803e3d6000fd5b60606000807f5a790dba3c23b59f4183a2d8e5d0ceae10b15e337a4dcaeae2d5897a5f68a3d4905060405181815260208160048360008961c350f25060208101604052909252505050565b6040516001600160a01b038316908290600081818185875af1925050503d8060008114610c73576040519150601f19603f3d011682016040523d82523d6000602084013e610c78565b606091505b5050505050565b60005b828110156106045760408051600481526024810182526020810180516001600160e01b03166338cc483160e01b17905290516001600160a01b03841691610cc891610e62565b6000604051808303816000865af19150503d8060008114610d05576040519150601f19603f3d011682016040523d82523d6000602084013e610d0a565b606091505b5050508080610d1890610eb3565b915050610c82565b6040516001600160a01b0383169082156108fc029083906000818181858888f19350505050158015610604573d6000803e3d6000fd5b609380610f0e83390190565b6001600160a01b0381168114610d7757600080fd5b50565b60008060408385031215610d8d57600080fd5b823591506020830135610d9f81610d62565b809150509250929050565b600060208284031215610dbc57600080fd5b8135610b3b81610d62565b600060208284031215610dd957600080fd5b5035919050565b600080600060608486031215610df557600080fd5b83359250602084013591506040840135610e0e81610d62565b809150509250925092565b60008060408385031215610e2c57600080fd5b8235610e3781610d62565b946020939093013593505050565b600060208284031215610e5757600080fd5b8151610b3b81610d62565b6000825160005b81811015610e835760208186018101518583015201610e69565b81811115610e92576000828501525b509190910192915050565b634e487b7160e01b600052601160045260246000fd5b6000600019821415610ec757610ec7610e9d565b5060010190565b60008219821115610ee157610ee1610e9d565b500190565b80516020808301519190811015610f07576000198160200360031b1b821691505b5091905056fe6080604052348015600f57600080fd5b50607680601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806338cc483114602d575b600080fd5b6040805130815290519081900360200190f3fea26469706673582212206581057925cb8c91b475dfd65cb1bc362e8198d1260dee32cec18103302c548464736f6c63430008090033a264697066735822122095333591d755aa725f8a8b489c8c528213ca55abc2d8981f073d5242f3b989f164736f6c634300080900336080604052348015600f57600080fd5b50607680601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806338cc483114602d575b600080fd5b6040805130815290519081900360200190f3fea26469706673582212206581057925cb8c91b475dfd65cb1bc362e8198d1260dee32cec18103302c548464736f6c63430008090033" +let transaction1559 = { + nonce: 2, + chainId: 0x12a, + to: "0x67d8d32e9bf1a9968a5ff53b87d777aa8ebbee69", + from: "0x05fba803be258049a27b820088bab1cad2058871", + value, + gasLimit: gasLimit, + accessList: [], + maxPriorityFeePerGas: defaultGasPrice, + maxFeePerGas:defaultGasPrice +}; + +// let transaction2930 = { +// nonce: 0, +// chainId: 0x12a, +// to: "0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69", +// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", +// value, +// gasLimit: gasLimit, +// type: 1, +// gasPrice: gasPrice, +// accessList: [], +// // maxPriorityFeePerGas: defaultGasPrice, +// // maxFeePerGas:defaultGasPrice + +// }; + +// let legacyTransaction = { +// nonce: 0, +// chainId: 0x12a, +// to: null, +// from: "0x29cbb51A44fd332c14180b4D471FBBc6654b1657", +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// type: 0, +// value +// }; + +// let createContractLegacyTransaction = { +// nonce: 2, +// chainId: 0x12a, +// to: null, +// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// type: 0, +// data: bytecode, +// }; +// // data: "0x608060405234801561001057600080fd5b506040516105fc3803806105fc83398101604081905261002f9161015f565b8051610042906000906020840190610080565b507fad181ee258ff92d26bf7ed2e6b571ef1cba3afc45f028b863b0f02adaffc2f0681604051610072919061020b565b60405180910390a150610279565b82805461008c9061023e565b90600052602060002090601f0160209004810192826100ae57600085556100f4565b82601f106100c757805160ff19168380011785556100f4565b828001600101855582156100f4579182015b828111156100f45782518255916020019190600101906100d9565b50610100929150610104565b5090565b5b808211156101005760008155600101610105565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561014a578181015183820152602001610132565b83811115610159576000848401525b50505050565b60006020828403121561017157600080fd5b81516001600160401b038082111561018857600080fd5b818401915084601f83011261019c57600080fd5b8151818111156101ae576101ae610119565b604051601f8201601f19908116603f011681019083821181831017156101d6576101d6610119565b816040528281528760208487010111156101ef57600080fd5b61020083602083016020880161012f565b979650505050505050565b602081526000825180602084015261022a81604085016020870161012f565b601f01601f19169190910160400192915050565b600181811c9082168061025257607f821691505b6020821081141561027357634e487b7160e01b600052602260045260246000fd5b50919050565b610374806102886000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610050575b600080fd5b61004e6100493660046101fd565b61006e565b005b6100586100bc565b60405161006591906102ae565b60405180910390f35b805161008190600090602084019061014e565b507fad181ee258ff92d26bf7ed2e6b571ef1cba3afc45f028b863b0f02adaffc2f06816040516100b191906102ae565b60405180910390a150565b6060600080546100cb90610303565b80601f01602080910402602001604051908101604052809291908181526020018280546100f790610303565b80156101445780601f1061011957610100808354040283529160200191610144565b820191906000526020600020905b81548152906001019060200180831161012757829003601f168201915b5050505050905090565b82805461015a90610303565b90600052602060002090601f01602090048101928261017c57600085556101c2565b82601f1061019557805160ff19168380011785556101c2565b828001600101855582156101c2579182015b828111156101c25782518255916020019190600101906101a7565b506101ce9291506101d2565b5090565b5b808211156101ce57600081556001016101d3565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561020f57600080fd5b813567ffffffffffffffff8082111561022757600080fd5b818401915084601f83011261023b57600080fd5b81358181111561024d5761024d6101e7565b604051601f8201601f19908116603f01168101908382118183101715610275576102756101e7565b8160405282815287602084870101111561028e57600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b818110156102db578581018301518582016040015282016102bf565b818111156102ed576000604083870101525b50601f01601f1916929092016040019392505050565b600181811c9082168061031757607f821691505b6020821081141561033857634e487b7160e01b600052602260045260246000fd5b5091905056fea2646970667358221220d450959bd13a5c79ab8546a400f6af65e1c3d24b6877b871e663861bbf17234664736f6c63430008090033000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000076e696b6f6c617900000000000000000000000000000000000000000000000000" + +// let invokeFunction = { +// nonce: 4, +// chainId: 0x12a, +// to: "0x4a9c86ffbf6f46221a6b18ed5b26ecece19c9b27", +// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// type: 0, +// data: '0x5c36b186', +// }; + +// let estimateGasContract = { +// nonce: 0, +// chainId: 0x12a, +// to: "0x637a6a8e5a69c087c24983b05261f63f64ed7e9c", +// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", +// gasPrice: gasPrice, +// type: 2, +// data: estimateGassContractBytecode, +// gasLimit: gasLimit, +// maxPriorityFeePerGas: defaultGasPrice, +// maxFeePerGas:defaultGasPrice +// }; + +// let estimateGasContractDeploy = { +// nonce: 1, +// chainId: 0x12a, +// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", +// gasPrice: gasPrice, +// type: 2, +// data: estimateGassContractBytecode, +// gasLimit: gasLimit, +// maxPriorityFeePerGas: defaultGasPrice, +// maxFeePerGas:defaultGasPrice +// }; + +// let estimateGasContractDelegate = { +// nonce: 1, +// chainId: 0x12a, +// to: "0xffc7d3ff264c838ad75167e64d043794bf1bd57c", +// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", +// gasLimit: gasLimit, +// gasPrice: '0x333f4c3c00', +// type: 0, +// data: '0x5c929889', + +// }; + +// let estimateGasContractUpdateCounter = { +// nonce: 3, +// chainId: 0x12a, +// to: "0xffc7d3ff264c838ad75167e64d043794bf1bd57c", +// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// type: 0, +// data: '0x1687fe8e', + +// }; + +async function main() { + const transactionHash = await helper.signTransaction(transaction1559, "0x2e1d968b041d84dd120a5860cee60cd83f9374ef527ca86996317ada3d0d03e7"); + console.log(transactionHash); +} +main(); \ No newline at end of file From f707accd4a70db1e80bb002461d3c7098e127472 Mon Sep 17 00:00:00 2001 From: Swirlds Automation <52682028+swirlds-automation@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:10:41 -0500 Subject: [PATCH 20/38] build(dep): [Snyk] Upgrade @graphprotocol/graph-cli from 0.81.0 to 0.82.0 (#3076) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: upgrade @graphprotocol/graph-cli from 0.81.0 to 0.82.0 Snyk has created this PR to upgrade @graphprotocol/graph-cli from 0.81.0 to 0.82.0. See this package in npm: @graphprotocol/graph-cli See this project in Snyk: https://app.snyk.io/org/json-rpc-relay/project/24dd80bc-cab7-4d83-83d7-a604d57322e3?utm_source=github&utm_medium=referral&page=upgrade-pr Signed-off-by: ebadiere * fix: Skip issue matching check for thirdparty build dependency librar… (#2990) * fix: Skip issue matching check for thirdparty build dependency libraries. We do not control them. Signed-off-by: ebadiere * fix: Now validates PRs listed in description by matching contributors in the repository and not matching PRs with issues from third party projects. Signed-off-by: ebadiere --------- Signed-off-by: ebadiere * fix: SDKClient timeouts are not getting logged with requestIds. (#3061) * fix: Enhanced logged warning to include SDKClient error message and bumped up SDK_REQUEST_TIMEOUT for CI. Signed-off-by: ebadiere * fix: Removed the increate in sdkClient timeout. Signed-off-by: ebadiere --------- Signed-off-by: ebadiere --------- Signed-off-by: ebadiere Co-authored-by: snyk-bot Co-authored-by: Eric Badiere --- tools/subgraph-example/subgraph/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/subgraph-example/subgraph/package.json b/tools/subgraph-example/subgraph/package.json index b887f8cd68..6dca30632d 100644 --- a/tools/subgraph-example/subgraph/package.json +++ b/tools/subgraph-example/subgraph/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@graphprotocol/graph-ts": "0.35.1", - "@graphprotocol/graph-cli": "0.81.0" + "@graphprotocol/graph-cli": "0.82.0" }, "devDependencies": { "matchstick-as": "0.5.2" From 59dbdd5d8333a6c0794fbdaad5abb0c2c36d4f72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:32:46 -0600 Subject: [PATCH 21/38] build(deps): bump cookie and express in /tools/truffle-example (#3078) Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `cookie` from 0.6.0 to 0.7.1 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1) Updates `express` from 4.21.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.1) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/truffle-example/package-lock.json | 91 ++----------------------- 1 file changed, 7 insertions(+), 84 deletions(-) diff --git a/tools/truffle-example/package-lock.json b/tools/truffle-example/package-lock.json index 1c5c221ebf..d5caf470e4 100644 --- a/tools/truffle-example/package-lock.json +++ b/tools/truffle-example/package-lock.json @@ -3178,20 +3178,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3807,9 +3793,9 @@ "peer": true }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -5220,16 +5206,16 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -5268,14 +5254,6 @@ "ms": "2.0.0" } }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6012,20 +5990,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/ganache/node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "inBundle": true, - "license": "ISC" - }, - "node_modules/ganache/node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "inBundle": true, - "license": "MIT" - }, "node_modules/ganache/node_modules/module-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", @@ -9733,47 +9697,6 @@ "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/servify": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", From ae4563d3d9eabbc5e2fba3393a866deb1e25ee97 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Wed, 9 Oct 2024 23:33:39 +0200 Subject: [PATCH 22/38] feat: Brownie example and description (#3070) * feat: brownie example usage Signed-off-by: Mariusz Jasuwienas * feat: brownie example usage Signed-off-by: Mariusz Jasuwienas * feat: brownie example usage Signed-off-by: Mariusz Jasuwienas * feat: brownie example usage Signed-off-by: Mariusz Jasuwienas * feat: brownie example usage Signed-off-by: Mariusz Jasuwienas * feat: brownie example usage Signed-off-by: Mariusz Jasuwienas --------- Signed-off-by: Mariusz Jasuwienas --- tools/brownie-example/.gitignore | 5 + tools/brownie-example/LICENSE | 15 ++ tools/brownie-example/README.md | 219 ++++++++++++++++++++ tools/brownie-example/contracts/Greeter.sol | 27 +++ tools/brownie-example/requirements.txt | 1 + tools/brownie-example/scripts/deploy.py | 7 + tools/brownie-example/tests/conftest.py | 22 ++ tools/brownie-example/tests/test_rpc.py | 25 +++ 8 files changed, 321 insertions(+) create mode 100644 tools/brownie-example/.gitignore create mode 100644 tools/brownie-example/LICENSE create mode 100644 tools/brownie-example/README.md create mode 100644 tools/brownie-example/contracts/Greeter.sol create mode 100644 tools/brownie-example/requirements.txt create mode 100644 tools/brownie-example/scripts/deploy.py create mode 100644 tools/brownie-example/tests/conftest.py create mode 100644 tools/brownie-example/tests/test_rpc.py diff --git a/tools/brownie-example/.gitignore b/tools/brownie-example/.gitignore new file mode 100644 index 0000000000..2bfec6df8a --- /dev/null +++ b/tools/brownie-example/.gitignore @@ -0,0 +1,5 @@ +__pycache__ +.pytest_cache/ +build/ +reports/ + diff --git a/tools/brownie-example/LICENSE b/tools/brownie-example/LICENSE new file mode 100644 index 0000000000..b902617e1b --- /dev/null +++ b/tools/brownie-example/LICENSE @@ -0,0 +1,15 @@ +Hedera Brownie Example + +Copyright (C) 2024 Hedera Hashgraph, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/tools/brownie-example/README.md b/tools/brownie-example/README.md new file mode 100644 index 0000000000..0741bc32a8 --- /dev/null +++ b/tools/brownie-example/README.md @@ -0,0 +1,219 @@ +# Brownie + +[Brownie](https://github.com/eth-brownie/brownie) is an open-source Python-based framework for testing, deploying, and interacting with Ethereum smart contracts. Built on top of the `web3.py` library, Brownie is designed to streamline the development workflow for decentralized applications by providing an easy-to-use interface for tasks such as contract deployment, testing, and debugging. It supports both Solidity and Vyper. + +Brownie's main capabilities are: + +- **Automated Contract Testing**: Facilitates both unit and integration testing using Python, with the ability to create fixtures, mocks, and use `pytest` for comprehensive test coverage. +- **Local Blockchain Environment**: Integrates with Ganache, allowing for the quick setup of a local Ethereum environment for development, testing, and debugging. +- **Contract Interaction**: Provides an intuitive API for interacting with smart contracts, including sending transactions, calling functions, and retrieving events. +- **Built-in Debugging Tools**: Offers detailed transaction tracing and error reporting to aid in identifying bugs and optimizing gas usage. +- **Seamless Integration with Development Pipelines**: Brownie can be easily integrated into continuous integration/continuous deployment (CI/CD) workflows to ensure contract reliability. + +## Installation: + +To install Brownie, the recommended approach is using `pip`: + +```Shell +pip install eth-brownie +``` + +You may also want to start the Hedera Local Node to run a local blockchain for testing: +https://github.com/hashgraph/hedera-local-node + +Once installed, you can initialize a new project: + +```Shell +brownie init +``` + +Brownie supports both Solidity and Vyper contract languages. + +Brownie will automatically download the required Solidity compiler and dependencies when you first compile a contract. + +You may also install additional dependencies running: + +```Shell +brownie pm install OpenZeppelin/openzeppelin-contracts@4.4.0 +``` + +## Tool Analysis: + +### Key Features and Advantages: + +1. **Cross-Platform Compatibility**: Brownie supports Ethereum-compatible blockchains, it can be used for Hedera Smart Contracts development. + +2. **Contract Testing and Debugging**: + - Brownie uses `pytest` for its testing framework, providing access to Python’s testing ecosystem. You can write both unit tests and functional tests for contracts. + - Transaction debugging is powerful with detailed stack traces, revert reasons, and gas reports. + +3. **Ease of Use**: By leveraging Python, a well-known and widely used programming language, Brownie makes it easier for developers to interact with the blockchain and smart contracts without needing deep knowledge of Solidity or EVM bytecode. + +5. **Event Monitoring and Logging**: Contracts deployed via Brownie are easy to monitor through event subscriptions, and the logs can be analyzed to detect specific contract behaviors in real-time. + +### Limitations and Considerations: + +1. **Dependency on `web3.py`**: As Brownie is built on `web3.py`, it inherits some of the limitations and idiosyncrasies of this library. Regular updates to `web3.py` are necessary to maintain compatibility with the latest Ethereum features. + +2. **Complex Projects**: For highly complex or large-scale projects, the simplified Python-based testing environment might require additional customization or integration with other more advanced development tools. + +> **Community and Support**: +> Brownie is no longer actively maintained, although developers can engage with the community on the [official GitHub](https://github.com/eth-brownie/brownie) page or the [Python Ethereum Developers Discord](https://discord.gg/YGzGZEfSBc). + +## Tests performed + +# Using Brownie (https://github.com/eth-brownie/brownie) with Hedera Hashgraph + +- Activate Python virtual environment +``` +python3 -m venv venv +source venv/bin/activate +``` +- Install Brownie +``` +pip3 install eth-brownie +``` +- Create a Brownie project +``` +mkdir brownie-test-project +cd brownie-test-project +brownie init +``` +- Add an account and start Brownie console +``` +brownie networks list +brownie accounts new myaccount +brownie console --network hedera-test +``` +- Check that Brownie can fetch balance of the account +``` +>>> accounts.load('myaccount') +>>> accounts[0].balance() +``` +- Exit Brownie console and create "Token" subproject +``` +brownie bake token +cd token +brownie compile +``` +- Start the console +``` +brownie console --network hedera-test +``` +- Check that "Token" is available +``` +>>> accounts.load('myaccount') +>>> Token +``` +- Deploy a token contract +``` +>>> t = Token.deploy("Test Token", "TST", 18, 1e23, {'from': accounts[0]}) +``` + +## Using fixtures and forking + +Brownie can connect to any network or its fork and is designed to work seamlessly with a ganache-cli script. However, if an alternative script is used in the configuration process to start the fork, Brownie may fail with the following error: +```shell +INTERNALERROR> ValueError: could not read ganache version: b'2.31.0\n' +``` + +To run your tests on forked networks, which allows the use of true fixtures (with snapshots created before each test and reverted after each test), you simply need to register an additional network in the Brownie configuration. + +### Debugging Transactions and Working with Events in Brownie + +Brownie provides tools for debugging transactions and working with events during development and testing: + +1. **Transaction Debugging**: + - Brownie allows you to inspect transactions in detail. After sending a transaction, you can call `tx.info()` on the transaction object to see its gas usage, logs, and status. + + Example: + ```python + tx = contract.someFunction({'from': accounts[0]}) + tx.info() # View transaction details + ``` + +2. **Accessing Events**: + - Events emitted by contracts during a transaction can be easily accessed through the transaction object. You can retrieve all events or filter for specific ones. + + Example: + ```python + tx = contract.someFunction({'from': accounts[0]}) + print(tx.events) # Print all emitted events + ``` + + To access a specific event, you can reference it by name: + ```python + transfer_event = tx.events['Transfer'] + print(transfer_event) + ``` + +3. **Real-Time Event Monitoring**: + - You can set up filters to watch for specific events in real time. This is useful when you want to monitor certain actions as they happen on the network. + + Example: + ```python + event_filter = contract.events.Transfer.createFilter(fromBlock='latest') + events = event_filter.get_all_entries() + print(events) + ``` + +4. **Reverting and Snapshotting**: + - When testing on forked networks, you can create a snapshot of the current blockchain state, perform tests, and then revert back to the snapshot for repeated testing. + + Example: + ```python + network.snapshot() + tx = contract.someFunction({'from': accounts[0]}) + network.revert() # Revert to the snapshot + ``` + + You can configure Brownie to automatically isolate each test by adding the following code to your conftest.py file: + ```python + @pytest.fixture(scope="function", autouse=True) + def isolate(fn_isolation): + pass + ``` + This ensures that each test runs in an isolated environment, preventing any side effects from one test affecting another. + +5. **Revert Reasons**: + - If a transaction fails or reverts, Brownie provides detailed error messages and stack traces, including revert reasons, making it easier to identify and fix issues in your contract logic. + +# Brownie usage example + +Simple scripts for basic operations like hbars transfer, balance fetching, and contract interactions (deployment and calls). + +## Setup & Install + +Follow this instruction from the installation section above: + +```Shell +pip install eth-brownie +``` + +## Running tests + +After installing brownie you can go to your project directory and run following script: + +1. Run a fork of the network of your choose (snapshot method is required for tests). + + In order to run the tests on the non-forked (no snapshot method available) network + you need to have 2 accounts configured in brownie + + Bob's account can not be empty: + + ```shell + brownie accounts new bob + brownie accounts new alice + ``` + Since the transfer of hbars is performed during the tests it is advised to connect to the forked network instead of + the actual remote network. + + In order to run the tests on the forked network add the fork config to the brownie networks list: + ```bash + brownie networks add development hedera-test-fork cmd=ganache-cli host=http://127.0.0.1 chain_id=296 fork='https://testnet.hashio.io/api' accounts=10 mnemonic=brownie port=8545 + ``` +2. Run `brownie test --network hedera-test-fork` + +## Deploying your Smart Contract + +1. Run brownie run deploy --network hedera-test diff --git a/tools/brownie-example/contracts/Greeter.sol b/tools/brownie-example/contracts/Greeter.sol new file mode 100644 index 0000000000..b3bcff989e --- /dev/null +++ b/tools/brownie-example/contracts/Greeter.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.9; + +contract Greeter { + string private greeting; + + event GreetingSet(string greeting); + + //This constructor assigns the initial greeting and emit GreetingSet event + constructor(string memory _greeting) { + greeting = _greeting; + + emit GreetingSet(_greeting); + } + + //This function returns the current value stored in greeting variable + function greet() public view returns (string memory) { + return greeting; + } + + //This function sets the new greeting msg from the one passed down as parameter and emit event + function setGreeting(string memory _greeting) public { + greeting = _greeting; + + emit GreetingSet(_greeting); + } +} diff --git a/tools/brownie-example/requirements.txt b/tools/brownie-example/requirements.txt new file mode 100644 index 0000000000..e4955b81a1 --- /dev/null +++ b/tools/brownie-example/requirements.txt @@ -0,0 +1 @@ +eth-brownie>=1.10.0,<2.0.0 diff --git a/tools/brownie-example/scripts/deploy.py b/tools/brownie-example/scripts/deploy.py new file mode 100644 index 0000000000..cfd3c10e4d --- /dev/null +++ b/tools/brownie-example/scripts/deploy.py @@ -0,0 +1,7 @@ +#!/usr/bin/python3 + +from brownie import Greeter, accounts + +def main(): + accounts.load('bob') + return Greeter.deploy("initial_msg", {'from': accounts[0]}) diff --git a/tools/brownie-example/tests/conftest.py b/tools/brownie-example/tests/conftest.py new file mode 100644 index 0000000000..9eee12f4f7 --- /dev/null +++ b/tools/brownie-example/tests/conftest.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 + +import pytest + +# You can run your tests in isolation mode by connecting to a forked network of the Hedera chain. +# This will enable the snapshot method during testing, ensuring that each test starts with a clean state. +# @pytest.fixture(scope="function", autouse=True) +# def isolate(fn_isolation): +# Rewinds the chain after each test to maintain proper isolation. +# More information can be found here: +# https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures +# pass + +# This setup is not required for forked networks with pre-funded accounts. +@pytest.fixture(scope="module", autouse=True) +def shared_setup(accounts): + accounts.load('bob') + accounts.load('alice') + +@pytest.fixture(scope="module") +def greeter(Greeter, accounts): + return Greeter.deploy("initial_msg", {'from': accounts[0]}) diff --git a/tools/brownie-example/tests/test_rpc.py b/tools/brownie-example/tests/test_rpc.py new file mode 100644 index 0000000000..d1f0e8bf68 --- /dev/null +++ b/tools/brownie-example/tests/test_rpc.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 + +import pytest + +def test_account_balance(accounts): + assert accounts[0].balance() > 0 # We need non empty account to test the transfer method... + +def test_hbar_transfer(accounts): + transfer_amount = 100 + initial_alice_balance = accounts[0].balance() + initial_bob_balance = accounts[1].balance() + accounts[0].transfer(accounts[1], transfer_amount) + assert accounts[0].balance() == initial_alice_balance - transfer_amount + assert accounts[1].balance() == initial_bob_balance + transfer_amount + +def test_deploy(Greeter, accounts): + deployed = Greeter.deploy("another_instance", {'from': accounts[0]}) + assert deployed.greet() == "another_instance" + +def test_view_call(greeter, accounts): + assert greeter.greet() == "initial_msg" + +def test_call(greeter, accounts): + greeter.setGreeting("updated_msg", {'from': accounts[0]}) + assert greeter.greet() == "updated_msg" From 1c659433e3a0c9ad00409f5cc5bc712a81c12e27 Mon Sep 17 00:00:00 2001 From: Brad Bowman <294617+beeradb@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:51:51 -0400 Subject: [PATCH 23/38] feat: Add release testing workflow for public networks (#3060) * Add public acceptance tests workflow Signed-off-by: beeradb <294617+beeradb@users.noreply.github.com> * Update CHAIN_ID Signed-off-by: beeradb <294617+beeradb@users.noreply.github.com> --------- Signed-off-by: beeradb <294617+beeradb@users.noreply.github.com> --- .github/workflows/acceptance-public.yml | 134 +----------------- .github/workflows/acceptance-workflow.yml | 3 +- packages/server/tests/mainnetAcceptance.env | 7 + .../server/tests/previewnetAcceptance.env | 1 + packages/server/tests/testnetAcceptance.env | 1 + 5 files changed, 16 insertions(+), 130 deletions(-) create mode 100644 packages/server/tests/mainnetAcceptance.env diff --git a/.github/workflows/acceptance-public.yml b/.github/workflows/acceptance-public.yml index 7284d724e2..84c2dde982 100644 --- a/.github/workflows/acceptance-public.yml +++ b/.github/workflows/acceptance-public.yml @@ -16,128 +16,16 @@ on: required: true type: choice options: + - mainnet - testnet - previewnet jobs: - api_batch_1: - name: API Batch 1 + release-tests: + name: Release Tests uses: ./.github/workflows/acceptance-workflow.yml with: - testfilter: api_batch1 - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - api_batch_2: - name: API Batch 2 - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: api_batch2 - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - api_batch_3: - name: API Batch 3 - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: api_batch3 - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - erc20: - name: ERC20 - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: erc20 - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - ratelimiter: - name: Rate Limiter - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: ratelimiter - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - tokencreate: - name: Token Create - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: tokencreate - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - tokenmanagement: - name: Token Management - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: tokenmanagement - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - htsprecompilev1: - name: Precompile - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: htsprecompilev1 - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - precompilecalls: - name: Precompile - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: precompile - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - websocket-batch-1: - name: Websocket Batch 1 - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: ws_batch1 - test_ws_server: true - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - websocket-batch-2: - name: Websocket Batch 2 - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: ws_batch2 - test_ws_server: true - envfile: ${{ inputs.network }}Acceptance.env - operator_id: ${{ inputs.operator_id }} - secrets: - operator_key: ${{ inputs.operator_key }} - - websocket-batch-3: - name: Websocket Batch 3 - uses: ./.github/workflows/acceptance-workflow.yml - with: - testfilter: ws_batch3 - test_ws_server: true + testfilter: release_light envfile: ${{ inputs.network }}Acceptance.env operator_id: ${{ inputs.operator_id }} secrets: @@ -147,19 +35,7 @@ jobs: name: Publish Results if: ${{ !cancelled() }} needs: - - api_batch_1 - - api_batch_2 - - api_batch_3 - - erc20 - - ratelimiter - - tokencreate - - tokenmanagement - - htsprecompilev1 - - precompilecalls - - websocket-batch-1 - - websocket-batch-2 - - websocket-batch-3 - + - release-tests runs-on: smart-contracts-linux-medium steps: - name: Harden Runner diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index feb61071b3..6ae4e2f66e 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -42,7 +42,8 @@ jobs: contents: write actions: read # issues: read - + env: + OPERATOR_ID_MAIN: ${{ inputs.operator_id }} steps: - name: Harden Runner uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 diff --git a/packages/server/tests/mainnetAcceptance.env b/packages/server/tests/mainnetAcceptance.env new file mode 100644 index 0000000000..4e56a122ff --- /dev/null +++ b/packages/server/tests/mainnetAcceptance.env @@ -0,0 +1,7 @@ +HEDERA_NETWORK=mainnet +CHAIN_ID=0x127 +MIRROR_NODE_URL=https://mainnet.mirrornode.hedera.com +MIRROR_NODE_URL_WEB3=https://mainnet.mirrornode.hedera.com +E2E_RELAY_HOST=https://mainnet.hashio.io/api +WS_RELAY_URL=wss://mainnet.hashio.io/ws +TEST_INITIAL_ACCOUNT_STARTING_BALANCE=500 diff --git a/packages/server/tests/previewnetAcceptance.env b/packages/server/tests/previewnetAcceptance.env index a4f0c0377c..06085484dd 100644 --- a/packages/server/tests/previewnetAcceptance.env +++ b/packages/server/tests/previewnetAcceptance.env @@ -19,3 +19,4 @@ WS_NEW_HEADS_ENABLED=true SERVER_REQUEST_TIMEOUT_MS=60000 MEMWATCH_ENABLED=false WRITE_SNAPSHOT_ON_MEMORY_LEAK=false +TEST_INITIAL_ACCOUNT_STARTING_BALANCE=500 diff --git a/packages/server/tests/testnetAcceptance.env b/packages/server/tests/testnetAcceptance.env index b85e9a60b0..d91715aab4 100644 --- a/packages/server/tests/testnetAcceptance.env +++ b/packages/server/tests/testnetAcceptance.env @@ -18,3 +18,4 @@ TEST_GAS_PRICE_DEVIATION=0.2 SERVER_REQUEST_TIMEOUT_MS=60000 MEMWATCH_ENABLED=false WRITE_SNAPSHOT_ON_MEMORY_LEAK=false +TEST_INITIAL_ACCOUNT_STARTING_BALANCE=500 From ec19625cba5db9aed1a189fe67f6261095af45c8 Mon Sep 17 00:00:00 2001 From: Nadezhda Popova <48063261+nadezhdapopovaa@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:09:46 +0300 Subject: [PATCH 24/38] fix: delete unnecessary script signTransaction (#3087) Signed-off-by: Nadezhda Popova --- scripts/signTransaction.js | 125 ------------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 scripts/signTransaction.js diff --git a/scripts/signTransaction.js b/scripts/signTransaction.js deleted file mode 100644 index 4b80659c6b..0000000000 --- a/scripts/signTransaction.js +++ /dev/null @@ -1,125 +0,0 @@ -require('ts-node/register'); -const helper = require('../packages/relay/tests/helpers.ts'); - -const gasPrice = '0x2C68AF0BB14000'; -const gasLimit = "0x493E0"; -const value = '0xDE0B6B3A7640000'; -const gasPrice1 = '0xa53f4c3c00'; -const defaultGasPrice = '0xA54F4C3C00'; -const bytecode ="0x6080604052348015600f57600080fd5b50609e8061001e6000396000f3fe608060405260043610602a5760003560e01c80635c36b18614603557806383197ef014605557600080fd5b36603057005b600080fd5b348015604057600080fd5b50600160405190815260200160405180910390f35b348015606057600080fd5b50606633ff5b00fea2646970667358221220886a6d6d6c88bcfc0063129ca2391a3d98aee75ad7fe3e870ec6679215456a3964736f6c63430008090033" -const estimateGassContractBytecode = "0x6080604052600160005534801561001557600080fd5b5060405161002290610064565b604051809103906000f08015801561003e573d6000803e3d6000fd5b50600180546001600160a01b0319166001600160a01b0392909216919091179055610070565b60938061105583390190565b610fd68061007f6000396000f3fe6080604052600436106101bb5760003560e01c806380f009b6116100ec578063ddf363d71161008a578063ec3e88cf11610064578063ec3e88cf14610461578063f96757d1146104a0578063fa5e414e146104b3578063ffaf0890146104d357600080fd5b8063ddf363d71461041b578063e080b4aa14610421578063e7df080e1461044157600080fd5b8063bbbfb986116100c6578063bbbfb986146103be578063c648049d146103d3578063d737d0c7146103f3578063dbb6f04a1461040657600080fd5b806380f009b61461036b57806383197ef01461038b578063bb376a961461039e57600080fd5b80635256b99d116101595780636e6662b9116101335780636e6662b914610301578063700799631461031657806374259795146103365780637df6ee271461034b57600080fd5b80635256b99d146102b65780635c929889146102d657806361bc221a146102eb57600080fd5b80633ec4de35116101955780633ec4de351461023957806341f32f0c146102615780634929af371461028157806351be4eaa146102a157600080fd5b80630c772ca5146101c75780630ec1551d146101f957806319a6e3d51461021757600080fd5b366101c257005b600080fd5b3480156101d357600080fd5b506101dc6104f3565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561020557600080fd5b5060045b6040519081526020016101f0565b34801561022357600080fd5b50610237610232366004610d7a565b610565565b005b34801561024557600080fd5b50610209610254366004610daa565b6001600160a01b03163190565b34801561026d57600080fd5b5061023761027c366004610daa565b610609565b34801561028d57600080fd5b5061023761029c366004610d7a565b61068d565b3480156102ad57600080fd5b50610209610742565b3480156102c257600080fd5b506102376102d1366004610dc7565b61074a565b3480156102e257600080fd5b506101dc610770565b3480156102f757600080fd5b5061020960005481565b34801561030d57600080fd5b506101dc6107d8565b34801561032257600080fd5b50610237610331366004610daa565b61080a565b34801561034257600080fd5b50610237610885565b34801561035757600080fd5b50610237610366366004610daa565b610939565b34801561037757600080fd5b50610237610386366004610d7a565b6109b2565b34801561039757600080fd5b5061023733ff5b3480156103aa57600080fd5b506102096103b9366004610de0565b610a65565b3480156103ca57600080fd5b506101dc610b42565b3480156103df57600080fd5b506102376103ee366004610dc7565b600055565b3480156103ff57600080fd5b50336101dc565b34801561041257600080fd5b506101dc610baa565b34610209565b34801561042d57600080fd5b5061023761043c366004610daa565b610bdf565b34801561044d57600080fd5b5061023761045c366004610e19565b610c2a565b34801561046d57600080fd5b506040517fffffffff000000000000000000000000000000000000000000000000000000006000351681526020016101f0565b3480156104ac57600080fd5b50326101dc565b3480156104bf57600080fd5b506102376104ce366004610d7a565b610c7f565b3480156104df57600080fd5b506102376104ee366004610e19565b610d20565b6001546040517f38cc48316aea9070a6b9a07b3cefc3f4db049e914955401a9d60fc9eb4c698d180825260009260609284926001600160a01b039092169190602081600481878761c350f2602082810160405282875290945061055c9186018101908601610e45565b94505050505090565b60005b828110156106045760408051600481526024810182526020810180516001600160e01b03166338cc483160e01b17905290516001600160a01b038416916105ae91610e62565b600060405180830381855af49150503d80600081146105e9576040519150601f19603f3d011682016040523d82523d6000602084013e6105ee565b606091505b50505080806105fc90610eb3565b915050610568565b505050565b60408051600481526024810182526020810180516001600160e01b0316632d3c86dd60e11b17905290516001600160a01b0383169161064791610e62565b600060405180830381855afa9150503d8060008114610682576040519150601f19603f3d011682016040523d82523d6000602084013e610687565b606091505b50505050565b60005b8281101561060457816001600160a01b0316816040516024016106b591815260200190565b60408051601f198184030181529181526020820180516001600160e01b031663c648049d60e01b179052516106ea9190610e62565b6000604051808303816000865af19150503d8060008114610727576040519150601f19603f3d011682016040523d82523d6000602084013e61072c565b606091505b505050808061073a90610eb3565b915050610690565b60005a905090565b60005b8181101561076c5760008190558061076481610eb3565b91505061074d565b5050565b6001546040517f38cc48316aea9070a6b9a07b3cefc3f4db049e914955401a9d60fc9eb4c698d180825260009260609284926001600160a01b0390921691906020816004818661c350f4602082810160405282875290945061055c9186018101908601610e45565b6000806040516107e790610d56565b604051809103906000f080158015610803573d6000803e3d6000fd5b5092915050565b60408051600481526024810182526020810180516001600160e01b0316632d3c86dd60e11b17905290516001600160a01b0383169161084891610e62565b6000604051808303816000865af19150503d8060008114610682576040519150601f19603f3d011682016040523d82523d6000602084013e610687565b608061160c8152602081a07fac3e966f295f2d5312f973dc6d42f30a6dc1c1f76ab8ee91cc8ca5dad1fa60fd80602083a17fae85c7887d510d629d8eb59ca412c0bf604c72c550fb0eec2734b12c76f2760b8082602085a261055160a0527ff4cd3854cb47c6b2f68a3a796635d026b9b412a93dfb80dd411c544cbc3c1817808284604087a37fe32ef46652011110f84325a4871007ee80018c1b6728ee04ffae74eb557e3fbf818385604088a450505050565b60408051600481526024810182526020810180516001600160e01b0316632d3c86dd60e11b17905290516001600160a01b0383169161097791610e62565b600060405180830381855af49150503d8060008114610682576040519150601f19603f3d011682016040523d82523d6000602084013e610687565b60005b8281101561060457816001600160a01b0316816040516024016109da91815260200190565b60408051601f198184030181529181526020820180516001600160e01b031663c648049d60e01b17905251610a0f9190610e62565b600060405180830381855af49150503d8060008114610a4a576040519150601f19603f3d011682016040523d82523d6000602084013e610a4f565b606091505b5050508080610a5d90610eb3565b9150506109b5565b600082841015610b385760006001600160a01b038316610a86866001610ece565b6040516024810191909152604481018690526001600160a01b038516606482015260840160408051601f198184030181529181526020820180516001600160e01b0316635d9bb54b60e11b17905251610adf9190610e62565b6000604051808303816000865af19150503d8060008114610b1c576040519150601f19603f3d011682016040523d82523d6000602084013e610b21565b606091505b5091505080610b2f90610ee6565b9150610b3b9050565b50825b9392505050565b6001546040517f38cc48316aea9070a6b9a07b3cefc3f4db049e914955401a9d60fc9eb4c698d180825260009260609284926001600160a01b0390921691906020816004818661c350fa602082810160405282875290945061055c9186018101908601610e45565b60008060005460001b604051610bbf90610d56565b8190604051809103906000f5905080158015610803573d6000803e3d6000fd5b60606000807f5a790dba3c23b59f4183a2d8e5d0ceae10b15e337a4dcaeae2d5897a5f68a3d4905060405181815260208160048360008961c350f25060208101604052909252505050565b6040516001600160a01b038316908290600081818185875af1925050503d8060008114610c73576040519150601f19603f3d011682016040523d82523d6000602084013e610c78565b606091505b5050505050565b60005b828110156106045760408051600481526024810182526020810180516001600160e01b03166338cc483160e01b17905290516001600160a01b03841691610cc891610e62565b6000604051808303816000865af19150503d8060008114610d05576040519150601f19603f3d011682016040523d82523d6000602084013e610d0a565b606091505b5050508080610d1890610eb3565b915050610c82565b6040516001600160a01b0383169082156108fc029083906000818181858888f19350505050158015610604573d6000803e3d6000fd5b609380610f0e83390190565b6001600160a01b0381168114610d7757600080fd5b50565b60008060408385031215610d8d57600080fd5b823591506020830135610d9f81610d62565b809150509250929050565b600060208284031215610dbc57600080fd5b8135610b3b81610d62565b600060208284031215610dd957600080fd5b5035919050565b600080600060608486031215610df557600080fd5b83359250602084013591506040840135610e0e81610d62565b809150509250925092565b60008060408385031215610e2c57600080fd5b8235610e3781610d62565b946020939093013593505050565b600060208284031215610e5757600080fd5b8151610b3b81610d62565b6000825160005b81811015610e835760208186018101518583015201610e69565b81811115610e92576000828501525b509190910192915050565b634e487b7160e01b600052601160045260246000fd5b6000600019821415610ec757610ec7610e9d565b5060010190565b60008219821115610ee157610ee1610e9d565b500190565b80516020808301519190811015610f07576000198160200360031b1b821691505b5091905056fe6080604052348015600f57600080fd5b50607680601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806338cc483114602d575b600080fd5b6040805130815290519081900360200190f3fea26469706673582212206581057925cb8c91b475dfd65cb1bc362e8198d1260dee32cec18103302c548464736f6c63430008090033a264697066735822122095333591d755aa725f8a8b489c8c528213ca55abc2d8981f073d5242f3b989f164736f6c634300080900336080604052348015600f57600080fd5b50607680601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806338cc483114602d575b600080fd5b6040805130815290519081900360200190f3fea26469706673582212206581057925cb8c91b475dfd65cb1bc362e8198d1260dee32cec18103302c548464736f6c63430008090033" -let transaction1559 = { - nonce: 2, - chainId: 0x12a, - to: "0x67d8d32e9bf1a9968a5ff53b87d777aa8ebbee69", - from: "0x05fba803be258049a27b820088bab1cad2058871", - value, - gasLimit: gasLimit, - accessList: [], - maxPriorityFeePerGas: defaultGasPrice, - maxFeePerGas:defaultGasPrice -}; - -// let transaction2930 = { -// nonce: 0, -// chainId: 0x12a, -// to: "0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69", -// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", -// value, -// gasLimit: gasLimit, -// type: 1, -// gasPrice: gasPrice, -// accessList: [], -// // maxPriorityFeePerGas: defaultGasPrice, -// // maxFeePerGas:defaultGasPrice - -// }; - -// let legacyTransaction = { -// nonce: 0, -// chainId: 0x12a, -// to: null, -// from: "0x29cbb51A44fd332c14180b4D471FBBc6654b1657", -// gasLimit: gasLimit, -// gasPrice: gasPrice, -// type: 0, -// value -// }; - -// let createContractLegacyTransaction = { -// nonce: 2, -// chainId: 0x12a, -// to: null, -// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", -// gasLimit: gasLimit, -// gasPrice: gasPrice, -// type: 0, -// data: bytecode, -// }; -// // data: "0x608060405234801561001057600080fd5b506040516105fc3803806105fc83398101604081905261002f9161015f565b8051610042906000906020840190610080565b507fad181ee258ff92d26bf7ed2e6b571ef1cba3afc45f028b863b0f02adaffc2f0681604051610072919061020b565b60405180910390a150610279565b82805461008c9061023e565b90600052602060002090601f0160209004810192826100ae57600085556100f4565b82601f106100c757805160ff19168380011785556100f4565b828001600101855582156100f4579182015b828111156100f45782518255916020019190600101906100d9565b50610100929150610104565b5090565b5b808211156101005760008155600101610105565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561014a578181015183820152602001610132565b83811115610159576000848401525b50505050565b60006020828403121561017157600080fd5b81516001600160401b038082111561018857600080fd5b818401915084601f83011261019c57600080fd5b8151818111156101ae576101ae610119565b604051601f8201601f19908116603f011681019083821181831017156101d6576101d6610119565b816040528281528760208487010111156101ef57600080fd5b61020083602083016020880161012f565b979650505050505050565b602081526000825180602084015261022a81604085016020870161012f565b601f01601f19169190910160400192915050565b600181811c9082168061025257607f821691505b6020821081141561027357634e487b7160e01b600052602260045260246000fd5b50919050565b610374806102886000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610050575b600080fd5b61004e6100493660046101fd565b61006e565b005b6100586100bc565b60405161006591906102ae565b60405180910390f35b805161008190600090602084019061014e565b507fad181ee258ff92d26bf7ed2e6b571ef1cba3afc45f028b863b0f02adaffc2f06816040516100b191906102ae565b60405180910390a150565b6060600080546100cb90610303565b80601f01602080910402602001604051908101604052809291908181526020018280546100f790610303565b80156101445780601f1061011957610100808354040283529160200191610144565b820191906000526020600020905b81548152906001019060200180831161012757829003601f168201915b5050505050905090565b82805461015a90610303565b90600052602060002090601f01602090048101928261017c57600085556101c2565b82601f1061019557805160ff19168380011785556101c2565b828001600101855582156101c2579182015b828111156101c25782518255916020019190600101906101a7565b506101ce9291506101d2565b5090565b5b808211156101ce57600081556001016101d3565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561020f57600080fd5b813567ffffffffffffffff8082111561022757600080fd5b818401915084601f83011261023b57600080fd5b81358181111561024d5761024d6101e7565b604051601f8201601f19908116603f01168101908382118183101715610275576102756101e7565b8160405282815287602084870101111561028e57600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b818110156102db578581018301518582016040015282016102bf565b818111156102ed576000604083870101525b50601f01601f1916929092016040019392505050565b600181811c9082168061031757607f821691505b6020821081141561033857634e487b7160e01b600052602260045260246000fd5b5091905056fea2646970667358221220d450959bd13a5c79ab8546a400f6af65e1c3d24b6877b871e663861bbf17234664736f6c63430008090033000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000076e696b6f6c617900000000000000000000000000000000000000000000000000" - -// let invokeFunction = { -// nonce: 4, -// chainId: 0x12a, -// to: "0x4a9c86ffbf6f46221a6b18ed5b26ecece19c9b27", -// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", -// gasLimit: gasLimit, -// gasPrice: gasPrice, -// type: 0, -// data: '0x5c36b186', -// }; - -// let estimateGasContract = { -// nonce: 0, -// chainId: 0x12a, -// to: "0x637a6a8e5a69c087c24983b05261f63f64ed7e9c", -// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", -// gasPrice: gasPrice, -// type: 2, -// data: estimateGassContractBytecode, -// gasLimit: gasLimit, -// maxPriorityFeePerGas: defaultGasPrice, -// maxFeePerGas:defaultGasPrice -// }; - -// let estimateGasContractDeploy = { -// nonce: 1, -// chainId: 0x12a, -// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", -// gasPrice: gasPrice, -// type: 2, -// data: estimateGassContractBytecode, -// gasLimit: gasLimit, -// maxPriorityFeePerGas: defaultGasPrice, -// maxFeePerGas:defaultGasPrice -// }; - -// let estimateGasContractDelegate = { -// nonce: 1, -// chainId: 0x12a, -// to: "0xffc7d3ff264c838ad75167e64d043794bf1bd57c", -// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", -// gasLimit: gasLimit, -// gasPrice: '0x333f4c3c00', -// type: 0, -// data: '0x5c929889', - -// }; - -// let estimateGasContractUpdateCounter = { -// nonce: 3, -// chainId: 0x12a, -// to: "0xffc7d3ff264c838ad75167e64d043794bf1bd57c", -// from: "0xc37f417fA09933335240FCA72DD257BFBdE9C275", -// gasLimit: gasLimit, -// gasPrice: gasPrice, -// type: 0, -// data: '0x1687fe8e', - -// }; - -async function main() { - const transactionHash = await helper.signTransaction(transaction1559, "0x2e1d968b041d84dd120a5860cee60cd83f9374ef527ca86996317ada3d0d03e7"); - console.log(transactionHash); -} -main(); \ No newline at end of file From 73200c1e66c3185bf6634a317a13f2172d6f60ce Mon Sep 17 00:00:00 2001 From: Swirlds Automation <52682028+swirlds-automation@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:58:39 -0500 Subject: [PATCH 25/38] build: (deps) [Snyk] Upgrade web3 from 4.12.1 to 4.13.0 (#3085) fix: upgrade web3 from 4.12.1 to 4.13.0 Snyk has created this PR to upgrade web3 from 4.12.1 to 4.13.0. See this package in npm: web3 See this project in Snyk: https://app.snyk.io/org/json-rpc-relay/project/9e95a688-4e8c-4400-bbd9-181a47c69778?utm_source=github&utm_medium=referral&page=upgrade-pr Signed-off-by: ebadiere Co-authored-by: snyk-bot --- tools/web3js-example/package-lock.json | 79 +++++++++++++------------- tools/web3js-example/package.json | 2 +- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/tools/web3js-example/package-lock.json b/tools/web3js-example/package-lock.json index c0c8bc84dd..7f23bedbe0 100644 --- a/tools/web3js-example/package-lock.json +++ b/tools/web3js-example/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "dotenv": "^16.4.5", - "web3": "^4.12.1" + "web3": "^4.13.0" }, "devDependencies": { "chai": "^4.3.6", @@ -1329,26 +1329,26 @@ } }, "node_modules/web3": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/web3/-/web3-4.12.1.tgz", - "integrity": "sha512-zIFUPdgo2uG5Vbl7C4KrTv8dmWKN3sGnY/GundbiJzcaJZDxaCyu3a5HXAcgUM1VvvsVb1zaUQAFPceq05/q/Q==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-4.13.0.tgz", + "integrity": "sha512-wRXTu/YjelvBJ7PSLzp/rW8/6pqj4RlXzdKSkjk01RaHDvnpLogLU/VL8OF5ygqhY7IzhY5MSrl9SnC8C9Z4uA==", "license": "LGPL-3.0", "dependencies": { - "web3-core": "^4.5.1", + "web3-core": "^4.6.0", "web3-errors": "^1.3.0", - "web3-eth": "^4.8.2", - "web3-eth-abi": "^4.2.3", + "web3-eth": "^4.9.0", + "web3-eth-abi": "^4.2.4", "web3-eth-accounts": "^4.2.1", "web3-eth-contract": "^4.7.0", "web3-eth-ens": "^4.4.0", "web3-eth-iban": "^4.0.7", - "web3-eth-personal": "^4.0.8", + "web3-eth-personal": "^4.1.0", "web3-net": "^4.1.0", "web3-providers-http": "^4.2.0", "web3-providers-ws": "^4.0.8", "web3-rpc-methods": "^1.3.0", "web3-rpc-providers": "^1.0.0-rc.2", - "web3-types": "^1.7.0", + "web3-types": "^1.8.0", "web3-utils": "^4.3.1", "web3-validator": "^2.0.6" }, @@ -1358,17 +1358,17 @@ } }, "node_modules/web3-core": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-4.5.1.tgz", - "integrity": "sha512-mFMOO/IWdKsLL1o2whh3oJ0LCG9P3l5c4lpiMoVsVln3QXh/B0Gf8gW3aY8S+Ixm0OHyzFDXJVc2CodxqmI4Gw==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-4.6.0.tgz", + "integrity": "sha512-j8uQ/7zSwpmLClMMeZb736Ok3V4cWSd0dnd29jkd10d1pedi32r+hSAgycxSJLLWtPHOzMBIXUjj3TF/IAClVQ==", "license": "LGPL-3.0", "dependencies": { "web3-errors": "^1.3.0", - "web3-eth-accounts": "^4.2.0", + "web3-eth-accounts": "^4.2.1", "web3-eth-iban": "^4.0.7", "web3-providers-http": "^4.2.0", "web3-providers-ws": "^4.0.8", - "web3-types": "^1.7.0", + "web3-types": "^1.8.0", "web3-utils": "^4.3.1", "web3-validator": "^2.0.6" }, @@ -1394,20 +1394,20 @@ } }, "node_modules/web3-eth": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-4.8.2.tgz", - "integrity": "sha512-DLV/fIMG6gBp/B0gv0+G4FzxZ4YCDQsY3lzqqv7avwh3uU7/O27aifCUcFd7Ye+3ixTqCjAvLEl9wYSeyG3zQw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-4.9.0.tgz", + "integrity": "sha512-lE+5rQUkQq1Mzf3uZ/tlay8nvMyC/CmaRFRFQ015OZuvSrRr/byZhhkzY5ZWkIetESTMqfWapu67yeHebcHxwA==", "license": "LGPL-3.0", "dependencies": { "setimmediate": "^1.0.5", - "web3-core": "^4.5.0", - "web3-errors": "^1.2.1", - "web3-eth-abi": "^4.2.3", - "web3-eth-accounts": "^4.1.3", + "web3-core": "^4.6.0", + "web3-errors": "^1.3.0", + "web3-eth-abi": "^4.2.4", + "web3-eth-accounts": "^4.2.1", "web3-net": "^4.1.0", "web3-providers-ws": "^4.0.8", "web3-rpc-methods": "^1.3.0", - "web3-types": "^1.7.0", + "web3-types": "^1.8.0", "web3-utils": "^4.3.1", "web3-validator": "^2.0.6" }, @@ -1417,14 +1417,14 @@ } }, "node_modules/web3-eth-abi": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-4.2.3.tgz", - "integrity": "sha512-rPVwTn0O1CzbtfXwEfIjUP0W5Y7u1OFjugwKpSqJzPQE6+REBg6OELjomTGZBu+GThxHnv0rp15SOxvqp+tyXA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-4.2.4.tgz", + "integrity": "sha512-FGoj/ENm/Iq3+6myJyiDCwbFkha9ZCx2fRdiIdw3mp7S4lgu+ay3EVzQPRxJjNBm09UEfxB9yoSAPKj9Z3Mbxg==", "license": "LGPL-3.0", "dependencies": { "abitype": "0.7.1", - "web3-errors": "^1.2.0", - "web3-types": "^1.7.0", + "web3-errors": "^1.3.0", + "web3-types": "^1.8.0", "web3-utils": "^4.3.1", "web3-validator": "^2.0.6" }, @@ -1521,16 +1521,17 @@ } }, "node_modules/web3-eth-personal": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz", - "integrity": "sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-4.1.0.tgz", + "integrity": "sha512-RFN83uMuvA5cu1zIwwJh9A/bAj0OBxmGN3tgx19OD/9ygeUZbifOL06jgFzN0t+1ekHqm3DXYQM8UfHpXi7yDQ==", + "license": "LGPL-3.0", "dependencies": { - "web3-core": "^4.3.0", - "web3-eth": "^4.3.1", - "web3-rpc-methods": "^1.1.3", - "web3-types": "^1.3.0", - "web3-utils": "^4.0.7", - "web3-validator": "^2.0.3" + "web3-core": "^4.6.0", + "web3-eth": "^4.9.0", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.8.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" }, "engines": { "node": ">=14", @@ -1636,9 +1637,9 @@ } }, "node_modules/web3-types": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.7.0.tgz", - "integrity": "sha512-nhXxDJ7a5FesRw9UG5SZdP/C/3Q2EzHGnB39hkAV+YGXDMgwxBXFWebQLfEzZzuArfHnvC0sQqkIHNwSKcVjdA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.8.0.tgz", + "integrity": "sha512-Z51wFLPGhZM/1uDxrxE8gzju3t2aEdRGn+YmLX463id5UjTuMEmP/9in1GFjqrsPB3m86czs8RnGBUt3ovueMw==", "license": "LGPL-3.0", "engines": { "node": ">=14", diff --git a/tools/web3js-example/package.json b/tools/web3js-example/package.json index c443748298..15b4aab53c 100644 --- a/tools/web3js-example/package.json +++ b/tools/web3js-example/package.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "dotenv": "^16.4.5", - "web3": "^4.12.1" + "web3": "^4.13.0" }, "devDependencies": { "chai": "^4.3.6", From 6bc6010a8ed8294c62cd64f2d2e88039ae8486cb Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Thu, 10 Oct 2024 19:17:10 +0200 Subject: [PATCH 26/38] feat: Wagmi example usage with Hedera (#3000) * feat: Wagmi example usage with Hedera Signed-off-by: mateuszm-arianelabs * chore: typo in readme, remove unused import Signed-off-by: mateuszm-arianelabs * chore: typo in readme, cleanup in code Signed-off-by: mateuszm-arianelabs * chore: change pnpm to npm, small fixes Signed-off-by: mateuszm-arianelabs * feat: pr suggestions applied Signed-off-by: Mariusz Jasuwienas * feat: pr suggestions applied Signed-off-by: Mariusz Jasuwienas * feat: cancuns opcodes are supported in hedera already so this comment about them was no longer valid Signed-off-by: Mariusz Jasuwienas --------- Signed-off-by: mateuszm-arianelabs Signed-off-by: Mariusz Jasuwienas Co-authored-by: mateuszm-arianelabs --- tools/wagmi-example/.gitignore | 24 +++++ tools/wagmi-example/.npmrc | 1 + tools/wagmi-example/README.md | 23 +++++ tools/wagmi-example/biome.json | 13 +++ tools/wagmi-example/contract/Greeter.sol | 18 ++++ .../contract/contract_Greeter_sol_Greeter.bin | 1 + .../contract_Greeter_sol_Greeter.json | 1 + tools/wagmi-example/index.html | 12 +++ tools/wagmi-example/package.json | 32 +++++++ tools/wagmi-example/src/App.tsx | 35 ++++++++ .../wagmi-example/src/components/Contract.tsx | 59 +++++++++++++ .../wagmi-example/src/components/Profile.tsx | 77 ++++++++++++++++ tools/wagmi-example/src/hooks/use-balance.ts | 35 ++++++++ tools/wagmi-example/src/hooks/use-deploy.ts | 88 +++++++++++++++++++ tools/wagmi-example/src/index.css | 25 ++++++ tools/wagmi-example/src/main.tsx | 44 ++++++++++ tools/wagmi-example/src/vite-env.d.ts | 1 + tools/wagmi-example/src/wagmi.ts | 38 ++++++++ tools/wagmi-example/tsconfig.json | 25 ++++++ tools/wagmi-example/vite.config.ts | 7 ++ 20 files changed, 559 insertions(+) create mode 100644 tools/wagmi-example/.gitignore create mode 100644 tools/wagmi-example/.npmrc create mode 100644 tools/wagmi-example/README.md create mode 100644 tools/wagmi-example/biome.json create mode 100644 tools/wagmi-example/contract/Greeter.sol create mode 100644 tools/wagmi-example/contract/contract_Greeter_sol_Greeter.bin create mode 100644 tools/wagmi-example/contract/contract_Greeter_sol_Greeter.json create mode 100644 tools/wagmi-example/index.html create mode 100644 tools/wagmi-example/package.json create mode 100644 tools/wagmi-example/src/App.tsx create mode 100644 tools/wagmi-example/src/components/Contract.tsx create mode 100644 tools/wagmi-example/src/components/Profile.tsx create mode 100644 tools/wagmi-example/src/hooks/use-balance.ts create mode 100644 tools/wagmi-example/src/hooks/use-deploy.ts create mode 100644 tools/wagmi-example/src/index.css create mode 100644 tools/wagmi-example/src/main.tsx create mode 100644 tools/wagmi-example/src/vite-env.d.ts create mode 100644 tools/wagmi-example/src/wagmi.ts create mode 100644 tools/wagmi-example/tsconfig.json create mode 100644 tools/wagmi-example/vite.config.ts diff --git a/tools/wagmi-example/.gitignore b/tools/wagmi-example/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/tools/wagmi-example/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/tools/wagmi-example/.npmrc b/tools/wagmi-example/.npmrc new file mode 100644 index 0000000000..80bcbed90c --- /dev/null +++ b/tools/wagmi-example/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps = true diff --git a/tools/wagmi-example/README.md b/tools/wagmi-example/README.md new file mode 100644 index 0000000000..ddb857e0a7 --- /dev/null +++ b/tools/wagmi-example/README.md @@ -0,0 +1,23 @@ +# Wagmi Hedera Example + +Wagmi is a library for building Web3 apps, offering hooks (React version) for wallet connections, blockchain data, and transactions. + +Learn more [here](https://wagmi.sh). + +## How to run example? +1. Install dependencies `npm install` +2. Run project `npm dev` + +## Example +Example of using Wagmi with Hedera covers +1. Sign in with wallet +2. Check account balance +3. Deploy contract from bytecode +4. Read from contract +5. Write to contract + +## Generate contract abi, bin +Using solc from [NPM](https://www.npmjs.com/package/solc) + +1. `solcjs contract/Greeter.sol --optimize --abi --bin -o contract/` or `npm run compile` +2. Change file extension in one of generated files from `.abi` to `.json` \ No newline at end of file diff --git a/tools/wagmi-example/biome.json b/tools/wagmi-example/biome.json new file mode 100644 index 0000000000..08eb8a26ab --- /dev/null +++ b/tools/wagmi-example/biome.json @@ -0,0 +1,13 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 120 + }, + "linter": { + "enabled": true + }, + "organizeImports": { + "enabled": true + } +} diff --git a/tools/wagmi-example/contract/Greeter.sol b/tools/wagmi-example/contract/Greeter.sol new file mode 100644 index 0000000000..d3df58013f --- /dev/null +++ b/tools/wagmi-example/contract/Greeter.sol @@ -0,0 +1,18 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +contract Greeter { + string private greeting; + + constructor(string memory _greeting) { + greeting = _greeting; + } + + function greet() external view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) external { + greeting = _greeting; + } +} diff --git a/tools/wagmi-example/contract/contract_Greeter_sol_Greeter.bin b/tools/wagmi-example/contract/contract_Greeter_sol_Greeter.bin new file mode 100644 index 0000000000..a8d2e398bb --- /dev/null +++ b/tools/wagmi-example/contract/contract_Greeter_sol_Greeter.bin @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b50604051610af2380380610af283398181016040528101906100319190610193565b805f908161003f91906103e7565b50506104b6565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6100a58261005f565b810181811067ffffffffffffffff821117156100c4576100c361006f565b5b80604052505050565b5f6100d6610046565b90506100e2828261009c565b919050565b5f67ffffffffffffffff8211156101015761010061006f565b5b61010a8261005f565b9050602081019050919050565b8281835e5f83830152505050565b5f610137610132846100e7565b6100cd565b9050828152602081018484840111156101535761015261005b565b5b61015e848285610117565b509392505050565b5f82601f83011261017a57610179610057565b5b815161018a848260208601610125565b91505092915050565b5f602082840312156101a8576101a761004f565b5b5f82015167ffffffffffffffff8111156101c5576101c4610053565b5b6101d184828501610166565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061022857607f821691505b60208210810361023b5761023a6101e4565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261029d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610262565b6102a78683610262565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6102eb6102e66102e1846102bf565b6102c8565b6102bf565b9050919050565b5f819050919050565b610304836102d1565b610318610310826102f2565b84845461026e565b825550505050565b5f90565b61032c610320565b6103378184846102fb565b505050565b5b8181101561035a5761034f5f82610324565b60018101905061033d565b5050565b601f82111561039f5761037081610241565b61037984610253565b81016020851015610388578190505b61039c61039485610253565b83018261033c565b50505b505050565b5f82821c905092915050565b5f6103bf5f19846008026103a4565b1980831691505092915050565b5f6103d783836103b0565b9150826002028217905092915050565b6103f0826101da565b67ffffffffffffffff8111156104095761040861006f565b5b6104138254610211565b61041e82828561035e565b5f60209050601f83116001811461044f575f841561043d578287015190505b61044785826103cc565b8655506104ae565b601f19841661045d86610241565b5f5b828110156104845784890151825560018201915060208501945060208101905061045f565b868310156104a1578489015161049d601f8916826103b0565b8355505b6001600288020188555050505b505050505050565b61062f806104c35f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063a413686214610038578063cfae321714610054575b5f80fd5b610052600480360381019061004d9190610260565b610072565b005b61005c610084565b6040516100699190610307565b60405180910390f35b805f9081610080919061052a565b5050565b60605f805461009290610354565b80601f01602080910402602001604051908101604052809291908181526020018280546100be90610354565b80156101095780601f106100e057610100808354040283529160200191610109565b820191905f5260205f20905b8154815290600101906020018083116100ec57829003601f168201915b5050505050905090565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6101728261012c565b810181811067ffffffffffffffff821117156101915761019061013c565b5b80604052505050565b5f6101a3610113565b90506101af8282610169565b919050565b5f67ffffffffffffffff8211156101ce576101cd61013c565b5b6101d78261012c565b9050602081019050919050565b828183375f83830152505050565b5f6102046101ff846101b4565b61019a565b9050828152602081018484840111156102205761021f610128565b5b61022b8482856101e4565b509392505050565b5f82601f83011261024757610246610124565b5b81356102578482602086016101f2565b91505092915050565b5f602082840312156102755761027461011c565b5b5f82013567ffffffffffffffff81111561029257610291610120565b5b61029e84828501610233565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f6102d9826102a7565b6102e381856102b1565b93506102f38185602086016102c1565b6102fc8161012c565b840191505092915050565b5f6020820190508181035f83015261031f81846102cf565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061036b57607f821691505b60208210810361037e5761037d610327565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026103e07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826103a5565b6103ea86836103a5565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61042e61042961042484610402565b61040b565b610402565b9050919050565b5f819050919050565b61044783610414565b61045b61045382610435565b8484546103b1565b825550505050565b5f90565b61046f610463565b61047a81848461043e565b505050565b5b8181101561049d576104925f82610467565b600181019050610480565b5050565b601f8211156104e2576104b381610384565b6104bc84610396565b810160208510156104cb578190505b6104df6104d785610396565b83018261047f565b50505b505050565b5f82821c905092915050565b5f6105025f19846008026104e7565b1980831691505092915050565b5f61051a83836104f3565b9150826002028217905092915050565b610533826102a7565b67ffffffffffffffff81111561054c5761054b61013c565b5b6105568254610354565b6105618282856104a1565b5f60209050601f831160018114610592575f8415610580578287015190505b61058a858261050f565b8655506105f1565b601f1984166105a086610384565b5f5b828110156105c7578489015182556001820191506020850194506020810190506105a2565b868310156105e457848901516105e0601f8916826104f3565b8355505b6001600288020188555050505b50505050505056fea2646970667358221220c4462f23962edba87cd01d76841a1ae026facaae75266d930953823abe8381eb64736f6c63430008190033 \ No newline at end of file diff --git a/tools/wagmi-example/contract/contract_Greeter_sol_Greeter.json b/tools/wagmi-example/contract/contract_Greeter_sol_Greeter.json new file mode 100644 index 0000000000..fe4d510312 --- /dev/null +++ b/tools/wagmi-example/contract/contract_Greeter_sol_Greeter.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/tools/wagmi-example/index.html b/tools/wagmi-example/index.html new file mode 100644 index 0000000000..5f841aab1b --- /dev/null +++ b/tools/wagmi-example/index.html @@ -0,0 +1,12 @@ + + + + + + Wagmi Hedera example + + +
+ + + diff --git a/tools/wagmi-example/package.json b/tools/wagmi-example/package.json new file mode 100644 index 0000000000..efe5c17766 --- /dev/null +++ b/tools/wagmi-example/package.json @@ -0,0 +1,32 @@ +{ + "name": "wagmi-example", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "biome check .", + "preview": "vite preview", + "compile": "solcjs contract/Greeter.sol --optimize --abi --bin -o contract/" + }, + "dependencies": { + "@tanstack/react-query": "5.45.1", + "@wagmi/core": "^2.13.4", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "^2.21.0", + "wagmi": "^2.12.7", + "solc": "^0.8.26" + }, + "devDependencies": { + "@biomejs/biome": "^1.8.0", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.2.1", + "@wagmi/cli": "^2.1.15", + "buffer": "^6.0.3", + "typescript": "^5.4.5", + "vite": "^5.2.11" + } +} diff --git a/tools/wagmi-example/src/App.tsx b/tools/wagmi-example/src/App.tsx new file mode 100644 index 0000000000..63a0f15360 --- /dev/null +++ b/tools/wagmi-example/src/App.tsx @@ -0,0 +1,35 @@ +/*- + * + * Hedera JSON RPC Relay - Wagmi Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import {Profile} from "./components/Profile.tsx"; +import {Contract} from "./components/Contract.tsx"; + +function App() { + return ( + <> + {/*This part covers account handling*/} + + {/*Deploy and interact with contract*/} + + + ) +} + +export default App diff --git a/tools/wagmi-example/src/components/Contract.tsx b/tools/wagmi-example/src/components/Contract.tsx new file mode 100644 index 0000000000..4f9cea022c --- /dev/null +++ b/tools/wagmi-example/src/components/Contract.tsx @@ -0,0 +1,59 @@ +/*- + * + * Hedera JSON RPC Relay - Wagmi Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import {useDeploy, useReadGreet, useWriteGreet,} from "../hooks/use-deploy.ts"; +import {useState} from "react"; +import {useAccount} from "wagmi"; + +export function Contract() { + const {status} = useAccount() + + const {deployContract, contractAddress, isDeployed} = useDeploy(); + const {data, status: greetStatusView, refetch} = useReadGreet(contractAddress) + const {status: setGreetStatus, setGreeting} = useWriteGreet(contractAddress, refetch) + + const [greetingInput, setGreetingInput] = useState("") + + return status === "connected" ? ( +
+

Deploy greeter contract

+ {!isDeployed ? ( +
+ +
+ ) : ( +

Your contract is deployed on address: {contractAddress}

+ )} + + {isDeployed && ( +
+

Current greet: {JSON.stringify(data)}

+

Get greet status: {greetStatusView}

+
+
+ setGreetingInput(e.target.value)}/> + +
+

Send transaction status: {setGreetStatus}

+
+ )} +
+ ) : null; +} diff --git a/tools/wagmi-example/src/components/Profile.tsx b/tools/wagmi-example/src/components/Profile.tsx new file mode 100644 index 0000000000..41389a77a5 --- /dev/null +++ b/tools/wagmi-example/src/components/Profile.tsx @@ -0,0 +1,77 @@ +/*- + * + * Hedera JSON RPC Relay - Wagmi Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import {useAccountBalance} from "../hooks/use-balance.ts"; +import {useAccount, useConnect, useDisconnect} from "wagmi"; + +export function Profile() { + const {status, address} = useAccount() + const {disconnect} = useDisconnect() + const {balance} = useAccountBalance(); + const {connectors, connect, status: statusWalletConnect, error: walletError} = useConnect() + + const isLoading = statusWalletConnect === "pending" || status === "connecting" || status === "reconnecting"; + + return ( +
+ {isLoading && ( +
+

Loading...

+
+ )} + + { + status === "connected" && ( +
+

Account

+ +
+

status: {status}

+

addresses: {address ?? ""}

+

balance: {balance}

+
+ + +
+ ) + } + + {status === 'disconnected' && ( +
+

Connect your account

+
+ {connectors.map((connector) => ( + + ))} +
+
{walletError?.message}
+
+ )} +
+ ) +} diff --git a/tools/wagmi-example/src/hooks/use-balance.ts b/tools/wagmi-example/src/hooks/use-balance.ts new file mode 100644 index 0000000000..78b7c546ff --- /dev/null +++ b/tools/wagmi-example/src/hooks/use-balance.ts @@ -0,0 +1,35 @@ +/*- + * + * Hedera JSON RPC Relay - Wagmi Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { useAccount, useBalance } from 'wagmi'; + +function calculateBalance(value: BigInt, decimals: number) { + return Number(value) / 10 ** decimals; +} + +export function useAccountBalance() { + const { address } = useAccount(); + + const { data } = useBalance({ address }); + + const balance = data && calculateBalance(data.value, data.decimals); + + return { balance }; +} diff --git a/tools/wagmi-example/src/hooks/use-deploy.ts b/tools/wagmi-example/src/hooks/use-deploy.ts new file mode 100644 index 0000000000..27c249e859 --- /dev/null +++ b/tools/wagmi-example/src/hooks/use-deploy.ts @@ -0,0 +1,88 @@ +/*- + * + * Hedera JSON RPC Relay - Wagmi Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { useDeployContract, useReadContract, useTransactionReceipt, useWriteContract } from 'wagmi'; +import { hederaTestnet } from 'wagmi/chains'; + +// You can find out in the documentation how to this files +import abi from '../../contract/contract_Greeter_sol_Greeter.json'; +// contract_Greeter_sol_Greeter.bin +const bin = + '608060405234801562000010575f80fd5b5060405162000b9438038062000b948339818101604052810190620000369190620001d3565b805f908162000046919062000459565b50506200053d565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000af8262000067565b810181811067ffffffffffffffff82111715620000d157620000d062000077565b5b80604052505050565b5f620000e56200004e565b9050620000f38282620000a4565b919050565b5f67ffffffffffffffff82111562000115576200011462000077565b5b620001208262000067565b9050602081019050919050565b5f5b838110156200014c5780820151818401526020810190506200012f565b5f8484015250505050565b5f6200016d6200016784620000f8565b620000da565b9050828152602081018484840111156200018c576200018b62000063565b5b620001998482856200012d565b509392505050565b5f82601f830112620001b857620001b76200005f565b5b8151620001ca84826020860162000157565b91505092915050565b5f60208284031215620001eb57620001ea62000057565b5b5f82015167ffffffffffffffff8111156200020b576200020a6200005b565b5b6200021984828501620001a1565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200027157607f821691505b6020821081036200028757620002866200022c565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620002eb7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002ae565b620002f78683620002ae565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620003416200033b62000335846200030f565b62000318565b6200030f565b9050919050565b5f819050919050565b6200035c8362000321565b620003746200036b8262000348565b848454620002ba565b825550505050565b5f90565b6200038a6200037c565b6200039781848462000351565b505050565b5b81811015620003be57620003b25f8262000380565b6001810190506200039d565b5050565b601f8211156200040d57620003d7816200028d565b620003e2846200029f565b81016020851015620003f2578190505b6200040a62000401856200029f565b8301826200039c565b50505b505050565b5f82821c905092915050565b5f6200042f5f198460080262000412565b1980831691505092915050565b5f6200044983836200041e565b9150826002028217905092915050565b620004648262000222565b67ffffffffffffffff81111562000480576200047f62000077565b5b6200048c825462000259565b62000499828285620003c2565b5f60209050601f831160018114620004cf575f8415620004ba578287015190505b620004c685826200043c565b86555062000535565b601f198416620004df866200028d565b5f5b828110156200050857848901518255600182019150602085019450602081019050620004e1565b8683101562000528578489015162000524601f8916826200041e565b8355505b6001600288020188555050505b505050505050565b610649806200054b5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063a413686214610038578063cfae321714610054575b5f80fd5b610052600480360381019061004d9190610260565b610072565b005b61005c610084565b6040516100699190610321565b60405180910390f35b805f90816100809190610544565b5050565b60605f80546100929061036e565b80601f01602080910402602001604051908101604052809291908181526020018280546100be9061036e565b80156101095780601f106100e057610100808354040283529160200191610109565b820191905f5260205f20905b8154815290600101906020018083116100ec57829003601f168201915b5050505050905090565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6101728261012c565b810181811067ffffffffffffffff821117156101915761019061013c565b5b80604052505050565b5f6101a3610113565b90506101af8282610169565b919050565b5f67ffffffffffffffff8211156101ce576101cd61013c565b5b6101d78261012c565b9050602081019050919050565b828183375f83830152505050565b5f6102046101ff846101b4565b61019a565b9050828152602081018484840111156102205761021f610128565b5b61022b8482856101e4565b509392505050565b5f82601f83011261024757610246610124565b5b81356102578482602086016101f2565b91505092915050565b5f602082840312156102755761027461011c565b5b5f82013567ffffffffffffffff81111561029257610291610120565b5b61029e84828501610233565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156102de5780820151818401526020810190506102c3565b5f8484015250505050565b5f6102f3826102a7565b6102fd81856102b1565b935061030d8185602086016102c1565b6103168161012c565b840191505092915050565b5f6020820190508181035f83015261033981846102e9565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061038557607f821691505b60208210810361039857610397610341565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026103fa7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826103bf565b61040486836103bf565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61044861044361043e8461041c565b610425565b61041c565b9050919050565b5f819050919050565b6104618361042e565b61047561046d8261044f565b8484546103cb565b825550505050565b5f90565b61048961047d565b610494818484610458565b505050565b5b818110156104b7576104ac5f82610481565b60018101905061049a565b5050565b601f8211156104fc576104cd8161039e565b6104d6846103b0565b810160208510156104e5578190505b6104f96104f1856103b0565b830182610499565b50505b505050565b5f82821c905092915050565b5f61051c5f1984600802610501565b1980831691505092915050565b5f610534838361050d565b9150826002028217905092915050565b61054d826102a7565b67ffffffffffffffff8111156105665761056561013c565b5b610570825461036e565b61057b8282856104bb565b5f60209050601f8311600181146105ac575f841561059a578287015190505b6105a48582610529565b86555061060b565b601f1984166105ba8661039e565b5f5b828110156105e1578489015182556001820191506020850194506020810190506105bc565b868310156105fe57848901516105fa601f89168261050d565b8355505b6001600288020188555050505b50505050505056fea26469706673582212206a9622606bbe375c853566219d542a74563f89b1545eccc6fda54ae4187ec97c64736f6c63430008180033'; + +export function useDeploy() { + const { data: deployTxHash, deployContract, status: deployStatus } = useDeployContract(); + const { data: deployReceipt, status: receiptStatus } = useTransactionReceipt({ + hash: deployTxHash, + query: { + enabled: Boolean(deployTxHash), + }, + }); + + return { + deployContract: () => { + deployContract({ + bytecode: `0x${bin}`, + abi, + args: ['Hello from react'], + }); + }, + deployTxHash, + deployReceipt, + contractAddress: deployReceipt?.contractAddress ?? undefined, + isDeployed: deployStatus === 'success' && receiptStatus === 'success', + }; +} + +type ContractAddress = `0x${string}` | undefined; + +export function useWriteGreet(address: ContractAddress, refetch: () => void) { + const { writeContractAsync, status, data } = useWriteContract(); + + return { + setGreeting: async (greet: string) => { + if (!address) { + return; + } + await writeContractAsync({ + abi, + address, + functionName: 'setGreeting', + args: [greet], + }); + refetch(); + }, + status, + data, + }; +} + +export function useReadGreet(address: ContractAddress) { + return useReadContract({ + query: { + enabled: Boolean(address), + }, + abi, + address, + functionName: 'greet', + args: [], + chainId: hederaTestnet.id, + }); +} diff --git a/tools/wagmi-example/src/index.css b/tools/wagmi-example/src/index.css new file mode 100644 index 0000000000..1cb417d9cc --- /dev/null +++ b/tools/wagmi-example/src/index.css @@ -0,0 +1,25 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} + +.flex { + display: flex; +} diff --git a/tools/wagmi-example/src/main.tsx b/tools/wagmi-example/src/main.tsx new file mode 100644 index 0000000000..cda3a08d7e --- /dev/null +++ b/tools/wagmi-example/src/main.tsx @@ -0,0 +1,44 @@ +/*- + * + * Hedera JSON RPC Relay - Wagmi Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import {Buffer} from 'buffer' +import {QueryClient, QueryClientProvider} from '@tanstack/react-query' +import React from 'react' +import ReactDOM from 'react-dom/client' +import {WagmiProvider} from 'wagmi' + +import App from './App.tsx' +import {config} from './wagmi.ts' + +import './index.css' + +globalThis.Buffer = Buffer + +const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + , +) diff --git a/tools/wagmi-example/src/vite-env.d.ts b/tools/wagmi-example/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/tools/wagmi-example/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tools/wagmi-example/src/wagmi.ts b/tools/wagmi-example/src/wagmi.ts new file mode 100644 index 0000000000..21a4f2d3df --- /dev/null +++ b/tools/wagmi-example/src/wagmi.ts @@ -0,0 +1,38 @@ +/*- + * + * Hedera JSON RPC Relay - Wagmi Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { http, createConfig } from 'wagmi'; +import { hedera, hederaTestnet } from 'wagmi/chains'; +import { coinbaseWallet, injected } from 'wagmi/connectors'; + +export const config = createConfig({ + chains: [hederaTestnet, hedera], + connectors: [injected(), coinbaseWallet()], + transports: { + [hederaTestnet.id]: http(), + [hedera.id]: http(), + }, +}); + +declare module 'wagmi' { + interface Register { + config: typeof config; + } +} diff --git a/tools/wagmi-example/tsconfig.json b/tools/wagmi-example/tsconfig.json new file mode 100644 index 0000000000..1204fadf64 --- /dev/null +++ b/tools/wagmi-example/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "composite": true, + }, + "include": ["src", "vite.config.ts"], +} diff --git a/tools/wagmi-example/vite.config.ts b/tools/wagmi-example/vite.config.ts new file mode 100644 index 0000000000..36f7f4e1bc --- /dev/null +++ b/tools/wagmi-example/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) From 2e2fc0791bc9ba3c07250b2b6e9ab797e55dc18b Mon Sep 17 00:00:00 2001 From: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:11:19 +0300 Subject: [PATCH 27/38] test: add helper method for overriding env variables (#3022) * test: add helper method for overriding env variables Signed-off-by: Victor Yanev * chore: improve readability Signed-off-by: Victor Yanev * fix: usages Signed-off-by: Victor Yanev * fix: cacheService.spec.ts Signed-off-by: Victor Yanev * fix: utils.spec.ts Signed-off-by: Victor Yanev * fix: relay.spec.ts and sdkClient.spec.ts Signed-off-by: Victor Yanev * fix: eth_call.spec.ts, eth_estimateGas.spec.ts, ethGetBlockBy.spec.ts Signed-off-by: Victor Yanev * fix: use helper method to override envs in acceptance test specs Signed-off-by: Victor Yanev * fix: hapiService.spec.ts and precheck.spec.ts Signed-off-by: Victor Yanev * fix: sdkClient.spec.ts Signed-off-by: Victor Yanev * fix: cacheService.spec.ts Signed-off-by: Victor Yanev * fix: metricService.spec.ts and subscriptionController.spec.ts Signed-off-by: Victor Yanev * fix: rateLimiter.spec.ts Signed-off-by: Victor Yanev * fix: failing tests Signed-off-by: Victor Yanev * chore: revert some changes in rpc_batch3.spec.ts Signed-off-by: Victor Yanev * fix: subscribe.spec.ts Signed-off-by: Victor Yanev * chore: add docs with examples to `overrideEnvs` and `withOverriddenEnvs` Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * fix: build image test Signed-off-by: Victor Yanev * Merge branch 'main' into add-helper-method-for-overriding-env-variables Signed-off-by: Victor Yanev # Conflicts: # packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts # packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts * chore: address comments + fix conflicts after merge Signed-off-by: Victor Yanev * chore: optimize TTL tests in localLRUCache.spec.ts Signed-off-by: Victor Yanev * fix: failing unit test after merge from main Signed-off-by: Victor Yanev * chore: optimize imports after merge from main Signed-off-by: Victor Yanev --------- Signed-off-by: Victor Yanev --- .../ethService/ethFilterService/index.ts | 2 +- packages/relay/tests/helpers.ts | 96 +++- .../tests/lib/clients/localLRUCache.spec.ts | 13 +- .../tests/lib/clients/redisCache.spec.ts | 4 +- packages/relay/tests/lib/eth/eth_call.spec.ts | 154 ++--- .../tests/lib/eth/eth_estimateGas.spec.ts | 58 +- .../tests/lib/eth/eth_feeHistory.spec.ts | 28 +- .../relay/tests/lib/eth/eth_gasPrice.spec.ts | 65 ++- .../tests/lib/eth/eth_getBalance.spec.ts | 12 +- .../tests/lib/eth/eth_getBlockByHash.spec.ts | 17 +- .../lib/eth/eth_getBlockByNumber.spec.ts | 21 +- ...eth_getBlockTransactionCountByHash.spec.ts | 4 +- .../relay/tests/lib/eth/eth_getCode.spec.ts | 7 +- .../relay/tests/lib/eth/eth_getLogs.spec.ts | 101 ++-- .../tests/lib/eth/eth_getStorageAt.spec.ts | 13 +- ...etTransactionByBlockNumberAndIndex.spec.ts | 4 +- .../lib/eth/eth_getTransactionCount.spec.ts | 19 +- .../lib/eth/eth_getTransactionReceipt.spec.ts | 4 +- .../lib/eth/eth_sendRawTransaction.spec.ts | 14 +- .../relay/tests/lib/ethGetBlockBy.spec.ts | 12 +- packages/relay/tests/lib/formatters.spec.ts | 15 +- packages/relay/tests/lib/hapiService.spec.ts | 235 ++++---- packages/relay/tests/lib/hbarLimiter.spec.ts | 537 +++++++++--------- .../relay/tests/lib/mirrorNodeClient.spec.ts | 41 +- packages/relay/tests/lib/net.spec.ts | 59 +- packages/relay/tests/lib/openrpc.spec.ts | 2 +- packages/relay/tests/lib/precheck.spec.ts | 25 +- packages/relay/tests/lib/relay.spec.ts | 43 +- ...hAddressHbarSpendingPlanRepository.spec.ts | 4 +- .../hbarSpendingPlanRepository.spec.ts | 5 +- ...pAddressHbarSpendingPlanRepository.spec.ts | 9 +- packages/relay/tests/lib/sdkClient.spec.ts | 255 ++++----- .../cacheService/cacheService.spec.ts | 11 +- .../lib/services/debugService/debug.spec.ts | 337 ++++++----- .../tests/lib/services/eth/filter.spec.ts | 199 +++---- .../metricService/metricService.spec.ts | 280 ++++----- .../tests/lib/subscriptionController.spec.ts | 15 +- packages/relay/tests/lib/utils.spec.ts | 19 +- packages/relay/tests/lib/web3.spec.ts | 23 +- .../tests/acceptance/cacheService.spec.ts | 38 +- .../tests/acceptance/hbarLimiter.spec.ts | 121 ++-- .../tests/acceptance/rpc_batch3.spec.ts | 12 +- .../integration/koaJsonRpc/utils.spec.ts | 123 ++-- .../tests/integration/rateLimiter.spec.ts | 36 +- .../server/tests/integration/server.spec.ts | 119 ++-- .../tests/acceptance/batchRequest.spec.ts | 61 +- .../tests/acceptance/subscribe.spec.ts | 220 +++---- .../acceptance/subscribeNewHeads.spec.ts | 150 +++-- .../tests/acceptance/validations.spec.ts | 4 +- packages/ws-server/tests/helper/index.ts | 73 ++- packages/ws-server/tests/unit/utils.spec.ts | 145 ++--- .../ws-server/tests/unit/validations.spec.ts | 22 +- 52 files changed, 1935 insertions(+), 1951 deletions(-) diff --git a/packages/relay/src/lib/services/ethService/ethFilterService/index.ts b/packages/relay/src/lib/services/ethService/ethFilterService/index.ts index 1e4bd60079..df586cf989 100644 --- a/packages/relay/src/lib/services/ethService/ethFilterService/index.ts +++ b/packages/relay/src/lib/services/ethService/ethFilterService/index.ts @@ -187,7 +187,7 @@ export class FilterService implements IFilterService { return predefined.UNSUPPORTED_METHOD; } - public async getFilterLogs(filterId: string, requestDetails: RequestDetails): Promise { + public async getFilterLogs(filterId: string, requestDetails: RequestDetails): Promise { this.logger.trace(`${requestDetails.formattedRequestId} getFilterLogs(${filterId})`); FilterService.requireFiltersEnabled(); diff --git a/packages/relay/tests/helpers.ts b/packages/relay/tests/helpers.ts index cedce4c6e0..53d8a7f82d 100644 --- a/packages/relay/tests/helpers.ts +++ b/packages/relay/tests/helpers.ts @@ -885,40 +885,98 @@ export const calculateTxRecordChargeAmount = (exchangeRateIncents: number) => { }; export const useInMemoryRedisServer = (logger: Logger, port: number) => { - let envsToReset: { TEST?: string; REDIS_ENABLED?: string; REDIS_URL?: string }; + overrideEnvsInMochaDescribe({ TEST: 'false', REDIS_ENABLED: 'true', REDIS_URL: `redis://127.0.0.1:${port}` }); + let redisInMemoryServer: RedisInMemoryServer; before(async () => { - ({ envsToReset, redisInMemoryServer } = await startRedisInMemoryServer(logger, port)); + redisInMemoryServer = await startRedisInMemoryServer(logger, port); }); after(async () => { - await stopRedisInMemoryServer(redisInMemoryServer, envsToReset); + await stopRedisInMemoryServer(redisInMemoryServer); }); }; export const startRedisInMemoryServer = async (logger: Logger, port: number) => { const redisInMemoryServer = new RedisInMemoryServer(logger.child({ name: 'RedisInMemoryServer' }), port); await redisInMemoryServer.start(); - const envsToReset = { - TEST: process.env.TEST, - REDIS_ENABLED: process.env.REDIS_ENABLED, - REDIS_URL: process.env.REDIS_URL, - }; - process.env.TEST = 'false'; - process.env.REDIS_ENABLED = 'true'; - process.env.REDIS_URL = `redis://127.0.0.1:${port}`; - return { redisInMemoryServer, envsToReset }; + return redisInMemoryServer; }; -export const stopRedisInMemoryServer = async ( - redisInMemoryServer: RedisInMemoryServer, - envsToReset: { TEST?: string; REDIS_ENABLED?: string; REDIS_URL?: string }, -): Promise => { +export const stopRedisInMemoryServer = async (redisInMemoryServer: RedisInMemoryServer): Promise => { await redisInMemoryServer.stop(); - process.env.TEST = envsToReset.TEST; - process.env.REDIS_ENABLED = envsToReset.REDIS_ENABLED; - process.env.REDIS_URL = envsToReset.REDIS_URL; +}; + +/** + * Temporarily overrides environment variables for the duration of the encapsulating describe block. + * @param envs - An object containing key-value pairs of environment variables to set. + * + * @example + * describe('given TEST is set to false', () => { + * overrideEnvsInMochaDescribe({ TEST: 'false' }); + * + * it('should return false', () => { + * expect(process.env.TEST).to.equal('false'); + * }); + * }); + * + * it('should return true', () => { + * expect(process.env.TEST).to.equal('true'); + * }); + */ +export const overrideEnvsInMochaDescribe = (envs: NodeJS.Dict) => { + let envsToReset: NodeJS.Dict = {}; + + const overrideEnv = (object: NodeJS.Dict, key: string, value: string | undefined) => { + if (value === undefined) { + delete object[key]; + } else { + object[key] = value; + } + }; + + before(() => { + for (const key in envs) { + envsToReset[key] = process.env[key]; + overrideEnv(process.env, key, envs[key]); + } + }); + + after(() => { + for (const key in envs) { + overrideEnv(process.env, key, envsToReset[key]); + } + }); +}; + +/** + * Overrides environment variables for the duration of the provided tests. + * + * @param {NodeJS.Dict} envs - An object containing key-value pairs of environment variables to set. + * @param {Function} tests - A function containing the tests to run with the overridden environment variables. + * + * @example + * withOverriddenEnvsInMochaTest({ TEST: 'false' }, () => { + * it('should return false', () => { + * expect(process.env.TEST).to.equal('false'); + * }); + * }); + * + * it('should return true', () => { + * expect(process.env.TEST).to.equal('true'); + * }); + */ +export const withOverriddenEnvsInMochaTest = (envs: NodeJS.Dict, tests: () => void) => { + const overriddenEnvs = Object.entries(envs) + .map(([key, value]) => `${key}=${value}`) + .join(', '); + + describe(`given ${overriddenEnvs} are set`, () => { + overrideEnvsInMochaDescribe(envs); + + tests(); + }); }; export const estimateFileTransactionsFee = ( diff --git a/packages/relay/tests/lib/clients/localLRUCache.spec.ts b/packages/relay/tests/lib/clients/localLRUCache.spec.ts index f2a27284d0..340c542253 100644 --- a/packages/relay/tests/lib/clients/localLRUCache.spec.ts +++ b/packages/relay/tests/lib/clients/localLRUCache.spec.ts @@ -23,7 +23,7 @@ import chaiAsPromised from 'chai-as-promised'; import { Registry } from 'prom-client'; import pino from 'pino'; import { LocalLRUCache } from '../../../src/lib/clients'; -import constants from '../../../src/lib/constants'; +import { overrideEnvsInMochaDescribe } from '../../helpers'; import { RequestDetails } from '../../../src/lib/types'; const logger = pino(); @@ -107,13 +107,9 @@ describe('LocalLRUCache Test Suite', async function () { }); describe('verify cache management', async function () { - beforeEach(() => { - process.env.CACHE_MAX = constants.CACHE_MAX.toString(); - }); + overrideEnvsInMochaDescribe({ CACHE_MAX: '2' }); it('verify cache size', async function () { - const cacheMaxSize = 2; - process.env.CACHE_MAX = `${cacheMaxSize}`; const customLocalLRUCache = new LocalLRUCache(logger.child({ name: `cache` }), registry); const keyValuePairs = { key1: 'value1', @@ -149,8 +145,9 @@ describe('LocalLRUCache Test Suite', async function () { it('verify cache ttl nature', async function () { const customLocalLRUCache = new LocalLRUCache(logger.child({ name: `cache` }), registry); const key = 'key'; - await customLocalLRUCache.set(key, 'value', callingMethod, requestDetails, 100); // set ttl to 1 ms - await new Promise((r) => setTimeout(r, 500)); // wait for ttl to expire + const ttl = 100; // set ttl to 100ms + await customLocalLRUCache.set(key, 'value', callingMethod, requestDetails, ttl); + await new Promise((r) => setTimeout(r, ttl + 100)); // wait for ttl to expire const cacheValue = await customLocalLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.null; }); diff --git a/packages/relay/tests/lib/clients/redisCache.spec.ts b/packages/relay/tests/lib/clients/redisCache.spec.ts index 16d150e67d..b094e3d7c2 100644 --- a/packages/relay/tests/lib/clients/redisCache.spec.ts +++ b/packages/relay/tests/lib/clients/redisCache.spec.ts @@ -106,7 +106,7 @@ describe('RedisCache Test Suite', async function () { it('should be able to set cache with TTL less than 1000 milliseconds', async () => { const key = 'int'; const value = 1; - const ttl = 500; + const ttl = 100; await redisCache.set(key, value, callingMethod, requestDetails, ttl); @@ -122,7 +122,7 @@ describe('RedisCache Test Suite', async function () { it('should be able to set cache with TTL greater than 1000 milliseconds', async () => { const key = 'int'; const value = 1; - const ttl = 1500; + const ttl = 1100; await redisCache.set(key, value, callingMethod, requestDetails, ttl); diff --git a/packages/relay/tests/lib/eth/eth_call.spec.ts b/packages/relay/tests/lib/eth/eth_call.spec.ts index 1a14846f20..fe19bf3ceb 100644 --- a/packages/relay/tests/lib/eth/eth_call.spec.ts +++ b/packages/relay/tests/lib/eth/eth_call.spec.ts @@ -53,6 +53,8 @@ import { defaultErrorMessageText, ethCallFailing, mockData, + overrideEnvsInMochaDescribe, + withOverriddenEnvsInMochaTest, } from '../../helpers'; import { generateEthTestEnv } from './eth-helpers'; import { IContractCallRequest, IContractCallResponse, RequestDetails } from '../../../src/lib/types'; @@ -63,7 +65,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethCall Eth Call spec', async function () { this.timeout(10000); @@ -78,6 +79,8 @@ describe('@ethCall Eth Call spec', async function () { const requestDetails = new RequestDetails({ requestId: 'eth_callTest', ipAddress: '0.0.0.0' }); + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + this.beforeEach(async () => { // reset cache and restMock await cacheService.clear(requestDetails); @@ -85,8 +88,6 @@ describe('@ethCall Eth Call spec', async function () { sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; restMock.onGet(`accounts/${ACCOUNT_ADDRESS_1}${NO_TRANSACTIONS}`).reply(200, { account: '0.0.1723', evm_address: ACCOUNT_ADDRESS_1, @@ -96,7 +97,6 @@ describe('@ethCall Eth Call spec', async function () { this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('eth_call precheck failures', async function () { @@ -104,9 +104,7 @@ describe('@ethCall Eth Call spec', async function () { let callMirrorNodeSpy: sinon.SinonSpy; let sandbox: sinon.SinonSandbox; - this.beforeAll(() => { - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'false'; - }); + overrideEnvsInMochaDescribe({ ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: 'false' }); beforeEach(() => { sandbox = sinon.createSandbox(); @@ -118,10 +116,6 @@ describe('@ethCall Eth Call spec', async function () { sandbox.restore(); }); - this.afterAll(() => { - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'true'; - }); - it('eth_call with incorrect `to` field length', async function () { await ethCallFailing( ethImpl, @@ -141,66 +135,66 @@ describe('@ethCall Eth Call spec', async function () { ); }); - it('should execute "eth_call" against mirror node with a false ETH_CALL_DEFAULT_TO_CONSENSUS_NODE', async function () { - web3Mock.onPost('contracts/call').reply(200); - const initialEthCallConesneusFF = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; + withOverriddenEnvsInMochaTest({ ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: 'false' }, () => { + it('should execute "eth_call" against mirror node with a false ETH_CALL_DEFAULT_TO_CONSENSUS_NODE', async function () { + web3Mock.onPost('contracts/call').reply(200); + restMock.onGet(`contracts/${defaultCallData.from}`).reply(404); + restMock.onGet(`accounts/${defaultCallData.from}${NO_TRANSACTIONS}`).reply(200, { + account: '0.0.1723', + evm_address: defaultCallData.from, + }); + restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'false'; - restMock.onGet(`contracts/${defaultCallData.from}`).reply(404); - restMock.onGet(`accounts/${defaultCallData.from}${NO_TRANSACTIONS}`).reply(200, { - account: '0.0.1723', - evm_address: defaultCallData.from, - }); - restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - await ethImpl.call( - { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, - 'latest', - requestDetails, - ); + await ethImpl.call( + { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, + 'latest', + requestDetails, + ); - assert(callMirrorNodeSpy.calledOnce); - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; + assert(callMirrorNodeSpy.calledOnce); + assert(callConsensusNodeSpy.notCalled); + }); }); - it('should execute "eth_call" against mirror node with an undefined ETH_CALL_DEFAULT_TO_CONSENSUS_NODE', async function () { - web3Mock.onPost('contracts/call').reply(200); - const initialEthCallConesneusFF = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; + withOverriddenEnvsInMochaTest({ ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: undefined }, () => { + it('should execute "eth_call" against mirror node with an undefined ETH_CALL_DEFAULT_TO_CONSENSUS_NODE', async function () { + web3Mock.onPost('contracts/call').reply(200); + restMock.onGet(`contracts/${defaultCallData.from}`).reply(404); + restMock.onGet(`accounts/${defaultCallData.from}${NO_TRANSACTIONS}`).reply(200, { + account: '0.0.1723', + evm_address: defaultCallData.from, + }); + restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - delete process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; - restMock.onGet(`contracts/${defaultCallData.from}`).reply(404); - restMock.onGet(`accounts/${defaultCallData.from}${NO_TRANSACTIONS}`).reply(200, { - account: '0.0.1723', - evm_address: defaultCallData.from, - }); - restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - await ethImpl.call( - { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, - 'latest', - requestDetails, - ); + await ethImpl.call( + { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, + 'latest', + requestDetails, + ); - assert(callMirrorNodeSpy.calledOnce); - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; + assert(callMirrorNodeSpy.calledOnce); + assert(callConsensusNodeSpy.notCalled); + }); }); - it('should execute "eth_call" against mirror node with a ETH_CALL_DEFAULT_TO_CONSENSUS_NODE set to true', async function () { - const initialEthCallConesneusFF = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; + withOverriddenEnvsInMochaTest({ ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: 'true' }, () => { + it('should execute "eth_call" against consensus node with a ETH_CALL_DEFAULT_TO_CONSENSUS_NODE set to true', async function () { + restMock.onGet(`contracts/${defaultCallData.from}`).reply(404); + restMock.onGet(`accounts/${defaultCallData.from}${NO_TRANSACTIONS}`).reply(200, { + account: '0.0.1723', + evm_address: defaultCallData.from, + }); + restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'true'; - restMock.onGet(`contracts/${defaultCallData.from}`).reply(404); - restMock.onGet(`accounts/${defaultCallData.from}${NO_TRANSACTIONS}`).reply(200, { - account: '0.0.1723', - evm_address: defaultCallData.from, - }); - restMock.onGet(`contracts/${defaultCallData.to}`).reply(200, DEFAULT_CONTRACT); - await ethImpl.call( - { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, - 'latest', - requestDetails, - ); + await ethImpl.call( + { ...defaultCallData, gas: `0x${defaultCallData.gas.toString(16)}` }, + 'latest', + requestDetails, + ); - assert(callConsensusNodeSpy.calledOnce); - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; + assert(callMirrorNodeSpy.notCalled); + assert(callConsensusNodeSpy.calledOnce); + }); }); it('to field is not a contract or token', async function () { @@ -246,16 +240,7 @@ describe('@ethCall Eth Call spec', async function () { }); describe('eth_call using consensus node', async function () { - let initialEthCallDefaultsToConsensus: string | undefined; - - before(() => { - initialEthCallDefaultsToConsensus = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'true'; - }); - - after(() => { - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallDefaultsToConsensus; - }); + overrideEnvsInMochaDescribe({ ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: 'true' }); it('eth_call with no gas', async function () { restMock.onGet(`contracts/${ACCOUNT_ADDRESS_1}`).reply(404); @@ -472,16 +457,8 @@ describe('@ethCall Eth Call spec', async function () { gas: 400000, value: null, }; - let initialEthCallConesneusFF: string | undefined; - before(() => { - initialEthCallConesneusFF = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'false'; - }); - - after(() => { - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallConesneusFF; - }); + overrideEnvsInMochaDescribe({ ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: 'false' }); //temporary workaround until precompiles are implemented in Mirror node evm module beforeEach(() => { @@ -930,24 +907,15 @@ describe('@ethCall Eth Call spec', async function () { }); describe('eth_call using consensus node because of redirect by selector', async function () { - let initialForceToConsensusBySelector: string | undefined; - let initialEthCallDefaultsToConsensus: string | undefined; const REDIRECTED_SELECTOR = '0x4d8fdd6d'; const NON_REDIRECTED_SELECTOR = '0xaaaaaaaa'; let callConsensusNodeSpy: sinon.SinonSpy; let callMirrorNodeSpy: sinon.SinonSpy; let sandbox: sinon.SinonSandbox; - before(() => { - initialForceToConsensusBySelector = process.env.ETH_CALL_FORCE_TO_CONSENSUS_BY_SELECTOR; - initialEthCallDefaultsToConsensus = process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE; - process.env.ETH_CALL_FORCE_TO_CONSENSUS_BY_SELECTOR = 'true'; - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'false'; - }); - - after(() => { - process.env.ETH_CALL_FORCE_TO_CONSENSUS_BY_SELECTOR = initialForceToConsensusBySelector; - process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = initialEthCallDefaultsToConsensus; + overrideEnvsInMochaDescribe({ + ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: 'false', + ETH_CALL_CONSENSUS_SELECTORS: JSON.stringify([REDIRECTED_SELECTOR.slice(2)]), }); beforeEach(() => { @@ -961,8 +929,6 @@ describe('@ethCall Eth Call spec', async function () { }); it('eth_call with matched selector redirects to consensus', async function () { - process.env.ETH_CALL_CONSENSUS_SELECTORS = JSON.stringify([REDIRECTED_SELECTOR.slice(2)]); - await ethImpl.call( { to: ACCOUNT_ADDRESS_1, diff --git a/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts b/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts index dd7f9ad23f..228cd7cce4 100644 --- a/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts +++ b/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts @@ -39,6 +39,7 @@ import { ONE_TINYBAR_IN_WEI_HEX, RECEIVER_ADDRESS, } from './eth-config'; +import { overrideEnvsInMochaDescribe, withOverriddenEnvsInMochaTest } from '../../helpers'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); @@ -46,7 +47,6 @@ use(chaiAsPromised); let sdkClientStub: SinonStubbedInstance; let getSdkClientStub: SinonStub<[], SDKClient>; let ethImplOverridden: Eth; -let currentMaxBlockRange: number; describe('@ethEstimateGas Estimate Gas spec', async function () { this.timeout(10000); @@ -77,18 +77,20 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }; const id = uuid(); const defaultGasOverride = constants.TX_DEFAULT_GAS_DEFAULT + 1; - process.env.TX_DEFAULT_GAS = defaultGasOverride.toString(); - this.beforeEach(() => { + overrideEnvsInMochaDescribe({ + ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1', + TX_DEFAULT_GAS: defaultGasOverride.toString(), + }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = createStubInstance(SDKClient); getSdkClientStub = stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); ethImplOverridden = new EthImpl(hapiServiceInstance, mirrorNodeInstance, logger, '0x12a', registry, cacheService); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; restMock.onGet(`accounts/undefined${NO_TRANSACTIONS}`).reply(404); mockGetAccount(hapiServiceInstance.getMainClientInstance().operatorAccountId!.toString(), 200, { evm_address: ACCOUNT_ADDRESS_1, @@ -98,7 +100,6 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('eth_estimateGas with contract call', async function () {}); @@ -405,31 +406,30 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { expect(estimatedGas).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!))); }); - it('should eth_estimateGas with contract revert and message does not equal executionReverted and ESTIMATE_GAS_THROWS is set to false', async function () { - const estimateGasThrows = process.env.ESTIMATE_GAS_THROWS; - process.env.ESTIMATE_GAS_THROWS = 'false'; - await mockContractCall( - transaction, - true, - 400, - { - _status: { - messages: [ - { - message: 'data field invalid hexadecimal string', - detail: '', - data: '', - }, - ], + withOverriddenEnvsInMochaTest({ ESTIMATE_GAS_THROWS: 'false' }, () => { + it('should eth_estimateGas with contract revert and message does not equal executionReverted and ESTIMATE_GAS_THROWS is set to false', async function () { + await mockContractCall( + transaction, + true, + 400, + { + _status: { + messages: [ + { + message: 'data field invalid hexadecimal string', + detail: '', + data: '', + }, + ], + }, }, - }, - requestDetails, - ); + requestDetails, + ); - const result: any = await ethImpl.estimateGas(transaction, id, requestDetails); + const result = await ethImpl.estimateGas(transaction, id, requestDetails); - expect(result).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!))); - process.env.ESTIMATE_GAS_THROWS = estimateGasThrows; + expect(result).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!))); + }); }); it('should eth_estimateGas with contract revert and message equals "execution reverted: Invalid number of recipients"', async function () { diff --git a/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts b/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts index afb930cda8..d33690f3b3 100644 --- a/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts +++ b/packages/relay/tests/lib/eth/eth_feeHistory.spec.ts @@ -32,12 +32,12 @@ import { BLOCKS_LIMIT_ORDER_URL, DEFAULT_BLOCK, DEFAULT_NETWORK_FEES, - ETH_FEE_HISTORY_VALUE, GAS_USED_RATIO, NOT_FOUND_RES, } from './eth-config'; import { numberTo0x } from '../../../src/formatters'; import { generateEthTestEnv } from './eth-helpers'; +import { overrideEnvsInMochaDescribe } from '../../helpers'; import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../../test.env') }); @@ -45,7 +45,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethFeeHistory using MirrorNode', async function () { this.timeout(10000); @@ -53,25 +52,20 @@ describe('@ethFeeHistory using MirrorNode', async function () { const requestDetails = new RequestDetails({ requestId: 'eth_feeHistoryTest', ipAddress: '0.0.0.0' }); - this.beforeEach(() => { + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; - }); - - this.afterAll(() => { - process.env.ETH_FEE_HISTORY_FIXED = ETH_FEE_HISTORY_VALUE; }); this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('eth_feeHistory with ... param', function () { @@ -237,20 +231,14 @@ describe('@ethFeeHistory using MirrorNode', async function () { return latestBlock; } - this.beforeAll(function () { - process.env.ETH_FEE_HISTORY_FIXED = 'true'; - }); + overrideEnvsInMochaDescribe({ ETH_FEE_HISTORY_FIXED: 'true' }); - this.beforeEach(function () { - cacheService.clear(requestDetails); + beforeEach(async function () { + await cacheService.clear(requestDetails); restMock.reset(); restMock.onGet(`network/fees`).reply(200, DEFAULT_NETWORK_FEES); }); - this.afterAll(function () { - process.env.ETH_FEE_HISTORY_FIXED = 'false'; - }); - it('eth_feeHistory with fixed fees', async function () { const latestBlockNumber = 20; const latestBlock = defineLatestBlockRestMock(latestBlockNumber); diff --git a/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts b/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts index 17f836b25a..c979673fb8 100644 --- a/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts +++ b/packages/relay/tests/lib/eth/eth_gasPrice.spec.ts @@ -30,7 +30,7 @@ import { DEFAULT_NETWORK_FEES, NOT_FOUND_RES } from './eth-config'; import { predefined } from '../../../src'; import RelayAssertions from '../../assertions'; import { generateEthTestEnv } from './eth-helpers'; -import { toHex } from '../../helpers'; +import { overrideEnvsInMochaDescribe, toHex } from '../../helpers'; import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -38,7 +38,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethGasPrice Gas Price spec', async function () { this.timeout(10000); @@ -46,21 +45,20 @@ describe('@ethGasPrice Gas Price spec', async function () { const requestDetails = new RequestDetails({ requestId: 'eth_getPriceTest', ipAddress: '0.0.0.0' }); - this.beforeEach(() => { + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; }); this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('@ethGasPrice', async function () { @@ -102,31 +100,36 @@ describe('@ethGasPrice Gas Price spec', async function () { '10.25', }; + let initialGasPrice: string; + + it('should return gas price without buffer', async function () { + await cacheService.clear(requestDetails); + initialGasPrice = await ethImpl.gasPrice(requestDetails); + expect(initialGasPrice).to.equal(toHex(DEFAULT_NETWORK_FEES.fees[2].gas * constants.TINYBAR_TO_WEIBAR_COEF)); + }); + for (let testCaseName in GAS_PRICE_PERCENTAGE_BUFFER_TESTCASES) { - it(testCaseName, async function () { - const GAS_PRICE_PERCENTAGE_BUFFER = GAS_PRICE_PERCENTAGE_BUFFER_TESTCASES[testCaseName]; - const initialGasPrice = await ethImpl.gasPrice(requestDetails); - process.env.GAS_PRICE_PERCENTAGE_BUFFER = GAS_PRICE_PERCENTAGE_BUFFER; - - await cacheService.clear(requestDetails); - - const gasPriceWithBuffer = await ethImpl.gasPrice(requestDetails); - process.env.GAS_PRICE_PERCENTAGE_BUFFER = '0'; - - const expectedInitialGasPrice = toHex(DEFAULT_NETWORK_FEES.fees[2].gas * constants.TINYBAR_TO_WEIBAR_COEF); - const expectedGasPriceWithBuffer = toHex( - Number(expectedInitialGasPrice) + - Math.round( - (Number(expectedInitialGasPrice) / constants.TINYBAR_TO_WEIBAR_COEF) * - (Number(GAS_PRICE_PERCENTAGE_BUFFER || 0) / 100), - ) * - constants.TINYBAR_TO_WEIBAR_COEF, - ); - - expect(expectedInitialGasPrice).to.not.equal(expectedGasPriceWithBuffer); - expect(initialGasPrice).to.not.equal(gasPriceWithBuffer); - expect(initialGasPrice).to.equal(expectedInitialGasPrice); - expect(gasPriceWithBuffer).to.equal(expectedGasPriceWithBuffer); + const GAS_PRICE_PERCENTAGE_BUFFER = GAS_PRICE_PERCENTAGE_BUFFER_TESTCASES[testCaseName]; + + describe(testCaseName, async function () { + overrideEnvsInMochaDescribe({ GAS_PRICE_PERCENTAGE_BUFFER: GAS_PRICE_PERCENTAGE_BUFFER }); + + it(`should return gas price with buffer`, async function () { + const expectedInitialGasPrice = toHex(DEFAULT_NETWORK_FEES.fees[2].gas * constants.TINYBAR_TO_WEIBAR_COEF); + const expectedGasPriceWithBuffer = toHex( + Number(expectedInitialGasPrice) + + Math.round( + (Number(expectedInitialGasPrice) / constants.TINYBAR_TO_WEIBAR_COEF) * + (Number(GAS_PRICE_PERCENTAGE_BUFFER || 0) / 100), + ) * + constants.TINYBAR_TO_WEIBAR_COEF, + ); + + const gasPriceWithBuffer = await ethImpl.gasPrice(requestDetails); + + expect(gasPriceWithBuffer).to.not.equal(initialGasPrice); + expect(gasPriceWithBuffer).to.equal(expectedGasPriceWithBuffer); + }); }); } }); diff --git a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts index 40b56bd3f2..8dd4e3bd6f 100644 --- a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts @@ -24,7 +24,7 @@ import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; import { EthImpl } from '../../../src/lib/eth'; -import { buildCryptoTransferTransaction } from '../../helpers'; +import { buildCryptoTransferTransaction, overrideEnvsInMochaDescribe } from '../../helpers'; import { SDKClient } from '../../../src/lib/clients'; import { numberTo0x } from '../../../dist/formatters'; import { @@ -51,7 +51,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethGetBalance using MirrorNode', async function () { this.timeout(10000); @@ -59,22 +58,21 @@ describe('@ethGetBalance using MirrorNode', async function () { const requestDetails = new RequestDetails({ requestId: 'eth_getBalanceTest', ipAddress: '0.0.0.0' }); - this.beforeEach(() => { + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; }); this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); it('should return balance from mirror node', async () => { diff --git a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts index 87a4940941..cb76c5f523 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts @@ -25,7 +25,12 @@ import chaiAsPromised from 'chai-as-promised'; import { predefined } from '../../../src'; import { EthImpl } from '../../../src/lib/eth'; -import { blockLogsBloom, defaultContractResults, defaultDetailedContractResults } from '../../helpers'; +import { + blockLogsBloom, + defaultContractResults, + defaultDetailedContractResults, + overrideEnvsInMochaDescribe, +} from '../../helpers'; import { SDKClient } from '../../../src/lib/clients'; import RelayAssertions from '../../assertions'; import { numberTo0x } from '../../../dist/formatters'; @@ -63,7 +68,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; let ethImplLowTransactionCount: EthImpl; describe('@ethGetBlockByHash using MirrorNode', async function () { @@ -75,9 +79,11 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { const requestDetails = new RequestDetails({ requestId: 'eth_getBlockByHashTest', ipAddress: '0.0.0.0' }); - this.beforeEach(() => { + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); @@ -90,8 +96,6 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { .onGet(contractByEvmAddress(CONTRACT_ADDRESS_2)) .reply(200, { ...DEFAULT_CONTRACT, evmAddress: CONTRACT_ADDRESS_2 }); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; ethImplLowTransactionCount = new EthImpl( hapiServiceInstance, mirrorNodeInstance, @@ -105,7 +109,6 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); it('eth_getBlockByHash with match', async function () { diff --git a/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts index 4b29f5272a..8372d04adf 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts @@ -22,10 +22,16 @@ import dotenv from 'dotenv'; import { expect, use } from 'chai'; import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; +import { Logger } from 'pino'; -import { Eth, predefined } from '../../../src'; +import { predefined } from '../../../src'; import { EthImpl } from '../../../src/lib/eth'; -import { blockLogsBloom, defaultContractResults, defaultDetailedContractResults } from '../../helpers'; +import { + blockLogsBloom, + defaultContractResults, + defaultDetailedContractResults, + overrideEnvsInMochaDescribe, +} from '../../helpers'; import { Block, Transaction } from '../../../src/lib/model'; import { MirrorNodeClient, SDKClient } from '../../../src/lib/clients'; import RelayAssertions from '../../assertions'; @@ -84,7 +90,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; let ethImplLowTransactionCount: EthImpl; describe('@ethGetBlockByNumber using MirrorNode', async function () { @@ -100,10 +105,10 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { }: { restMock: MockAdapter; hapiServiceInstance: HAPIService; - ethImpl: Eth; + ethImpl: EthImpl; cacheService: CacheService; mirrorNodeInstance: MirrorNodeClient; - logger: any; + logger: Logger; registry: Registry; } = generateEthTestEnv(true); const results = defaultContractResults.results; @@ -128,6 +133,8 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { expect(transactions[1].gas).equal(hashNumber(GAS_USED_2)); } + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + this.beforeEach(async () => { // reset cache and restMock await cacheService.clear(requestDetails); @@ -137,8 +144,6 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; ethImplLowTransactionCount = new EthImpl( hapiServiceInstance, mirrorNodeInstance, @@ -162,8 +167,6 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { this.afterEach(() => { getSdkClientStub.restore(); - - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); it('"eth_blockNumber" should return the latest block number', async function () { diff --git a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts index e626de8bbb..bcecf9cdbb 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockTransactionCountByHash.spec.ts @@ -50,9 +50,9 @@ describe('@ethGetBlockTransactionCountByHash using MirrorNode', async function ( ipAddress: '0.0.0.0', }); - this.beforeEach(() => { + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); diff --git a/packages/relay/tests/lib/eth/eth_getCode.spec.ts b/packages/relay/tests/lib/eth/eth_getCode.spec.ts index c5cd107218..cb15b5f168 100644 --- a/packages/relay/tests/lib/eth/eth_getCode.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getCode.spec.ts @@ -38,6 +38,7 @@ import { } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; import { JsonRpcError, predefined } from '../../../src'; +import { overrideEnvsInMochaDescribe } from '../../helpers'; import { RequestDetails } from '../../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -45,7 +46,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethGetCode using MirrorNode', async function () { this.timeout(10000); @@ -55,6 +55,8 @@ describe('@ethGetCode using MirrorNode', async function () { const requestDetails = new RequestDetails({ requestId: 'eth_getCodeTest', ipAddress: '0.0.0.0' }); + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + this.beforeEach(async () => { // reset cache and restMock await cacheService.clear(requestDetails); @@ -63,8 +65,6 @@ describe('@ethGetCode using MirrorNode', async function () { sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(404, null); restMock.onGet(`tokens/0.0.${parseInt(CONTRACT_ADDRESS_1, 16)}`).reply(404, null); @@ -75,7 +75,6 @@ describe('@ethGetCode using MirrorNode', async function () { this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('eth_getCode', async function () { diff --git a/packages/relay/tests/lib/eth/eth_getLogs.spec.ts b/packages/relay/tests/lib/eth/eth_getLogs.spec.ts index 744c44c9d2..90e39e3cf4 100644 --- a/packages/relay/tests/lib/eth/eth_getLogs.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getLogs.spec.ts @@ -34,6 +34,8 @@ import { expectLogData2, expectLogData3, expectLogData4, + overrideEnvsInMochaDescribe, + withOverriddenEnvsInMochaTest, } from '../../helpers'; import { SDKClient } from '../../../src/lib/clients'; import { @@ -69,7 +71,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethGetLogs using MirrorNode', async function () { this.timeout(100000); @@ -94,21 +95,20 @@ describe('@ethGetLogs using MirrorNode', async function () { const requestDetails = new RequestDetails({ requestId: 'eth_getLogsTest', ipAddress: '0.0.0.0' }); - beforeEach(() => { + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + + beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; }); afterEach(() => { getSdkClientStub.restore(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('timeout', async function () { @@ -201,55 +201,54 @@ describe('@ethGetLogs using MirrorNode', async function () { }); }); - it('should be able to return more than two logs with limit of two per request', async function () { - const unfilteredLogs = { - logs: [ - { ...DEFAULT_LOGS.logs[0], address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69' }, - { ...DEFAULT_LOGS.logs[1], address: '0x0000000000000000000000000000000002131952' }, - { ...DEFAULT_LOGS.logs[2], address: '0x0000000000000000000000000000000002131953' }, - { ...DEFAULT_LOGS.logs[3], address: '0x0000000000000000000000000000000002131954' }, - ], - }; - const filteredLogs = { - logs: [ - { ...DEFAULT_LOGS.logs[0], address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69' }, - { ...DEFAULT_LOGS.logs[1], address: '0x0000000000000000000000000000000002131952' }, - ], - links: { next: 'contracts/results/logs?limit=2&order=desc×tamp=lte:1668432962.375200975&index=lt:0' }, - }; - const filteredLogsNext = { - logs: [ - { ...DEFAULT_LOGS.logs[2], address: '0x0000000000000000000000000000000002131953' }, - { ...DEFAULT_LOGS.logs[3], address: '0x0000000000000000000000000000000002131954' }, - ], - links: { next: null }, - }; + withOverriddenEnvsInMochaTest({ MIRROR_NODE_LIMIT_PARAM: '2' }, () => { + it('should be able to return more than two logs with limit of two per request', async function () { + const unfilteredLogs = { + logs: [ + { ...DEFAULT_LOGS.logs[0], address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69' }, + { ...DEFAULT_LOGS.logs[1], address: '0x0000000000000000000000000000000002131952' }, + { ...DEFAULT_LOGS.logs[2], address: '0x0000000000000000000000000000000002131953' }, + { ...DEFAULT_LOGS.logs[3], address: '0x0000000000000000000000000000000002131954' }, + ], + }; + const filteredLogs = { + logs: [ + { ...DEFAULT_LOGS.logs[0], address: '0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69' }, + { ...DEFAULT_LOGS.logs[1], address: '0x0000000000000000000000000000000002131952' }, + ], + links: { next: 'contracts/results/logs?limit=2&order=desc×tamp=lte:1668432962.375200975&index=lt:0' }, + }; + const filteredLogsNext = { + logs: [ + { ...DEFAULT_LOGS.logs[2], address: '0x0000000000000000000000000000000002131953' }, + { ...DEFAULT_LOGS.logs[3], address: '0x0000000000000000000000000000000002131954' }, + ], + links: { next: null }, + }; - restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); + restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, DEFAULT_BLOCKS_RES); - restMock - .onGet( - `contracts/results/logs?timestamp=gte:${DEFAULT_BLOCK.timestamp.from}×tamp=lte:${DEFAULT_BLOCK.timestamp.to}&limit=2&order=asc`, - ) - .replyOnce(200, filteredLogs) - .onGet('contracts/results/logs?limit=2&order=desc×tamp=lte:1668432962.375200975&index=lt:0') - .replyOnce(200, filteredLogsNext); + restMock + .onGet( + `contracts/results/logs?timestamp=gte:${DEFAULT_BLOCK.timestamp.from}×tamp=lte:${DEFAULT_BLOCK.timestamp.to}&limit=2&order=asc`, + ) + .replyOnce(200, filteredLogs) + .onGet('contracts/results/logs?limit=2&order=desc×tamp=lte:1668432962.375200975&index=lt:0') + .replyOnce(200, filteredLogsNext); - unfilteredLogs.logs.forEach((log, index) => { - restMock.onGet(`contracts/${log.address}`).reply(200, { ...DEFAULT_CONTRACT, contract_id: `0.0.105${index}` }); - }); - //setting mirror node limit to 2 for this test only - process.env['MIRROR_NODE_LIMIT_PARAM'] = '2'; - const result = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); - //resetting mirror node limit to 100 - process.env['MIRROR_NODE_LIMIT_PARAM'] = '100'; - expect(result).to.exist; + unfilteredLogs.logs.forEach((log, index) => { + restMock.onGet(`contracts/${log.address}`).reply(200, { ...DEFAULT_CONTRACT, contract_id: `0.0.105${index}` }); + }); - expect(result.length).to.eq(4); - expectLogData(result[0], filteredLogs.logs[0], defaultDetailedContractResults); - expectLogData(result[1], filteredLogs.logs[1], defaultDetailedContractResults); - expectLogData(result[2], filteredLogsNext.logs[0], defaultDetailedContractResults2); - expectLogData(result[3], filteredLogsNext.logs[1], defaultDetailedContractResults3); + const result = await ethImpl.getLogs(null, 'latest', 'latest', null, null, requestDetails); + expect(result).to.exist; + + expect(result.length).to.eq(4); + expectLogData(result[0], filteredLogs.logs[0], defaultDetailedContractResults); + expectLogData(result[1], filteredLogs.logs[1], defaultDetailedContractResults); + expectLogData(result[2], filteredLogsNext.logs[0], defaultDetailedContractResults2); + expectLogData(result[3], filteredLogsNext.logs[1], defaultDetailedContractResults3); + }); }); it('Should return evm address if contract has one', async function () { diff --git a/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts b/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts index d06dd51844..1f69221c55 100644 --- a/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts @@ -42,7 +42,7 @@ import { } from './eth-config'; import { Eth, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; -import { defaultDetailedContractResults } from '../../helpers'; +import { defaultDetailedContractResults, overrideEnvsInMochaDescribe } from '../../helpers'; import { numberTo0x } from '../../../src/formatters'; import { generateEthTestEnv } from './eth-helpers'; import { RequestDetails } from '../../../src/lib/types'; @@ -55,7 +55,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethGetStorageAt eth_getStorageAt spec', async function () { this.timeout(10000); @@ -73,16 +72,16 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { expect(result).equal(DEFAULT_CURRENT_CONTRACT_STATE.state[0].value); } - this.beforeEach(() => { + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, DEFAULT_BLOCK); restMock.onGet(`blocks/${BLOCK_HASH}`).reply(200, DEFAULT_BLOCK); restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOST_RECENT_BLOCK); @@ -91,7 +90,6 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('eth_getStorageAt', async function () { @@ -241,6 +239,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () { CONTRACT_ADDRESS_1, defaultDetailedContractResults.state_changes[0].slot, requestDetails, + null, ); confirmResult(result); diff --git a/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts index 82c274e719..26483cf71b 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionByBlockNumberAndIndex.spec.ts @@ -81,9 +81,9 @@ describe('@ethGetTransactionByBlockNumberAndIndex using MirrorNode', async funct ipAddress: '0.0.0.0', }); - this.beforeEach(() => { + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); diff --git a/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts index 8beeda137a..4fd4cf5ab0 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionCount.spec.ts @@ -29,7 +29,12 @@ import { SDKClient } from '../../../src/lib/clients'; import { DEFAULT_NETWORK_FEES, NO_TRANSACTIONS } from './eth-config'; import { Eth, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; -import { defaultDetailedContractResults, defaultEthereumTransactions, mockData } from '../../helpers'; +import { + defaultDetailedContractResults, + defaultEthereumTransactions, + mockData, + overrideEnvsInMochaDescribe, +} from '../../helpers'; import { numberTo0x } from '../../../src/formatters'; import { generateEthTestEnv } from './eth-helpers'; import { RequestDetails } from '../../../src/lib/types'; @@ -42,7 +47,6 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethGetTransactionCount eth_getTransactionCount spec', async function () { this.timeout(10000); @@ -72,6 +76,8 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function return `accounts/${address}?transactiontype=ETHEREUMTRANSACTION×tamp=lte:${mockData.blocks.blocks[2].timestamp.to}&limit=${num}&order=desc`; } + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + this.beforeEach(() => { restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); restMock.onGet(blockPath).reply(200, mockData.blocks.blocks[2]); @@ -87,16 +93,13 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function restMock .onGet(transactionPath(mockData.account.evm_address, 2)) .reply(200, { transactions: [{ transaction_id: transactionId }, {}] }); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; }); - this.afterEach(() => { + this.afterEach(async () => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); }); @@ -108,6 +111,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should return 0x0 nonce for no block consideration with not found acoount', async () => { restMock.onGet(contractPath).reply(404, mockData.notFound); restMock.onGet(accountPath).reply(404, mockData.notFound); + // @ts-ignore const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, null, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(EthImpl.zeroHex); @@ -116,6 +120,7 @@ describe('@ethGetTransactionCount eth_getTransactionCount spec', async function it('should return latest nonce for no block consideration but valid account', async () => { restMock.onGet(contractPath).reply(404, mockData.notFound); restMock.onGet(accountPath).reply(200, mockData.account); + // @ts-ignore const nonce = await ethImpl.getTransactionCount(MOCK_ACCOUNT_ADDR, null, requestDetails); expect(nonce).to.exist; expect(nonce).to.equal(numberTo0x(mockData.account.ethereum_nonce)); diff --git a/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts index 4676593171..44df551711 100644 --- a/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts @@ -140,10 +140,10 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func sandbox.stub(ethImpl, 'getFeeWeibars').resolves(gasPrice); }; - this.afterEach(() => { + this.afterEach(async () => { restMock.resetHandlers(); sandbox.restore(); - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); }); it('returns `null` for non-existent hash', async function () { diff --git a/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts b/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts index 6b84daed42..6ac2c3ed0d 100644 --- a/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts +++ b/packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts @@ -28,7 +28,7 @@ import { SDKClient } from '../../../src/lib/clients'; import { ACCOUNT_ADDRESS_1, DEFAULT_NETWORK_FEES, MAX_GAS_LIMIT_HEX, NO_TRANSACTIONS } from './eth-config'; import { JsonRpcError, predefined } from '../../../src'; import RelayAssertions from '../../assertions'; -import { getRequestId, mockData, signTransaction } from '../../helpers'; +import { getRequestId, mockData, overrideEnvsInMochaDescribe, signTransaction } from '../../helpers'; import { generateEthTestEnv } from './eth-helpers'; import { SDKClientError } from '../../../src/lib/errors/SDKClientError'; import { RequestDetails } from '../../../src/lib/types'; @@ -38,29 +38,27 @@ use(chaiAsPromised); let sdkClientStub: sinon.SinonStubbedInstance; let getSdkClientStub: sinon.SinonStub; -let currentMaxBlockRange: number; describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () { this.timeout(10000); let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); - const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); + const requestDetails = new RequestDetails({ requestId: 'eth_sendRawTransactionTest', ipAddress: '0.0.0.0' }); - this.beforeEach(() => { + overrideEnvsInMochaDescribe({ ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE: '1' }); + + this.beforeEach(async () => { // reset cache and restMock - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); restMock.reset(); sdkClientStub = sinon.createStubInstance(SDKClient); getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); - currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; }); this.afterEach(() => { getSdkClientStub.restore(); restMock.resetHandlers(); - process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = currentMaxBlockRange.toString(); }); describe('eth_sendRawTransaction', async function () { diff --git a/packages/relay/tests/lib/ethGetBlockBy.spec.ts b/packages/relay/tests/lib/ethGetBlockBy.spec.ts index 567191b4ca..e7cbf87037 100644 --- a/packages/relay/tests/lib/ethGetBlockBy.spec.ts +++ b/packages/relay/tests/lib/ethGetBlockBy.spec.ts @@ -34,8 +34,9 @@ import HbarLimit from '../../src/lib/hbarlimiter'; import { Log, Transaction } from '../../src/lib/model'; import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../../packages/relay/src/formatters'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; -import { defaultDetailedContractResults, useInMemoryRedisServer } from '../helpers'; +import { defaultDetailedContractResults, overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../helpers'; import { EventEmitter } from 'events'; +import { RequestDetails } from '../../src/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -117,7 +118,10 @@ describe('eth_getBlockBy', async function () { this.timeout(10000); let ethImpl: EthImpl; + const requestDetails = new RequestDetails({ requestId: 'ethGetBlockByTest', ipAddress: '0.0.0.0' }); + useInMemoryRedisServer(logger, 5031); + overrideEnvsInMochaDescribe({ ETH_FEE_HISTORY_FIXED: 'false' }); this.beforeAll(async () => { cacheService = new CacheService(logger.child({ name: `cache` }), registry); @@ -139,14 +143,12 @@ describe('eth_getBlockBy', async function () { const eventEmitter = new EventEmitter(); hapiServiceInstance = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); - process.env.ETH_FEE_HISTORY_FIXED = 'false'; - // @ts-ignore ethImpl = new EthImpl(hapiServiceInstance, mirrorNodeInstance, logger, '0x12a', registry, cacheService); }); - this.beforeEach(() => { - cacheService.clear(); + this.beforeEach(async () => { + await cacheService.clear(requestDetails); restMock.reset(); }); diff --git a/packages/relay/tests/lib/formatters.spec.ts b/packages/relay/tests/lib/formatters.spec.ts index cd0c81d413..5aed252afe 100644 --- a/packages/relay/tests/lib/formatters.spec.ts +++ b/packages/relay/tests/lib/formatters.spec.ts @@ -48,6 +48,7 @@ import { import constants from '../../src/lib/constants'; import { BigNumber as BN } from 'bignumber.js'; import { AbiCoder, keccak256 } from 'ethers'; +import { overrideEnvsInMochaDescribe } from '../helpers'; describe('Formatters', () => { describe('formatRequestIdMessage', () => { @@ -140,16 +141,10 @@ describe('Formatters', () => { }); describe('parseNumericEnvVar', () => { - before(() => { - process.env.TEST_ONLY_ENV_VAR_EMPTYSTRING = ''; - process.env.TEST_ONLY_ENV_VAR_NONNUMERICSTRING = 'foobar'; - process.env.TEST_ONLY_ENV_VAR_NUMERICSTRING = '12345'; - }); - - after(() => { - process.env.TEST_ONLY_ENV_VAR_EMPTYSTRING = undefined; - process.env.TEST_ONLY_ENV_VAR_NONNUMERICSTRING = undefined; - process.env.TEST_ONLY_ENV_VAR_NUMERICSTRING = undefined; + overrideEnvsInMochaDescribe({ + TEST_ONLY_ENV_VAR_EMPTYSTRING: '', + TEST_ONLY_ENV_VAR_NONNUMERICSTRING: 'foobar', + TEST_ONLY_ENV_VAR_NUMERICSTRING: '12345', }); it('should use default value when env var is undefined', () => { diff --git a/packages/relay/tests/lib/hapiService.spec.ts b/packages/relay/tests/lib/hapiService.spec.ts index 856d26d04e..303691eec8 100644 --- a/packages/relay/tests/lib/hapiService.spec.ts +++ b/packages/relay/tests/lib/hapiService.spec.ts @@ -30,6 +30,7 @@ import HbarLimit from '../../src/lib/hbarlimiter'; import HAPIService from '../../src/lib/services/hapiService/hapiService'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { RequestDetails } from '../../src/lib/types'; +import { overrideEnvsInMochaDescribe, withOverriddenEnvsInMochaTest } from '../helpers'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -54,10 +55,10 @@ describe('HAPI Service', async function () { hbarLimiter = new HbarLimit(logger.child({ name: 'hbar-rate-limit' }), Date.now(), total, duration, registry); }); - this.beforeEach(() => { - process.env.HAPI_CLIENT_TRANSACTION_RESET = '0'; - process.env.HAPI_CLIENT_DURATION_RESET = '0'; - process.env.HAPI_CLIENT_ERROR_RESET = '[50]'; + overrideEnvsInMochaDescribe({ + HAPI_CLIENT_TRANSACTION_RESET: '0', + HAPI_CLIENT_DURATION_RESET: '0', + HAPI_CLIENT_ERROR_RESET: '[50]', }); it('should be able to initialize SDK instance', async function () { @@ -69,113 +70,133 @@ describe('HAPI Service', async function () { expect(sdkClient).to.be.instanceof(SDKClient); }); - it('should be able to reinitialise SDK instance upon reaching transaction limit', async function () { - process.env.HAPI_CLIENT_TRANSACTION_RESET = '2'; - hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); - expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!)); - - const oldClientInstance = hapiService.getMainClientInstance(); - let oldSDKInstance = hapiService.getSDKClient(); // decrease transaction limit by taking the instance - oldSDKInstance = hapiService.getSDKClient(); // decrease transaction limit by taking the instance - expect(hapiService.getTransactionCount()).to.eq(0); - const newSDKInstance = hapiService.getSDKClient(); - const newClientInstance = hapiService.getMainClientInstance(); - - expect(oldSDKInstance).to.not.be.equal(newSDKInstance); - expect(oldClientInstance).to.not.be.equal(newClientInstance); - expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!) - 1); // one less because we took the instance once and decreased the counter - }); - - it('should be able to reinitialise SDK instance upon reaching time limit', async function () { - process.env.HAPI_CLIENT_DURATION_RESET = '100'; - hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); - expect(hapiService.getTimeUntilReset()).to.eq(parseInt(process.env.HAPI_CLIENT_DURATION_RESET!)); - - const oldClientInstance = hapiService.getMainClientInstance(); - await new Promise((r) => setTimeout(r, 200)); // await to reach time limit - const oldSDKInstance = hapiService.getSDKClient(); - const newSDKInstance = hapiService.getSDKClient(); - const newClientInstance = hapiService.getMainClientInstance(); - - expect(hapiService.getTimeUntilReset()).to.eq(parseInt(process.env.HAPI_CLIENT_DURATION_RESET!)); - expect(oldSDKInstance).to.not.be.equal(newSDKInstance); - expect(oldClientInstance).to.not.be.equal(newClientInstance); + withOverriddenEnvsInMochaTest({ HAPI_CLIENT_TRANSACTION_RESET: '2' }, () => { + it('should be able to reinitialise SDK instance upon reaching transaction limit', async function () { + hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); + expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!)); + + const oldClientInstance = hapiService.getMainClientInstance(); + let oldSDKInstance = hapiService.getSDKClient(); // decrease transaction limit by taking the instance + oldSDKInstance = hapiService.getSDKClient(); // decrease transaction limit by taking the instance + expect(hapiService.getTransactionCount()).to.eq(0); + const newSDKInstance = hapiService.getSDKClient(); + const newClientInstance = hapiService.getMainClientInstance(); + + expect(oldSDKInstance).to.not.be.equal(newSDKInstance); + expect(oldClientInstance).to.not.be.equal(newClientInstance); + expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!) - 1); // one less because we took the instance once and decreased the counter + }); }); - it('should be able to reinitialise SDK instance upon error status code encounter', async function () { - process.env.HAPI_CLIENT_ERROR_RESET = '[50]'; - hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); - expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); - - const oldClientInstance = hapiService.getMainClientInstance(); - const oldSDKInstance = hapiService.getSDKClient(); - hapiService.decrementErrorCounter(errorStatus); - const newSDKInstance = hapiService.getSDKClient(); - const newClientInstance = hapiService.getMainClientInstance(); - - expect(oldSDKInstance).to.not.be.equal(newSDKInstance); - expect(oldClientInstance).to.not.be.equal(newClientInstance); - expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); + withOverriddenEnvsInMochaTest({ HAPI_CLIENT_DURATION_RESET: '100' }, () => { + it('should be able to reinitialise SDK instance upon reaching time limit', async function () { + hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); + expect(hapiService.getTimeUntilReset()).to.eq(parseInt(process.env.HAPI_CLIENT_DURATION_RESET!)); + + const oldClientInstance = hapiService.getMainClientInstance(); + await new Promise((r) => setTimeout(r, 200)); // await to reach time limit + const oldSDKInstance = hapiService.getSDKClient(); + const newSDKInstance = hapiService.getSDKClient(); + const newClientInstance = hapiService.getMainClientInstance(); + + expect(hapiService.getTimeUntilReset()).to.eq(parseInt(process.env.HAPI_CLIENT_DURATION_RESET!)); + expect(oldSDKInstance).to.not.be.equal(newSDKInstance); + expect(oldClientInstance).to.not.be.equal(newClientInstance); + }); }); - it('should be able to reset all counter upon reinitialization of the SDK Client', async function () { - process.env.HAPI_CLIENT_ERROR_RESET = '[50]'; - process.env.HAPI_CLIENT_TRANSACTION_RESET = '50'; - process.env.HAPI_CLIENT_DURATION_RESET = '36000'; - hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); - - expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); - const oldClientInstance = hapiService.getMainClientInstance(); - const oldSDKInstance = hapiService.getSDKClient(); - hapiService.decrementErrorCounter(errorStatus); - const newSDKInstance = hapiService.getSDKClient(); - const newClientInstance = hapiService.getMainClientInstance(); - - expect(hapiService.getTimeUntilReset()).to.eq(parseInt(process.env.HAPI_CLIENT_DURATION_RESET!)); - expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); - expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!) - 1); // one less because we took the instance once and decreased the counter - expect(oldSDKInstance).to.not.be.equal(newSDKInstance); - expect(oldClientInstance).to.not.be.equal(newClientInstance); + withOverriddenEnvsInMochaTest({ HAPI_CLIENT_ERROR_RESET: '[50]' }, () => { + it('should be able to reinitialise SDK instance upon error status code encounter', async function () { + hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); + expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); + + const oldClientInstance = hapiService.getMainClientInstance(); + const oldSDKInstance = hapiService.getSDKClient(); + hapiService.decrementErrorCounter(errorStatus); + const newSDKInstance = hapiService.getSDKClient(); + const newClientInstance = hapiService.getMainClientInstance(); + + expect(oldSDKInstance).to.not.be.equal(newSDKInstance); + expect(oldClientInstance).to.not.be.equal(newClientInstance); + expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); + }); }); - it('should keep the same instance of hbar limiter and not reset the budget', async function () { - process.env.HAPI_CLIENT_ERROR_RESET = '[50]'; - process.env.HAPI_CLIENT_TRANSACTION_RESET = '50'; - process.env.HAPI_CLIENT_DURATION_RESET = '36000'; - const costAmount = 10000; - hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); - - const hbarLimiterBudgetBefore = hbarLimiter.getRemainingBudget(); - const oldClientInstance = hapiService.getMainClientInstance(); - const oldSDKInstance = hapiService.getSDKClient(); - - hbarLimiter.addExpense(costAmount, Date.now(), requestDetails); - hapiService.decrementErrorCounter(errorStatus); - - const newSDKInstance = hapiService.getSDKClient(); - const newClientInstance = hapiService.getMainClientInstance(); - const hbarLimiterBudgetAfter = hbarLimiter.getRemainingBudget(); - - expect(hbarLimiterBudgetBefore).to.be.greaterThan(hbarLimiterBudgetAfter); - expect(oldSDKInstance).to.not.be.equal(newSDKInstance); - expect(oldClientInstance).to.not.be.equal(newClientInstance); - }); - - it('should not be able to reinitialise and decrement counters, if it is disabled', async function () { - process.env.HAPI_CLIENT_TRANSACTION_RESET = '0'; - process.env.HAPI_CLIENT_DURATION_RESET = '0'; - process.env.HAPI_CLIENT_ERROR_RESET = '[]'; - - hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); - expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!)); - - const oldClientInstance = hapiService.getMainClientInstance(); - const oldSDKInstance = hapiService.getSDKClient(); - const newSDKInstance = hapiService.getSDKClient(); - const newClientInstance = hapiService.getMainClientInstance(); - - expect(oldSDKInstance).to.be.equal(newSDKInstance); - expect(oldClientInstance).to.be.equal(newClientInstance); - expect(hapiService.getIsReinitEnabled()).to.be.equal(false); - }); + withOverriddenEnvsInMochaTest( + { + HAPI_CLIENT_ERROR_RESET: '[50]', + HAPI_CLIENT_TRANSACTION_RESET: '50', + HAPI_CLIENT_DURATION_RESET: '36000', + }, + () => { + it('should be able to reset all counter upon reinitialization of the SDK Client', async function () { + hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); + + expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); + const oldClientInstance = hapiService.getMainClientInstance(); + const oldSDKInstance = hapiService.getSDKClient(); + hapiService.decrementErrorCounter(errorStatus); + const newSDKInstance = hapiService.getSDKClient(); + const newClientInstance = hapiService.getMainClientInstance(); + + expect(hapiService.getTimeUntilReset()).to.eq(parseInt(process.env.HAPI_CLIENT_DURATION_RESET!)); + expect(hapiService.getErrorCodes()[0]).to.eq(JSON.parse(process.env.HAPI_CLIENT_ERROR_RESET!)[0]); + expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!) - 1); // one less because we took the instance once and decreased the counter + expect(oldSDKInstance).to.not.be.equal(newSDKInstance); + expect(oldClientInstance).to.not.be.equal(newClientInstance); + }); + }, + ); + + withOverriddenEnvsInMochaTest( + { + HAPI_CLIENT_ERROR_RESET: '[50]', + HAPI_CLIENT_TRANSACTION_RESET: '50', + HAPI_CLIENT_DURATION_RESET: '36000', + }, + () => { + it('should keep the same instance of hbar limiter and not reset the budget', async function () { + const costAmount = 10000; + hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); + + const hbarLimiterBudgetBefore = hbarLimiter.getRemainingBudget(); + const oldClientInstance = hapiService.getMainClientInstance(); + const oldSDKInstance = hapiService.getSDKClient(); + + hbarLimiter.addExpense(costAmount, Date.now(), requestDetails); + hapiService.decrementErrorCounter(errorStatus); + + const newSDKInstance = hapiService.getSDKClient(); + const newClientInstance = hapiService.getMainClientInstance(); + const hbarLimiterBudgetAfter = hbarLimiter.getRemainingBudget(); + + expect(hbarLimiterBudgetBefore).to.be.greaterThan(hbarLimiterBudgetAfter); + expect(oldSDKInstance).to.not.be.equal(newSDKInstance); + expect(oldClientInstance).to.not.be.equal(newClientInstance); + }); + }, + ); + + withOverriddenEnvsInMochaTest( + { + HAPI_CLIENT_TRANSACTION_RESET: '0', + HAPI_CLIENT_DURATION_RESET: '0', + HAPI_CLIENT_ERROR_RESET: '[]', + }, + () => { + it('should not be able to reinitialise and decrement counters, if it is disabled', async function () { + hapiService = new HAPIService(logger, registry, hbarLimiter, cacheService, eventEmitter); + expect(hapiService.getTransactionCount()).to.eq(parseInt(process.env.HAPI_CLIENT_TRANSACTION_RESET!)); + + const oldClientInstance = hapiService.getMainClientInstance(); + const oldSDKInstance = hapiService.getSDKClient(); + const newSDKInstance = hapiService.getSDKClient(); + const newClientInstance = hapiService.getMainClientInstance(); + + expect(oldSDKInstance).to.be.equal(newSDKInstance); + expect(oldClientInstance).to.be.equal(newClientInstance); + expect(hapiService.getIsReinitEnabled()).to.be.equal(false); + }); + }, + ); }); diff --git a/packages/relay/tests/lib/hbarLimiter.spec.ts b/packages/relay/tests/lib/hbarLimiter.spec.ts index 2d1e45f35c..c608697501 100644 --- a/packages/relay/tests/lib/hbarLimiter.spec.ts +++ b/packages/relay/tests/lib/hbarLimiter.spec.ts @@ -22,7 +22,7 @@ import pino from 'pino'; import { expect } from 'chai'; import { Registry } from 'prom-client'; import HbarLimit from '../../src/lib/hbarlimiter'; -import { estimateFileTransactionsFee, getRequestId, random20BytesAddress } from '../helpers'; +import { estimateFileTransactionsFee, random20BytesAddress, withOverriddenEnvsInMochaTest } from '../helpers'; import { RequestDetails } from '../../src/lib/types'; const registry = new Registry(); @@ -43,283 +43,276 @@ describe('HBAR Rate Limiter', async function () { const randomAccountAddress = random20BytesAddress(); const randomWhiteListedAccountAddress = random20BytesAddress(); const fileChunkSize = Number(process.env.FILE_APPEND_CHUNK_SIZE) || 5120; - const requestDetails = new RequestDetails({ requestId: getRequestId(), ipAddress: '0.0.0.0' }); + const requestDetails = new RequestDetails({ requestId: 'hbarRateLimiterTest', ipAddress: '0.0.0.0' }); this.beforeEach(() => { currentDateNow = Date.now(); - process.env.HBAR_RATE_LIMIT_WHITELIST = `[${randomWhiteListedAccountAddress}]`; rateLimiter = new HbarLimit(logger, currentDateNow, validTotal, validDuration, registry); rateLimiterWithEmptyBudget = new HbarLimit(logger, currentDateNow, invalidTotal, validDuration, registry); }); - this.beforeAll(() => { - process.env.HBAR_RATE_LIMIT_WHITELIST = `[${randomWhiteListedAccountAddress}]`; - }); - - this.afterAll(() => { - delete process.env.HBAR_RATE_LIMIT_WHITELIST; - }); - - it('should be disabled, if we pass invalid total', async function () { - const isEnabled = rateLimiterWithEmptyBudget.isEnabled(); - const limiterResetTime = rateLimiterWithEmptyBudget.getResetTime(); - const limiterRemainingBudget = rateLimiterWithEmptyBudget.getRemainingBudget(); - const shouldRateLimit = rateLimiterWithEmptyBudget.shouldLimit( - currentDateNow, - 'QUERY', - 'eth_call', - randomAccountAddress, - requestDetails, - ); - rateLimiterWithEmptyBudget.addExpense(validTotal, currentDateNow, requestDetails); - - expect(isEnabled).to.equal(false); - expect(shouldRateLimit).to.equal(false); - expect(limiterResetTime).to.equal(currentDateNow); - expect(limiterRemainingBudget).to.equal(0); - }); - - it('should be disabled, if we pass invalid duration', async function () { - rateLimiter = new HbarLimit(logger, currentDateNow, validTotal, invalidDuration, registry); - - const isEnabled = rateLimiter.isEnabled(); - const limiterResetTime = rateLimiter.getResetTime(); - const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - const shouldRateLimit = rateLimiter.shouldLimit( - currentDateNow, - 'QUERY', - 'eth_call', - randomAccountAddress, - requestDetails, - ); - rateLimiter.addExpense(validTotal, currentDateNow, requestDetails); - - expect(isEnabled).to.equal(false); - expect(shouldRateLimit).to.equal(false); - expect(limiterResetTime).to.equal(currentDateNow); - expect(limiterRemainingBudget).to.equal(0); - }); - - it('should be disabled, if we pass both invalid duration and total', async function () { - const invalidRateLimiter = new HbarLimit(logger, currentDateNow, invalidTotal, invalidDuration, registry); - - const isEnabled = invalidRateLimiter.isEnabled(); - const limiterResetTime = invalidRateLimiter.getResetTime(); - const limiterRemainingBudget = invalidRateLimiter.getRemainingBudget(); - const shouldRateLimit = invalidRateLimiter.shouldLimit( - currentDateNow, - 'QUERY', - 'eth_call', - randomAccountAddress, - requestDetails, - ); - invalidRateLimiter.addExpense(validTotal, currentDateNow, requestDetails); - - expect(isEnabled).to.equal(false); - expect(shouldRateLimit).to.equal(false); - expect(limiterResetTime).to.equal(currentDateNow); - expect(limiterRemainingBudget).to.equal(0); - }); - - it('should be enabled, if we pass valid duration and total', async function () { - const isEnabled = rateLimiter.isEnabled(); - const limiterResetTime = rateLimiter.getResetTime(); - const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - const shouldRateLimit = rateLimiter.shouldLimit( - currentDateNow, - 'QUERY', - 'eth_call', - randomAccountAddress, - requestDetails, - ); - - expect(isEnabled).to.equal(true); - expect(shouldRateLimit).to.equal(false); - expect(limiterResetTime).to.equal(currentDateNow + validDuration); - expect(limiterRemainingBudget).to.equal(validTotal); - }); - - it('should not rate limit', async function () { - const cost = 10000000; - - rateLimiter.addExpense(cost, currentDateNow, requestDetails); - - const isEnabled = rateLimiter.isEnabled(); - const limiterResetTime = rateLimiter.getResetTime(); - const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - const shouldRateLimit = rateLimiter.shouldLimit( - currentDateNow, - 'TRANSACTION', - 'eth_sendRawTransaction', - randomAccountAddress, - requestDetails, - ); - - expect(isEnabled).to.equal(true); - expect(shouldRateLimit).to.equal(false); - expect(limiterResetTime).to.equal(currentDateNow + validDuration); - expect(limiterRemainingBudget).to.equal(validTotal - cost); - }); - - it('should rate limit', async function () { - const cost = 1000000000; - - rateLimiter.addExpense(cost, currentDateNow, requestDetails); - - const isEnabled = rateLimiter.isEnabled(); - const limiterResetTime = rateLimiter.getResetTime(); - const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - const shouldRateLimit = rateLimiter.shouldLimit( - currentDateNow, - 'TRANSACTION', - 'eth_sendRawTransaction', - randomAccountAddress, - requestDetails, - ); - - expect(isEnabled).to.equal(true); - expect(shouldRateLimit).to.equal(true); - expect(limiterResetTime).to.equal(currentDateNow + validDuration); - expect(limiterRemainingBudget).to.equal(validTotal - cost); - }); - - it('should reset budget, while checking if we should rate limit', async function () { - const cost = 1000000000; - - rateLimiter.addExpense(cost, currentDateNow, requestDetails); - - const isEnabled = rateLimiter.isEnabled(); - const futureDate = currentDateNow + validDuration * 2; - const shouldRateLimit = rateLimiter.shouldLimit( - futureDate, - 'TRANSACTION', - 'eth_sendRawTransaction', - randomAccountAddress, - requestDetails, - ); - const limiterResetTime = rateLimiter.getResetTime(); - const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - - expect(isEnabled).to.equal(true); - expect(shouldRateLimit).to.equal(false); - expect(limiterResetTime).to.equal(futureDate + validDuration); - expect(limiterRemainingBudget).to.equal(validTotal); - }); - - it('should reset budget, while adding expense', async function () { - const cost = 1000000000; - - rateLimiter.addExpense(cost, currentDateNow, requestDetails); - const shouldRateLimitBefore = rateLimiter.shouldLimit( - currentDateNow, - 'TRANSACTION', - 'eth_sendRawTransaction', - randomAccountAddress, - requestDetails, - ); - - const futureDate = currentDateNow + validDuration * 2; - rateLimiter.addExpense(100, futureDate, requestDetails); - const shouldRateLimitAfter = rateLimiter.shouldLimit( - futureDate, - 'TRANSACTION', - 'eth_sendRawTransaction', - randomAccountAddress, - requestDetails, - ); - - const isEnabled = rateLimiter.isEnabled(); - const limiterResetTime = rateLimiter.getResetTime(); - const limiterRemainingBudget = rateLimiter.getRemainingBudget(); - - expect(isEnabled).to.equal(true); - expect(shouldRateLimitBefore).to.equal(true); - expect(shouldRateLimitAfter).to.equal(false); - expect(limiterResetTime).to.equal(futureDate + validDuration); - expect(limiterRemainingBudget).to.equal(validTotal - 100); - }); - - it('Should execute shouldPreemptivelyLimitFileTransactions() and return TRUE if expected transactionFee is greater than remaining balance', () => { - const result = rateLimiterWithEmptyBudget.shouldPreemptivelyLimitFileTransactions( - randomAccountAddress, - callDataSize, - fileChunkSize, - mockedExchangeRateInCents, - requestDetails, - ); - expect(result).to.be.true; - }); - - it('Shouldexecute shouldPreemptivelyLimitFileTransactions() and return FALSE if expected transactionFee is less than remaining balance', () => { - const result = rateLimiter.shouldPreemptivelyLimitFileTransactions( - randomAccountAddress, - callDataSize, - fileChunkSize, - mockedExchangeRateInCents, - requestDetails, - ); - expect(result).to.be.false; - }); - - it('Should verify if an account is whitelisted', () => { - const shoulNotdBeWhiteListed = rateLimiterWithEmptyBudget.isAccountWhiteListed(randomAccountAddress); - const shouldBeWhiteListed = rateLimiterWithEmptyBudget.isAccountWhiteListed(randomWhiteListedAccountAddress); - - expect(shoulNotdBeWhiteListed).to.be.false; - expect(shouldBeWhiteListed).to.be.true; - }); - - it('should bypass rate limit if original caller is a white listed account', async function () { - // add expense to rate limit throttle - rateLimiter.addExpense(validTotal, currentDateNow, requestDetails); - - // should return true as `randomAccountAddress` is not white listed - const shouldNOTByPassRateLimit = rateLimiter.shouldLimit( - currentDateNow, - 'TRANSACTION', - 'eth_sendRawTransaction', - randomAccountAddress, - requestDetails, - ); - - // should return false as `randomWhiteListedAccountAddress` is white listed - const shouldByPassRateLimit = rateLimiter.shouldLimit( - currentDateNow, - 'TRANSACTION', - 'eth_sendRawTransaction', - randomWhiteListedAccountAddress, - requestDetails, - ); - - expect(shouldByPassRateLimit).to.equal(false); - expect(shouldNOTByPassRateLimit).to.equal(true); - }); - - it('Should execute shouldPreemptivelyLimitFileTransactions() and return FALSE if the original caller is a white listed account', () => { - const result = rateLimiterWithEmptyBudget.shouldPreemptivelyLimitFileTransactions( - randomWhiteListedAccountAddress, - callDataSize, - fileChunkSize, - mockedExchangeRateInCents, - requestDetails, - ); - expect(result).to.be.false; - }); - - it('Should execute shouldPreemptivelyLimitFileTransactions() and return TRUE if the original caller is NOT a white listed account', () => { - const result = rateLimiterWithEmptyBudget.shouldPreemptivelyLimitFileTransactions( - randomAccountAddress, - callDataSize, - fileChunkSize, - mockedExchangeRateInCents, - requestDetails, - ); - expect(result).to.be.true; - }); - - it('Should execute estimateFileTransactionFee() to estimate total fee of file transactions', async () => { - const result = rateLimiter.estimateFileTransactionsFee(callDataSize, fileChunkSize, mockedExchangeRateInCents); - const expectedResult = estimateFileTransactionsFee(callDataSize, fileChunkSize, mockedExchangeRateInCents); - expect(result).to.eq(expectedResult); + withOverriddenEnvsInMochaTest({ HBAR_RATE_LIMIT_WHITELIST: `[${randomWhiteListedAccountAddress}]` }, () => { + it('should be disabled, if we pass invalid total', async function () { + const isEnabled = rateLimiterWithEmptyBudget.isEnabled(); + const limiterResetTime = rateLimiterWithEmptyBudget.getResetTime(); + const limiterRemainingBudget = rateLimiterWithEmptyBudget.getRemainingBudget(); + const shouldRateLimit = rateLimiterWithEmptyBudget.shouldLimit( + currentDateNow, + 'QUERY', + 'eth_call', + randomAccountAddress, + requestDetails, + ); + rateLimiterWithEmptyBudget.addExpense(validTotal, currentDateNow, requestDetails); + + expect(isEnabled).to.equal(false); + expect(shouldRateLimit).to.equal(false); + expect(limiterResetTime).to.equal(currentDateNow); + expect(limiterRemainingBudget).to.equal(0); + }); + + it('should be disabled, if we pass invalid duration', async function () { + rateLimiter = new HbarLimit(logger, currentDateNow, validTotal, invalidDuration, registry); + + const isEnabled = rateLimiter.isEnabled(); + const limiterResetTime = rateLimiter.getResetTime(); + const limiterRemainingBudget = rateLimiter.getRemainingBudget(); + const shouldRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'QUERY', + 'eth_call', + randomAccountAddress, + requestDetails, + ); + rateLimiter.addExpense(validTotal, currentDateNow, requestDetails); + + expect(isEnabled).to.equal(false); + expect(shouldRateLimit).to.equal(false); + expect(limiterResetTime).to.equal(currentDateNow); + expect(limiterRemainingBudget).to.equal(0); + }); + + it('should be disabled, if we pass both invalid duration and total', async function () { + const invalidRateLimiter = new HbarLimit(logger, currentDateNow, invalidTotal, invalidDuration, registry); + + const isEnabled = invalidRateLimiter.isEnabled(); + const limiterResetTime = invalidRateLimiter.getResetTime(); + const limiterRemainingBudget = invalidRateLimiter.getRemainingBudget(); + const shouldRateLimit = invalidRateLimiter.shouldLimit( + currentDateNow, + 'QUERY', + 'eth_call', + randomAccountAddress, + requestDetails, + ); + invalidRateLimiter.addExpense(validTotal, currentDateNow, requestDetails); + + expect(isEnabled).to.equal(false); + expect(shouldRateLimit).to.equal(false); + expect(limiterResetTime).to.equal(currentDateNow); + expect(limiterRemainingBudget).to.equal(0); + }); + + it('should be enabled, if we pass valid duration and total', async function () { + const isEnabled = rateLimiter.isEnabled(); + const limiterResetTime = rateLimiter.getResetTime(); + const limiterRemainingBudget = rateLimiter.getRemainingBudget(); + const shouldRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'QUERY', + 'eth_call', + randomAccountAddress, + requestDetails, + ); + + expect(isEnabled).to.equal(true); + expect(shouldRateLimit).to.equal(false); + expect(limiterResetTime).to.equal(currentDateNow + validDuration); + expect(limiterRemainingBudget).to.equal(validTotal); + }); + + it('should not rate limit', async function () { + const cost = 10000000; + + rateLimiter.addExpense(cost, currentDateNow, requestDetails); + + const isEnabled = rateLimiter.isEnabled(); + const limiterResetTime = rateLimiter.getResetTime(); + const limiterRemainingBudget = rateLimiter.getRemainingBudget(); + const shouldRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'TRANSACTION', + 'eth_sendRawTransaction', + randomAccountAddress, + requestDetails, + ); + + expect(isEnabled).to.equal(true); + expect(shouldRateLimit).to.equal(false); + expect(limiterResetTime).to.equal(currentDateNow + validDuration); + expect(limiterRemainingBudget).to.equal(validTotal - cost); + }); + + it('should rate limit', async function () { + const cost = 1000000000; + + rateLimiter.addExpense(cost, currentDateNow, requestDetails); + + const isEnabled = rateLimiter.isEnabled(); + const limiterResetTime = rateLimiter.getResetTime(); + const limiterRemainingBudget = rateLimiter.getRemainingBudget(); + const shouldRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'TRANSACTION', + 'eth_sendRawTransaction', + randomAccountAddress, + requestDetails, + ); + + expect(isEnabled).to.equal(true); + expect(shouldRateLimit).to.equal(true); + expect(limiterResetTime).to.equal(currentDateNow + validDuration); + expect(limiterRemainingBudget).to.equal(validTotal - cost); + }); + + it('should reset budget, while checking if we should rate limit', async function () { + const cost = 1000000000; + + rateLimiter.addExpense(cost, currentDateNow, requestDetails); + + const isEnabled = rateLimiter.isEnabled(); + const futureDate = currentDateNow + validDuration * 2; + const shouldRateLimit = rateLimiter.shouldLimit( + futureDate, + 'TRANSACTION', + 'eth_sendRawTransaction', + randomAccountAddress, + requestDetails, + ); + const limiterResetTime = rateLimiter.getResetTime(); + const limiterRemainingBudget = rateLimiter.getRemainingBudget(); + + expect(isEnabled).to.equal(true); + expect(shouldRateLimit).to.equal(false); + expect(limiterResetTime).to.equal(futureDate + validDuration); + expect(limiterRemainingBudget).to.equal(validTotal); + }); + + it('should reset budget, while adding expense', async function () { + const cost = 1000000000; + + rateLimiter.addExpense(cost, currentDateNow, requestDetails); + const shouldRateLimitBefore = rateLimiter.shouldLimit( + currentDateNow, + 'TRANSACTION', + 'eth_sendRawTransaction', + randomAccountAddress, + requestDetails, + ); + + const futureDate = currentDateNow + validDuration * 2; + rateLimiter.addExpense(100, futureDate, requestDetails); + const shouldRateLimitAfter = rateLimiter.shouldLimit( + futureDate, + 'TRANSACTION', + 'eth_sendRawTransaction', + randomAccountAddress, + requestDetails, + ); + + const isEnabled = rateLimiter.isEnabled(); + const limiterResetTime = rateLimiter.getResetTime(); + const limiterRemainingBudget = rateLimiter.getRemainingBudget(); + + expect(isEnabled).to.equal(true); + expect(shouldRateLimitBefore).to.equal(true); + expect(shouldRateLimitAfter).to.equal(false); + expect(limiterResetTime).to.equal(futureDate + validDuration); + expect(limiterRemainingBudget).to.equal(validTotal - 100); + }); + + it('Should execute shouldPreemptivelyLimitFileTransactions() and return TRUE if expected transactionFee is greater than remaining balance', () => { + const result = rateLimiterWithEmptyBudget.shouldPreemptivelyLimitFileTransactions( + randomAccountAddress, + callDataSize, + fileChunkSize, + mockedExchangeRateInCents, + requestDetails, + ); + expect(result).to.be.true; + }); + + it('Should execute shouldPreemptivelyLimitFileTransactions() and return FALSE if expected transactionFee is less than remaining balance', () => { + const result = rateLimiter.shouldPreemptivelyLimitFileTransactions( + randomAccountAddress, + callDataSize, + fileChunkSize, + mockedExchangeRateInCents, + requestDetails, + ); + expect(result).to.be.false; + }); + + it('Should verify if an account is whitelisted', () => { + const shoulNotdBeWhiteListed = rateLimiterWithEmptyBudget.isAccountWhiteListed(randomAccountAddress); + const shouldBeWhiteListed = rateLimiterWithEmptyBudget.isAccountWhiteListed(randomWhiteListedAccountAddress); + + expect(shoulNotdBeWhiteListed).to.be.false; + expect(shouldBeWhiteListed).to.be.true; + }); + + it('should bypass rate limit if original caller is a white listed account', async function () { + // add expense to rate limit throttle + rateLimiter.addExpense(validTotal, currentDateNow, requestDetails); + + // should return true as `randomAccountAddress` is not white listed + const shouldNOTByPassRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'TRANSACTION', + 'eth_sendRawTransaction', + randomAccountAddress, + requestDetails, + ); + + // should return false as `randomWhiteListedAccountAddress` is white listed + const shouldByPassRateLimit = rateLimiter.shouldLimit( + currentDateNow, + 'TRANSACTION', + 'eth_sendRawTransaction', + randomWhiteListedAccountAddress, + requestDetails, + ); + + expect(shouldByPassRateLimit).to.equal(false); + expect(shouldNOTByPassRateLimit).to.equal(true); + }); + + it('Should execute shouldPreemptivelyLimitFileTransactions() and return FALSE if the original caller is a white listed account', () => { + const result = rateLimiterWithEmptyBudget.shouldPreemptivelyLimitFileTransactions( + randomWhiteListedAccountAddress, + callDataSize, + fileChunkSize, + mockedExchangeRateInCents, + requestDetails, + ); + expect(result).to.be.false; + }); + + it('Should execute shouldPreemptivelyLimitFileTransactions() and return TRUE if the original caller is NOT a white listed account', () => { + const result = rateLimiterWithEmptyBudget.shouldPreemptivelyLimitFileTransactions( + randomAccountAddress, + callDataSize, + fileChunkSize, + mockedExchangeRateInCents, + requestDetails, + ); + expect(result).to.be.true; + }); + + it('Should execute estimateFileTransactionFee() to estimate total fee of file transactions', async () => { + const result = rateLimiter.estimateFileTransactionsFee(callDataSize, fileChunkSize, mockedExchangeRateInCents); + const expectedResult = estimateFileTransactionsFee(callDataSize, fileChunkSize, mockedExchangeRateInCents); + expect(result).to.eq(expectedResult); + }); }); }); diff --git a/packages/relay/tests/lib/mirrorNodeClient.spec.ts b/packages/relay/tests/lib/mirrorNodeClient.spec.ts index 4ac09984ad..1efe61b1d8 100644 --- a/packages/relay/tests/lib/mirrorNodeClient.spec.ts +++ b/packages/relay/tests/lib/mirrorNodeClient.spec.ts @@ -26,7 +26,7 @@ import { MirrorNodeClient } from '../../src/lib/clients'; import constants from '../../src/lib/constants'; import axios, { AxiosInstance } from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { getRequestId, mockData, random20BytesAddress } from '../helpers'; +import { mockData, random20BytesAddress, withOverriddenEnvsInMochaTest } from '../helpers'; import pino from 'pino'; import { ethers } from 'ethers'; import { MirrorNodeClientError, predefined } from '../../src'; @@ -37,15 +37,14 @@ import { BigNumber } from 'bignumber.js'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); -const registry = new Registry(); - -const logger = pino(); -const noTransactions = '?transactions=false'; -const requestDetails = new RequestDetails({ requestId: getRequestId(), ipAddress: '0.0.0.0' }); - describe('MirrorNodeClient', async function () { this.timeout(20000); + const registry = new Registry(); + const logger = pino(); + const noTransactions = '?transactions=false'; + const requestDetails = new RequestDetails({ requestId: 'mirrorNodeClientTest', ipAddress: '0.0.0.0' }); + let instance: AxiosInstance, mock: MockAdapter, mirrorNodeInstance: MirrorNodeClient, cacheService: CacheService; before(() => { @@ -68,9 +67,9 @@ describe('MirrorNodeClient', async function () { ); }); - beforeEach(() => { + beforeEach(async () => { mock = new MockAdapter(instance); - cacheService.clear(requestDetails); + await cacheService.clear(requestDetails); }); describe('handleError', async () => { @@ -160,18 +159,18 @@ describe('MirrorNodeClient', async function () { expect(extractedEvmAddress).to.eq(evmAddress); }); - it('Can provide custom x-api-key header', async () => { - const exampleApiKey = 'abc123iAManAPIkey'; - process.env.MIRROR_NODE_URL_HEADER_X_API_KEY = exampleApiKey; - const mirrorNodeInstanceOverridden = new MirrorNodeClient( - process.env.MIRROR_NODE_URL || '', - logger.child({ name: `mirror-node` }), - registry, - cacheService, - ); - const axiosHeaders = mirrorNodeInstanceOverridden.getMirrorNodeRestInstance().defaults.headers.common; - expect(axiosHeaders).has.property('x-api-key'); - expect(axiosHeaders['x-api-key']).to.eq(exampleApiKey); + withOverriddenEnvsInMochaTest({ MIRROR_NODE_URL_HEADER_X_API_KEY: 'abc123iAManAPIkey' }, () => { + it('Can provide custom x-api-key header', async () => { + const mirrorNodeInstanceOverridden = new MirrorNodeClient( + process.env.MIRROR_NODE_URL || '', + logger.child({ name: `mirror-node` }), + registry, + cacheService, + ); + const axiosHeaders = mirrorNodeInstanceOverridden.getMirrorNodeRestInstance().defaults.headers.common; + expect(axiosHeaders).has.property('x-api-key'); + expect(axiosHeaders['x-api-key']).to.eq(process.env.MIRROR_NODE_URL_HEADER_X_API_KEY); + }); }); it('`getQueryParams` general', async () => { diff --git a/packages/relay/tests/lib/net.spec.ts b/packages/relay/tests/lib/net.spec.ts index f915ca37c6..6b5f00c7c0 100644 --- a/packages/relay/tests/lib/net.spec.ts +++ b/packages/relay/tests/lib/net.spec.ts @@ -23,6 +23,7 @@ import { expect } from 'chai'; import { Registry } from 'prom-client'; import { RelayImpl } from '../../src/lib/relay'; import constants from '../../src/lib/constants'; +import { withOverriddenEnvsInMochaTest } from '../helpers'; const logger = pino(); let Relay; @@ -46,41 +47,43 @@ describe('Net', async function () { expect(actualNetVersion).to.eq(expectedNetVersion); }); - it('should set chainId from CHAIN_ID environment variable', () => { - process.env.CHAIN_ID = '123'; - Relay = new RelayImpl(logger, new Registry()); - const actualNetVersion = Relay.net().version(); - expect(actualNetVersion).to.equal('123'); + withOverriddenEnvsInMochaTest({ CHAIN_ID: '123' }, () => { + it('should set chainId from CHAIN_ID environment variable', () => { + Relay = new RelayImpl(logger, new Registry()); + const actualNetVersion = Relay.net().version(); + expect(actualNetVersion).to.equal('123'); + }); }); - it('should set chainId from CHAIN_ID environment variable starting with 0x', () => { - process.env.CHAIN_ID = '0x1a'; - Relay = new RelayImpl(logger, new Registry()); - const actualNetVersion = Relay.net().version(); - expect(actualNetVersion).to.equal('26'); // 0x1a in decimal is 26 + withOverriddenEnvsInMochaTest({ CHAIN_ID: '0x1a' }, () => { + it('should set chainId from CHAIN_ID environment variable starting with 0x', () => { + Relay = new RelayImpl(logger, new Registry()); + const actualNetVersion = Relay.net().version(); + expect(actualNetVersion).to.equal('26'); // 0x1a in decimal is 26 + }); }); - it('should default chainId to 298 when no environment variables are set', () => { - delete process.env.HEDERA_NETWORK; - delete process.env.CHAIN_ID; - Relay = new RelayImpl(logger, new Registry()); - const actualNetVersion = Relay.net().version(); - expect(actualNetVersion).to.equal('298'); + withOverriddenEnvsInMochaTest({ HEDERA_NETWORK: undefined, CHAIN_ID: undefined }, () => { + it('should default chainId to 298 when no environment variables are set', () => { + Relay = new RelayImpl(logger, new Registry()); + const actualNetVersion = Relay.net().version(); + expect(actualNetVersion).to.equal('298'); + }); }); - it('should handle empty HEDERA_NETWORK and set chainId to default', () => { - process.env.HEDERA_NETWORK = ''; - delete process.env.CHAIN_ID; - Relay = new RelayImpl(logger, new Registry()); - const actualNetVersion = Relay.net().version(); - expect(actualNetVersion).to.equal('298'); + withOverriddenEnvsInMochaTest({ HEDERA_NETWORK: '', CHAIN_ID: undefined }, () => { + it('should handle empty HEDERA_NETWORK and set chainId to default', () => { + Relay = new RelayImpl(logger, new Registry()); + const actualNetVersion = Relay.net().version(); + expect(actualNetVersion).to.equal('298'); + }); }); - it('should prioritize CHAIN_ID over HEDERA_NETWORK', () => { - process.env.HEDERA_NETWORK = 'mainnet'; - process.env.CHAIN_ID = '0x2'; - Relay = new RelayImpl(logger, new Registry()); - const actualNetVersion = Relay.net().version(); - expect(actualNetVersion).to.equal('2'); // 0x2 in decimal is 2 + withOverriddenEnvsInMochaTest({ HEDERA_NETWORK: 'mainnet', CHAIN_ID: '0x2' }, () => { + it('should prioritize CHAIN_ID over HEDERA_NETWORK', () => { + Relay = new RelayImpl(logger, new Registry()); + const actualNetVersion = Relay.net().version(); + expect(actualNetVersion).to.equal('2'); // 0x2 in decimal is 2 + }); }); }); diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts index c96a0a1351..a335c17315 100644 --- a/packages/relay/tests/lib/openrpc.spec.ts +++ b/packages/relay/tests/lib/openrpc.spec.ts @@ -96,7 +96,7 @@ describe('Open RPC Specification', function () { let methodsResponseSchema: { [method: string]: any }; let ethImpl: EthImpl; - const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); + const requestDetails = new RequestDetails({ requestId: 'openRpcTest', ipAddress: '0.0.0.0' }); this.beforeAll(async () => { rpcDocument = await parseOpenRPCDocument(JSON.stringify(openRpcSchema)); diff --git a/packages/relay/tests/lib/precheck.spec.ts b/packages/relay/tests/lib/precheck.spec.ts index f2e84b841f..f51f033420 100644 --- a/packages/relay/tests/lib/precheck.spec.ts +++ b/packages/relay/tests/lib/precheck.spec.ts @@ -23,7 +23,14 @@ import { Registry } from 'prom-client'; import { Hbar, HbarUnit } from '@hashgraph/sdk'; import pino from 'pino'; import { Precheck } from '../../src/lib/precheck'; -import { blobVersionedHash, contractAddress1, expectedError, mockData, signTransaction } from '../helpers'; +import { + blobVersionedHash, + contractAddress1, + expectedError, + mockData, + overrideEnvsInMochaDescribe, + signTransaction, +} from '../helpers'; import { MirrorNodeClient } from '../../src/lib/clients'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; @@ -235,24 +242,14 @@ describe('Precheck', async function () { }); describe('gas price', async function () { - let initialMinGasPriceBuffer; - before(async () => { - initialMinGasPriceBuffer = constants.GAS_PRICE_TINY_BAR_BUFFER; - process.env.GAS_PRICE_TINY_BAR_BUFFER = '10000000000'; // 1 tinybar - }); - - after(async () => { - process.env.GAS_PRICE_TINY_BAR_BUFFER = initialMinGasPriceBuffer; - }); + overrideEnvsInMochaDescribe({ GAS_PRICE_TINY_BAR_BUFFER: '10000000000' }); // 1 tinybar it('should pass for gas price gt to required gas price', async function () { - const result = precheck.gasPrice(parsedTxWithMatchingChainId, 10, requestDetails); - expect(result).to.not.exist; + expect(() => precheck.gasPrice(parsedTxWithMatchingChainId, 10, requestDetails)).to.not.throw; }); it('should pass for gas price equal to required gas price', async function () { - const result = precheck.gasPrice(parsedTxWithMatchingChainId, defaultGasPrice, requestDetails); - expect(result).to.not.exist; + expect(() => precheck.gasPrice(parsedTxWithMatchingChainId, defaultGasPrice, requestDetails)).to.not.throw; }); it('should recognize if a signed raw transaction is the deterministic deployment transaction', async () => { diff --git a/packages/relay/tests/lib/relay.spec.ts b/packages/relay/tests/lib/relay.spec.ts index c480ac04d6..c40d00ef7f 100644 --- a/packages/relay/tests/lib/relay.spec.ts +++ b/packages/relay/tests/lib/relay.spec.ts @@ -1,8 +1,29 @@ +/* + * + * Hedera JSON RPC Relay + * + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + import { expect } from 'chai'; import sinon from 'sinon'; import pino from 'pino'; import { Registry } from 'prom-client'; import { RelayImpl } from '../../src'; +import { withOverriddenEnvsInMochaTest } from '../helpers'; describe('RelayImpl', () => { const logger = pino(); @@ -36,19 +57,21 @@ describe('RelayImpl', () => { expect(eth).to.not.be.undefined; }); - it('should return the correct subscription implementation when enabled', () => { - process.env.SUBSCRIPTIONS_ENABLED = 'true'; - relay = new RelayImpl(logger, register); + withOverriddenEnvsInMochaTest({ SUBSCRIPTIONS_ENABLED: 'true' }, () => { + it('should return the correct subscription implementation when enabled', () => { + relay = new RelayImpl(logger, register); - const subs = relay.subs(); - expect(subs).to.not.be.undefined; + const subs = relay.subs(); + expect(subs).to.not.be.undefined; + }); }); - it('should return undefined subscription implementation when not enabled', () => { - process.env.SUBSCRIPTIONS_ENABLED = 'false'; - relay = new RelayImpl(logger, register); + withOverriddenEnvsInMochaTest({ SUBSCRIPTIONS_ENABLED: 'false' }, () => { + it('should return undefined subscription implementation when not enabled', () => { + relay = new RelayImpl(logger, register); - const subs = relay.subs(); - expect(subs).to.be.undefined; + const subs = relay.subs(); + expect(subs).to.be.undefined; + }); }); }); diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts index 4416db4139..7cec8bd8cb 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts @@ -28,7 +28,7 @@ import { IEthAddressHbarSpendingPlan } from '../../../../src/lib/db/types/hbarLi import { EthAddressHbarSpendingPlanNotFoundError } from '../../../../src/lib/db/types/hbarLimiter/errors'; import { randomBytes, uuidV4 } from 'ethers'; import { Registry } from 'prom-client'; -import { useInMemoryRedisServer } from '../../../helpers'; +import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../helpers'; import { RequestDetails } from '../../../../dist/lib/types'; chai.use(chaiAsPromised); @@ -58,6 +58,8 @@ describe('EthAddressHbarSpendingPlanRepository', function () { if (isSharedCacheEnabled) { useInMemoryRedisServer(logger, 6382); + } else { + overrideEnvsInMochaDescribe({ REDIS_ENABLED: 'false' }); } afterEach(async () => { diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts index 6ac974f99d..b85dc399a1 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts @@ -30,10 +30,9 @@ import { HbarSpendingPlanNotFoundError, } from '../../../../src/lib/db/types/hbarLimiter/errors'; import { IHbarSpendingRecord } from '../../../../src/lib/db/types/hbarLimiter/hbarSpendingRecord'; - import { SubscriptionType } from '../../../../src/lib/db/types/hbarLimiter/subscriptionType'; import { IDetailedHbarSpendingPlan } from '../../../../src/lib/db/types/hbarLimiter/hbarSpendingPlan'; -import { useInMemoryRedisServer } from '../../../helpers'; +import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../helpers'; import { RequestDetails } from '../../../../src/lib/types'; chai.use(chaiAsPromised); @@ -56,6 +55,8 @@ describe('HbarSpendingPlanRepository', function () { if (isSharedCacheEnabled) { useInMemoryRedisServer(logger, 6380); + } else { + overrideEnvsInMochaDescribe({ REDIS_ENABLED: 'false' }); } afterEach(async () => { diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts index 24670fb02f..44d8e649ef 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts @@ -28,7 +28,7 @@ import { IIPAddressHbarSpendingPlan } from '../../../../src/lib/db/types/hbarLim import { IPAddressHbarSpendingPlanNotFoundError } from '../../../../src/lib/db/types/hbarLimiter/errors'; import { randomBytes, uuidV4 } from 'ethers'; import { Registry } from 'prom-client'; -import { useInMemoryRedisServer } from '../../../helpers'; +import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../helpers'; import { RequestDetails } from '../../../../src/lib/types'; chai.use(chaiAsPromised); @@ -36,7 +36,10 @@ chai.use(chaiAsPromised); describe('IPAddressHbarSpendingPlanRepository', function () { const logger = pino(); const registry = new Registry(); - const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); + const requestDetails = new RequestDetails({ + requestId: 'ipAddressHbarSpendingPlanRepositoryTest', + ipAddress: '0.0.0.0', + }); const ttl = 86_400_000; // 1 day const ipAddress = '555.555.555.555'; const nonExistingIpAddress = 'xxx.xxx.xxx.xxx'; @@ -57,6 +60,8 @@ describe('IPAddressHbarSpendingPlanRepository', function () { if (isSharedCacheEnabled) { useInMemoryRedisServer(logger, 6383); + } else { + overrideEnvsInMochaDescribe({ REDIS_ENABLED: 'false' }); } afterEach(async () => { diff --git a/packages/relay/tests/lib/sdkClient.spec.ts b/packages/relay/tests/lib/sdkClient.spec.ts index c06e145482..74d74cda5c 100644 --- a/packages/relay/tests/lib/sdkClient.spec.ts +++ b/packages/relay/tests/lib/sdkClient.spec.ts @@ -24,7 +24,6 @@ import { expect } from 'chai'; import { resolve } from 'path'; import * as sinon from 'sinon'; import { config } from 'dotenv'; -import { Context } from 'mocha'; import EventEmitter from 'events'; import { Registry } from 'prom-client'; import { Utils } from '../../src/utils'; @@ -33,12 +32,17 @@ import MockAdapter from 'axios-mock-adapter'; import constants from '../../src/lib/constants'; import HbarLimit from '../../src/lib/hbarlimiter'; import { formatTransactionId } from '../../src/formatters'; -import { predefined } from '../../src/lib/errors/JsonRpcError'; +import { predefined } from '../../src'; import { MirrorNodeClient, SDKClient } from '../../src/lib/clients'; import HAPIService from '../../src/lib/services/hapiService/hapiService'; import MetricService from '../../src/lib/services/metricService/metricService'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; -import { calculateTxRecordChargeAmount, random20BytesAddress } from '../helpers'; +import { + calculateTxRecordChargeAmount, + overrideEnvsInMochaDescribe, + random20BytesAddress, + withOverriddenEnvsInMochaTest, +} from '../helpers'; import { AccountId, Client, @@ -76,7 +80,7 @@ describe('SdkClient', async function () { let metricService: MetricService; let mirrorNodeClient: MirrorNodeClient; - const requestDetails = new RequestDetails({ requestId: 'testId', ipAddress: '0.0.0.0' }); + const requestDetails = new RequestDetails({ requestId: 'sdkClientTest', ipAddress: '0.0.0.0' }); const feeSchedules = { current: { transactionFeeSchedule: [ @@ -94,6 +98,8 @@ describe('SdkClient', async function () { }, } as unknown as FeeSchedules; + overrideEnvsInMochaDescribe({ GET_RECORD_DEFAULT_TO_CONSENSUS_NODE: 'true' }); + before(() => { const hederaNetwork = process.env.HEDERA_NETWORK!; if (hederaNetwork in constants.CHAIN_IDS) { @@ -118,8 +124,6 @@ describe('SdkClient', async function () { eventEmitter, ); - process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE = 'true'; - instance = axios.create({ baseURL: 'https://localhost:5551/api/v1', responseType: 'json' as const, @@ -252,8 +256,6 @@ describe('SdkClient', async function () { }); describe('HAPIService', async () => { - let originalEnv: NodeJS.ProcessEnv; - const OPERATOR_KEY_ED25519 = { DER: '302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137', HEX_ED25519: '0x91132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137', @@ -264,31 +266,6 @@ describe('SdkClient', async function () { HEX_ECDSA: '0x08e926c84220295b5db5df25be107ce905b41e237ac748dd04d479c23dcdf2d5', }; - before(function (this: Context) { - // Store the original process.env - originalEnv = process.env; - - if ( - this.currentTest?.title === - 'Initialize the privateKey for default which is DER when OPERATOR_KEY_FORMAT is null' - ) { - process.env = new Proxy(process.env, { - get: (target, prop) => { - if (prop === 'OPERATOR_KEY_FORMAT') { - return null; - } - // @ts-ignore - return target[prop]; - }, - }); - } - }); - - after(() => { - // Restore the original process.env after the test - process.env = originalEnv; - }); - it('Initialize the privateKey for default which is DER', async () => { const hapiService = new HAPIService( logger, @@ -301,66 +278,71 @@ describe('SdkClient', async function () { expect(privateKey.toString()).to.eq(OPERATOR_KEY_ED25519.DER); }); - it('Initialize the privateKey for default which is DER when OPERATOR_KEY_FORMAT is undefined', async () => { - delete process.env.OPERATOR_KEY_FORMAT; - const hapiService = new HAPIService( - logger, - registry, - hbarLimiter, - new CacheService(logger, registry), - eventEmitter, - ); - const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ED25519.DER); - expect(privateKey.toString()).to.eq(OPERATOR_KEY_ED25519.DER); + withOverriddenEnvsInMochaTest({ OPERATOR_KEY_FORMAT: undefined }, () => { + it('Initialize the privateKey for default which is DER when OPERATOR_KEY_FORMAT is undefined', async () => { + const hapiService = new HAPIService( + logger, + registry, + hbarLimiter, + new CacheService(logger, registry), + eventEmitter, + ); + const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ED25519.DER); + expect(privateKey.toString()).to.eq(OPERATOR_KEY_ED25519.DER); + }); }); - it('Initialize the privateKey for OPERATOR_KEY_FORMAT set to DER', async () => { - process.env.OPERATOR_KEY_FORMAT = 'DER'; - const hapiService = new HAPIService( - logger, - registry, - hbarLimiter, - new CacheService(logger, registry), - eventEmitter, - ); - const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ECDSA.DER); - expect(privateKey.toString()).to.eq(OPERATOR_KEY_ECDSA.DER); + withOverriddenEnvsInMochaTest({ OPERATOR_KEY_FORMAT: 'DER' }, () => { + it('Initialize the privateKey for OPERATOR_KEY_FORMAT set to DER', async () => { + const hapiService = new HAPIService( + logger, + registry, + hbarLimiter, + new CacheService(logger, registry), + eventEmitter, + ); + const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ECDSA.DER); + expect(privateKey.toString()).to.eq(OPERATOR_KEY_ECDSA.DER); + }); }); - it('Initialize the privateKey for OPERATOR_KEY_FORMAT set to HEX_ED25519', async () => { - process.env.OPERATOR_KEY_FORMAT = 'HEX_ED25519'; - const hapiService = new HAPIService( - logger, - registry, - hbarLimiter, - new CacheService(logger, registry), - eventEmitter, - ); - const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ED25519.HEX_ED25519); - expect(privateKey.toString()).to.eq(OPERATOR_KEY_ED25519.DER); + withOverriddenEnvsInMochaTest({ OPERATOR_KEY_FORMAT: 'HEX_ED25519' }, () => { + it('Initialize the privateKey for OPERATOR_KEY_FORMAT set to HEX_ED25519', async () => { + const hapiService = new HAPIService( + logger, + registry, + hbarLimiter, + new CacheService(logger, registry), + eventEmitter, + ); + const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ED25519.HEX_ED25519); + expect(privateKey.toString()).to.eq(OPERATOR_KEY_ED25519.DER); + }); }); - it('Initialize the privateKey for OPERATOR_KEY_FORMAT set to HEX_ECDSA', async () => { - process.env.OPERATOR_KEY_FORMAT = 'HEX_ECDSA'; - const hapiService = new HAPIService( - logger, - registry, - hbarLimiter, - new CacheService(logger, registry), - eventEmitter, - ); - const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ECDSA.HEX_ECDSA); - expect(privateKey.toString()).to.eq(OPERATOR_KEY_ECDSA.DER); + withOverriddenEnvsInMochaTest({ OPERATOR_KEY_FORMAT: 'HEX_ECDSA' }, () => { + it('Initialize the privateKey for OPERATOR_KEY_FORMAT set to HEX_ECDSA', async () => { + const hapiService = new HAPIService( + logger, + registry, + hbarLimiter, + new CacheService(logger, registry), + eventEmitter, + ); + const privateKey = Utils.createPrivateKeyBasedOnFormat.call(hapiService, OPERATOR_KEY_ECDSA.HEX_ECDSA); + expect(privateKey.toString()).to.eq(OPERATOR_KEY_ECDSA.DER); + }); }); - it('It should throw an Error when an unexpected string is set', async () => { - process.env.OPERATOR_KEY_FORMAT = 'BAD_FORMAT'; - try { - new HAPIService(logger, registry, hbarLimiter, new CacheService(logger, registry), eventEmitter); - expect.fail(`Expected an error but nothing was thrown`); - } catch (e: any) { - expect(e.message).to.eq('Invalid OPERATOR_KEY_FORMAT provided: BAD_FORMAT'); - } + withOverriddenEnvsInMochaTest({ OPERATOR_KEY_FORMAT: 'BAD_FORMAT' }, () => { + it('It should throw an Error when an unexpected string is set', async () => { + try { + new HAPIService(logger, registry, hbarLimiter, new CacheService(logger, registry), eventEmitter); + expect.fail(`Expected an error but nothing was thrown`); + } catch (e: any) { + expect(e.message).to.eq('Invalid OPERATOR_KEY_FORMAT provided: BAD_FORMAT'); + } + }); }); }); @@ -2164,7 +2146,6 @@ describe('SdkClient', async function () { const mockedNetworkGasPrice = 710000; const randomAccountAddress = random20BytesAddress(); - let hbarRateLimitPreemptiveCheck: string | undefined; const getMockedTransaction = (transactionType: string, toHbar: boolean) => { let transactionFee: any; @@ -2268,12 +2249,12 @@ describe('SdkClient', async function () { let hbarLimitMock: sinon.SinonMock; let sdkClientMock: sinon.SinonMock; + overrideEnvsInMochaDescribe({ HBAR_RATE_LIMIT_PREEMPTIVE_CHECK: 'true' }); + beforeEach(() => { hbarLimitMock = sinon.mock(hbarLimiter); sdkClientMock = sinon.mock(sdkClient); mock = new MockAdapter(instance); - hbarRateLimitPreemptiveCheck = process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK; - process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK = 'true'; }); afterEach(() => { @@ -2281,7 +2262,6 @@ describe('SdkClient', async function () { sinon.restore(); sdkClientMock.restore(); hbarLimitMock.restore(); - process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK = hbarRateLimitPreemptiveCheck; }); it('should rate limit before creating file', async () => { @@ -2627,55 +2607,56 @@ describe('SdkClient', async function () { expect(transactionRecordStub.called).to.be.true; }); - it('should execute EthereumTransaction, retrieve transactionStatus and expenses via MIRROR NODE', async () => { - process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE = 'false'; // switch to mirror node mode - const mockedTransactionId = transactionId.toString(); - const mockedTransactionIdFormatted = formatTransactionId(mockedTransactionId); - const mockedMirrorNodeTransactionRecord = { - transactions: [ - { - charged_tx_fee: defaultTransactionFee, - result: 'SUCCESS', - transaction_id: mockedTransactionIdFormatted, - transfers: [ - { - account: '0.0.800', - amount: defaultTransactionFee, - is_approval: false, - }, - { - account: process.env.OPERATOR_ID_MAIN, - amount: -1 * defaultTransactionFee, - is_approval: false, - }, - ], - }, - ], - }; - mock.onGet(`transactions/${mockedTransactionIdFormatted}?nonce=0`).reply(200, mockedMirrorNodeTransactionRecord); - const transactionResponse = getMockedTransactionResponse(EthereumTransaction.name); - const transactionStub = sinon.stub(EthereumTransaction.prototype, 'execute').resolves(transactionResponse); - - hbarLimitMock.expects('addExpense').withArgs(defaultTransactionFee).once(); - hbarLimitMock - .expects('shouldLimit') - .withArgs(sinon.match.any, constants.EXECUTION_MODE.TRANSACTION, mockedCallerName) - .once() - .returns(false); - - const response = await sdkClient.executeTransaction( - new EthereumTransaction().setCallDataFileId(fileId).setEthereumData(transactionBuffer), - mockedCallerName, - mockedInteractingEntity, - requestDetails, - true, - randomAccountAddress, - ); - - expect(response).to.eq(transactionResponse); - expect(transactionStub.called).to.be.true; + withOverriddenEnvsInMochaTest({ GET_RECORD_DEFAULT_TO_CONSENSUS_NODE: 'false' }, () => { + it('should execute EthereumTransaction, retrieve transactionStatus and expenses via MIRROR NODE', async () => { + const mockedTransactionId = transactionId.toString(); + const mockedTransactionIdFormatted = formatTransactionId(mockedTransactionId); + const mockedMirrorNodeTransactionRecord = { + transactions: [ + { + charged_tx_fee: defaultTransactionFee, + result: 'SUCCESS', + transaction_id: mockedTransactionIdFormatted, + transfers: [ + { + account: '0.0.800', + amount: defaultTransactionFee, + is_approval: false, + }, + { + account: process.env.OPERATOR_ID_MAIN, + amount: -1 * defaultTransactionFee, + is_approval: false, + }, + ], + }, + ], + }; + mock + .onGet(`transactions/${mockedTransactionIdFormatted}?nonce=0`) + .reply(200, mockedMirrorNodeTransactionRecord); + const transactionResponse = getMockedTransactionResponse(EthereumTransaction.name); + const transactionStub = sinon.stub(EthereumTransaction.prototype, 'execute').resolves(transactionResponse); + + hbarLimitMock.expects('addExpense').withArgs(defaultTransactionFee).once(); + hbarLimitMock + .expects('shouldLimit') + .withArgs(sinon.match.any, constants.EXECUTION_MODE.TRANSACTION, mockedCallerName) + .once() + .returns(false); + + const response = await sdkClient.executeTransaction( + new EthereumTransaction().setCallDataFileId(fileId).setEthereumData(transactionBuffer), + mockedCallerName, + mockedInteractingEntity, + requestDetails, + true, + randomAccountAddress, + ); - process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE = 'true'; // switch back to consensus node + expect(response).to.eq(transactionResponse); + expect(transactionStub.called).to.be.true; + }); }); it('Should execute calculateTxRecordChargeAmount() to get the charge amount of transaction record', () => { diff --git a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts index 931e537f0a..7917467507 100644 --- a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts +++ b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts @@ -26,7 +26,7 @@ import { CacheService } from '../../../../src/lib/services/cacheService/cacheSer import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; -import { useInMemoryRedisServer } from '../../../helpers'; +import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../helpers'; import { RequestDetails } from '../../../../dist/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -141,8 +141,9 @@ describe('CacheService Test Suite', async function () { }; describe('Internal Cache Test Suite', async function () { + overrideEnvsInMochaDescribe({ REDIS_ENABLED: 'false' }); + this.beforeAll(() => { - process.env.REDIS_ENABLED = 'false'; cacheService = new CacheService(logger.child({ name: 'cache-service' }), registry); }); @@ -276,18 +277,14 @@ describe('CacheService Test Suite', async function () { }; useInMemoryRedisServer(logger, 6381); - - let multiSet: string | undefined; + overrideEnvsInMochaDescribe({ MULTI_SET: 'true' }); this.beforeAll(async () => { - multiSet = process.env.MULTI_SET; - process.env.MULTI_SET = 'true'; cacheService = new CacheService(logger.child({ name: 'cache-service' }), registry); }); this.afterAll(async () => { await cacheService.disconnectRedisClient(); - process.env.MULTI_SET = multiSet; }); this.beforeEach(async () => { diff --git a/packages/relay/tests/lib/services/debugService/debug.spec.ts b/packages/relay/tests/lib/services/debugService/debug.spec.ts index 41e7e36294..1c860fe88d 100644 --- a/packages/relay/tests/lib/services/debugService/debug.spec.ts +++ b/packages/relay/tests/lib/services/debugService/debug.spec.ts @@ -28,7 +28,7 @@ import { MirrorNodeClient } from '../../../../src/lib/clients'; import pino from 'pino'; import { TracerType } from '../../../../src/lib/constants'; import { DebugService } from '../../../../src/lib/services/debugService'; -import { getQueryParams } from '../../../helpers'; +import { getQueryParams, withOverriddenEnvsInMochaTest } from '../../../helpers'; import RelayAssertions from '../../../assertions'; import { predefined } from '../../../../src'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; @@ -321,19 +321,8 @@ describe('Debug API Test Suite', async function () { web3Mock.reset(); }); - describe('all methods require a debug flag', async function () { - let ffAtStart; - - before(function () { - ffAtStart = process.env.DEBUG_API_ENABLED; - }); - - after(function () { - process.env.DEBUG_API_ENABLED = ffAtStart; - }); - - it('DEBUG_API_ENABLED is not specified', async function () { - delete process.env.DEBUG_API_ENABLED; + withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: undefined }, () => { + it('should throw UNSUPPORTED_METHOD', async function () { await RelayAssertions.assertRejection( predefined.UNSUPPORTED_METHOD, debugService.debug_traceTransaction, @@ -342,21 +331,10 @@ describe('Debug API Test Suite', async function () { [transactionHash, callTracer, tracerConfigFalse, requestDetails], ); }); + }); - it('DEBUG_API_ENABLED=true', async function () { - process.env.DEBUG_API_ENABLED = 'true'; - - const traceTransaction = await debugService.debug_traceTransaction( - transactionHash, - callTracer, - tracerConfigFalse, - requestDetails, - ); - expect(traceTransaction).to.exist; - }); - - it('DEBUG_API_ENABLED=false', async function () { - process.env.DEBUG_API_ENABLED = 'false'; + withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: 'false' }, () => { + it('should throw UNSUPPORTED_METHOD', async function () { await RelayAssertions.assertRejection( predefined.UNSUPPORTED_METHOD, debugService.debug_traceTransaction, @@ -367,176 +345,179 @@ describe('Debug API Test Suite', async function () { }); }); - describe('callTracer', async function () { - before(() => { - process.env.DEBUG_API_ENABLED = 'true'; - }); - - it('Test call tracer with onlyTopCall false', async function () { - const expectedResult = { - type: 'CREATE', - from: '0xc37f417fa09933335240fca72dd257bfbde9c275', - to: '0x637a6a8e5a69c087c24983b05261f63f64ed7e9b', - value: '0x0', - gas: '0x493e0', - gasUsed: '0x3a980', - input: '0x1', - output: '0x2', - calls: [ - { - type: 'CREATE', - from: '0x637a6a8e5a69c087c24983b05261f63f64ed7e9b', - to: '0x91b1c451777122afc9b83f9b96160d7e59847ad7', - gas: '0x2e525', - gasUsed: '0x4b', - input: '0x', - output: '0x', - value: '0x0', - }, - ], - }; - - const result = await debugService.debug_traceTransaction( + withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: 'true' }, () => { + it('should successfully debug a transaction', async function () { + const traceTransaction = await debugService.debug_traceTransaction( transactionHash, callTracer, tracerConfigFalse, requestDetails, ); - - expect(result).to.deep.equal(expectedResult); + expect(traceTransaction).to.exist; }); - it('Test call tracer with onlyTopCall true', async function () { - const expectedResult = { - type: 'CREATE', - from: '0xc37f417fa09933335240fca72dd257bfbde9c275', - to: '0x637a6a8e5a69c087c24983b05261f63f64ed7e9b', - value: '0x0', - gas: '0x493e0', - gasUsed: '0x3a980', - input: '0x1', - output: '0x2', - calls: undefined, - }; - const result = await debugService.debug_traceTransaction( - transactionHash, - callTracer, - tracerConfigTrue, - requestDetails, - ); - - expect(result).to.deep.equal(expectedResult); - }); - }); + describe('callTracer', async function () { + it('Test call tracer with onlyTopCall false', async function () { + const expectedResult = { + type: 'CREATE', + from: '0xc37f417fa09933335240fca72dd257bfbde9c275', + to: '0x637a6a8e5a69c087c24983b05261f63f64ed7e9b', + value: '0x0', + gas: '0x493e0', + gasUsed: '0x3a980', + input: '0x1', + output: '0x2', + calls: [ + { + type: 'CREATE', + from: '0x637a6a8e5a69c087c24983b05261f63f64ed7e9b', + to: '0x91b1c451777122afc9b83f9b96160d7e59847ad7', + gas: '0x2e525', + gasUsed: '0x4b', + input: '0x', + output: '0x', + value: '0x0', + }, + ], + }; - describe('opcodeLogger', async function () { - before(() => { - process.env.DEBUG_API_ENABLED = 'true'; - }); + const result = await debugService.debug_traceTransaction( + transactionHash, + callTracer, + tracerConfigFalse, + requestDetails, + ); - for (const config of opcodeLoggerConfigs) { - const opcodeLoggerParams = Object.keys(config) - .map((key) => `${key}=${config[key]}`) - .join(', '); - - describe(`When opcode logger is called with ${opcodeLoggerParams}`, async function () { - const emptyFields = Object.keys(config) - .filter((key) => (key.startsWith('disable') && config[key]) || (key.startsWith('enable') && !config[key])) - .map((key) => (config[key] ? key.replace('disable', '') : key.replace('enable', ''))) - .map((key) => key.toLowerCase()); - - it(`Then ${ - emptyFields.length ? `'${emptyFields}' should be empty` : 'all should be returned' - }`, async function () { - const expectedResult = { - gas: opcodesResponse.gas, - failed: opcodesResponse.failed, - returnValue: strip0x(opcodesResponse.return_value!), - structLogs: opcodesResponse.opcodes?.map((opcode) => ({ - pc: opcode.pc, - op: opcode.op, - gas: opcode.gas, - gasCost: opcode.gas_cost, - depth: opcode.depth, - stack: config.disableStack ? null : opcode.stack, - memory: config.enableMemory ? opcode.memory : null, - storage: config.disableStorage ? null : opcode.storage, - reason: opcode.reason ? strip0x(opcode.reason) : null, - })), - }; - - const result = await debugService.debug_traceTransaction( - transactionHash, - opcodeLogger, - config, - requestDetails, - ); - - expect(result).to.deep.equal(expectedResult); - }); + expect(result).to.deep.equal(expectedResult); }); - } - }); - - describe('Invalid scenarios', async function () { - let notFound; - before(() => { - process.env.DEBUG_API_ENABLED = 'true'; - }); - beforeEach(() => { - notFound = { - _status: { - messages: [ - { - message: 'Not found', - }, - ], - }, - }; - restMock.onGet(CONTRACTS_RESULTS_BY_NON_EXISTENT_HASH).reply(404, notFound); - restMock.onGet(CONTRACT_RESULTS_BY_ACTIONS_NON_EXISTENT_HASH).reply(404, notFound); + it('Test call tracer with onlyTopCall true', async function () { + const expectedResult = { + type: 'CREATE', + from: '0xc37f417fa09933335240fca72dd257bfbde9c275', + to: '0x637a6a8e5a69c087c24983b05261f63f64ed7e9b', + value: '0x0', + gas: '0x493e0', + gasUsed: '0x3a980', + input: '0x1', + output: '0x2', + calls: undefined, + }; + const result = await debugService.debug_traceTransaction( + transactionHash, + callTracer, + tracerConfigTrue, + requestDetails, + ); + + expect(result).to.deep.equal(expectedResult); + }); }); - afterEach(() => { - restMock.reset(); + describe('opcodeLogger', async function () { + for (const config of opcodeLoggerConfigs) { + const opcodeLoggerParams = Object.keys(config) + .map((key) => `${key}=${config[key]}`) + .join(', '); + + describe(`When opcode logger is called with ${opcodeLoggerParams}`, async function () { + const emptyFields = Object.keys(config) + .filter((key) => (key.startsWith('disable') && config[key]) || (key.startsWith('enable') && !config[key])) + .map((key) => (config[key] ? key.replace('disable', '') : key.replace('enable', ''))) + .map((key) => key.toLowerCase()); + + it(`Then ${ + emptyFields.length ? `'${emptyFields}' should be empty` : 'all should be returned' + }`, async function () { + const expectedResult = { + gas: opcodesResponse.gas, + failed: opcodesResponse.failed, + returnValue: strip0x(opcodesResponse.return_value!), + structLogs: opcodesResponse.opcodes?.map((opcode) => ({ + pc: opcode.pc, + op: opcode.op, + gas: opcode.gas, + gasCost: opcode.gas_cost, + depth: opcode.depth, + stack: config.disableStack ? null : opcode.stack, + memory: config.enableMemory ? opcode.memory : null, + storage: config.disableStorage ? null : opcode.storage, + reason: opcode.reason ? strip0x(opcode.reason) : null, + })), + }; + + const result = await debugService.debug_traceTransaction( + transactionHash, + opcodeLogger, + config, + requestDetails, + ); + + expect(result).to.deep.equal(expectedResult); + }); + }); + } }); - it('test case for non-existing transaction hash', async function () { - const expectedError = predefined.RESOURCE_NOT_FOUND( - `Failed to retrieve contract results for transaction ${nonExistentTransactionHash}`, - ); + describe('Invalid scenarios', async function () { + let notFound: { _status: { messages: { message: string }[] } }; + + beforeEach(() => { + notFound = { + _status: { + messages: [ + { + message: 'Not found', + }, + ], + }, + }; + restMock.onGet(CONTRACTS_RESULTS_BY_NON_EXISTENT_HASH).reply(404, notFound); + restMock.onGet(CONTRACT_RESULTS_BY_ACTIONS_NON_EXISTENT_HASH).reply(404, notFound); + }); - await RelayAssertions.assertRejection(expectedError, debugService.debug_traceTransaction, true, debugService, [ - nonExistentTransactionHash, - callTracer, - tracerConfigTrue, - requestDetails, - ]); - }); + afterEach(() => { + restMock.reset(); + }); - it('should return empty result with invalid parameters in formatOpcodeResult', async function () { - const opcodeResult = await debugService.formatOpcodesResult(null, {}); - // @ts-ignore - expect(opcodeResult.gas).to.eq(0); - // @ts-ignore - expect(opcodeResult.failed).to.eq(true); - // @ts-ignore - expect(opcodeResult.returnValue).to.eq(''); - // @ts-ignore - expect(opcodeResult.structLogs).to.be.an('array').that.is.empty; - }); + it('test case for non-existing transaction hash', async function () { + const expectedError = predefined.RESOURCE_NOT_FOUND( + `Failed to retrieve contract results for transaction ${nonExistentTransactionHash}`, + ); + + await RelayAssertions.assertRejection( + expectedError, + debugService.debug_traceTransaction, + true, + debugService, + [nonExistentTransactionHash, callTracer, tracerConfigTrue, requestDetails], + ); + }); - describe('resolveAddress', async function () { - it('should return null address with invalid parameters in resolveAddress', async function () { - const address = await debugService.resolveAddress(null!, requestDetails); - expect(address).to.be.null; + it('should return empty result with invalid parameters in formatOpcodeResult', async function () { + const opcodeResult = await debugService.formatOpcodesResult(null, {}); + // @ts-ignore + expect(opcodeResult.gas).to.eq(0); + // @ts-ignore + expect(opcodeResult.failed).to.eq(true); + // @ts-ignore + expect(opcodeResult.returnValue).to.eq(''); + // @ts-ignore + expect(opcodeResult.structLogs).to.be.an('array').that.is.empty; }); - it('should return passed address on notFound entity from the mirror node', async function () { - restMock.onGet(ACCOUNT_BY_ADDRESS).reply(404, notFound); - const address = await debugService.resolveAddress(accountAddress, requestDetails); - expect(address).to.eq(accountAddress); + describe('resolveAddress', async function () { + it('should return null address with invalid parameters in resolveAddress', async function () { + // @ts-ignore + const address = await debugService.resolveAddress(null, requestDetails); + expect(address).to.be.null; + }); + + it('should return passed address on notFound entity from the mirror node', async function () { + restMock.onGet(ACCOUNT_BY_ADDRESS).reply(404, notFound); + const address = await debugService.resolveAddress(accountAddress, requestDetails); + expect(address).to.eq(accountAddress); + }); }); }); }); diff --git a/packages/relay/tests/lib/services/eth/filter.spec.ts b/packages/relay/tests/lib/services/eth/filter.spec.ts index cbfaaa8d90..096fe091f5 100644 --- a/packages/relay/tests/lib/services/eth/filter.spec.ts +++ b/packages/relay/tests/lib/services/eth/filter.spec.ts @@ -27,7 +27,14 @@ import { MirrorNodeClient } from '../../../../src/lib/clients'; import pino from 'pino'; import constants from '../../../../src/lib/constants'; import { CommonService, FilterService } from '../../../../src/lib/services/ethService'; -import { defaultBlock, defaultEvmAddress, defaultLogs1, defaultLogTopics, toHex } from '../../../helpers'; +import { + defaultBlock, + defaultEvmAddress, + defaultLogs1, + defaultLogTopics, + toHex, + withOverriddenEnvsInMochaTest, +} from '../../../helpers'; import RelayAssertions from '../../../assertions'; import { predefined } from '../../../../src'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; @@ -39,7 +46,7 @@ dotenv.config({ path: path.resolve(__dirname, '../test.env') }); const logger = pino(); const registry = new Registry(); -let restMock: MockAdapter, web3Mock: MockAdapter; +let restMock: MockAdapter; let mirrorNodeInstance: MirrorNodeClient; let filterService: FilterService; let cacheService: CacheService; @@ -52,14 +59,19 @@ describe('Filter API Test Suite', async function () { toBlock: 'latest', }; - let blockFilterObject; + const blockFilterObject = { + type: constants.FILTER.TYPE.NEW_BLOCK, + params: { + blockAtCreation: defaultBlock.number, + }, + lastQueried: null, + }; const existingFilterId = '0x1112233'; const nonExistingFilterId = '0x1112231'; const LATEST_BLOCK_QUERY = 'blocks?limit=1&order=desc'; const BLOCK_BY_NUMBER_QUERY = 'blocks'; - let logFilterObject; - const validateFilterCache = async (filterId, expectedFilterType, expectedParams = {}) => { + const validateFilterCache = async (filterId: string, expectedFilterType: string, expectedParams = {}) => { const cacheKey = `${constants.CACHE_KEY.FILTERID}_${filterId}`; const cachedFilter = await cacheService.getAsync(cacheKey, 'validateFilterCache', requestDetails); expect(cachedFilter).to.exist; @@ -71,24 +83,7 @@ describe('Filter API Test Suite', async function () { }; this.beforeAll(() => { - blockFilterObject = { - type: constants.FILTER.TYPE.NEW_BLOCK, - params: { - blockAtCreation: defaultBlock.number, - }, - lastQueried: null, - }; - - logFilterObject = { - type: constants.FILTER.TYPE.LOG, - params: { - fromBlock: defaultBlock.number, - }, - lastQueried: null, - }; - cacheService = new CacheService(logger.child({ name: `cache` }), registry); - // @ts-ignore mirrorNodeInstance = new MirrorNodeClient( process.env.MIRROR_NODE_URL ?? '', logger.child({ name: `mirror-node` }), @@ -96,13 +91,8 @@ describe('Filter API Test Suite', async function () { cacheService, ); - // @ts-ignore restMock = new MockAdapter(mirrorNodeInstance.getMirrorNodeRestInstance(), { onNoMatch: 'throwException' }); - // @ts-ignore - web3Mock = new MockAdapter(mirrorNodeInstance.getMirrorNodeWeb3Instance(), { onNoMatch: 'throwException' }); - - // @ts-ignore const common = new CommonService(mirrorNodeInstance, logger.child({ name: 'common-service' }), cacheService); filterService = new FilterService( mirrorNodeInstance, @@ -123,89 +113,104 @@ describe('Filter API Test Suite', async function () { }); describe('all methods require a filter flag', async function () { - let ffAtStart; + withOverriddenEnvsInMochaTest({ FILTER_API_ENABLED: undefined }, () => { + it(`should throw UNSUPPORTED_METHOD for newFilter`, async function () { + await RelayAssertions.assertRejection( + predefined.UNSUPPORTED_METHOD, + filterService.newFilter, + true, + filterService, + [undefined, undefined, requestDetails], + ); + }); - before(function () { - ffAtStart = process.env.FILTER_API_ENABLED; - }); + it(`should throw UNSUPPORTED_METHOD for uninstallFilter`, async function () { + await RelayAssertions.assertRejection( + predefined.UNSUPPORTED_METHOD, + filterService.uninstallFilter, + true, + filterService, + [existingFilterId, requestDetails], + ); + }); - after(function () { - process.env.FILTER_API_ENABLED = ffAtStart; + it(`should throw UNSUPPORTED_METHOD for getFilterChanges`, async function () { + await RelayAssertions.assertRejection( + predefined.UNSUPPORTED_METHOD, + filterService.getFilterChanges, + true, + filterService, + [existingFilterId, requestDetails], + ); + }); }); - it('FILTER_API_ENABLED is not specified', async function () { - delete process.env.FILTER_API_ENABLED; - await RelayAssertions.assertRejection( - predefined.UNSUPPORTED_METHOD, - filterService.newFilter, - true, - filterService, - [undefined, undefined, requestDetails], - ); - await RelayAssertions.assertRejection( - predefined.UNSUPPORTED_METHOD, - filterService.uninstallFilter, - true, - filterService, - [existingFilterId, requestDetails], - ); - await RelayAssertions.assertRejection( - predefined.UNSUPPORTED_METHOD, - filterService.getFilterChanges, - true, - filterService, - [existingFilterId, requestDetails], - ); - }); + withOverriddenEnvsInMochaTest({ FILTER_API_ENABLED: 'true' }, () => { + let filterId: string; - it('FILTER_API_ENABLED=true', async function () { - process.env.FILTER_API_ENABLED = 'true'; - restMock.onGet(LATEST_BLOCK_QUERY).reply(200, { blocks: [{ ...defaultBlock }] }); - const filterId = await filterService.newFilter(undefined, undefined, requestDetails); - expect(filterId).to.exist; - expect(RelayAssertions.validateHash(filterId, 32)).to.eq(true, 'returns valid filterId'); + beforeEach(async () => { + restMock.onGet(LATEST_BLOCK_QUERY).reply(200, { blocks: [{ ...defaultBlock }] }); + filterId = await filterService.newFilter(undefined, undefined, requestDetails); + }); - restMock.onGet(`blocks/${defaultBlock.number}`).reply(200, defaultBlock); - restMock - .onGet( - `contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}×tamp=lte:${defaultBlock.timestamp.to}&limit=100&order=asc`, - ) - .reply(200, defaultLogs1); - const filterChanges = await filterService.getFilterChanges(filterId, requestDetails); - expect(filterChanges).to.exist; + it(`should call newFilter`, async function () { + expect(filterId).to.exist; + expect(RelayAssertions.validateHash(filterId, 32)).to.eq(true, 'returns valid filterId'); + }); - const isFilterUninstalled = await filterService.uninstallFilter(filterId, requestDetails); - expect(isFilterUninstalled).to.eq(true, 'executes correctly'); + it(`should call getFilterChanges`, async function () { + restMock.onGet(`blocks/${defaultBlock.number}`).reply(200, defaultBlock); + restMock + .onGet( + `contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}×tamp=lte:${defaultBlock.timestamp.to}&limit=100&order=asc`, + ) + .reply(200, defaultLogs1); + const filterChanges = await filterService.getFilterChanges(filterId, requestDetails); + expect(filterChanges).to.exist; + }); + + it(`should call uninstallFilter`, async function () { + const isFilterUninstalled = await filterService.uninstallFilter(filterId, requestDetails); + expect(isFilterUninstalled).to.eq(true, 'executes correctly'); + }); }); - it('FILTER_API_ENABLED=false', async function () { - process.env.FILTER_API_ENABLED = 'false'; - await RelayAssertions.assertRejection( - predefined.UNSUPPORTED_METHOD, - filterService.newFilter, - true, - filterService, - [undefined, undefined, requestDetails], - ); - await RelayAssertions.assertRejection( - predefined.UNSUPPORTED_METHOD, - filterService.uninstallFilter, - true, - filterService, - [existingFilterId, requestDetails], - ); - await RelayAssertions.assertRejection( - predefined.UNSUPPORTED_METHOD, - filterService.getFilterChanges, - true, - filterService, - [existingFilterId, requestDetails], - ); + withOverriddenEnvsInMochaTest({ FILTER_API_ENABLED: 'false' }, () => { + it(`should throw UNSUPPORTED_METHOD for newFilter`, async function () { + await RelayAssertions.assertRejection( + predefined.UNSUPPORTED_METHOD, + filterService.newFilter, + true, + filterService, + [undefined, undefined, requestDetails], + ); + }); + + it(`should throw UNSUPPORTED_METHOD for uninstallFilter`, async function () { + await RelayAssertions.assertRejection( + predefined.UNSUPPORTED_METHOD, + filterService.uninstallFilter, + true, + filterService, + [existingFilterId, requestDetails], + ); + }); + + it(`should throw UNSUPPORTED_METHOD for getFilterChanges`, async function () { + await RelayAssertions.assertRejection( + predefined.UNSUPPORTED_METHOD, + filterService.getFilterChanges, + true, + filterService, + [existingFilterId, requestDetails], + ); + }); }); }); describe('eth_newFilter', async function () { - let blockNumberHexes, numberHex; + let blockNumberHexes: Record; + let numberHex: string; beforeEach(() => { blockNumberHexes = { diff --git a/packages/relay/tests/lib/services/metricService/metricService.spec.ts b/packages/relay/tests/lib/services/metricService/metricService.spec.ts index e03b0b8ca4..49aabbfc03 100644 --- a/packages/relay/tests/lib/services/metricService/metricService.spec.ts +++ b/packages/relay/tests/lib/services/metricService/metricService.spec.ts @@ -31,7 +31,11 @@ import { Utils } from '../../../../src/utils'; import constants from '../../../../src/lib/constants'; import HbarLimit from '../../../../src/lib/hbarlimiter'; import { MirrorNodeClient, SDKClient } from '../../../../src/lib/clients'; -import { calculateTxRecordChargeAmount } from '../../../helpers'; +import { + calculateTxRecordChargeAmount, + overrideEnvsInMochaDescribe, + withOverriddenEnvsInMochaTest, +} from '../../../helpers'; import MetricService from '../../../../src/lib/services/metricService/metricService'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import { IExecuteQueryEventPayload, IExecuteTransactionEventPayload, RequestDetails } from '../../../../src/lib/types'; @@ -96,9 +100,9 @@ describe('Metric Service', function () { ], } as unknown as TransactionRecord; - before(() => { - process.env.OPERATOR_KEY_FORMAT = 'DER'; + overrideEnvsInMochaDescribe({ OPERATOR_KEY_FORMAT: 'DER' }); + before(() => { // consensus node client const hederaNetwork = process.env.HEDERA_NETWORK!; if (hederaNetwork in constants.CHAIN_IDS) { @@ -164,151 +168,155 @@ describe('Metric Service', function () { requestDetails, }; - it('Should execute captureTransactionMetrics() by retrieving transaction record from MIRROR NODE client', async () => { - mock.onGet(`transactions/${mockedTransactionIdFormatted}?nonce=0`).reply(200, mockedMirrorNodeTransactionRecord); - - process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE = 'false'; - - const originalBudget = hbarLimiter.getRemainingBudget(); - - // capture metrics - await metricService.captureTransactionMetrics(mockedExecuteTransactionEventPayload); - - // validate hbarLimiter - const updatedBudget = hbarLimiter.getRemainingBudget(); - expect(originalBudget - updatedBudget).to.eq(mockedTxFee); - - // validate cost metrics - const costMetricObject = (await metricService['consensusNodeClientHistogramCost'].get()).values.find( - (metric) => metric.metricName === metricHistogramCostSumTitle, - ); - expect(costMetricObject).to.not.be.undefined; - expect(costMetricObject!.metricName).to.eq(metricHistogramCostSumTitle); - expect(costMetricObject!.labels.caller).to.eq(mockedCallerName); - expect(costMetricObject!.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(costMetricObject!.value).to.eq(mockedTxFee); - }); - - it('Should execute captureTransactionMetrics() by retrieving transaction record from CONSENSUS NODE client', async () => { - process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE = 'true'; - const mockedExchangeRateInCents = 12; - const expectedTxRecordFee = calculateTxRecordChargeAmount(mockedExchangeRateInCents); + withOverriddenEnvsInMochaTest({ GET_RECORD_DEFAULT_TO_CONSENSUS_NODE: 'false' }, () => { + it('Should execute captureTransactionMetrics() by retrieving transaction record from MIRROR NODE client', async () => { + mock + .onGet(`transactions/${mockedTransactionIdFormatted}?nonce=0`) + .reply(200, mockedMirrorNodeTransactionRecord); - const transactionRecordStub = sinon - .stub(TransactionRecordQuery.prototype, 'execute') - .resolves(mockedConsensusNodeTransactionRecord); + const originalBudget = hbarLimiter.getRemainingBudget(); - const originalBudget = hbarLimiter.getRemainingBudget(); + // capture metrics + await metricService.captureTransactionMetrics(mockedExecuteTransactionEventPayload); - await metricService.captureTransactionMetrics(mockedExecuteTransactionEventPayload); - expect(transactionRecordStub.called).to.be.true; + // validate hbarLimiter + const updatedBudget = hbarLimiter.getRemainingBudget(); + expect(originalBudget - updatedBudget).to.eq(mockedTxFee); - // validate hbarLimiter - // note: since the query is made to consensus node, the total charged amount = txFee + txRecordFee - const updatedBudget = hbarLimiter.getRemainingBudget(); - expect(originalBudget - updatedBudget).to.eq(mockedTxFee + expectedTxRecordFee); - - // validate cost metric - // @ts-ignore - const metricObjects = await metricService['consensusNodeClientHistogramCost'].get(); - const txRecordFeeMetricObject = metricObjects.values.find((metric) => { - return ( - metric.labels.mode === constants.EXECUTION_MODE.RECORD && metric.metricName === metricHistogramCostSumTitle + // validate cost metrics + const costMetricObject = (await metricService['consensusNodeClientHistogramCost'].get()).values.find( + (metric) => metric.metricName === metricHistogramCostSumTitle, ); + expect(costMetricObject).to.not.be.undefined; + expect(costMetricObject!.metricName).to.eq(metricHistogramCostSumTitle); + expect(costMetricObject!.labels.caller).to.eq(mockedCallerName); + expect(costMetricObject!.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(costMetricObject!.value).to.eq(mockedTxFee); }); - const transactionFeeMetricObject = metricObjects.values.find((metric) => { - return ( - metric.labels.mode === constants.EXECUTION_MODE.TRANSACTION && - metric.metricName === metricHistogramCostSumTitle - ); - }); - - expect(txRecordFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); - expect(txRecordFeeMetricObject?.labels.caller).to.eq(mockedCallerName); - expect(txRecordFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(txRecordFeeMetricObject?.value).to.eq(expectedTxRecordFee); - - expect(transactionFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); - expect(transactionFeeMetricObject?.labels.caller).to.eq(mockedCallerName); - expect(transactionFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(transactionFeeMetricObject?.value).to.eq(mockedTxFee); - - // validate gas metric - // @ts-ignore - const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( - (metric) => metric.metricName === metricHistogramGasFeeSumTitle, - )!; - - expect(gasMetricObject.metricName).to.eq(metricHistogramGasFeeSumTitle); - expect(gasMetricObject.labels.caller).to.eq(mockedCallerName); - expect(gasMetricObject.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(gasMetricObject.value).to.eq( - mockedConsensusNodeTransactionRecord.contractFunctionResult?.gasUsed.toNumber(), - ); }); - it('Should listen to EXECUTE_TRANSACTION event to kick off captureTransactionMetrics()', async () => { - process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE = 'true'; - const mockedExchangeRateInCents = 12; - const expectedTxRecordFee = calculateTxRecordChargeAmount(mockedExchangeRateInCents); - - const transactionRecordStub = sinon - .stub(TransactionRecordQuery.prototype, 'execute') - .resolves(mockedConsensusNodeTransactionRecord); - - const originalBudget = hbarLimiter.getRemainingBudget(); - - // emitting an EXECUTE_TRANSACTION event to kick off capturing metrics process asynchronously - eventEmitter.emit(constants.EVENTS.EXECUTE_TRANSACTION, mockedExecuteTransactionEventPayload); - - // small wait for hbar rate limiter to settle - await new Promise((r) => setTimeout(r, 100)); - - expect(transactionRecordStub.called).to.be.true; - - // validate hbarLimiter - // note: since the query is made to consensus node, the total charged amount = txFee + txRecordFee - const updatedBudget = hbarLimiter.getRemainingBudget(); - - expect(originalBudget - updatedBudget).to.eq(mockedTxFee + expectedTxRecordFee); - - // validate cost metric - // @ts-ignore - const metricObjects = await metricService['consensusNodeClientHistogramCost'].get(); - const txRecordFeeMetricObject = metricObjects.values.find((metric) => { - return ( - metric.labels.mode === constants.EXECUTION_MODE.RECORD && metric.metricName === metricHistogramCostSumTitle + withOverriddenEnvsInMochaTest({ GET_RECORD_DEFAULT_TO_CONSENSUS_NODE: 'true' }, () => { + it('Should execute captureTransactionMetrics() by retrieving transaction record from CONSENSUS NODE client', async () => { + const mockedExchangeRateInCents = 12; + const expectedTxRecordFee = calculateTxRecordChargeAmount(mockedExchangeRateInCents); + + const transactionRecordStub = sinon + .stub(TransactionRecordQuery.prototype, 'execute') + .resolves(mockedConsensusNodeTransactionRecord); + + const originalBudget = hbarLimiter.getRemainingBudget(); + + await metricService.captureTransactionMetrics(mockedExecuteTransactionEventPayload); + expect(transactionRecordStub.called).to.be.true; + + // validate hbarLimiter + // note: since the query is made to consensus node, the total charged amount = txFee + txRecordFee + const updatedBudget = hbarLimiter.getRemainingBudget(); + expect(originalBudget - updatedBudget).to.eq(mockedTxFee + expectedTxRecordFee); + + // validate cost metric + // @ts-ignore + const metricObjects = await metricService['consensusNodeClientHistogramCost'].get(); + const txRecordFeeMetricObject = metricObjects.values.find((metric) => { + return ( + metric.labels.mode === constants.EXECUTION_MODE.RECORD && metric.metricName === metricHistogramCostSumTitle + ); + }); + const transactionFeeMetricObject = metricObjects.values.find((metric) => { + return ( + metric.labels.mode === constants.EXECUTION_MODE.TRANSACTION && + metric.metricName === metricHistogramCostSumTitle + ); + }); + + expect(txRecordFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); + expect(txRecordFeeMetricObject?.labels.caller).to.eq(mockedCallerName); + expect(txRecordFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(txRecordFeeMetricObject?.value).to.eq(expectedTxRecordFee); + + expect(transactionFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); + expect(transactionFeeMetricObject?.labels.caller).to.eq(mockedCallerName); + expect(transactionFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(transactionFeeMetricObject?.value).to.eq(mockedTxFee); + + // validate gas metric + // @ts-ignore + const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( + (metric) => metric.metricName === metricHistogramGasFeeSumTitle, + )!; + + expect(gasMetricObject.metricName).to.eq(metricHistogramGasFeeSumTitle); + expect(gasMetricObject.labels.caller).to.eq(mockedCallerName); + expect(gasMetricObject.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(gasMetricObject.value).to.eq( + mockedConsensusNodeTransactionRecord.contractFunctionResult?.gasUsed.toNumber(), ); }); - const transactionFeeMetricObject = metricObjects.values.find((metric) => { - return ( - metric.labels.mode === constants.EXECUTION_MODE.TRANSACTION && - metric.metricName === metricHistogramCostSumTitle + }); + + withOverriddenEnvsInMochaTest({ GET_RECORD_DEFAULT_TO_CONSENSUS_NODE: 'true' }, () => { + it('Should listen to EXECUTE_TRANSACTION event to kick off captureTransactionMetrics()', async () => { + const mockedExchangeRateInCents = 12; + const expectedTxRecordFee = calculateTxRecordChargeAmount(mockedExchangeRateInCents); + + const transactionRecordStub = sinon + .stub(TransactionRecordQuery.prototype, 'execute') + .resolves(mockedConsensusNodeTransactionRecord); + + const originalBudget = hbarLimiter.getRemainingBudget(); + + // emitting an EXECUTE_TRANSACTION event to kick off capturing metrics process asynchronously + eventEmitter.emit(constants.EVENTS.EXECUTE_TRANSACTION, mockedExecuteTransactionEventPayload); + + // small wait for hbar rate limiter to settle + await new Promise((r) => setTimeout(r, 100)); + + expect(transactionRecordStub.called).to.be.true; + + // validate hbarLimiter + // note: since the query is made to consensus node, the total charged amount = txFee + txRecordFee + const updatedBudget = hbarLimiter.getRemainingBudget(); + + expect(originalBudget - updatedBudget).to.eq(mockedTxFee + expectedTxRecordFee); + + // validate cost metric + // @ts-ignore + const metricObjects = await metricService['consensusNodeClientHistogramCost'].get(); + const txRecordFeeMetricObject = metricObjects.values.find((metric) => { + return ( + metric.labels.mode === constants.EXECUTION_MODE.RECORD && metric.metricName === metricHistogramCostSumTitle + ); + }); + const transactionFeeMetricObject = metricObjects.values.find((metric) => { + return ( + metric.labels.mode === constants.EXECUTION_MODE.TRANSACTION && + metric.metricName === metricHistogramCostSumTitle + ); + }); + + expect(txRecordFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); + expect(txRecordFeeMetricObject?.labels.caller).to.eq(mockedCallerName); + expect(txRecordFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(txRecordFeeMetricObject?.value).to.eq(expectedTxRecordFee); + + expect(transactionFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); + expect(transactionFeeMetricObject?.labels.caller).to.eq(mockedCallerName); + expect(transactionFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(transactionFeeMetricObject?.value).to.eq(mockedTxFee); + + // validate gas metric + // @ts-ignore + const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( + (metric) => metric.metricName === metricHistogramGasFeeSumTitle, + )!; + + expect(gasMetricObject.metricName).to.eq(metricHistogramGasFeeSumTitle); + expect(gasMetricObject.labels.caller).to.eq(mockedCallerName); + expect(gasMetricObject.labels.interactingEntity).to.eq(mockedInteractingEntity); + expect(gasMetricObject.value).to.eq( + mockedConsensusNodeTransactionRecord.contractFunctionResult?.gasUsed.toNumber(), ); }); - - expect(txRecordFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); - expect(txRecordFeeMetricObject?.labels.caller).to.eq(mockedCallerName); - expect(txRecordFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(txRecordFeeMetricObject?.value).to.eq(expectedTxRecordFee); - - expect(transactionFeeMetricObject?.metricName).to.eq(metricHistogramCostSumTitle); - expect(transactionFeeMetricObject?.labels.caller).to.eq(mockedCallerName); - expect(transactionFeeMetricObject?.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(transactionFeeMetricObject?.value).to.eq(mockedTxFee); - - // validate gas metric - // @ts-ignore - const gasMetricObject = (await metricService['consensusNodeClientHistogramGasFee'].get()).values.find( - (metric) => metric.metricName === metricHistogramGasFeeSumTitle, - )!; - - expect(gasMetricObject.metricName).to.eq(metricHistogramGasFeeSumTitle); - expect(gasMetricObject.labels.caller).to.eq(mockedCallerName); - expect(gasMetricObject.labels.interactingEntity).to.eq(mockedInteractingEntity); - expect(gasMetricObject.value).to.eq( - mockedConsensusNodeTransactionRecord.contractFunctionResult?.gasUsed.toNumber(), - ); }); }); diff --git a/packages/relay/tests/lib/subscriptionController.spec.ts b/packages/relay/tests/lib/subscriptionController.spec.ts index 272de5e716..b9fc9cf8f7 100644 --- a/packages/relay/tests/lib/subscriptionController.spec.ts +++ b/packages/relay/tests/lib/subscriptionController.spec.ts @@ -29,6 +29,7 @@ import dotenv from 'dotenv'; import path from 'path'; import { Registry } from 'prom-client'; import ConnectionLimiter from '@hashgraph/json-rpc-ws-server/src/metrics/connectionLimiter'; +import { overrideEnvsInMochaDescribe } from '../helpers'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -265,24 +266,16 @@ describe('subscriptionController', async function () { }); describe('With WS_SAME_SUB_FOR_SAME_EVENT == `false`', async function () { - let originalEnv; - let originalSubscriptionController; + let subscriptionController: SubscriptionController; - before(() => { - originalEnv = process.env.WS_SAME_SUB_FOR_SAME_EVENT; - originalSubscriptionController = subscriptionController; + overrideEnvsInMochaDescribe({ WS_SAME_SUB_FOR_SAME_EVENT: 'false' }); - process.env.WS_SAME_SUB_FOR_SAME_EVENT = 'false'; + before(() => { const registry = new Registry(); poller = new Poller(ethImpl, logger, registry); subscriptionController = new SubscriptionController(poller, logger, registry); }); - after(() => { - process.env.WS_SAME_SUB_FOR_SAME_EVENT = originalEnv; - subscriptionController = originalSubscriptionController; - }); - it('Subscribing to the same event and filters should return different subscription id', async function () { const connectionId = '7'; const wsConnection = new MockWsConnection(connectionId); diff --git a/packages/relay/tests/lib/utils.spec.ts b/packages/relay/tests/lib/utils.spec.ts index 19bf1a84fe..2807de74ba 100644 --- a/packages/relay/tests/lib/utils.spec.ts +++ b/packages/relay/tests/lib/utils.spec.ts @@ -21,13 +21,10 @@ import { expect } from 'chai'; import { Utils } from '../../src/utils'; import constants from '../../src/lib/constants'; +import { overrideEnvsInMochaDescribe } from '../helpers'; describe('Utils', () => { describe('addPercentageBufferToGasPrice', () => { - afterEach(() => { - process.env.GAS_PRICE_PERCENTAGE_BUFFER = '0'; - }); - const TW_COEF = constants.TINYBAR_TO_WEIBAR_COEF; const TEST_CASES = [ { testName: 'zero input', buffer: '0', input: 0, output: 0 }, @@ -38,10 +35,18 @@ describe('Utils', () => { { testName: 'negative buffer -6%', buffer: '-6', input: 100 * TW_COEF, output: 94 * TW_COEF }, { testName: 'negative buffer -12.58%', buffer: '-12.58', input: 136 * TW_COEF, output: 119 * TW_COEF }, ]; + const gasFormat = Intl.NumberFormat('en-US', { + notation: 'compact', + maximumFractionDigits: 2, + }); + for (let i in TEST_CASES) { - it(TEST_CASES[i].testName, () => { - process.env.GAS_PRICE_PERCENTAGE_BUFFER = TEST_CASES[i].buffer; - expect(Utils.addPercentageBufferToGasPrice(TEST_CASES[i].input)).to.equal(TEST_CASES[i].output); + describe(`${TEST_CASES[i].testName}, ${gasFormat.format(TEST_CASES[i].input)} gas`, () => { + overrideEnvsInMochaDescribe({ GAS_PRICE_PERCENTAGE_BUFFER: TEST_CASES[i].buffer }); + + it(`should return ${gasFormat.format(TEST_CASES[i].output)} gas`, () => { + expect(Utils.addPercentageBufferToGasPrice(TEST_CASES[i].input)).to.equal(TEST_CASES[i].output); + }); }); } }); diff --git a/packages/relay/tests/lib/web3.spec.ts b/packages/relay/tests/lib/web3.spec.ts index fbef5a873d..9ca69715c5 100644 --- a/packages/relay/tests/lib/web3.spec.ts +++ b/packages/relay/tests/lib/web3.spec.ts @@ -22,8 +22,9 @@ import path from 'path'; import dotenv from 'dotenv'; import { expect } from 'chai'; import { Registry } from 'prom-client'; -import { RelayImpl } from '../../src/lib/relay'; import pino from 'pino'; +import { RelayImpl } from '../../src'; +import { withOverriddenEnvsInMochaTest } from '../helpers'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); @@ -32,17 +33,17 @@ const logger = pino(); const Relay = new RelayImpl(logger, new Registry()); describe('Web3', function () { - it('should execute "web3_clientVersion"', async function () { - process.env.npm_package_version = '1.0.0'; - const clientVersion = Relay.web3().clientVersion(); - - expect(clientVersion).to.be.equal('relay/' + process.env.npm_package_version); + withOverriddenEnvsInMochaTest({ npm_package_version: '1.0.0' }, () => { + it('should return "relay/1.0.0"', async function () { + const clientVersion = Relay.web3().clientVersion(); + expect(clientVersion).to.be.equal('relay/' + process.env.npm_package_version); + }); }); - it('should return "relay/" when npm_package_version is not set', () => { - delete process.env.npm_package_version; - const version = Relay.web3().clientVersion(); - - expect(version).to.equal('relay/'); + withOverriddenEnvsInMochaTest({ npm_package_version: undefined }, () => { + it('should return "relay/"', () => { + const version = Relay.web3().clientVersion(); + expect(version).to.equal('relay/'); + }); }); }); diff --git a/packages/server/tests/acceptance/cacheService.spec.ts b/packages/server/tests/acceptance/cacheService.spec.ts index d6beed0ff7..7d4dcdefdb 100644 --- a/packages/server/tests/acceptance/cacheService.spec.ts +++ b/packages/server/tests/acceptance/cacheService.spec.ts @@ -22,6 +22,7 @@ import { expect } from 'chai'; import { CacheService } from '../../../../packages/relay/src/lib/services/cacheService/cacheService'; import { Registry } from 'prom-client'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import { overrideEnvsInMochaDescribe, withOverriddenEnvsInMochaTest } from '../../../relay/tests/helpers'; const registry = new Registry(); @@ -64,11 +65,11 @@ describe('@cache-service Acceptance Tests for shared cache', function () { }); it('Correctly sets TTL time', async () => { - const ttl = 1000; + const ttl = 200; const dataLabel = `${DATA_LABEL_PREFIX}2`; await cacheService.set(dataLabel, DATA, CALLING_METHOD, requestDetails, ttl); - await new Promise((r) => setTimeout(r, 200)); + await new Promise((r) => setTimeout(r, 100)); const cache = await cacheService.getAsync(dataLabel, CALLING_METHOD, requestDetails); expect(cache).to.deep.eq(DATA, 'data is stored with TTL'); @@ -82,20 +83,19 @@ describe('@cache-service Acceptance Tests for shared cache', function () { expect(deletedCacheFromService).to.eq(null, 'getAsync method cannot read expired cache'); }); - it('Fallsback to local cache for REDIS_ENABLED !== true', async () => { - const dataLabel = `${DATA_LABEL_PREFIX}3`; - - process.env.REDIS_ENABLED = 'false'; - const serviceWithDisabledRedis = new CacheService(global.logger, registry); - await new Promise((r) => setTimeout(r, 1000)); - expect(serviceWithDisabledRedis.isRedisEnabled()).to.eq(false, 'redis is disabled'); - await serviceWithDisabledRedis.set(dataLabel, DATA, CALLING_METHOD, requestDetails); - await new Promise((r) => setTimeout(r, 200)); + withOverriddenEnvsInMochaTest({ REDIS_ENABLED: 'false' }, () => { + it('Falls back to local cache for REDIS_ENABLED !== true', async () => { + const dataLabel = `${DATA_LABEL_PREFIX}3`; - const dataInLRU = await serviceWithDisabledRedis.getAsync(dataLabel, CALLING_METHOD, requestDetails); - expect(dataInLRU).to.deep.eq(DATA, 'data is stored in local cache'); + const serviceWithDisabledRedis = new CacheService(global.logger, registry); + await new Promise((r) => setTimeout(r, 1000)); + expect(serviceWithDisabledRedis.isRedisEnabled()).to.eq(false, 'redis is disabled'); + await serviceWithDisabledRedis.set(dataLabel, DATA, CALLING_METHOD, requestDetails); + await new Promise((r) => setTimeout(r, 200)); - process.env.REDIS_ENABLED = 'true'; + const dataInLRU = await serviceWithDisabledRedis.getAsync(dataLabel, CALLING_METHOD, requestDetails); + expect(dataInLRU).to.deep.eq(DATA, 'data is stored in local cache'); + }); }); it('Cache set by one instance can be accessed by another', async () => { @@ -111,13 +111,11 @@ describe('@cache-service Acceptance Tests for shared cache', function () { describe('fallback to local cache in case of Redis error', async () => { const dataLabel = `${DATA_LABEL_PREFIX}_redis_error`; - let currentRedisEnabledEnv; let cacheService: CacheService; - before(async () => { - currentRedisEnabledEnv = process.env.REDIS_ENABLED; + overrideEnvsInMochaDescribe({ REDIS_ENABLED: 'true' }); - process.env.REDIS_ENABLED = 'true'; + before(async () => { cacheService = new CacheService(global.logger, registry); // disconnect redis client to simulate Redis error @@ -125,10 +123,6 @@ describe('@cache-service Acceptance Tests for shared cache', function () { await new Promise((r) => setTimeout(r, 1000)); }); - after(async () => { - process.env.REDIS_ENABLED = currentRedisEnabledEnv; - }); - it('test getAsync operation', async () => { await cacheService.set(dataLabel, DATA, CALLING_METHOD, requestDetails); await new Promise((r) => setTimeout(r, 200)); diff --git a/packages/server/tests/acceptance/hbarLimiter.spec.ts b/packages/server/tests/acceptance/hbarLimiter.spec.ts index ba8e4f0272..ce2e88c126 100644 --- a/packages/server/tests/acceptance/hbarLimiter.spec.ts +++ b/packages/server/tests/acceptance/hbarLimiter.spec.ts @@ -29,7 +29,11 @@ import { Utils } from '../helpers/utils'; import Assertions from '../helpers/assertions'; import testConstants from '../helpers/constants'; import { AliasAccount } from '../types/AliasAccount'; -import { estimateFileTransactionsFee } from '@hashgraph/json-rpc-relay/tests/helpers'; +import { + estimateFileTransactionsFee, + overrideEnvsInMochaDescribe, + withOverriddenEnvsInMochaTest, +} from '@hashgraph/json-rpc-relay/tests/helpers'; // Contracts used in tests import parentContractJson from '../contracts/Parent.json'; @@ -37,7 +41,7 @@ import EstimateGasContract from '../contracts/EstimateGasContract.json'; import largeContractJson from '../contracts/hbarLimiterContracts/largeSizeContract.json'; import mediumSizeContract from '../contracts/hbarLimiterContracts/mediumSizeContract.json'; import fs from 'fs'; -import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; +import { ITransfer, RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import MirrorClient from '../clients/mirrorClient'; import RelayClient from '../clients/relayClient'; import { Logger } from 'pino'; @@ -86,7 +90,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { expect(remainingHbarsAfter).to.be.approximately(remainingHbarsBefore - expectedCost, delta); }; - const sumAccountTransfers = (transfers: any, account?: string) => { + const sumAccountTransfers = (transfers: ITransfer[], account?: string) => { return Math.abs( transfers .filter((transfer) => transfer.account === account) @@ -94,7 +98,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { ); }; - const getExpectedCostOfFileCreateTx = async (requestId: string) => { + const getExpectedCostOfFileCreateTx = async () => { const fileCreateTx = ( await mirrorNode.get( `/transactions?transactiontype=FILECREATE&order=desc&account.id=${operatorAccount}&limit=1`, @@ -107,14 +111,14 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { return { fileCreateTxFee, fileCreateTimestamp }; }; - const getExpectedCostOfFileAppendTx = async (requestId: string, timeStamp: string, txData: string) => { + const getExpectedCostOfFileAppendTx = async (timeStamp: string, txData: string) => { const fileAppendTxs = ( await mirrorNode.get( `/transactions?order=desc&transactiontype=FILEAPPEND&account.id=${operatorAccount}×tamp=gt:${timeStamp}`, requestId, ) ).transactions; - const fileAppendTxFee = fileAppendTxs.reduce((total, data) => { + const fileAppendTxFee = fileAppendTxs.reduce((total: number, data: { transfers: ITransfer[] }) => { const sum = sumAccountTransfers(data.transfers, operatorAccount); return total + sum; }, 0); @@ -126,7 +130,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { return fileAppendTxFee; }; - const getExpectedCostOfLastLargeTx = async (txData: string, requestDetails: RequestDetails) => { + const getExpectedCostOfLastLargeTx = async (txData: string) => { const ethereumTransaction = ( await mirrorNode.get( `/transactions?transactiontype=ETHEREUMTRANSACTION&order=desc&account.id=${operatorAccount}&limit=1`, @@ -134,8 +138,8 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { ) ).transactions[0]; const ethereumTxFee = sumAccountTransfers(ethereumTransaction.transfers, operatorAccount); - const { fileCreateTxFee, fileCreateTimestamp } = await getExpectedCostOfFileCreateTx(requestDetails); - const fileAppendTxFee = await getExpectedCostOfFileAppendTx(requestDetails, fileCreateTimestamp, txData); + const { fileCreateTxFee, fileCreateTimestamp } = await getExpectedCostOfFileCreateTx(); + const fileAppendTxFee = await getExpectedCostOfFileAppendTx(fileCreateTimestamp, txData); const fileDeleteTx = ( await mirrorNode.get( @@ -201,9 +205,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { }); describe('Remaining HBAR Limit', () => { - before(() => { - process.env.GET_RECORD_DEFAULT_TO_CONSENSUS_NODE = 'true'; - }); + overrideEnvsInMochaDescribe({ GET_RECORD_DEFAULT_TO_CONSENSUS_NODE: 'true' }); it('should execute "eth_sendRawTransaction" without triggering HBAR rate limit exceeded', async function () { const parentContract = await deployContract(parentContractJson, accounts[0].wallet); @@ -240,10 +242,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { const contract = await deployContract(largeContractJson, accounts[0].wallet); const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - const expectedCost = await getExpectedCostOfLastLargeTx( - contract.deploymentTransaction()!.data, - requestDetails, - ); + const expectedCost = await getExpectedCostOfLastLargeTx(contract.deploymentTransaction()!.data); verifyRemainingLimit(expectedCost, remainingHbarsBefore, remainingHbarsAfter); }); @@ -268,10 +267,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { const contract = await deployContract(mediumSizeContract, accounts[0].wallet); const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - const expectedCost = await getExpectedCostOfLastLargeTx( - contract.deploymentTransaction()!.data, - requestDetails, - ); + const expectedCost = await getExpectedCostOfLastLargeTx(contract.deploymentTransaction()!.data); verifyRemainingLimit(expectedCost, remainingHbarsBefore, remainingHbarsAfter); }); @@ -280,9 +276,8 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { let exchangeRateResult = (await mirrorNode.get(`/network/exchangerate`, requestId)).current_rate; const exchangeRateInCents = exchangeRateResult.cent_equivalent / exchangeRateResult.hbar_equivalent; - const { fileCreateTxFee, fileCreateTimestamp } = await getExpectedCostOfFileCreateTx(requestId); + const { fileCreateTxFee, fileCreateTimestamp } = await getExpectedCostOfFileCreateTx(); const fileAppendTxFee = await getExpectedCostOfFileAppendTx( - requestId, fileCreateTimestamp, contract.deploymentTransaction()!.data, ); @@ -303,15 +298,6 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { }); describe('Rate Limit', () => { - let hbarRateLimitPreemptiveCheck: string | undefined; - - beforeEach(() => { - hbarRateLimitPreemptiveCheck = process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK; - }); - afterEach(() => { - process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK = hbarRateLimitPreemptiveCheck; - }); - it('HBAR limiter is updated within acceptable tolerance range in relation to actual spent amount by the relay operator', async function () { const TOLERANCE = 0.02; const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); @@ -325,10 +311,7 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { const amountPaidByOperator = operatorBalanceBefore - operatorBalanceAfter; - const totalOperatorFees = await getExpectedCostOfLastLargeTx( - largeContract.deploymentTransaction()!.data, - requestDetails, - ); + const totalOperatorFees = await getExpectedCostOfLastLargeTx(largeContract.deploymentTransaction()!.data); const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); const hbarLimitReducedAmount = remainingHbarsBefore - remainingHbarsAfter; @@ -337,45 +320,45 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { Assertions.expectWithinTolerance(amountPaidByOperator, totalOperatorFees, TOLERANCE); }); - it('Should preemptively check the rate limit before submitting EthereumTransaction', async function () { - process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK = 'true'; - - try { - for (let i = 0; i < 50; i++) { - const largeContract = await Utils.deployContract( - largeContractJson.abi, - largeContractJson.bytecode, - accounts[0].wallet, - ); - await largeContract.waitForDeployment(); + withOverriddenEnvsInMochaTest({ HBAR_RATE_LIMIT_PREEMPTIVE_CHECK: 'true' }, () => { + it('Should preemptively check the rate limit before submitting EthereumTransaction', async function () { + try { + for (let i = 0; i < 50; i++) { + const largeContract = await Utils.deployContract( + largeContractJson.abi, + largeContractJson.bytecode, + accounts[0].wallet, + ); + await largeContract.waitForDeployment(); + } + expect.fail('Expected an error, but no error was thrown from the hbar rate limiter'); + } catch (e: any) { + expect(e.message).to.contain(predefined.HBAR_RATE_LIMIT_PREEMPTIVE_EXCEEDED.message); } - expect.fail('Expected an error, but no error was thrown from the hbar rate limiter'); - } catch (e) { - expect(e.message).to.contain(predefined.HBAR_RATE_LIMIT_PREEMPTIVE_EXCEEDED.message); - } + }); }); - it('multiple deployments of large contracts should eventually exhaust the remaining hbar limit', async function () { - process.env.HBAR_RATE_LIMIT_PREEMPTIVE_CHECK = 'false'; - - const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - const lastRemainingHbars = remainingHbarsBefore; - expect(remainingHbarsBefore).to.be.gt(0); - try { - for (let i = 0; i < 50; i++) { - const contract = await deployContract(largeContractJson, accounts[0].wallet); - await contract.waitForDeployment(); - const remainingHbars = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - // FIXME this check is very flaky, ideally it should be uncommented - // expect(remainingHbars).to.be.lt(lastRemainingHbars); + withOverriddenEnvsInMochaTest({ HBAR_RATE_LIMIT_PREEMPTIVE_CHECK: 'false' }, () => { + it('multiple deployments of large contracts should eventually exhaust the remaining hbar limit', async function () { + const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); + const lastRemainingHbars = remainingHbarsBefore; + expect(remainingHbarsBefore).to.be.gt(0); + try { + for (let i = 0; i < 50; i++) { + const contract = await deployContract(largeContractJson, accounts[0].wallet); + await contract.waitForDeployment(); + const remainingHbars = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); + // FIXME this check is very flaky, ideally it should be uncommented + // expect(remainingHbars).to.be.lt(lastRemainingHbars); + } + expect.fail(`Expected an error but nothing was thrown`); + } catch (e: any) { + expect(e.message).to.contain(predefined.HBAR_RATE_LIMIT_EXCEEDED.message); } - expect.fail(`Expected an error but nothing was thrown`); - } catch (e: any) { - expect(e.message).to.contain(predefined.HBAR_RATE_LIMIT_EXCEEDED.message); - } - const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); - expect(remainingHbarsAfter).to.be.lte(0); + const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); + expect(remainingHbarsAfter).to.be.lte(0); + }); }); }); }); diff --git a/packages/server/tests/acceptance/rpc_batch3.spec.ts b/packages/server/tests/acceptance/rpc_batch3.spec.ts index 5bff80a411..6c0dbfa511 100644 --- a/packages/server/tests/acceptance/rpc_batch3.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch3.spec.ts @@ -51,6 +51,7 @@ import HRC719ContractJson from '../contracts/HRC719Contract.json'; import { EthImpl } from '@hashgraph/json-rpc-relay/src/lib/eth'; import { predefined } from '@hashgraph/json-rpc-relay'; import { TYPES } from '../../src/validator'; +import { overrideEnvsInMochaDescribe } from '../../../relay/tests/helpers'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import RelayClient from '../clients/relayClient'; import ServicesClient from '../clients/servicesClient'; @@ -2023,16 +2024,7 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () { }); describe('Batch Request Test Suite BATCH_REQUESTS_ENABLED = true', async function () { - let PREV_BATCH_REQUESTS_ENABLED: string | undefined; - - before(async () => { - PREV_BATCH_REQUESTS_ENABLED = process.env.BATCH_REQUESTS_ENABLED; - process.env.BATCH_REQUESTS_ENABLED = 'true'; - }); - - after(async () => { - process.env.BATCH_REQUESTS_ENABLED = PREV_BATCH_REQUESTS_ENABLED; - }); + overrideEnvsInMochaDescribe({ BATCH_REQUESTS_ENABLED: 'true' }); it('Should return a batch of requests', async function () { const testAccount = await Utils.createAliasAccount(mirrorNode, accounts[0], requestId); diff --git a/packages/server/tests/integration/koaJsonRpc/utils.spec.ts b/packages/server/tests/integration/koaJsonRpc/utils.spec.ts index 4c8de8f673..beadfffc53 100644 --- a/packages/server/tests/integration/koaJsonRpc/utils.spec.ts +++ b/packages/server/tests/integration/koaJsonRpc/utils.spec.ts @@ -23,6 +23,7 @@ import sinon from 'sinon'; import { Server } from 'http'; import * as utils from '../../../src/koaJsonRpc/lib/utils'; import constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; +import { withOverriddenEnvsInMochaTest } from '../../../../relay/tests/helpers'; describe('utils.ts', () => { describe('hasOwnProperty', () => { @@ -51,96 +52,98 @@ describe('utils.ts', () => { sinon.restore(); }); - it('should set server timeout from environment variable', () => { - process.env.SERVER_REQUEST_TIMEOUT_MS = '30000'; - - utils.setServerTimeout(server); - expect(spy.calledWith(30000)).to.eq(true); + withOverriddenEnvsInMochaTest({ SERVER_REQUEST_TIMEOUT_MS: '30000' }, () => { + it('should set server timeout from environment variable', () => { + utils.setServerTimeout(server); + expect(spy.calledWith(30000)).to.eq(true); + }); }); - it('should set server timeout to default value when environment variable is not set', () => { - delete process.env.SERVER_REQUEST_TIMEOUT_MS; - - utils.setServerTimeout(server); - expect(spy.calledWith(60000)).to.eq(true); + withOverriddenEnvsInMochaTest({ SERVER_REQUEST_TIMEOUT_MS: undefined }, () => { + it('should set server timeout to default value when environment variable is not set', () => { + utils.setServerTimeout(server); + expect(spy.calledWith(60000)).to.eq(true); + }); }); }); describe('getBatchRequestsMaxSize', () => { - it('should return the batch request max size from environment variable', () => { - process.env.BATCH_REQUESTS_MAX_SIZE = '150'; - - const result = utils.getBatchRequestsMaxSize(); - expect(result).to.equal(150); + withOverriddenEnvsInMochaTest({ BATCH_REQUESTS_MAX_SIZE: '150' }, () => { + it('should return the batch request max size from environment variable', () => { + const result = utils.getBatchRequestsMaxSize(); + expect(result).to.equal(150); + }); }); - it('should return default batch request max size when environment variable is not set', () => { - delete process.env.BATCH_REQUESTS_MAX_SIZE; - - const result = utils.getBatchRequestsMaxSize(); - expect(result).to.equal(100); + withOverriddenEnvsInMochaTest({ BATCH_REQUESTS_MAX_SIZE: undefined }, () => { + it('should return default batch request max size when environment variable is not set', () => { + const result = utils.getBatchRequestsMaxSize(); + expect(result).to.equal(100); + }); }); }); describe('getLimitDuration', () => { - it('should return the limit duration from environment variable', () => { - process.env.LIMIT_DURATION = '500'; - - const result = utils.getLimitDuration(); - expect(result).to.equal(500); + withOverriddenEnvsInMochaTest({ LIMIT_DURATION: '500' }, () => { + it('should return the limit duration from environment variable', () => { + const result = utils.getLimitDuration(); + expect(result).to.equal(500); + }); }); - it('should return the default limit duration when environment variable is not set', () => { - delete process.env.LIMIT_DURATION; - - const result = utils.getLimitDuration(); - expect(result).to.equal(constants.DEFAULT_RATE_LIMIT.DURATION); + withOverriddenEnvsInMochaTest({ LIMIT_DURATION: undefined }, () => { + it('should return the default limit duration when environment variable is not set', () => { + const result = utils.getLimitDuration(); + expect(result).to.equal(constants.DEFAULT_RATE_LIMIT.DURATION); + }); }); }); describe('getDefaultRateLimit', () => { - it('should return the default rate limit from environment variable', () => { - process.env.DEFAULT_RATE_LIMIT = '250'; - - const result = utils.getDefaultRateLimit(); - expect(result).to.equal(250); + withOverriddenEnvsInMochaTest({ DEFAULT_RATE_LIMIT: '250' }, () => { + it('should return the default rate limit from environment variable', () => { + const result = utils.getDefaultRateLimit(); + expect(result).to.equal(250); + }); }); - it('should return the default rate limit when environment variable is not set', () => { - delete process.env.DEFAULT_RATE_LIMIT; - - const result = utils.getDefaultRateLimit(); - expect(result).to.equal(200); + withOverriddenEnvsInMochaTest({ DEFAULT_RATE_LIMIT: undefined }, () => { + it('should return the default rate limit when environment variable is not set', () => { + const result = utils.getDefaultRateLimit(); + expect(result).to.equal(200); + }); }); }); describe('getRequestIdIsOptional', () => { - it('should return true when REQUEST_ID_IS_OPTIONAL is set to true', () => { - process.env.REQUEST_ID_IS_OPTIONAL = 'true'; - - const result = utils.getRequestIdIsOptional(); - expect(result).to.be.true; + withOverriddenEnvsInMochaTest({ REQUEST_ID_IS_OPTIONAL: 'true' }, () => { + it('should return true when REQUEST_ID_IS_OPTIONAL is set to true', () => { + const result = utils.getRequestIdIsOptional(); + expect(result).to.be.true; + }); }); - it('should return false when REQUEST_ID_IS_OPTIONAL is not set to true', () => { - process.env.REQUEST_ID_IS_OPTIONAL = 'false'; - - const result = utils.getRequestIdIsOptional(); - expect(result).to.be.false; + withOverriddenEnvsInMochaTest({ REQUEST_ID_IS_OPTIONAL: 'false' }, () => { + it('should return false when REQUEST_ID_IS_OPTIONAL is not set to true', () => { + const result = utils.getRequestIdIsOptional(); + expect(result).to.be.false; + }); }); }); describe('getBatchRequestsEnabled', () => { - it('should return true when BATCH_REQUESTS_ENABLED is set to true', () => { - process.env.BATCH_REQUESTS_ENABLED = 'true'; - const result = utils.getBatchRequestsEnabled(); - expect(result).to.be.true; - }); - - it('should return false when BATCH_REQUESTS_ENABLED is not set to true', () => { - process.env.BATCH_REQUESTS_ENABLED = 'false'; - const result = utils.getBatchRequestsEnabled(); - expect(result).to.be.false; + withOverriddenEnvsInMochaTest({ BATCH_REQUESTS_ENABLED: 'true' }, () => { + it('should return true when BATCH_REQUESTS_ENABLED is set to true', () => { + const result = utils.getBatchRequestsEnabled(); + expect(result).to.be.true; + }); + }); + + withOverriddenEnvsInMochaTest({ BATCH_REQUESTS_ENABLED: 'false' }, () => { + it('should return false when BATCH_REQUESTS_ENABLED is not set to true', () => { + const result = utils.getBatchRequestsEnabled(); + expect(result).to.be.false; + }); }); }); }); diff --git a/packages/server/tests/integration/rateLimiter.spec.ts b/packages/server/tests/integration/rateLimiter.spec.ts index 8ef5c6425f..88e3b95cbc 100644 --- a/packages/server/tests/integration/rateLimiter.spec.ts +++ b/packages/server/tests/integration/rateLimiter.spec.ts @@ -23,6 +23,7 @@ import sinon from 'sinon'; import { Registry } from 'prom-client'; import pino, { Logger } from 'pino'; import RateLimit from '../../src/rateLimit'; +import { overrideEnvsInMochaDescribe, withOverriddenEnvsInMochaTest } from '../../../relay/tests/helpers'; describe('RateLimit', () => { let logger: Logger; @@ -35,9 +36,9 @@ describe('RateLimit', () => { registry = new Registry(); }); + overrideEnvsInMochaDescribe({ RATE_LIMIT_DISABLED: 'false' }); + beforeEach(() => { - process.env.RATE_LIMIT_DISABLED = 'false'; - // Create a new instance of RateLimit rateLimit = new RateLimit(logger, registry, duration); }); @@ -46,12 +47,13 @@ describe('RateLimit', () => { sinon.restore(); }); - it('should not rate limit when RATE_LIMIT_DISABLED is true', () => { - process.env.RATE_LIMIT_DISABLED = 'true'; - rateLimit = new RateLimit(logger, registry, duration); - const shouldLimit = rateLimit.shouldRateLimit('127.0.0.1', 'method1', 10, 'requestId'); + withOverriddenEnvsInMochaTest({ RATE_LIMIT_DISABLED: 'true' }, () => { + it('should not rate limit when RATE_LIMIT_DISABLED is true', () => { + rateLimit = new RateLimit(logger, registry, duration); + const shouldLimit = rateLimit.shouldRateLimit('127.0.0.1', 'method1', 10, 'requestId'); - expect(shouldLimit).to.be.false; + expect(shouldLimit).to.be.false; + }); }); it('should set a new IP and method when first encountered', () => { @@ -116,19 +118,19 @@ describe('RateLimit', () => { expect(counterSpy.calledOnce).to.be.true; }); - it('should prioritize environment variable RATE_LIMIT_DISABLED', () => { - process.env.RATE_LIMIT_DISABLED = 'true'; + withOverriddenEnvsInMochaTest({ RATE_LIMIT_DISABLED: 'true' }, () => { + it('should prioritize environment variable RATE_LIMIT_DISABLED', () => { + const logSpy = sinon.spy(logger, 'warn'); + const counterSpy = sinon.spy(rateLimit['ipRateLimitCounter'], 'inc'); - const logSpy = sinon.spy(logger, 'warn'); - const counterSpy = sinon.spy(rateLimit['ipRateLimitCounter'], 'inc'); + for (let i = 0; i < 10; i++) { + rateLimit.shouldRateLimit('127.0.0.1', 'method1', 10, 'requestId'); + } - for (let i = 0; i < 10; i++) { rateLimit.shouldRateLimit('127.0.0.1', 'method1', 10, 'requestId'); - } - - rateLimit.shouldRateLimit('127.0.0.1', 'method1', 10, 'requestId'); - expect(logSpy.called).to.be.false; - expect(counterSpy.called).to.be.false; + expect(logSpy.called).to.be.false; + expect(counterSpy.called).to.be.false; + }); }); }); diff --git a/packages/server/tests/integration/server.spec.ts b/packages/server/tests/integration/server.spec.ts index 38e4894ff2..875f76ae88 100644 --- a/packages/server/tests/integration/server.spec.ts +++ b/packages/server/tests/integration/server.spec.ts @@ -32,7 +32,14 @@ import RelayCalls from '../../tests/helpers/constants'; import * as Constants from '../../src/validator/constants'; import { Utils } from '../helpers/utils'; import { predefined } from '@hashgraph/json-rpc-relay'; -import { contractAddress1, contractAddress2, contractHash1, contractId1 } from '../../../relay/tests/helpers'; +import { + contractAddress1, + contractAddress2, + contractHash1, + contractId1, + overrideEnvsInMochaDescribe, + withOverriddenEnvsInMochaTest, +} from '../../../relay/tests/helpers'; import { MirrorNodeClient } from '@hashgraph/json-rpc-relay/dist/lib/clients'; dotenv.config({ path: path.resolve(__dirname, './test.env') }); @@ -118,35 +125,35 @@ describe('RPC Server', function () { } }); - xit('supports optionality of request id when configured', async function () { - const app2 = require('../../src/server').default; - const port = `1${process.env.E2E_SERVER_PORT}`; - const testServer2 = app2.listen(port); - const testClient2 = BaseTest.createTestClient(port); + withOverriddenEnvsInMochaTest({ REQUEST_ID_IS_OPTIONAL: 'true' }, async function () { + xit('supports optionality of request id when configured', async function () { + const app2 = require('../../src/server').default; + const port = `1${process.env.E2E_SERVER_PORT}`; + const testServer2 = app2.listen(port); - try { - process.env.REQUEST_ID_IS_OPTIONAL = 'true'; - const response = await testClient2.post('/', { - jsonrpc: '2.0', - method: RelayCalls.ETH_ENDPOINTS.ETH_CHAIN_ID, - params: [null], - }); - - expect(response.status).to.eq(200); - expect(response.statusText).to.eq('OK'); - expect(response, "Default response: Should have 'data' property").to.have.property('data'); - expect(response.data, "Default response: 'data' should have 'id' property").to.have.property('id'); - expect(response.data, "Default response: 'data' should have 'jsonrpc' property").to.have.property('jsonrpc'); - expect(response.data, "Default response: 'data' should have 'result' property").to.have.property('result'); - expect(response.data.id, "Default response: 'data.id' should equal '2'").to.be.equal('2'); - expect(response.data.jsonrpc, "Default response: 'data.jsonrpc' should equal '2.0'").to.be.equal('2.0'); - expect(response.data.result).to.be.equal('0x' + Number(process.env.CHAIN_ID).toString(16)); - } catch (error: any) { - expect(true, `Unexpected error: ${error.message}`).to.eq(false); - } + try { + const testClient2 = BaseTest.createTestClient(port); + const response = await testClient2.post('/', { + jsonrpc: '2.0', + method: RelayCalls.ETH_ENDPOINTS.ETH_CHAIN_ID, + params: [null], + }); - process.env.REQUEST_ID_IS_OPTIONAL = 'false'; - testServer2.close(); + expect(response.status).to.eq(200); + expect(response.statusText).to.eq('OK'); + expect(response, "Default response: Should have 'data' property").to.have.property('data'); + expect(response.data, "Default response: 'data' should have 'id' property").to.have.property('id'); + expect(response.data, "Default response: 'data' should have 'jsonrpc' property").to.have.property('jsonrpc'); + expect(response.data, "Default response: 'data' should have 'result' property").to.have.property('result'); + expect(response.data.id, "Default response: 'data.id' should equal '2'").to.be.equal('2'); + expect(response.data.jsonrpc, "Default response: 'data.jsonrpc' should equal '2.0'").to.be.equal('2.0'); + expect(response.data.result).to.be.equal('0x' + Number(process.env.CHAIN_ID).toString(16)); + } catch (error: any) { + expect(true, `Unexpected error: ${error.message}`).to.eq(false); + } finally { + testServer2.close(); + } + }); }); it('should execute "eth_accounts"', async function () { @@ -473,15 +480,7 @@ describe('RPC Server', function () { }); describe('batchRequest Test Cases', async function () { - const batchRequestEnabledValue = process.env.BATCH_REQUESTS_ENABLED; - - this.beforeAll(function () { - process.env.BATCH_REQUESTS_ENABLED = 'true'; - }); - - this.afterAll(function () { - process.env.BATCH_REQUESTS_ENABLED = batchRequestEnabledValue; - }); + overrideEnvsInMochaDescribe({ BATCH_REQUESTS_ENABLED: 'true' }); function getEthChainIdRequest(id) { return { @@ -647,36 +646,26 @@ describe('RPC Server', function () { } }); - it('should not execute batch request when disabled', async function () { - // disable batch request - process.env.BATCH_REQUESTS_ENABLED = 'false'; - - // do batch request - try { - await testClient.post('/', [getEthChainIdRequest(2), getEthAccountsRequest(3), getEthChainIdRequest(4)]); - Assertions.expectedError(); - } catch (error: any) { - BaseTest.batchDisabledErrorCheck(error.response); - } - - // enable batch request again - process.env.BATCH_REQUESTS_ENABLED = 'true'; + withOverriddenEnvsInMochaTest({ BATCH_REQUESTS_ENABLED: 'false' }, async function () { + it('should not execute batch request when disabled', async function () { + try { + await testClient.post('/', [getEthChainIdRequest(2), getEthAccountsRequest(3), getEthChainIdRequest(4)]); + Assertions.expectedError(); + } catch (error: any) { + BaseTest.batchDisabledErrorCheck(error.response); + } + }); }); - it('batch request be disabled by default', async function () { - // disable batch request - process.env.BATCH_REQUESTS_ENABLED = undefined; - - // do batch request - try { - await testClient.post('/', [getEthChainIdRequest(2), getEthAccountsRequest(3), getEthChainIdRequest(4)]); - Assertions.expectedError(); - } catch (error: any) { - BaseTest.batchDisabledErrorCheck(error.response); - } - - // enable batch requests again - process.env.BATCH_REQUESTS_ENABLED = 'true'; + withOverriddenEnvsInMochaTest({ BATCH_REQUESTS_ENABLED: undefined }, async function () { + it('batch request be disabled by default', async function () { + try { + await testClient.post('/', [getEthChainIdRequest(2), getEthAccountsRequest(3), getEthChainIdRequest(4)]); + Assertions.expectedError(); + } catch (error: any) { + BaseTest.batchDisabledErrorCheck(error.response); + } + }); }); }); diff --git a/packages/ws-server/tests/acceptance/batchRequest.spec.ts b/packages/ws-server/tests/acceptance/batchRequest.spec.ts index eee6fd68d1..0d73803dba 100644 --- a/packages/ws-server/tests/acceptance/batchRequest.spec.ts +++ b/packages/ws-server/tests/acceptance/batchRequest.spec.ts @@ -24,7 +24,7 @@ import { ethers, WebSocketProvider } from 'ethers'; import { WsTestConstant, WsTestHelper } from '../helper'; import { predefined } from '@hashgraph/json-rpc-relay/src'; -describe('@web-socket-batch-1 Batch Requests', async function () { +describe('@web-socket-batch-request Batch Requests', async function () { const METHOD_NAME = 'batch_request'; let ethersWsProvider: WebSocketProvider; let batchRequests: any = []; @@ -65,13 +65,11 @@ describe('@web-socket-batch-1 Batch Requests', async function () { }); beforeEach(async () => { - process.env.WS_BATCH_REQUESTS_ENABLED = 'true'; ethersWsProvider = new ethers.WebSocketProvider(WsTestConstant.WS_RELAY_URL); }); afterEach(async () => { if (ethersWsProvider) await ethersWsProvider.destroy(); - delete process.env.WS_BATCH_REQUESTS_ENABLED; }); after(async () => { @@ -83,41 +81,44 @@ describe('@web-socket-batch-1 Batch Requests', async function () { } }); - it(`@release Should submit batch requests to WS server using Standard Web Socket and retrieve batch responses`, async () => { - // call batch request - const batchResponses = await WsTestHelper.sendRequestToStandardWebSocket(METHOD_NAME, batchRequests); + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_ENABLED: 'true' }, () => { + it(`@release Should submit batch requests to WS server using Standard Web Socket and retrieve batch responses`, async () => { + // call batch request + const batchResponses = await WsTestHelper.sendRequestToStandardWebSocket(METHOD_NAME, batchRequests); - // individually process each request - let promises: any = []; - batchRequests.forEach((request: any) => { - promises.push(WsTestHelper.sendRequestToStandardWebSocket(request.method, request.params)); - }); - const individualResponses = await Promise.all(promises); + // individually process each request + let promises: any = []; + batchRequests.forEach((request: any) => { + promises.push(WsTestHelper.sendRequestToStandardWebSocket(request.method, request.params)); + }); + const individualResponses = await Promise.all(promises); - expect(batchResponses).to.deep.eq(individualResponses); - }); + expect(batchResponses).to.deep.eq(individualResponses); + }); - it('Should submit batch requests to WS server and get batchRequestDisabledError if WS_BATCH_REQUESTS_DISABLED=false ', async () => { - process.env.WS_BATCH_REQUESTS_ENABLED = 'false'; - const batchResponses = await WsTestHelper.sendRequestToStandardWebSocket(METHOD_NAME, batchRequests); + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_MAX_SIZE: '1' }, () => { + it('Should submit batch requests to WS server and get batchRequestAmountMaxExceed if requests size exceeds WS_BATCH_REQUESTS_MAX_SIZE', async () => { + const batchResponses = await WsTestHelper.sendRequestToStandardWebSocket(METHOD_NAME, batchRequests); - const expectedError = predefined.WS_BATCH_REQUESTS_DISABLED; - delete expectedError.data; + const expectedError = predefined.BATCH_REQUESTS_AMOUNT_MAX_EXCEEDED( + batchRequests.length, + Number(process.env.WS_BATCH_REQUESTS_MAX_SIZE), + ); + delete expectedError.data; - expect(batchResponses[0].error).to.deep.eq(expectedError); + expect(batchResponses[0].error).to.deep.eq(expectedError); + }); + }); }); - it('Should submit batch requests to WS server and get batchRequestAmountMaxExceed if requests size exceeds WS_BATCH_REQUESTS_MAX_SIZE', async () => { - process.env.WS_BATCH_REQUESTS_MAX_SIZE = '1'; - - const batchResponses = await WsTestHelper.sendRequestToStandardWebSocket(METHOD_NAME, batchRequests); + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_ENABLED: 'false' }, () => { + it('Should submit batch requests to WS server and get batchRequestDisabledError if WS_BATCH_REQUESTS_DISABLED=false ', async () => { + const batchResponses = await WsTestHelper.sendRequestToStandardWebSocket(METHOD_NAME, batchRequests); - const expectedError = predefined.BATCH_REQUESTS_AMOUNT_MAX_EXCEEDED( - batchRequests.length, - Number(process.env.WS_BATCH_REQUESTS_MAX_SIZE), - ); - delete expectedError.data; + const expectedError = predefined.WS_BATCH_REQUESTS_DISABLED; + delete expectedError.data; - expect(batchResponses[0].error).to.deep.eq(expectedError); + expect(batchResponses[0].error).to.deep.eq(expectedError); + }); }); }); diff --git a/packages/ws-server/tests/acceptance/subscribe.spec.ts b/packages/ws-server/tests/acceptance/subscribe.spec.ts index b4a5161f20..fb287a4fef 100644 --- a/packages/ws-server/tests/acceptance/subscribe.spec.ts +++ b/packages/ws-server/tests/acceptance/subscribe.spec.ts @@ -82,9 +82,6 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { let wsProvider; const accounts: AliasAccount[] = []; let logContractSigner; - // Cached original ENV variables - let originalWsMaxInactivityTtl; - let originalWsMultipleAddressesEnabledValue; const topics = [ '0xa8fb2f9a49afc2ea148319326c7208965555151db2ce137c05174098730aedc3', @@ -114,15 +111,9 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { // Deploy Log Contract logContractSigner = await Utils.deployContractWithEthersV2([], LogContractJson, accounts[0].wallet); - - // cache original ENV values - originalWsMultipleAddressesEnabledValue = process.env.WS_MULTIPLE_ADDRESSES_ENABLED; }); beforeEach(async () => { - // restore original ENV value - process.env.WS_MULTIPLE_ADDRESSES_ENABLED = originalWsMultipleAddressesEnabledValue; - wsProvider = await new ethers.WebSocketProvider(WS_RELAY_URL); requestId = Utils.generateRequestId(); // Stabilizes the initial connection test. @@ -250,104 +241,105 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { // skip this test if using a remote relay since updating the env vars would not affect it if (global.relayIsLocal) { - it('Subscribe to multiple contracts on same subscription', async function () { - process.env.WS_MULTIPLE_ADDRESSES_ENABLED = 'true'; // enable feature flag for this test - await new Promise((resolve) => setTimeout(resolve, 10000)); - - const logContractSigner2 = await Utils.deployContractWithEthersV2([], LogContractJson, accounts[0].wallet); - const logContractSigner3 = await Utils.deployContractWithEthersV2([], LogContractJson, accounts[0].wallet); - const addressCollection = [logContractSigner.target, logContractSigner2.target, logContractSigner3.target]; - let subscriptionId = ''; - const webSocket = new WebSocket(WS_RELAY_URL); - - let latestEventFromSubscription; - webSocket.on('message', function incoming(data) { - const parsed = JSON.parse(data); - if (parsed.id !== null || parsed.method) { - if (subscriptionId == '') { - subscriptionId = parsed.result; - } else { - latestEventFromSubscription = parsed; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_MULTIPLE_ADDRESSES_ENABLED: 'true' }, () => { + it('Subscribe to multiple contracts on same subscription', async function () { + await new Promise((resolve) => setTimeout(resolve, 10000)); + + const logContractSigner2 = await Utils.deployContractWithEthersV2([], LogContractJson, accounts[0].wallet); + const logContractSigner3 = await Utils.deployContractWithEthersV2([], LogContractJson, accounts[0].wallet); + const addressCollection = [logContractSigner.target, logContractSigner2.target, logContractSigner3.target]; + let subscriptionId = ''; + const webSocket = new WebSocket(WS_RELAY_URL); + + let latestEventFromSubscription; + webSocket.on('message', function incoming(data) { + const parsed = JSON.parse(data); + if (parsed.id !== null || parsed.method) { + if (subscriptionId == '') { + subscriptionId = parsed.result; + } else { + latestEventFromSubscription = parsed; + } } - } + }); + + webSocket.on('open', function open() { + const request = `{"jsonrpc":"2.0","method":"eth_subscribe","params":["logs", {"address":${JSON.stringify( + addressCollection, + )}}],"id":1}`; + webSocket.send(request); + }); + await new Promise((resolve) => setTimeout(resolve, 500)); // wait for subscription to be created + + const gasOptions = await Utils.gasOptions(requestId, 500_000); + + // create event on contract 1 + const tx1 = await logContractSigner.log1(100, gasOptions); + await tx1.wait(); + await new Promise((resolve) => setTimeout(resolve, 2000)); // wait for event to be received + expect('1: ' + latestEventFromSubscription.params.result.address).to.be.eq( + '1: ' + logContractSigner.target.toLowerCase(), + ); + expect('1: ' + latestEventFromSubscription.params.subscription).to.be.eq('1: ' + subscriptionId); + + // create event on contract 2 + const tx2 = await logContractSigner2.log1(200, gasOptions); + await tx2.wait(); + await new Promise((resolve) => setTimeout(resolve, 2000)); // wait for event to be received + expect('2: ' + latestEventFromSubscription.params.result.address).to.be.eq( + '2: ' + logContractSigner2.target.toLowerCase(), + ); + expect('2: ' + latestEventFromSubscription.params.subscription).to.be.eq('2: ' + subscriptionId); + + // create event on contract 3 + const tx3 = await logContractSigner3.log1(300, gasOptions); + await tx3.wait(); + await new Promise((resolve) => setTimeout(resolve, 2000)); // wait for event to be received + expect('3: ' + latestEventFromSubscription.params.result.address).to.be.eq( + '3: ' + logContractSigner3.target.toLowerCase(), + ); + expect('3: ' + latestEventFromSubscription.params.subscription).to.be.eq('3: ' + subscriptionId); + + // close the connection + webSocket.close(); + + // wait for the connections to be closed + await new Promise((resolve) => setTimeout(resolve, 500)); }); + }); + } + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_MULTIPLE_ADDRESSES_ENABLED: 'false' }, () => { + it('Subscribe to multiple contracts on same subscription Should fail with INVALID_PARAMETER due to feature flag disabled', async function () { + const logContractSigner2 = await Utils.deployContractWithEthersV2([], LogContractJson, accounts[0].wallet); + const addressCollection = [logContractSigner.target, logContractSigner2.target]; + const webSocket = new WebSocket(WS_RELAY_URL); + const requestId = 3; webSocket.on('open', function open() { const request = `{"jsonrpc":"2.0","method":"eth_subscribe","params":["logs", {"address":${JSON.stringify( addressCollection, - )}}],"id":1}`; + )}}],"id":${requestId}}`; webSocket.send(request); }); - await new Promise((resolve) => setTimeout(resolve, 500)); // wait for subscription to be created + let response; + webSocket.on('message', function incoming(data) { + response = JSON.parse(data); + }); - const gasOptions = await Utils.gasOptions(requestId, 500_000); + await new Promise((resolve) => setTimeout(resolve, 1000)); - // create event on contract 1 - const tx1 = await logContractSigner.log1(100, gasOptions); - await tx1.wait(); - await new Promise((resolve) => setTimeout(resolve, 2000)); // wait for event to be received - expect('1: ' + latestEventFromSubscription.params.result.address).to.be.eq( - '1: ' + logContractSigner.target.toLowerCase(), - ); - expect('1: ' + latestEventFromSubscription.params.subscription).to.be.eq('1: ' + subscriptionId); - - // create event on contract 2 - const tx2 = await logContractSigner2.log1(200, gasOptions); - await tx2.wait(); - await new Promise((resolve) => setTimeout(resolve, 2000)); // wait for event to be received - expect('2: ' + latestEventFromSubscription.params.result.address).to.be.eq( - '2: ' + logContractSigner2.target.toLowerCase(), - ); - expect('2: ' + latestEventFromSubscription.params.subscription).to.be.eq('2: ' + subscriptionId); - - // create event on contract 3 - const tx3 = await logContractSigner3.log1(300, gasOptions); - await tx3.wait(); - await new Promise((resolve) => setTimeout(resolve, 2000)); // wait for event to be received - expect('3: ' + latestEventFromSubscription.params.result.address).to.be.eq( - '3: ' + logContractSigner3.target.toLowerCase(), + expect(response.id).to.be.eq(requestId); + expect(response.error.code).to.be.eq(-32602); + expect(response.error.message).to.be.eq( + `Invalid parameter filters.address: Only one contract address is allowed`, ); - expect('3: ' + latestEventFromSubscription.params.subscription).to.be.eq('3: ' + subscriptionId); - // close the connection + // post test clean-up webSocket.close(); - // wait for the connections to be closed + // wait 500 ms for the connection to be closed await new Promise((resolve) => setTimeout(resolve, 500)); - process.env.WS_MULTIPLE_ADDRESSES_ENABLED = originalWsMultipleAddressesEnabledValue; // restore original value }); - } - - it('Subscribe to multiple contracts on same subscription Should fail with INVALID_PARAMETER due to feature flag disabled', async function () { - process.env.WS_MULTIPLE_ADDRESSES_ENABLED = 'false'; // disable feature flag - const logContractSigner2 = await Utils.deployContractWithEthersV2([], LogContractJson, accounts[0].wallet); - const addressCollection = [logContractSigner.target, logContractSigner2.target]; - const webSocket = new WebSocket(WS_RELAY_URL); - const requestId = 3; - webSocket.on('open', function open() { - const request = `{"jsonrpc":"2.0","method":"eth_subscribe","params":["logs", {"address":${JSON.stringify( - addressCollection, - )}}],"id":${requestId}}`; - webSocket.send(request); - }); - let response; - webSocket.on('message', function incoming(data) { - response = JSON.parse(data); - }); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - expect(response.id).to.be.eq(requestId); - expect(response.error.code).to.be.eq(-32602); - expect(response.error.message).to.be.eq( - `Invalid parameter filters.address: Only one contract address is allowed`, - ); - - // post test clean-up - webSocket.close(); - - // wait 500 ms for the connection to be closed - await new Promise((resolve) => setTimeout(resolve, 500)); }); it('Expect Unsupported Method Error message when subscribing for newPendingTransactions method', async function () { @@ -434,14 +426,11 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { }); describe('Connection limit', async function () { - let originalWsMaxConnectionLimit, - providers: ethers.WebSocketProvider[] = []; + let providers: ethers.WebSocketProvider[] = []; - beforeEach(async () => { - // cache original ENV values - originalWsMaxConnectionLimit = process.env.WS_CONNECTION_LIMIT; - process.env.WS_CONNECTION_LIMIT = 5; + WsTestHelper.overrideEnvsInMochaDescribe({ WS_CONNECTION_LIMIT: '5' }); + beforeEach(async () => { // We already have one connection expect(server._connections).to.equal(1); @@ -454,9 +443,6 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { }); afterEach(async () => { - // Return ENV variables to their original value - process.env.WS_CONNECTION_LIMIT = originalWsMaxConnectionLimit; - for (const provider of providers) { await provider.destroy(); } @@ -485,15 +471,7 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { describe('Connection TTL', async function () { let TEST_TTL = 5000; - this.beforeAll(async () => { - // cache original ENV values - originalWsMaxInactivityTtl = process.env.WS_MAX_INACTIVITY_TTL || '300000'; - process.env.WS_MAX_INACTIVITY_TTL = TEST_TTL.toString(); - }); - this.afterAll(async () => { - // Return ENV variables to their original value - process.env.WS_MAX_INACTIVITY_TTL = originalWsMaxInactivityTtl; - }); + WsTestHelper.overrideEnvsInMochaDescribe({ WS_MAX_INACTIVITY_TTL: TEST_TTL.toString() }); it('Connection TTL is enforced, should close all connections', async function () { const wsConn2 = await new ethers.WebSocketProvider(WS_RELAY_URL); @@ -907,16 +885,7 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { // skip this test if using a remote relay since updating the env vars would not affect it if (global.relayIsLocal) { describe('IP connection limits', async function () { - let originalConnectionLimitPerIp; - - before(() => { - originalConnectionLimitPerIp = process.env.WS_CONNECTION_LIMIT_PER_IP; - process.env.WS_CONNECTION_LIMIT_PER_IP = 3; - }); - - after(() => { - process.env.WS_CONNECTION_LIMIT_PER_IP = originalConnectionLimitPerIp; - }); + WsTestHelper.overrideEnvsInMochaDescribe({ WS_CONNECTION_LIMIT_PER_IP: '3' }); it('Does not allow more connections from the same IP than the specified limit', async function () { const providers = []; @@ -956,20 +925,9 @@ describe('@web-socket-batch-3 eth_subscribe', async function () { }); describe('Connection subscription limits', async function () { - let originalSubsPerConnection; - - before(() => { - originalSubsPerConnection = process.env.WS_SUBSCRIPTION_LIMIT; - process.env.WS_SUBSCRIPTION_LIMIT = 2; - }); - - after(() => { - process.env.WS_SUBSCRIPTION_LIMIT = originalSubsPerConnection; - }); + WsTestHelper.overrideEnvsInMochaDescribe({ WS_SUBSCRIPTION_LIMIT: '2' }); it('Does not allow more subscriptions per connection than the specified limit', async function () { - let errorsHandled = 0; - // Create different subscriptions for (let i = 0; i < 3; i++) { if (i === 2) { diff --git a/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts b/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts index c14b467df9..a4b9912edf 100644 --- a/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts +++ b/packages/ws-server/tests/acceptance/subscribeNewHeads.spec.ts @@ -27,6 +27,7 @@ import { predefined } from '@hashgraph/json-rpc-relay'; import { Utils } from '@hashgraph/json-rpc-server/tests/helpers/utils'; import Assertions from '@hashgraph/json-rpc-server/tests/helpers/assertions'; import { AliasAccount } from '@hashgraph/json-rpc-server/tests/types/AliasAccount'; +import { WsTestHelper } from '../helper'; import MirrorClient from '@hashgraph/json-rpc-server/tests/clients/mirrorClient'; import RelayClient from '@hashgraph/json-rpc-server/tests/clients/relayClient'; @@ -102,7 +103,6 @@ describe('@web-socket-batch-3 eth_subscribe newHeads', async function () { let mirrorNodeServer, requestId, rpcServer, wsServer; let wsProvider; - let originalWsNewHeadsEnabledValue, originalWsSubcriptionLimitValue; before(async () => { // @ts-ignore @@ -127,16 +127,9 @@ describe('@web-socket-batch-3 eth_subscribe newHeads', async function () { )), ); global.accounts.push(...accounts); - - // cache original ENV values - originalWsNewHeadsEnabledValue = process.env.WS_NEW_HEADS_ENABLED; - originalWsSubcriptionLimitValue = process.env.WS_SUBSCRIPTION_LIMIT; }); beforeEach(async () => { - process.env.WS_NEW_HEADS_ENABLED = originalWsNewHeadsEnabledValue; - process.env.WS_SUBSCRIPTION_LIMIT = '10'; - wsProvider = await new ethers.WebSocketProvider(WS_RELAY_URL); await new Promise((resolve) => setTimeout(resolve, 1000)); }); @@ -146,97 +139,94 @@ describe('@web-socket-batch-3 eth_subscribe newHeads', async function () { await wsProvider.destroy(); await new Promise((resolve) => setTimeout(resolve, 1000)); } - process.env.WS_SUBSCRIPTION_LIMIT = originalWsSubcriptionLimitValue; }); describe('Configuration', async function () { - it('Should return unsupported method when WS_NEW_HEADS_ENABLED is set to false', async function () { - const webSocket = new WebSocket(WS_RELAY_URL); - process.env.WS_NEW_HEADS_ENABLED = 'false'; - const messagePromise = new Promise((resolve, reject) => { - webSocket.on('message', function incoming(data) { - try { - const response = JSON.parse(data); - expect(response).to.have.property('error'); - expect(response.error).to.have.property('code'); - expect(response.error.code).to.equal(-32601); - expect(response.error).to.have.property('message'); - expect(response.error.message).to.equal('Unsupported JSON-RPC method'); - resolve(); - } catch (error) { - reject(error); - } + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_NEW_HEADS_ENABLED: 'false' }, () => { + it('Should return unsupported method when WS_NEW_HEADS_ENABLED is set to false', async function () { + const webSocket = new WebSocket(WS_RELAY_URL); + const messagePromise = new Promise((resolve, reject) => { + webSocket.on('message', function incoming(data) { + try { + const response = JSON.parse(data); + expect(response).to.have.property('error'); + expect(response.error).to.have.property('code'); + expect(response.error.code).to.equal(-32601); + expect(response.error).to.have.property('message'); + expect(response.error.message).to.equal('Unsupported JSON-RPC method'); + resolve(); + } catch (error) { + reject(error); + } + }); + webSocket.on('open', function open() { + // send the request for newHeads + webSocket.send('{"id":1,"jsonrpc":"2.0","method":"eth_subscribe","params":["newHeads"]}'); + }); + webSocket.on('error', (error) => { + reject(error); // Reject the promise on WebSocket error + }); }); - webSocket.on('open', function open() { - // send the request for newHeads - webSocket.send('{"id":1,"jsonrpc":"2.0","method":"eth_subscribe","params":["newHeads"]}'); - }); - webSocket.on('error', (error) => { - reject(error); // Reject the promise on WebSocket error - }); - }); - await messagePromise; + await messagePromise; - webSocket.close(); - process.env.WS_NEW_HEADS_ENABLED = originalWsNewHeadsEnabledValue; + webSocket.close(); + }); }); - it('Does not allow more subscriptions per connection than the specified limit with newHeads', async function () { - process.env.WS_SUBSCRIPTION_LIMIT = '2'; - process.env.WS_NEW_HEADS_ENABLED = 'true'; - // Create different subscriptions - for (let i = 0; i < 3; i++) { - if (i === 2) { - const expectedError = predefined.MAX_SUBSCRIPTIONS; - await Assertions.assertPredefinedRpcError(expectedError, wsProvider.send, true, wsProvider, [ - 'eth_subscribe', - ['newHeads'], - ]); - } else { - await wsProvider.send('eth_subscribe', ['newHeads']); + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_SUBSCRIPTION_LIMIT: '2', WS_NEW_HEADS_ENABLED: 'true' }, () => { + it('Does not allow more subscriptions per connection than the specified limit with newHeads', async function () { + // Create different subscriptions + for (let i = 0; i < 3; i++) { + if (i === 2) { + const expectedError = predefined.MAX_SUBSCRIPTIONS; + await Assertions.assertPredefinedRpcError(expectedError, wsProvider.send, true, wsProvider, [ + 'eth_subscribe', + ['newHeads'], + ]); + } else { + await wsProvider.send('eth_subscribe', ['newHeads']); + } } - } - await new Promise((resolve) => setTimeout(resolve, 500)); - process.env.WS_NEW_HEADS_ENABLED = originalWsNewHeadsEnabledValue; + await new Promise((resolve) => setTimeout(resolve, 500)); + }); }); - it('@release should subscribe to newHeads even when WS_NEW_HEADS_ENABLED=undefined, and receive a valid JSON RPC response', async (done) => { - delete process.env.WS_NEW_HEADS_ENABLED; - expect(process.env.WS_NEW_HEADS_ENABLED).to.be.undefined; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_NEW_HEADS_ENABLED: undefined }, () => { + it('@release should subscribe to newHeads and receive a valid JSON RPC response', async (done) => { + expect(process.env.WS_NEW_HEADS_ENABLED).to.be.undefined; - const webSocket = new WebSocket(WS_RELAY_URL); - const subscriptionId = 1; - webSocket.on('open', function open() { - webSocket.send( - JSON.stringify({ - id: subscriptionId, - jsonrpc: '2.0', - method: 'eth_subscribe', - params: ['newHeads', { includeTransactions: true }], - }), - ); - }); + const webSocket = new WebSocket(WS_RELAY_URL); + const subscriptionId = 1; + webSocket.on('open', function open() { + webSocket.send( + JSON.stringify({ + id: subscriptionId, + jsonrpc: '2.0', + method: 'eth_subscribe', + params: ['newHeads', { includeTransactions: true }], + }), + ); + }); - let responseCounter = 0; + let responseCounter = 0; - Utils.sendTransaction(ONE_TINYBAR, CHAIN_ID, accounts, rpcServer, requestId, mirrorNodeServer); - webSocket.on('message', function incoming(data) { - const response = JSON.parse(data); - responseCounter++; - verifyResponse(response, done, webSocket, true); - if (responseCounter > 1) { - webSocket.close(); - } + Utils.sendTransaction(ONE_TINYBAR, CHAIN_ID, accounts, rpcServer, requestId, mirrorNodeServer); + webSocket.on('message', function incoming(data) { + const response = JSON.parse(data); + responseCounter++; + verifyResponse(response, done, webSocket, true); + if (responseCounter > 1) { + webSocket.close(); + } + }); + done(); }); - done(); }); }); describe('Subscriptions for newHeads', async function () { - this.beforeEach(() => { - process.env.WS_NEW_HEADS_ENABLED = 'true'; - }); + WsTestHelper.overrideEnvsInMochaDescribe({ WS_NEW_HEADS_ENABLED: 'true' }); it('should subscribe to newHeads, include transactions true, and receive a valid JSON RPC response', (done) => { const webSocket = new WebSocket(WS_RELAY_URL); diff --git a/packages/ws-server/tests/acceptance/validations.spec.ts b/packages/ws-server/tests/acceptance/validations.spec.ts index cc9a3af5f1..88e0defca8 100644 --- a/packages/ws-server/tests/acceptance/validations.spec.ts +++ b/packages/ws-server/tests/acceptance/validations.spec.ts @@ -49,16 +49,16 @@ describe('@release @web-socket-batch-1 JSON-RPC requests validation', async func const UNSUPPORTED_METHODS = ['eth_getChainId', 'getLogs', 'ethCall', 'blockNum', 'getGasPrice']; + WsTestHelper.overrideEnvsInMochaDescribe({ REQUEST_ID_IS_OPTIONAL: 'true' }); + let ethersWsProvider: WebSocketProvider; beforeEach(async () => { - process.env.REQUEST_ID_IS_OPTIONAL = 'true'; ethersWsProvider = new ethers.WebSocketProvider(WsTestConstant.WS_RELAY_URL); }); afterEach(async () => { if (ethersWsProvider) await ethersWsProvider.destroy(); - delete process.env.REQUEST_ID_IS_OPTIONAL; }); after(async () => { diff --git a/packages/ws-server/tests/helper/index.ts b/packages/ws-server/tests/helper/index.ts index 144ab1efb2..67409c3f46 100644 --- a/packages/ws-server/tests/helper/index.ts +++ b/packages/ws-server/tests/helper/index.ts @@ -31,7 +31,7 @@ export class WsTestHelper { try { await wsProvider.send(methodName, params); expect(true).to.eq(false); - } catch (error) { + } catch (error: any) { if (error.info) error = error.info; expect(error.error).to.exist; expect(error.error.code).to.be.oneOf([-32602, -32603]); @@ -88,6 +88,77 @@ export class WsTestHelper { params, }; } + + /** + * Temporarily overrides environment variables for the duration of the encapsulating describe block. + * @param envs - An object containing key-value pairs of environment variables to set. + * + * @example + * describe('given TEST is set to false', () => { + * overrideEnvsInMochaDescribe({ TEST: 'false' }); + * + * it('should return false', () => { + * expect(process.env.TEST).to.equal('false'); + * }); + * }); + * + * it('should return true', () => { + * expect(process.env.TEST).to.equal('true'); + * }); + */ + static overrideEnvsInMochaDescribe(envs: NodeJS.Dict) { + let envsToReset: NodeJS.Dict = {}; + + const overrideEnv = (object: NodeJS.Dict, key: string, value: string | undefined) => { + if (value === undefined) { + delete object[key]; + } else { + object[key] = value; + } + }; + + before(() => { + for (const key in envs) { + envsToReset[key] = process.env[key]; + overrideEnv(process.env, key, envs[key]); + } + }); + + after(() => { + for (const key in envs) { + overrideEnv(process.env, key, envsToReset[key]); + } + }); + } + + /** + * Overrides environment variables for the duration of the provided tests. + * + * @param {NodeJS.Dict} envs - An object containing key-value pairs of environment variables to set. + * @param {Function} tests - A function containing the tests to run with the overridden environment variables. + * + * @example + * withOverriddenEnvsInMochaTest({ TEST: 'false' }, () => { + * it('should return false', () => { + * expect(process.env.TEST).to.equal('false'); + * }); + * }); + * + * it('should return true', () => { + * expect(process.env.TEST).to.equal('true'); + * }); + */ + static withOverriddenEnvsInMochaTest(envs: NodeJS.Dict, tests: () => void) { + const overriddenEnvs = Object.entries(envs) + .map(([key, value]) => `${key}=${value}`) + .join(', '); + + describe(`given ${overriddenEnvs} are set`, () => { + WsTestHelper.overrideEnvsInMochaDescribe(envs); + + tests(); + }); + } } export class WsTestConstant { diff --git a/packages/ws-server/tests/unit/utils.spec.ts b/packages/ws-server/tests/unit/utils.spec.ts index eae0293810..0bac16f4e2 100644 --- a/packages/ws-server/tests/unit/utils.spec.ts +++ b/packages/ws-server/tests/unit/utils.spec.ts @@ -30,9 +30,12 @@ import { sendToClient, } from '../../src/utils/utils'; import { WS_CONSTANTS } from '../../src/utils/constants'; -import { Relay } from '@hashgraph/json-rpc-relay/src'; import ConnectionLimiter from '../../src/metrics/connectionLimiter'; import WsMetricRegistry from '../../src/metrics/wsMetricRegistry'; +import { WsTestHelper } from '../helper'; +import { RelayImpl } from '@hashgraph/json-rpc-relay'; +import { Counter, Histogram } from 'prom-client'; +import { SubscriptionController } from '@hashgraph/json-rpc-relay/dist/lib/subscriptionController'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; describe('Utilities unit tests', async function () { @@ -191,34 +194,27 @@ describe('Utilities unit tests', async function () { }); describe('handleConnectionClose', () => { - let relayStub: sinon.SinonStubbedInstance; + let relayStub: sinon.SinonStubbedInstance; let limiterStub: sinon.SinonStubbedInstance; let wsMetricRegistryStub: sinon.SinonStubbedInstance; let ctxStub: any; let startTime: [number, number]; - beforeEach(() => { - relayStub = { - subs: sinon.stub().returns({ - unsubscribe: sinon.spy(), - }), - } as any; - - limiterStub = { - decrementCounters: sinon.spy(), - } as any; - - wsMetricRegistryStub = { - getCounter: sinon.stub().returns({ - inc: sinon.spy(), - }), - getHistogram: sinon.stub().returns({ - labels: sinon.stub().returns({ + beforeEach(async () => { + relayStub = sinon.createStubInstance(RelayImpl, { + subs: sinon.createStubInstance(SubscriptionController), + }); + limiterStub = sinon.createStubInstance(ConnectionLimiter); + wsMetricRegistryStub = sinon.createStubInstance(WsMetricRegistry); + wsMetricRegistryStub.getCounter.returns(sinon.createStubInstance(Counter)); + wsMetricRegistryStub.getHistogram.returns( + sinon.createStubInstance(Histogram, { + labels: sinon.stub<[Partial>]>().returns({ observe: sinon.spy(), + startTimer: sinon.spy(), }), }), - } as any; - + ); ctxStub = { websocket: { id: 'mock-id', @@ -227,54 +223,25 @@ describe('Utilities unit tests', async function () { }; startTime = process.hrtime(); + + await handleConnectionClose(ctxStub, relayStub, limiterStub, wsMetricRegistryStub, startTime); }); it('should unsubscribe subscriptions', async () => { - await handleConnectionClose( - ctxStub, - relayStub as Relay, - limiterStub as ConnectionLimiter, - wsMetricRegistryStub as WsMetricRegistry, - startTime, - ); - - expect(relayStub.subs()?.unsubscribe.calledWith(ctxStub.websocket)).to.be.true; + const unsubscribeSpy = relayStub.subs()?.unsubscribe as sinon.SinonSpy; + expect(unsubscribeSpy.calledWith(ctxStub.websocket)).to.be.true; }); it('should decrement the limiter counters', async () => { - await handleConnectionClose( - ctxStub, - relayStub as Relay, - limiterStub as ConnectionLimiter, - wsMetricRegistryStub as WsMetricRegistry, - startTime, - ); - expect(limiterStub.decrementCounters.calledWith(ctxStub)).to.be.true; }); it('should increment the total closed connections counter', async () => { - await handleConnectionClose( - ctxStub, - relayStub as Relay, - limiterStub as ConnectionLimiter, - wsMetricRegistryStub as WsMetricRegistry, - startTime, - ); - const incStub = wsMetricRegistryStub.getCounter('totalClosedConnections').inc as sinon.SinonSpy; expect(incStub.calledOnce).to.be.true; }); it('should update the connection duration histogram', async () => { - await handleConnectionClose( - ctxStub, - relayStub as Relay, - limiterStub as ConnectionLimiter, - wsMetricRegistryStub as WsMetricRegistry, - startTime, - ); - const labelsStub = wsMetricRegistryStub.getHistogram('connectionDuration').labels as sinon.SinonStub; const observeSpy = labelsStub().observe as sinon.SinonSpy; @@ -283,61 +250,67 @@ describe('Utilities unit tests', async function () { }); it('should terminate the websocket connection', async () => { - await handleConnectionClose( - ctxStub, - relayStub as Relay, - limiterStub as ConnectionLimiter, - wsMetricRegistryStub as WsMetricRegistry, - startTime, - ); - expect(ctxStub.websocket.terminate.calledOnce).to.be.true; }); }); describe('getMultipleAddressesEnabled', () => { - it('should return true when WS_MULTIPLE_ADDRESSES_ENABLED is set to "true"', () => { - process.env.WS_MULTIPLE_ADDRESSES_ENABLED = 'true'; - expect(getMultipleAddressesEnabled()).to.be.true; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_MULTIPLE_ADDRESSES_ENABLED: 'true' }, () => { + it('should return true', () => { + expect(getMultipleAddressesEnabled()).to.be.true; + }); }); - it('should return false when WS_MULTIPLE_ADDRESSES_ENABLED is set to "false"', () => { - process.env.WS_MULTIPLE_ADDRESSES_ENABLED = 'false'; - expect(getMultipleAddressesEnabled()).to.be.false; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_MULTIPLE_ADDRESSES_ENABLED: 'false' }, () => { + it('should return false', () => { + expect(getMultipleAddressesEnabled()).to.be.false; + }); }); - it('should return false when WS_MULTIPLE_ADDRESSES_ENABLED is undefined', () => { - delete process.env.WS_MULTIPLE_ADDRESSES_ENABLED; - expect(getMultipleAddressesEnabled()).to.be.false; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_MULTIPLE_ADDRESSES_ENABLED: undefined }, () => { + it('should return false', () => { + expect(getMultipleAddressesEnabled()).to.be.false; + }); }); }); describe('getWsBatchRequestsEnabled', () => { - it('should return true when WS_BATCH_REQUESTS_ENABLED is set to "true"', () => { - process.env.WS_BATCH_REQUESTS_ENABLED = 'true'; - expect(getWsBatchRequestsEnabled()).to.be.true; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_ENABLED: 'true' }, () => { + it('should return true', () => { + expect(getWsBatchRequestsEnabled()).to.be.true; + }); }); - it('should return false when WS_BATCH_REQUESTS_ENABLED is set to "false"', () => { - process.env.WS_BATCH_REQUESTS_ENABLED = 'false'; - expect(getWsBatchRequestsEnabled()).to.be.false; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_ENABLED: 'false' }, () => { + it('should return false', () => { + expect(getWsBatchRequestsEnabled()).to.be.false; + }); }); - it('should return true when WS_BATCH_REQUESTS_ENABLED is undefined', () => { - delete process.env.WS_BATCH_REQUESTS_ENABLED; - expect(getWsBatchRequestsEnabled()).to.be.true; + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_ENABLED: undefined }, () => { + it('should return true', () => { + expect(getWsBatchRequestsEnabled()).to.be.true; + }); }); }); describe('getBatchRequestsMaxSize', () => { - it('should return the value of WS_BATCH_REQUESTS_MAX_SIZE when it is set', () => { - process.env.WS_BATCH_REQUESTS_MAX_SIZE = '50'; - expect(getBatchRequestsMaxSize()).to.equal(50); + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_MAX_SIZE: '50' }, () => { + it('should return 50', () => { + expect(getBatchRequestsMaxSize()).to.equal(50); + }); + }); + + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_MAX_SIZE: '0' }, () => { + it('should return 0', () => { + expect(getBatchRequestsMaxSize()).to.equal(0); + }); }); - it('should return 20 when WS_BATCH_REQUESTS_MAX_SIZE is not set', () => { - delete process.env.WS_BATCH_REQUESTS_MAX_SIZE; - expect(getBatchRequestsMaxSize()).to.equal(20); + WsTestHelper.withOverriddenEnvsInMochaTest({ WS_BATCH_REQUESTS_MAX_SIZE: undefined }, () => { + it('should return 20', () => { + expect(getBatchRequestsMaxSize()).to.equal(20); + }); }); }); }); diff --git a/packages/ws-server/tests/unit/validations.spec.ts b/packages/ws-server/tests/unit/validations.spec.ts index feaf05c186..1b4dbf1550 100644 --- a/packages/ws-server/tests/unit/validations.spec.ts +++ b/packages/ws-server/tests/unit/validations.spec.ts @@ -22,6 +22,7 @@ import pino from 'pino'; import { expect } from 'chai'; import { WS_CONSTANTS } from '../../src/utils/constants'; import { validateJsonRpcRequest, verifySupportedMethod } from '../../src/utils/utils'; +import { WsTestHelper } from '../helper'; import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; const logger = pino(); @@ -72,17 +73,16 @@ describe('validations unit test', async function () { }); }); - it('Should execute validateJsonRpcRequest() to validate JSON RPC request that has no id field but return true because REQUEST_ID_IS_OPTIONAL=true', () => { - process.env.REQUEST_ID_IS_OPTIONAL = 'true'; - - const REQUEST = { - jsonrpc: '2.0', - method: 'eth_chainId', - params: [], - }; - // @ts-ignore - expect(validateJsonRpcRequest(REQUEST, logger, requestDetails)).to.be.true; - delete process.env.REQUEST_ID_IS_OPTIONAL; + WsTestHelper.withOverriddenEnvsInMochaTest({ REQUEST_ID_IS_OPTIONAL: 'true' }, () => { + it('Should execute validateJsonRpcRequest() to validate JSON RPC request that has no id field but return true because REQUEST_ID_IS_OPTIONAL=true', () => { + const REQUEST = { + jsonrpc: '2.0', + method: 'eth_chainId', + params: [], + }; + // @ts-ignore + expect(validateJsonRpcRequest(REQUEST, logger, requestDetails)).to.be.true; + }); }); it("Should execute verifySupportedMethod() to validate requests' methods and return true if methods are supported", () => { From 1f776e68a530ffb25aeb03e42268bc46176d9ba5 Mon Sep 17 00:00:00 2001 From: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:58:52 +0300 Subject: [PATCH 28/38] feat: implement configuration to pre-populate the cache with spending plans (#3058) * chore: refactor configurations of `HbarLimitService` Signed-off-by: Victor Yanev * chore: fix ts errors Signed-off-by: Victor Yanev * chore: revert formatting of table Signed-off-by: Victor Yanev * refactor: configurations of `HbarLimitService` Signed-off-by: Victor Yanev * Merge branch 'main' into 2970-Note-what-configurations-are-used Signed-off-by: Victor Yanev # Conflicts: # packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts # packages/relay/src/lib/services/hbarLimitService/index.ts # packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts * docs: add configuration subsections to table of contents Signed-off-by: Victor Yanev * docs: make descriptions of configurations consistent Signed-off-by: Victor Yanev * chore: revert changes to localLRUCache.ts and redisCache.ts Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: moves TODO comment to constants.ts Signed-off-by: Victor Yanev * chore: optimize resetDate calculation Signed-off-by: Victor Yanev * chore: remove unused imports Signed-off-by: Victor Yanev * Merge branch 'main' into 2970-Note-what-configurations-are-used Signed-off-by: Victor Yanev # Conflicts: # packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts # packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts # packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts # packages/relay/src/lib/services/hbarLimitService/index.ts # packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts # packages/relay/tests/lib/sdkClient.spec.ts # packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts * feat: Implement configuration to pre-populate the cache with spending plans Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: address comments + added improvements to logic Signed-off-by: Victor Yanev * chore: added tests + improvements to logic Signed-off-by: Victor Yanev * docs: update docs Signed-off-by: Victor Yanev * docs: reformat table Signed-off-by: Victor Yanev * chore: improvements to hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: fix typo Signed-off-by: Victor Yanev * chore: revert unneeded changes Signed-off-by: Victor Yanev * chore: censor IP addresses in logs of localLRUCache.ts and redisCache.ts Signed-off-by: Victor Yanev * chore: censor IP addresses in logs of localLRUCache.ts and redisCache.ts Signed-off-by: Victor Yanev * fix: hbarSpendingPlanRepository.spec.ts after resolving conflicts from main branch Signed-off-by: Victor Yanev * fix: metricService.spec.ts Signed-off-by: Victor Yanev * chore: fix wrong value for limit duration in `hbar-limiter.md` Co-authored-by: Logan Nguyen Signed-off-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> * chore: fix jsdocs Co-authored-by: Logan Nguyen Signed-off-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> * fix: hbarSpendingPlanRepository.ts Signed-off-by: Victor Yanev * fix: ethAddressHbarSpendingPlanRepository.ts Signed-off-by: Victor Yanev * fix: ipAddressHbarSpendingPlanRepository.ts Signed-off-by: Victor Yanev * fix: jsdocs in hbarLimitService/index.ts Signed-off-by: Victor Yanev * fix: do not reset metrics Signed-off-by: Victor Yanev * Merge branch '2970-Note-what-configurations-are-used' into 3055-Implement-configuration-to-pre-populate-the-cache-with-spending-plans Signed-off-by: Victor Yanev # Conflicts: # packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts # packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts * chore: remove mocks from hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: remove mocks from hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * fix: delete obsolete IP and ETH address associations from the cache Signed-off-by: Victor Yanev * chore: rename `SubscriptionType` to `SubscriptionTier` Signed-off-by: Victor Yanev * chore: add jsdocs in hbarSpendingPlanConfigService.ts Signed-off-by: Victor Yanev * chore: address sonar issue Signed-off-by: Victor Yanev * chore: extend tests in relay.spec.ts Signed-off-by: Victor Yanev * chore: extend tests in server.spec.ts Signed-off-by: Victor Yanev * chore: extend tests in hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: extend tests in hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: reduce code duplication in hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: remove unneeded mocks from hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: extend tests in hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: remove unused argument of helper method in hbarSpendingPlanConfigService.spec.ts Signed-off-by: Victor Yanev * chore: move call of populatePreconfiguredSpendingPlans() back to constructor Signed-off-by: Victor Yanev * chore: remove unused import Signed-off-by: Victor Yanev * chore: address comments Signed-off-by: Victor Yanev * chore: extend tests in localLRUCache.spec.ts and redisCache.spec.ts Signed-off-by: Victor Yanev * fix: case sensitivity in ethAddressHbarSpendingPlanRepository.ts Signed-off-by: Victor Yanev * chore: only trace log when configuration file is missing Signed-off-by: Victor Yanev * chore: log `Pre-configured spending plans populated successfully` only if any spending plans have been deleted or added Signed-off-by: Victor Yanev * test: extend repository tests with new methods Signed-off-by: Victor Yanev * test: extend relay.spec.ts Signed-off-by: Victor Yanev * fix: failing unit tests Signed-off-by: Victor Yanev * chore: use override envs helper method in tests Signed-off-by: Victor Yanev --------- Signed-off-by: Victor Yanev Signed-off-by: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Co-authored-by: Logan Nguyen --- docs/configuration.md | 155 +++--- docs/design/hbar-limiter.md | 130 ++++- .../src/lib/clients/cache/localLRUCache.ts | 23 +- .../relay/src/lib/clients/cache/redisCache.ts | 33 +- .../config/hbarSpendingPlanConfigService.ts | 347 ++++++++++++ .../entities/hbarLimiter/hbarSpendingPlan.ts | 6 +- .../ethAddressHbarSpendingPlanRepository.ts | 74 ++- .../hbarLimiter/hbarSpendingPlanRepository.ts | 62 ++- .../ipAddressHbarSpendingPlanRepository.ts | 68 ++- .../db/types/hbarLimiter/hbarSpendingPlan.ts | 4 +- ...ubscriptionType.ts => subscriptionTier.ts} | 2 +- packages/relay/src/lib/relay.ts | 56 +- .../lib/services/hbarLimitService/index.ts | 42 +- .../relay/src/lib/types/spendingPlanConfig.ts | 76 +++ packages/relay/src/utils.ts | 2 + .../tests/lib/clients/localLRUCache.spec.ts | 37 +- .../tests/lib/clients/redisCache.spec.ts | 22 + .../hbarSpendingPlanConfigService.spec.ts | 514 ++++++++++++++++++ packages/relay/tests/lib/relay.spec.ts | 72 ++- ...hAddressHbarSpendingPlanRepository.spec.ts | 61 +++ .../hbarSpendingPlanRepository.spec.ts | 118 ++-- ...pAddressHbarSpendingPlanRepository.spec.ts | 59 ++ .../cacheService/cacheService.spec.ts | 25 +- .../hbarLimitService/hbarLimitService.spec.ts | 46 +- .../server/tests/integration/server.spec.ts | 15 +- spendingPlansConfig.example.json | 28 + tools/wagmi-example/vite.config.ts | 6 +- 27 files changed, 1792 insertions(+), 291 deletions(-) create mode 100644 packages/relay/src/lib/config/hbarSpendingPlanConfigService.ts rename packages/relay/src/lib/db/types/hbarLimiter/{subscriptionType.ts => subscriptionTier.ts} (96%) create mode 100644 packages/relay/src/lib/types/spendingPlanConfig.ts create mode 100644 packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts create mode 100644 spendingPlansConfig.example.json diff --git a/docs/configuration.md b/docs/configuration.md index 8d5d655067..2f8b4d9680 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -24,88 +24,89 @@ These properties are noted below and should be custom set per deployment. The following table lists the available properties along with their default values for the [Server package](/packages/server/). Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`. -| Name | Default | Description | -| --------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests. | -| `BATCH_REQUESTS_MAX_SIZE` | "100" | Maximum number of requests allowed in a batch. | -| `CHAIN_ID` | "" | The network chain id. Local and previewnet envs should use `0x12a` (298). Previewnet, Testnet and Mainnet should use `0x129` (297), `0x128` (296) and `0x127` (295) respectively. | -| `HEDERA_NETWORK` | "" | Which network to connect to. Automatically populates the main node & mirror node endpoints. Can be `previewnet`, `testnet`, `mainnet` or a map of network IPs -> node accountIds e.g. `{"127.0.0.1:50211":"0.0.3"}` | -| `INPUT_SIZE_LIMIT` | "1mb" | The [koa-jsonrpc](https://github.com/Bitclimb/koa-jsonrpc) maximum size allowed for requests | -| `MAX_BLOCK_RANGE` | "5" | The maximum block number greater than the mirror node's latest block to query for | -| `OPERATOR_ID_MAIN` | "" | Operator account ID used to pay for transactions. In `S.R.N` format, e.g. `0.0.1001`. | -| `OPERATOR_KEY_FORMAT` | "DER" | Operator private key format. Valid types are `DER`, `HEX_ECDSA`, or `HEX_ED25519` | -| `OPERATOR_KEY_MAIN` | "" | Operator private key used to sign transactions in hex encoded DER format. This may be either an ED22519 private key or an ECDSA private key. The private key must be associated with/used to derive `OPERATOR_ID_MAIN`. | -| `RATE_LIMIT_DISABLED` | "false" | Flag to disable IP based rate limiting. | -| `REQUEST_ID_IS_OPTIONAL` | "" | Flag to set it the JSON RPC request id field in the body should be optional. Note, this breaks the API spec and is not advised and is provided for test purposes only where some wallets may be non compliant | -| `SERVER_PORT` | "7546" | The RPC server port number to listen for requests on. Currently a static value defaulting to 7546. See [#955](https://github.com/hashgraph/hedera-json-rpc-relay/issues/955) | -| `SERVER_HOST` | undefined | The hostname or IP address on which the server listens for incoming connections. If `SERVER_HOST` is not configured or left undefined (same as `0.0.0.0`), it permits external connections by default, offering more flexibility. | -| `SERVER_REQUEST_TIMEOUT_MS` | "60000" | The time of inactivity allowed before a timeout is triggered and the socket is closed. See [NodeJs Server Timeout](https://nodejs.org/api/http.html#serversettimeoutmsecs-callback) | +| Name | Default | Description | +|-----------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests. | +| `BATCH_REQUESTS_MAX_SIZE` | "100" | Maximum number of requests allowed in a batch. | +| `CHAIN_ID` | "" | The network chain id. Local and previewnet envs should use `0x12a` (298). Previewnet, Testnet and Mainnet should use `0x129` (297), `0x128` (296) and `0x127` (295) respectively. | +| `HEDERA_NETWORK` | "" | Which network to connect to. Automatically populates the main node & mirror node endpoints. Can be `previewnet`, `testnet`, `mainnet` or a map of network IPs -> node accountIds e.g. `{"127.0.0.1:50211":"0.0.3"}` | +| `INPUT_SIZE_LIMIT` | "1mb" | The [koa-jsonrpc](https://github.com/Bitclimb/koa-jsonrpc) maximum size allowed for requests | +| `MAX_BLOCK_RANGE` | "5" | The maximum block number greater than the mirror node's latest block to query for | +| `OPERATOR_ID_MAIN` | "" | Operator account ID used to pay for transactions. In `S.R.N` format, e.g. `0.0.1001`. | +| `OPERATOR_KEY_FORMAT` | "DER" | Operator private key format. Valid types are `DER`, `HEX_ECDSA`, or `HEX_ED25519` | +| `OPERATOR_KEY_MAIN` | "" | Operator private key used to sign transactions in hex encoded DER format. This may be either an ED22519 private key or an ECDSA private key. The private key must be associated with/used to derive `OPERATOR_ID_MAIN`. | +| `RATE_LIMIT_DISABLED` | "false" | Flag to disable IP based rate limiting. | +| `REQUEST_ID_IS_OPTIONAL` | "" | Flag to set it the JSON RPC request id field in the body should be optional. Note, this breaks the API spec and is not advised and is provided for test purposes only where some wallets may be non compliant | +| `SERVER_PORT` | "7546" | The RPC server port number to listen for requests on. Currently a static value defaulting to 7546. See [#955](https://github.com/hashgraph/hedera-json-rpc-relay/issues/955) | +| `SERVER_HOST` | undefined | The hostname or IP address on which the server listens for incoming connections. If `SERVER_HOST` is not configured or left undefined (same as `0.0.0.0`), it permits external connections by default, offering more flexibility. | +| `SERVER_REQUEST_TIMEOUT_MS` | "60000" | The time of inactivity allowed before a timeout is triggered and the socket is closed. See [NodeJs Server Timeout](https://nodejs.org/api/http.html#serversettimeoutmsecs-callback) | ## Relay The following table lists the available properties along with their default values for the [Relay package](/packages/relay/). Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`. -| Name | Default | Description | -|---------------------------------------------|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `CACHE_MAX` | "1000" | The maximum number (or size) of items that remain in the cache (assuming no TTL pruning or explicit deletions). | -| `CACHE_TTL` | "3_600_000" | Max time to live in ms, for items before they are considered stale. Default is one hour in milliseconds | -| `CLIENT_TRANSPORT_SECURITY` | "false" | Flag to enable or disable TLS for both networks. | -| `CONTRACT_CALL_GAS_LIMIT` | "50_000_000" | Maximum gas limit applied to eth_call endpoint networks, the Relay will accept up to 50M but keep it capped at 15M for the actual call. | -| `CONSENSUS_MAX_EXECUTION_TIME` | "15000" | Maximum time in ms the SDK will wait when submitting a transaction/query before throwing a TIMEOUT error. | -| `DEFAULT_RATE_LIMIT` | "200" | default fallback rate limit, if no other is configured. | -| `ETH_CALL_CACHE_TTL` | "200" | Maximum time in ms to cache an eth_call response. | -| `ETH_BLOCK_NUMBER_CACHE_TTL_MS` | "1000" | Time in ms to cache response from mirror node | -| `ETH_GET_BALANCE_CACHE_TTL_MS` | "1000" | Time in ms to cache balance returned | -| `ETH_GET_BLOCK_BY_RESULTS_BATCH_SIZE` | "25" | The number of contract results to request from the Mirror Node per batch durin an eth_getBlockByHash or eth_getBlockByNumber call | -| `ETH_GET_GAS_PRICE_CACHE_TTL_MS` | "1_800_000" | Time in ms to cache ethGasPrice returned | -| `ETH_CALL_DEFAULT_TO_CONSENSUS_NODE` | "false" | Flag to set if eth_call logic should first query the mirror node. | -| `ETH_CALL_CONSENSUS_SELECTORS` | [""] | A comma-separated list of special transaction selectors that should always be routed to the Consensus node. | -| `ETH_GET_LOGS_BLOCK_RANGE_LIMIT` | "1000" | The maximum block number range to consider during an eth_getLogs call. | -| `ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE` | "1000" | The maximum number of transactions to return when running eth_getBlockByHash or eth_getBlockByNumber with transaction objects set to true call. | -| `FEE_HISTORY_MAX_RESULTS` | "10" | The maximum number of results to returns as part of `eth_feeHistory`. | -| `ETH_FEE_HISTORY_FIXED` | "true" | Flag to set if eth_feeHistory should return a fixed fee for the set of results. | -| `GAS_PRICE_PERCENTAGE_BUFFER` | "0" | The additional buffer that adds a percentage on top of the calculated network gasPrice. This may be used by operators to reduce the chances of `INSUFFICIENT_TX_FEE` errors experienced by users caused by minor fluctuations in the exchange rate. | -| `GAS_PRICE_TINY_BAR_BUFFER` | "10000000000" | The additional buffer range to allow during a relay precheck of gas price. This supports slight fluctuations in network gasprice calculations. | -| `HBAR_RATE_LIMIT_DURATION` | "80000" | HBar budget limit duration. This creates a timestamp, which resets all limits, when it's reached. Default is to 80000 (80 seconds). | -| `HBAR_RATE_LIMIT_TINYBAR` | "11_000_000_000" | Total hbar budget in tinybars (110 hbars per 80 seconds). | -| `HBAR_RATE_LIMIT_BASIC` | "92_592_592" | Individual limit in tinybars for spending plans with a BASIC tier. (equivalent of 1_000 HBARs per day for a duration of 80 seconds) | -| `HBAR_RATE_LIMIT_EXTENDED` | "925_925_925" | Individual limit in tinybars for spending plans with a EXTENDED tier. (equivalent of 10_000 HBARs per day for a duration of 80 seconds) | -| `HBAR_RATE_LIMIT_PRIVILEGED` | "1_851_851_850" | Individual limit in tinybars for spending plans with a PRIVILEGED tier. (equivalent of 20_000 HBARs per day for a duration of 80 seconds) | -| `HAPI_CLIENT_DURATION_RESET` | "3600000" | Time until client reinitialization. (ms) | -| `HAPI_CLIENT_ERROR_RESET` | [21, 50] | Array of status codes, which when encountered will trigger a reinitialization. Status codes are availble [here](https://github.com/hashgraph/hedera-protobufs/blob/main/services/response_code.proto). | -| `HAPI_CLIENT_TRANSACTION_RESET` | "50" | Number of transaction executions, until client reinitialization. | -| `TEST_INITIAL_ACCOUNT_STARTING_BALANCE` | "2000" | The number of HBars to allocate to the initial account in acceptance test runs. This account is responsible for the gas payment of tests within the suite run session and needs to be adequately funded. | -| `LIMIT_DURATION` | "60000" | The maximum duration in ms applied to IP-method based rate limits. | -| `MIRROR_NODE_CONTRACT_RESULTS_PG_MAX` | "25" | The maximum number of pages to be requested for contract results from the mirror node. | -| `MIRROR_NODE_CONTRACT_RESULTS_LOGS_PG_MAX` | "200" | The maximum number of pages to be requested for contract results logs from the mirror node. (each page will contain a max of 100 results) | -| `MIRROR_NODE_LIMIT_PARAM` | "100" | The mirror node custom limit value to be set on GET requests. This optimizes the flow to reduce the number of calls made to the mirror node by setting a limit larger than it's default limit. | -| `MIRROR_NODE_RETRIES` | "0" | The maximum number of retries on a GET request to the mirror node when an acceptable error code is returned. | -| `MIRROR_NODE_RETRY_CODES` | "[]" | The acceptable error codes to retry on a request to the mirror node. If more than 1 error is defined value should be like ie: [400,404,500] | -| `MIRROR_NODE_RETRY_DELAY` | "2000" | The delay in ms between retry requests. | -| `MIRROR_NODE_RETRIES_DEVMODE` | "5" | The maximum number of retries on a GET request to the mirror node when an acceptable error code is returned in dev mode. | -| `MIRROR_NODE_RETRY_DELAY_DEVMODE` | "200" | The delay in ms between retry requests in dev mode. | -| `MIRROR_NODE_URL` | "" | The Mirror Node API endpoint. Official endpoints are Previewnet (https://previewnet.mirrornode.hedera.com), Testnet (https://testnet.mirrornode.hedera.com), Mainnet (https://mainnet-public.mirrornode.hedera.com). See [Mirror Node REST API](https://docs.hedera.com/hedera/sdks-and-apis/rest-api) | -| `MIRROR_NODE_URL_HEADER_X_API_KEY` | "" | Authentication for a `MIRROR_NODE_URL` that requires authentication via the `x-api-key` header. | -| `MIRROR_NODE_REQUEST_RETRY_COUNT` | "10" | Maximun amount of retries to repeat on `GetContractResults` `contracts/results/)` requests when fetching contract results after eth_sendRawTransaction submission. \*Note that this in addition and multiplies the configured Axios retries values. | -| `MIRROR_NODE_AGENT_CACHEABLE_DNS` | "true" | Flag to set if the mirror node agent should cacheable DNS lookups, using better-lookup library. | -| `SDK_REQUEST_TIMEOUT` | "10000" | The complete timeout for running the SDK `execute()` method. This controls the GRPC channel timeout config when querying with network nodes. | -| `CONTRACT_QUERY_TIMEOUT_RETRIES` | "3" | Maximum retries for failed contract call query with timeout exceeded error | -| `TIER_1_RATE_LIMIT` | "100" | Maximum restrictive request count limit used for expensive endpoints rate limiting. | -| `TIER_2_RATE_LIMIT` | "800" | Maximum moderate request count limit used for non expensive endpoints. | -| `TIER_3_RATE_LIMIT` | "1600" | Maximum relaxed request count limit used for static return endpoints. | -| `TX_DEFAULT_GAS` | "400000" | Default gas for transactions that do not specify gas. | -| `FILE_APPEND_MAX_CHUNKS` | "20" | Default maximum number of chunks for the `HAPI` `FileAppendTransaction` to use during contract creation submissions to consensus nodes as part of `eth_sendRawTransactionsaction`. | -| `FILE_APPEND_CHUNK_SIZE=5120` | "5120" | Size in bytes of file chunks for the `HAPI` `FileAppendTransaction` to use during contract creation submissions to consensus nodes as part of `eth_sendRawTransactionsaction`. | -| `FILTER_API_ENABLED` | "false" | Enables all filter related methods: `eth_newFilter`, `eth_uninstallFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_newBlockFilter` | -| `DEBUG_API_ENABLED` | "false" | Enables all debug related methods: `debug_traceTransaction` | -| `REDIS_ENABLED` | "true" | Enable usage of Redis as shared cache | -| `REDIS_URL` | "redis://127.0.0.1:6379" | Sets the url for the Redis shared cache | -| `MULTI_SET` | "true" | Switch between different implementation of setting multiple K/V pairs in the shared cache. True is mSet, false is pipeline | -| `REDIS_RECONNECT_DELAY_MS` | "1000" | Sets the delay between reconnect retries from the Redis client in ms | -| `SEND_RAW_TRANSACTION_SIZE_LIMIT` | "131072" | Sets the limit of the transaction size the relay accepts on eth_sendRawTransaction | -| `GET_RECORD_DEFAULT_TO_CONSENSUS_NODE` | "false" | Flag to set if get transaction record logic should first query the mirror node (false) or consensus node via the SDK (true). | -| `HBAR_RATE_LIMIT_WHITELIST` | [""] | An array of EVM addresses that are allowed to bypass the HBAR rate limits | +| Name | Default | Description | +|---------------------------------------------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `CACHE_MAX` | "1000" | The maximum number (or size) of items that remain in the cache (assuming no TTL pruning or explicit deletions). | +| `CACHE_TTL` | "3_600_000" | Max time to live in ms, for items before they are considered stale. Default is one hour in milliseconds | +| `CLIENT_TRANSPORT_SECURITY` | "false" | Flag to enable or disable TLS for both networks. | +| `CONTRACT_CALL_GAS_LIMIT` | "50_000_000" | Maximum gas limit applied to eth_call endpoint networks, the Relay will accept up to 50M but keep it capped at 15M for the actual call. | +| `CONSENSUS_MAX_EXECUTION_TIME` | "15000" | Maximum time in ms the SDK will wait when submitting a transaction/query before throwing a TIMEOUT error. | +| `DEFAULT_RATE_LIMIT` | "200" | default fallback rate limit, if no other is configured. | +| `ETH_CALL_CACHE_TTL` | "200" | Maximum time in ms to cache an eth_call response. | +| `ETH_BLOCK_NUMBER_CACHE_TTL_MS` | "1000" | Time in ms to cache response from mirror node | +| `ETH_GET_BALANCE_CACHE_TTL_MS` | "1000" | Time in ms to cache balance returned | +| `ETH_GET_BLOCK_BY_RESULTS_BATCH_SIZE` | "25" | The number of contract results to request from the Mirror Node per batch durin an eth_getBlockByHash or eth_getBlockByNumber call | +| `ETH_GET_GAS_PRICE_CACHE_TTL_MS` | "1_800_000" | Time in ms to cache ethGasPrice returned | +| `ETH_CALL_DEFAULT_TO_CONSENSUS_NODE` | "false" | Flag to set if eth_call logic should first query the mirror node. | +| `ETH_CALL_CONSENSUS_SELECTORS` | [""] | A comma-separated list of special transaction selectors that should always be routed to the Consensus node. | +| `ETH_GET_LOGS_BLOCK_RANGE_LIMIT` | "1000" | The maximum block number range to consider during an eth_getLogs call. | +| `ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE` | "1000" | The maximum number of transactions to return when running eth_getBlockByHash or eth_getBlockByNumber with transaction objects set to true call. | +| `FEE_HISTORY_MAX_RESULTS` | "10" | The maximum number of results to returns as part of `eth_feeHistory`. | +| `ETH_FEE_HISTORY_FIXED` | "true" | Flag to set if eth_feeHistory should return a fixed fee for the set of results. | +| `GAS_PRICE_PERCENTAGE_BUFFER` | "0" | The additional buffer that adds a percentage on top of the calculated network gasPrice. This may be used by operators to reduce the chances of `INSUFFICIENT_TX_FEE` errors experienced by users caused by minor fluctuations in the exchange rate. | +| `GAS_PRICE_TINY_BAR_BUFFER` | "10000000000" | The additional buffer range to allow during a relay precheck of gas price. This supports slight fluctuations in network gasprice calculations. | +| `HBAR_RATE_LIMIT_DURATION` | "80000" | HBar budget limit duration. This creates a timestamp, which resets all limits, when it's reached. Default is to 80000 (80 seconds). | +| `HBAR_RATE_LIMIT_TINYBAR` | "11_000_000_000" | Total hbar budget in tinybars (110 hbars per 80 seconds). | +| `HBAR_RATE_LIMIT_BASIC` | "92_592_592" | Individual limit in tinybars for spending plans with a BASIC tier. (equivalent of 1_000 HBARs per day for a duration of 80 seconds) | +| `HBAR_RATE_LIMIT_EXTENDED` | "925_925_925" | Individual limit in tinybars for spending plans with a EXTENDED tier. (equivalent of 10_000 HBARs per day for a duration of 80 seconds) | +| `HBAR_RATE_LIMIT_PRIVILEGED` | "1_851_851_850" | Individual limit in tinybars for spending plans with a PRIVILEGED tier. (equivalent of 20_000 HBARs per day for a duration of 80 seconds) | +| `HBAR_SPENDING_PLANS_CONFIG_FILE` | "spendingPlansConfig.json" | The name of the JSON file containing the pre-configured spending plans for supported projects and partner projects. | +| `HAPI_CLIENT_DURATION_RESET` | "3600000" | Time until client reinitialization. (ms) | +| `HAPI_CLIENT_ERROR_RESET` | [21, 50] | Array of status codes, which when encountered will trigger a reinitialization. Status codes are availble [here](https://github.com/hashgraph/hedera-protobufs/blob/main/services/response_code.proto). | +| `HAPI_CLIENT_TRANSACTION_RESET` | "50" | Number of transaction executions, until client reinitialization. | +| `TEST_INITIAL_ACCOUNT_STARTING_BALANCE` | "2000" | The number of HBars to allocate to the initial account in acceptance test runs. This account is responsible for the gas payment of tests within the suite run session and needs to be adequately funded. | +| `LIMIT_DURATION` | "60000" | The maximum duration in ms applied to IP-method based rate limits. | +| `MIRROR_NODE_CONTRACT_RESULTS_PG_MAX` | "25" | The maximum number of pages to be requested for contract results from the mirror node. | +| `MIRROR_NODE_CONTRACT_RESULTS_LOGS_PG_MAX` | "200" | The maximum number of pages to be requested for contract results logs from the mirror node. (each page will contain a max of 100 results) | +| `MIRROR_NODE_LIMIT_PARAM` | "100" | The mirror node custom limit value to be set on GET requests. This optimizes the flow to reduce the number of calls made to the mirror node by setting a limit larger than it's default limit. | +| `MIRROR_NODE_RETRIES` | "0" | The maximum number of retries on a GET request to the mirror node when an acceptable error code is returned. | +| `MIRROR_NODE_RETRY_CODES` | "[]" | The acceptable error codes to retry on a request to the mirror node. If more than 1 error is defined value should be like ie: [400,404,500] | +| `MIRROR_NODE_RETRY_DELAY` | "2000" | The delay in ms between retry requests. | +| `MIRROR_NODE_RETRIES_DEVMODE` | "5" | The maximum number of retries on a GET request to the mirror node when an acceptable error code is returned in dev mode. | +| `MIRROR_NODE_RETRY_DELAY_DEVMODE` | "200" | The delay in ms between retry requests in dev mode. | +| `MIRROR_NODE_URL` | "" | The Mirror Node API endpoint. Official endpoints are Previewnet (https://previewnet.mirrornode.hedera.com), Testnet (https://testnet.mirrornode.hedera.com), Mainnet (https://mainnet-public.mirrornode.hedera.com). See [Mirror Node REST API](https://docs.hedera.com/hedera/sdks-and-apis/rest-api) | +| `MIRROR_NODE_URL_HEADER_X_API_KEY` | "" | Authentication for a `MIRROR_NODE_URL` that requires authentication via the `x-api-key` header. | +| `MIRROR_NODE_REQUEST_RETRY_COUNT` | "10" | Maximun amount of retries to repeat on `GetContractResults` `contracts/results/)` requests when fetching contract results after eth_sendRawTransaction submission. \*Note that this in addition and multiplies the configured Axios retries values. | +| `MIRROR_NODE_AGENT_CACHEABLE_DNS` | "true" | Flag to set if the mirror node agent should cacheable DNS lookups, using better-lookup library. | +| `SDK_REQUEST_TIMEOUT` | "10000" | The complete timeout for running the SDK `execute()` method. This controls the GRPC channel timeout config when querying with network nodes. | +| `CONTRACT_QUERY_TIMEOUT_RETRIES` | "3" | Maximum retries for failed contract call query with timeout exceeded error | +| `TIER_1_RATE_LIMIT` | "100" | Maximum restrictive request count limit used for expensive endpoints rate limiting. | +| `TIER_2_RATE_LIMIT` | "800" | Maximum moderate request count limit used for non expensive endpoints. | +| `TIER_3_RATE_LIMIT` | "1600" | Maximum relaxed request count limit used for static return endpoints. | +| `TX_DEFAULT_GAS` | "400000" | Default gas for transactions that do not specify gas. | +| `FILE_APPEND_MAX_CHUNKS` | "20" | Default maximum number of chunks for the `HAPI` `FileAppendTransaction` to use during contract creation submissions to consensus nodes as part of `eth_sendRawTransactionsaction`. | +| `FILE_APPEND_CHUNK_SIZE=5120` | "5120" | Size in bytes of file chunks for the `HAPI` `FileAppendTransaction` to use during contract creation submissions to consensus nodes as part of `eth_sendRawTransactionsaction`. | +| `FILTER_API_ENABLED` | "false" | Enables all filter related methods: `eth_newFilter`, `eth_uninstallFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_newBlockFilter` | +| `DEBUG_API_ENABLED` | "false" | Enables all debug related methods: `debug_traceTransaction` | +| `REDIS_ENABLED` | "true" | Enable usage of Redis as shared cache | +| `REDIS_URL` | "redis://127.0.0.1:6379" | Sets the url for the Redis shared cache | +| `MULTI_SET` | "true" | Switch between different implementation of setting multiple K/V pairs in the shared cache. True is mSet, false is pipeline | +| `REDIS_RECONNECT_DELAY_MS` | "1000" | Sets the delay between reconnect retries from the Redis client in ms | +| `SEND_RAW_TRANSACTION_SIZE_LIMIT` | "131072" | Sets the limit of the transaction size the relay accepts on eth_sendRawTransaction | +| `GET_RECORD_DEFAULT_TO_CONSENSUS_NODE` | "false" | Flag to set if get transaction record logic should first query the mirror node (false) or consensus node via the SDK (true). | +| `HBAR_RATE_LIMIT_WHITELIST` | [""] | An array of EVM addresses that are allowed to bypass the HBAR rate limits | ## WS-Server @@ -113,7 +114,7 @@ The following table lists the available properties along with their default valu Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`. | Name | Default | Description | -| ------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|---------------------------------|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `WS_BATCH_REQUESTS_ENABLED` | "true" | Flag to disable or enable batch requests on the websocket server. | | `WS_BATCH_REQUESTS_MAX_SIZE` | "20" | Maximum number of requests allowed in a batch on websocket server. | | `SUBSCRIPTIONS_ENABLED` | "false" | If enabled eth_subscribe will be enabled using WebSockets. | diff --git a/docs/design/hbar-limiter.md b/docs/design/hbar-limiter.md index 9eedc48242..9e2c29c840 100644 --- a/docs/design/hbar-limiter.md +++ b/docs/design/hbar-limiter.md @@ -81,6 +81,15 @@ The purpose of the HBar Limiter is to track and control the spending of HBars in The HBar limiter will be implemented as a separate service, used by other services/classes that need it. It will have two main purposes - to capture the gas fees for different operation and to check if an operation needs to be paused, due to an exceeded HBar limit. +### What is an HbarSpendingPlan? + +An `HbarSpendingPlan` is a record that tracks the total amount of HBars spent by a user or group of users (linked by ETH and IP addresses) over a specific period. It includes the following information: +- **id**: A unique identifier for the spending plan. +- **subscriptionTier**: The tier of the user or group of users (BASIC, EXTENDED, or PRIVILEGED). +- **createdAt**: The timestamp when the spending plan was created. +- **active**: A flag indicating whether the spending plan is currently active. +- **spendingHistory**: A list of spending records, each containing the amount spent and the timestamp. + ### General Users (BASIC tier): **NOTE:** Each general user will have a unique spending plan, linked both to their ETH and IP addresses. Each new user will be automatically assigned a BASIC spending plan when they send their first transaction and this plan will remain linked to them for any subsequent requests. @@ -172,7 +181,7 @@ classDiagram classDiagram class HbarSpendingPlan { -id: string - -subscriptionType: SubscriptionType + -subscriptionTier: SubscriptionTier -createdAt: Date -active: boolean -spendingHistory: HbarSpendingRecord[] @@ -211,7 +220,7 @@ classDiagram -cache: CacheService +findById(id: string): Promise +findByIdWithDetails(id: string): Promise - +create(subscriptionType: SubscriptionType): Promise + +create(subscriptionTier: SubscriptionTier): Promise +checkExistsAndActive(id: string): Promise +getSpendingHistory(id: string): Promise +addAmountToSpendingHistory(id: string, amount: number): Promise @@ -233,13 +242,13 @@ classDiagram +delete(ip: string): Promise } - class SubscriptionType - <> SubscriptionType - SubscriptionType : BASIC - SubscriptionType : EXTENDED - SubscriptionType : PRIVILEGED + class SubscriptionTier + <> SubscriptionTier + SubscriptionTier : BASIC + SubscriptionTier : EXTENDED + SubscriptionTier : PRIVILEGED - HbarSpendingPlan --> SubscriptionType : could be one of the types + HbarSpendingPlan --> SubscriptionTier : could be one of the types HbarSpendingPlan --> HbarSpendingRecord : stores history of EthAddressHbarSpendingPlan --> HbarSpendingPlan : links an ETH address to IpAddressHbarSpendingPlan --> HbarSpendingPlan : links an IP address to @@ -306,61 +315,128 @@ The following configurations will be used to automatically populate the cache wi All other users (ETH and IP addresses which are not specified in the configuration file) will be treated as "general users" and will be assigned a basic `HbarSpendingPlan` on their first request and their ETH address and IP address will be linked to that plan for all subsequent requests. +### JSON Configuration File + +The relay will read the pre-configured spending plans from a JSON file. This file should be placed in the root directory of the relay. + +The default filename for the configuration file is `spendingPlansConfig.json`, but it could also be specified by the environment variable `HBAR_SPENDING_PLANS_CONFIG_FILE`. +- `HBAR_SPENDING_PLANS_CONFIG_FILE`: The name of the file containing the pre-configured spending plans for supported projects and partners. + +#### The JSON file should have the following structure: ```json [ { + "id": "c758c095-342c-4607-9db5-867d7e90ab9d", "name": "partner name", "ethAddresses": ["0x123", "0x124"], "ipAddresses": ["127.0.0.1", "128.0.0.1"], - "subscriptionType": "PRIVILEGED" + "subscriptionTier": "PRIVILEGED" }, { + "id": "a68488b0-6f7d-44a0-87c1-774ad64615f2", "name": "some other partner that has given us only eth addresses", "ethAddresses": ["0x125", "0x126"], - "subscriptionType": "PRIVILEGED" + "subscriptionTier": "PRIVILEGED" }, { + "id": "af13d6ed-d676-4d33-8b9d-cf05d1ad7134", "name": "supported project name", "ethAddresses": ["0x127", "0x128"], "ipAddresses": ["129.0.0.1", "130.0.0.1"], - "subscriptionType": "EXTENDED" + "subscriptionTier": "EXTENDED" }, { + "id": "7f665aa3-6b73-41d7-bf9b-92d04cdab96b", "name": "some other supported project that has given us only ip addresses", "ipAddresses": ["131.0.0.1", "132.0.0.1"], - "subscriptionType": "EXTENDED" + "subscriptionTier": "EXTENDED" } ] ``` -On every start-up, the relay will check if these entries are already populated in the cache. If not, it will populate them accordingly. +#### Important notes +- The `id` field is **strictly required** for each supported project or partner project. It is used as a unique identifier and as key in the cache and also for reference in the logs. We recommend using a UUID for this field, but any unique string will work. +- The `name` field is used just for reference and can be any string. It is not used in the cache or for any other purpose, only for better readability in the logs on start-up of the relay when the spending plans are being configured. +- The `ethAddresses` and `ipAddresses` fields are arrays of strings containing the ETH addresses and IP addresses associated with the supported project or partner project. **At least one** of these two fields must be present and contain **at least one entry**. +- The `subscriptionTier` field is also **required**. It is an enum with the following possible values: `BASIC`, `EXTENDED`, and `PRIVILEGED`. -The JSON file can also be updated over time to add new supported projects or partner projects, and it will populate only the new entries on the next start-up. +On every start-up, the relay will check if these entries are already populated in the cache. If not, it will populate them accordingly. -```json +If the cache already contains some of these entries, it will only populate the new entries and remove the obsolete ones. + +### Incremental changes to the JSON file + +#### Adding new partners or supported projects + +The JSON file can also be updated over time to add new partners or supported projects, and it will populate only the new entries on the next start-up. + +```javascript [ - ..., + // rest of JSON file remains the same + ...oldContent, { + "id": "0b054498-5c48-4402-aad4-b9b455f33457", "name": "new partner name", "ethAddresses": ["0x129", "0x130"], "ipAddresses": ["133.0.0.1"], - "subscriptionType": "PRIVILEGED" + "subscriptionTier": "PRIVILEGED" + } +] +``` + +#### Removing or updating existing partners or supported projects + +If some of the pre-configured plans are removed them from the JSON file, they will be considered "obsolete" and removed from the cache on the next start-up of the relay. + +You can also add new ETH addresses or IP addresses to existing plans by updating the JSON file. + +```javascript +[ + // rest of JSON file remains the same + ...oldContent, + { + "id": "c758c095-342c-4607-9db5-867d7e90ab9d", + "name": "partner name", + "ethAddresses": ["0x123", "0x124", ""], + "ipAddresses": ["127.0.0.1", "128.0.0.1", "", ""], + "subscriptionTier": "PRIVILEGED", + } +] +``` + +Or if you remove any existing ETH addresses or IP addresses from the JSON file, only those will be removed from the cache on the next start-up. + +```javascript +[ + // rest of JSON file remains the same + ...oldContent, + { + "id": "c758c095-342c-4607-9db5-867d7e90ab9d", + "name": "partner name", + "ethAddresses": ["0x123"], // removed "0x124" + "ipAddresses": ["127.0.0.1"], // removed "128.0.0.1" + "subscriptionTier": "PRIVILEGED", } ] ``` ### Spending Limits of Different Tiers +**Units:** All environment variables are specified in tinybars (tℏ): +```math +1 HBAR (ℏ) = 100,000,000 tinybars (tℏ) +``` + The spending limits for different tiers are defined as environment variables: -- `HBAR_RATE_LIMIT_BASIC`: The spending limit (in tinybars) for general users (tier 3) -- `HBAR_RATE_LIMIT_EXTENDED`: The spending limit (in tinybars) for supported projects (tier 2) -- `HBAR_RATE_LIMIT_PRIVILEGED`: The spending limit (in tinybars) for trusted partners (tier 1) +- `HBAR_RATE_LIMIT_BASIC`: The spending limit (tℏ) for general users (tier 3) +- `HBAR_RATE_LIMIT_EXTENDED`: The spending limit (tℏ) for supported projects (tier 2) +- `HBAR_RATE_LIMIT_PRIVILEGED`: The spending limit (tℏ) for trusted partners (tier 1) Example configuration for tiered spending limits: ```dotenv -HBAR_RATE_LIMIT_BASIC=92592592 -HBAR_RATE_LIMIT_EXTENDED=925925925 -HBAR_RATE_LIMIT_PRIVILEGED=1851851850 +HBAR_RATE_LIMIT_BASIC=10000000# 0.1 ℏ +HBAR_RATE_LIMIT_EXTENDED=100000000# 1 ℏ +HBAR_RATE_LIMIT_PRIVILEGED=1000000000# 10 ℏ ``` ### Total Budget and Limit Duration @@ -369,14 +445,14 @@ The total budget and the limit duration are defined as environment variables: - `HBAR_RATE_LIMIT_DURATION`: The time window (in milliseconds) for which both the total budget and the spending limits are applicable. - On initialization of `HbarLimitService`, a reset timestamp is calculated by adding the `HBAR_RATE_LIMIT_DURATION` to the current timestamp. - The total budget and spending limits are reset when the current timestamp exceeds the reset timestamp. -- `HBAR_RATE_LIMIT_TINYBAR`: The ceiling (in tinybars) on the total amount of HBARs that can be spent in the limit duration. +- `HBAR_RATE_LIMIT_TINYBAR`: The ceiling on the total amount (in tℏ) that can be spent in the limit duration. - This is the largest bucket from which others pull from. - If the total amount spent exceeds this limit, all spending is paused until the next reset. -Example configuration for a total budget of 110 HBARs (11_000_000_000 tinybars) per 80 seconds: +Example configuration for a total budget and limit duration: ```dotenv -HBAR_RATE_LIMIT_TINYBAR=11000000000 -HBAR_RATE_LIMIT_DURATION=80000 +HBAR_RATE_LIMIT_TINYBAR=11000000000# 110 ℏ +HBAR_RATE_LIMIT_DURATION=80000# 80 seconds ``` ## Additional Considerations diff --git a/packages/relay/src/lib/clients/cache/localLRUCache.ts b/packages/relay/src/lib/clients/cache/localLRUCache.ts index c366b8897d..bccad80b12 100644 --- a/packages/relay/src/lib/clients/cache/localLRUCache.ts +++ b/packages/relay/src/lib/clients/cache/localLRUCache.ts @@ -24,6 +24,7 @@ import { ICacheClient } from './ICacheClient'; import constants from '../../constants'; import LRUCache, { LimitedByCount, LimitedByTTL } from 'lru-cache'; import { RequestDetails } from '../../types'; +import { Utils } from '../../../utils'; /** * Represents a LocalLRUCache instance that uses an LRU (Least Recently Used) caching strategy @@ -105,11 +106,10 @@ export class LocalLRUCache implements ICacheClient { public async get(key: string, callingMethod: string, requestDetails: RequestDetails): Promise { const value = this.cache.get(key); if (value !== undefined) { - this.logger.trace( - `${requestDetails.formattedRequestId} returning cached value ${key}:${JSON.stringify( - value, - )} on ${callingMethod} call`, - ); + const censoredKey = key.replace(Utils.IP_ADDRESS_REGEX, ''); + const censoredValue = JSON.stringify(value).replace(/"ipAddress":"[^"]+"/, '"ipAddress":""'); + const message = `Returning cached value ${censoredKey}:${censoredValue} on ${callingMethod} call`; + this.logger.trace(`${requestDetails.formattedRequestId} ${message}`); return value; } @@ -148,10 +148,19 @@ export class LocalLRUCache implements ICacheClient { ttl?: number, ): Promise { const resolvedTtl = ttl ?? this.options.ttl; + if (resolvedTtl > 0) { + this.cache.set(key, value, { ttl: resolvedTtl }); + } else { + this.cache.set(key, value, { ttl: 0 }); // 0 means indefinite time + } + const censoredKey = key.replace(Utils.IP_ADDRESS_REGEX, ''); + const censoredValue = JSON.stringify(value).replace(/"ipAddress":"[^"]+"/, '"ipAddress":""'); + const message = `Caching ${censoredKey}:${censoredValue} on ${callingMethod} for ${ + resolvedTtl > 0 ? `${resolvedTtl} ms` : 'indefinite time' + }`; this.logger.trace( - `${requestDetails.formattedRequestId} caching ${key}:${JSON.stringify(value)} for ${resolvedTtl} ms`, + `${requestDetails.formattedRequestId} ${message} (cache size: ${this.cache.size}, max: ${this.options.max})`, ); - this.cache.set(key, value, { ttl: resolvedTtl }); } /** diff --git a/packages/relay/src/lib/clients/cache/redisCache.ts b/packages/relay/src/lib/clients/cache/redisCache.ts index 2c9263d239..1961374604 100644 --- a/packages/relay/src/lib/clients/cache/redisCache.ts +++ b/packages/relay/src/lib/clients/cache/redisCache.ts @@ -26,6 +26,7 @@ import { RedisCacheError } from '../../errors/RedisCacheError'; import constants from '../../constants'; import { IRedisCacheClient } from './IRedisCacheClient'; import { RequestDetails } from '../../types'; +import { Utils } from '../../../utils'; /** * A class that provides caching functionality using Redis. @@ -133,11 +134,10 @@ export class RedisCache implements IRedisCacheClient { const client = await this.getConnectedClient(); const result = await client.get(key); if (result) { - this.logger.trace( - `${requestDetails.formattedRequestId} returning cached value ${key}:${JSON.stringify( - result, - )} on ${callingMethod} call`, - ); + const censoredKey = key.replace(Utils.IP_ADDRESS_REGEX, ''); + const censoredValue = result.replace(/"ipAddress":"[^"]+"/, '"ipAddress":""'); + const message = `Returning cached value ${censoredKey}:${censoredValue} on ${callingMethod} call`; + this.logger.trace(`${requestDetails.formattedRequestId} ${message}`); // TODO: add metrics return JSON.parse(result); } @@ -164,11 +164,18 @@ export class RedisCache implements IRedisCacheClient { const client = await this.getConnectedClient(); const serializedValue = JSON.stringify(value); const resolvedTtl = ttl ?? this.options.ttl; // in milliseconds + if (resolvedTtl > 0) { + await client.set(key, serializedValue, { PX: resolvedTtl }); + } else { + await client.set(key, serializedValue); + } - await client.set(key, serializedValue, { PX: resolvedTtl }); - this.logger.trace( - `${requestDetails.formattedRequestId} caching ${key}: ${serializedValue} on ${callingMethod} for ${resolvedTtl} s`, - ); + const censoredKey = key.replace(Utils.IP_ADDRESS_REGEX, ''); + const censoredValue = serializedValue.replace(/"ipAddress":"[^"]+"/, '"ipAddress":""'); + const message = `Caching ${censoredKey}:${censoredValue} on ${callingMethod} for ${ + resolvedTtl > 0 ? `${resolvedTtl} ms` : 'indefinite time' + }`; + this.logger.trace(`${requestDetails.formattedRequestId} ${message}`); // TODO: add metrics } @@ -192,12 +199,8 @@ export class RedisCache implements IRedisCacheClient { serializedKeyValuePairs[key] = JSON.stringify(value); } - try { - // Perform mSet operation - await client.mSet(serializedKeyValuePairs); - } catch (e) { - this.logger.error(e); - } + // Perform mSet operation + await client.mSet(serializedKeyValuePairs); // Log the operation const entriesLength = Object.keys(keyValuePairs).length; diff --git a/packages/relay/src/lib/config/hbarSpendingPlanConfigService.ts b/packages/relay/src/lib/config/hbarSpendingPlanConfigService.ts new file mode 100644 index 0000000000..0ea851d1a9 --- /dev/null +++ b/packages/relay/src/lib/config/hbarSpendingPlanConfigService.ts @@ -0,0 +1,347 @@ +/* + * + * Hedera JSON RPC Relay + * + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import fs from 'fs'; +import findConfig from 'find-config'; +import { isValidSpendingPlanConfig, SpendingPlanConfig } from '../types/spendingPlanConfig'; +import { HbarSpendingPlanRepository } from '../db/repositories/hbarLimiter/hbarSpendingPlanRepository'; +import { EthAddressHbarSpendingPlanRepository } from '../db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; +import { IPAddressHbarSpendingPlanRepository } from '../db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; +import { RequestDetails } from '../types'; +import { Logger } from 'pino'; +import { SubscriptionTier } from '../db/types/hbarLimiter/subscriptionTier'; +import { IDetailedHbarSpendingPlan } from '../db/types/hbarLimiter/hbarSpendingPlan'; + +/** + * Service for managing pre-configured {@link HbarSpendingPlan} entities. + * + * It reads the pre-configured spending plans from a JSON file and populates the cache with them. + * + * @see SpendingPlanConfig + * @see SPENDING_PLANS_CONFIG_FILE + */ +export class HbarSpendingPlanConfigService { + /** + * The time-to-live (TTL) for the pre-configured spending plans in the cache. + * Defaults to `-1`, which means no TTL, i.e. the data will not expire. + * + * @type {number} + * @private + */ + private readonly TTL: number = -1; + + /** + * The name of the spending plans configuration file. Defaults to `spendingPlansConfig.json`. + * + * @type {string} + * @private + */ + private readonly SPENDING_PLANS_CONFIG_FILE: string = + process.env.HBAR_SPENDING_PLANS_CONFIG_FILE || 'spendingPlansConfig.json'; + + /** + * Creates an instance of `HbarSpendingPlanConfigService`. + * + * @constructor + * @param {Logger} logger - The logger instance. + * @param {HbarSpendingPlanRepository} hbarSpendingPlanRepository - The repository for HBAR spending plans. + * @param {EthAddressHbarSpendingPlanRepository} ethAddressHbarSpendingPlanRepository - The repository for ETH address associations. + * @param {IPAddressHbarSpendingPlanRepository} ipAddressHbarSpendingPlanRepository - The repository for IP address associations. + */ + constructor( + private readonly logger: Logger, + private readonly hbarSpendingPlanRepository: HbarSpendingPlanRepository, + private readonly ethAddressHbarSpendingPlanRepository: EthAddressHbarSpendingPlanRepository, + private readonly ipAddressHbarSpendingPlanRepository: IPAddressHbarSpendingPlanRepository, + ) {} + + /** + * Populates the database with pre-configured spending plans. + * + * @returns {Promise} - A promise that resolves with the number of spending plans which were added or deleted. + * @throws {Error} - If the spending plans configuration file is not found or cannot be loaded. + */ + public async populatePreconfiguredSpendingPlans(): Promise { + const spendingPlanConfigs = this.loadSpendingPlansConfig(); + if (!spendingPlanConfigs.length) { + return 0; + } + this.validateSpendingPlanConfig(spendingPlanConfigs); + + const requestDetails = new RequestDetails({ requestId: '', ipAddress: '' }); + const existingPlans: IDetailedHbarSpendingPlan[] = + await this.hbarSpendingPlanRepository.findAllActiveBySubscriptionTier( + [SubscriptionTier.EXTENDED, SubscriptionTier.PRIVILEGED], + requestDetails, + ); + const plansDeleted = await this.deleteObsoletePlans(existingPlans, spendingPlanConfigs, requestDetails); + const plansAdded = await this.addNewPlans(spendingPlanConfigs, existingPlans, requestDetails); + await this.updatePlanAssociations(spendingPlanConfigs, requestDetails); + + return plansDeleted + plansAdded; + } + + /** + * Loads the pre-configured spending plans from a JSON file. + * + * @returns {SpendingPlanConfig[]} An array of spending plan configurations. + * @throws {Error} If the configuration file is not found or cannot be read or parsed. + * @private + */ + private loadSpendingPlansConfig(): SpendingPlanConfig[] { + const configPath = findConfig(this.SPENDING_PLANS_CONFIG_FILE); + if (!configPath || !fs.existsSync(configPath)) { + this.logger.trace(`Configuration file not found at path "${configPath ?? this.SPENDING_PLANS_CONFIG_FILE}"`); + return []; + } + try { + const rawData = fs.readFileSync(configPath, 'utf-8'); + return JSON.parse(rawData) as SpendingPlanConfig[]; + } catch (error: any) { + throw new Error(`Failed to parse JSON from ${configPath}: ${error.message}`); + } + } + + /** + * Validates the spending plan configuration. + * + * @param {SpendingPlanConfig[]} spendingPlans - The spending plan configurations to validate. + * @throws {Error} If any spending plan configuration is invalid. + * @private + */ + private validateSpendingPlanConfig(spendingPlans: SpendingPlanConfig[]): void { + for (const plan of spendingPlans) { + if (!isValidSpendingPlanConfig(plan)) { + throw new Error(`Invalid spending plan configuration: ${JSON.stringify(plan)}`); + } + } + } + + /** + * Deletes obsolete HBAR spending plans from the database. + * + * @param {IDetailedHbarSpendingPlan[]} existingPlans - The existing HBAR spending plans in the database. + * @param {SpendingPlanConfig[]} spendingPlanConfigs - The current spending plan configurations. + * @param {RequestDetails} requestDetails - The details of the current request. + * @returns {Promise} - A promise that resolves with the number of plans deleted. + * @private + */ + private async deleteObsoletePlans( + existingPlans: IDetailedHbarSpendingPlan[], + spendingPlanConfigs: SpendingPlanConfig[], + requestDetails: RequestDetails, + ): Promise { + const plansToDelete = existingPlans.filter((plan) => !spendingPlanConfigs.some((spc) => spc.id === plan.id)); + for (const { id } of plansToDelete) { + this.logger.info( + `Deleting HBAR spending plan with ID "${id}", as it is no longer in the spending plan configuration...`, + ); + await this.hbarSpendingPlanRepository.delete(id, requestDetails); + await this.ethAddressHbarSpendingPlanRepository.deleteAllByPlanId( + id, + 'populatePreconfiguredSpendingPlans', + requestDetails, + ); + await this.ipAddressHbarSpendingPlanRepository.deleteAllByPlanId( + id, + 'populatePreconfiguredSpendingPlans', + requestDetails, + ); + } + return plansToDelete.length; + } + + /** + * Adds new HBAR spending plans to the database. + * + * @param {SpendingPlanConfig[]} spendingPlanConfigs - The current spending plan configurations. + * @param {IDetailedHbarSpendingPlan[]} existingPlans - The existing HBAR spending plans in the database. + * @param {RequestDetails} requestDetails - The details of the current request. + * @returns {Promise} - A promise that resolves with the number of plans added. + * @private + */ + private async addNewPlans( + spendingPlanConfigs: SpendingPlanConfig[], + existingPlans: IDetailedHbarSpendingPlan[], + requestDetails: RequestDetails, + ): Promise { + const plansToAdd = spendingPlanConfigs.filter((spc) => !existingPlans.some((plan) => plan.id === spc.id)); + for (const { id, name, subscriptionTier } of plansToAdd) { + await this.hbarSpendingPlanRepository.create(subscriptionTier, requestDetails, this.TTL, id); + this.logger.info( + `Created HBAR spending plan "${name}" with ID "${id}" and subscriptionTier "${subscriptionTier}"`, + ); + } + return plansToAdd.length; + } + + /** + * Updates the associations of HBAR spending plans with ETH and IP addresses. + * + * @param {SpendingPlanConfig[]} spendingPlanConfigs - The current spending plan configurations. + * @param {RequestDetails} requestDetails - The details of the current request. + * @returns {Promise} - A promise that resolves when the operation is complete. + * @private + */ + private async updatePlanAssociations( + spendingPlanConfigs: SpendingPlanConfig[], + requestDetails: RequestDetails, + ): Promise { + for (const planConfig of spendingPlanConfigs) { + this.logger.trace( + `Updating associations for HBAR spending plan '${planConfig.name}' with ID ${planConfig.id}...`, + ); + await this.deleteObsoleteEthAddressAssociations(planConfig, requestDetails); + await this.deleteObsoleteIpAddressAssociations(planConfig, requestDetails); + await this.updateEthAddressAssociations(planConfig, requestDetails); + await this.updateIpAddressAssociations(planConfig, requestDetails); + } + } + + /** + * Updates the associations of an HBAR spending plan with ETH addresses. + * + * @param {SpendingPlanConfig} planConfig - The spending plan configuration. + * @param {RequestDetails} requestDetails - The details of the current request. + * @returns {Promise} - A promise that resolves when the operation is complete. + * @private + */ + private async updateEthAddressAssociations( + planConfig: SpendingPlanConfig, + requestDetails: RequestDetails, + ): Promise { + const currentEthAddresses = await this.ethAddressHbarSpendingPlanRepository + .findAllByPlanId(planConfig.id, 'populatePreconfiguredSpendingPlans', requestDetails) + .then((ethAddressPlans) => ethAddressPlans.map((plan) => plan.ethAddress)); + + const addressesToDelete = currentEthAddresses.filter( + (ethAddress) => !planConfig.ethAddresses?.includes(ethAddress), + ); + await Promise.all( + addressesToDelete.map(async (ethAddress) => { + await this.ethAddressHbarSpendingPlanRepository.delete(ethAddress, requestDetails); + this.logger.info( + `Removed association between ETH address ${ethAddress} and HBAR spending plan '${planConfig.name}'`, + ); + }), + ); + + const addressesToAdd = + planConfig.ethAddresses?.filter((ethAddress) => !currentEthAddresses.includes(ethAddress)) || []; + await Promise.all( + addressesToAdd.map(async (ethAddress) => { + await this.ethAddressHbarSpendingPlanRepository.save( + { ethAddress, planId: planConfig.id }, + requestDetails, + this.TTL, + ); + this.logger.info(`Associated HBAR spending plan '${planConfig.name}' with ETH address ${ethAddress}`); + }), + ); + } + + /** + * Updates the associations of an HBAR spending plan with IP addresses. + * + * @param {SpendingPlanConfig} planConfig - The spending plan configuration. + * @param {RequestDetails} requestDetails - The details of the current request. + * @returns {Promise} - A promise that resolves when the operation is complete. + * @private + */ + private async updateIpAddressAssociations( + planConfig: SpendingPlanConfig, + requestDetails: RequestDetails, + ): Promise { + const currentIpAddresses = await this.ipAddressHbarSpendingPlanRepository + .findAllByPlanId(planConfig.id, 'populatePreconfiguredSpendingPlans', requestDetails) + .then((ipAddressPlans) => ipAddressPlans.map((plan) => plan.ipAddress)); + + const addressesToDelete = currentIpAddresses.filter((ipAddress) => !planConfig.ipAddresses?.includes(ipAddress)); + await Promise.all( + addressesToDelete.map(async (ipAddress) => { + await this.ipAddressHbarSpendingPlanRepository.delete(ipAddress, requestDetails); + this.logger.info(`Removed association between IP address and HBAR spending plan '${planConfig.name}'`); + }), + ); + + const addressesToAdd = planConfig.ipAddresses?.filter((ipAddress) => !currentIpAddresses.includes(ipAddress)) || []; + await Promise.all( + addressesToAdd.map(async (ipAddress) => { + await this.ipAddressHbarSpendingPlanRepository.save( + { ipAddress, planId: planConfig.id }, + requestDetails, + this.TTL, + ); + this.logger.info(`Associated HBAR spending plan '${planConfig.name}' with IP address`); + }), + ); + } + + /** + * Deletes obsolete ETH address associations from the cache. + * + * For example, if an ETH address is associated with a plan different from the one in the {@link SPENDING_PLANS_CONFIG_FILE}, + * the association is deleted from the cache to allow the new association from the configuration file to take effect. + * + * @param {SpendingPlanConfig} planConfig - The spending plan configuration. + * @param {RequestDetails} requestDetails - The details of the current request. + * @private + */ + private async deleteObsoleteEthAddressAssociations(planConfig: SpendingPlanConfig, requestDetails: RequestDetails) { + for (const ethAddress of planConfig.ethAddresses || []) { + const exists = await this.ethAddressHbarSpendingPlanRepository.existsByAddress(ethAddress, requestDetails); + if (exists) { + const ethAddressPlan = await this.ethAddressHbarSpendingPlanRepository.findByAddress( + ethAddress, + requestDetails, + ); + if (ethAddressPlan.planId !== planConfig.id) { + this.logger.info( + `Deleting association between ETH address ${ethAddress} and HBAR spending plan '${planConfig.name}'`, + ); + await this.ethAddressHbarSpendingPlanRepository.delete(ethAddress, requestDetails); + } + } + } + } + + /** + * Deletes obsolete IP address associations from the cache. + * + * For example, if an IP address is associated with a plan different from the one in the {@link SPENDING_PLANS_CONFIG_FILE}, + * the association is deleted from the cache to allow the new association from the configuration file to take effect. + * + * @param {SpendingPlanConfig} planConfig - The spending plan configuration. + * @param {RequestDetails} requestDetails - The details of the current request. + * @private + */ + private async deleteObsoleteIpAddressAssociations(planConfig: SpendingPlanConfig, requestDetails: RequestDetails) { + for (const ipAddress of planConfig.ipAddresses || []) { + const exists = await this.ipAddressHbarSpendingPlanRepository.existsByAddress(ipAddress, requestDetails); + if (exists) { + const ipAddressPlan = await this.ipAddressHbarSpendingPlanRepository.findByAddress(ipAddress, requestDetails); + if (ipAddressPlan.planId !== planConfig.id) { + this.logger.info(`Deleting association between IP address and HBAR spending plan '${planConfig.name}'`); + await this.ipAddressHbarSpendingPlanRepository.delete(ipAddress, requestDetails); + } + } + } + } +} diff --git a/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts b/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts index e1c72c5c60..e5ebeac3ac 100644 --- a/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts +++ b/packages/relay/src/lib/db/entities/hbarLimiter/hbarSpendingPlan.ts @@ -20,11 +20,11 @@ import { IDetailedHbarSpendingPlan } from '../../types/hbarLimiter/hbarSpendingPlan'; import { HbarSpendingRecord } from './hbarSpendingRecord'; -import { SubscriptionType } from '../../types/hbarLimiter/subscriptionType'; +import { SubscriptionTier } from '../../types/hbarLimiter/subscriptionTier'; export class HbarSpendingPlan implements IDetailedHbarSpendingPlan { id: string; - subscriptionType: SubscriptionType; + subscriptionTier: SubscriptionTier; createdAt: Date; active: boolean; spendingHistory: HbarSpendingRecord[]; @@ -32,7 +32,7 @@ export class HbarSpendingPlan implements IDetailedHbarSpendingPlan { constructor(data: IDetailedHbarSpendingPlan) { this.id = data.id; - this.subscriptionType = data.subscriptionType; + this.subscriptionTier = data.subscriptionTier; this.createdAt = new Date(data.createdAt); this.active = data.active; this.spendingHistory = data.spendingHistory?.map((spending) => new HbarSpendingRecord(spending)) || []; diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts index 00022c1625..41076ca2b0 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.ts @@ -27,7 +27,6 @@ import { RequestDetails } from '../../../types'; export class EthAddressHbarSpendingPlanRepository { private readonly collectionKey = 'ethAddressHbarSpendingPlan'; - private readonly oneDayInMillis = 24 * 60 * 60 * 1000; /** * The cache service used for storing data. @@ -46,6 +45,63 @@ export class EthAddressHbarSpendingPlanRepository { this.logger = logger; } + /** + * Checks if an {@link EthAddressHbarSpendingPlan} exists for an ETH address. + * + * @param {string} ethAddress - The ETH address to check for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - A promise that resolves with a boolean indicating if the plan exists. + */ + async existsByAddress(ethAddress: string, requestDetails: RequestDetails): Promise { + const key = this.getKey(ethAddress); + const addressPlan = await this.cache.getAsync(key, 'existsByAddress', requestDetails); + return !!addressPlan; + } + + /** + * Finds all ETH addresses associated with a spending plan. + * @param {string} planId - The ID of the spending plan to search for. + * @param {string} callingMethod - The method calling this function. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - A promise that resolves with an array of associated plans. + */ + async findAllByPlanId( + planId: string, + callingMethod: string, + requestDetails: RequestDetails, + ): Promise { + const ethAddressPlans: EthAddressHbarSpendingPlan[] = []; + const key = this.getKey('*'); + const keys = await this.cache.keys(key, callingMethod, requestDetails); + for (const key of keys) { + const addressPlan = await this.cache.getAsync(key, callingMethod, requestDetails); + if (addressPlan?.planId === planId) { + ethAddressPlans.push(new EthAddressHbarSpendingPlan(addressPlan)); + } + } + return ethAddressPlans; + } + + /** + * Deletes all ETH addresses associated with a spending plan. + * @param planId - The ID of the spending plan to search for. + * @param callingMethod - The method calling this function. + * @param requestDetails - The request details for logging and tracking. + */ + async deleteAllByPlanId(planId: string, callingMethod: string, requestDetails: RequestDetails): Promise { + const key = this.getKey('*'); + const keys = await this.cache.keys(key, callingMethod, requestDetails); + for (const key of keys) { + const addressPlan = await this.cache.getAsync(key, callingMethod, requestDetails); + if (addressPlan?.planId === planId) { + this.logger.trace( + `${requestDetails.formattedRequestId} Removing ETH address ${addressPlan.ethAddress} from HbarSpendingPlan with ID ${planId}`, + ); + await this.cache.delete(key, callingMethod, requestDetails); + } + } + } + /** * Finds an {@link EthAddressHbarSpendingPlan} for an ETH address. * @@ -59,7 +115,9 @@ export class EthAddressHbarSpendingPlanRepository { if (!addressPlan) { throw new EthAddressHbarSpendingPlanNotFoundError(ethAddress); } - this.logger.trace(`Retrieved EthAddressHbarSpendingPlan with address ${ethAddress}`); + this.logger.trace( + `${requestDetails.formattedRequestId} Retrieved link between ETH address ${ethAddress} and HbarSpendingPlan with ID ${addressPlan.planId}`, + ); return new EthAddressHbarSpendingPlan(addressPlan); } @@ -74,7 +132,9 @@ export class EthAddressHbarSpendingPlanRepository { async save(addressPlan: IEthAddressHbarSpendingPlan, requestDetails: RequestDetails, ttl: number): Promise { const key = this.getKey(addressPlan.ethAddress); await this.cache.set(key, addressPlan, 'save', requestDetails, ttl); - this.logger.trace(`Saved EthAddressHbarSpendingPlan with address ${addressPlan.ethAddress}`); + this.logger.trace( + `${requestDetails.formattedRequestId} Linked ETH address ${addressPlan.ethAddress} to HbarSpendingPlan with ID ${addressPlan.planId}`, + ); } /** @@ -86,8 +146,12 @@ export class EthAddressHbarSpendingPlanRepository { */ async delete(ethAddress: string, requestDetails: RequestDetails): Promise { const key = this.getKey(ethAddress); + const ethAddressPlan = await this.cache.getAsync(key, 'delete', requestDetails); await this.cache.delete(key, 'delete', requestDetails); - this.logger.trace(`Deleted EthAddressHbarSpendingPlan with address ${ethAddress}`); + const errorMessage = ethAddressPlan + ? `Removed ETH address ${ethAddress} from HbarSpendingPlan with ID ${ethAddressPlan.planId}` + : `Trying to remove ETH address ${ethAddress}, which is not linked to a spending plan`; + this.logger.trace(`${requestDetails.formattedRequestId} ${errorMessage}`); } /** @@ -97,6 +161,6 @@ export class EthAddressHbarSpendingPlanRepository { * @private */ private getKey(ethAddress: string): string { - return `${this.collectionKey}:${ethAddress}`; + return `${this.collectionKey}:${ethAddress?.trim().toLowerCase()}`; } } diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts index 10b16f3cd2..71b7184afe 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository.ts @@ -25,7 +25,7 @@ import { CacheService } from '../../../services/cacheService/cacheService'; import { HbarSpendingPlanNotActiveError, HbarSpendingPlanNotFoundError } from '../../types/hbarLimiter/errors'; import { IDetailedHbarSpendingPlan, IHbarSpendingPlan } from '../../types/hbarLimiter/hbarSpendingPlan'; import { HbarSpendingRecord } from '../../entities/hbarLimiter/hbarSpendingRecord'; -import { SubscriptionType } from '../../types/hbarLimiter/subscriptionType'; +import { SubscriptionTier } from '../../types/hbarLimiter/subscriptionTier'; import { HbarSpendingPlan } from '../../entities/hbarLimiter/hbarSpendingPlan'; import { RequestDetails } from '../../../types'; @@ -61,7 +61,7 @@ export class HbarSpendingPlanRepository { if (!plan) { throw new HbarSpendingPlanNotFoundError(id); } - this.logger.trace(`Retrieved subscription with ID ${id}`); + this.logger.trace(`${requestDetails.formattedRequestId} Retrieved subscription with ID ${id}`); return { ...plan, createdAt: new Date(plan.createdAt), @@ -85,30 +85,38 @@ export class HbarSpendingPlanRepository { /** * Creates a new HBar spending plan. - * @param {SubscriptionType} subscriptionType - The subscription type of the plan to create. + * @param {SubscriptionTier} subscriptionTier - The subscription tier of the plan to create. * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @param {number} ttl - The time-to-live for the plan in milliseconds. + * @param {string} planId - The ID to assign to the plan. (default: generated UUID) * @returns {Promise} - The created HBar spending plan object. */ async create( - subscriptionType: SubscriptionType, + subscriptionTier: SubscriptionTier, requestDetails: RequestDetails, ttl: number, + planId?: string, ): Promise { const plan: IDetailedHbarSpendingPlan = { - id: uuidV4(randomBytes(16)), - subscriptionType, + id: planId ?? uuidV4(randomBytes(16)), + subscriptionTier: subscriptionTier, createdAt: new Date(), active: true, spendingHistory: [], amountSpent: 0, }; - this.logger.trace(`Creating HbarSpendingPlan with ID ${plan.id}...`); + this.logger.trace(`${requestDetails.formattedRequestId} Creating HbarSpendingPlan with ID ${plan.id}...`); const key = this.getKey(plan.id); await this.cache.set(key, plan, 'create', requestDetails, ttl); return new HbarSpendingPlan(plan); } + async delete(id: string, requestDetails: RequestDetails): Promise { + this.logger.trace(`${requestDetails.formattedRequestId} Deleting HbarSpendingPlan with ID ${id}...`); + const key = this.getKey(id); + await this.cache.delete(key, 'delete', requestDetails); + } + /** * Verify that an HBar spending plan exists and is active. * @param {string} id - The ID of the plan. @@ -131,7 +139,9 @@ export class HbarSpendingPlanRepository { async getSpendingHistory(id: string, requestDetails: RequestDetails): Promise { await this.checkExistsAndActive(id, requestDetails); - this.logger.trace(`Retrieving spending history for HbarSpendingPlan with ID ${id}...`); + this.logger.trace( + `${requestDetails.formattedRequestId} Retrieving spending history for HbarSpendingPlan with ID ${id}...`, + ); const key = this.getSpendingHistoryKey(id); const spendingHistory = await this.cache.lRange( key, @@ -153,7 +163,9 @@ export class HbarSpendingPlanRepository { async addAmountToSpendingHistory(id: string, amount: number, requestDetails: RequestDetails): Promise { await this.checkExistsAndActive(id, requestDetails); - this.logger.trace(`Adding ${amount} to spending history for HbarSpendingPlan with ID ${id}...`); + this.logger.trace( + `${requestDetails.formattedRequestId} Adding ${amount} to spending history for HbarSpendingPlan with ID ${id}...`, + ); const key = this.getSpendingHistoryKey(id); const entry: IHbarSpendingRecord = { amount, timestamp: new Date() }; return this.cache.rPush(key, entry, 'addAmountToSpendingHistory', requestDetails); @@ -168,7 +180,9 @@ export class HbarSpendingPlanRepository { async getAmountSpent(id: string, requestDetails: RequestDetails): Promise { await this.checkExistsAndActive(id, requestDetails); - this.logger.trace(`Retrieving amountSpent for HbarSpendingPlan with ID ${id}...`); + this.logger.trace( + `${requestDetails.formattedRequestId} Retrieving amountSpent for HbarSpendingPlan with ID ${id}...`, + ); const key = this.getAmountSpentKey(id); return this.cache .getAsync(key, 'getAmountSpent', requestDetails) @@ -180,11 +194,15 @@ export class HbarSpendingPlanRepository { * @returns {Promise} - A promise that resolves when the operation is complete. */ async resetAmountSpentOfAllPlans(requestDetails: RequestDetails): Promise { - this.logger.trace('Resetting the `amountSpent` entries for all HbarSpendingPlans...'); + this.logger.trace( + `${requestDetails.formattedRequestId} Resetting the \`amountSpent\` entries for all HbarSpendingPlans...`, + ); const callerMethod = this.resetAmountSpentOfAllPlans.name; const keys = await this.cache.keys(this.getAmountSpentKey('*'), callerMethod, requestDetails); await Promise.all(keys.map((key) => this.cache.delete(key, callerMethod, requestDetails))); - this.logger.trace(`Successfully reset ${keys.length} "amountSpent" entries for HbarSpendingPlans.`); + this.logger.trace( + `${requestDetails.formattedRequestId} Successfully reset ${keys.length} "amountSpent" entries for HbarSpendingPlans.`, + ); } /** @@ -200,32 +218,36 @@ export class HbarSpendingPlanRepository { const key = this.getAmountSpentKey(id); if (!(await this.cache.getAsync(key, 'addToAmountSpent', requestDetails))) { - this.logger.trace(`No spending yet for HbarSpendingPlan with ID ${id}, setting amountSpent to ${amount}...`); + this.logger.trace( + `${requestDetails.formattedRequestId} No spending yet for HbarSpendingPlan with ID ${id}, setting amountSpent to ${amount}...`, + ); await this.cache.set(key, amount, 'addToAmountSpent', requestDetails, ttl); } else { - this.logger.trace(`Adding ${amount} to amountSpent for HbarSpendingPlan with ID ${id}...`); + this.logger.trace( + `${requestDetails.formattedRequestId} Adding ${amount} to amountSpent for HbarSpendingPlan with ID ${id}...`, + ); await this.cache.incrBy(key, amount, 'addToAmountSpent', requestDetails); } } /** - * Finds all active HBar spending plans by subscription type. - * @param {SubscriptionType} subscriptionType - The subscription type to filter by. + * Finds all active HBar spending plans by subscription tier. + * @param {SubscriptionTier[]} tiers - The subscription tiers to filter by. * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @returns {Promise} - A promise that resolves with the active spending plans. */ - async findAllActiveBySubscriptionType( - subscriptionType: SubscriptionType, + async findAllActiveBySubscriptionTier( + tiers: SubscriptionTier[], requestDetails: RequestDetails, ): Promise { - const callerMethod = this.findAllActiveBySubscriptionType.name; + const callerMethod = this.findAllActiveBySubscriptionTier.name; const keys = await this.cache.keys(this.getKey('*'), callerMethod, requestDetails); const plans = await Promise.all( keys.map((key) => this.cache.getAsync(key, callerMethod, requestDetails)), ); return Promise.all( plans - .filter((plan) => plan.subscriptionType === subscriptionType && plan.active) + .filter((plan) => tiers.includes(plan.subscriptionTier) && plan.active) .map( async (plan) => new HbarSpendingPlan({ diff --git a/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts b/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts index 75957defe1..5e6892b1f5 100644 --- a/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts +++ b/packages/relay/src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.ts @@ -45,6 +45,63 @@ export class IPAddressHbarSpendingPlanRepository { this.logger = logger; } + /** + * Checks if an {@link IPAddressHbarSpendingPlan} exists for an IP address. + * + * @param {string} ipAddress - The IP address to check for. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - A promise that resolves with a boolean indicating if the plan exists. + */ + async existsByAddress(ipAddress: string, requestDetails: RequestDetails): Promise { + const key = this.getKey(ipAddress); + const addressPlan = await this.cache.getAsync(key, 'existsByAddress', requestDetails); + return !!addressPlan; + } + + /** + * Finds all IP addresses associated with a spending plan. + * @param {string} planId - The ID of the spending plan to search for. + * @param {string} callingMethod - The method calling this function. + * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * @returns {Promise} - A promise that resolves with an array of associated plans. + */ + async findAllByPlanId( + planId: string, + callingMethod: string, + requestDetails: RequestDetails, + ): Promise { + const ipAddressPlans: IPAddressHbarSpendingPlan[] = []; + const key = this.getKey('*'); + const keys = await this.cache.keys(key, callingMethod, requestDetails); + for (const key of keys) { + const addressPlan = await this.cache.getAsync(key, callingMethod, requestDetails); + if (addressPlan?.planId === planId) { + ipAddressPlans.push(new IPAddressHbarSpendingPlan(addressPlan)); + } + } + return ipAddressPlans; + } + + /** + * Deletes all IP addresses associated with a spending plan. + * @param planId - The ID of the spending plan to search for. + * @param callingMethod - The method calling this function. + * @param requestDetails - The request details for logging and tracking. + */ + async deleteAllByPlanId(planId: string, callingMethod: string, requestDetails: RequestDetails): Promise { + const key = this.getKey('*'); + const keys = await this.cache.keys(key, callingMethod, requestDetails); + for (const key of keys) { + const addressPlan = await this.cache.getAsync(key, callingMethod, requestDetails); + if (addressPlan?.planId === planId) { + this.logger.trace( + `${requestDetails.formattedRequestId} Removing IP address from HbarSpendingPlan with ID ${planId}`, + ); + await this.cache.delete(key, callingMethod, requestDetails); + } + } + } + /** * Finds an {@link IPAddressHbarSpendingPlan} for an IP address. * @@ -58,7 +115,9 @@ export class IPAddressHbarSpendingPlanRepository { if (!addressPlan) { throw new IPAddressHbarSpendingPlanNotFoundError(ipAddress); } - this.logger.trace(`Retrieved link between IP address and HbarSpendingPlan with ID ${addressPlan.planId}`); + this.logger.trace( + `${requestDetails.formattedRequestId} Retrieved link between IP address and HbarSpendingPlan with ID ${addressPlan.planId}`, + ); return new IPAddressHbarSpendingPlan(addressPlan); } @@ -73,13 +132,16 @@ export class IPAddressHbarSpendingPlanRepository { async save(addressPlan: IIPAddressHbarSpendingPlan, requestDetails: RequestDetails, ttl: number): Promise { const key = this.getKey(addressPlan.ipAddress); await this.cache.set(key, addressPlan, 'save', requestDetails, ttl); - this.logger.trace(`Linked new IP address to HbarSpendingPlan with ID ${addressPlan.planId}`); + this.logger.trace( + `${requestDetails.formattedRequestId} Linked new IP address to HbarSpendingPlan with ID ${addressPlan.planId}`, + ); } /** * Deletes an {@link IPAddressHbarSpendingPlan} from the cache, unlinking the plan from the IP address. * * @param {string} ipAddress - The IP address to unlink the plan from. + * @param {RequestDetails} requestDetails - The request details used for logging and tracking. * @returns {Promise} - A promise that resolves when the IP address is unlinked from the plan. */ async delete(ipAddress: string, requestDetails: RequestDetails): Promise { @@ -89,7 +151,7 @@ export class IPAddressHbarSpendingPlanRepository { const errorMessage = ipAddressSpendingPlan ? `Removed IP address from HbarSpendingPlan with ID ${ipAddressSpendingPlan.planId}` : `Trying to remove an IP address, which is not linked to a spending plan`; - this.logger.trace(errorMessage); + this.logger.trace(`${requestDetails.formattedRequestId} ${errorMessage}`); } /** diff --git a/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts b/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts index cdf4a5806d..fe9654ccfb 100644 --- a/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts +++ b/packages/relay/src/lib/db/types/hbarLimiter/hbarSpendingPlan.ts @@ -19,11 +19,11 @@ */ import { IHbarSpendingRecord } from './hbarSpendingRecord'; -import { SubscriptionType } from './subscriptionType'; +import { SubscriptionTier } from './subscriptionTier'; export interface IHbarSpendingPlan { id: string; - subscriptionType: SubscriptionType; + subscriptionTier: SubscriptionTier; createdAt: Date; active: boolean; } diff --git a/packages/relay/src/lib/db/types/hbarLimiter/subscriptionType.ts b/packages/relay/src/lib/db/types/hbarLimiter/subscriptionTier.ts similarity index 96% rename from packages/relay/src/lib/db/types/hbarLimiter/subscriptionType.ts rename to packages/relay/src/lib/db/types/hbarLimiter/subscriptionTier.ts index a2e7236a4b..58e0b3e8de 100644 --- a/packages/relay/src/lib/db/types/hbarLimiter/subscriptionType.ts +++ b/packages/relay/src/lib/db/types/hbarLimiter/subscriptionTier.ts @@ -18,7 +18,7 @@ * */ -export enum SubscriptionType { +export enum SubscriptionTier { /** * General users (default) */ diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index 48d603d9d5..7737563fae 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -39,6 +39,10 @@ import MetricService from './services/metricService/metricService'; import { CacheService } from './services/cacheService/cacheService'; import { RequestDetails } from './types'; import { Utils } from '../utils'; +import { HbarSpendingPlanConfigService } from './config/hbarSpendingPlanConfigService'; +import { HbarSpendingPlanRepository } from './db/repositories/hbarLimiter/hbarSpendingPlanRepository'; +import { EthAddressHbarSpendingPlanRepository } from './db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; +import { IPAddressHbarSpendingPlanRepository } from './db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; dotenv.config({ path: findConfig('.env') || '' }); @@ -92,6 +96,13 @@ export class RelayImpl implements Relay { */ private readonly cacheService: CacheService; + /** + * @private + * @readonly + * @property {HbarSpendingPlanConfigService} hbarSpendingPlanConfigService - The service responsible for managing HBAR spending plans. + */ + private readonly hbarSpendingPlanConfigService: HbarSpendingPlanConfigService; + /** * @private * @readonly @@ -115,7 +126,10 @@ export class RelayImpl implements Relay { * @param {Logger} logger - Logger instance for logging system messages. * @param {Registry} register - Registry instance for registering metrics. */ - constructor(logger: Logger, register: Registry) { + constructor( + private readonly logger: Logger, + register: Registry, + ) { logger.info('Configurations successfully loaded'); const hederaNetwork: string = (process.env.HEDERA_NETWORK || '{}').toLowerCase(); @@ -161,15 +175,55 @@ export class RelayImpl implements Relay { this.cacheService, ); + const hbarSpendingPlanRepository = new HbarSpendingPlanRepository( + this.cacheService, + logger.child({ name: 'hbar-spending-plan-repository' }), + ); + + const ethAddressHbarSpendingPlanRepository = new EthAddressHbarSpendingPlanRepository( + this.cacheService, + logger.child({ name: 'eth-address-hbar-spending-plan-repository' }), + ); + + const ipAddressHbarSpendingPlanRepository = new IPAddressHbarSpendingPlanRepository( + this.cacheService, + logger.child({ name: 'ip-address-hbar-spending-plan-repository' }), + ); + + this.hbarSpendingPlanConfigService = new HbarSpendingPlanConfigService( + logger.child({ name: 'hbar-spending-plan-config-service' }), + hbarSpendingPlanRepository, + ethAddressHbarSpendingPlanRepository, + ipAddressHbarSpendingPlanRepository, + ); + if (process.env.SUBSCRIPTIONS_ENABLED && process.env.SUBSCRIPTIONS_ENABLED === 'true') { const poller = new Poller(this.ethImpl, logger.child({ name: `poller` }), register); this.subImpl = new SubscriptionController(poller, logger.child({ name: `subscr-ctrl` }), register); } this.initOperatorMetric(this.clientMain, this.mirrorNodeClient, logger, register); + + this.populatePreconfiguredSpendingPlans().then(); + logger.info('Relay running with chainId=%s', chainId); } + /** + * Populates pre-configured spending plans from a configuration file. + * @returns {Promise} A promise that resolves when the spending plans have been successfully populated. + */ + private async populatePreconfiguredSpendingPlans(): Promise { + return this.hbarSpendingPlanConfigService + .populatePreconfiguredSpendingPlans() + .then((plansUpdated) => { + if (plansUpdated > 0) { + this.logger.info('Pre-configured spending plans populated successfully'); + } + }) + .catch((e) => this.logger.warn(`Failed to load pre-configured spending plans: ${e.message}`)); + } + /** * Initialize operator account metrics * @param {Client} clientMain diff --git a/packages/relay/src/lib/services/hbarLimitService/index.ts b/packages/relay/src/lib/services/hbarLimitService/index.ts index 1758796f22..7cd11af069 100644 --- a/packages/relay/src/lib/services/hbarLimitService/index.ts +++ b/packages/relay/src/lib/services/hbarLimitService/index.ts @@ -21,7 +21,7 @@ import { Logger } from 'pino'; import { Counter, Gauge, Registry } from 'prom-client'; import { IHbarLimitService } from './IHbarLimitService'; -import { SubscriptionType } from '../../db/types/hbarLimiter/subscriptionType'; +import { SubscriptionTier } from '../../db/types/hbarLimiter/subscriptionTier'; import { IDetailedHbarSpendingPlan } from '../../db/types/hbarLimiter/hbarSpendingPlan'; import { HbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/hbarSpendingPlanRepository'; import { EthAddressHbarSpendingPlanRepository } from '../../db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; @@ -31,7 +31,7 @@ import constants from '../../constants'; import { Hbar } from '@hashgraph/sdk'; export class HbarLimitService implements IHbarLimitService { - static readonly TIER_LIMITS: Record = { + static readonly TIER_LIMITS: Record = { BASIC: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_BASIC), EXTENDED: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_EXTENDED), PRIVILEGED: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_PRIVILEGED), @@ -58,13 +58,13 @@ export class HbarLimitService implements IHbarLimitService { * * @private */ - private readonly dailyUniqueSpendingPlansCounter: Record; + private readonly dailyUniqueSpendingPlansCounter: Record; /** * Tracks the average daily spending plan usages. * @private */ - private readonly averageDailySpendingPlanUsagesGauge: Record; + private readonly averageDailySpendingPlanUsagesGauge: Record; /** * The remaining budget for the rate limiter. @@ -109,32 +109,32 @@ export class HbarLimitService implements IHbarLimitService { }); this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); - this.dailyUniqueSpendingPlansCounter = Object.values(SubscriptionType).reduce( + this.dailyUniqueSpendingPlansCounter = Object.values(SubscriptionTier).reduce( (acc, type) => { const dailyUniqueSpendingPlansCounterName = `daily_unique_spending_plans_counter_${type.toLowerCase()}`; this.register.removeSingleMetric(dailyUniqueSpendingPlansCounterName); acc[type] = new Counter({ name: dailyUniqueSpendingPlansCounterName, - help: `Tracks the number of unique spending plans used daily for ${type} subscription type`, + help: `Tracks the number of unique spending plans used daily for ${type} subscription tier`, registers: [register], }); return acc; }, - {} as Record, + {} as Record, ); - this.averageDailySpendingPlanUsagesGauge = Object.values(SubscriptionType).reduce( + this.averageDailySpendingPlanUsagesGauge = Object.values(SubscriptionTier).reduce( (acc, type) => { const averageDailySpendingGaugeName = `average_daily_spending_plan_usages_gauge_${type.toLowerCase()}`; this.register.removeSingleMetric(averageDailySpendingGaugeName); acc[type] = new Gauge({ name: averageDailySpendingGaugeName, - help: `Tracks the average daily spending plan usages for ${type} subscription type`, + help: `Tracks the average daily spending plan usages for ${type} subscription tier`, registers: [register], }); return acc; }, - {} as Record, + {} as Record, ); } @@ -185,7 +185,7 @@ export class HbarLimitService implements IHbarLimitService { spendingPlan = await this.createBasicSpendingPlan(ethAddress, requestDetails); } - const spendingLimit = HbarLimitService.TIER_LIMITS[spendingPlan.subscriptionType].toTinybars(); + const spendingLimit = HbarLimitService.TIER_LIMITS[spendingPlan.subscriptionTier].toTinybars(); const exceedsLimit = spendingLimit.lte(spendingPlan.amountSpent) || spendingLimit.lt(spendingPlan.amountSpent + estimatedTxFee); @@ -224,7 +224,7 @@ export class HbarLimitService implements IHbarLimitService { // Check if the spending plan is being used for the first time today if (spendingPlan.amountSpent === 0) { - this.dailyUniqueSpendingPlansCounter[spendingPlan.subscriptionType].inc(1); + this.dailyUniqueSpendingPlansCounter[spendingPlan.subscriptionTier].inc(1); } await this.hbarSpendingPlanRepository.addToAmountSpent(spendingPlan.id, cost, requestDetails, this.limitDuration); @@ -232,7 +232,7 @@ export class HbarLimitService implements IHbarLimitService { this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); // Done asynchronously in the background - this.updateAverageDailyUsagePerSubscriptionType(spendingPlan.subscriptionType, requestDetails).then(); + this.updateAverageDailyUsagePerSubscriptionTier(spendingPlan.subscriptionTier, requestDetails).then(); this.logger.trace( `${requestDetails.formattedRequestId} HBAR rate limit expense update: cost=${cost} tℏ, remainingBudget=${this.remainingBudget}`, @@ -272,22 +272,22 @@ export class HbarLimitService implements IHbarLimitService { } /** - * Updates the average daily usage per subscription type. - * @param {SubscriptionType} subscriptionType - The subscription type to update the average daily usage for. + * Updates the average daily usage per subscription tier. + * @param {SubscriptionTier} subscriptionTier - The subscription tier to update the average daily usage for. * @param {RequestDetails} requestDetails - The request details for logging and tracking. * @private {Promise} - A promise that resolves when the average daily usage has been updated. */ - private async updateAverageDailyUsagePerSubscriptionType( - subscriptionType: SubscriptionType, + private async updateAverageDailyUsagePerSubscriptionTier( + subscriptionTier: SubscriptionTier, requestDetails: RequestDetails, ): Promise { - const plans = await this.hbarSpendingPlanRepository.findAllActiveBySubscriptionType( - subscriptionType, + const plans = await this.hbarSpendingPlanRepository.findAllActiveBySubscriptionTier( + [subscriptionTier], requestDetails, ); const totalUsage = plans.reduce((total, plan) => total + plan.amountSpent, 0); const averageUsage = Math.round(totalUsage / plans.length); - this.averageDailySpendingPlanUsagesGauge[subscriptionType].set(averageUsage); + this.averageDailySpendingPlanUsagesGauge[subscriptionTier].set(averageUsage); } /** @@ -415,7 +415,7 @@ export class HbarLimitService implements IHbarLimitService { } const spendingPlan = await this.hbarSpendingPlanRepository.create( - SubscriptionType.BASIC, + SubscriptionTier.BASIC, requestDetails, this.limitDuration, ); diff --git a/packages/relay/src/lib/types/spendingPlanConfig.ts b/packages/relay/src/lib/types/spendingPlanConfig.ts new file mode 100644 index 0000000000..53aaebd928 --- /dev/null +++ b/packages/relay/src/lib/types/spendingPlanConfig.ts @@ -0,0 +1,76 @@ +/* + * + * Hedera JSON RPC Relay + * + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { SubscriptionTier } from '../db/types/hbarLimiter/subscriptionTier'; + +/** + * Represents the configuration for a spending plan. + * Pre-configured spending plans are specified in a `spendingPlansConfig.json` file in the root of the project. + * @interface SpendingPlanConfig + */ +export interface SpendingPlanConfig { + /** + * The unique identifier for the spending plan. + */ + id: string; + + /** + * The name of the spending plan. + * @type {string} + */ + name: string; + + /** + * ETH addresses associated with the spending plan. + * @type {string[]} + * @optional + */ + ethAddresses?: string[]; + + /** + * IP addresses associated with the spending plan. + * @type {string[]} + * @optional + */ + ipAddresses?: string[]; + + /** + * The subscription tier associated with the spending plan. + * @type {SubscriptionTier} + */ + subscriptionTier: SubscriptionTier; +} + +/** + * Determines if the provided object is a valid spending plan configuration. + * @param plan - The object to validate. + * @returns {boolean} - True if the object is a valid {@link SpendingPlanConfig}, false otherwise. + */ +export function isValidSpendingPlanConfig(plan: any): plan is SpendingPlanConfig { + return ( + plan && + typeof plan.id === 'string' && + typeof plan.name === 'string' && + typeof plan.subscriptionTier === 'string' && + Object.values(SubscriptionTier).includes(plan.subscriptionTier) && + ((Array.isArray(plan.ethAddresses) && plan.ethAddresses.length > 0) || + (Array.isArray(plan.ipAddresses) && plan.ipAddresses.length > 0)) + ); +} diff --git a/packages/relay/src/utils.ts b/packages/relay/src/utils.ts index 369b347d70..8eb96d73c9 100644 --- a/packages/relay/src/utils.ts +++ b/packages/relay/src/utils.ts @@ -23,6 +23,8 @@ import constants from './lib/constants'; import crypto from 'crypto'; export class Utils { + public static readonly IP_ADDRESS_REGEX = /\b((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}\b/g; + public static readonly addPercentageBufferToGasPrice = (gasPrice: number): number => { // converting to tinybar and afterward to weibar again is needed // in order to handle the possibility of an invalid floating number being calculated as a gas price diff --git a/packages/relay/tests/lib/clients/localLRUCache.spec.ts b/packages/relay/tests/lib/clients/localLRUCache.spec.ts index 340c542253..bea839445e 100644 --- a/packages/relay/tests/lib/clients/localLRUCache.spec.ts +++ b/packages/relay/tests/lib/clients/localLRUCache.spec.ts @@ -23,22 +23,22 @@ import chaiAsPromised from 'chai-as-promised'; import { Registry } from 'prom-client'; import pino from 'pino'; import { LocalLRUCache } from '../../../src/lib/clients'; -import { overrideEnvsInMochaDescribe } from '../../helpers'; +import { overrideEnvsInMochaDescribe, withOverriddenEnvsInMochaTest } from '../../helpers'; import { RequestDetails } from '../../../src/lib/types'; - -const logger = pino(); -const registry = new Registry(); -let localLRUCache: LocalLRUCache; - -const callingMethod = 'localLRUCacheTest'; +import sinon from 'sinon'; chai.use(chaiAsPromised); describe('LocalLRUCache Test Suite', async function () { this.timeout(10000); + const logger = pino(); + const registry = new Registry(); + const callingMethod = 'localLRUCacheTest'; const requestDetails = new RequestDetails({ requestId: 'localLRUCacheTest', ipAddress: '0.0.0.0' }); + let localLRUCache: LocalLRUCache; + this.beforeAll(() => { localLRUCache = new LocalLRUCache(logger.child({ name: `cache` }), registry); }); @@ -151,6 +151,29 @@ describe('LocalLRUCache Test Suite', async function () { const cacheValue = await customLocalLRUCache.get(key, callingMethod, requestDetails); expect(cacheValue).to.be.null; }); + + // set default cache ttl to 100ms to test the default ttl will be overridden by the ttl passed in set method + withOverriddenEnvsInMochaTest({ CACHE_TTL: '100' }, async () => { + it('it should set without TTL if -1 is passed for TTL', async () => { + const customLocalLRUCache = new LocalLRUCache(logger.child({ name: `cache` }), registry); + const lruCacheSpy = sinon.spy(customLocalLRUCache['cache']); + + const key = 'key'; + const value = { intField: 1, stringField: 'string', boolField: true, arrField: [1, 2, 3] }; + const ttl = -1; + + await customLocalLRUCache.set(key, value, callingMethod, requestDetails, ttl); + sinon.assert.calledOnceWithExactly(lruCacheSpy.set, key, value, { ttl: 0 }); + + const cachedValue = await customLocalLRUCache.get(key, callingMethod, requestDetails); + expect(cachedValue).equal(value); + + await new Promise((resolve) => setTimeout(resolve, customLocalLRUCache['options'].ttl + 100)); + + const cachedValueAfterTTL = await customLocalLRUCache.get(key, callingMethod, requestDetails); + expect(cachedValueAfterTTL).equal(value); + }); + }); }); describe('KEYS Test Suite', async function () { diff --git a/packages/relay/tests/lib/clients/redisCache.spec.ts b/packages/relay/tests/lib/clients/redisCache.spec.ts index b094e3d7c2..06ab4889a4 100644 --- a/packages/relay/tests/lib/clients/redisCache.spec.ts +++ b/packages/relay/tests/lib/clients/redisCache.spec.ts @@ -25,6 +25,8 @@ import { RedisCache } from '../../../src/lib/clients'; import { Registry } from 'prom-client'; import { useInMemoryRedisServer } from '../../helpers'; import { RequestDetails } from '../../../dist/lib/types'; +import sinon from 'sinon'; +import { RedisClientType } from 'redis'; chai.use(chaiAsPromised); @@ -37,11 +39,14 @@ describe('RedisCache Test Suite', async function () { const requestDetails = new RequestDetails({ requestId: 'localLRUCacheTest', ipAddress: '0.0.0.0' }); let redisCache: RedisCache; + let redisClientSpy: sinon.SinonSpiedInstance; useInMemoryRedisServer(logger, 6379); this.beforeAll(async () => { redisCache = new RedisCache(logger.child({ name: `cache` }), registry); + redisCache['options'].ttl = 100; // set default cache ttl to 100ms for testing + redisClientSpy = sinon.spy(redisCache['client']); }); this.beforeEach(async () => { @@ -49,6 +54,7 @@ describe('RedisCache Test Suite', async function () { await redisCache.connect(); } await redisCache.clear(); + sinon.resetHistory(); }); this.afterAll(async () => { @@ -109,6 +115,7 @@ describe('RedisCache Test Suite', async function () { const ttl = 100; await redisCache.set(key, value, callingMethod, requestDetails, ttl); + sinon.assert.calledOnceWithExactly(redisClientSpy.set, key, JSON.stringify(value), { PX: ttl }); const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).equal(value); @@ -125,6 +132,7 @@ describe('RedisCache Test Suite', async function () { const ttl = 1100; await redisCache.set(key, value, callingMethod, requestDetails, ttl); + sinon.assert.calledOnceWithExactly(redisClientSpy.set, key, JSON.stringify(value), { PX: ttl }); const cachedValue = await redisCache.get(key, callingMethod, requestDetails); expect(cachedValue).equal(value); @@ -134,6 +142,20 @@ describe('RedisCache Test Suite', async function () { const expiredValue = await redisCache.get(key, callingMethod, requestDetails); expect(expiredValue).to.be.null; }); + + it('it should set without TTL if -1 is passed for TTL', async () => { + const key = 'int'; + const value = 1; + const ttl = -1; + + await redisCache.set(key, value, callingMethod, requestDetails, ttl); + sinon.assert.calledOnceWithExactly(redisClientSpy.set, key, JSON.stringify(value)); + + const cachedValue = await redisCache.get(key, callingMethod, requestDetails); + expect(cachedValue).equal(value); + + await new Promise((resolve) => setTimeout(resolve, redisCache['options'].ttl)); + }); }); describe('MultiSet Test Suite', async function () { diff --git a/packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts b/packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts new file mode 100644 index 0000000000..cb1dbc996e --- /dev/null +++ b/packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts @@ -0,0 +1,514 @@ +/* + * + * Hedera JSON RPC Relay + * + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import sinon from 'sinon'; +import fs from 'fs'; +import pino, { Logger } from 'pino'; +import { HbarSpendingPlanRepository } from '../../../src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository'; +import { EthAddressHbarSpendingPlanRepository } from '../../../src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; +import { IPAddressHbarSpendingPlanRepository } from '../../../src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; +import { SubscriptionTier } from '../../../src/lib/db/types/hbarLimiter/subscriptionTier'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { SpendingPlanConfig } from '../../../src/lib/types/spendingPlanConfig'; +import { RequestDetails } from '../../../src/lib/types'; +import { overrideEnvsInMochaDescribe, toHex, useInMemoryRedisServer } from '../../helpers'; +import findConfig from 'find-config'; +import { HbarSpendingPlanConfigService } from '../../../src/lib/config/hbarSpendingPlanConfigService'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; +import { Registry } from 'prom-client'; + +chai.use(chaiAsPromised); + +describe('HbarSpendingPlanConfigService', function () { + const logger = pino(); + const registry = new Registry(); + const neverExpireTtl = -1; + const emptyRequestDetails = new RequestDetails({ requestId: '', ipAddress: '' }); + const spendingPlansConfigFile = 'spendingPlansConfig.example.json'; + const path = findConfig(spendingPlansConfigFile); + const spendingPlansConfig = JSON.parse(fs.readFileSync(path!, 'utf-8')) as SpendingPlanConfig[]; + + const tests = (isSharedCacheEnabled: boolean) => { + let cacheService: CacheService; + let hbarSpendingPlanRepository: HbarSpendingPlanRepository; + let ethAddressHbarSpendingPlanRepository: EthAddressHbarSpendingPlanRepository; + let ipAddressHbarSpendingPlanRepository: IPAddressHbarSpendingPlanRepository; + let hbarSpendingPlanConfigService: HbarSpendingPlanConfigService; + + let loggerSpy: sinon.SinonSpiedInstance; + let cacheServiceSpy: sinon.SinonSpiedInstance; + let hbarSpendingPlanRepositorySpy: sinon.SinonSpiedInstance; + let ethAddressHbarSpendingPlanRepositorySpy: sinon.SinonSpiedInstance; + let ipAddressHbarSpendingPlanRepositorySpy: sinon.SinonSpiedInstance; + + overrideEnvsInMochaDescribe({ HBAR_SPENDING_PLANS_CONFIG_FILE: spendingPlansConfigFile }); + + before(function () { + cacheService = new CacheService(logger, registry); + hbarSpendingPlanRepository = new HbarSpendingPlanRepository(cacheService, logger); + ethAddressHbarSpendingPlanRepository = new EthAddressHbarSpendingPlanRepository(cacheService, logger); + ipAddressHbarSpendingPlanRepository = new IPAddressHbarSpendingPlanRepository(cacheService, logger); + hbarSpendingPlanConfigService = new HbarSpendingPlanConfigService( + logger, + hbarSpendingPlanRepository, + ethAddressHbarSpendingPlanRepository, + ipAddressHbarSpendingPlanRepository, + ); + }); + + if (isSharedCacheEnabled) { + useInMemoryRedisServer(logger, 6384); + } + + beforeEach(function () { + loggerSpy = sinon.spy(logger); + cacheServiceSpy = sinon.spy(cacheService); + hbarSpendingPlanRepositorySpy = sinon.spy(hbarSpendingPlanRepository); + ethAddressHbarSpendingPlanRepositorySpy = sinon.spy(ethAddressHbarSpendingPlanRepository); + ipAddressHbarSpendingPlanRepositorySpy = sinon.spy(ipAddressHbarSpendingPlanRepository); + }); + + afterEach(async function () { + sinon.restore(); + await cacheService.clear(emptyRequestDetails); + }); + + describe('populatePreconfiguredSpendingPlans', function () { + describe('negative scenarios', function () { + it('should not throw an error if the configuration file is not found', async function () { + sinon.stub(fs, 'existsSync').returns(false); + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).not.to.be.rejected; + }); + + it('should throw an error if configuration file is not a parsable JSON', async function () { + sinon.stub(fs, 'readFileSync').returns('invalid JSON'); + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).to.be.rejectedWith( + `Failed to parse JSON from ${path}: Unexpected token 'i', "invalid JSON" is not valid JSON`, + ); + }); + + it('should throw an error if the configuration file has entry without ID', async function () { + const invalidPlan = { + name: 'Plan without ID', + subscriptionTier: SubscriptionTier.EXTENDED, + ethAddresses: ['0x123'], + }; + sinon + .stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any) + .returns([...spendingPlansConfig, invalidPlan]); + + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).to.be.rejectedWith( + `Invalid spending plan configuration: ${JSON.stringify(invalidPlan)}`, + ); + }); + + it('should throw an error if the configuration file has entry without name', async function () { + const invalidPlan = { + id: 'plan-without-name', + subscriptionTier: SubscriptionTier.EXTENDED, + ethAddresses: ['0x123'], + }; + sinon + .stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any) + .returns([...spendingPlansConfig, invalidPlan]); + + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).to.be.rejectedWith( + `Invalid spending plan configuration: ${JSON.stringify(invalidPlan)}`, + ); + }); + + it('should throw an error if the configuration file has entry without subscriptionTier', async function () { + const invalidPlan = { + id: 'plan-without-tier', + name: 'Plan without tier', + ethAddresses: ['0x123'], + }; + sinon + .stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any) + .returns([...spendingPlansConfig, invalidPlan]); + + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).to.be.rejectedWith( + `Invalid spending plan configuration: ${JSON.stringify(invalidPlan)}`, + ); + }); + + it('should throw an error if the configuration file has entry with invalid subscriptionTier', async function () { + const invalidPlan = { + id: 'plan-invalid-tier', + name: 'Plan with invalid tier', + subscriptionTier: 'INVALID_TIER', + ethAddresses: ['0x123'], + }; + sinon + .stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any) + .returns([...spendingPlansConfig, invalidPlan]); + + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).to.be.rejectedWith( + `Invalid spending plan configuration: ${JSON.stringify(invalidPlan)}`, + ); + }); + + it('should throw an error if the configuration file has entry without ethAddresses and ipAddresses', async function () { + const invalidPlan = { + id: 'plan-without-addresses', + name: 'Plan without addresses', + subscriptionTier: SubscriptionTier.EXTENDED, + }; + sinon + .stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any) + .returns([...spendingPlansConfig, invalidPlan]); + + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).to.be.rejectedWith( + `Invalid spending plan configuration: ${JSON.stringify(invalidPlan)}`, + ); + }); + + it('should throw an error if the configuration file has entry with empty ethAddresses and ipAddresses', async function () { + const invalidPlan = { + id: 'plan-with-empty-addresses', + name: 'Plan with empty addresses', + subscriptionTier: SubscriptionTier.EXTENDED, + ethAddresses: [], + ipAddresses: [], + }; + sinon + .stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any) + .returns([...spendingPlansConfig, invalidPlan]); + + await expect(hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans()).to.be.rejectedWith( + `Invalid spending plan configuration: ${JSON.stringify(invalidPlan)}`, + ); + }); + }); + + describe('positive scenarios', function () { + const saveSpendingPlans = async (spendingPlansConfig: SpendingPlanConfig[]) => { + for (const plan of spendingPlansConfig) { + await hbarSpendingPlanRepository.create( + plan.subscriptionTier, + emptyRequestDetails, + neverExpireTtl, + plan.id, + ); + for (const ethAddress of plan.ethAddresses || []) { + await ethAddressHbarSpendingPlanRepository.save( + { ethAddress, planId: plan.id }, + emptyRequestDetails, + neverExpireTtl, + ); + } + for (const ipAddress of plan.ipAddresses || []) { + await ipAddressHbarSpendingPlanRepository.save( + { ipAddress, planId: plan.id }, + emptyRequestDetails, + neverExpireTtl, + ); + } + } + hbarSpendingPlanRepositorySpy.create.resetHistory(); + ethAddressHbarSpendingPlanRepositorySpy.save.resetHistory(); + ipAddressHbarSpendingPlanRepositorySpy.save.resetHistory(); + }; + + it('should populate the database with pre-configured spending plans', async function () { + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + spendingPlansConfig.forEach(({ id, name, subscriptionTier }) => { + sinon.assert.calledWith( + hbarSpendingPlanRepositorySpy.create, + subscriptionTier, + emptyRequestDetails, + neverExpireTtl, + id, + ); + sinon.assert.calledWith( + loggerSpy.info, + `Created HBAR spending plan "${name}" with ID "${id}" and subscriptionTier "${subscriptionTier}"`, + ); + }); + }); + + it('should remove associations of addresses which were previously linked to BASIC spending plans, but now appear in the configuration file', async function () { + const basicPlans = spendingPlansConfig.map((plan, index) => ({ + ...plan, + id: `basic-plan-${index}`, + subscriptionTier: SubscriptionTier.BASIC, + })); + await saveSpendingPlans(basicPlans); + + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + spendingPlansConfig.forEach(({ id, ethAddresses, ipAddresses }) => { + if (ethAddresses) { + ethAddresses.forEach((ethAddress) => { + sinon.assert.calledWith( + ethAddressHbarSpendingPlanRepositorySpy.save, + { ethAddress, planId: id }, + emptyRequestDetails, + neverExpireTtl, + ); + sinon.assert.calledWith( + ethAddressHbarSpendingPlanRepositorySpy.delete, + ethAddress, + emptyRequestDetails, + ); + }); + } + + if (ipAddresses) { + ipAddresses.forEach((ipAddress) => { + sinon.assert.calledWith( + ipAddressHbarSpendingPlanRepositorySpy.save, + { ipAddress, planId: id }, + emptyRequestDetails, + neverExpireTtl, + ); + sinon.assert.calledWith(ipAddressHbarSpendingPlanRepositorySpy.delete, ipAddress, emptyRequestDetails); + }); + } + }); + }); + + it('should delete obsolete EXTENDED and PRIVILEGED plans from the database', async function () { + const obsoletePlans: SpendingPlanConfig[] = [ + { + id: 'plan-basic', + name: 'Basic Plan', + subscriptionTier: SubscriptionTier.BASIC, + ethAddresses: ['0x122'], + ipAddresses: ['126.0.0.1'], + }, + { + id: 'plan-extended', + name: 'Extended Plan', + subscriptionTier: SubscriptionTier.EXTENDED, + ethAddresses: ['0x123'], + ipAddresses: ['127.0.0.1'], + }, + { + id: 'plan-privileged', + name: 'Privileged Plan', + subscriptionTier: SubscriptionTier.PRIVILEGED, + ethAddresses: ['0x124'], + ipAddresses: ['128.0.0.1'], + }, + ]; + await saveSpendingPlans(obsoletePlans); + + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + expect(hbarSpendingPlanRepositorySpy.delete.calledTwice).to.be.true; + for (const { id, subscriptionTier, ethAddresses, ipAddresses } of obsoletePlans) { + if (subscriptionTier === SubscriptionTier.BASIC) { + sinon.assert.neverCalledWith(hbarSpendingPlanRepositorySpy.delete, id, emptyRequestDetails); + continue; + } + sinon.assert.calledWith(hbarSpendingPlanRepositorySpy.delete, id, emptyRequestDetails); + sinon.assert.calledWith( + loggerSpy.info, + `Deleting HBAR spending plan with ID "${id}", as it is no longer in the spending plan configuration...`, + ); + sinon.assert.calledWithMatch(ethAddressHbarSpendingPlanRepositorySpy.deleteAllByPlanId, id); + sinon.assert.calledWithMatch(ipAddressHbarSpendingPlanRepositorySpy.deleteAllByPlanId, id); + ethAddresses?.forEach((ethAddress) => { + const key = ethAddressHbarSpendingPlanRepository['getKey'](ethAddress); + sinon.assert.calledWithMatch(cacheServiceSpy.delete, key); + }); + ipAddresses?.forEach((ipAddress) => { + const key = ipAddressHbarSpendingPlanRepository['getKey'](ipAddress); + sinon.assert.calledWithMatch(cacheServiceSpy.delete, key); + }); + } + }); + + it('should not duplicate already existing spending plans and their associations', async function () { + await saveSpendingPlans(spendingPlansConfig); + + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + sinon.assert.notCalled(hbarSpendingPlanRepositorySpy.create); + sinon.assert.notCalled(ethAddressHbarSpendingPlanRepositorySpy.save); + sinon.assert.notCalled(ipAddressHbarSpendingPlanRepositorySpy.save); + }); + + it('should save only new associations for ETH addresses', async function () { + sinon.stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any).returns( + spendingPlansConfig.map((plan, index) => ({ + id: plan.id, + name: plan.name, + subscriptionTier: plan.subscriptionTier, + ethAddresses: [toHex(index)].concat(plan.ethAddresses ? plan.ethAddresses : []), + ipAddresses: plan.ipAddresses, + })), + ); + await saveSpendingPlans(spendingPlansConfig); + + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + spendingPlansConfig.forEach((config, index) => { + sinon.assert.calledWith( + ethAddressHbarSpendingPlanRepositorySpy.save, + { ethAddress: toHex(index), planId: config.id }, + emptyRequestDetails, + neverExpireTtl, + ); + if (config.ethAddresses) { + config.ethAddresses.forEach((ethAddress) => { + sinon.assert.neverCalledWith( + ethAddressHbarSpendingPlanRepositorySpy.save, + { ethAddress, planId: config.id }, + emptyRequestDetails, + neverExpireTtl, + ); + sinon.assert.neverCalledWith( + ethAddressHbarSpendingPlanRepositorySpy.delete, + ethAddress, + emptyRequestDetails, + ); + }); + } + }); + }); + + it('should save only new associations for IP addresses', async function () { + sinon.stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any).returns( + spendingPlansConfig.map((plan, index) => ({ + id: plan.id, + name: plan.name, + subscriptionTier: plan.subscriptionTier, + ethAddresses: plan.ethAddresses, + ipAddresses: [`255.0.0.${index}`].concat(plan.ipAddresses ? plan.ipAddresses : []), + })), + ); + await saveSpendingPlans(spendingPlansConfig); + + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + spendingPlansConfig.forEach((config, index) => { + sinon.assert.calledWith(ipAddressHbarSpendingPlanRepositorySpy.save, { + ipAddress: `255.0.0.${index}`, + planId: config.id, + }); + if (config.ipAddresses) { + config.ipAddresses.forEach((ipAddress) => { + sinon.assert.neverCalledWith(ipAddressHbarSpendingPlanRepositorySpy.save, { + ipAddress, + planId: config.id, + }); + sinon.assert.neverCalledWith( + ipAddressHbarSpendingPlanRepositorySpy.delete, + ipAddress, + emptyRequestDetails, + ); + }); + } + }); + }); + + it('should delete obsolete associations for ETH addresses', async function () { + sinon.stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any).returns( + spendingPlansConfig.map((plan) => ({ + id: plan.id, + name: plan.name, + subscriptionTier: plan.subscriptionTier, + ethAddresses: plan.ethAddresses ? [plan.ethAddresses[0]] : [], + ipAddresses: plan.ipAddresses, + })), + ); + await saveSpendingPlans(spendingPlansConfig); + + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + spendingPlansConfig.forEach(({ id, ethAddresses }) => { + if (ethAddresses) { + ethAddresses.forEach((ethAddress, index) => { + sinon.assert.neverCalledWith(ethAddressHbarSpendingPlanRepositorySpy.save, { + ethAddress, + planId: id, + }); + if (index === 0) { + sinon.assert.neverCalledWith( + ethAddressHbarSpendingPlanRepositorySpy.delete, + ethAddress, + emptyRequestDetails, + ); + } else { + sinon.assert.calledWith( + ethAddressHbarSpendingPlanRepositorySpy.delete, + ethAddress, + emptyRequestDetails, + ); + } + }); + } + }); + }); + + it('should delete obsolete associations for IP addresses', async function () { + sinon.stub(hbarSpendingPlanConfigService, 'loadSpendingPlansConfig' as any).returns( + spendingPlansConfig.map((plan) => ({ + id: plan.id, + name: plan.name, + subscriptionTier: plan.subscriptionTier, + ethAddresses: plan.ethAddresses, + ipAddresses: plan.ipAddresses ? [plan.ipAddresses[0]] : [], + })), + ); + await saveSpendingPlans(spendingPlansConfig); + + await hbarSpendingPlanConfigService.populatePreconfiguredSpendingPlans(); + + spendingPlansConfig.forEach(({ id, ipAddresses }) => { + if (ipAddresses) { + ipAddresses.forEach((ipAddress, index) => { + sinon.assert.neverCalledWith(ipAddressHbarSpendingPlanRepositorySpy.save, { + ipAddress, + planId: id, + }); + if (index === 0) { + sinon.assert.neverCalledWith( + ipAddressHbarSpendingPlanRepositorySpy.delete, + ipAddress, + emptyRequestDetails, + ); + } else { + sinon.assert.calledWith( + ipAddressHbarSpendingPlanRepositorySpy.delete, + ipAddress, + emptyRequestDetails, + ); + } + }); + } + }); + }); + }); + }); + }; + + describe('with shared cache enabled', function () { + tests(true); + }); + + describe('with shared cache disabled', function () { + tests(false); + }); +}); diff --git a/packages/relay/tests/lib/relay.spec.ts b/packages/relay/tests/lib/relay.spec.ts index c40d00ef7f..07ebe0819c 100644 --- a/packages/relay/tests/lib/relay.spec.ts +++ b/packages/relay/tests/lib/relay.spec.ts @@ -18,12 +18,17 @@ * */ -import { expect } from 'chai'; -import sinon from 'sinon'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import findConfig from 'find-config'; +import fs from 'fs'; import pino from 'pino'; +import sinon from 'sinon'; import { Registry } from 'prom-client'; import { RelayImpl } from '../../src'; -import { withOverriddenEnvsInMochaTest } from '../helpers'; +import { overrideEnvsInMochaDescribe, withOverriddenEnvsInMochaTest } from '../helpers'; + +chai.use(chaiAsPromised); describe('RelayImpl', () => { const logger = pino(); @@ -74,4 +79,65 @@ describe('RelayImpl', () => { expect(subs).to.be.undefined; }); }); + + describe('populatePreconfiguredSpendingPlans', () => { + let loggerSpy: sinon.SinonSpiedInstance; + let populatePreconfiguredSpendingPlansSpy: sinon.SinonSpy; + + beforeEach(() => { + loggerSpy = sinon.spy(logger); + populatePreconfiguredSpendingPlansSpy = sinon.spy(RelayImpl.prototype, 'populatePreconfiguredSpendingPlans'); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('when a configuration file is provided', () => { + overrideEnvsInMochaDescribe({ HBAR_SPENDING_PLANS_CONFIG_FILE: 'spendingPlansConfig.example.json' }); + + it('should populate preconfigured spending plans successfully', async () => { + expect((relay = new RelayImpl(logger, register))).to.not.throw; + + expect(populatePreconfiguredSpendingPlansSpy.calledOnce).to.be.true; + await expect(populatePreconfiguredSpendingPlansSpy.returnValues[0]).to.not.be.rejected; + expect(loggerSpy.info.calledWith('Pre-configured spending plans populated successfully')).to.be.true; + }); + }); + + describe('when no configuration file is provided', () => { + const nonExistingFile = 'nonExistingFile.json'; + overrideEnvsInMochaDescribe({ HBAR_SPENDING_PLANS_CONFIG_FILE: nonExistingFile }); + + it('should not throw an error', async () => { + expect((relay = new RelayImpl(logger, register))).to.not.throw; + + expect(populatePreconfiguredSpendingPlansSpy.calledOnce).to.be.true; + await expect(populatePreconfiguredSpendingPlansSpy.returnValues[0]).to.not.be.rejected; + expect(loggerSpy.warn.notCalled).to.be.true; + }); + }); + + describe('when a configuration file with invalid JSON is provided', () => { + let path: string | null; + + overrideEnvsInMochaDescribe({ HBAR_SPENDING_PLANS_CONFIG_FILE: 'spendingPlansConfig.example.json' }); + + beforeEach(() => { + path = findConfig('spendingPlansConfig.example.json'); + sinon.stub(fs, 'readFileSync').returns('invalid JSON'); + }); + + it('should log a warning', async () => { + expect((relay = new RelayImpl(logger, register))).to.not.throw; + + expect(populatePreconfiguredSpendingPlansSpy.calledOnce).to.be.true; + await expect(populatePreconfiguredSpendingPlansSpy.returnValues[0]).not.to.be.rejected; + + const cause = `Failed to parse JSON from ${path}: Unexpected token 'i', "invalid JSON" is not valid JSON`; + const message = `Failed to load pre-configured spending plans: ${cause}`; + expect(loggerSpy.warn.calledWith(message)).to.be.true; + }); + }); + }); }); diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts index 7cec8bd8cb..0c9f52f5a3 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts @@ -30,6 +30,7 @@ import { randomBytes, uuidV4 } from 'ethers'; import { Registry } from 'prom-client'; import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../helpers'; import { RequestDetails } from '../../../../dist/lib/types'; +import { EthAddressHbarSpendingPlan } from '../../../../src/lib/db/entities/hbarLimiter/ethAddressHbarSpendingPlan'; chai.use(chaiAsPromised); @@ -70,6 +71,66 @@ describe('EthAddressHbarSpendingPlanRepository', function () { await cacheService.disconnectRedisClient(); }); + describe('existsByAddress', () => { + it('returns true if address plan exists', async () => { + const ethAddress = '0x123'; + const addressPlan = new EthAddressHbarSpendingPlan({ ethAddress, planId: uuidV4(randomBytes(16)) }); + await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test', requestDetails); + + await expect(repository.existsByAddress(ethAddress, requestDetails)).to.eventually.be.true; + }); + + it('returns false if address plan does not exist', async () => { + const ethAddress = '0xnonexistent'; + await expect(repository.existsByAddress(ethAddress, requestDetails)).to.eventually.be.false; + }); + }); + + describe('findAllByPlanId', () => { + it('retrieves all address plans by plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + const ethAddressPlans = [ + new EthAddressHbarSpendingPlan({ ethAddress: '0x123', planId }), + new EthAddressHbarSpendingPlan({ ethAddress: '0x456', planId }), + ]; + for (const plan of ethAddressPlans) { + await cacheService.set(`${repository['collectionKey']}:${plan.ethAddress}`, plan, 'test', requestDetails); + } + + const result = await repository.findAllByPlanId(planId, 'findAllByPlanId', requestDetails); + expect(result).to.have.deep.members(ethAddressPlans); + }); + + it('returns an empty array if no address plans are found for the plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + const result = await repository.findAllByPlanId(planId, 'findAllByPlanId', requestDetails); + expect(result).to.deep.equal([]); + }); + }); + + describe('deleteAllByPlanId', () => { + it('deletes all address plans by plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + const ethAddresses = ['0x123', '0x456', '0x789']; + for (const ethAddress of ethAddresses) { + const addressPlan = new EthAddressHbarSpendingPlan({ ethAddress, planId }); + await cacheService.set(`${repository['collectionKey']}:${ethAddress}`, addressPlan, 'test', requestDetails); + } + + await repository.deleteAllByPlanId(planId, 'deleteAllByPlanId', requestDetails); + + for (const ethAddress of ethAddresses) { + await expect(cacheService.getAsync(`${repository['collectionKey']}:${ethAddress}`, 'test', requestDetails)).to + .eventually.be.null; + } + }); + + it('does not throw an error if no address plans are found for the plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + await expect(repository.deleteAllByPlanId(planId, 'deleteAllByPlanId', requestDetails)).to.be.fulfilled; + }); + }); + describe('findByAddress', () => { it('retrieves an address plan by address', async () => { const ethAddress = '0x123'; diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts index b85dc399a1..7a930c37d1 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts @@ -30,7 +30,7 @@ import { HbarSpendingPlanNotFoundError, } from '../../../../src/lib/db/types/hbarLimiter/errors'; import { IHbarSpendingRecord } from '../../../../src/lib/db/types/hbarLimiter/hbarSpendingRecord'; -import { SubscriptionType } from '../../../../src/lib/db/types/hbarLimiter/subscriptionType'; +import { SubscriptionTier } from '../../../../src/lib/db/types/hbarLimiter/subscriptionTier'; import { IDetailedHbarSpendingPlan } from '../../../../src/lib/db/types/hbarLimiter/hbarSpendingPlan'; import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../helpers'; import { RequestDetails } from '../../../../src/lib/types'; @@ -44,11 +44,12 @@ describe('HbarSpendingPlanRepository', function () { const ttl = 86_400_000; // 1 day const tests = (isSharedCacheEnabled: boolean) => { + let cacheService: CacheService; let cacheServiceSpy: sinon.SinonSpiedInstance; let repository: HbarSpendingPlanRepository; before(async () => { - const cacheService = new CacheService(logger.child({ name: `CacheService` }), registry); + cacheService = new CacheService(logger.child({ name: `CacheService` }), registry); cacheServiceSpy = sinon.spy(cacheService); repository = new HbarSpendingPlanRepository(cacheService, logger.child({ name: `HbarSpendingPlanRepository` })); }); @@ -60,17 +61,17 @@ describe('HbarSpendingPlanRepository', function () { } afterEach(async () => { - await cacheServiceSpy.clear(requestDetails); + await cacheService.clear(requestDetails); }); after(async () => { - await cacheServiceSpy.disconnectRedisClient(); + await cacheService.disconnectRedisClient(); }); describe('create', () => { it('creates a plan successfully', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); await expect(repository.findByIdWithDetails(createdPlan.id, requestDetails)).to.be.eventually.deep.equal( createdPlan, ); @@ -94,8 +95,8 @@ describe('HbarSpendingPlanRepository', function () { }); it('returns a plan by ID', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); await expect(repository.findById(createdPlan.id, requestDetails)).to.be.eventually.deep.equal(createdPlan); }); }); @@ -109,8 +110,8 @@ describe('HbarSpendingPlanRepository', function () { }); it('returns a plan by ID', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); await expect(repository.findByIdWithDetails(createdPlan.id, requestDetails)).to.be.eventually.deep.equal( createdPlan, ); @@ -126,19 +127,19 @@ describe('HbarSpendingPlanRepository', function () { }); it('returns an empty array if spending history is empty', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.deep.equal([]); }); it('retrieves spending history for a plan', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); const key = `${repository['collectionKey']}:${createdPlan.id}:spendingHistory`; const hbarSpending = { amount: 100, timestamp: new Date() } as IHbarSpendingRecord; - await cacheServiceSpy.rPush(key, hbarSpending, 'test', requestDetails); + await cacheService.rPush(key, hbarSpending, 'test', requestDetails); const spendingHistory = await repository.getSpendingHistory(createdPlan.id, requestDetails); expect(spendingHistory).to.have.lengthOf(1); @@ -149,8 +150,8 @@ describe('HbarSpendingPlanRepository', function () { describe('addAmountToSpendingHistory', () => { it('adds amount to spending history', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); await expect(repository.getSpendingHistory(createdPlan.id, requestDetails)).to.be.eventually.deep.equal([]); const amount = 100; @@ -171,8 +172,8 @@ describe('HbarSpendingPlanRepository', function () { }); it('adds multiple amounts to spending history', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); await expect(repository.getSpendingHistory(createdPlan.id, requestDetails)).to.be.eventually.deep.equal([]); const amounts = [100, 200, 300]; @@ -200,7 +201,7 @@ describe('HbarSpendingPlanRepository', function () { let createdPlan: IDetailedHbarSpendingPlan; beforeEach(async () => { - createdPlan = await repository.create(SubscriptionType.BASIC, requestDetails, ttl); + createdPlan = await repository.create(SubscriptionTier.BASIC, requestDetails, ttl); }); it('retrieves amountSpent for a plan', async () => { @@ -228,8 +229,8 @@ describe('HbarSpendingPlanRepository', function () { describe('resetAllAmountSpentEntries', () => { it('resets all amountSpent entries', async () => { const plans: IDetailedHbarSpendingPlan[] = []; - for (const subscriptionType of Object.values(SubscriptionType)) { - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + for (const subscriptionTier of Object.values(SubscriptionTier)) { + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); plans.push(createdPlan); const amount = 50 * plans.length; await repository.addToAmountSpent(createdPlan.id, amount, requestDetails, ttl); @@ -250,8 +251,8 @@ describe('HbarSpendingPlanRepository', function () { describe('addToAmountSpent', () => { it('adds amount to amountSpent', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); const amount = 50; await repository.addToAmountSpent(createdPlan.id, amount, requestDetails, ttl); @@ -295,12 +296,12 @@ describe('HbarSpendingPlanRepository', function () { }); it('throws an error if plan is not active when adding to amountSpent', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${createdPlan.id}`; - await cacheServiceSpy.set(key, { ...createdPlan, active: false }, 'test', requestDetails); + await cacheService.set(key, { ...createdPlan, active: false }, 'test', requestDetails); const amount = 50; await expect( @@ -322,12 +323,12 @@ describe('HbarSpendingPlanRepository', function () { }); it('throws error if plan is not active when checking if exists and active', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan = await repository.create(subscriptionType, requestDetails, ttl); + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan = await repository.create(subscriptionTier, requestDetails, ttl); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${createdPlan.id}`; - await cacheServiceSpy.set(key, { ...createdPlan, active: false }, 'test', requestDetails); + await cacheService.set(key, { ...createdPlan, active: false }, 'test', requestDetails); await expect(repository.checkExistsAndActive(createdPlan.id, requestDetails)).to.be.eventually.rejectedWith( HbarSpendingPlanNotActiveError, @@ -336,61 +337,68 @@ describe('HbarSpendingPlanRepository', function () { }); }); - describe('findAllActiveBySubscriptionType', () => { - it('returns an empty array if no active plans exist for the subscription type', async () => { - const subscriptionType = SubscriptionType.BASIC; - const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); + describe('findAllActiveBysubscriptionTier', () => { + it('returns an empty array if no active plans exist for the subscription tier', async () => { + const subscriptionTier = SubscriptionTier.BASIC; + const activePlans = await repository.findAllActiveBySubscriptionTier([subscriptionTier], requestDetails); expect(activePlans).to.deep.equal([]); }); - it('returns all active plans for the subscription type', async () => { - const subscriptionType = SubscriptionType.BASIC; - const createdPlan1 = await repository.create(subscriptionType, requestDetails, ttl); - const createdPlan2 = await repository.create(subscriptionType, requestDetails, ttl); + it('returns all active plans for the subscription tier', async () => { + const subscriptionTier = SubscriptionTier.BASIC; + const createdPlan1 = await repository.create(subscriptionTier, requestDetails, ttl); + const createdPlan2 = await repository.create(subscriptionTier, requestDetails, ttl); - const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); + const activePlans = await repository.findAllActiveBySubscriptionTier([subscriptionTier], requestDetails); expect(activePlans).to.have.lengthOf(2); expect(activePlans.map((plan) => plan.id)).to.include.members([createdPlan1.id, createdPlan2.id]); }); - it('does not return inactive plans for the subscription type', async () => { - const subscriptionType = SubscriptionType.BASIC; - const activePlan = await repository.create(subscriptionType, requestDetails, ttl); - const inactivePlan = await repository.create(subscriptionType, requestDetails, ttl); + it('does not return inactive plans for the subscription tier', async () => { + const subscriptionTier = SubscriptionTier.BASIC; + const activePlan = await repository.create(subscriptionTier, requestDetails, ttl); + const inactivePlan = await repository.create(subscriptionTier, requestDetails, ttl); // Manually set the plan to inactive const key = `${repository['collectionKey']}:${inactivePlan.id}`; - await cacheServiceSpy.set(key, { ...inactivePlan, active: false }, 'test', requestDetails); + await cacheService.set(key, { ...inactivePlan, active: false }, 'test', requestDetails); - const activePlans = await repository.findAllActiveBySubscriptionType(subscriptionType, requestDetails); + const activePlans = await repository.findAllActiveBySubscriptionTier([subscriptionTier], requestDetails); expect(activePlans).to.deep.equal([activePlan]); }); - it('returns only active plans for the specified subscription type', async () => { - const basicPlan = await repository.create(SubscriptionType.BASIC, requestDetails, ttl); - const extendedPlan = await repository.create(SubscriptionType.EXTENDED, requestDetails, ttl); - const privilegedPlan = await repository.create(SubscriptionType.PRIVILEGED, requestDetails, ttl); + it('returns only active plans for the specified subscription tier', async () => { + const basicPlan = await repository.create(SubscriptionTier.BASIC, requestDetails, ttl); + const extendedPlan = await repository.create(SubscriptionTier.EXTENDED, requestDetails, ttl); + const privilegedPlan = await repository.create(SubscriptionTier.PRIVILEGED, requestDetails, ttl); - const activeBasicPlans = await repository.findAllActiveBySubscriptionType( - SubscriptionType.BASIC, + const activeBasicPlans = await repository.findAllActiveBySubscriptionTier( + [SubscriptionTier.BASIC], requestDetails, ); expect(activeBasicPlans).to.have.lengthOf(1); expect(activeBasicPlans[0].id).to.equal(basicPlan.id); - const activeExtendedPlans = await repository.findAllActiveBySubscriptionType( - SubscriptionType.EXTENDED, + const activeExtendedPlans = await repository.findAllActiveBySubscriptionTier( + [SubscriptionTier.EXTENDED], requestDetails, ); expect(activeExtendedPlans).to.have.lengthOf(1); expect(activeExtendedPlans[0].id).to.equal(extendedPlan.id); - const activePrivilegedPlans = await repository.findAllActiveBySubscriptionType( - SubscriptionType.PRIVILEGED, + const activePrivilegedPlans = await repository.findAllActiveBySubscriptionTier( + [SubscriptionTier.PRIVILEGED], requestDetails, ); expect(activePrivilegedPlans).to.have.lengthOf(1); expect(activePrivilegedPlans[0].id).to.equal(privilegedPlan.id); + + const activeBasicAndExtendedPlans = await repository.findAllActiveBySubscriptionTier( + [SubscriptionTier.BASIC, SubscriptionTier.EXTENDED], + requestDetails, + ); + expect(activeBasicAndExtendedPlans).to.have.lengthOf(2); + expect(activeBasicAndExtendedPlans.map((plan) => plan.id)).to.include.members([basicPlan.id, extendedPlan.id]); }); }); }; diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts index 44d8e649ef..9ccdd90d37 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts @@ -30,6 +30,7 @@ import { randomBytes, uuidV4 } from 'ethers'; import { Registry } from 'prom-client'; import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../helpers'; import { RequestDetails } from '../../../../src/lib/types'; +import { IPAddressHbarSpendingPlan } from '../../../../src/lib/db/entities/hbarLimiter/ipAddressHbarSpendingPlan'; chai.use(chaiAsPromised); @@ -72,6 +73,64 @@ describe('IPAddressHbarSpendingPlanRepository', function () { await cacheService.disconnectRedisClient(); }); + describe('existsByAddress', () => { + it('returns true if address plan exists', async () => { + const addressPlan = new IPAddressHbarSpendingPlan({ ipAddress, planId: uuidV4(randomBytes(16)) }); + await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test', requestDetails); + + await expect(repository.existsByAddress(ipAddress, requestDetails)).to.eventually.be.true; + }); + + it('returns false if address plan does not exist', async () => { + await expect(repository.existsByAddress(nonExistingIpAddress, requestDetails)).to.eventually.be.false; + }); + }); + + describe('findAllByPlanId', () => { + it('retrieves all address plans by plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + const ipAddressPlans = [ + new IPAddressHbarSpendingPlan({ ipAddress: '555.555.555.555', planId }), + new IPAddressHbarSpendingPlan({ ipAddress: '666.666.666.666', planId }), + ]; + for (const plan of ipAddressPlans) { + await cacheService.set(`${repository['collectionKey']}:${plan.ipAddress}`, plan, 'test', requestDetails); + } + + const result = await repository.findAllByPlanId(planId, 'findAllByPlanId', requestDetails); + expect(result).to.have.deep.members(ipAddressPlans); + }); + + it('returns an empty array if no address plans are found for the plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + const result = await repository.findAllByPlanId(planId, 'findAllByPlanId', requestDetails); + expect(result).to.deep.equal([]); + }); + }); + + describe('deleteAllByPlanId', () => { + it('deletes all address plans by plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + const ipAddresses = ['555.555.555.555', '666.666.666.666']; + for (const ipAddress of ipAddresses) { + const addressPlan = new IPAddressHbarSpendingPlan({ ipAddress, planId }); + await cacheService.set(`${repository['collectionKey']}:${ipAddress}`, addressPlan, 'test', requestDetails); + } + + await repository.deleteAllByPlanId(planId, 'deleteAllByPlanId', requestDetails); + + for (const ipAddress of ipAddresses) { + await expect(cacheService.getAsync(`${repository['collectionKey']}:${ipAddress}`, 'test', requestDetails)).to + .eventually.be.null; + } + }); + + it('does not throw an error if no address plans are found for the plan ID', async () => { + const planId = uuidV4(randomBytes(16)); + await expect(repository.deleteAllByPlanId(planId, 'deleteAllByPlanId', requestDetails)).to.be.fulfilled; + }); + }); + describe('findByAddress', () => { it('retrieves an address plan by ip', async () => { const addressPlan: IIPAddressHbarSpendingPlan = { ipAddress, planId: uuidV4(randomBytes(16)) }; diff --git a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts index 7917467507..be82a30b92 100644 --- a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts +++ b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts @@ -30,17 +30,19 @@ import { overrideEnvsInMochaDescribe, useInMemoryRedisServer } from '../../../he import { RequestDetails } from '../../../../dist/lib/types'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); -const logger = pino(); -const registry = new Registry(); -let cacheService: CacheService; - -const callingMethod = 'CacheServiceTest'; chai.use(chaiAsPromised); describe('CacheService Test Suite', async function () { this.timeout(10000); + + const logger = pino(); + const registry = new Registry(); + const callingMethod = 'CacheServiceTest'; const requestDetails = new RequestDetails({ requestId: 'cacheServiceTest', ipAddress: '0.0.0.0' }); + + let cacheService: CacheService; + const describeKeysTestSuite = () => { describe('keys', async function () { it('should retrieve all keys', async function () { @@ -428,16 +430,6 @@ describe('CacheService Test Suite', async function () { }); describe('incrBy', async function () { - it('should increment value in internal cache', async function () { - const key = 'counter'; - const amount = 5; - - await cacheService.set(key, 10, callingMethod, requestDetails); - const newValue = await cacheService.incrBy(key, amount, callingMethod, requestDetails); - - expect(newValue).to.equal(15); - }); - it('should increment value in shared cache', async function () { const key = 'counter'; const amount = 5; @@ -454,9 +446,10 @@ describe('CacheService Test Suite', async function () { await cacheService.disconnectRedisClient(); + await cacheService.set(key, 10, callingMethod, requestDetails); const newValue = await cacheService.incrBy(key, amount, callingMethod, requestDetails); - expect(newValue).to.equal(5); + expect(newValue).to.equal(15); }); }); diff --git a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts index 5bcbc1e403..3785e85788 100644 --- a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts +++ b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts @@ -26,7 +26,7 @@ import chaiAsPromised from 'chai-as-promised'; import constants from '../../../../src/lib/constants'; import { Counter, Gauge, Registry } from 'prom-client'; import { HbarLimitService } from '../../../../src/lib/services/hbarLimitService'; -import { SubscriptionType } from '../../../../src/lib/db/types/hbarLimiter/subscriptionType'; +import { SubscriptionTier } from '../../../../src/lib/db/types/hbarLimiter/subscriptionTier'; import { HbarSpendingPlan } from '../../../../src/lib/db/entities/hbarLimiter/hbarSpendingPlan'; import { HbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository'; import { EthAddressHbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; @@ -88,7 +88,7 @@ describe('HbarLimitService', function () { function createSpendingPlan(id: string, amountSpent: number | Long | Hbar = 0) { return new HbarSpendingPlan({ id, - subscriptionType: SubscriptionType.BASIC, + subscriptionTier: SubscriptionTier.BASIC, createdAt: new Date(), active: true, spendingHistory: [], @@ -99,9 +99,9 @@ describe('HbarLimitService', function () { it('should initialize metrics correctly', () => { expect(hbarLimitService['hbarLimitCounter']).to.be.instanceOf(Counter); expect(hbarLimitService['hbarLimitRemainingGauge']).to.be.instanceOf(Gauge); - Object.values(SubscriptionType).forEach((subscriptionType) => { - expect(hbarLimitService['dailyUniqueSpendingPlansCounter'][subscriptionType]).to.be.instanceOf(Counter); - expect(hbarLimitService['averageDailySpendingPlanUsagesGauge'][subscriptionType]).to.be.instanceOf(Gauge); + Object.values(SubscriptionTier).forEach((tier) => { + expect(hbarLimitService['dailyUniqueSpendingPlansCounter'][tier]).to.be.instanceOf(Counter); + expect(hbarLimitService['averageDailySpendingPlanUsagesGauge'][tier]).to.be.instanceOf(Gauge); }); }); @@ -222,7 +222,7 @@ describe('HbarLimitService', function () { }); it('should return true if amountSpent is exactly at the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC]); + const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC]); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, planId: mockPlanId, @@ -237,7 +237,7 @@ describe('HbarLimitService', function () { it('should return false if amountSpent is just below the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(1), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -253,7 +253,7 @@ describe('HbarLimitService', function () { it('should return true if amountSpent is just above the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().add(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().add(1), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -269,7 +269,7 @@ describe('HbarLimitService', function () { it('should return true if amountSpent + estimatedTxFee is above the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).add(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(mockEstimatedTxFee).add(1), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -291,7 +291,7 @@ describe('HbarLimitService', function () { it('should return false if amountSpent + estimatedTxFee is below the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).sub(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(mockEstimatedTxFee).sub(1), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -307,7 +307,7 @@ describe('HbarLimitService', function () { it('should return false if amountSpent + estimatedTxFee is at the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(mockEstimatedTxFee), ); ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ethAddress: mockEthAddress, @@ -364,7 +364,7 @@ describe('HbarLimitService', function () { }); it('should return true if amountSpent is exactly at the limit', async function () { - const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC]); + const spendingPlan = createSpendingPlan(mockPlanId, HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC]); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, planId: mockPlanId, @@ -379,7 +379,7 @@ describe('HbarLimitService', function () { it('should return false if amountSpent is just below the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(1), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -395,7 +395,7 @@ describe('HbarLimitService', function () { it('should return true if amountSpent is just above the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().add(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().add(1), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -411,7 +411,7 @@ describe('HbarLimitService', function () { it('should return true if amountSpent + estimatedTxFee is above the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).add(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(mockEstimatedTxFee).add(1), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -427,7 +427,7 @@ describe('HbarLimitService', function () { it('should return false if amountSpent + estimatedTxFee is below the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee).sub(1), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(mockEstimatedTxFee).sub(1), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -443,7 +443,7 @@ describe('HbarLimitService', function () { it('should return false if amountSpent + estimatedTxFee is at the limit', async function () { const spendingPlan = createSpendingPlan( mockPlanId, - HbarLimitService.TIER_LIMITS[SubscriptionType.BASIC].toTinybars().sub(mockEstimatedTxFee), + HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(mockEstimatedTxFee), ); ipAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({ ipAddress: mockIpAddress, @@ -619,7 +619,7 @@ describe('HbarLimitService', function () { } hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(existingSpendingPlan); hbarSpendingPlanRepositoryStub.addToAmountSpent.resolves(); - hbarSpendingPlanRepositoryStub.findAllActiveBySubscriptionType.resolves([ + hbarSpendingPlanRepositoryStub.findAllActiveBySubscriptionTier.resolves([ otherPlanOfTheSameTier, { ...existingSpendingPlan, @@ -628,16 +628,16 @@ describe('HbarLimitService', function () { }, ]); const incDailyUniqueSpendingPlansCounterSpy = sinon.spy( - hbarLimitService['dailyUniqueSpendingPlansCounter'][SubscriptionType.BASIC], + hbarLimitService['dailyUniqueSpendingPlansCounter'][SubscriptionTier.BASIC], 'inc', ); const setAverageDailySpendingPlanUsagesGaugeSpy = sinon.spy( - hbarLimitService['averageDailySpendingPlanUsagesGauge'][SubscriptionType.BASIC], + hbarLimitService['averageDailySpendingPlanUsagesGauge'][SubscriptionTier.BASIC], 'set', ); - const updateAverageDailyUsagePerSubscriptionTypeSpy = sinon.spy( + const updateAverageDailyUsagePerSubscriptionTierSpy = sinon.spy( hbarLimitService, - 'updateAverageDailyUsagePerSubscriptionType', + 'updateAverageDailyUsagePerSubscriptionTier', ); await hbarLimitService.addExpense(expense, ethAddress, requestDetails); @@ -649,7 +649,7 @@ describe('HbarLimitService', function () { expect((await hbarLimitService['hbarLimitRemainingGauge'].get()).values[0].value).to.equal( hbarLimitService['totalBudget'].toTinybars().sub(expense).toNumber(), ); - await Promise.all(updateAverageDailyUsagePerSubscriptionTypeSpy.returnValues); + await Promise.all(updateAverageDailyUsagePerSubscriptionTierSpy.returnValues); const expectedAverageUsage = Math.round((otherPlanOfTheSameTier.amountSpent + expense) / 2); sinon.assert.calledOnceWithExactly(setAverageDailySpendingPlanUsagesGaugeSpy, expectedAverageUsage); sinon.assert.calledOnceWithExactly(incDailyUniqueSpendingPlansCounterSpy, 1); diff --git a/packages/server/tests/integration/server.spec.ts b/packages/server/tests/integration/server.spec.ts index 875f76ae88..aa4caa3631 100644 --- a/packages/server/tests/integration/server.spec.ts +++ b/packages/server/tests/integration/server.spec.ts @@ -26,12 +26,11 @@ import sinon from 'sinon'; import { Server } from 'http'; import { GCProfiler } from 'v8'; import Assertions from '../helpers/assertions'; -import app from '../../src/server'; import { TracerType, Validator } from '../../src/validator'; import RelayCalls from '../../tests/helpers/constants'; import * as Constants from '../../src/validator/constants'; import { Utils } from '../helpers/utils'; -import { predefined } from '@hashgraph/json-rpc-relay'; +import { predefined, RelayImpl } from '@hashgraph/json-rpc-relay'; import { contractAddress1, contractAddress2, @@ -41,6 +40,7 @@ import { withOverriddenEnvsInMochaTest, } from '../../../relay/tests/helpers'; import { MirrorNodeClient } from '@hashgraph/json-rpc-relay/dist/lib/clients'; +import Koa from 'koa'; dotenv.config({ path: path.resolve(__dirname, './test.env') }); @@ -49,8 +49,12 @@ const MISSING_PARAM_ERROR = 'Missing value for required parameter'; describe('RPC Server', function () { let testServer: Server; let testClient: AxiosInstance; + let populatePreconfiguredSpendingPlansSpy: sinon.SinonSpy; + let app: Koa; before(function () { + populatePreconfiguredSpendingPlansSpy = sinon.spy(RelayImpl.prototype, 'populatePreconfiguredSpendingPlans'); + app = require('../../src/server').default; testServer = app.listen(process.env.E2E_SERVER_PORT); testClient = BaseTest.createTestClient(); }); @@ -99,6 +103,13 @@ describe('RPC Server', function () { }); }); + it('should try to populate preconfigured spending plans', async function () { + const calls = populatePreconfiguredSpendingPlansSpy.getCalls(); + expect(calls.length).to.be.equal(1); + await calls[0].returnValue; + expect(populatePreconfiguredSpendingPlansSpy.calledOnce).to.be.true; + }); + it('should execute "eth_chainId"', async function () { const res = await testClient.post('/', { id: '2', diff --git a/spendingPlansConfig.example.json b/spendingPlansConfig.example.json new file mode 100644 index 0000000000..4e9a12da85 --- /dev/null +++ b/spendingPlansConfig.example.json @@ -0,0 +1,28 @@ +[ + { + "id": "c758c095-342c-4607-9db5-867d7e90ab9d", + "name": "partner name", + "ethAddresses": ["0x123", "0x124"], + "ipAddresses": ["127.0.0.1", "128.0.0.1"], + "subscriptionTier": "PRIVILEGED" + }, + { + "id": "a68488b0-6f7d-44a0-87c1-774ad64615f2", + "name": "some other partner that has given us only eth addresses", + "ethAddresses": ["0x125", "0x126"], + "subscriptionTier": "PRIVILEGED" + }, + { + "id": "af13d6ed-d676-4d33-8b9d-cf05d1ad7134", + "name": "supported project name", + "ethAddresses": ["0x127", "0x128"], + "ipAddresses": ["129.0.0.1", "130.0.0.1"], + "subscriptionTier": "EXTENDED" + }, + { + "id": "7f665aa3-6b73-41d7-bf9b-92d04cdab96b", + "name": "some other supported project that has given us only ip addresses", + "ipAddresses": ["131.0.0.1", "132.0.0.1"], + "subscriptionTier": "EXTENDED" + } +] diff --git a/tools/wagmi-example/vite.config.ts b/tools/wagmi-example/vite.config.ts index 36f7f4e1bc..4e7004ebc6 100644 --- a/tools/wagmi-example/vite.config.ts +++ b/tools/wagmi-example/vite.config.ts @@ -1,7 +1,7 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +}); From 9b3319ab6397a12d91cb9217f8bb751eced6e0ca Mon Sep 17 00:00:00 2001 From: Nikolay Atanasow Date: Fri, 11 Oct 2024 19:52:43 +0300 Subject: [PATCH 29/38] feat: eth_call revert error message is too long, and should not be a REVERT but a BAD Request Response (#3088) * chore: add validation for even calldata length Signed-off-by: nikolay * chore: add test coverage Signed-off-by: nikolay --------- Signed-off-by: nikolay --- packages/server/src/validator/constants.ts | 1 + packages/server/src/validator/objectTypes.ts | 2 +- packages/server/src/validator/types.ts | 4 ++++ packages/server/tests/integration/server.spec.ts | 4 ++-- packages/server/tests/integration/validator.spec.ts | 5 ++++- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/server/src/validator/constants.ts b/packages/server/src/validator/constants.ts index 3274cb7d2f..767a6fba87 100644 --- a/packages/server/src/validator/constants.ts +++ b/packages/server/src/validator/constants.ts @@ -21,6 +21,7 @@ export const BASE_HEX_REGEX = '^0[xX][a-fA-F0-9]'; export const ERROR_CODE = -32602; export const DEFAULT_HEX_ERROR = 'Expected 0x prefixed hexadecimal value'; +export const EVEN_HEX_ERROR = `${DEFAULT_HEX_ERROR} with even length`; export const HASH_ERROR = 'Expected 0x prefixed string representing the hash (32 bytes)'; export const ADDRESS_ERROR = 'Expected 0x prefixed string representing the address (20 bytes)'; export const BLOCK_NUMBER_ERROR = diff --git a/packages/server/src/validator/objectTypes.ts b/packages/server/src/validator/objectTypes.ts index ef0d81f2cd..9e99ecd2fb 100644 --- a/packages/server/src/validator/objectTypes.ts +++ b/packages/server/src/validator/objectTypes.ts @@ -162,7 +162,7 @@ export const OBJECTS_VALIDATIONS: { [key: string]: IObjectSchema } = { nullable: false, }, data: { - type: 'hex', + type: 'hexEvenLength', nullable: true, }, type: { diff --git a/packages/server/src/validator/types.ts b/packages/server/src/validator/types.ts index b4643377ec..273bce0592 100644 --- a/packages/server/src/validator/types.ts +++ b/packages/server/src/validator/types.ts @@ -90,6 +90,10 @@ export const TYPES: { [key: string]: ITypeValidation } = { test: (param: string) => new RegExp(Constants.BASE_HEX_REGEX + '*$').test(param), error: Constants.DEFAULT_HEX_ERROR, }, + hexEvenLength: { + test: (param: string) => new RegExp(Constants.BASE_HEX_REGEX + '*$').test(param) && !(param.length % 2), + error: Constants.EVEN_HEX_ERROR, + }, hex64: { test: (param: string) => new RegExp(Constants.BASE_HEX_REGEX + '{1,64}$').test(param), error: Constants.HASH_ERROR, diff --git a/packages/server/tests/integration/server.spec.ts b/packages/server/tests/integration/server.spec.ts index aa4caa3631..b3d96e8544 100644 --- a/packages/server/tests/integration/server.spec.ts +++ b/packages/server/tests/integration/server.spec.ts @@ -859,7 +859,7 @@ describe('RPC Server', function () { BaseTest.invalidParamError( error.response, Validator.ERROR_CODE, - `Invalid parameter 'data' for TransactionObject: ${Validator.DEFAULT_HEX_ERROR}, value: 123`, + `Invalid parameter 'data' for TransactionObject: ${Validator.EVEN_HEX_ERROR}, value: 123`, ); } }); @@ -1503,7 +1503,7 @@ describe('RPC Server', function () { BaseTest.invalidParamError( error.response, Validator.ERROR_CODE, - `Invalid parameter 'data' for TransactionObject: ${Validator.DEFAULT_HEX_ERROR}, value: 123`, + `Invalid parameter 'data' for TransactionObject: ${Validator.EVEN_HEX_ERROR}, value: 123`, ); } }); diff --git a/packages/server/tests/integration/validator.spec.ts b/packages/server/tests/integration/validator.spec.ts index bcde823836..d8f14d090d 100644 --- a/packages/server/tests/integration/validator.spec.ts +++ b/packages/server/tests/integration/validator.spec.ts @@ -564,7 +564,10 @@ describe('Validator', async () => { expectInvalidObject('value', Validator.DEFAULT_HEX_ERROR, object, '123456'), ); expect(() => Validator.validateParams([{ data: '123456' }], validation)).to.throw( - expectInvalidObject('data', Validator.DEFAULT_HEX_ERROR, object, '123456'), + expectInvalidObject('data', Validator.EVEN_HEX_ERROR, object, '123456'), + ); + expect(() => Validator.validateParams([{ data: '0x1234567' }], validation)).to.throw( + expectInvalidObject('data', Validator.EVEN_HEX_ERROR, object, '0x1234567'), ); }); }); From 1c8579bc60c0dd4a578a0c9ba0d14f9f5562f186 Mon Sep 17 00:00:00 2001 From: Victor Yanev <161485803+victor-yanev@users.noreply.github.com> Date: Fri, 11 Oct 2024 20:13:59 +0300 Subject: [PATCH 30/38] fix: bugged metrics and wrong env vars (#3090) * fix: bugged metrics and wrong env vars Signed-off-by: Victor Yanev * fix: jsdocs Signed-off-by: Victor Yanev --------- Signed-off-by: Victor Yanev --- packages/relay/src/lib/constants.ts | 6 +- .../lib/services/hbarLimitService/index.ts | 69 +++++++++++-------- .../hbarLimitService/hbarLimitService.spec.ts | 44 ++++++------ 3 files changed, 64 insertions(+), 55 deletions(-) diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index abcbf5e6af..4c1b7859c1 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -142,9 +142,9 @@ export default { // TODO: Replace with actual values - https://github.com/hashgraph/hedera-json-rpc-relay/issues/2895 HBAR_RATE_LIMIT_DURATION: parseInt(process.env.HBAR_RATE_LIMIT_DURATION || '80000'), // 80 seconds HBAR_RATE_LIMIT_TOTAL: BigNumber(process.env.HBAR_RATE_LIMIT_TINYBAR || '11000000000'), // 110 HBARs per 80 seconds - HBAR_RATE_LIMIT_BASIC: BigNumber(process.env.HBAR_DAILY_LIMIT_BASIC || '92592592'), // Equivalent of 1000 HBARs per day - HBAR_RATE_LIMIT_EXTENDED: BigNumber(process.env.HBAR_DAILY_LIMIT_EXTENDED || '925925925'), // Equivalent of 10000 HBARs per day - HBAR_RATE_LIMIT_PRIVILEGED: BigNumber(process.env.HBAR_DAILY_LIMIT_PRIVILEGED || '1851851850'), // Equivalent of 20000 HBARs per day + HBAR_RATE_LIMIT_BASIC: BigNumber(process.env.HBAR_RATE_LIMIT_BASIC || '92592592'), // Equivalent of 1000 HBARs per day + HBAR_RATE_LIMIT_EXTENDED: BigNumber(process.env.HBAR_RATE_LIMIT_EXTENDED || '925925925'), // Equivalent of 10000 HBARs per day + HBAR_RATE_LIMIT_PRIVILEGED: BigNumber(process.env.HBAR_RATE_LIMIT_PRIVILEGED || '1851851850'), // Equivalent of 20000 HBARs per day GAS_PRICE_TINY_BAR_BUFFER: parseInt(process.env.GAS_PRICE_TINY_BAR_BUFFER || '10000000000'), WEB_SOCKET_PORT: process.env.WEB_SOCKET_PORT || 8546, WEB_SOCKET_HTTP_PORT: process.env.WEB_SOCKET_HTTP_PORT || 8547, diff --git a/packages/relay/src/lib/services/hbarLimitService/index.ts b/packages/relay/src/lib/services/hbarLimitService/index.ts index 7cd11af069..0596abb3d6 100644 --- a/packages/relay/src/lib/services/hbarLimitService/index.ts +++ b/packages/relay/src/lib/services/hbarLimitService/index.ts @@ -50,21 +50,21 @@ export class HbarLimitService implements IHbarLimitService { private readonly hbarLimitRemainingGauge: Gauge; /** - * Tracks the number of unique spending plans that have been utilized on a daily basis + * Tracks the number of unique spending plans that have been utilized during the limit duration. * (i.e., plans that had expenses added to them). * - * For basic spending plans, this equates to the number of unique users who have made requests on that day, + * For basic spending plans, this equates to the number of unique users who have made requests during that period, * since each user has their own individual spending plan. * * @private */ - private readonly dailyUniqueSpendingPlansCounter: Record; + private readonly uniqueSpendingPlansCounter: Record; /** - * Tracks the average daily spending plan usages. + * Tracks the average amount of tinybars spent by spending plans per subscription tier * @private */ - private readonly averageDailySpendingPlanUsagesGauge: Record; + private readonly averageSpendingPlanAmountSpentGauge: Record; /** * The remaining budget for the rate limiter. @@ -109,13 +109,13 @@ export class HbarLimitService implements IHbarLimitService { }); this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); - this.dailyUniqueSpendingPlansCounter = Object.values(SubscriptionTier).reduce( - (acc, type) => { - const dailyUniqueSpendingPlansCounterName = `daily_unique_spending_plans_counter_${type.toLowerCase()}`; - this.register.removeSingleMetric(dailyUniqueSpendingPlansCounterName); - acc[type] = new Counter({ - name: dailyUniqueSpendingPlansCounterName, - help: `Tracks the number of unique spending plans used daily for ${type} subscription tier`, + this.uniqueSpendingPlansCounter = Object.values(SubscriptionTier).reduce( + (acc, tier) => { + const uniqueSpendingPlansCounterName = `unique_spending_plans_counter_${tier.toLowerCase()}`; + this.register.removeSingleMetric(uniqueSpendingPlansCounterName); + acc[tier] = new Counter({ + name: uniqueSpendingPlansCounterName, + help: `Tracks the number of unique ${tier} spending plans used during the limit duration`, registers: [register], }); return acc; @@ -123,13 +123,13 @@ export class HbarLimitService implements IHbarLimitService { {} as Record, ); - this.averageDailySpendingPlanUsagesGauge = Object.values(SubscriptionTier).reduce( - (acc, type) => { - const averageDailySpendingGaugeName = `average_daily_spending_plan_usages_gauge_${type.toLowerCase()}`; - this.register.removeSingleMetric(averageDailySpendingGaugeName); - acc[type] = new Gauge({ - name: averageDailySpendingGaugeName, - help: `Tracks the average daily spending plan usages for ${type} subscription tier`, + this.averageSpendingPlanAmountSpentGauge = Object.values(SubscriptionTier).reduce( + (acc, tier) => { + const averageAmountSpentGaugeName = `average_spending_plan_amount_spent_gauge_${tier.toLowerCase()}`; + this.register.removeSingleMetric(averageAmountSpentGaugeName); + acc[tier] = new Gauge({ + name: averageAmountSpentGaugeName, + help: `Tracks the average amount of tinybars spent by ${tier} spending plans`, registers: [register], }); return acc; @@ -147,6 +147,7 @@ export class HbarLimitService implements IHbarLimitService { this.logger.trace(`${requestDetails.formattedRequestId} Resetting HBAR rate limiter...`); await this.hbarSpendingPlanRepository.resetAmountSpentOfAllPlans(requestDetails); this.resetBudget(); + this.resetTemporaryMetrics(); this.reset = this.getResetTimestamp(); this.logger.trace( `${requestDetails.formattedRequestId} HBAR Rate Limit reset: remainingBudget=${this.remainingBudget}, newResetTimestamp=${this.reset}`, @@ -170,7 +171,7 @@ export class HbarLimitService implements IHbarLimitService { estimatedTxFee: number = 0, ): Promise { const ipAddress = requestDetails.ipAddress; - if (await this.isDailyBudgetExceeded(mode, methodName, estimatedTxFee, requestDetails)) { + if (await this.isTotalBudgetExceeded(mode, methodName, estimatedTxFee, requestDetails)) { return true; } if (!ethAddress && !ipAddress) { @@ -224,7 +225,7 @@ export class HbarLimitService implements IHbarLimitService { // Check if the spending plan is being used for the first time today if (spendingPlan.amountSpent === 0) { - this.dailyUniqueSpendingPlansCounter[spendingPlan.subscriptionTier].inc(1); + this.uniqueSpendingPlansCounter[spendingPlan.subscriptionTier].inc(1); } await this.hbarSpendingPlanRepository.addToAmountSpent(spendingPlan.id, cost, requestDetails, this.limitDuration); @@ -232,7 +233,7 @@ export class HbarLimitService implements IHbarLimitService { this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); // Done asynchronously in the background - this.updateAverageDailyUsagePerSubscriptionTier(spendingPlan.subscriptionTier, requestDetails).then(); + this.updateAverageAmountSpentPerSubscriptionTier(spendingPlan.subscriptionTier, requestDetails).then(); this.logger.trace( `${requestDetails.formattedRequestId} HBAR rate limit expense update: cost=${cost} tℏ, remainingBudget=${this.remainingBudget}`, @@ -240,15 +241,15 @@ export class HbarLimitService implements IHbarLimitService { } /** - * Checks if the total daily budget has been exceeded. + * Checks if the total budget of the limiter has been exceeded. * @param {string} mode - The mode of the transaction or request. * @param {string} methodName - The name of the method being invoked. * @param {number} estimatedTxFee - The total estimated transaction fee, default to 0. * @param {RequestDetails} requestDetails The request details for logging and tracking - * @returns {Promise} - Resolves `true` if the daily budget has been exceeded, otherwise `false`. + * @returns {Promise} - Resolves `true` if the total budget has been exceeded, otherwise `false`. * @private */ - private async isDailyBudgetExceeded( + private async isTotalBudgetExceeded( mode: string, methodName: string, estimatedTxFee: number = 0, @@ -272,12 +273,12 @@ export class HbarLimitService implements IHbarLimitService { } /** - * Updates the average daily usage per subscription tier. - * @param {SubscriptionTier} subscriptionTier - The subscription tier to update the average daily usage for. + * Updates the average amount of tinybars spent of spending plans per subscription tier. + * @param {SubscriptionTier} subscriptionTier - The subscription tier to update the average usage for. * @param {RequestDetails} requestDetails - The request details for logging and tracking. - * @private {Promise} - A promise that resolves when the average daily usage has been updated. + * @private {Promise} - A promise that resolves when the average usage has been updated. */ - private async updateAverageDailyUsagePerSubscriptionTier( + private async updateAverageAmountSpentPerSubscriptionTier( subscriptionTier: SubscriptionTier, requestDetails: RequestDetails, ): Promise { @@ -287,7 +288,7 @@ export class HbarLimitService implements IHbarLimitService { ); const totalUsage = plans.reduce((total, plan) => total + plan.amountSpent, 0); const averageUsage = Math.round(totalUsage / plans.length); - this.averageDailySpendingPlanUsagesGauge[subscriptionTier].set(averageUsage); + this.averageSpendingPlanAmountSpentGauge[subscriptionTier].set(averageUsage); } /** @@ -308,6 +309,14 @@ export class HbarLimitService implements IHbarLimitService { this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber()); } + /** + * Resets the metrics which are used to track the number of unique spending plans used during the limit duration. + * @private + */ + private resetTemporaryMetrics(): void { + Object.values(SubscriptionTier).forEach((tier) => this.uniqueSpendingPlansCounter[tier].reset()); + } + /** * Calculates the next reset timestamp for the rate limiter. * diff --git a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts index 3785e85788..e9291a315c 100644 --- a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts +++ b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts @@ -100,8 +100,8 @@ describe('HbarLimitService', function () { expect(hbarLimitService['hbarLimitCounter']).to.be.instanceOf(Counter); expect(hbarLimitService['hbarLimitRemainingGauge']).to.be.instanceOf(Gauge); Object.values(SubscriptionTier).forEach((tier) => { - expect(hbarLimitService['dailyUniqueSpendingPlansCounter'][tier]).to.be.instanceOf(Counter); - expect(hbarLimitService['averageDailySpendingPlanUsagesGauge'][tier]).to.be.instanceOf(Gauge); + expect(hbarLimitService['uniqueSpendingPlansCounter'][tier]).to.be.instanceOf(Counter); + expect(hbarLimitService['averageSpendingPlanAmountSpentGauge'][tier]).to.be.instanceOf(Gauge); }); }); @@ -175,7 +175,7 @@ describe('HbarLimitService', function () { describe('shouldLimit', function () { describe('based on ethAddress', async function () { - it('should return true if the total daily budget is exceeded', async function () { + it('should return true if the total budget is exceeded', async function () { // @ts-ignore hbarLimitService.remainingBudget = Hbar.fromTinybars(0); const result = await hbarLimitService.shouldLimit(mode, methodName, mockEthAddress, requestDetails); @@ -322,7 +322,7 @@ describe('HbarLimitService', function () { }); describe('based on ipAddress', async function () { - it('should return true if the total daily budget is exceeded', async function () { + it('should return true if the total budget is exceeded', async function () { // @ts-ignore hbarLimitService.remainingBudget = Hbar.fromTinybars(0); const result = await hbarLimitService.shouldLimit(mode, methodName, '', requestDetails); @@ -627,17 +627,17 @@ describe('HbarLimitService', function () { spendingHistory: [{ amount: expense, timestamp: new Date() }], }, ]); - const incDailyUniqueSpendingPlansCounterSpy = sinon.spy( - hbarLimitService['dailyUniqueSpendingPlansCounter'][SubscriptionTier.BASIC], + const incUniqueSpendingPlansCounterSpy = sinon.spy( + hbarLimitService['uniqueSpendingPlansCounter'][SubscriptionTier.BASIC], 'inc', ); - const setAverageDailySpendingPlanUsagesGaugeSpy = sinon.spy( - hbarLimitService['averageDailySpendingPlanUsagesGauge'][SubscriptionTier.BASIC], + const setAverageSpendingPlanAmountSpentGaugeSpy = sinon.spy( + hbarLimitService['averageSpendingPlanAmountSpentGauge'][SubscriptionTier.BASIC], 'set', ); - const updateAverageDailyUsagePerSubscriptionTierSpy = sinon.spy( + const updateAverageAmountSpentPerSubscriptionTierSpy = sinon.spy( hbarLimitService, - 'updateAverageDailyUsagePerSubscriptionTier', + 'updateAverageAmountSpentPerSubscriptionTier' as any, ); await hbarLimitService.addExpense(expense, ethAddress, requestDetails); @@ -649,10 +649,10 @@ describe('HbarLimitService', function () { expect((await hbarLimitService['hbarLimitRemainingGauge'].get()).values[0].value).to.equal( hbarLimitService['totalBudget'].toTinybars().sub(expense).toNumber(), ); - await Promise.all(updateAverageDailyUsagePerSubscriptionTierSpy.returnValues); + await Promise.all(updateAverageAmountSpentPerSubscriptionTierSpy.returnValues); const expectedAverageUsage = Math.round((otherPlanOfTheSameTier.amountSpent + expense) / 2); - sinon.assert.calledOnceWithExactly(setAverageDailySpendingPlanUsagesGaugeSpy, expectedAverageUsage); - sinon.assert.calledOnceWithExactly(incDailyUniqueSpendingPlansCounterSpy, 1); + sinon.assert.calledOnceWithExactly(setAverageSpendingPlanAmountSpentGaugeSpy, expectedAverageUsage); + sinon.assert.calledOnceWithExactly(incUniqueSpendingPlansCounterSpy, 1); }; it('should throw an error if empty ethAddress or ipAddress is provided', async function () { @@ -710,31 +710,31 @@ describe('HbarLimitService', function () { }); }); - describe('isDailyBudgetExceeded', function () { - const testIsDailyBudgetExceeded = async (remainingBudget: number, expected: boolean) => { + describe('isTotalBudgetExceeded', function () { + const testIsTotalBudgetExceeded = async (remainingBudget: number, expected: boolean) => { // @ts-ignore hbarLimitService.remainingBudget = Hbar.fromTinybars(remainingBudget); await expect( - hbarLimitService['isDailyBudgetExceeded'](mode, methodName, undefined, requestDetails), + hbarLimitService['isTotalBudgetExceeded'](mode, methodName, undefined, requestDetails), ).to.eventually.equal(expected); }; it('should return true when the remaining budget is zero', async function () { - await testIsDailyBudgetExceeded(0, true); + await testIsTotalBudgetExceeded(0, true); }); it('should return true when the remaining budget is negative', async function () { - await testIsDailyBudgetExceeded(-1, true); + await testIsTotalBudgetExceeded(-1, true); }); it('should return false when the remaining budget is greater than zero', async function () { - await testIsDailyBudgetExceeded(100, false); + await testIsTotalBudgetExceeded(100, false); }); - it('should update the hbar limit counter when a method is called and the daily budget is exceeded', async function () { + it('should update the hbar limit counter when a method is called and the total budget is exceeded', async function () { // @ts-ignore const hbarLimitCounterSpy = sinon.spy(hbarLimitService.hbarLimitCounter, 'inc'); - await testIsDailyBudgetExceeded(0, true); + await testIsTotalBudgetExceeded(0, true); expect(hbarLimitCounterSpy.calledWithMatch({ mode, methodName }, 1)).to.be.true; }); @@ -742,7 +742,7 @@ describe('HbarLimitService', function () { // @ts-ignore hbarLimitService.reset = new Date(); const resetLimiterSpy = sinon.spy(hbarLimitService, 'resetLimiter'); - await testIsDailyBudgetExceeded(0, false); + await testIsTotalBudgetExceeded(0, false); expect(resetLimiterSpy.calledOnce).to.be.true; }); }); From afc33495df2cbc7692475f57df1f566222da8ea4 Mon Sep 17 00:00:00 2001 From: Brad Bowman <294617+beeradb@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:55:28 -0400 Subject: [PATCH 31/38] feat: add maxAttempts parameter to the acceptance test workflow (#3089) Add retry parameter and default public acceptance tests to 1 Signed-off-by: beeradb <294617+beeradb@users.noreply.github.com> --- .github/workflows/acceptance-public.yml | 1 + .github/workflows/acceptance-workflow.yml | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/acceptance-public.yml b/.github/workflows/acceptance-public.yml index 84c2dde982..a6471a821a 100644 --- a/.github/workflows/acceptance-public.yml +++ b/.github/workflows/acceptance-public.yml @@ -28,6 +28,7 @@ jobs: testfilter: release_light envfile: ${{ inputs.network }}Acceptance.env operator_id: ${{ inputs.operator_id }} + maxAttempts: 1 secrets: operator_key: ${{ inputs.operator_key }} diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index 6ae4e2f66e..2ec6bb5ec2 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -26,6 +26,10 @@ on: relayTag: required: false type: string + maxAttempts: + required: false + type: number + default: 3 secrets: operator_key: description: 'The ED25519, ECDSA, or DER encoded private key of the operator' @@ -82,7 +86,7 @@ jobs: - name: Run acceptance tests uses: step-security/retry@2ab886c0de89f68f146c9b43f53e61abc59c46dc # v3.0.1 with: - max_attempts: 3 + max_attempts: ${{ inputs.maxAttempts }} timeout_minutes: 30 command: npm run acceptancetest:${{ inputs.testfilter }} env: From 7796aca3d8028e9732c58fb6855bef4520c4b3cc Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Tue, 15 Oct 2024 15:30:57 -0400 Subject: [PATCH 32/38] fix: included only transfer amounts that are charged to the operator in getTransferAmountSumForAccount() (#3103) Signed-off-by: Logan Nguyen --- packages/relay/src/lib/clients/mirrorNodeClient.ts | 2 +- packages/relay/src/lib/clients/sdkClient.ts | 2 +- packages/relay/tests/lib/mirrorNodeClient.spec.ts | 13 ++++++++++++- packages/relay/tests/lib/sdkClient.spec.ts | 13 +++++++++---- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index 3ff0f0b9f5..9a45a91286 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -1351,7 +1351,7 @@ export class MirrorNodeClient { */ public getTransferAmountSumForAccount(transactionRecord: MirrorNodeTransactionRecord, accountId: string): number { return transactionRecord.transfers - .filter((transfer) => transfer.account === accountId) + .filter((transfer) => transfer.account === accountId && transfer.amount < 0) .reduce((acc, transfer) => { return acc - transfer.amount; }, 0); diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 6a6e9f20f7..84b7c240f0 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -1039,7 +1039,7 @@ export class SDKClient { */ public getTransferAmountSumForAccount(transactionRecord: TransactionRecord, accountId: string): number { return transactionRecord.transfers - .filter((transfer) => transfer.accountId.toString() === accountId) + .filter((transfer) => transfer.accountId.toString() === accountId && transfer.amount.isNegative()) .reduce((acc, transfer) => { return acc - transfer.amount.toTinybars().toNumber(); }, 0); diff --git a/packages/relay/tests/lib/mirrorNodeClient.spec.ts b/packages/relay/tests/lib/mirrorNodeClient.spec.ts index 1efe61b1d8..2d7a81e068 100644 --- a/packages/relay/tests/lib/mirrorNodeClient.spec.ts +++ b/packages/relay/tests/lib/mirrorNodeClient.spec.ts @@ -1295,11 +1295,12 @@ describe('MirrorNodeClient', async function () { }); describe('getTransactionRecordMetrics', () => { - it('Should execute getTransferAmountSumForAccount() to calculate transactionFee of the specify accountId', () => { + it('Should execute getTransferAmountSumForAccount() to calculate transactionFee by only transfers that are paid by the specify accountId', () => { const accountIdA = `0.0.1022`; const accountIdB = `0.0.1023`; const mockedTxFeeA = 300; const mockedTxFeeB = 600; + const mockedTxFeeC = 900; const expectedTxFeeForAccountIdA = mockedTxFeeA + mockedTxFeeB; @@ -1325,6 +1326,16 @@ describe('MirrorNodeClient', async function () { amount: -1 * mockedTxFeeB, is_approval: false, }, + { + account: accountIdA, + amount: mockedTxFeeC, + is_approval: false, + }, + { + account: accountIdA, + amount: mockedTxFeeB, + is_approval: false, + }, ], }, ], diff --git a/packages/relay/tests/lib/sdkClient.spec.ts b/packages/relay/tests/lib/sdkClient.spec.ts index 74d74cda5c..a350bd5e5d 100644 --- a/packages/relay/tests/lib/sdkClient.spec.ts +++ b/packages/relay/tests/lib/sdkClient.spec.ts @@ -2194,6 +2194,11 @@ describe('SdkClient', async function () { amount: Hbar.fromTinybars(-1 * defaultTransactionFee), is_approval: false, }, + { + accountId: process.env.OPERATOR_ID_MAIN, + amount: Hbar.fromTinybars(defaultTransactionFee), + is_approval: false, + }, { accountId: accountId.toString(), amount: Hbar.fromTinybars(-1 * defaultTransactionFee), @@ -2225,7 +2230,7 @@ describe('SdkClient', async function () { }, }) as unknown as TransactionResponse; - const getMockedTransactionRecord: any = (transactionType: string) => ({ + const getMockedTransactionRecord: any = (transactionType: string, toHbar: boolean = false) => ({ receipt: { status: Status.Success, exchangeRate: { exchangeRateInCents: 12 }, @@ -2234,7 +2239,7 @@ describe('SdkClient', async function () { contractFunctionResult: { gasUsed, }, - transfers: getMockedTransaction(transactionType, false).transfers, + transfers: getMockedTransaction(transactionType, toHbar).transfers, }); const fileInfo = { @@ -2710,9 +2715,9 @@ describe('SdkClient', async function () { } }); - it('Should execute getTransferAmountSumForAccount() to calculate transactionFee of the specify accountId', () => { + it('Should execute getTransferAmountSumForAccount() to calculate transactionFee by only transfers that are paid by the specify accountId', () => { const accountId = process.env.OPERATOR_ID_MAIN || ''; - const mockedTxRecord = getMockedTransactionRecord(); + const mockedTxRecord = getMockedTransactionRecord(EthereumTransaction.name, true); const transactionFee = sdkClient.getTransferAmountSumForAccount(mockedTxRecord, accountId); expect(transactionFee).to.eq(defaultTransactionFee); From 4d88bcfdb04d15d477c7a96de6791b642f82d982 Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 11 Sep 2024 10:18:52 +0300 Subject: [PATCH 33/38] Updating workflows/actions to be compatible to and updating to the step-security maintained version. Signed-off-by: Vasil Boyadzhiev Resolving conflicts and rebasing. --- .github/workflows/acceptance-public.yml | 4 ++-- .github/workflows/acceptance-workflow.yml | 12 +++++++--- .github/workflows/acceptance.yml | 7 +++--- .github/workflows/manual-testing.yml | 29 ++++++++++++++--------- .github/workflows/release-acceptance.yml | 6 ++--- .github/workflows/test.yml | 7 +++--- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/.github/workflows/acceptance-public.yml b/.github/workflows/acceptance-public.yml index a6471a821a..f043662cc2 100644 --- a/.github/workflows/acceptance-public.yml +++ b/.github/workflows/acceptance-public.yml @@ -51,8 +51,8 @@ jobs: merge-multiple: true - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: check_name: Test Results json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index 2ec6bb5ec2..aa042b7bd2 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -62,6 +62,12 @@ jobs: with: node-version: 20 + - name: Install make + run: sudo apt-get update; sudo apt-get install build-essential -y + + - name: Checkout repo + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - name: Install packages run: npm ci @@ -119,10 +125,10 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 if: ${{ !cancelled() }} with: - check_run_disabled: true + check_name: '' # Set to empty to disable check run comment_mode: off json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index db7a21f737..9fc002884f 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -139,9 +139,8 @@ jobs: merge-multiple: true - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: - check_name: Acceptance Tests - check_run_disabled: true + check_name: '' # Set to empty to disable check run json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/manual-testing.yml b/.github/workflows/manual-testing.yml index 7e2700a15e..3b2b342061 100644 --- a/.github/workflows/manual-testing.yml +++ b/.github/workflows/manual-testing.yml @@ -42,7 +42,7 @@ jobs: testfilter: api_batch3 networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + erc20: name: ERC20 uses: ./.github/workflows/acceptance-workflow.yml @@ -50,7 +50,7 @@ jobs: testfilter: erc20 networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + ratelimiter: name: Rate Limiter uses: ./.github/workflows/acceptance-workflow.yml @@ -67,7 +67,7 @@ jobs: testfilter: hbarlimiter networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + tokencreate: name: Token Create uses: ./.github/workflows/acceptance-workflow.yml @@ -75,7 +75,7 @@ jobs: testfilter: tokencreate networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + tokenmanagement: name: Token Management uses: ./.github/workflows/acceptance-workflow.yml @@ -83,7 +83,7 @@ jobs: testfilter: tokenmanagement networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + htsprecompilev1: name: Precompile uses: ./.github/workflows/acceptance-workflow.yml @@ -91,7 +91,7 @@ jobs: testfilter: htsprecompilev1 networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + precompilecalls: name: Precompile Calls uses: ./.github/workflows/acceptance-workflow.yml @@ -100,8 +100,13 @@ jobs: networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} +<<<<<<< HEAD websocket-batch-1: name: Websocket Batch 1 +======= + websocket: + name: Websocket +>>>>>>> 96afa059 (Updating workflows/actions to be compatible to and updating to the step-security maintained version.) uses: ./.github/workflows/acceptance-workflow.yml with: testfilter: ws_batch1 @@ -126,7 +131,7 @@ jobs: test_ws_server: true networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} - + cacheservice: name: Cache Service uses: ./.github/workflows/acceptance-workflow.yml @@ -135,6 +140,7 @@ jobs: networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} +<<<<<<< HEAD server-config: name: Server Config uses: ./.github/workflows/acceptance-workflow.yml @@ -143,6 +149,8 @@ jobs: networkTag: ${{inputs.networkNodeTag}} mirrorTag: ${{inputs.mirrorNodeTag}} +======= +>>>>>>> 96afa059 (Updating workflows/actions to be compatible to and updating to the step-security maintained version.) publish_results: name: Publish Results if: ${{ !cancelled() }} @@ -174,9 +182,8 @@ jobs: merge-multiple: true - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: - check_name: Acceptance Tests - check_run_disabled: true + check_name: '' # Set to empty to disable check run json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/release-acceptance.yml b/.github/workflows/release-acceptance.yml index cab5f2df74..b42e708b00 100644 --- a/.github/workflows/release-acceptance.yml +++ b/.github/workflows/release-acceptance.yml @@ -89,10 +89,10 @@ jobs: path: test-*.xml - name: Publish Test Report - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 if: ${{ !cancelled() }} with: - check_run_disabled: true + check_name: '' # Set to empty to disable check run comment_mode: off json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 941aa920e2..6b1a3e656b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,9 +64,8 @@ jobs: - name: Publish Test Report if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && github.actor != 'dependabot[bot]' && github.actor != 'swirlds-automation' && !cancelled() && !failure() }} - uses: actionite/publish-unit-test-result-action@1e01e49081c6c4073913aa4b7980fa83e709f322 # v2.3.0 + uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: - check_name: Tests - check_run_disabled: true + check_name: '' # Set to empty to disable check run json_thousands_separator: ',' - junit_files: 'test-*.xml' + files: 'test-*.xml' From 7e4eaf2777f358e5e6343d262792e72c7fc7b902 Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 11 Sep 2024 10:42:12 +0300 Subject: [PATCH 34/38] Adding github Token to the action. Signed-off-by: Vasil Boyadzhiev --- .github/workflows/acceptance-public.yml | 1 + .github/workflows/acceptance-workflow.yml | 1 + .github/workflows/acceptance.yml | 1 + .github/workflows/manual-testing.yml | 1 + .github/workflows/release-acceptance.yml | 1 + .github/workflows/test.yml | 1 + 6 files changed, 6 insertions(+) diff --git a/.github/workflows/acceptance-public.yml b/.github/workflows/acceptance-public.yml index f043662cc2..88292ca4fc 100644 --- a/.github/workflows/acceptance-public.yml +++ b/.github/workflows/acceptance-public.yml @@ -56,3 +56,4 @@ jobs: check_name: Test Results json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index aa042b7bd2..4c3d2207c2 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -132,3 +132,4 @@ jobs: comment_mode: off json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 9fc002884f..4c2a0854ff 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -144,3 +144,4 @@ jobs: check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/manual-testing.yml b/.github/workflows/manual-testing.yml index 3b2b342061..f0ae3e482e 100644 --- a/.github/workflows/manual-testing.yml +++ b/.github/workflows/manual-testing.yml @@ -187,3 +187,4 @@ jobs: check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-acceptance.yml b/.github/workflows/release-acceptance.yml index b42e708b00..70354e15bf 100644 --- a/.github/workflows/release-acceptance.yml +++ b/.github/workflows/release-acceptance.yml @@ -96,3 +96,4 @@ jobs: comment_mode: off json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b1a3e656b..cc75355c9a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -69,3 +69,4 @@ jobs: check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' + github_token: ${{ secrets.GITHUB_TOKEN }} From bbae986986bc7d8d64c733b8c862f3b0a459507a Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 11 Sep 2024 11:15:39 +0300 Subject: [PATCH 35/38] Adding permissions for checks to: write Signed-off-by: Vasil Boyadzhiev Resolving conflicts and rebasing. --- .github/workflows/acceptance-workflow.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index 4c3d2207c2..b2f9625092 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -44,7 +44,11 @@ jobs: timeout-minutes: 50 permissions: contents: write +<<<<<<< HEAD actions: read +======= + checks: write +>>>>>>> bc640352 (Adding permissions for checks to: write) # issues: read env: OPERATOR_ID_MAIN: ${{ inputs.operator_id }} From e790fee6dad4376d11192164a3305f66c938aeae Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Fri, 13 Sep 2024 16:28:19 +0300 Subject: [PATCH 36/38] Applying suggestions from code review. Signed-off-by: Vasil Boyadzhiev --- .github/workflows/acceptance.yml | 1 + .github/workflows/manual-testing.yml | 1 + .github/workflows/test.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 4c2a0854ff..13e68595ed 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -141,6 +141,7 @@ jobs: - name: Publish Test Report uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: + # check_name: Acceptance Tests check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' diff --git a/.github/workflows/manual-testing.yml b/.github/workflows/manual-testing.yml index f0ae3e482e..b316e913c6 100644 --- a/.github/workflows/manual-testing.yml +++ b/.github/workflows/manual-testing.yml @@ -184,6 +184,7 @@ jobs: - name: Publish Test Report uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: + # check_name: Acceptance Tests check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc75355c9a..ea593998b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,6 +66,7 @@ jobs: if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && github.actor != 'dependabot[bot]' && github.actor != 'swirlds-automation' && !cancelled() && !failure() }} uses: step-security/publish-unit-test-result-action@4519d7c9f71dd765f8bbb98626268780f23bab28 # v2.17.0 with: + # check_name: Tests check_name: '' # Set to empty to disable check run json_thousands_separator: ',' files: 'test-*.xml' From e866bfde1c6daa6cb3c1c14329ef43c4d8ad2166 Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 16 Oct 2024 13:54:24 +0300 Subject: [PATCH 37/38] ci: Updating without merge conflicts. Signed-off-by: Vasil Boyadzhiev --- .github/workflows/acceptance-workflow.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index b2f9625092..3dbc8cd1ab 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -44,11 +44,8 @@ jobs: timeout-minutes: 50 permissions: contents: write -<<<<<<< HEAD actions: read -======= checks: write ->>>>>>> bc640352 (Adding permissions for checks to: write) # issues: read env: OPERATOR_ID_MAIN: ${{ inputs.operator_id }} From bb0895e1424be21ad928975aa9af1559d194249c Mon Sep 17 00:00:00 2001 From: Vasil Boyadzhiev Date: Wed, 16 Oct 2024 13:59:54 +0300 Subject: [PATCH 38/38] ci: Fixing changes and merge conflicts. Signed-off-by: Vasil Boyadzhiev --- .github/workflows/acceptance-workflow.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/acceptance-workflow.yml b/.github/workflows/acceptance-workflow.yml index f65cb0ee20..3dbc8cd1ab 100644 --- a/.github/workflows/acceptance-workflow.yml +++ b/.github/workflows/acceptance-workflow.yml @@ -44,11 +44,7 @@ jobs: timeout-minutes: 50 permissions: contents: write -<<<<<<< HEAD actions: read -======= - checks: write ->>>>>>> bc640352 (Adding permissions for checks to: write) checks: write # issues: read env: