From f8a2131e9fc0ea05c1c1c8863489a43a74f019ae Mon Sep 17 00:00:00 2001 From: Michal Bajer Date: Tue, 4 Jul 2023 15:13:20 +0000 Subject: [PATCH] feat(cactus-plugin-ledger-connector-ethereum): add new connector plugin Add new plugin for connecting with Ethereum ledgers. New connector is based on already existing quorum connector. The main reason for introducing yet another plugin is a need for web3js upgrade (to 4.X) which is imposible in current quorum / besu connectors due to dependency to `web3js-quorum` which requires web3js 1.X. We have plans to make web3js library pluggable and reduce code duplication among other connectors in the future, but it will be delivered later on in a separate PR. Changes: - Add new plugin based on quorum connector. - Removed private transaction and other quorum related functionalities. - Update web3js to 1.10 - will be updated to 4.X in a separate commit. - Add missing `web3-eth-contract` dependencies to besu and xdai connectors. - Add new connector to cactus-verifier-client - Add integration tests in `cactus-test-plugin-ledger-connector-ethereum` (based on similar ones for quorum connector.) - Add new connector to CI. - Add `web3*` 4.1.1 dependencies to root `package.json` because they are already required in a root level (by `typings/web3js-quorum`). Ideally this could be put into another package (quorum connector?) and have the dependencies there, but for now I think it's important to be explicit about it since it's easy to mess up if wrong web3js library is hoisted up from any monorepo package. - Sort main `package.json` - Remove tap test scripts from the root `package.json` - they don't use `.taprc` and cause bunch of errors when they try to execute jest tests. This is confusing for the users because of all false negative errors printed. - Reorganize jest config and taprc to keep tests from quorum and ethereum conenctors grouped. Peter's tweaks: 1. Switched the root's web3 dependencies from 1.5.2 to 4.1.1 because in the meantime we've done a few upgrades (plus the old versions come with CVEs) 2. There is no more hoisting, it's been completely disabled, so we should not have any more issues regarding that. It is still true however, that the root folder has our handwritten type definitions which do import web3 packages and therefore the root should declare web3 as a dependency. 3. Once https://github.com/hyperledger/cacti/issues/2648 has been resolved we can remove both the root dependency declarations of web3 and the typings that necessitate them to begin with. Closes: #2534 Co-authored-by: Peter Somogyvari Signed-off-by: Michal Bajer Signed-off-by: Peter Somogyvari --- .github/workflows/ci.yaml | 56 + .taprc | 27 +- jest.config.js | 28 +- package.json | 36 +- .../Dockerfile | 5 + .../README.md | 326 +++ .../openapitools.json | 7 + .../package.json | 107 + .../src/main/json/openapi.json | 1388 ++++++++++++ .../api-client/ethereum-api-client.ts | 255 +++ .../.openapi-generator-ignore | 27 + .../typescript-axios/.openapi-generator/FILES | 5 + .../.openapi-generator/VERSION | 1 + .../generated/openapi/typescript-axios/api.ts | 1966 +++++++++++++++++ .../openapi/typescript-axios/base.ts | 72 + .../openapi/typescript-axios/common.ts | 150 ++ .../openapi/typescript-axios/configuration.ts | 101 + .../openapi/typescript-axios/index.ts | 18 + .../src/main/typescript/index.ts | 1 + .../src/main/typescript/index.web.ts | 1 + .../src/main/typescript/model-type-guards.ts | 42 + .../plugin-factory-ledger-connector.ts | 20 + .../plugin-ledger-connector-ethereum.ts | 802 +++++++ .../prometheus-exporter/data.fetcher.ts | 12 + .../typescript/prometheus-exporter/metrics.ts | 10 + .../prometheus-exporter.ts | 39 + .../prometheus-exporter/response.type.ts | 3 + .../src/main/typescript/public-api.ts | 31 + ...-solidity-bytecode-endpoint-json-object.ts | 109 + ...loy-contract-solidity-bytecode-endpoint.ts | 102 + ...prometheus-exporter-metrics-endpoint-v1.ts | 100 + .../invoke-contract-endpoint-json-object.ts | 103 + .../web-services/invoke-contract-endpoint.ts | 102 + ...invoke-raw-web3eth-contract-v1-endpoint.ts | 109 + .../invoke-raw-web3eth-method-v1-endpoint.ts | 107 + .../web-services/run-transaction-endpoint.ts | 100 + .../web-services/watch-blocks-v1-endpoint.ts | 103 + .../scripts/get-quorum-connector-status.ts | 113 + .../hello-world-contract/HelloWorld.json | 418 ++++ .../hello-world-contract/HelloWorld.sol | 26 + .../integration/api-surface.test.ts | 5 + .../openapi-validation-no-keychain.test.ts | 323 +++ .../openapi/openapi-validation.test.ts | 427 ++++ ...ct-from-json-json-object-endpoints.test.ts | 390 ++++ ...loy-contract-from-json-json-object.test.ts | 381 ++++ .../v2.3.0-deploy-contract-from-json.test.ts | 469 ++++ ...oke-contract-json-object-endpoints.test.ts | 357 +++ ...v2.3.0-invoke-contract-json-object.test.ts | 310 +++ .../v2.3.0-invoke-contract.test.ts | 380 ++++ ...ct-from-json-json-object-endpoints.test.ts | 386 ++++ ...loy-contract-from-json-json-object.test.ts | 378 ++++ .../v21.4.1-deploy-contract-from-json.test.ts | 441 ++++ ...oke-contract-json-object-endpoints.test.ts | 352 +++ ...21.4.1-invoke-contract-json-object.test.ts | 306 +++ .../v21.4.1-invoke-contract.test.ts | 365 +++ .../v21.4.1-invoke-web3-contract-v1.test.ts | 203 ++ .../v21.4.1-invoke-web3-method-v1.test.ts | 157 ++ .../test/typescript/unit/api-surface.test.ts | 6 + .../typescript/unit/model-type-guards.test.ts | 45 + .../tsconfig.json | 32 + .../README.md | 15 + .../package.json | 79 + .../src/main/typescript/index.ts | 1 + .../src/main/typescript/index.web.ts | 1 + .../src/main/typescript/public-api.ts | 1 + .../hello-world-contract/HelloWorld.json | 418 ++++ .../hello-world-contract/HelloWorld.sol | 26 + ...ntegration-with-ethereum-connector.test.ts | 631 ++++++ .../integration/api-surface.test.ts | 5 + .../deploy-contract-via-web-service.test.ts | 270 +++ .../test/typescript/unit/api-surface.test.ts | 6 + .../tsconfig.json | 38 + packages/cactus-verifier-client/README.md | 3 +- packages/cactus-verifier-client/package.json | 1 + .../typescript/get-validator-api-client.ts | 11 + packages/cactus-verifier-client/tsconfig.json | 3 + tsconfig.json | 6 + yarn.lock | 342 ++- 78 files changed, 14557 insertions(+), 41 deletions(-) create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/Dockerfile create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/README.md create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/openapitools.json create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/package.json create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/api-client/ethereum-api-client.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/base.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/common.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/configuration.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/index.ts create mode 100755 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.ts create mode 100755 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/model-type-guards.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-factory-ledger-connector.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/data.fetcher.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/metrics.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/prometheus-exporter.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/response.type.ts create mode 100755 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint-json-object.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/get-prometheus-exporter-metrics-endpoint-v1.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint-json-object.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/scripts/get-quorum-connector-status.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-contract-v1.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-method-v1.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/README.md create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/package.json create mode 100755 packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.ts create mode 100755 packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts create mode 100755 packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/deploy-contract-via-web-service.test.ts create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts create mode 100644 packages/cactus-test-plugin-ledger-connector-ethereum/tsconfig.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 16e6190612..4960b64a8d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1319,6 +1319,35 @@ jobs: restore-keys: | ${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }} - run: ./tools/ci.sh + cactus-plugin-ledger-connector-ethereum: + continue-on-error: false + env: + FULL_BUILD_DISABLED: true + JEST_TEST_PATTERN: packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts + JEST_TEST_RUNNER_DISABLED: false + TAPE_TEST_PATTERN: >- + --files={./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts,./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts} + TAPE_TEST_RUNNER_DISABLED: false + needs: build-dev + runs-on: ubuntu-20.04 + steps: + - name: Use Node.js v16.14.2 + uses: actions/setup-node@v3.6.0 + with: + node-version: v16.14.2 + - uses: actions/checkout@v3.5.2 + - id: yarn-cache-dir-path + name: Get yarn cache directory path + run: echo "::set-output name=dir::$(yarn cache dir)" + - id: yarn-cache + name: Restore Yarn Cache + uses: actions/cache@v3.0.4 + with: + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + restore-keys: | + ${{ runner.os }}-yarn- + - run: ./tools/ci.sh cactus-plugin-ledger-connector-quorum: continue-on-error: false env: @@ -1694,6 +1723,33 @@ jobs: restore-keys: | ${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }} - run: ./tools/ci.sh + cactus-test-plugin-ledger-connector-ethereum: + continue-on-error: false + env: + FULL_BUILD_DISABLED: true + JEST_TEST_PATTERN: packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts + JEST_TEST_RUNNER_DISABLED: false + TAPE_TEST_RUNNER_DISABLED: true + needs: build-dev + runs-on: ubuntu-20.04 + steps: + - name: Use Node.js v16.14.2 + uses: actions/setup-node@v3.6.0 + with: + node-version: v16.14.2 + - uses: actions/checkout@v3.5.2 + - id: yarn-cache-dir-path + name: Get yarn cache directory path + run: echo "::set-output name=dir::$(yarn cache dir)" + - id: yarn-cache + name: Restore Yarn Cache + uses: actions/cache@v3.0.4 + with: + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + restore-keys: | + ${{ runner.os }}-yarn- + - run: ./tools/ci.sh cactus-test-tooling: continue-on-error: false env: diff --git a/.taprc b/.taprc index e78b31a783..5b6bdfcb64 100644 --- a/.taprc +++ b/.taprc @@ -13,25 +13,36 @@ files: - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation.test.ts + - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts + - ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts - ./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/counterparty-htlc-endpoint.test.ts - ./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/own-htlc-endpoint.test.ts - ./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/withdraw-counterparty-endpoint.test.ts - ./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/refund.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts - ./packages/cactus-plugin-keychain-memory-wasm/src/test/typescript/unit/plugin-keychain-memory-wasm.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts - ./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/add-orgs.test.ts - ./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/obtain-profiles.test.ts - ./packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts - ./packages/cactus-plugin-keychain-aws-sm/src/test/typescript/integration/plugin-factory-keychain.test.ts - ./packages/cactus-plugin-keychain-aws-sm/src/test/typescript/integration/plugin-factory-keychain.test.ts - ./packages/cactus-plugin-keychain-azure-kv/src/test/typescript/integration/plugin-keychain-azure-kv.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation.test.ts - - ./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts - ./packages/cactus-test-plugin-htlc-eth-besu-erc20/src/test/typescript/integration/plugin-htlc-eth-besu-erc20/get-status-endpoint.test.ts - ./packages/cactus-test-plugin-htlc-eth-besu-erc20/src/test/typescript/integration/plugin-htlc-eth-besu-erc20/openapi/openapi-validation.test.ts - ./packages/cactus-test-plugin-htlc-eth-besu-erc20/src/test/typescript/integration/plugin-htlc-eth-besu-erc20/refund-endpoint.test.ts diff --git a/jest.config.js b/jest.config.js index 5df0863340..3adc29071a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,25 +19,37 @@ module.exports = { `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts`, `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts`, `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation.test.ts`, + `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts`, + `./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts`, `./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/counterparty-htlc-endpoint.test.ts`, `./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/own-htlc-endpoint.test.ts`, `./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/withdraw-counterparty-endpoint.test.ts`, `./extensions/cactus-plugin-htlc-coordinator-besu/src/test/typescript/integration/plugin-htlc-coordinator/refund.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts`, `./packages/cactus-plugin-keychain-memory-wasm/src/test/typescript/unit/plugin-keychain-memory-wasm.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts`, `./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/add-orgs.test.ts`, `./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/obtain-profiles.test.ts`, `./packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts`, `./packages/cactus-plugin-keychain-aws-sm/src/test/typescript/integration/plugin-factory-keychain.test.ts`, `./packages/cactus-plugin-keychain-aws-sm/src/test/typescript/integration/plugin-factory-keychain.test.ts`, `./packages/cactus-plugin-keychain-azure-kv/src/test/typescript/integration/plugin-keychain-azure-kv.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation.test.ts`, - `./packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts`, + `./packages/cactus-test-plugin-htlc-eth-besu-erc20/src/test/typescript/integration/plugin-htlc-eth-besu-erc20/get-status-endpoint.test.ts`, `./packages/cactus-test-plugin-htlc-eth-besu-erc20/src/test/typescript/integration/plugin-htlc-eth-besu-erc20/openapi/openapi-validation.test.ts`, `./packages/cactus-test-plugin-htlc-eth-besu-erc20/src/test/typescript/integration/plugin-htlc-eth-besu-erc20/refund-endpoint.test.ts`, diff --git a/package.json b/package.json index ecf820d8d7..6378f9ad08 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "weaver/samples/besu/simplestate" ] }, - "packageManager": "yarn@3.6.0", "scripts": { "run-ci": "./tools/ci.sh", "reset:node-modules": "del-cli '**/node_modules'", @@ -66,24 +65,12 @@ "build:dev:frontend": "lerna run build:dev:frontend --scope='@hyperledger/cactus-example-*-frontend' --scope='@hyperledger/cacti-ledger-browser'", "build:dev:common": "lerna exec --stream --scope '*/*common' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:backend:postbuild": "lerna run build:dev:backend:postbuild", - "test:cmd-api-server": "tap --ts --timeout=600 \"packages/cactus-*cmd-api-server/src/test/typescript/{unit,integration}/\"", - "test:plugin-ledger-connector-besu": "tap --ts --jobs=1 --timeout=60 \"packages/cactus-*-besu/src/test/typescript/{unit,integration}/\"", - "test:plugin-htlc-besu-erc20": "tap --jobs=1 --timeout=600 \"packages/*htlc-eth-besu-erc20/src/test/typescript/{unit,integration}/\"", - "test:plugin": "tap --jobs=1 --timeout=600 \"packages/*test-plugin-htlc-eth-besu/src/test/typescript/{unit,integration}/\"", - "test:plugin-ledger-connector-quorum": "tap --ts --jobs=1 --timeout=60 \"packages/cactus-*-quorum/src/test/typescript/{unit,integration}/\"", - "test:plugin-ledger-connector-iroha": "tap --ts --jobs=1 --timeout=600 \"packages/cactus-*-iroha/src/test/typescript/{unit,integration}/\"", - "test:plugin-htlc-besu": "tap --jobs=1 --timeout=600 \"packages/*htlc-eth-besu/src/test/typescript/{integration}/\"", - "test:plugin-ledger-connector-corda": "tap --ts --jobs=1 --timeout=600 \"packages/cactus-*-corda/src/test/typescript/{unit,integration}/\"", "webpack": "lerna run webpack:dev", "webpack:dev:web": "lerna run webpack:dev:web", "webpack:dev:node": "lerna run webpack:dev:node", "test:jest:all": "NODE_OPTIONS=--max_old_space_size=3072 jest", "test:tap:all": "tap", "test:all": "yarn test:jest:all && yarn test:tap:all", - "posttest:all": "tap --no-check-coverage --coverage-report=lcov", - "test:unit": "tap --ts --node-arg=--max-old-space-size=4096 --timeout=600 --no-check-coverage \"packages/cactus-*/src/test/typescript/unit/\"", - "test:benchmark": "tap --ts --jobs=1 --no-timeout --no-check-coverage \"packages/cactus-*/src/test/typescript/benchmark/\"", - "test:integration": "tap --ts --node-arg=--max-old-space-size=4096 --jobs=1 --timeout=3600 --no-check-coverage \"packages/cactus-*/src/test/typescript/integration/\"", "changelog": "conventional-changelog --infile CHANGELOG.md --outfile CHANGELOG.md && git add CHANGELOG.md", "commit": "git-cz --signoff", "prettier": "prettier --write --config .prettierrc.js \"./**/src/main/json/openapi.json\"", @@ -93,6 +80,14 @@ "prepare": "husky install", "init-registries": "npm config set @iroha2:registry=https://nexus.iroha.tech/repository/npm-group/" }, + "resolutions": { + "ansi-html": ">0.0.8", + "glob-parent": "5.1.2", + "lodash": ">=4.17.21", + "minimist": ">=1.2.6", + "node-forge": ">=1.3.0", + "underscore": "1.13.2" + }, "devDependencies": { "@commitlint/cli": "13.2.1", "@commitlint/config-conventional": "13.2.0", @@ -161,20 +156,16 @@ "ts-loader": "9.4.4", "ts-node": "10.9.1", "typescript": "4.9.5", + "web3": "4.1.1", + "web3-core": "4.1.1", + "web3-eth": "4.1.1", + "web3-utils": "4.0.5", "webpack": "5.88.2", "webpack-bundle-analyzer": "4.9.0", "webpack-cli": "4.10.0", "wget-improved": "3.4.0", "yargs": "17.7.2" }, - "resolutions": { - "ansi-html": ">0.0.8", - "glob-parent": "5.1.2", - "lodash": ">=4.17.21", - "minimist": ">=1.2.6", - "node-forge": ">=1.3.0", - "underscore": "1.13.2" - }, "dependenciesMeta": { "@apollo/protobufjs": { "built": false @@ -263,5 +254,6 @@ "web3-shh": { "built": false } - } + }, + "packageManager": "yarn@3.6.0" } diff --git a/packages/cactus-plugin-ledger-connector-ethereum/Dockerfile b/packages/cactus-plugin-ledger-connector-ethereum/Dockerfile new file mode 100644 index 0000000000..1ea2ed65ce --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/Dockerfile @@ -0,0 +1,5 @@ +FROM ghcr.io/hyperledger/cactus-cmd-api-server:v1.0.0 + +ARG NPM_PKG_VERSION=latest + +RUN npm i @hyperledger/cactus-plugin-ledger-connector-ethereum@${NPM_PKG_VERSION} --production diff --git a/packages/cactus-plugin-ledger-connector-ethereum/README.md b/packages/cactus-plugin-ledger-connector-ethereum/README.md new file mode 100644 index 0000000000..597251cf10 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/README.md @@ -0,0 +1,326 @@ +# `@hyperledger/cactus-plugin-ledger-connector-ethereum` + +This plugin provides `Cactus` a way to interact with Ethereum networks. Using this we can perform: +* Deploy Smart-contracts through bytecode. +* Build and sign transactions using different keystores. +* Invoke smart-contract functions that we have deployed on the network. + +## Summary + + - [Getting Started](#getting-started) + - [Usage](#usage) + - [Prometheus Exporter](#prometheus-exporter) + - [Runing the tests](#running-the-tests) + - [Contributing](#contributing) + - [License](#license) + - [Acknowledgments](#acknowledgments) + +## Getting Started + +Clone the git repository on your local machine. Follow these instructions that will get you a copy of the project up and running on +your local machine for development and testing purposes. + +### Prerequisites + +In the root of the project to install the dependencies execute the command: +```sh +npm run configure +``` + +## Usage + +To use this import public-api and create new **PluginLedgerConnectorEthereum**. +```typescript + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum({ + instanceId: uuidV4(), + rpcApiHttpHost, + pluginRegistry: new PluginRegistry(), + }); +``` +You can make calls through the connector to the plugin API: + +```typescript +async invokeContract(req: InvokeContractJsonObjectV1Request):Promise; +async transact(req: RunTransactionRequest): Promise; +async transactSigned(rawTransaction: string): Promise; +async transactGethKeychain(txIn: RunTransactionRequest): Promise; +async transactPrivateKey(req: RunTransactionRequest): Promise; +async transactCactusKeychainRef(req: RunTransactionRequest):Promise; +async deployContract(req: DeployContractSolidityBytecodeV1Request :Promise; +async deployContractJsonObject(req: DeployContractSolidityBytecodeJsonObjectV1Request): Promise +async invokeRawWeb3EthMethod(req: InvokeRawWeb3EthMethodV1Request): Promise; +async invokeRawWeb3EthContract(req: InvokeRawWeb3EthContractV1Request): Promise; +``` + +Call example to deploy a contract: +```typescript +const deployOut = await connector.deployContract({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GETHKEYCHAINPASSWORD, + }, + bytecode: ContractJson.bytecode, + gas: 1000000, +}); +``` +The field "type" can have the following values: +```typescript +enum Web3SigningCredentialType { + CACTUSKEYCHAINREF = 'CACTUS_KEYCHAIN_REF', + GETHKEYCHAINPASSWORD = 'GETH_KEYCHAIN_PASSWORD', + PRIVATEKEYHEX = 'PRIVATE_KEY_HEX', + NONE = 'NONE' +} +``` +> Extensive documentation and examples in the [readthedocs](https://readthedocs.org/projects/hyperledger-cactus/) (WIP) + +## EthereumApiClient + +All connector API endpoints are defined in [open-api specification](./src/main/json/openapi.json). You can use [EthereumApiClient](./src/main/typescript/api-client) to call remote ethereum connector functions. It also contain additional utility functions to ease integration. + +### REST Functions +See [DefaultApi](./src/main/typescript/generated/openapi/typescript-axios/api.ts) for up-to-date listing of supported endpoints. +- deployContractSolBytecodeJsonObjectV1 +- deployContractSolBytecodeV1 +- getPrometheusMetricsV1 +- invokeContractV1 +- invokeContractV1NoKeychain +- invokeRawWeb3EthContractV1 +- invokeRawWeb3EthMethodV1 +- runTransactionV1 + +### Asynchronous Functions (socket.io) +- watchBlocksV1 + +### Send Request Methods +Both methods are deprecated, async version returns immediately while sync respond with Promise of a call results. +- `sendAsyncRequest` +- `sendSyncRequest` + +#### Supported Requests +- `web3Eth`: Calls `invokeRawWeb3EthMethodV1` +- `web3EthContract`: Calls `invokeRawWeb3EthContractV1` + +#### Arguments +- The same for both async and sync methods. +- Arguments interpretation depends on `method.type` (i.e. request type) +``` typescript +// Contract definition for web3EthContract request, ignored otherwise +contract: { + abi?: AbiItem[], + address?: string +}, + +// Request definition +method: { + type: "web3Eth" | "web3EthContract", + command: string // web3 method + function?: string; // contract function + params?: any[]; // contract parameters +} + +// web3 method arguments +args: { + { + args?: any[] | Record; + } +}, +``` + +## Running the tests + +To check that all has been installed correctly and that the pugin has no errors, run both `tap` and `jest` test suites: + +* Run this command at the project's root: + +```sh +# Tap +npx tap --ts --jobs=1 --timeout=60 ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts ./packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts + +# Jest +npx jest cactus-plugin-ledger-connector-ethereum +``` + +### Building/running the container image locally + +In the Cactus project root say: + +```sh +DOCKER_BUILDKIT=1 docker build -f ./packages/cactus-plugin-ledger-connector-ethereum/Dockerfile . -t cplcb +``` + +Build with a specific version of the npm package: +```sh +DOCKER_BUILDKIT=1 docker build --build-arg NPM_PKG_VERSION=0.4.1 -f ./packages/cactus-plugin-ledger-connector-ethereum/Dockerfile . -t cplcb +``` + +#### Running the container + +Launch container with plugin configuration as an **environment variable**: +```sh +docker run \ + --rm \ + --publish 3000:3000 \ + --publish 4000:4000 \ + --env AUTHORIZATION_PROTOCOL='NONE' \ + --env AUTHORIZATION_CONFIG_JSON='{}' \ + --env PLUGINS='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-ethereum", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-ethereum-connector-instance-id"}}]' \ + cplcb +``` + +Launch container with plugin configuration as a **CLI argument**: +```sh +docker run \ + --rm \ + --publish 3000:3000 \ + --publish 4000:4000 \ + cplcb \ + ./node_modules/@hyperledger/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js \ + --authorization-protocol='NONE' \ + --authorization-config-json='{}' \ + --plugins='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-ethereum", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-ethereum-connector-instance-id"}}]' +``` + +Launch container with **configuration file** mounted from host machine: +```sh + +echo '{"authorizationProtocol":"NONE","authorizationConfigJson":{},"plugins":[{"packageName":"@hyperledger/cactus-plugin-ledger-connector-ethereum","type":"org.hyperledger.cactus.plugin_import_type.LOCAL","action":"org.hyperledger.cactus.plugin_import_action.INSTALL","options":{"rpcApiHttpHost":"http://localhost:8545","instanceId":"some-unique-ethereum-connector-instance-id"}}]}' > cactus.json + +docker run \ + --rm \ + --publish 3000:3000 \ + --publish 4000:4000 \ + --mount type=bind,source="$(pwd)"/cactus.json,target=/cactus.json \ + cplcb \ + ./node_modules/@hyperledger/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js \ + --config-file=/cactus.json +``` + +#### Testing API calls with the container + +Don't have a ethereum network on hand to test with? Test or develop against our ethereum All-In-One container! + +**Terminal Window 1 (Ledger)** +```sh +docker run -p 0.0.0.0:8545:8545/tcp -p 0.0.0.0:8546:8546/tcp -p 0.0.0.0:8888:8888/tcp -p 0.0.0.0:9001:9001/tcp -p 0.0.0.0:9545:9545/tcp hyperledger/cactus-quorum-all-in-one:latest +``` + +**Terminal Window 2 (Cactus API Server)** +```sh +docker run \ + --network host \ + --rm \ + --publish 3000:3000 \ + --publish 4000:4000 \ + --env PLUGINS='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-ethereum", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-ethereum-connector-instance-id"}}]' \ + cplcb +``` + +**Terminal Window 3 (curl - replace eth accounts as needed)** +```sh +curl --location --request POST 'http://127.0.0.1:4000/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/run-transaction' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "web3SigningCredential": { + "ethAccount": "627306090abaB3A6e1400e9345bC60c78a8BEf57", + "secret": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + "type": "PRIVATE_KEY_HEX" + }, + "consistencyStrategy": { + "blockConfirmations": 0, + "receiptType": "NODE_TX_POOL_ACK" + }, + "transactionConfig": { + "from": "627306090abaB3A6e1400e9345bC60c78a8BEf57", + "to": "f17f52151EbEF6C7334FAD080c5704D77216b732", + "value": 1, + "gas": 10000000 + } +}' +``` + +The above should produce a response that looks similar to this: + +```json +{ + "success": true, + "data": { + "transactionReceipt": { + "blockHash": "0x7c97c038a5d3bd84613fe23ed442695276d5d2df97f4e7c4f10ca06765033ffd", + "blockNumber": 1218, + "contractAddress": null, + "cumulativeGasUsed": 21000, + "from": "0x627306090abab3a6e1400e9345bc60c78a8bef57", + "gasUsed": 21000, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": true, + "to": "0xf17f52151ebef6c7334fad080c5704d77216b732", + "transactionHash": "0xc7fcb46c735bdc696d500bfc70c72595a2b8c31813929e5c61d9a5aec3376d6f", + "transactionIndex": 0 + } + } +} +``` + +## Prometheus Exporter + +This class creates a prometheus exporter, which scrapes the transactions (total transaction count) for the use cases incorporating the use of Ethereum connector plugin. + +### Prometheus Exporter Usage +The prometheus exporter object is initialized in the `PluginLedgerConnectorEthereum` class constructor itself, so instantiating the object of the `PluginLedgerConnectorEthereum` class, gives access to the exporter object. +You can also initialize the prometheus exporter object seperately and then pass it to the `IPluginLedgerConnectorEthereumOptions` interface for `PluginLedgerConnectoEthereum` constructor. + +`getPrometheusMetricsV1` function returns the prometheus exporter metrics, currently displaying the total transaction count, which currently increments everytime the `transact()` method of the `PluginLedgerConnectorEthereum` class is called. + +### Prometheus Integration +To use Prometheus with this exporter make sure to install [Prometheus main component](https://prometheus.io/download/). +Once Prometheus is setup, the corresponding scrape_config needs to be added to the prometheus.yml + +```(yaml) +- job_name: 'ethereum_ledger_connector_exporter' + metrics_path: api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics + scrape_interval: 5s + static_configs: + - targets: ['{host}:{port}'] +``` + +Here the `host:port` is where the prometheus exporter metrics are exposed. The test cases (For example, packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/deploy-contract-from-json.test.ts) exposes it over `0.0.0.0` and a random port(). The random port can be found in the running logs of the test case and looks like (42379 in the below mentioned URL) +`Metrics URL: http://0.0.0.0:42379/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics` + +Once edited, you can start the prometheus service by referencing the above edited prometheus.yml file. +On the prometheus graphical interface (defaulted to http://localhost:9090), choose **Graph** from the menu bar, then select the **Console** tab. From the **Insert metric at cursor** drop down, select **cactus_ethereum_total_tx_count** and click **execute** + +### Helper code + +###### response.type.ts +This file contains the various responses of the metrics. + +###### data-fetcher.ts +This file contains functions encasing the logic to process the data points + +###### metrics.ts +This file lists all the prometheus metrics and what they are used for. + +## Running the tests + +To check that all has been installed correctly and that the pugin has no errors, there are two options to run the tests: + +* Run this command at the project's root: +```sh +npm run test:plugin-ledger-connector-ethereum +``` + +## Contributing + +We welcome contributions to Hyperledger Cactus in many forms, and there’s always plenty to do! + +Please review [CONTIRBUTING.md](../../CONTRIBUTING.md) to get started. + +## License + +This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file. + +## Acknowledgments +``` \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ethereum/openapitools.json b/packages/cactus-plugin-ledger-connector-ethereum/openapitools.json new file mode 100644 index 0000000000..03392961f6 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.3.0" + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/package.json b/packages/cactus-plugin-ledger-connector-ethereum/package.json new file mode 100644 index 0000000000..bc0bf7c909 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/package.json @@ -0,0 +1,107 @@ +{ + "name": "@hyperledger/cactus-plugin-ledger-connector-ethereum", + "version": "2.0.0-alpha.1", + "description": "Allows Cactus nodes to connect to a Ethereum ledger.", + "keywords": [ + "Hyperledger", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "homepage": "https://github.com/hyperledger/cacti#readme", + "bugs": { + "url": "https://github.com/hyperledger/cacti/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cacti.git" + }, + "license": "Apache-2.0", + "author": { + "name": "Hyperledger Cactus Contributors", + "email": "cactus@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cactus" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Michal Bajer", + "email": "michal.bajer@fujitsu.com", + "url": "https://www.fujitsu.com/global/" + }, + { + "name": "Peter Somogyvari", + "email": "peter.somogyvari@accenture.com", + "url": "https://accenture.com" + } + ], + "main": "dist/lib/main/typescript/index.js", + "module": "dist/lib/main/typescript/index.js", + "browser": "dist/cactus-plugin-ledger-connector-ethereum.web.umd.js", + "types": "dist/lib/main/typescript/index.d.ts", + "bin": { + "cacti-ethereum-connector-status": "dist/lib/scripts/get-ethereum-connector-status.js" + }, + "files": [ + "dist/*" + ], + "scripts": { + "codegen": "run-p 'codegen:*'", + "codegen:openapi": "npm run generate-sdk", + "generate-sdk": "run-p 'generate-sdk:*'", + "generate-sdk:typescript-axios": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/ --reserved-words-mappings protected=protected", + "watch": "npm-watch", + "webpack": "npm-run-all webpack:dev", + "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", + "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", + "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js" + }, + "dependencies": { + "@hyperledger/cactus-common": "2.0.0-alpha.1", + "@hyperledger/cactus-core": "2.0.0-alpha.1", + "@hyperledger/cactus-core-api": "2.0.0-alpha.1", + "axios": "0.21.4", + "express": "4.17.3", + "minimist": "1.2.8", + "prom-client": "13.2.0", + "run-time-error": "1.4.0", + "rxjs": "7.8.1", + "sanitize-html": "2.7.0", + "socket.io-client": "4.5.4", + "typescript-optional": "2.0.1", + "web3": "1.10.0", + "web3-eth-contract": "1.10.0", + "web3-utils": "1.10.0" + }, + "devDependencies": { + "@hyperledger/cactus-plugin-keychain-memory": "2.0.0-alpha.1", + "@hyperledger/cactus-test-tooling": "2.0.0-alpha.1", + "@types/express": "4.17.13", + "@types/minimist": "1.2.2", + "@types/sanitize-html": "2.6.2", + "chalk": "4.1.2", + "socket.io": "4.5.4", + "web3-eth": "1.10.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "publishConfig": { + "access": "public" + }, + "browserMinified": "dist/cactus-plugin-ledger-connector-ethereum.web.umd.min.js", + "mainMinified": "dist/cactus-plugin-ledger-connector-ethereum.node.umd.min.js", + "watch": { + "codegen:openapi": { + "patterns": [ + "./src/main/json/openapi.json" + ] + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json new file mode 100644 index 0000000000..303f77636c --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json @@ -0,0 +1,1388 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cactus Plugin - Connector Ethereum", + "description": "Can perform basic tasks on a Ethereum ledger", + "version": "0.0.1", + "license": { + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "components": { + "schemas": { + "Web3SigningCredential": { + "type": "object", + "required": ["type"], + "discriminator": { + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/Web3SigningCredentialGethKeychainPassword" + }, + { + "$ref": "#/components/schemas/Web3SigningCredentialCactusKeychainRef" + }, + { + "$ref": "#/components/schemas/Web3SigningCredentialPrivateKeyHex" + }, + { + "$ref": "#/components/schemas/Web3SigningCredentialNone" + } + ], + "properties": { + "type": { + "$ref": "#/components/schemas/Web3SigningCredentialType" + } + } + }, + "Web3SigningCredentialGethKeychainPassword": { + "type": "object", + "required": ["type", "ethAccount", "secret"], + "properties": { + "type": { + "$ref": "#/components/schemas/Web3SigningCredentialType" + }, + "ethAccount": { + "type": "string", + "description": "The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication.", + "minLength": 66, + "maxLength": 66, + "nullable": false + }, + "secret": { + "type": "string", + "description": "A geth keychain unlock password.", + "minLength": 0, + "maxLength": 65535 + } + } + }, + "Web3SigningCredentialCactusKeychainRef": { + "type": "object", + "required": ["type", "ethAccount", "keychainEntryKey"], + "properties": { + "type": { + "$ref": "#/components/schemas/Web3SigningCredentialType" + }, + "ethAccount": { + "type": "string", + "description": "The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication.", + "minLength": 66, + "maxLength": 66, + "nullable": false + }, + "keychainEntryKey": { + "type": "string", + "description": "The key to use when looking up the the keychain entry holding the secret pointed to by the keychainEntryKey parameter.", + "minLength": 0, + "maxLength": 1024 + }, + "keychainId": { + "type": "string", + "description": "The keychain ID to use when looking up the the keychain plugin instance that will be used to retrieve the secret pointed to by the keychainEntryKey parameter.", + "minLength": 0, + "maxLength": 1024 + } + } + }, + "Web3SigningCredentialPrivateKeyHex": { + "type": "object", + "required": ["type", "ethAccount", "secret"], + "properties": { + "type": { + "$ref": "#/components/schemas/Web3SigningCredentialType" + }, + "ethAccount": { + "type": "string", + "description": "The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication.", + "minLength": 66, + "maxLength": 66, + "nullable": false + }, + "secret": { + "type": "string", + "description": "The HEX encoded private key of an eth account.", + "minLength": 0, + "maxLength": 65535 + } + } + }, + "Web3SigningCredentialNone": { + "type": "object", + "required": ["type"], + "description": "Using this denotes that there is no signing required because the transaction is pre-signed.", + "properties": { + "type": { + "$ref": "#/components/schemas/Web3SigningCredentialType" + } + } + }, + "Web3SigningCredentialType": { + "type": "string", + "enum": [ + "CACTUS_KEYCHAIN_REF", + "GETH_KEYCHAIN_PASSWORD", + "PRIVATE_KEY_HEX", + "NONE" + ] + }, + "EthContractInvocationType": { + "type": "string", + "enum": ["SEND", "CALL"] + }, + "EthContractInvocationWeb3Method": { + "type": "string", + "enum": ["send", "call", "encodeABI", "estimateGas"] + }, + "SolidityContractJsonArtifact": { + "type": "object", + "required": ["contractName"], + "properties": { + "contractName": { + "type": "string", + "nullable": false + }, + "metadata": { + "type": "string", + "nullable": false + }, + "bytecode": { + "type": "string", + "nullable": false + }, + "deployedBytecode": { + "type": "string", + "nullable": false + }, + "sourceMap": { + "type": "string", + "nullable": false + }, + "deployedSourceMap": { + "type": "string", + "nullable": false + }, + "sourcePath": { + "type": "string" + }, + "compiler": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "functionHashes": { + "type": "object", + "additionalProperties": true + }, + "gasEstimates": { + "properties": { + "creation": { + "type": "object", + "properties": { + "codeDepositCost": { + "type": "string" + }, + "executionCost": { + "type": "string" + }, + "totalCost": { + "type": "string" + } + } + }, + "external": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "EthereumTransactionConfig": { + "type": "object", + "required": [], + "additionalProperties": true, + "properties": { + "rawTransaction": { + "type": "string", + "nullable": false + }, + "from": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "to": { + "oneOf": [ + { + "type": "string" + } + ] + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gas": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gasPrice": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "nonce": { + "type": "number" + }, + "data": { + "oneOf": [ + { + "type": "string" + } + ] + } + } + }, + "Web3TransactionReceipt": { + "type": "object", + "required": [ + "blockHash", + "blockNumber", + "transactionHash", + "transactionIndex", + "status", + "from", + "to", + "gasUsed" + ], + "additionalProperties": true, + "properties": { + "status": { + "type": "boolean", + "nullable": false + }, + "transactionHash": { + "type": "string", + "minLength": 66, + "maxLength": 66, + "pattern": "^0x([A-Fa-f0-9]{64})$" + }, + "transactionIndex": { + "type": "number", + "nullable": false + }, + "blockHash": { + "type": "string", + "minLength": 66, + "maxLength": 66, + "pattern": "^0x([A-Fa-f0-9]{64})$" + }, + "blockNumber": { + "type": "number", + "nullable": false + }, + "gasUsed": { + "type": "number", + "nullable": false + }, + "contractAddress": { + "type": "string", + "nullable": true + }, + "from": { + "type": "string", + "nullable": false + }, + "to": { + "type": "string", + "nullable": false + }, + "logs": { + "type": "array", + "default": [], + "items": {}, + "nullable": false + }, + "logsBloom": { + "type": "string", + "nullable": false + }, + "revertReason": { + "type": "string", + "nullable": false + }, + "output": { + "type": "string", + "nullable": false + }, + "commitmentHash": { + "type": "string", + "nullable": false + }, + "cumulativeGasUSed": { + "type": "number", + "nullable": false + } + } + }, + "ContractJSON": { + "type": "object", + "required": ["contractName", "bytecode"], + "additionalProperties": true, + "properties": { + "contractName": { + "type": "string", + "nullable": false + }, + "bytecode": { + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 24576, + "description": "See https://ethereum.stackexchange.com/a/47556 regarding the maximum length of the bytecode" + }, + "abi": { + "description": "The application binary interface of the solidity contract, optional parameter", + "type": "array", + "items": {}, + "nullable": false + }, + "metadata": { + "type": "string" + }, + "deployedBytecode": { + "type": "string" + }, + "sourceMap": { + "type": "string" + }, + "deployedSourceMap": { + "type": "string" + }, + "sourcePath": { + "type": "string" + }, + "compiler": { + "type": "object" + }, + "networks": { + "type": "object" + }, + "ast": { + "type": "object" + }, + "functionHashes": { + "type": "object" + }, + "gasEstimates": { + "type": "object" + } + } + }, + "RunTransactionRequest": { + "type": "object", + "required": ["web3SigningCredential", "transactionConfig"], + "additionalProperties": false, + "properties": { + "web3SigningCredential": { + "$ref": "#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "transactionConfig": { + "$ref": "#/components/schemas/EthereumTransactionConfig", + "nullable": false + }, + "timeoutMs": { + "type": "number", + "description": "The amount of milliseconds to wait for a transaction receipt with thehash of the transaction(which indicates successful execution) beforegiving up and crashing.", + "minimum": 0, + "default": 60000, + "nullable": false + } + } + }, + "RunTransactionResponse": { + "type": "object", + "required": ["transactionReceipt"], + "properties": { + "transactionReceipt": { + "$ref": "#/components/schemas/Web3TransactionReceipt" + } + } + }, + "DeployContractSolidityBytecodeV1Request": { + "type": "object", + "required": ["contractName", "web3SigningCredential", "keychainId"], + "additionalProperties": false, + "properties": { + "contractName": { + "type": "string", + "description": "The contract name for retrieve the contracts json on the keychain.", + "minLength": 1, + "maxLength": 100, + "nullable": false + }, + "contractAbi": { + "description": "The application binary interface of the solidity contract", + "type": "array", + "items": {}, + "nullable": false + }, + "web3SigningCredential": { + "$ref": "#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "bytecode": { + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 24576, + "description": "See https://ethereum.stackexchange.com/a/47556 regarding the maximum length of the bytecode" + }, + "keychainId": { + "type": "string", + "description": "The keychainId for retrieve the contracts json.", + "minLength": 1, + "maxLength": 100, + "nullable": false + }, + "gas": { + "type": "number", + "nullable": false + }, + "gasPrice": { + "type": "number", + "nullable": false + }, + "nonce": { + "type": "number", + "nullable": false + }, + "value": { + "type": "number", + "nullable": false + }, + "timeoutMs": { + "type": "number", + "description": "The amount of milliseconds to wait for a transaction receipt with theaddress of the contract(which indicates successful deployment) beforegiving up and crashing.", + "minimum": 0, + "default": 60000, + "nullable": false + }, + "contractJSON": { + "type": "object", + "description": "For use when not using keychain, pass the contract in as this variable", + "nullable": false + }, + "constructorArgs": { + "description": "The list of arguments to pass in to the constructor of the contract being deployed.", + "type": "array", + "default": [], + "items": {} + } + } + }, + "DeployContractSolidityBytecodeV1Response": { + "type": "object", + "required": ["transactionReceipt"], + "properties": { + "transactionReceipt": { + "$ref": "#/components/schemas/Web3TransactionReceipt" + } + } + }, + "DeployContractSolidityBytecodeJsonObjectV1Request": { + "type": "object", + "required": ["web3SigningCredential", "contractJSON"], + "additionalProperties": false, + "properties": { + "web3SigningCredential": { + "$ref": "#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "gas": { + "type": "number", + "nullable": false + }, + "gasPrice": { + "type": "string", + "nullable": false + }, + "timeoutMs": { + "type": "number", + "description": "The amount of milliseconds to wait for a transaction receipt with theaddress of the contract(which indicates successful deployment) beforegiving up and crashing.", + "minimum": 0, + "default": 60000, + "nullable": false + }, + "contractJSON": { + "$ref": "#/components/schemas/ContractJSON", + "description": "For use when not using keychain, pass the contract in as this variable", + "nullable": false + }, + "constructorArgs": { + "description": "The list of arguments to pass in to the constructor of the contract being deployed.", + "type": "array", + "default": [], + "items": {} + } + } + }, + "InvokeContractV1Request": { + "type": "object", + "required": [ + "web3SigningCredential", + "invocationType", + "methodName", + "params", + "contractName", + "keychainId" + ], + "additionalProperties": false, + "properties": { + "contractName": { + "description": "The contract name to find it in the keychain plugin", + "type": "string", + "nullable": false + }, + "web3SigningCredential": { + "$ref": "#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "invocationType": { + "$ref": "#/components/schemas/EthContractInvocationType", + "nullable": false, + "description": "Indicates wether it is a CALL or a SEND type of invocation where only SEND ends up creating an actual transaction on the ledger." + }, + "methodName": { + "description": "The name of the contract method to invoke.", + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 2048 + }, + "params": { + "description": "The list of arguments to pass in to the contract method being invoked.", + "type": "array", + "default": [], + "items": {} + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gas": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gasPrice": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "nonce": { + "type": "number" + }, + "timeoutMs": { + "type": "number", + "description": "The amount of milliseconds to wait for a transaction receipt beforegiving up and crashing. Only has any effect if the invocation type is SEND", + "minimum": 0, + "default": 60000, + "nullable": false + }, + "keychainId": { + "type": "string", + "description": "The keychainId for retrieve the contracts json.", + "minLength": 1, + "maxLength": 100 + } + } + }, + "InvokeContractJsonObjectV1Request": { + "type": "object", + "required": [ + "web3SigningCredential", + "invocationType", + "methodName", + "params", + "contractJSON", + "contractAddress" + ], + "additionalProperties": false, + "properties": { + "web3SigningCredential": { + "$ref": "#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "invocationType": { + "$ref": "#/components/schemas/EthContractInvocationType", + "nullable": false, + "description": "Indicates wether it is a CALL or a SEND type of invocation where only SEND ends up creating an actual transaction on the ledger." + }, + "methodName": { + "description": "The name of the contract method to invoke.", + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 2048 + }, + "params": { + "description": "The list of arguments to pass in to the contract method being invoked.", + "type": "array", + "default": [], + "items": {} + }, + "contractAddress": { + "description": "Address of the solidity contract", + "type": "string", + "nullable": false + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gas": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gasPrice": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "nonce": { + "type": "number" + }, + "timeoutMs": { + "type": "number", + "description": "The amount of milliseconds to wait for a transaction receipt beforegiving up and crashing. Only has any effect if the invocation type is SEND", + "minimum": 0, + "default": 60000, + "nullable": false + }, + "contractJSON": { + "$ref": "#/components/schemas/ContractJSON", + "description": "For use when not using keychain, pass the contract in as this variable", + "nullable": false + } + } + }, + "InvokeContractV1Response": { + "type": "object", + "required": ["success"], + "properties": { + "transactionReceipt": { + "$ref": "#/components/schemas/Web3TransactionReceipt" + }, + "callOutput": {}, + "success": { + "type": "boolean", + "nullable": false + } + } + }, + "InvokeRawWeb3EthMethodV1Request": { + "type": "object", + "required": ["methodName"], + "additionalProperties": false, + "properties": { + "methodName": { + "description": "The name of the web3.eth method to invoke", + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 2048 + }, + "params": { + "description": "The list of arguments to pass to web3.eth method specified in methodName", + "type": "array", + "default": [], + "items": {} + } + } + }, + "InvokeRawWeb3EthMethodV1Response": { + "type": "object", + "required": ["status"], + "additionalProperties": false, + "properties": { + "status": { + "type": "number", + "nullable": false, + "description": "Status code of the operation" + }, + "data": { + "description": "Output of requested web3.eth method" + }, + "errorDetail": { + "type": "string", + "nullable": false, + "description": "Error details" + } + } + }, + "InvokeRawWeb3EthContractV1Request": { + "type": "object", + "required": ["abi", "address", "invocationType", "contractMethod"], + "additionalProperties": false, + "properties": { + "abi": { + "description": "The application binary interface of the solidity contract", + "type": "array", + "items": {} + }, + "address": { + "description": "Deployed solidity contract address", + "type": "string" + }, + "invocationType": { + "description": "Contract invocation method to be performed (send, call, etc...)", + "$ref": "#/components/schemas/EthContractInvocationWeb3Method" + }, + "invocationParams": { + "description": "The list of arguments for contract invocation method (send, call, etc...)", + "type": "object", + "default": {} + }, + "contractMethod": { + "description": "Method of deployed solidity contract to execute", + "type": "string" + }, + "contractMethodArgs": { + "description": "The list of arguments for deployed solidity contract method", + "type": "array", + "default": [], + "items": {} + } + } + }, + "InvokeRawWeb3EthContractV1Response": { + "type": "object", + "required": ["status"], + "additionalProperties": false, + "properties": { + "status": { + "description": "Status code of the operation", + "type": "number" + }, + "data": { + "description": "Output of contract invocation method" + }, + "errorDetail": { + "description": "Error details", + "type": "string" + } + } + }, + "PrometheusExporterMetricsResponse": { + "type": "string", + "nullable": false + }, + "WatchBlocksV1": { + "type": "string", + "enum": [ + "org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Subscribe", + "org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Next", + "org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Unsubscribe", + "org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Error", + "org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Complete" + ], + "x-enum-varnames": [ + "Subscribe", + "Next", + "Unsubscribe", + "Error", + "Complete" + ] + }, + "WatchBlocksV1Options": { + "type": "object", + "properties": { + "getBlockData": { + "type": "boolean" + } + } + }, + "Web3BlockHeader": { + "type": "object", + "required": [ + "number", + "hash", + "parentHash", + "nonce", + "sha3Uncles", + "logsBloom", + "transactionRoot", + "stateRoot", + "receiptRoot", + "miner", + "extraData", + "gasLimit", + "gasUsed", + "timestamp" + ], + "properties": { + "number": { + "type": "number" + }, + "hash": { + "type": "string" + }, + "parentHash": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "sha3Uncles": { + "type": "string" + }, + "logsBloom": { + "type": "string" + }, + "transactionsRoot": { + "type": "string" + }, + "stateRoot": { + "type": "string" + }, + "receiptsRoot": { + "type": "string" + }, + "difficulty": { + "type": "string" + }, + "mixHash": { + "type": "string" + }, + "miner": { + "type": "string" + }, + "extraData": { + "type": "string" + }, + "gasLimit": { + "type": "integer" + }, + "gasUsed": { + "type": "integer" + }, + "timestamp": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + } + } + }, + "Web3Transaction": { + "type": "object", + "required": [ + "hash", + "nonce", + "blockHash", + "blockNumber", + "transactionIndex", + "from", + "to", + "value", + "gasPrice", + "gas", + "input" + ], + "properties": { + "hash": { + "type": "string" + }, + "nonce": { + "type": "number" + }, + "blockHash": { + "type": "string", + "nullable": true + }, + "blockNumber": { + "type": "number", + "nullable": true + }, + "transactionIndex": { + "type": "number", + "nullable": true + }, + "from": { + "type": "string" + }, + "to": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string" + }, + "gasPrice": { + "type": "string" + }, + "gas": { + "type": "number" + }, + "input": { + "type": "string" + }, + "v": { + "type": "string" + }, + "r": { + "type": "string" + }, + "s": { + "type": "string" + } + } + }, + "WatchBlocksV1BlockData": { + "type": "object", + "required": [ + "number", + "hash", + "parentHash", + "nonce", + "sha3Uncles", + "logsBloom", + "transactionRoot", + "stateRoot", + "receiptRoot", + "miner", + "extraData", + "gasLimit", + "gasUsed", + "timestamp", + "size", + "totalDifficulty", + "uncles", + "transactions" + ], + "properties": { + "number": { + "type": "number" + }, + "hash": { + "type": "string" + }, + "parentHash": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "sha3Uncles": { + "type": "string" + }, + "logsBloom": { + "type": "string" + }, + "transactionsRoot": { + "type": "string" + }, + "stateRoot": { + "type": "string" + }, + "receiptsRoot": { + "type": "string" + }, + "difficulty": { + "type": "string" + }, + "mixHash": { + "type": "string" + }, + "miner": { + "type": "string" + }, + "extraData": { + "type": "string" + }, + "gasLimit": { + "type": "integer" + }, + "gasUsed": { + "type": "integer" + }, + "timestamp": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "size": { + "type": "number" + }, + "totalDifficulty": { + "type": "string" + }, + "uncles": { + "type": "array", + "items": { + "type": "string" + } + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Web3Transaction" + } + } + } + }, + "WatchBlocksV1Progress": { + "type": "object", + "properties": { + "blockHeader": { + "$ref": "#/components/schemas/Web3BlockHeader" + }, + "blockData": { + "$ref": "#/components/schemas/WatchBlocksV1BlockData" + } + } + } + } + }, + "paths": { + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode" + } + }, + "operationId": "deployContractSolBytecodeV1", + "summary": "Deploys the bytecode of a Solidity contract.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractSolidityBytecodeV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractSolidityBytecodeV1Response" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode-json-object": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode-json-object" + } + }, + "operationId": "deployContractSolBytecodeJsonObjectV1", + "summary": "Deploys the bytecode of a Solidity contract.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractSolidityBytecodeJsonObjectV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractSolidityBytecodeV1Response" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/run-transaction": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/run-transaction" + } + }, + "operationId": "runTransactionV1", + "summary": "Executes a transaction on a ethereum ledger", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunTransactionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunTransactionResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract" + } + }, + "operationId": "invokeContractV1", + "summary": "Invokes a contract on a besu ledger", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeContractV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeContractV1Response" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract-json-object": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract-json-object" + } + }, + "operationId": "invokeContractV1NoKeychain", + "summary": "Invokes a contract on a besu ledger", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeContractJsonObjectV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeContractV1Response" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics": { + "get": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "get", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics" + } + }, + "operationId": "getPrometheusMetricsV1", + "summary": "Get the Prometheus Metrics", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PrometheusExporterMetricsResponse" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-method": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-method" + } + }, + "operationId": "invokeWeb3EthMethodV1", + "summary": "Invoke any method from web3.eth (low-level)", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeRawWeb3EthMethodV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/InvokeRawWeb3EthMethodV1Response" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-contract": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-contract" + } + }, + "operationId": "invokeRawWeb3EthContractV1", + "summary": "Low-level endpoint to invoke a method on deployed contract.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeRawWeb3EthContractV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/InvokeRawWeb3EthContractV1Response" + } + } + } + } + } + } + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/api-client/ethereum-api-client.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/api-client/ethereum-api-client.ts new file mode 100644 index 0000000000..18636a4639 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/api-client/ethereum-api-client.ts @@ -0,0 +1,255 @@ +import { Observable, ReplaySubject } from "rxjs"; +import { finalize } from "rxjs/operators"; +import { io } from "socket.io-client"; +import { Logger, Checks } from "@hyperledger/cactus-common"; +import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common"; +import { Constants, ISocketApiClient } from "@hyperledger/cactus-core-api"; +import { + DefaultApi, + EthContractInvocationWeb3Method, + InvokeRawWeb3EthContractV1Request, + WatchBlocksV1, + WatchBlocksV1Options, + WatchBlocksV1Progress, +} from "../generated/openapi/typescript-axios"; +import { Configuration } from "../generated/openapi/typescript-axios/configuration"; +import { AbiItem } from "web3-utils"; + +export class EthereumApiClientOptions extends Configuration { + readonly logLevel?: LogLevelDesc; + readonly wsApiHost?: string; + readonly wsApiPath?: string; +} + +// Command 'web3Eth' input method type +export type EthereumRequestInputWeb3EthMethod = { + type: "web3Eth"; + command: string; +}; + +// Command 'web3EthContract' input method type +export type EthereumRequestInputWeb3EthContractMethod = { + type: "web3EthContract"; + command: EthContractInvocationWeb3Method; + function: string; + params?: any[]; +}; + +// Common input types for sending requests +export type EthereumRequestInputContract = { + abi?: AbiItem[]; + address?: string; +}; +export type EthereumRequestInputMethod = + | EthereumRequestInputWeb3EthMethod + | EthereumRequestInputWeb3EthContractMethod; +export type EthereumRequestInputArgs = { + args?: any[] | Record; +}; + +export class EthereumApiClient + extends DefaultApi + implements ISocketApiClient { + public static readonly CLASS_NAME = "EthereumApiClient"; + + private readonly log: Logger; + private readonly wsApiHost: string; + private readonly wsApiPath: string; + + public get className(): string { + return EthereumApiClient.CLASS_NAME; + } + + constructor(public readonly options: EthereumApiClientOptions) { + super(options); + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + + this.wsApiHost = options.wsApiHost || options.basePath || location.host; + this.wsApiPath = options.wsApiPath || Constants.SocketIoConnectionPathV1; + this.log.debug(`Created ${this.className} OK.`); + this.log.debug(`wsApiHost=${this.wsApiHost}`); + this.log.debug(`wsApiPath=${this.wsApiPath}`); + this.log.debug(`basePath=${this.options.basePath}`); + } + + public watchBlocksV1( + options?: WatchBlocksV1Options, + ): Observable { + const socket = io(this.wsApiHost, { path: this.wsApiPath }); + const subject = new ReplaySubject(0); + + socket.on(WatchBlocksV1.Next, (data: WatchBlocksV1Progress) => { + this.log.debug("Received WatchBlocksV1.Next"); + subject.next(data); + }); + + socket.on(WatchBlocksV1.Error, (ex: string) => { + this.log.warn("Received WatchBlocksV1.Error:", ex); + subject.error(ex); + }); + + socket.on(WatchBlocksV1.Complete, () => { + this.log.debug("Received WatchBlocksV1.Complete"); + subject.complete(); + }); + + socket.on("connect", () => { + this.log.info("Connected OK, sending WatchBlocksV1.Subscribe request..."); + socket.emit(WatchBlocksV1.Subscribe, options); + }); + + socket.connect(); + + return subject.pipe( + finalize(() => { + this.log.info("FINALIZE - unsubscribing from the stream..."); + socket.emit(WatchBlocksV1.Unsubscribe); + socket.disconnect(); + }), + ); + } + + /** + * Immediately sends request to the validator, doesn't report any error or responses. + * @param contract - contract to execute on the ledger. + * @param method - function / method to be executed by validator. + * @param args - arguments. + * @note Internally, it's just a wrapper around sendSyncRequest, but handles the promise resolution seamlessly. + * @deprecated Use EthereumApiClient REST calls directly. + */ + public sendAsyncRequest( + contract: EthereumRequestInputContract, + method: EthereumRequestInputMethod, + args: EthereumRequestInputArgs, + ): void { + const callName = `${method.type} - ${method.command}`; + this.log.debug("sendAsyncRequest()", callName); + + this.sendSyncRequest(contract, method, args) + .then((value) => { + this.log.info(`sendAsyncRequest call resolved (${callName})`); + this.log.debug("sendAsyncRequest results:", JSON.stringify(value)); + }) + .catch((err) => { + this.log.warn(`sendAsyncRequest failed (${callName}). Error:`, err); + }); + } + + private sendWeb3EthRequest( + method: EthereumRequestInputWeb3EthMethod, + args?: any[], + ): Promise { + return new Promise((resolve, reject) => { + // Check parameters + Checks.nonBlankString(method.command, "Method command must not be empty"); + if (args && !Array.isArray(args)) { + throw new Error("web3Eth arguments (args) must be an array"); + } + + // Prepare input + const invokeArgs = { + methodName: method.command, + params: args, + }; + + // Call the endpoint + this.invokeWeb3EthMethodV1(invokeArgs) + .then((value) => { + this.log.debug("sendWeb3EthRequest() OK"); + resolve(value.data); + }) + .catch((err) => { + this.log.debug("sendWeb3EthRequest() Error:", err); + reject(err); + }); + }); + } + + private sendWeb3EthContractRequest( + contract: EthereumRequestInputContract, + method: EthereumRequestInputWeb3EthContractMethod, + args?: Record, + ): Promise { + return new Promise((resolve, reject) => { + // Check parameters + Checks.truthy(contract.abi, "Contract ABI must be defined"); + Checks.truthy(contract.address, "Contract address must be set"); + if ( + !Object.values(EthContractInvocationWeb3Method).includes(method.command) + ) { + throw new Error( + `Unknown invocationType (${method.command}), must be specified in EthContractInvocationWeb3Method`, + ); + } + Checks.nonBlankString( + method.function, + "contractMethod (method.function) must not be empty", + ); + if (method.params && !Array.isArray(method.params)) { + throw new Error( + "Contract method arguments (method.params) must be an array", + ); + } + + // Prepare input + const invokeArgs: InvokeRawWeb3EthContractV1Request = { + abi: contract.abi as AbiItem[], + address: contract.address as string, + invocationType: method.command, + invocationParams: args, + contractMethod: method.function, + contractMethodArgs: method.params, + }; + + // Call the endpoint + this.invokeRawWeb3EthContractV1(invokeArgs) + .then((value) => { + resolve(value.data); + }) + .catch((err) => { + reject(err); + }); + }); + } + + /** + * Sends request to be executed on the ledger, watches and reports any error and the response from a ledger. + * @param contract - contract to execute on the ledger. + * @param method - function / method specification to be executed by validator. + * @param args - arguments. + * @returns Promise that will resolve with response from the ledger, or reject when error occurred. + * @deprecated Use EthereumApiClient REST calls directly. + */ + public sendSyncRequest( + contract: EthereumRequestInputContract, + method: EthereumRequestInputMethod, + args: EthereumRequestInputArgs, + ): Promise { + this.log.debug("sendSyncRequest()"); + + switch (method.type) { + case "web3Eth": { + this.log.info("Send 'web3Eth' request command"); + return this.sendWeb3EthRequest(method, args.args as any); + } + case "web3EthContract": { + this.log.info("Send 'web3EthContract' request command"); + return this.sendWeb3EthContractRequest( + contract, + method, + args.args as any, + ); + } + default: + const value: never = method; + return Promise.reject( + `Not support request method on Ethereum: ${JSON.stringify(value)}`, + ); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore new file mode 100644 index 0000000000..57cdd7b74b --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore @@ -0,0 +1,27 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +git_push.sh +.npmignore +.gitignore diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES new file mode 100644 index 0000000000..53250c0269 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES @@ -0,0 +1,5 @@ +api.ts +base.ts +common.ts +configuration.ts +index.ts diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION new file mode 100644 index 0000000000..e7e42a4b58 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.3.0 \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 0000000000..bb7a0d6c16 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,1966 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Connector Ethereum + * Can perform basic tasks on a Ethereum ledger + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; +import type { RequestArgs } from './base'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base'; + +/** + * + * @export + * @interface ContractJSON + */ +export interface ContractJSON { + [key: string]: any; + + /** + * + * @type {string} + * @memberof ContractJSON + */ + 'contractName': string; + /** + * See https://ethereum.stackexchange.com/a/47556 regarding the maximum length of the bytecode + * @type {string} + * @memberof ContractJSON + */ + 'bytecode': string; + /** + * The application binary interface of the solidity contract, optional parameter + * @type {Array} + * @memberof ContractJSON + */ + 'abi'?: Array; + /** + * + * @type {string} + * @memberof ContractJSON + */ + 'metadata'?: string; + /** + * + * @type {string} + * @memberof ContractJSON + */ + 'deployedBytecode'?: string; + /** + * + * @type {string} + * @memberof ContractJSON + */ + 'sourceMap'?: string; + /** + * + * @type {string} + * @memberof ContractJSON + */ + 'deployedSourceMap'?: string; + /** + * + * @type {string} + * @memberof ContractJSON + */ + 'sourcePath'?: string; + /** + * + * @type {object} + * @memberof ContractJSON + */ + 'compiler'?: object; + /** + * + * @type {object} + * @memberof ContractJSON + */ + 'networks'?: object; + /** + * + * @type {object} + * @memberof ContractJSON + */ + 'ast'?: object; + /** + * + * @type {object} + * @memberof ContractJSON + */ + 'functionHashes'?: object; + /** + * + * @type {object} + * @memberof ContractJSON + */ + 'gasEstimates'?: object; +} +/** + * + * @export + * @interface DeployContractSolidityBytecodeJsonObjectV1Request + */ +export interface DeployContractSolidityBytecodeJsonObjectV1Request { + /** + * + * @type {Web3SigningCredential} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + 'web3SigningCredential': Web3SigningCredential; + /** + * + * @type {number} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + 'gas'?: number; + /** + * + * @type {string} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + 'gasPrice'?: string; + /** + * The amount of milliseconds to wait for a transaction receipt with theaddress of the contract(which indicates successful deployment) beforegiving up and crashing. + * @type {number} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + 'timeoutMs'?: number; + /** + * + * @type {ContractJSON} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + 'contractJSON': ContractJSON; + /** + * The list of arguments to pass in to the constructor of the contract being deployed. + * @type {Array} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + 'constructorArgs'?: Array; +} +/** + * + * @export + * @interface DeployContractSolidityBytecodeV1Request + */ +export interface DeployContractSolidityBytecodeV1Request { + /** + * The contract name for retrieve the contracts json on the keychain. + * @type {string} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'contractName': string; + /** + * The application binary interface of the solidity contract + * @type {Array} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'contractAbi'?: Array; + /** + * + * @type {Web3SigningCredential} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'web3SigningCredential': Web3SigningCredential; + /** + * See https://ethereum.stackexchange.com/a/47556 regarding the maximum length of the bytecode + * @type {string} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'bytecode'?: string; + /** + * The keychainId for retrieve the contracts json. + * @type {string} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'keychainId': string; + /** + * + * @type {number} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'gas'?: number; + /** + * + * @type {number} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'gasPrice'?: number; + /** + * + * @type {number} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'nonce'?: number; + /** + * + * @type {number} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'value'?: number; + /** + * The amount of milliseconds to wait for a transaction receipt with theaddress of the contract(which indicates successful deployment) beforegiving up and crashing. + * @type {number} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'timeoutMs'?: number; + /** + * For use when not using keychain, pass the contract in as this variable + * @type {object} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'contractJSON'?: object; + /** + * The list of arguments to pass in to the constructor of the contract being deployed. + * @type {Array} + * @memberof DeployContractSolidityBytecodeV1Request + */ + 'constructorArgs'?: Array; +} +/** + * + * @export + * @interface DeployContractSolidityBytecodeV1Response + */ +export interface DeployContractSolidityBytecodeV1Response { + /** + * + * @type {Web3TransactionReceipt} + * @memberof DeployContractSolidityBytecodeV1Response + */ + 'transactionReceipt': Web3TransactionReceipt; +} +/** + * + * @export + * @enum {string} + */ + +export const EthContractInvocationType = { + Send: 'SEND', + Call: 'CALL' +} as const; + +export type EthContractInvocationType = typeof EthContractInvocationType[keyof typeof EthContractInvocationType]; + + +/** + * + * @export + * @enum {string} + */ + +export const EthContractInvocationWeb3Method = { + Send: 'send', + Call: 'call', + EncodeAbi: 'encodeABI', + EstimateGas: 'estimateGas' +} as const; + +export type EthContractInvocationWeb3Method = typeof EthContractInvocationWeb3Method[keyof typeof EthContractInvocationWeb3Method]; + + +/** + * + * @export + * @interface EthereumTransactionConfig + */ +export interface EthereumTransactionConfig { + [key: string]: any; + + /** + * + * @type {string} + * @memberof EthereumTransactionConfig + */ + 'rawTransaction'?: string; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof EthereumTransactionConfig + */ + 'from'?: EthereumTransactionConfigFrom; + /** + * + * @type {EthereumTransactionConfigTo} + * @memberof EthereumTransactionConfig + */ + 'to'?: EthereumTransactionConfigTo; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof EthereumTransactionConfig + */ + 'value'?: EthereumTransactionConfigFrom; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof EthereumTransactionConfig + */ + 'gas'?: EthereumTransactionConfigFrom; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof EthereumTransactionConfig + */ + 'gasPrice'?: EthereumTransactionConfigFrom; + /** + * + * @type {number} + * @memberof EthereumTransactionConfig + */ + 'nonce'?: number; + /** + * + * @type {EthereumTransactionConfigTo} + * @memberof EthereumTransactionConfig + */ + 'data'?: EthereumTransactionConfigTo; +} +/** + * @type EthereumTransactionConfigFrom + * @export + */ +export type EthereumTransactionConfigFrom = number | string; + +/** + * @type EthereumTransactionConfigTo + * @export + */ +export type EthereumTransactionConfigTo = string; + +/** + * + * @export + * @interface InvokeContractJsonObjectV1Request + */ +export interface InvokeContractJsonObjectV1Request { + /** + * + * @type {Web3SigningCredential} + * @memberof InvokeContractJsonObjectV1Request + */ + 'web3SigningCredential': Web3SigningCredential; + /** + * + * @type {EthContractInvocationType} + * @memberof InvokeContractJsonObjectV1Request + */ + 'invocationType': EthContractInvocationType; + /** + * The name of the contract method to invoke. + * @type {string} + * @memberof InvokeContractJsonObjectV1Request + */ + 'methodName': string; + /** + * The list of arguments to pass in to the contract method being invoked. + * @type {Array} + * @memberof InvokeContractJsonObjectV1Request + */ + 'params': Array; + /** + * Address of the solidity contract + * @type {string} + * @memberof InvokeContractJsonObjectV1Request + */ + 'contractAddress': string; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof InvokeContractJsonObjectV1Request + */ + 'value'?: EthereumTransactionConfigFrom; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof InvokeContractJsonObjectV1Request + */ + 'gas'?: EthereumTransactionConfigFrom; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof InvokeContractJsonObjectV1Request + */ + 'gasPrice'?: EthereumTransactionConfigFrom; + /** + * + * @type {number} + * @memberof InvokeContractJsonObjectV1Request + */ + 'nonce'?: number; + /** + * The amount of milliseconds to wait for a transaction receipt beforegiving up and crashing. Only has any effect if the invocation type is SEND + * @type {number} + * @memberof InvokeContractJsonObjectV1Request + */ + 'timeoutMs'?: number; + /** + * + * @type {ContractJSON} + * @memberof InvokeContractJsonObjectV1Request + */ + 'contractJSON': ContractJSON; +} + + +/** + * + * @export + * @interface InvokeContractV1Request + */ +export interface InvokeContractV1Request { + /** + * The contract name to find it in the keychain plugin + * @type {string} + * @memberof InvokeContractV1Request + */ + 'contractName': string; + /** + * + * @type {Web3SigningCredential} + * @memberof InvokeContractV1Request + */ + 'web3SigningCredential': Web3SigningCredential; + /** + * + * @type {EthContractInvocationType} + * @memberof InvokeContractV1Request + */ + 'invocationType': EthContractInvocationType; + /** + * The name of the contract method to invoke. + * @type {string} + * @memberof InvokeContractV1Request + */ + 'methodName': string; + /** + * The list of arguments to pass in to the contract method being invoked. + * @type {Array} + * @memberof InvokeContractV1Request + */ + 'params': Array; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof InvokeContractV1Request + */ + 'value'?: EthereumTransactionConfigFrom; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof InvokeContractV1Request + */ + 'gas'?: EthereumTransactionConfigFrom; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof InvokeContractV1Request + */ + 'gasPrice'?: EthereumTransactionConfigFrom; + /** + * + * @type {number} + * @memberof InvokeContractV1Request + */ + 'nonce'?: number; + /** + * The amount of milliseconds to wait for a transaction receipt beforegiving up and crashing. Only has any effect if the invocation type is SEND + * @type {number} + * @memberof InvokeContractV1Request + */ + 'timeoutMs'?: number; + /** + * The keychainId for retrieve the contracts json. + * @type {string} + * @memberof InvokeContractV1Request + */ + 'keychainId': string; +} + + +/** + * + * @export + * @interface InvokeContractV1Response + */ +export interface InvokeContractV1Response { + /** + * + * @type {Web3TransactionReceipt} + * @memberof InvokeContractV1Response + */ + 'transactionReceipt'?: Web3TransactionReceipt; + /** + * + * @type {any} + * @memberof InvokeContractV1Response + */ + 'callOutput'?: any; + /** + * + * @type {boolean} + * @memberof InvokeContractV1Response + */ + 'success': boolean; +} +/** + * + * @export + * @interface InvokeRawWeb3EthContractV1Request + */ +export interface InvokeRawWeb3EthContractV1Request { + /** + * The application binary interface of the solidity contract + * @type {Array} + * @memberof InvokeRawWeb3EthContractV1Request + */ + 'abi': Array; + /** + * Deployed solidity contract address + * @type {string} + * @memberof InvokeRawWeb3EthContractV1Request + */ + 'address': string; + /** + * + * @type {EthContractInvocationWeb3Method} + * @memberof InvokeRawWeb3EthContractV1Request + */ + 'invocationType': EthContractInvocationWeb3Method; + /** + * The list of arguments for contract invocation method (send, call, etc...) + * @type {object} + * @memberof InvokeRawWeb3EthContractV1Request + */ + 'invocationParams'?: object; + /** + * Method of deployed solidity contract to execute + * @type {string} + * @memberof InvokeRawWeb3EthContractV1Request + */ + 'contractMethod': string; + /** + * The list of arguments for deployed solidity contract method + * @type {Array} + * @memberof InvokeRawWeb3EthContractV1Request + */ + 'contractMethodArgs'?: Array; +} + + +/** + * + * @export + * @interface InvokeRawWeb3EthContractV1Response + */ +export interface InvokeRawWeb3EthContractV1Response { + /** + * Status code of the operation + * @type {number} + * @memberof InvokeRawWeb3EthContractV1Response + */ + 'status': number; + /** + * Output of contract invocation method + * @type {any} + * @memberof InvokeRawWeb3EthContractV1Response + */ + 'data'?: any; + /** + * Error details + * @type {string} + * @memberof InvokeRawWeb3EthContractV1Response + */ + 'errorDetail'?: string; +} +/** + * + * @export + * @interface InvokeRawWeb3EthMethodV1Request + */ +export interface InvokeRawWeb3EthMethodV1Request { + /** + * The name of the web3.eth method to invoke + * @type {string} + * @memberof InvokeRawWeb3EthMethodV1Request + */ + 'methodName': string; + /** + * The list of arguments to pass to web3.eth method specified in methodName + * @type {Array} + * @memberof InvokeRawWeb3EthMethodV1Request + */ + 'params'?: Array; +} +/** + * + * @export + * @interface InvokeRawWeb3EthMethodV1Response + */ +export interface InvokeRawWeb3EthMethodV1Response { + /** + * Status code of the operation + * @type {number} + * @memberof InvokeRawWeb3EthMethodV1Response + */ + 'status': number; + /** + * Output of requested web3.eth method + * @type {any} + * @memberof InvokeRawWeb3EthMethodV1Response + */ + 'data'?: any; + /** + * Error details + * @type {string} + * @memberof InvokeRawWeb3EthMethodV1Response + */ + 'errorDetail'?: string; +} +/** + * + * @export + * @interface RunTransactionRequest + */ +export interface RunTransactionRequest { + /** + * + * @type {Web3SigningCredential} + * @memberof RunTransactionRequest + */ + 'web3SigningCredential': Web3SigningCredential; + /** + * + * @type {EthereumTransactionConfig} + * @memberof RunTransactionRequest + */ + 'transactionConfig': EthereumTransactionConfig; + /** + * The amount of milliseconds to wait for a transaction receipt with thehash of the transaction(which indicates successful execution) beforegiving up and crashing. + * @type {number} + * @memberof RunTransactionRequest + */ + 'timeoutMs'?: number; +} +/** + * + * @export + * @interface RunTransactionResponse + */ +export interface RunTransactionResponse { + /** + * + * @type {Web3TransactionReceipt} + * @memberof RunTransactionResponse + */ + 'transactionReceipt': Web3TransactionReceipt; +} +/** + * + * @export + * @interface SolidityContractJsonArtifact + */ +export interface SolidityContractJsonArtifact { + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifact + */ + 'contractName': string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifact + */ + 'metadata'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifact + */ + 'bytecode'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifact + */ + 'deployedBytecode'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifact + */ + 'sourceMap'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifact + */ + 'deployedSourceMap'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifact + */ + 'sourcePath'?: string; + /** + * + * @type {SolidityContractJsonArtifactCompiler} + * @memberof SolidityContractJsonArtifact + */ + 'compiler'?: SolidityContractJsonArtifactCompiler; + /** + * + * @type {{ [key: string]: any; }} + * @memberof SolidityContractJsonArtifact + */ + 'functionHashes'?: { [key: string]: any; }; + /** + * + * @type {SolidityContractJsonArtifactGasEstimates} + * @memberof SolidityContractJsonArtifact + */ + 'gasEstimates'?: SolidityContractJsonArtifactGasEstimates; +} +/** + * + * @export + * @interface SolidityContractJsonArtifactCompiler + */ +export interface SolidityContractJsonArtifactCompiler { + [key: string]: any; + + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifactCompiler + */ + 'name'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifactCompiler + */ + 'version'?: string; +} +/** + * + * @export + * @interface SolidityContractJsonArtifactGasEstimates + */ +export interface SolidityContractJsonArtifactGasEstimates { + /** + * + * @type {SolidityContractJsonArtifactGasEstimatesCreation} + * @memberof SolidityContractJsonArtifactGasEstimates + */ + 'creation'?: SolidityContractJsonArtifactGasEstimatesCreation; + /** + * + * @type {{ [key: string]: any; }} + * @memberof SolidityContractJsonArtifactGasEstimates + */ + 'external'?: { [key: string]: any; }; +} +/** + * + * @export + * @interface SolidityContractJsonArtifactGasEstimatesCreation + */ +export interface SolidityContractJsonArtifactGasEstimatesCreation { + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifactGasEstimatesCreation + */ + 'codeDepositCost'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifactGasEstimatesCreation + */ + 'executionCost'?: string; + /** + * + * @type {string} + * @memberof SolidityContractJsonArtifactGasEstimatesCreation + */ + 'totalCost'?: string; +} +/** + * + * @export + * @enum {string} + */ + +export const WatchBlocksV1 = { + Subscribe: 'org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Subscribe', + Next: 'org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Next', + Unsubscribe: 'org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Unsubscribe', + Error: 'org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Error', + Complete: 'org.hyperledger.cactus.api.async.ethereum.WatchBlocksV1.Complete' +} as const; + +export type WatchBlocksV1 = typeof WatchBlocksV1[keyof typeof WatchBlocksV1]; + + +/** + * + * @export + * @interface WatchBlocksV1BlockData + */ +export interface WatchBlocksV1BlockData { + /** + * + * @type {number} + * @memberof WatchBlocksV1BlockData + */ + 'number': number; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'hash': string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'parentHash': string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'nonce': string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'sha3Uncles': string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'logsBloom': string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'transactionsRoot'?: string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'stateRoot': string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'receiptsRoot'?: string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'difficulty'?: string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'mixHash'?: string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'miner': string; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'extraData': string; + /** + * + * @type {number} + * @memberof WatchBlocksV1BlockData + */ + 'gasLimit': number; + /** + * + * @type {number} + * @memberof WatchBlocksV1BlockData + */ + 'gasUsed': number; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof WatchBlocksV1BlockData + */ + 'timestamp': EthereumTransactionConfigFrom; + /** + * + * @type {number} + * @memberof WatchBlocksV1BlockData + */ + 'size': number; + /** + * + * @type {string} + * @memberof WatchBlocksV1BlockData + */ + 'totalDifficulty': string; + /** + * + * @type {Array} + * @memberof WatchBlocksV1BlockData + */ + 'uncles': Array; + /** + * + * @type {Array} + * @memberof WatchBlocksV1BlockData + */ + 'transactions': Array; +} +/** + * + * @export + * @interface WatchBlocksV1Options + */ +export interface WatchBlocksV1Options { + /** + * + * @type {boolean} + * @memberof WatchBlocksV1Options + */ + 'getBlockData'?: boolean; +} +/** + * + * @export + * @interface WatchBlocksV1Progress + */ +export interface WatchBlocksV1Progress { + /** + * + * @type {Web3BlockHeader} + * @memberof WatchBlocksV1Progress + */ + 'blockHeader'?: Web3BlockHeader; + /** + * + * @type {WatchBlocksV1BlockData} + * @memberof WatchBlocksV1Progress + */ + 'blockData'?: WatchBlocksV1BlockData; +} +/** + * + * @export + * @interface Web3BlockHeader + */ +export interface Web3BlockHeader { + /** + * + * @type {number} + * @memberof Web3BlockHeader + */ + 'number': number; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'hash': string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'parentHash': string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'nonce': string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'sha3Uncles': string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'logsBloom': string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'transactionsRoot'?: string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'stateRoot': string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'receiptsRoot'?: string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'difficulty'?: string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'mixHash'?: string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'miner': string; + /** + * + * @type {string} + * @memberof Web3BlockHeader + */ + 'extraData': string; + /** + * + * @type {number} + * @memberof Web3BlockHeader + */ + 'gasLimit': number; + /** + * + * @type {number} + * @memberof Web3BlockHeader + */ + 'gasUsed': number; + /** + * + * @type {EthereumTransactionConfigFrom} + * @memberof Web3BlockHeader + */ + 'timestamp': EthereumTransactionConfigFrom; +} +/** + * @type Web3SigningCredential + * @export + */ +export type Web3SigningCredential = Web3SigningCredentialCactusKeychainRef | Web3SigningCredentialGethKeychainPassword | Web3SigningCredentialNone | Web3SigningCredentialPrivateKeyHex; + +/** + * + * @export + * @interface Web3SigningCredentialCactusKeychainRef + */ +export interface Web3SigningCredentialCactusKeychainRef { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + 'type': Web3SigningCredentialType; + /** + * The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + 'ethAccount': string; + /** + * The key to use when looking up the the keychain entry holding the secret pointed to by the keychainEntryKey parameter. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + 'keychainEntryKey': string; + /** + * The keychain ID to use when looking up the the keychain plugin instance that will be used to retrieve the secret pointed to by the keychainEntryKey parameter. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + 'keychainId'?: string; +} + + +/** + * + * @export + * @interface Web3SigningCredentialGethKeychainPassword + */ +export interface Web3SigningCredentialGethKeychainPassword { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialGethKeychainPassword + */ + 'type': Web3SigningCredentialType; + /** + * The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication. + * @type {string} + * @memberof Web3SigningCredentialGethKeychainPassword + */ + 'ethAccount': string; + /** + * A geth keychain unlock password. + * @type {string} + * @memberof Web3SigningCredentialGethKeychainPassword + */ + 'secret': string; +} + + +/** + * Using this denotes that there is no signing required because the transaction is pre-signed. + * @export + * @interface Web3SigningCredentialNone + */ +export interface Web3SigningCredentialNone { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialNone + */ + 'type': Web3SigningCredentialType; +} + + +/** + * + * @export + * @interface Web3SigningCredentialPrivateKeyHex + */ +export interface Web3SigningCredentialPrivateKeyHex { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialPrivateKeyHex + */ + 'type': Web3SigningCredentialType; + /** + * The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication. + * @type {string} + * @memberof Web3SigningCredentialPrivateKeyHex + */ + 'ethAccount': string; + /** + * The HEX encoded private key of an eth account. + * @type {string} + * @memberof Web3SigningCredentialPrivateKeyHex + */ + 'secret': string; +} + + +/** + * + * @export + * @enum {string} + */ + +export const Web3SigningCredentialType = { + CactusKeychainRef: 'CACTUS_KEYCHAIN_REF', + GethKeychainPassword: 'GETH_KEYCHAIN_PASSWORD', + PrivateKeyHex: 'PRIVATE_KEY_HEX', + None: 'NONE' +} as const; + +export type Web3SigningCredentialType = typeof Web3SigningCredentialType[keyof typeof Web3SigningCredentialType]; + + +/** + * + * @export + * @interface Web3Transaction + */ +export interface Web3Transaction { + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'hash': string; + /** + * + * @type {number} + * @memberof Web3Transaction + */ + 'nonce': number; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'blockHash': string | null; + /** + * + * @type {number} + * @memberof Web3Transaction + */ + 'blockNumber': number | null; + /** + * + * @type {number} + * @memberof Web3Transaction + */ + 'transactionIndex': number | null; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'from': string; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'to': string | null; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'value': string; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'gasPrice': string; + /** + * + * @type {number} + * @memberof Web3Transaction + */ + 'gas': number; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'input': string; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'v'?: string; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 'r'?: string; + /** + * + * @type {string} + * @memberof Web3Transaction + */ + 's'?: string; +} +/** + * + * @export + * @interface Web3TransactionReceipt + */ +export interface Web3TransactionReceipt { + [key: string]: any; + + /** + * + * @type {boolean} + * @memberof Web3TransactionReceipt + */ + 'status': boolean; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'transactionHash': string; + /** + * + * @type {number} + * @memberof Web3TransactionReceipt + */ + 'transactionIndex': number; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'blockHash': string; + /** + * + * @type {number} + * @memberof Web3TransactionReceipt + */ + 'blockNumber': number; + /** + * + * @type {number} + * @memberof Web3TransactionReceipt + */ + 'gasUsed': number; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'contractAddress'?: string | null; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'from': string; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'to': string; + /** + * + * @type {Array} + * @memberof Web3TransactionReceipt + */ + 'logs'?: Array; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'logsBloom'?: string; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'revertReason'?: string; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'output'?: string; + /** + * + * @type {string} + * @memberof Web3TransactionReceipt + */ + 'commitmentHash'?: string; + /** + * + * @type {number} + * @memberof Web3TransactionReceipt + */ + 'cumulativeGasUSed'?: number; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractSolBytecodeJsonObjectV1: async (deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode-json-object`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(deployContractSolidityBytecodeJsonObjectV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeV1Request} [deployContractSolidityBytecodeV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractSolBytecodeV1: async (deployContractSolidityBytecodeV1Request?: DeployContractSolidityBytecodeV1Request, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(deployContractSolidityBytecodeV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPrometheusMetricsV1: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractV1Request} [invokeContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeContractV1: async (invokeContractV1Request?: InvokeContractV1Request, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(invokeContractV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeContractV1NoKeychain: async (invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract-json-object`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(invokeContractJsonObjectV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Low-level endpoint to invoke a method on deployed contract. + * @param {InvokeRawWeb3EthContractV1Request} [invokeRawWeb3EthContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeRawWeb3EthContractV1: async (invokeRawWeb3EthContractV1Request?: InvokeRawWeb3EthContractV1Request, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-contract`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(invokeRawWeb3EthContractV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Invoke any method from web3.eth (low-level) + * @param {InvokeRawWeb3EthMethodV1Request} [invokeRawWeb3EthMethodV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeWeb3EthMethodV1: async (invokeRawWeb3EthMethodV1Request?: InvokeRawWeb3EthMethodV1Request, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-method`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(invokeRawWeb3EthMethodV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Executes a transaction on a ethereum ledger + * @param {RunTransactionRequest} [runTransactionRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runTransactionV1: async (runTransactionRequest?: RunTransactionRequest, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/run-transaction`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(runTransactionRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeV1Request} [deployContractSolidityBytecodeV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deployContractSolBytecodeV1(deployContractSolidityBytecodeV1Request?: DeployContractSolidityBytecodeV1Request, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deployContractSolBytecodeV1(deployContractSolidityBytecodeV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getPrometheusMetricsV1(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPrometheusMetricsV1(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractV1Request} [invokeContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async invokeContractV1(invokeContractV1Request?: InvokeContractV1Request, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.invokeContractV1(invokeContractV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async invokeContractV1NoKeychain(invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.invokeContractV1NoKeychain(invokeContractJsonObjectV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Low-level endpoint to invoke a method on deployed contract. + * @param {InvokeRawWeb3EthContractV1Request} [invokeRawWeb3EthContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async invokeRawWeb3EthContractV1(invokeRawWeb3EthContractV1Request?: InvokeRawWeb3EthContractV1Request, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.invokeRawWeb3EthContractV1(invokeRawWeb3EthContractV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Invoke any method from web3.eth (low-level) + * @param {InvokeRawWeb3EthMethodV1Request} [invokeRawWeb3EthMethodV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async invokeWeb3EthMethodV1(invokeRawWeb3EthMethodV1Request?: InvokeRawWeb3EthMethodV1Request, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.invokeWeb3EthMethodV1(invokeRawWeb3EthMethodV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Executes a transaction on a ethereum ledger + * @param {RunTransactionRequest} [runTransactionRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async runTransactionV1(runTransactionRequest?: RunTransactionRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.runTransactionV1(runTransactionRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = DefaultApiFp(configuration) + return { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options?: any): AxiosPromise { + return localVarFp.deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeV1Request} [deployContractSolidityBytecodeV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractSolBytecodeV1(deployContractSolidityBytecodeV1Request?: DeployContractSolidityBytecodeV1Request, options?: any): AxiosPromise { + return localVarFp.deployContractSolBytecodeV1(deployContractSolidityBytecodeV1Request, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPrometheusMetricsV1(options?: any): AxiosPromise { + return localVarFp.getPrometheusMetricsV1(options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractV1Request} [invokeContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeContractV1(invokeContractV1Request?: InvokeContractV1Request, options?: any): AxiosPromise { + return localVarFp.invokeContractV1(invokeContractV1Request, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeContractV1NoKeychain(invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options?: any): AxiosPromise { + return localVarFp.invokeContractV1NoKeychain(invokeContractJsonObjectV1Request, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Low-level endpoint to invoke a method on deployed contract. + * @param {InvokeRawWeb3EthContractV1Request} [invokeRawWeb3EthContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeRawWeb3EthContractV1(invokeRawWeb3EthContractV1Request?: InvokeRawWeb3EthContractV1Request, options?: any): AxiosPromise { + return localVarFp.invokeRawWeb3EthContractV1(invokeRawWeb3EthContractV1Request, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Invoke any method from web3.eth (low-level) + * @param {InvokeRawWeb3EthMethodV1Request} [invokeRawWeb3EthMethodV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeWeb3EthMethodV1(invokeRawWeb3EthMethodV1Request?: InvokeRawWeb3EthMethodV1Request, options?: any): AxiosPromise { + return localVarFp.invokeWeb3EthMethodV1(invokeRawWeb3EthMethodV1Request, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Executes a transaction on a ethereum ledger + * @param {RunTransactionRequest} [runTransactionRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runTransactionV1(runTransactionRequest?: RunTransactionRequest, options?: any): AxiosPromise { + return localVarFp.runTransactionV1(runTransactionRequest, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeV1Request} [deployContractSolidityBytecodeV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public deployContractSolBytecodeV1(deployContractSolidityBytecodeV1Request?: DeployContractSolidityBytecodeV1Request, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).deployContractSolBytecodeV1(deployContractSolidityBytecodeV1Request, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Get the Prometheus Metrics + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getPrometheusMetricsV1(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getPrometheusMetricsV1(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractV1Request} [invokeContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public invokeContractV1(invokeContractV1Request?: InvokeContractV1Request, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).invokeContractV1(invokeContractV1Request, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public invokeContractV1NoKeychain(invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).invokeContractV1NoKeychain(invokeContractJsonObjectV1Request, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Low-level endpoint to invoke a method on deployed contract. + * @param {InvokeRawWeb3EthContractV1Request} [invokeRawWeb3EthContractV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public invokeRawWeb3EthContractV1(invokeRawWeb3EthContractV1Request?: InvokeRawWeb3EthContractV1Request, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).invokeRawWeb3EthContractV1(invokeRawWeb3EthContractV1Request, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Invoke any method from web3.eth (low-level) + * @param {InvokeRawWeb3EthMethodV1Request} [invokeRawWeb3EthMethodV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public invokeWeb3EthMethodV1(invokeRawWeb3EthMethodV1Request?: InvokeRawWeb3EthMethodV1Request, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).invokeWeb3EthMethodV1(invokeRawWeb3EthMethodV1Request, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Executes a transaction on a ethereum ledger + * @param {RunTransactionRequest} [runTransactionRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public runTransactionV1(runTransactionRequest?: RunTransactionRequest, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).runTransactionV1(runTransactionRequest, options).then((request) => request(this.axios, this.basePath)); + } +} + + diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/base.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/base.ts new file mode 100644 index 0000000000..542aebb1fb --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/base.ts @@ -0,0 +1,72 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Connector Ethereum + * Can perform basic tasks on a Ethereum ledger + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: AxiosRequestConfig; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/common.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/common.ts new file mode 100644 index 0000000000..ba7055be44 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Connector Ethereum + * Can perform basic tasks on a Ethereum ledger + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from "./configuration"; +import type { RequestArgs } from "./base"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from "./base"; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/configuration.ts new file mode 100644 index 0000000000..667f2c7cb6 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/configuration.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Connector Ethereum + * Can perform basic tasks on a Ethereum ledger + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/index.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/index.ts new file mode 100644 index 0000000000..7b2d607998 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/generated/openapi/typescript-axios/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cactus Plugin - Connector Ethereum + * Can perform basic tasks on a Ethereum ledger + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; + diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.ts new file mode 100755 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts new file mode 100755 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/model-type-guards.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/model-type-guards.ts new file mode 100644 index 0000000000..312e8851a2 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/model-type-guards.ts @@ -0,0 +1,42 @@ +import { + Web3SigningCredentialCactusKeychainRef, + Web3SigningCredentialGethKeychainPassword, + Web3SigningCredentialNone, + Web3SigningCredentialPrivateKeyHex, + Web3SigningCredentialType, +} from "./generated/openapi/typescript-axios/api"; + +export function isWeb3SigningCredentialPrivateKeyHex(x?: { + type?: Web3SigningCredentialType; +}): x is Web3SigningCredentialPrivateKeyHex { + return x?.type === Web3SigningCredentialType.PrivateKeyHex; +} + +export function isWeb3SigningCredentialNone(x?: { + type?: Web3SigningCredentialType; +}): x is Web3SigningCredentialNone { + return x?.type === Web3SigningCredentialType.None; +} + +export function isWeb3SigningCredentialGethKeychainPassword(x?: { + type?: Web3SigningCredentialType; +}): x is Web3SigningCredentialGethKeychainPassword { + return x?.type === Web3SigningCredentialType.GethKeychainPassword; +} + +export function isWeb3SigningCredentialCactusKeychainRef(x?: { + type?: Web3SigningCredentialType; + keychainEntryKey?: string | unknown; + keychainId?: string | unknown; +}): x is Web3SigningCredentialCactusKeychainRef { + return ( + !!x?.type && + x?.type === Web3SigningCredentialType.CactusKeychainRef && + !!x?.keychainEntryKey && + typeof x?.keychainEntryKey === "string" && + x?.keychainEntryKey.trim().length > 0 && + !!x?.keychainId && + typeof x?.keychainId === "string" && + x?.keychainId.trim().length > 0 + ); +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-factory-ledger-connector.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-factory-ledger-connector.ts new file mode 100644 index 0000000000..8ae6f686cf --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-factory-ledger-connector.ts @@ -0,0 +1,20 @@ +import { + IPluginFactoryOptions, + PluginFactory, +} from "@hyperledger/cactus-core-api"; +import { + IPluginLedgerConnectorEthereumOptions, + PluginLedgerConnectorEthereum, +} from "./plugin-ledger-connector-ethereum"; + +export class PluginFactoryLedgerConnector extends PluginFactory< + PluginLedgerConnectorEthereum, + IPluginLedgerConnectorEthereumOptions, + IPluginFactoryOptions +> { + async create( + pluginOptions: IPluginLedgerConnectorEthereumOptions, + ): Promise { + return new PluginLedgerConnectorEthereum(pluginOptions); + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts new file mode 100644 index 0000000000..7feac3680a --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts @@ -0,0 +1,802 @@ +import type { + Server as SocketIoServer, + Socket as SocketIoSocket, +} from "socket.io"; + +import { Express } from "express"; +import Web3 from "web3"; +import { AbiItem } from "web3-utils"; +import { Contract } from "web3-eth-contract"; +import { ContractSendMethod } from "web3-eth-contract"; +import { TransactionReceipt } from "web3-eth"; + +import OAS from "../json/openapi.json"; + +import { + ConsensusAlgorithmFamily, + IPluginLedgerConnector, + IWebServiceEndpoint, + IPluginWebService, + ICactusPlugin, + ICactusPluginOptions, +} from "@hyperledger/cactus-core-api"; + +import { + PluginRegistry, + consensusHasTransactionFinality, +} from "@hyperledger/cactus-core"; + +import { + Checks, + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { DeployContractSolidityBytecodeEndpoint } from "./web-services/deploy-contract-solidity-bytecode-endpoint"; +import { DeployContractSolidityBytecodeJsonObjectEndpoint } from "./web-services/deploy-contract-solidity-bytecode-endpoint-json-object"; + +import { + DeployContractSolidityBytecodeV1Request, + DeployContractSolidityBytecodeJsonObjectV1Request, + DeployContractSolidityBytecodeV1Response, + EthContractInvocationType, + EthContractInvocationWeb3Method, + InvokeContractV1Request, + InvokeContractJsonObjectV1Request, + InvokeContractV1Response, + RunTransactionRequest, + RunTransactionResponse, + Web3SigningCredentialGethKeychainPassword, + Web3SigningCredentialCactusKeychainRef, + Web3SigningCredentialPrivateKeyHex, + Web3SigningCredentialType, + WatchBlocksV1, + WatchBlocksV1Options, + InvokeRawWeb3EthMethodV1Request, + InvokeRawWeb3EthContractV1Request, +} from "./generated/openapi/typescript-axios"; + +import { RunTransactionEndpoint } from "./web-services/run-transaction-endpoint"; +import { InvokeContractEndpoint } from "./web-services/invoke-contract-endpoint"; +import { InvokeContractJsonObjectEndpoint } from "./web-services/invoke-contract-endpoint-json-object"; +import { WatchBlocksV1Endpoint } from "./web-services/watch-blocks-v1-endpoint"; +import { GetPrometheusExporterMetricsEndpointV1 } from "./web-services/get-prometheus-exporter-metrics-endpoint-v1"; +import { InvokeRawWeb3EthMethodEndpoint } from "./web-services/invoke-raw-web3eth-method-v1-endpoint"; +import { InvokeRawWeb3EthContractEndpoint } from "./web-services/invoke-raw-web3eth-contract-v1-endpoint"; + +import { isWeb3SigningCredentialNone } from "./model-type-guards"; +import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter"; +import { RuntimeError } from "run-time-error"; + +export interface IPluginLedgerConnectorEthereumOptions + extends ICactusPluginOptions { + rpcApiHttpHost: string; + rpcApiWsHost?: string; + logLevel?: LogLevelDesc; + prometheusExporter?: PrometheusExporter; + pluginRegistry: PluginRegistry; +} + +export class PluginLedgerConnectorEthereum + implements + IPluginLedgerConnector< + DeployContractSolidityBytecodeV1Request, + DeployContractSolidityBytecodeV1Response, + RunTransactionRequest, + RunTransactionResponse + >, + ICactusPlugin, + IPluginWebService { + private readonly pluginRegistry: PluginRegistry; + public prometheusExporter: PrometheusExporter; + private readonly instanceId: string; + private readonly log: Logger; + private readonly web3: Web3; + private endpoints: IWebServiceEndpoint[] | undefined; + public static readonly CLASS_NAME = "PluginLedgerConnectorEthereum"; + + public get className(): string { + return PluginLedgerConnectorEthereum.CLASS_NAME; + } + + private getWeb3Provider() { + if (!this.options.rpcApiWsHost) { + return new Web3.providers.HttpProvider(this.options.rpcApiHttpHost); + } + return new Web3.providers.WebsocketProvider(this.options.rpcApiWsHost); + } + + constructor(public readonly options: IPluginLedgerConnectorEthereumOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.rpcApiHttpHost, `${fnTag} options.rpcApiHttpHost`); + Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); + Checks.truthy(options.pluginRegistry, `${fnTag} options.pluginRegistry`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + this.web3 = new Web3(this.getWeb3Provider()); + + this.instanceId = options.instanceId; + this.pluginRegistry = options.pluginRegistry as PluginRegistry; + this.prometheusExporter = + options.prometheusExporter || + new PrometheusExporter({ pollingIntervalInMin: 1 }); + Checks.truthy( + this.prometheusExporter, + `${fnTag} options.prometheusExporter`, + ); + + this.prometheusExporter.startMetricsCollection(); + } + + public getOpenApiSpec(): unknown { + return OAS; + } + + public getPrometheusExporter(): PrometheusExporter { + return this.prometheusExporter; + } + + public async getPrometheusExporterMetrics(): Promise { + const res: string = await this.prometheusExporter.getPrometheusMetrics(); + this.log.debug(`getPrometheusExporterMetrics() response: %o`, res); + return res; + } + + public getInstanceId(): string { + return this.instanceId; + } + + public async shutdown(): Promise { + this.log.info(`Shutting down ${this.className}...`); + const provider = this.web3.currentProvider; + if (provider && typeof provider == "object") { + if ("disconnect" in provider) { + provider.disconnect(1000, "shutdown"); + } + } + } + + public async onPluginInit(): Promise { + return; + } + + async registerWebServices( + app: Express, + wsApi: SocketIoServer, + ): Promise { + const { web3 } = this; + const { logLevel } = this.options; + const webServices = await this.getOrCreateWebServices(); + await Promise.all(webServices.map((ws) => ws.registerExpress(app))); + + wsApi.on("connection", (socket: SocketIoSocket) => { + this.log.debug(`New Socket connected. ID=${socket.id}`); + + socket.on(WatchBlocksV1.Subscribe, (options?: WatchBlocksV1Options) => { + new WatchBlocksV1Endpoint({ + web3, + socket, + logLevel, + options, + }).subscribe(); + }); + }); + + return webServices; + } + + public async getOrCreateWebServices(): Promise { + if (Array.isArray(this.endpoints)) { + return this.endpoints; + } + const endpoints: IWebServiceEndpoint[] = []; + { + const endpoint = new DeployContractSolidityBytecodeEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new DeployContractSolidityBytecodeJsonObjectEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new RunTransactionEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new InvokeContractEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new InvokeContractJsonObjectEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new GetPrometheusExporterMetricsEndpointV1({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new InvokeRawWeb3EthMethodEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new InvokeRawWeb3EthContractEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + this.endpoints = endpoints; + return endpoints; + } + + public getPackageName(): string { + return `@hyperledger/cactus-plugin-ledger-connector-ethereum`; + } + + public async getConsensusAlgorithmFamily(): Promise< + ConsensusAlgorithmFamily + > { + return ConsensusAlgorithmFamily.Stake; + } + + public async hasTransactionFinality(): Promise { + const currentConsensusAlgorithmFamily = await this.getConsensusAlgorithmFamily(); + + return consensusHasTransactionFinality(currentConsensusAlgorithmFamily); + } + + /** + * Verifies that it is safe to call a specific method on an object. + * + * @param object Object instance to check whether it has a method with a specific name or not. + * @param name The name of the method that will be checked if it's usable on `object` or not. + * @returns Boolean `true` when it IS safe to call the method named `name` on the object. + * @throws If the object instance is falsy or the method name is a blank string. + */ + public isSafeToCallObjectMethod( + object: Record, + name: string, + ): boolean { + Checks.truthy( + object, + `${this.className}#isSafeToCallObjectMethod():contract`, + ); + Checks.nonBlankString( + name, + `${this.className}#isSafeToCallObjectMethod():name`, + ); + + return ( + Object.prototype.hasOwnProperty.call(object, name) && + typeof object[name] === "function" + ); + } + + /** + * Verifies that it is safe to call a specific method of a Web3 Contract. + * + * @param contract The Web3 Contract instance to check whether it has a method with a specific name or not. + * @param name The name of the method that will be checked if it's usable on `contract` or not. + * @returns Boolean `true` when it IS safe to call the method named `name` on the contract. + * @throws If the contract instance is falsy or it's methods object is falsy. Also throws if the method name is a blank string. + */ + public async isSafeToCallContractMethod( + contract: InstanceType, + name: string, + ): Promise { + Checks.truthy( + contract, + `${this.className}#isSafeToCallContractMethod():contract`, + ); + + Checks.truthy( + contract.methods, + `${this.className}#isSafeToCallContractMethod():contract.methods`, + ); + + Checks.nonBlankString( + name, + `${this.className}#isSafeToCallContractMethod():name`, + ); + + return this.isSafeToCallObjectMethod(contract.methods, name); + } + + public async getContractInfoKeychain( + req: InvokeContractV1Request, + ): Promise { + const fnTag = `${this.className}#invokeContract()`; + + const { contractName, keychainId } = req; + if (!contractName) { + throw new Error( + `${fnTag} Cannot recover the keychain plugin because the contractName is empty`, + ); + } + if (!keychainId) { + throw new Error(`${fnTag} Cannot invoke contract without keychainId`); + } + const keychainPlugin = this.pluginRegistry.findOneByKeychainId(keychainId); + if (!keychainPlugin.has(contractName)) { + throw new Error( + `${fnTag} Cannot invoke the contract because contractName is not in the keychainPlugin`, + ); + } + const contractStr = await keychainPlugin.get(contractName); + const contractJSON = JSON.parse(contractStr); + + // if not exists a contract deployed, we deploy it + const networkId = await this.web3.eth.net.getId(); + if ( + !contractJSON.networks || + !contractJSON.networks[networkId] || + !contractJSON.networks[networkId].address + ) { + if (isWeb3SigningCredentialNone(req.web3SigningCredential)) { + throw new Error(`${fnTag} Cannot deploy contract with pre-signed TX`); + } + + const receipt = await this.runDeploy(req); + + const address = { + address: receipt.transactionReceipt.contractAddress, + }; + const network = { [networkId]: address }; + contractJSON.networks = network; + keychainPlugin.set(req.contractName, JSON.stringify(contractJSON)); + } + + return this.invokeContract({ + ...req, + contractAddress: contractJSON.networks[networkId].address, + contractJSON: contractJSON, + }); + } + + public async getContractInfo( + req: InvokeContractJsonObjectV1Request, + ): Promise { + const fnTag = `${this.className}#invokeContractNoKeychain()`; + const { contractJSON, contractAddress } = req; + if (!contractJSON) { + throw new Error(`${fnTag} The contractJson param is needed`); + } + if (!contractAddress) { + throw new Error(`${fnTag} The contractAddress param is needed`); + } + return this.invokeContract(req); + } + + public async invokeContract( + req: InvokeContractJsonObjectV1Request, + ): Promise { + const fnTag = `${this.className}#invokeContract()`; + + const { contractAddress, contractJSON } = req; + + let abi; + if (contractJSON.abi) { + if (typeof contractJSON.abi === "string") { + abi = JSON.parse(contractJSON.abi); + } else { + abi = contractJSON.abi; + } + } else { + throw new Error(`${fnTag} Contract ABI is necessary`); + } + + const contractInstance: InstanceType = new this.web3.eth.Contract( + abi, + contractAddress, + ); + + const isSafeToCall = await this.isSafeToCallContractMethod( + contractInstance, + req.methodName, + ); + if (!isSafeToCall) { + throw new RuntimeError( + `Invalid method name provided in request. ${req.methodName} does not exist on the Web3 contract object's "methods" property.`, + ); + } + + const methodRef = contractInstance.methods[req.methodName]; + Checks.truthy(methodRef, `${fnTag} YourContract.${req.methodName}`); + + const method: ContractSendMethod = methodRef(...req.params); + if (req.invocationType === EthContractInvocationType.Call) { + contractInstance.methods[req.methodName]; + const callOutput = await (method as any).call(); + const success = true; + return { success, callOutput }; + } else if (req.invocationType === EthContractInvocationType.Send) { + if (isWeb3SigningCredentialNone(req.web3SigningCredential)) { + throw new Error(`${fnTag} Cannot deploy contract with pre-signed TX`); + } + const web3SigningCredential = req.web3SigningCredential as + | Web3SigningCredentialPrivateKeyHex + | Web3SigningCredentialCactusKeychainRef; + const payload = (method.send as any).request(); + const { params } = payload; + const [transactionConfig] = params; + if (!req.gas) { + req.gas = await this.web3.eth.estimateGas(transactionConfig); + } + transactionConfig.from = web3SigningCredential.ethAccount; + transactionConfig.gas = req.gas; + transactionConfig.gasPrice = req.gasPrice; + transactionConfig.value = req.value; + transactionConfig.nonce = req.nonce; + + const txReq: RunTransactionRequest = { + transactionConfig, + web3SigningCredential, + timeoutMs: req.timeoutMs || 60000, + }; + const out = await this.transact(txReq); + const success = out.transactionReceipt.status; + const data = { success, out }; + return data; + } else { + throw new Error( + `${fnTag} Unsupported invocation type ${req.invocationType}`, + ); + } + } + + public async transact( + req: RunTransactionRequest, + ): Promise { + const fnTag = `${this.className}#transact()`; + + switch (req.web3SigningCredential.type) { + case Web3SigningCredentialType.CactusKeychainRef: { + return this.transactCactusKeychainRef(req); + } + case Web3SigningCredentialType.GethKeychainPassword: { + return this.transactGethKeychain(req); + } + case Web3SigningCredentialType.PrivateKeyHex: { + return this.transactPrivateKey(req); + } + case Web3SigningCredentialType.None: { + if (req.transactionConfig.rawTransaction) { + return this.transactSigned(req.transactionConfig.rawTransaction); + } else { + throw new Error( + `${fnTag} Expected pre-signed raw transaction ` + + ` since signing credential is specified as` + + `Web3SigningCredentialType.NONE`, + ); + } + } + default: { + throw new Error( + `${fnTag} Unrecognized Web3SigningCredentialType: ` + + `${req.web3SigningCredential.type} Supported ones are: ` + + `${Object.values(Web3SigningCredentialType).join(";")}`, + ); + } + } + } + + public async transactSigned( + rawTransaction: string, + ): Promise { + const fnTag = `${this.className}#transactSigned()`; + + const receipt = await this.web3.eth.sendSignedTransaction(rawTransaction); + + if (receipt instanceof Error) { + this.log.debug(`${fnTag} Web3 sendSignedTransaction failed`, receipt); + throw receipt; + } else { + this.prometheusExporter.addCurrentTransaction(); + return { transactionReceipt: receipt }; + } + } + + public async transactGethKeychain( + txIn: RunTransactionRequest, + ): Promise { + const fnTag = `${this.className}#transactGethKeychain()`; + const { sendTransaction } = this.web3.eth.personal; + const { transactionConfig, web3SigningCredential } = txIn; + const { + secret, + } = web3SigningCredential as Web3SigningCredentialGethKeychainPassword; + + try { + const txHash = await sendTransaction(transactionConfig, secret); + const transactionReceipt = await this.pollForTxReceipt(txHash); + return { transactionReceipt }; + } catch (ex) { + throw new Error( + `${fnTag} Failed to invoke web3.eth.personal.sendTransaction(). ` + + `InnerException: ${ex.stack}`, + ); + } + } + + public async transactPrivateKey( + req: RunTransactionRequest, + ): Promise { + const fnTag = `${this.className}#transactPrivateKey()`; + const { transactionConfig, web3SigningCredential } = req; + const { + secret, + } = web3SigningCredential as Web3SigningCredentialPrivateKeyHex; + + const signedTx = await this.web3.eth.accounts.signTransaction( + transactionConfig, + secret, + ); + + if (signedTx.rawTransaction) { + return this.transactSigned(signedTx.rawTransaction); + } else { + throw new Error( + `${fnTag} Failed to sign eth transaction. ` + + `signedTransaction.rawTransaction is blank after .signTransaction().`, + ); + } + } + + public async transactCactusKeychainRef( + req: RunTransactionRequest, + ): Promise { + const fnTag = `${this.className}#transactCactusKeychainRef()`; + const { transactionConfig, web3SigningCredential } = req; + const { + ethAccount, + keychainEntryKey, + keychainId, + } = web3SigningCredential as Web3SigningCredentialCactusKeychainRef; + + // locate the keychain plugin that has access to the keychain backend + // denoted by the keychainID from the request. + const keychainPlugin = this.pluginRegistry.findOneByKeychainId( + keychainId as string, + ); + Checks.truthy(keychainPlugin, `${fnTag} keychain for ID:"${keychainId}"`); + + // Now use the found keychain plugin to actually perform the lookup of + // the private key that we need to run the transaction. + const privateKeyHex = await keychainPlugin?.get(keychainEntryKey as string); + + if (!transactionConfig.gas) { + this.log.debug( + `${fnTag} Gas not specified in the transaction values. Using the estimate from web3`, + ); + transactionConfig.gas = await this.web3.eth.estimateGas( + transactionConfig, + ); + this.log.debug( + `${fnTag} Gas estimated from web3 is: `, + transactionConfig.gas, + ); + } + + return this.transactPrivateKey({ + transactionConfig, + web3SigningCredential: { + ethAccount, + type: Web3SigningCredentialType.PrivateKeyHex, + secret: privateKeyHex, + }, + }); + } + + public async pollForTxReceipt( + txHash: string, + timeoutMs = 60000, + ): Promise { + const fnTag = `${this.className}#pollForTxReceipt()`; + let txReceipt; + let timedOut = false; + let tries = 0; + const startedAt = new Date(); + + do { + txReceipt = await this.web3.eth.getTransactionReceipt(txHash); + tries++; + timedOut = Date.now() >= startedAt.getTime() + timeoutMs; + } while (!timedOut && !txReceipt); + + if (!txReceipt) { + throw new Error(`${fnTag} Timed out ${timeoutMs}ms, polls=${tries}`); + } else { + return txReceipt; + } + } + + private async generateBytecode(req: any): Promise { + const tmpContracts = new this.web3.eth.Contract( + (req.contractJSON as any).abi, + ); + const deployment = tmpContracts.deploy({ + data: req.contractJSON.bytecode, + arguments: req.constructorArgs, + }); + const abi = deployment.encodeABI(); + return abi.startsWith("0x") ? abi : `0x${abi}`; + } + + async runDeploy(req: any): Promise { + const web3SigningCredential = req.web3SigningCredential as + | Web3SigningCredentialGethKeychainPassword + | Web3SigningCredentialPrivateKeyHex; + + const bytecode = await this.generateBytecode(req); + + const receipt = await this.transact({ + transactionConfig: { + data: bytecode, + from: web3SigningCredential.ethAccount, + gas: req.gas, + gasPrice: req.gasPrice, + }, + web3SigningCredential, + }); + + return receipt; + } + + public async deployContract( + req: DeployContractSolidityBytecodeV1Request, + ): Promise { + const fnTag = `${this.className}#deployContract()`; + Checks.truthy(req, `${fnTag} req`); + + if (isWeb3SigningCredentialNone(req.web3SigningCredential)) { + throw new Error(`${fnTag} Cannot deploy contract with pre-signed TX`); + } + if (!req.keychainId || !req.contractName) { + throw new Error( + `${fnTag} Cannot deploy contract without keychainId and the contractName`, + ); + } + const keychainPlugin = this.pluginRegistry.findOneByKeychainId( + req.keychainId, + ); + Checks.truthy( + keychainPlugin, + `${fnTag} keychain for ID:"${req.keychainId}"`, + ); + if (!keychainPlugin.has(req.contractName)) { + throw new Error( + `${fnTag} Cannot create an instance of the contract because the contractName sent and the contractName of the JSON doesn't match`, + ); + } + + // obtain the contractJSON from keychainPlugin + const contractStr = await keychainPlugin.get(req.contractName); + const contractJSON = JSON.parse(contractStr); + req.contractJSON = contractJSON; + + // deploy the contract + const receipt = await this.runDeploy(req); + + // save the contract address in the keychainPlugin + if ( + receipt.transactionReceipt.status && + receipt.transactionReceipt.contractAddress && + receipt.transactionReceipt.contractAddress != null + ) { + const networkId = await this.web3.eth.net.getId(); + const address = { address: receipt.transactionReceipt.contractAddress }; + const network = { [networkId]: address }; + contractJSON.networks = network; + keychainPlugin.set(req.contractName, JSON.stringify(contractJSON)); + } + + return receipt; + } + + public async deployContractJsonObject( + req: DeployContractSolidityBytecodeJsonObjectV1Request, + ): Promise { + const fnTag = `${this.className}#deployContractNoKeychain()`; + if ( + !req.contractJSON || + !req.contractJSON.bytecode || + !req.web3SigningCredential + ) { + throw new Error( + `${fnTag} Cannot deploy contract without contractJSON, bytecode or web3SigningCredential`, + ); + } + return this.runDeploy(req); + } + + // Low level function to call any method from web3.eth + // Should be used only if given functionality is not already covered by another endpoint. + public async invokeRawWeb3EthMethod( + args: InvokeRawWeb3EthMethodV1Request, + ): Promise { + this.log.debug("invokeRawWeb3EthMethod input:", JSON.stringify(args)); + + Checks.nonBlankString( + args.methodName, + "web3.eth method string must not be empty", + ); + + const looseWeb3Eth = this.web3.eth as any; + const isSafeToCall = this.isSafeToCallObjectMethod( + looseWeb3Eth, + args.methodName, + ); + if (!isSafeToCall) { + throw new RuntimeError( + `Invalid method name provided in request. ${args.methodName} does not exist on the Web3.Eth object.`, + ); + } + + const web3MethodArgs = args.params || []; + return looseWeb3Eth[args.methodName](...web3MethodArgs); + } + + // Low level function to invoke contract + // Should be used only if given functionality is not already covered by another endpoint. + public async invokeRawWeb3EthContract( + args: InvokeRawWeb3EthContractV1Request, + ): Promise { + this.log.debug("invokeRawWeb3EthContract input:", JSON.stringify(args)); + + const contractMethodArgs = args.contractMethodArgs || []; + + if ( + !Object.values(EthContractInvocationWeb3Method).includes( + args.invocationType, + ) + ) { + throw new Error( + `Unknown invocationType (${args.invocationType}), must be specified in EthContractInvocationWeb3Method`, + ); + } + + const contract = new this.web3.eth.Contract( + args.abi as AbiItem[], + args.address, + ); + + const isSafeToCall = await this.isSafeToCallContractMethod( + contract, + args.contractMethod, + ); + if (!isSafeToCall) { + throw new RuntimeError( + `Invalid method name provided in request. ${args.contractMethod} does not exist on the Web3 contract object's "methods" property.`, + ); + } + + return contract.methods[args.contractMethod](...contractMethodArgs)[ + args.invocationType + ](args.invocationParams); + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/data.fetcher.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/data.fetcher.ts new file mode 100644 index 0000000000..8849477649 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/data.fetcher.ts @@ -0,0 +1,12 @@ +import { Transactions } from "./response.type"; + +import { totalTxCount, K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "./metrics"; + +export async function collectMetrics( + transactions: Transactions, +): Promise { + transactions.counter++; + totalTxCount + .labels(K_CACTUS_ETHEREUM_TOTAL_TX_COUNT) + .set(transactions.counter); +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/metrics.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/metrics.ts new file mode 100644 index 0000000000..0d736b6275 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/metrics.ts @@ -0,0 +1,10 @@ +import { Gauge } from "prom-client"; + +export const K_CACTUS_ETHEREUM_TOTAL_TX_COUNT = "cactus_eth_total_tx_count"; + +export const totalTxCount = new Gauge({ + registers: [], + name: K_CACTUS_ETHEREUM_TOTAL_TX_COUNT, + help: "Total transactions executed", + labelNames: ["type"], +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/prometheus-exporter.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/prometheus-exporter.ts new file mode 100644 index 0000000000..312d332d32 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/prometheus-exporter.ts @@ -0,0 +1,39 @@ +import promClient, { Registry } from "prom-client"; +import { Transactions } from "./response.type"; +import { collectMetrics } from "./data.fetcher"; +import { K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "./metrics"; +import { totalTxCount } from "./metrics"; + +export interface IPrometheusExporterOptions { + pollingIntervalInMin?: number; +} + +export class PrometheusExporter { + public readonly metricsPollingIntervalInMin: number; + public readonly transactions: Transactions = { counter: 0 }; + public readonly registry: Registry; + + constructor( + public readonly prometheusExporterOptions: IPrometheusExporterOptions, + ) { + this.metricsPollingIntervalInMin = + prometheusExporterOptions.pollingIntervalInMin || 1; + this.registry = new Registry(); + } + + public addCurrentTransaction(): void { + collectMetrics(this.transactions); + } + + public async getPrometheusMetrics(): Promise { + const result = await this.registry.getSingleMetricAsString( + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT, + ); + return result; + } + + public startMetricsCollection(): void { + this.registry.registerMetric(totalTxCount); + promClient.collectDefaultMetrics({ register: this.registry }); + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/response.type.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/response.type.ts new file mode 100644 index 0000000000..3f1bc7f491 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/prometheus-exporter/response.type.ts @@ -0,0 +1,3 @@ +export type Transactions = { + counter: number; +}; diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..b7bc4f674f --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts @@ -0,0 +1,31 @@ +export * from "./generated/openapi/typescript-axios"; + +export { + PluginLedgerConnectorEthereum, + IPluginLedgerConnectorEthereumOptions, +} from "./plugin-ledger-connector-ethereum"; + +export * from "./model-type-guards"; + +export { PluginFactoryLedgerConnector } from "./plugin-factory-ledger-connector"; + +import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; +import { PluginFactoryLedgerConnector } from "./plugin-factory-ledger-connector"; + +export { + EthereumApiClient, + EthereumApiClientOptions, + EthereumRequestInputWeb3EthMethod, + EthereumRequestInputWeb3EthContractMethod, + EthereumRequestInputContract, + EthereumRequestInputMethod, + EthereumRequestInputArgs, +} from "./api-client/ethereum-api-client"; + +export * from "./generated/openapi/typescript-axios/api"; + +export async function createPluginFactory( + pluginFactoryOptions: IPluginFactoryOptions, +): Promise { + return new PluginFactoryLedgerConnector(pluginFactoryOptions); +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint-json-object.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint-json-object.ts new file mode 100644 index 0000000000..bb9ef90fa1 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint-json-object.ts @@ -0,0 +1,109 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; +import { DeployContractSolidityBytecodeJsonObjectV1Request } from "../generated/openapi/typescript-axios"; +import OAS from "../../json/openapi.json"; + +export interface IDeployContractSolidityBytecodeOptionsJsonObject { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorEthereum; +} + +export class DeployContractSolidityBytecodeJsonObjectEndpoint + implements IWebServiceEndpoint { + public static readonly CLASS_NAME = + "DeployContractSolidityBytecodeEndpointJsonObject"; + + private readonly log: Logger; + + public get className(): string { + return DeployContractSolidityBytecodeJsonObjectEndpoint.CLASS_NAME; + } + + constructor( + public readonly options: IDeployContractSolidityBytecodeOptionsJsonObject, + ) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getOasPath() { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode-json-object" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + const reqBody: DeployContractSolidityBytecodeJsonObjectV1Request = req.body; + try { + const resBody = await this.options.connector.deployContractJsonObject( + reqBody, + ); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint.ts new file mode 100644 index 0000000000..3badc666cf --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint.ts @@ -0,0 +1,102 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; +import { DeployContractSolidityBytecodeV1Request } from "../generated/openapi/typescript-axios"; +import OAS from "../../json/openapi.json"; + +export interface IDeployContractSolidityBytecodeOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorEthereum; +} + +export class DeployContractSolidityBytecodeEndpoint + implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "DeployContractSolidityBytecodeEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return DeployContractSolidityBytecodeEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IDeployContractSolidityBytecodeOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): typeof OAS.paths["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/deploy-contract-solidity-bytecode" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + const reqBody: DeployContractSolidityBytecodeV1Request = req.body; + try { + const resBody = await this.options.connector.deployContract(reqBody); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/get-prometheus-exporter-metrics-endpoint-v1.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/get-prometheus-exporter-metrics-endpoint-v1.ts new file mode 100644 index 0000000000..e927fe4437 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/get-prometheus-exporter-metrics-endpoint-v1.ts @@ -0,0 +1,100 @@ +import { Express, Request, Response } from "express"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import OAS from "../../json/openapi.json"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + LogLevelDesc, + Logger, + LoggerProvider, + Checks, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; + +export interface IGetPrometheusExporterMetricsEndpointV1Options { + connector: PluginLedgerConnectorEthereum; + logLevel?: LogLevelDesc; +} + +export class GetPrometheusExporterMetricsEndpointV1 + implements IWebServiceEndpoint { + private readonly log: Logger; + + constructor( + public readonly options: IGetPrometheusExporterMetricsEndpointV1Options, + ) { + const fnTag = "GetPrometheusExporterMetricsEndpointV1#constructor()"; + + Checks.truthy(options, `${fnTag} options`); + Checks.truthy(options.connector, `${fnTag} options.connector`); + + const label = "get-prometheus-exporter-metrics-endpoint"; + const level = options.logLevel || "INFO"; + this.log = LoggerProvider.getOrCreate({ label, level }); + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public get oasPath(): typeof OAS.paths["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics" + ]; + } + + public getPath(): string { + return this.oasPath.get["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.get["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.get.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = "GetPrometheusExporterMetrics#handleRequest()"; + const verbUpper = this.getVerbLowerCase().toUpperCase(); + this.log.debug(`${verbUpper} ${this.getPath()}`); + + try { + const resBody = await this.options.connector.getPrometheusExporterMetrics(); + res.status(200); + res.send(resBody); + } catch (ex) { + this.log.error(`${fnTag} failed to serve request`, ex); + res.status(500); + res.statusMessage = ex.message; + res.json({ error: ex.stack }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint-json-object.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint-json-object.ts new file mode 100644 index 0000000000..c7beb955bb --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint-json-object.ts @@ -0,0 +1,103 @@ +import { Express, Request, Response } from "express"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + IEndpointAuthzOptions, + IExpressRequestHandler, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; + +import OAS from "../../json/openapi.json"; + +export interface IInvokeContractEndpointJsonObjectOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorEthereum; +} + +export class InvokeContractJsonObjectEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "InvokeContractJsonObjectEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return InvokeContractJsonObjectEndpoint.CLASS_NAME; + } + + constructor( + public readonly options: IInvokeContractEndpointJsonObjectOptions, + ) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getOasPath() { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract-json-object" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + const reqBody = req.body; + try { + const resBody = await this.options.connector.getContractInfo(reqBody); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint.ts new file mode 100644 index 0000000000..559665230a --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-endpoint.ts @@ -0,0 +1,102 @@ +import { Express, Request, Response } from "express"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + IEndpointAuthzOptions, + IExpressRequestHandler, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; + +import OAS from "../../json/openapi.json"; +import { InvokeContractV1Request } from "../generated/openapi/typescript-axios/api"; + +export interface IInvokeContractEndpointOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorEthereum; +} + +export class InvokeContractEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "InvokeContractEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return InvokeContractEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IInvokeContractEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): typeof OAS.paths["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + const reqBody: InvokeContractV1Request = req.body; + try { + const resBody = await this.options.connector.getContractInfoKeychain( + reqBody, + ); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts new file mode 100644 index 0000000000..18fa5b2677 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts @@ -0,0 +1,109 @@ +import { Express, Request, Response } from "express"; +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + IEndpointAuthzOptions, + IExpressRequestHandler, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; +import OAS from "../../json/openapi.json"; +import sanitizeHtml from "sanitize-html"; +import { InvokeRawWeb3EthContractV1Response } from "../generated/openapi/typescript-axios"; + +export interface IInvokeRawWeb3EthContractEndpointOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorEthereum; +} + +export class InvokeRawWeb3EthContractEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "InvokeRawWeb3EthContractEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return InvokeRawWeb3EthContractEndpoint.CLASS_NAME; + } + + constructor( + public readonly options: IInvokeRawWeb3EthContractEndpointOptions, + ) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): typeof OAS.paths["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-contract"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-contract" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + + try { + const methodResponse = await this.options.connector.invokeRawWeb3EthContract( + req.body, + ); + const response: InvokeRawWeb3EthContractV1Response = { + status: 200, + data: methodResponse, + }; + res.json(response); + } catch (ex: any) { + this.log.warn(`Error while serving ${reqTag}`, ex); + res.json({ + status: 504, + errorDetail: sanitizeHtml(ex, { + allowedTags: [], + allowedAttributes: {}, + }), + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts new file mode 100644 index 0000000000..b6a88567b0 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts @@ -0,0 +1,107 @@ +import { Express, Request, Response } from "express"; +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + IEndpointAuthzOptions, + IExpressRequestHandler, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; +import OAS from "../../json/openapi.json"; +import sanitizeHtml from "sanitize-html"; +import { InvokeRawWeb3EthMethodV1Response } from "../public-api"; + +export interface IInvokeRawWeb3EthMethodEndpointOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorEthereum; +} + +export class InvokeRawWeb3EthMethodEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "InvokeRawWeb3EthMethodEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return InvokeRawWeb3EthMethodEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IInvokeRawWeb3EthMethodEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): typeof OAS.paths["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-method"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-raw-web3eth-method" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + + try { + const methodResponse = await this.options.connector.invokeRawWeb3EthMethod( + req.body, + ); + const response: InvokeRawWeb3EthMethodV1Response = { + status: 200, + data: methodResponse, + }; + res.json(response); + } catch (ex: any) { + this.log.warn(`Error while serving ${reqTag}`, ex); + res.json({ + status: 504, + errorDetail: sanitizeHtml(ex, { + allowedTags: [], + allowedAttributes: {}, + }), + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-endpoint.ts new file mode 100644 index 0000000000..39bfa68378 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-endpoint.ts @@ -0,0 +1,100 @@ +import { Express, Request, Response } from "express"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + IEndpointAuthzOptions, + IExpressRequestHandler, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; + +import OAS from "../../json/openapi.json"; +import { RunTransactionRequest } from "../generated/openapi/typescript-axios/api"; + +export interface IRunTransactionEndpointOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorEthereum; +} + +export class RunTransactionEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RunTransactionEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return RunTransactionEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IRunTransactionEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): typeof OAS.paths["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/run-transaction"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/run-transaction" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + const reqBody: RunTransactionRequest = req.body; + try { + const resBody = await this.options.connector.transact(reqBody); + res.json({ success: true, data: resBody }); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts new file mode 100644 index 0000000000..c56f76ebbb --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts @@ -0,0 +1,103 @@ +import { + Logger, + LogLevelDesc, + LoggerProvider, + Checks, +} from "@hyperledger/cactus-common"; +import { + WatchBlocksV1Options, + WatchBlocksV1Progress, + WatchBlocksV1, + WatchBlocksV1BlockData, +} from "../generated/openapi/typescript-axios"; +import { Socket as SocketIoSocket } from "socket.io"; +import Web3 from "web3"; + +export interface IWatchBlocksV1EndpointConfiguration { + logLevel?: LogLevelDesc; + socket: SocketIoSocket; + web3: Web3; + options?: WatchBlocksV1Options; +} + +export class WatchBlocksV1Endpoint { + public static readonly CLASS_NAME = "WatchBlocksV1Endpoint"; + + private readonly log: Logger; + private readonly socket: SocketIoSocket< + Record void>, + Record void> + >; + private readonly web3: Web3; + private readonly isGetBlockData: boolean; + + public get className(): string { + return WatchBlocksV1Endpoint.CLASS_NAME; + } + + constructor(public readonly config: IWatchBlocksV1EndpointConfiguration) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(config, `${fnTag} arg options`); + Checks.truthy(config.web3, `${fnTag} arg options.web3`); + Checks.truthy(config.socket, `${fnTag} arg options.socket`); + + this.web3 = config.web3; + this.socket = config.socket; + this.isGetBlockData = config.options?.getBlockData == true; + + const level = this.config.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public async subscribe(): Promise { + const { socket, log, web3, isGetBlockData } = this; + log.debug(`${WatchBlocksV1.Subscribe} => ${socket.id}`); + + const sub = web3.eth.subscribe( + "newBlockHeaders", + async (ex, blockHeader) => { + log.debug("newBlockHeaders: Error=%o BlockHeader=%o", ex, blockHeader); + + if (ex) { + socket.emit(WatchBlocksV1.Error, ex.message); + sub.unsubscribe(); + } else if (blockHeader) { + let next: WatchBlocksV1Progress; + + if (isGetBlockData) { + const web3BlockData = await web3.eth.getBlock( + blockHeader.hash, + true, + ); + + next = { + // difficulty and totalDifficulty returned from the ledger are string, forcing typecast + blockData: (web3BlockData as unknown) as WatchBlocksV1BlockData, + }; + } else { + next = { blockHeader }; + } + + socket.emit(WatchBlocksV1.Next, next); + } + }, + ); + + log.debug("Subscribing to Web3 new block headers event..."); + + socket.on("disconnect", async (reason: string) => { + log.debug("WebSocket:disconnect reason=%o", reason); + sub.unsubscribe((ex: Error, success: boolean) => { + log.debug("Web3 unsubscribe success=%o, ex=%", success, ex); + }); + }); + + socket.on(WatchBlocksV1.Unsubscribe, () => { + log.debug(`${WatchBlocksV1.Unsubscribe}: unsubscribing Web3...`); + sub.unsubscribe((ex: Error, success: boolean) => { + log.debug("Web3 unsubscribe error=%o, success=%", ex, success); + }); + }); + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/scripts/get-quorum-connector-status.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/scripts/get-quorum-connector-status.ts new file mode 100644 index 0000000000..5b2d81ad11 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/scripts/get-quorum-connector-status.ts @@ -0,0 +1,113 @@ +#!/usr/bin/env node + +/** + * Simple command line tool to check ledger connection of running ethereum connector. + * Will try to get latest block from the ledger. + * + * Usage: + * After installing the connector package... + * `npm install -g` in it's dir or directly from NPM - `npm install @hyperledger/cactus-plugin-ledger-connector-ethereum` + * ...you can start the command line tool with npx: + * `npx cacti-ethereum-connector-status :` + * + * TODO: + * - Add healthcheck endpoint to ethereum connector and query it instead of reading the latest block. + */ + +import { + EthereumApiClient, + EthereumApiClientOptions, +} from "../main/typescript/public-api"; + +import minimist from "minimist"; +import path from "path"; +import axios from "axios"; +import chalk from "chalk"; + +type AuthOptions = { + apiKey?: string; + accessToken?: string; + username?: string; + password?: string; +}; + +async function main(url: string, auth: AuthOptions = {}) { + try { + console.log(`Check Ethereum connector ${url}...`); + const config = new EthereumApiClientOptions({ + basePath: url, + ...auth, + }); + const ethereumApiClient = new EthereumApiClient(config); + + // Get latest block + const connectorResponse = await ethereumApiClient.invokeWeb3EthMethodV1({ + methodName: "getBlock", + params: ["latest"], + }); + + // Check response + if ( + !connectorResponse || + !connectorResponse.data || + !connectorResponse.data.data || + connectorResponse.data.status !== 200 + ) { + console.log(connectorResponse.data); + throw new Error("Invalid response from the connector"); + } + + const blockData = connectorResponse.data.data; + console.log( + chalk.green(`OK - Latest block #${blockData.number} ${blockData.hash}`), + ); + } catch (error: unknown) { + let errorMessage = `Error: ${error}`; + if (axios.isAxiosError(error)) { + errorMessage = `${error.name}: ${error.message}`; + } + + console.error(chalk.red(errorMessage)); + process.exit(2); + } +} + +function showHelp() { + const scriptName = path.basename(__filename); + console.log( + chalk.yellow( + `Usage: ${scriptName} : [-h|--help] [--apiKey ] [--accessToken ] [--username --password ]`, + ), + ); + process.exit(1); +} + +if (require.main === module) { + const argv = minimist(process.argv.slice(2)); + if ( + argv["h"] || + argv["help"] || + argv["_"].length !== 1 || + (argv["username"] && !argv["password"]) || + (argv["password"] && !argv["username"]) + ) { + showHelp(); + } + const connectorUrl = argv["_"][0]; + + // Ensure valid URL was provided. + // Without it the connector request will hang indefinitely. + try { + new URL(connectorUrl); + } catch (err) { + console.error(chalk.red(err)); + showHelp(); + } + + main(connectorUrl, { + apiKey: argv["apiKey"], + accessToken: argv["accessToken"], + username: argv["username"], + password: argv["password"], + }); +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json b/packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json new file mode 100644 index 0000000000..799cbad7c7 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json @@ -0,0 +1,418 @@ +{ + "contractName": "HelloWorld", + "abi": [ + { + "inputs": [], + "name": "getName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sayHello", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "newName", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.7.2+commit.51b20bc0\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"getName\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sayHello\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"newName\",\"type\":\"string\"}],\"name\":\"setName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":\"HelloWorld\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":{\"keccak256\":\"0x0b78fa11f33f7936a80da194c49f04198e38947e3f98f3a7b765b4adb4c455c1\",\"urls\":[\"bzz-raw://12697aa12341c70ed7a411a27a17398dcb2d4336a14dac51845e2123acf174c7\",\"dweb:/ipfs/QmPhH1UbHtUeeen9W2qMDwEVVWAtVJSMN29Nch5q8Gax1D\"]}},\"version\":1}", + "bytecode": "60c0604052600d60808190526c4361707461696e43616374757360981b60a090815261002e9160009190610041565b5034801561003b57600080fd5b506100d4565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008257805160ff19168380011785556100af565b828001600101855582156100af579182015b828111156100af578251825591602001919060010190610094565b506100bb9291506100bf565b5090565b5b808211156100bb57600081556001016100c0565b61030f806100e36000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220965216915797db694e698c024753d560e1989f2aebb14463f9225b2297003b2c64736f6c63430007020033", + "deployedBytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220965216915797db694e698c024753d560e1989f2aebb14463f9225b2297003b2c64736f6c63430007020033", + "sourceMap": "463:37:0:-:0;439:322;463:37;;439:322;463:37;;;-1:-1:-1;;;463:37:0;;;;;;-1:-1:-1;;463:37:0;;:::i;:::-;;439:322;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;439:322:0;;;-1:-1:-1;439:322:0;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "439:322:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;683:76;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;683:76:0;;-1:-1:-1;683:76:0;;-1:-1:-1;;;;;683:76:0:i;:::-;;505:89;;;:::i;598:81::-;670:4;663:11;;;;;;;;-1:-1:-1;;663:11:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;638:13;;663:11;;670:4;;663:11;;670:4;663:11;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;:::o;683:76::-;740:14;;;;:4;;:14;;;;;:::i;:::-;;683:76;:::o;505:89::-;568:21;;;;;;;;;;;;-1:-1:-1;;;568:21:0;;;;505:89;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;", + "sourcePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", + "compiler": { + "name": "solc", + "version": "0.7.2+commit.51b20bc0" + }, + "networks": {}, + "ast": { + "absolutePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", + "exportedSymbols": { + "HelloWorld": [ + 31 + ] + }, + "id": 32, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + ">=", + "0.7", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "413:24:0" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 31, + "linearizedBaseContracts": [ + 31 + ], + "name": "HelloWorld", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": false, + "id": 4, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 31, + "src": "463:37:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string" + }, + "typeName": { + "id": 2, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "463:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": { + "hexValue": "4361707461696e436163747573", + "id": 3, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "485:15:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bdd2f21877c99489ddcc32737686677f40d460368c7982ce22ce4f72b41b0312", + "typeString": "literal_string \"CaptainCactus\"" + }, + "value": "CaptainCactus" + }, + "visibility": "private" + }, + { + "body": { + "id": 11, + "nodeType": "Block", + "src": "562:32:0", + "statements": [ + { + "expression": { + "hexValue": "48656c6c6f20576f726c6421", + "id": 9, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "575:14:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0", + "typeString": "literal_string \"Hello World!\"" + }, + "value": "Hello World!" + }, + "functionReturnParameters": 8, + "id": 10, + "nodeType": "Return", + "src": "568:21:0" + } + ] + }, + "functionSelector": "ef5fb05b", + "id": 12, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sayHello", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 5, + "nodeType": "ParameterList", + "parameters": [], + "src": "523:2:0" + }, + "returnParameters": { + "id": 8, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 7, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 12, + "src": "547:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 6, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "547:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "546:15:0" + }, + "scope": 31, + "src": "505:89:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 19, + "nodeType": "Block", + "src": "655:24:0", + "statements": [ + { + "expression": { + "id": 17, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "670:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "functionReturnParameters": 16, + "id": 18, + "nodeType": "Return", + "src": "663:11:0" + } + ] + }, + "functionSelector": "17d7de7c", + "id": 20, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 13, + "nodeType": "ParameterList", + "parameters": [], + "src": "614:2:0" + }, + "returnParameters": { + "id": 16, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 20, + "src": "638:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 14, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "638:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "637:15:0" + }, + "scope": 31, + "src": "598:81:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 29, + "nodeType": "Block", + "src": "732:27:0", + "statements": [ + { + "expression": { + "id": 27, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 25, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "740:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 26, + "name": "newName", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 22, + "src": "747:7:0", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + "src": "740:14:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "id": 28, + "nodeType": "ExpressionStatement", + "src": "740:14:0" + } + ] + }, + "functionSelector": "c47f0027", + "id": 30, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 23, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 22, + "mutability": "mutable", + "name": "newName", + "nodeType": "VariableDeclaration", + "scope": 30, + "src": "700:21:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 21, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "700:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "699:23:0" + }, + "returnParameters": { + "id": 24, + "nodeType": "ParameterList", + "parameters": [], + "src": "732:0:0" + }, + "scope": 31, + "src": "683:76:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 32, + "src": "439:322:0" + } + ], + "src": "413:349:0" + }, + "functionHashes": { + "getName()": "17d7de7c", + "sayHello()": "ef5fb05b", + "setName(string)": "c47f0027" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "156600", + "executionCost": "infinite", + "totalCost": "infinite" + }, + "external": { + "getName()": "infinite", + "sayHello()": "infinite", + "setName(string)": "infinite" + } + } +} \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol b/packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol new file mode 100644 index 0000000000..befd843936 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol @@ -0,0 +1,26 @@ +// ***************************************************************************** +// IMPORTANT: If you update this code then make sure to recompile +// it and update the .json file as well so that they +// remain in sync for consistent test executions. +// With that said, there shouldn't be any reason to recompile this, like ever... +// ***************************************************************************** + +pragma solidity >=0.7.0; + +contract HelloWorld { + string private name = "CaptainCactus"; + + function sayHello () public pure returns (string memory) { + return 'Hello World!'; + } + + function getName() public view returns (string memory) + { + return name; + } + + function setName(string memory newName) public + { + name = newName; + } +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts new file mode 100644 index 0000000000..a65ea58247 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts @@ -0,0 +1,5 @@ +import * as apiSurface from "../../../main/typescript/public-api"; + +test("Library can be loaded", async () => { + expect(apiSurface).toBeTruthy(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts new file mode 100644 index 0000000000..cab629aa34 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts @@ -0,0 +1,323 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, + DeployContractSolidityBytecodeJsonObjectV1Request, + InvokeContractJsonObjectV1Request, +} from "../../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +const testCase = "Ethereum Ledger Connector Plugin"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Server as SocketIoServer } from "socket.io"; + +import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core"; +import OAS from "../../../../../../main/json/openapi.json"; + +const logLevel: LogLevelDesc = "INFO"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await installOpenapiValidationMiddleware({ + logLevel, + app: expressApp, + apiSpec: OAS, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + const fDeploy = "deployContractSolBytecodeJsonObjectV1"; + const fInvoke = "invokeContractV1NoKeychain"; + const cOk = "without bad request error"; + const cWithoutParams = "not sending all required parameters"; + const cInvalidParams = "sending invalid parameters"; + + let contractAddress: string; + + test(`${testCase} - ${fDeploy} - ${cOk}`, async (t2: Test) => { + const parameters = { + contractAddress, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + // bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }; + const res = await apiClient.deployContractSolBytecodeJsonObjectV1( + parameters as DeployContractSolidityBytecodeJsonObjectV1Request, + ); + t2.ok(res, "Contract deployed successfully"); + t2.ok(res.data); + t2.equal( + res.status, + 200, + `Endpoint ${fDeploy}: response.status === 200 OK`, + ); + + contractAddress = res.data.transactionReceipt.contractAddress as string; + + t2.end(); + }); + + test(`${testCase} - ${fInvoke} - ${cOk}`, async (t2: Test) => { + const parameters = { + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [`DrCactus${uuidV4()}`], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }; + const res = await apiClient.invokeContractV1NoKeychain( + parameters as InvokeContractJsonObjectV1Request, + ); + t2.ok(res, "Contract invoked successfully"); + t2.ok(res.data); + t2.equal( + res.status, + 200, + `Endpoint ${fInvoke}: response.status === 200 OK`, + ); + + t2.end(); + }); + + test(`${testCase} - ${fDeploy} - ${cWithoutParams}`, async (t2: Test) => { + try { + const parameters = { + contractAddress, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + }; + await apiClient.deployContractSolBytecodeJsonObjectV1( + (parameters as any) as DeployContractSolidityBytecodeJsonObjectV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeploy} without required contractJSON and bytecode: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("contractJSON"), + "Rejected because contractJSON is required", + ); + t2.notOk(fields.includes("gas"), "gas is not required"); + } + + t2.end(); + }); + + test(`${testCase} - ${fDeploy} - ${cInvalidParams}`, async (t2: Test) => { + try { + const parameters = { + contractAddress, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + contractJSON: HelloWorldContractJson, + fake: 4, + }; + await apiClient.deployContractSolBytecodeJsonObjectV1( + (parameters as any) as DeployContractSolidityBytecodeJsonObjectV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeploy} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + test(`${testCase} - ${fInvoke} - ${cWithoutParams}`, async (t2: Test) => { + try { + const parameters = { + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [`DrCactus${uuidV4()}`], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }; + await apiClient.invokeContractV1NoKeychain( + (parameters as any) as InvokeContractJsonObjectV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fInvoke} without required contractJSON and methodName: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("contractJSON"), + "Rejected because contractJSON is required", + ); + t2.notOk(fields.includes("nonce"), "nonce is not required"); + } + + t2.end(); + }); + + test(`${testCase} - ${fInvoke} - ${cInvalidParams}`, async (t2: Test) => { + try { + const parameters = { + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [`DrCactus${uuidV4()}`], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + fake: 4, + }; + await apiClient.invokeContractV1NoKeychain( + (parameters as any) as InvokeContractJsonObjectV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fInvoke} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts new file mode 100644 index 0000000000..41f43ae322 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/openapi/openapi-validation.test.ts @@ -0,0 +1,427 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidV4 } from "uuid"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import HelloWorldContractJson from "../../../../../solidity/hello-world-contract/HelloWorld.json"; +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, + DeployContractSolidityBytecodeV1Request, + InvokeContractV1Request, + RunTransactionRequest, +} from "../../../../../../main/typescript/public-api"; +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { AddressInfo } from "net"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { Server as SocketIoServer } from "socket.io"; + +import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core"; +import OAS from "../../../../../../main/json/openapi.json"; + +const logLevel: LogLevelDesc = "INFO"; +const testCase = "Ethereum API"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + // create the test ethereumTestLedger + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + + const ledgerOptions = { containerImageVersion }; + const ethereumTestLedger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ethereumTestLedger.stop(); + await ethereumTestLedger.destroy(); + }); + await ethereumTestLedger.start(); + + // retrieve host to which connector will attack + const rpcApiHttpHost = await ethereumTestLedger.getRpcApiHttpHost(); + + // obtain accounts from genesis + const ethereumGenesisOptions: IQuorumGenesisOptions = await ethereumTestLedger.getGenesisJsObject(); + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + // create a new account + const testEthAccount = await ethereumTestLedger.createEthTestAccount(); + + // create the keychain plugin for recently created account + const keychainEntryKey = uuidV4(); + const keychainId = uuidV4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidV4(), + keychainId, + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + + // create a plugin registry with the recently created keychain plugin + const pluginRegistry = new PluginRegistry({ + plugins: [keychainPlugin], + }); + + // create the connector including test ledger host and plugin registry + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry, + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await installOpenapiValidationMiddleware({ + logLevel, + app: expressApp, + apiSpec: OAS, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + const fDeploy = "apiV1EthereumDeployContractSolidityBytecode"; + const fInvoke = "apiV1EthereumInvokeContract"; + const fRun = "apiV1EthereumRunTransaction"; + const cOk = "without bad request error"; + const cWithoutParams = "not sending all required parameters"; + const cInvalidParams = "sending invalid parameters"; + + let contractAddress: string; + + test(`${testCase} - ${fDeploy} - ${cOk}`, async (t2: Test) => { + const parameters = { + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + }; + const res = await apiClient.deployContractSolBytecodeV1( + parameters as DeployContractSolidityBytecodeV1Request, + ); + t2.ok(res, "Contract deployed successfully"); + t2.ok(res.data); + t2.equal( + res.status, + 200, + `Endpoint ${fDeploy}: response.status === 200 OK`, + ); + + contractAddress = res.data.transactionReceipt.contractAddress as string; + + t2.end(); + }); + + test(`${testCase} - ${fDeploy} - ${cWithoutParams}`, async (t2: Test) => { + try { + const parameters = { + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }; + await apiClient.deployContractSolBytecodeV1( + parameters as DeployContractSolidityBytecodeV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeploy} without required contractName and bytecode: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("contractName"), + "Rejected because contractName is required", + ); + t2.notOk(fields.includes("gas"), "gas is not required"); + } + + t2.end(); + }); + + test(`${testCase} - ${fDeploy} - ${cInvalidParams}`, async (t2: Test) => { + try { + const parameters = { + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + fake: 4, + }; + await apiClient.deployContractSolBytecodeV1( + parameters as DeployContractSolidityBytecodeV1Request, + ); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fDeploy} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + test(`${testCase} - ${fInvoke} - ${cOk}`, async (t2: Test) => { + const parameters = { + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [`DrCactus${uuidV4()}`], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }; + const res = await apiClient.invokeContractV1( + parameters as InvokeContractV1Request, + ); + t2.ok(res, "Contract invoked successfully"); + t2.ok(res.data); + t2.equal( + res.status, + 200, + `Endpoint ${fInvoke}: response.status === 200 OK`, + ); + + t2.end(); + }); + + test(`${testCase} - ${fInvoke} - ${cWithoutParams}`, async (t2: Test) => { + try { + const parameters = { + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + params: [`DrCactus${uuidV4()}`], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }; + await apiClient.invokeContractV1(parameters as InvokeContractV1Request); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fInvoke} without required methodName: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("methodName"), + "Rejected because methodName is required", + ); + t2.notOk(fields.includes("nonce"), "nonce is not required"); + } + + t2.end(); + }); + + test(`${testCase} - ${fInvoke} - ${cInvalidParams}`, async (t2: Test) => { + try { + const parameters = { + contractName: HelloWorldContractJson.contractName, + contractAddress, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [`DrCactus${uuidV4()}`], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + fake: 4, + }; + await apiClient.invokeContractV1(parameters as InvokeContractV1Request); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fInvoke} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: any) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + test(`${testCase} - ${fRun} - ${cOk}`, async (t2: Test) => { + const parameters = { + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }; + const res = await apiClient.runTransactionV1( + parameters as RunTransactionRequest, + ); + t2.ok(res, "Transaction executed successfully"); + t2.ok(res.data); + t2.equal(res.status, 200, `Endpoint ${fRun}: response.status === 200 OK`); + + t2.end(); + }); + + test(`${testCase} - ${fRun} - ${cWithoutParams}`, async (t2: Test) => { + try { + const parameters = { + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }; + await apiClient.runTransactionV1(parameters as RunTransactionRequest); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fRun} without required transactionConfig: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: { path: string }) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("transactionConfig"), + "Rejected because transactionConfig is required", + ); + t2.notOk(fields.includes("timeoutMs"), "timeoutMs is not required"); + } + + t2.end(); + }); + + test(`${testCase} - ${fRun} - ${cInvalidParams}`, async (t2: Test) => { + try { + const parameters = { + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + fake: 4, + }; + await apiClient.runTransactionV1(parameters as RunTransactionRequest); + } catch (e) { + t2.equal( + e.response.status, + 400, + `Endpoint ${fRun} with fake=4: response.status === 400 OK`, + ); + const fields = e.response.data.map((param: { path: string }) => + param.path.replace(".body.", ""), + ); + t2.ok( + fields.includes("fake"), + "Rejected because fake is not a valid parameter", + ); + } + + t2.end(); + }); + + t.end(); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning did not throw OK"); + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts new file mode 100644 index 0000000000..73ccffc596 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object-endpoints.test.ts @@ -0,0 +1,390 @@ +import test, { Test } from "tape-promise/tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const testCase = "Ethereum Ledger Connector Plugin"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { Server as SocketIoServer } from "socket.io"; + +const logLevel: LogLevelDesc = "INFO"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + // Instantiate connector with the keychain plugin that already has the + // private key we want to use for one of our tests + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await apiClient.deployContractSolBytecodeJsonObjectV1({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.data.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.data.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.data.transactionReceipt + .contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const invokeOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(invokeOut.data.callOutput, "sayHello() output is truthy"); + t2.true( + typeof invokeOut.data.callOutput === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok( + getNameOut.data.success, + `getName() SEND invocation produced receipt OK`, + ); + + const getNameRes = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameRes.data.callOutput, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + // const { callOutput: getNameOut } = + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + // gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut.data.callOutput, + newName, + `getName() output reflects the update OK`, + ); + + const getNameOut2 = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + //gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + test("get prometheus exporter metrics", async (t2: Test) => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '"} 3'; + t2.ok(res); + t2.ok(res.data); + t2.equal(res.status, 200); + t2.true( + res.data.includes(promMetricsOutput), + "Total Transaction Count of 3 recorded as expected. RESULT OK.", + ); + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts new file mode 100644 index 0000000000..0ceaaeb107 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts @@ -0,0 +1,381 @@ +import test, { Test } from "tape-promise/tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const testCase = "Ethereum Ledger Connector Plugin"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { Server as SocketIoServer } from "socket.io"; + +const logLevel: LogLevelDesc = "INFO"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + // Instantiate connector with the keychain plugin that already has the + // private key we want to use for one of our tests + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + test("get prometheus exporter metrics", async (t2: Test) => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '"} 3'; + t2.ok(res); + t2.ok(res.data); + t2.equal(res.status, 200); + t2.true( + res.data.includes(promMetricsOutput), + "Total Transaction Count of 3 recorded as expected. RESULT OK.", + ); + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts new file mode 100644 index 0000000000..4e9240498e --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts @@ -0,0 +1,469 @@ +import Web3 from "web3"; +import { Account } from "web3-core"; +import { v4 as uuidV4 } from "uuid"; +import "jest-extended"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialCactusKeychainRef, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { Server as SocketIoServer } from "socket.io"; + +const testCase = "Ethereum Ledger Connector Plugin"; + +describe(testCase, () => { + const logLevel: LogLevelDesc = "INFO"; + const contractName = "HelloWorld"; + const keychainEntryKey = uuidV4(); + let firstHighNetWorthAccount: string, + testEthAccount: Account, + web3: Web3, + addressInfo, + address: string, + port: number, + contractAddress: string, + apiHost, + apiConfig, + ledger: QuorumTestLedger, + apiClient: EthereumApi, + connector: PluginLedgerConnectorEthereum, + rpcApiHttpHost: string, + keychainPlugin: PluginKeychainMemory; + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + + beforeAll(async () => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + const containerImageName = "hyperledger/cactus-quorum-all-in-one"; + const ledgerOptions = { containerImageName, containerImageVersion }; + ledger = new QuorumTestLedger(ledgerOptions); + await ledger.start(); + + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + + expect(ethereumGenesisOptions).toBeTruthy(); + expect(ethereumGenesisOptions.alloc).toBeTruthy(); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + [firstHighNetWorthAccount] = highNetWorthAccounts; + }); + + afterAll(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + + afterAll(async () => await Servers.shutdown(server)); + + afterAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + beforeAll(async () => { + await ledger.start(); + + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + ({ address, port } = addressInfo); + apiHost = `http://${address}:${port}`; + apiConfig = new Configuration({ basePath: apiHost }); + apiClient = new EthereumApi(apiConfig); + rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(uuidV4()); + + const keychainEntryValue = testEthAccount.privateKey; + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidV4(), + keychainId: uuidV4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + connector = new PluginLedgerConnectorEthereum({ + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + }); + + test(testCase, async () => { + // Instantiate connector with the keychain plugin that already has the + // private key we want to use for one of our tests + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toBe(10e9); + }); + + test("deploys contract via .json file", async () => { + const deployOut = await connector.deployContract({ + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + expect(typeof contractAddress).toBe("string"); + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + keychainId: keychainPlugin.getKeychainId(), + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + }); + expect(helloMsg).toBeTruthy(); + expect(typeof helloMsg).toBe("string"); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async () => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + fail("Expected getContractInfoKeychain call to fail but it succeeded."); + } catch (error) { + expect(error).not.toEqual("Nonce too low"); + } + + const getNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(getNameOut.success).toBeTruthy(); + + const { callOutput: getNameOut2 } = await connector.getContractInfoKeychain( + { + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }, + ); + expect(getNameOut2).toBe(newName); + }); + + test("invoke Web3SigningCredentialType.NONE", async () => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + expect(balance2).toBeTruthy(); + expect(parseInt(balance2, 10)).toBe(10e6); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async () => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + fail("Expected getContractInfoKeychain call to fail but it succeeded."); + } catch (error) { + expect(error).not.toEqual("Nonce too low"); + } + + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut).toBe(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut2).toBeTruthy(); + }); + + test("invoke Web3SigningCredentialType.CactusKeychainRef", async () => { + const newName = `DrCactus${uuidV4()}`; + + const web3SigningCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CactusKeychainRef, + }; + + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential, + nonce: 3, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 3, + }); + fail("Expected getContractInfoKeychain call to fail but it succeeded."); + } catch (error) { + expect(error).not.toEqual("Nonce too low"); + } + + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut).toContain(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut2).toBeTruthy(); + }); + + test("get prometheus exporter metrics", async () => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '"} 5'; + expect(res); + expect(res.data); + expect(res.status).toEqual(200); + expect(res.data).toContain(promMetricsOutput); + }); + + test("deploys contract via .json file with constructorArgs", async () => { + const deployOut = await connector.deployContract({ + contractName: HelloWorldContractJson.contractName, + contractJSON: HelloWorldContractJson, + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + constructorArgs: ["Test Arg"], + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + expect(contractAddress).toBeString(); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts new file mode 100644 index 0000000000..94a9201924 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object-endpoints.test.ts @@ -0,0 +1,357 @@ +import test, { Test } from "tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { Server as SocketIoServer } from "socket.io"; + +const logLevel: LogLevelDesc = "INFO"; + +test("Ethereum Ledger Connector Plugin", async (t: Test) => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await apiClient.deployContractSolBytecodeJsonObjectV1({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.data.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.data.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.data.transactionReceipt + .contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const helloMsgRes = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsgRes.data.callOutput, "sayHello() output is truthy"); + t2.true( + typeof helloMsgRes.data.callOutput === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok( + getNameOut.data.success, + `getName() SEND invocation produced receipt OK`, + ); + + const getNameOut2 = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2.data.callOutput, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut.data.callOutput, + newName, + `getName() output reflects the update OK`, + ); + + const getNameOut2 = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2.data, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts new file mode 100644 index 0000000000..d1de4ee3d4 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts @@ -0,0 +1,310 @@ +import test, { Test } from "tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const logLevel: LogLevelDesc = "INFO"; + +test("Ethereum Ledger Connector Plugin", async (t: Test) => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract.test.ts new file mode 100644 index 0000000000..3f1877786b --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v2.3.0-invoke-contract.test.ts @@ -0,0 +1,380 @@ +import Web3 from "web3"; +import "jest-extended"; +import { Account } from "web3-core"; +import { v4 as uuidV4 } from "uuid"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialCactusKeychainRef, + Web3SigningCredentialType, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const logLevel: LogLevelDesc = "INFO"; +const contractName = "HelloWorld"; +const testcase = ""; + +describe(testcase, () => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + const keychainEntryKey = uuidV4(); + + let contractAddress: string, + connector: PluginLedgerConnectorEthereum, + rpcApiHttpHost: string, + web3: Web3, + keychainPlugin: PluginKeychainMemory, + testEthAccount: Account, + ethereumGenesisOptions: IQuorumGenesisOptions, + highNetWorthAccounts: string[], + firstHighNetWorthAccount: string; + + afterAll(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + + beforeAll(async () => { + await ledger.start(); + rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(uuidV4()); + + const keychainEntryValue = testEthAccount.privateKey; + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidV4(), + keychainId: uuidV4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + ethereumGenesisOptions = await ledger.getGenesisJsObject(); + highNetWorthAccounts = Object.keys(ethereumGenesisOptions.alloc).filter( + (address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }, + ); + [firstHighNetWorthAccount] = highNetWorthAccounts; + + connector = new PluginLedgerConnectorEthereum({ + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + }); + + test("Ethereum Ledger Connector Plugin", async () => { + expect(ethereumGenesisOptions).toBeTruthy(); + expect(ethereumGenesisOptions.alloc).toBeTruthy(); + + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + // Instantiate connector with the keychain plugin that already has the + // private key we want to use for one of our tests + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toEqual(10e9); + }); + + test("deploys contract via .json file", async () => { + const deployOut = await connector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: HelloWorldContractJson.contractName, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + expect(typeof contractAddress).toBeString(); + + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(helloMsg).toBeTruthy(); + expect(typeof helloMsg).toBeString(); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async () => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + fail( + "PluginLedgerConnectorEthereum.invokeContract failed to throw error", + ); + } catch (error) { + expect(error).not.toBe("Nonce too low"); + } + + const getNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(getNameOut.success).toBeTruthy(); + + const { callOutput: getNameOut2 } = await connector.getContractInfoKeychain( + { + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }, + ); + expect(getNameOut2).toEqual(newName); + }); + + test("invoke Web3SigningCredentialType.NONE", async () => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + expect(balance2).toBeTruthy(); + expect(parseInt(balance2, 10)).toEqual(10e6); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async () => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + fail( + "PluginLedgerConnectorEthereum.invokeContract failed to throw error", + ); + } catch (error) { + expect(error).not.toBe("Nonce too low"); + } + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut).toEqual(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut2).toBeTruthy(); + }); + + test("invoke Web3SigningCredentialType.CactusKeychainRef", async () => { + const newName = `DrCactus${uuidV4()}`; + + const web3SigningCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CactusKeychainRef, + }; + + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential, + nonce: 3, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 3, + }); + fail( + "PluginLedgerConnectorEthereum.invokeContract failed to throw error", + ); + } catch (error) { + expect(error).not.toBe("Nonce too low"); + } + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut).toEqual(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut2).toBeTruthy(); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts new file mode 100644 index 0000000000..9d72d756fb --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object-endpoints.test.ts @@ -0,0 +1,386 @@ +import test, { Test } from "tape-promise/tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +const testCase = "Ethereum Ledger Connector Plugin"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Server as SocketIoServer } from "socket.io"; + +const logLevel: LogLevelDesc = "INFO"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await apiClient.deployContractSolBytecodeJsonObjectV1({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.data.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.data.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.data.transactionReceipt + .contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const helloMsg = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg.data.callOutput, "sayHello() output is truthy"); + t2.true( + typeof helloMsg.data.callOutput === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut.data, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok( + getNameOut.data.success, + `getName() SEND invocation produced receipt OK`, + ); + + const getNameOut2 = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2.data.callOutput, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount(testEthAccount.address); + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut.data, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + // gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + // gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut.data.callOutput, + newName, + `getName() output reflects the update OK`, + ); + + const getNameOut2 = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2.data, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + test("get prometheus exporter metrics", async (t2: Test) => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '"} 3'; + t2.ok(res); + t2.ok(res.data); + t2.equal(res.status, 200); + t2.true( + res.data.includes(promMetricsOutput), + "Total Transaction Count of 3 recorded as expected. RESULT OK.", + ); + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts new file mode 100644 index 0000000000..11389ef369 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts @@ -0,0 +1,378 @@ +import test, { Test } from "tape-promise/tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +const testCase = "Ethereum Ledger Connector Plugin"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Server as SocketIoServer } from "socket.io"; + +const logLevel: LogLevelDesc = "INFO"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount(testEthAccount.address); + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + test("get prometheus exporter metrics", async (t2: Test) => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '"} 3'; + t2.ok(res); + t2.ok(res.data); + t2.equal(res.status, 200); + t2.true( + res.data.includes(promMetricsOutput), + "Total Transaction Count of 3 recorded as expected. RESULT OK.", + ); + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts new file mode 100644 index 0000000000..3e887f0ee7 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts @@ -0,0 +1,441 @@ +import Web3 from "web3"; +import { Account } from "web3-core"; +import { v4 as uuidV4 } from "uuid"; +import "jest-extended"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_ETHEREUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialCactusKeychainRef, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { Server as SocketIoServer } from "socket.io"; + +const testCase = "Ethereum Ledger Connector Plugin"; + +describe(testCase, () => { + const logLevel: LogLevelDesc = "INFO"; + const contractName = "HelloWorld"; + + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + let addressInfo, + rpcApiHttpHost: string, + ethereumGenesisOptions: IQuorumGenesisOptions, + connector: PluginLedgerConnectorEthereum, + contractAddress: string, + address: string, + port: number, + apiHost, + apiConfig, + apiClient: EthereumApi, + web3: Web3, + testEthAccount: Account, + keychainEntryKey: string, + keychainEntryValue: string, + keychainPlugin: PluginKeychainMemory, + firstHighNetWorthAccount: string; + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + afterAll(async () => await Servers.shutdown(server)); + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + afterAll(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + afterAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + + beforeAll(async () => { + await ledger.start(); + rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + expect(rpcApiHttpHost).toBeString(); + + ethereumGenesisOptions = await ledger.getGenesisJsObject(); + expect(ethereumGenesisOptions).toBeTruthy(); + expect(ethereumGenesisOptions.alloc).toBeTruthy(); + + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(uuidV4()); + + keychainEntryKey = uuidV4(); + keychainEntryValue = testEthAccount.privateKey; + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidV4(), + keychainId: uuidV4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + connector = new PluginLedgerConnectorEthereum({ + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + ({ address, port } = addressInfo); + apiHost = `http://${address}:${port}`; + apiConfig = new Configuration({ basePath: apiHost }); + apiClient = new EthereumApi(apiConfig); + }); + test("Bootsrap test ETH account with funds from genesis", async () => { + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + [firstHighNetWorthAccount] = highNetWorthAccounts; + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + // Instantiate connector with the keychain plugin that already has the + // private key we want to use for one of our tests + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toBe(10e9); + }); + + test("deploys contract via .json file", async () => { + const deployOut = await connector.deployContract({ + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + expect(typeof contractAddress).toBe("string"); + + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + keychainId: keychainPlugin.getKeychainId(), + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(helloMsg).toBeTruthy(); + expect(typeof contractAddress).toBe("string"); + expect(typeof helloMsg).toBe("string"); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async () => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + fail( + "PluginLedgerConnectorEthereum.invokeContract failed to throw error", + ); + } catch (error) { + expect(error).not.toBe("Nonce too low"); + } + + const getNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(getNameOut.success).toBeTruthy(); + + const { callOutput: getNameOut2 } = await connector.getContractInfoKeychain( + { + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }, + ); + expect(getNameOut2).toEqual(newName); + }); + + test("invoke Web3SigningCredentialType.NONE", async () => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + expect(balance2).toBeTruthy(); + expect(parseInt(balance2, 10)).toEqual(10e6); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async () => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + fail("It should not reach here"); + } catch (error) { + expect(error).not.toBe("Nonce too low"); + } + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut).toEqual(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut2).toBeTruthy(); + }); + + test("invoke Web3SigningCredentialType.CactusKeychainRef", async () => { + const newName = `DrCactus${uuidV4()}`; + + const web3SigningCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CactusKeychainRef, + }; + + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential, + nonce: 3, + }); + expect(setNameOut).toBeTruthy(); + + try { + await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 3, + }); + fail("it should not reach here"); + } catch (error) { + expect(error).not.toBe("Nonce too low"); + } + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut).toEqual(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut2).toBeTruthy(); + }); + + test("get prometheus exporter metrics", async () => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_ETHEREUM_TOTAL_TX_COUNT + + '"} 5'; + expect(res).toBeTruthy(); + expect(res.data).toBeTruthy(); + expect(res.status).toEqual(200); + expect(res.data).toContain(promMetricsOutput); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts new file mode 100644 index 0000000000..a742872688 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object-endpoints.test.ts @@ -0,0 +1,352 @@ +import test, { Test } from "tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Configuration, Constants } from "@hyperledger/cactus-core-api"; +import { Server as SocketIoServer } from "socket.io"; + +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; + +const logLevel: LogLevelDesc = "INFO"; + +test("Ethereum Ledger Connector Plugin", async (t: Test) => { + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new EthereumApi(apiConfig); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await apiClient.deployContractSolBytecodeJsonObjectV1({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.data.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.data.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.data.transactionReceipt + .contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const helloMsg = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg.data.callOutput, "sayHello() output is truthy"); + t2.true( + typeof helloMsg.data.callOutput === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut.data, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok( + getNameOut.data.success, + `getName() SEND invocation produced receipt OK`, + ); + + const getNameOut2 = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2.data.callOutput, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount(testEthAccount.address); + const setNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut.data, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.data.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const getNameOut = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut.data.callOutput, + newName, + `getName() output reflects the update OK`, + ); + + const getNameOut2 = await apiClient.invokeContractV1NoKeychain({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2.data, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts new file mode 100644 index 0000000000..245ced94c2 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts @@ -0,0 +1,306 @@ +import test, { Test } from "tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const logLevel: LogLevelDesc = "INFO"; + +test("Ethereum Ledger Connector Plugin", async (t: Test) => { + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(ethereumGenesisOptions); + t.ok(ethereumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount( + firstHighNetWorthAccount, + ); + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const txCount = await web3.eth.getTransactionCount(testEthAccount.address); + const setNameOut = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: txCount, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract.test.ts new file mode 100644 index 0000000000..17a5c76c35 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-contract.test.ts @@ -0,0 +1,365 @@ +import "jest-extended"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialCactusKeychainRef, + Web3SigningCredentialType, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const logLevel: LogLevelDesc = "INFO"; +const contractName = "HelloWorld"; +const testCase = "Ethereum Ledger Connector Plugin"; + +describe(testCase, () => { + afterAll(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + const ledgerOptions = { containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + + test(testCase, async () => { + await ledger.start(); + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + expect(ethereumGenesisOptions).toBeTruthy(); + expect(ethereumGenesisOptions.alloc).toBeTruthy(); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + const keychainEntryKey = uuidV4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidV4(), + keychainId: uuidV4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + // Instantiate connector with the keychain plugin that already has the + // private key we want to use for one of our tests + const connector: PluginLedgerConnectorEthereum = new PluginLedgerConnectorEthereum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }, + ); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toEqual(10e9); + let contractAddress: string; + + { + const deployOut = await connector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: HelloWorldContractJson.contractName, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + expect(typeof contractAddress === "string").toBeTrue(); + + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(helloMsg).toBeTruthy(); + expect(typeof helloMsg === "string").toBeTrue(); + } + + { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + expect(setNameOut).toBeTruthy(); + + try { + const setNameOutInvalid = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + expect(setNameOutInvalid.transactionReceipt).toBeFalsy(); + } catch (error) { + expect(error.message).toMatch(/nonce too low/); + } + + const getNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(getNameOut.success).toBeTruthy(); + + const { + callOutput: getNameOut2, + } = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(getNameOut2).toEqual(newName); + } + + { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + expect(balance2).toBeTruthy(); + expect(parseInt(balance2, 10)).toEqual(10e6); + } + + { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + expect(setNameOut).toBeTruthy(); + + try { + const setNameOutInvalid = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + expect(setNameOutInvalid.transactionReceipt).toBeFalsy(); + } catch (error) { + expect(error.message).toMatch(/nonce too low/); + } + const { + callOutput: getNameOut, + } = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut).toEqual(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + expect(getNameOut2).toBeTruthy(); + } + + { + const newName = `DrCactus${uuidV4()}`; + + const web3SigningCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CactusKeychainRef, + }; + + const setNameOut = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential, + nonce: 3, + }); + expect(setNameOut).toBeTruthy(); + + try { + const setNameOutInvalid = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 3, + }); + expect(setNameOutInvalid.transactionReceipt).toBeFalsy(); + } catch (error) { + expect(error.message).toMatch(/nonce too low/); + } + const { + callOutput: getNameOut, + } = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut).toEqual(newName); + + const getNameOut2 = await connector.getContractInfoKeychain({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential, + }); + expect(getNameOut2).toBeTruthy(); + } + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-contract-v1.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-contract-v1.test.ts new file mode 100644 index 0000000000..a8904ed1d5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-contract-v1.test.ts @@ -0,0 +1,203 @@ +/* + * Copyright 2020-2022 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const testLogLevel = "info"; +const sutLogLevel = "info"; + +const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + +import "jest-extended"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + EthContractInvocationWeb3Method, + InvokeRawWeb3EthContractV1Request, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, +} from "../../../../../main/typescript/index"; +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import { AbiItem } from "web3-utils"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +// Unit Test logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "v21.4.1-invoke-web3-contract-v1.test", + level: testLogLevel, +}); +log.info("Test started"); + +describe("invokeRawWeb3EthContract Tests", () => { + let ethereumTestLedger: QuorumTestLedger; + let connector: PluginLedgerConnectorEthereum; + let firstHighNetWorthAccount: string; + let contractAbi: AbiItem[]; + let contractAddress: string; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + + log.info("Start QuorumTestLedger..."); + log.debug("Ethereum version:", containerImageVersion); + ethereumTestLedger = new QuorumTestLedger({ + containerImageVersion, + }); + await ethereumTestLedger.start(); + + log.info("Get highNetWorthAccounts..."); + const ethereumGenesisOptions: IQuorumGenesisOptions = await ethereumTestLedger.getGenesisJsObject(); + expect(ethereumGenesisOptions).toBeTruthy(); + expect(ethereumGenesisOptions.alloc).toBeTruthy(); + + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + [firstHighNetWorthAccount] = highNetWorthAccounts; + + const rpcApiHttpHost = await ethereumTestLedger.getRpcApiHttpHost(); + log.debug("rpcApiHttpHost:", rpcApiHttpHost); + + log.info("Create PluginKeychainMemory..."); + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + logLevel: sutLogLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + + log.info("Create PluginLedgerConnectorEthereum..."); + connector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: rpcApiHttpHost, + logLevel: sutLogLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + log.info("Deploy contract to interact with..."); + const deployOut = await connector.deployContract({ + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + expect(deployOut.transactionReceipt.status).toBeTrue(); + + contractAbi = HelloWorldContractJson.abi as AbiItem[]; + contractAddress = deployOut.transactionReceipt.contractAddress as string; + }); + + afterAll(async () => { + log.info("Shutdown connector"); + await connector.shutdown(); + + log.info("Stop and destroy the test ledger..."); + await ethereumTestLedger.stop(); + await ethereumTestLedger.destroy(); + + log.info("Prune docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + }); + + test("invokeRawWeb3EthContract send and call to valid contract works correctly", async () => { + const newName = "EthereumCactus"; + + // 1. Set new value (send) + const sendInvocationArgs = { + from: firstHighNetWorthAccount, + }; + + const sendInvokeArgs: InvokeRawWeb3EthContractV1Request = { + abi: contractAbi, + address: contractAddress, + invocationType: EthContractInvocationWeb3Method.Send, + invocationParams: sendInvocationArgs, + contractMethod: "setName", + contractMethodArgs: [newName], + }; + + const resultsSend = await connector.invokeRawWeb3EthContract( + sendInvokeArgs, + ); + expect(resultsSend).toBeTruthy(); + expect(resultsSend.status).toBeTrue(); + + // // 2. Get new, updated value (call) + const callInvokeArgs: InvokeRawWeb3EthContractV1Request = { + abi: contractAbi, + address: contractAddress, + invocationType: EthContractInvocationWeb3Method.Call, + contractMethod: "getName", + }; + + const resultsCall = await connector.invokeRawWeb3EthContract( + callInvokeArgs, + ); + expect(resultsCall).toBeTruthy(); + expect(resultsCall).toEqual(newName); + }); + + test("invokeRawWeb3EthContract throws error when called on wrong contract", async () => { + const callInvokeArgs: InvokeRawWeb3EthContractV1Request = { + abi: contractAbi, + address: "0x0321", + invocationType: EthContractInvocationWeb3Method.Call, + contractMethod: "getName", + }; + + await expect(connector.invokeRawWeb3EthContract(callInvokeArgs)).toReject(); + }); + + test("invokeRawWeb3EthContract throws error when requested wrong invocation method", async () => { + const callInvokeArgs: InvokeRawWeb3EthContractV1Request = { + abi: contractAbi, + address: contractAddress, + invocationType: "foo" as EthContractInvocationWeb3Method, + contractMethod: "getName", + }; + + await expect(connector.invokeRawWeb3EthContract(callInvokeArgs)).toReject(); + }); + + test("invokeRawWeb3EthContract throws error when called non existent contract method", async () => { + const callInvokeArgs: InvokeRawWeb3EthContractV1Request = { + abi: contractAbi, + address: contractAddress, + invocationType: EthContractInvocationWeb3Method.Call, + contractMethod: "nonExistingFoo", + }; + + await expect(connector.invokeRawWeb3EthContract(callInvokeArgs)).toReject(); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-method-v1.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-method-v1.test.ts new file mode 100644 index 0000000000..70b75dfd5f --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/v21.4.1-invoke-web3-method-v1.test.ts @@ -0,0 +1,157 @@ +/* + * Copyright 2020-2022 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const testLogLevel = "info"; +const sutLogLevel = "info"; + +const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + +import "jest-extended"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { PluginLedgerConnectorEthereum } from "../../../../../main/typescript/index"; +import { + QuorumTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import Web3 from "web3"; + +// Unit Test logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "v21.4.1-invoke-web3-method-v1.test", + level: testLogLevel, +}); +log.info("Test started"); + +describe("invokeRawWeb3EthMethod Tests", () => { + let ethereumTestLedger: QuorumTestLedger; + let connector: PluginLedgerConnectorEthereum; + let web3: Web3; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + + log.info("Start QuorumTestLedger..."); + log.debug("Ethereum version:", containerImageVersion); + ethereumTestLedger = new QuorumTestLedger({ + containerImageVersion, + }); + await ethereumTestLedger.start(); + + const rpcApiHttpHost = await ethereumTestLedger.getRpcApiHttpHost(); + log.debug("rpcApiHttpHost:", rpcApiHttpHost); + + log.info("Create PluginLedgerConnectorEthereum..."); + connector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: rpcApiHttpHost, + logLevel: sutLogLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry(), + }); + + web3 = new Web3(rpcApiHttpHost); + }); + + afterAll(async () => { + log.info("Shutdown connector"); + await connector.shutdown(); + + log.info("Stop and destroy the test ledger..."); + await ethereumTestLedger.stop(); + await ethereumTestLedger.destroy(); + + log.info("Prune docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + }); + + test("invokeRawWeb3EthMethod with 0-argument method works (getGasPrice)", async () => { + const connectorResponse = await connector.invokeRawWeb3EthMethod({ + methodName: "getGasPrice", + }); + expect(connectorResponse).toBeTruthy(); + expect(connectorResponse).toEqual("0"); // gas is free on ethereum + }); + + test("invokeRawWeb3EthMethod with 1-argument method works (getBlock)", async () => { + const connectorResponse = await connector.invokeRawWeb3EthMethod({ + methodName: "getBlock", + params: ["earliest"], + }); + expect(connectorResponse).toBeTruthy(); + expect(connectorResponse.hash.length).toBeGreaterThan(5); + + // Compare with direct web3 response + const web3Response = await web3.eth.getBlock("earliest"); + expect(web3Response).toBeTruthy(); + expect(web3Response).toEqual(connectorResponse); + }); + + test("invokeRawWeb3EthMethod with 2-argument method works (getStorageAt)", async () => { + const genesisAccount = await ethereumTestLedger.getGenesisAccount(); + log.debug("genesisAccount:", genesisAccount); + + const connectorResponse = await connector.invokeRawWeb3EthMethod({ + methodName: "getStorageAt", + params: [genesisAccount, 0], + }); + expect(connectorResponse).toBeTruthy(); + + // Compare with direct web3 response + const web3Response = await web3.eth.getStorageAt(genesisAccount, 0); + expect(web3Response).toBeTruthy(); + expect(web3Response).toEqual(connectorResponse); + }); + + test("invokeRawWeb3EthMethod with missing arg throws error (getBlock)", async () => { + try { + const connectorResponse = connector.invokeRawWeb3EthMethod({ + methodName: "getBlock", + }); + + await connectorResponse; + fail("Calling getBlock with missing argument should throw an error"); + } catch (err) { + expect(err).toBeTruthy(); + } + }); + + test("invokeRawWeb3EthMethod with invalid arg throws error (getBlock)", async () => { + try { + const connectorResponse = connector.invokeRawWeb3EthMethod({ + methodName: "getBlock", + params: ["foo"], + }); + + await connectorResponse; + fail("Calling getBlock with argument should throw an error"); + } catch (err) { + expect(err).toBeTruthy(); + } + }); + + test("invokeRawWeb3EthMethod with non existing method throws error", async () => { + try { + const connectorResponse = connector.invokeRawWeb3EthMethod({ + methodName: "foo", + params: ["foo"], + }); + + await connectorResponse; + fail("Calling non existing method should throw an error"); + } catch (err) { + expect(err).toBeTruthy(); + } + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts new file mode 100644 index 0000000000..34aba3a0ae --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts @@ -0,0 +1,6 @@ +import * as apiSurface from "../../../main/typescript/public-api"; +import "jest-extended"; + +test("Library can be loaded", async () => { + expect(apiSurface).toBeTruthy(); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts new file mode 100644 index 0000000000..fdd4daac69 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts @@ -0,0 +1,45 @@ +import "jest-extended"; +import { + isWeb3SigningCredentialGethKeychainPassword, + isWeb3SigningCredentialNone, + isWeb3SigningCredentialPrivateKeyHex, +} from "../../../main/typescript/model-type-guards"; +import { + Web3SigningCredentialGethKeychainPassword, + Web3SigningCredentialType, + Web3SigningCredentialPrivateKeyHex, + Web3SigningCredentialNone, +} from "../../../main/typescript/public-api"; + +describe("Type guards for OpenAPI spec model type definitions", () => { + test("isWeb3SigningCredentialGethKeychainPassword()", () => { + const valid: Web3SigningCredentialGethKeychainPassword = { + secret: "yes", + ethAccount: "fake-account", + type: Web3SigningCredentialType.GethKeychainPassword, + }; + + expect(isWeb3SigningCredentialGethKeychainPassword(valid)).toBe(true); + expect(isWeb3SigningCredentialGethKeychainPassword({})).not.toBe(true); + }); + + test("isWeb3SigningCredentialPrivateKeyHex()", () => { + const valid: Web3SigningCredentialPrivateKeyHex = { + secret: "yes", + ethAccount: "fake-account", + type: Web3SigningCredentialType.PrivateKeyHex, + }; + + expect(isWeb3SigningCredentialPrivateKeyHex(valid)).toBe(true); + expect(isWeb3SigningCredentialPrivateKeyHex({})).not.toBe(true); + }); + + test("isWeb3SigningCredentialNone()", () => { + const valid: Web3SigningCredentialNone = { + type: Web3SigningCredentialType.None, + }; + + expect(isWeb3SigningCredentialNone(valid)).toBe(true); + expect(isWeb3SigningCredentialNone({})).not.toBe(true); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json b/packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json new file mode 100644 index 0000000000..2c24a0daea --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib", + "declarationDir": "./dist/lib", + "resolveJsonModule": true, + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-ledger-connector-ethereum.tsbuildinfo" + }, + "include": [ + "./src", + "src/**/*.json" + ], + "references": [ + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-core/tsconfig.json" + }, + { + "path": "../cactus-core-api/tsconfig.json" + }, + { + "path": "../cactus-plugin-keychain-memory/tsconfig.json" + }, + { + "path": "../cactus-test-tooling/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/README.md b/packages/cactus-test-plugin-ledger-connector-ethereum/README.md new file mode 100644 index 0000000000..816efa51ac --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/README.md @@ -0,0 +1,15 @@ +# `@hyperledger/cactus-test-plugin-ledger-connector-ethereum` + + +## Usage + +``` sh +# In root project dir +npx jest cactus-test-plugin-ledger-connector-ethereum +``` + +## FAQ + +### **What is a dedicated test package for?** + +This is a dedicated test package meaning that it verifies the integration between two packages that are somehow dependent on each other and therefore these tests cannot be added properly in the child package due to circular dependency issues and it would not be fitting to add it in the parent because the child package's tests should not be held by the parent as a matter of principle. diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/package.json b/packages/cactus-test-plugin-ledger-connector-ethereum/package.json new file mode 100644 index 0000000000..cfb219798d --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/package.json @@ -0,0 +1,79 @@ +{ + "name": "@hyperledger/cactus-test-plugin-ledger-connector-ethereum", + "version": "2.0.0-alpha.1", + "description": "Integration tests for the Ethereum ledger and the API server.", + "keywords": [ + "Hyperledger", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "homepage": "https://github.com/hyperledger/cacti#readme", + "bugs": { + "url": "https://github.com/hyperledger/cacti/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cacti.git" + }, + "license": "Apache-2.0", + "author": { + "name": "Hyperledger Cactus Contributors", + "email": "cactus@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cactus" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Peter Somogyvari", + "email": "peter.somogyvari@accenture.com", + "url": "https://accenture.com" + } + ], + "main": "dist/lib/main/typescript/index.js", + "module": "dist/lib/main/typescript/index.js", + "browser": "dist/cactus-test-plugin-ledger-connector-ethereum.web.umd.js", + "types": "dist/lib/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "watch": "npm-watch", + "webpack": "npm-run-all webpack:dev", + "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", + "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", + "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js" + }, + "dependencies": { + "@hyperledger/cactus-cmd-api-server": "2.0.0-alpha.1", + "@hyperledger/cactus-common": "2.0.0-alpha.1", + "@hyperledger/cactus-core": "2.0.0-alpha.1", + "@hyperledger/cactus-core-api": "2.0.0-alpha.1", + "@hyperledger/cactus-plugin-keychain-memory": "2.0.0-alpha.1", + "@hyperledger/cactus-plugin-ledger-connector-ethereum": "2.0.0-alpha.1", + "@hyperledger/cactus-verifier-client": "2.0.0-alpha.1", + "web3": "1.10.0", + "web3-utils": "1.10.0" + }, + "devDependencies": { + "@hyperledger/cactus-test-tooling": "2.0.0-alpha.1", + "@types/convict": "6.1.1", + "@types/lodash": "4.14.195", + "lodash": "4.17.21" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "publishConfig": { + "access": "public" + }, + "browserMinified": "dist/cactus-test-plugin-ledger-connector-ethereum.web.umd.min.js", + "mainMinified": "dist/cactus-test-plugin-ledger-connector-ethereum.node.umd.min.js", + "watch": {} +} diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.ts new file mode 100755 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts new file mode 100755 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json new file mode 100644 index 0000000000..29f7eb9fc7 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.json @@ -0,0 +1,418 @@ +{ + "contractName": "HelloWorld", + "abi": [ + { + "inputs": [], + "name": "getName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sayHello", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "newName", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.7.2+commit.51b20bc0\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"getName\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sayHello\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"newName\",\"type\":\"string\"}],\"name\":\"setName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":\"HelloWorld\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":{\"keccak256\":\"0x0b78fa11f33f7936a80da194c49f04198e38947e3f98f3a7b765b4adb4c455c1\",\"urls\":[\"bzz-raw://12697aa12341c70ed7a411a27a17398dcb2d4336a14dac51845e2123acf174c7\",\"dweb:/ipfs/QmPhH1UbHtUeeen9W2qMDwEVVWAtVJSMN29Nch5q8Gax1D\"]}},\"version\":1}", + "bytecode": "60c0604052600d60808190526c4361707461696e43616374757360981b60a090815261002e9160009190610041565b5034801561003b57600080fd5b506100d4565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008257805160ff19168380011785556100af565b828001600101855582156100af579182015b828111156100af578251825591602001919060010190610094565b506100bb9291506100bf565b5090565b5b808211156100bb57600081556001016100c0565b61030f806100e36000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220b72c0e77fdf429e47051940ba62646ae01296865473f15795ffca6619fe80f9964736f6c63430007020033", + "deployedBytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220b72c0e77fdf429e47051940ba62646ae01296865473f15795ffca6619fe80f9964736f6c63430007020033", + "sourceMap": "463:37:0:-:0;439:322;463:37;;439:322;463:37;;;-1:-1:-1;;;463:37:0;;;;;;-1:-1:-1;;463:37:0;;:::i;:::-;;439:322;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;439:322:0;;;-1:-1:-1;439:322:0;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "439:322:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;683:76;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;683:76:0;;-1:-1:-1;683:76:0;;-1:-1:-1;;;;;683:76:0:i;:::-;;505:89;;;:::i;598:81::-;670:4;663:11;;;;;;;;-1:-1:-1;;663:11:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;638:13;;663:11;;670:4;;663:11;;670:4;663:11;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;:::o;683:76::-;740:14;;;;:4;;:14;;;;;:::i;:::-;;683:76;:::o;505:89::-;568:21;;;;;;;;;;;;-1:-1:-1;;;568:21:0;;;;505:89;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;", + "sourcePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", + "compiler": { + "name": "solc", + "version": "0.7.2+commit.51b20bc0" + }, + "networks":{}, + "ast": { + "absolutePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", + "exportedSymbols": { + "HelloWorld": [ + 31 + ] + }, + "id": 32, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + ">=", + "0.7", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "413:24:0" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 31, + "linearizedBaseContracts": [ + 31 + ], + "name": "HelloWorld", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": false, + "id": 4, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 31, + "src": "463:37:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string" + }, + "typeName": { + "id": 2, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "463:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": { + "hexValue": "4361707461696e436163747573", + "id": 3, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "485:15:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bdd2f21877c99489ddcc32737686677f40d460368c7982ce22ce4f72b41b0312", + "typeString": "literal_string \"CaptainCactus\"" + }, + "value": "CaptainCactus" + }, + "visibility": "private" + }, + { + "body": { + "id": 11, + "nodeType": "Block", + "src": "562:32:0", + "statements": [ + { + "expression": { + "hexValue": "48656c6c6f20576f726c6421", + "id": 9, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "575:14:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0", + "typeString": "literal_string \"Hello World!\"" + }, + "value": "Hello World!" + }, + "functionReturnParameters": 8, + "id": 10, + "nodeType": "Return", + "src": "568:21:0" + } + ] + }, + "functionSelector": "ef5fb05b", + "id": 12, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sayHello", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 5, + "nodeType": "ParameterList", + "parameters": [], + "src": "523:2:0" + }, + "returnParameters": { + "id": 8, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 7, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 12, + "src": "547:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 6, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "547:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "546:15:0" + }, + "scope": 31, + "src": "505:89:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 19, + "nodeType": "Block", + "src": "655:24:0", + "statements": [ + { + "expression": { + "id": 17, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "670:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "functionReturnParameters": 16, + "id": 18, + "nodeType": "Return", + "src": "663:11:0" + } + ] + }, + "functionSelector": "17d7de7c", + "id": 20, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 13, + "nodeType": "ParameterList", + "parameters": [], + "src": "614:2:0" + }, + "returnParameters": { + "id": 16, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 20, + "src": "638:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 14, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "638:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "637:15:0" + }, + "scope": 31, + "src": "598:81:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 29, + "nodeType": "Block", + "src": "732:27:0", + "statements": [ + { + "expression": { + "id": 27, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 25, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "740:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 26, + "name": "newName", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 22, + "src": "747:7:0", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + "src": "740:14:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "id": 28, + "nodeType": "ExpressionStatement", + "src": "740:14:0" + } + ] + }, + "functionSelector": "c47f0027", + "id": 30, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 23, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 22, + "mutability": "mutable", + "name": "newName", + "nodeType": "VariableDeclaration", + "scope": 30, + "src": "700:21:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 21, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "700:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "699:23:0" + }, + "returnParameters": { + "id": 24, + "nodeType": "ParameterList", + "parameters": [], + "src": "732:0:0" + }, + "scope": 31, + "src": "683:76:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 32, + "src": "439:322:0" + } + ], + "src": "413:349:0" + }, + "functionHashes": { + "getName()": "17d7de7c", + "sayHello()": "ef5fb05b", + "setName(string)": "c47f0027" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "156600", + "executionCost": "infinite", + "totalCost": "infinite" + }, + "external": { + "getName()": "infinite", + "sayHello()": "infinite", + "setName(string)": "infinite" + } + } +} \ No newline at end of file diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol new file mode 100644 index 0000000000..befd843936 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/solidity/hello-world-contract/HelloWorld.sol @@ -0,0 +1,26 @@ +// ***************************************************************************** +// IMPORTANT: If you update this code then make sure to recompile +// it and update the .json file as well so that they +// remain in sync for consistent test executions. +// With that said, there shouldn't be any reason to recompile this, like ever... +// ***************************************************************************** + +pragma solidity >=0.7.0; + +contract HelloWorld { + string private name = "CaptainCactus"; + + function sayHello () public pure returns (string memory) { + return 'Hello World!'; + } + + function getName() public view returns (string memory) + { + return name; + } + + function setName(string memory newName) public + { + name = newName; + } +} diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts new file mode 100644 index 0000000000..013b9b12f2 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts @@ -0,0 +1,631 @@ +/* + * Copyright 2022 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const testLogLevel = "info"; +const sutLogLevel = "info"; + +const containerImageName = + "ghcr.io/hyperledger/cactus-quorum-multi-party-all-in-one"; +const containerImageVersion = "2022-04-06-fd10e27"; + +import "jest-extended"; +import lodash from "lodash"; +import { v4 as uuidv4 } from "uuid"; +import Web3 from "web3"; +import { AbiItem } from "web3-utils"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + PluginLedgerConnectorEthereum, + EthereumApiClient, + WatchBlocksV1Progress, + Web3BlockHeader, + Web3SigningCredentialType, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import { + ICactusPlugin, + IVerifierEventListener, + LedgerEvent, +} from "@hyperledger/cactus-core-api"; +import { AddressInfo } from "net"; +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; + +import { Verifier, VerifierFactory } from "@hyperledger/cactus-verifier-client"; +import { + pruneDockerAllIfGithubAction, + QuorumMultiPartyTestLedger, +} from "@hyperledger/cactus-test-tooling"; + +import HelloWorldContractJson from "../../../solidity/hello-world-contract/HelloWorld.json"; + +const log: Logger = LoggerProvider.getOrCreate({ + label: "verifier-integration-with-ethereum-connector.test", + level: testLogLevel, +}); + +log.info("Test started"); + +describe("Verifier integration with ethereum connector tests", () => { + let ethereumTestLedger: QuorumMultiPartyTestLedger; + let apiServer: ApiServer; + let connector: PluginLedgerConnectorEthereum; + let web3: Web3; + let keychainPlugin: PluginKeychainMemory; + let connectionProfile: ReturnType< + typeof QuorumMultiPartyTestLedger.prototype.getKeys + > extends Promise + ? T + : never; + + const ethereumValidatorId = "testEthereumId"; + let globalVerifierFactory: VerifierFactory; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + + // Start Ledger + log.info("Start QuorumMultiPartyTestLedger..."); + log.debug("QuorumMultiParty image:", containerImageName); + log.debug("QuorumMultiParty version:", containerImageVersion); + ethereumTestLedger = new QuorumMultiPartyTestLedger({ + containerImageName, + containerImageVersion, + logLevel: sutLogLevel, + emitContainerLogs: false, + //useRunningLedger: true, + }); + await ethereumTestLedger.start(); + + connectionProfile = await ethereumTestLedger.getKeys(); + log.debug("connectionProfile:", connectionProfile); + + // Setup ApiServer plugins + const plugins: ICactusPlugin[] = []; + const pluginRegistry = new PluginRegistry({ plugins }); + + log.info("Create PluginKeychainMemory..."); + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + logLevel: sutLogLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + plugins.push(keychainPlugin); + + log.info("Create PluginLedgerConnectorEthereum..."); + connector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: connectionProfile.quorum.member1.url, + rpcApiWsHost: connectionProfile.quorum.member1.wsUrl, + logLevel: sutLogLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + plugins.push(connector); + + // Create web3 provider for test + web3 = new Web3(connectionProfile.quorum.member1.url); + + // Create Api Server + log.info("Create ApiServer..."); + const configService = new ConfigService(); + const cactusApiServerOptions = await configService.newExampleConfig(); + cactusApiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + cactusApiServerOptions.configFile = ""; + cactusApiServerOptions.apiCorsDomainCsv = "*"; + cactusApiServerOptions.apiTlsEnabled = false; + cactusApiServerOptions.apiPort = 0; + const config = await configService.newExampleConfigConvict( + cactusApiServerOptions, + ); + + apiServer = new ApiServer({ + config: config.getProperties(), + pluginRegistry, + }); + + // Start ApiServer + const apiServerStartOut = await apiServer.start(); + log.debug(`apiServerStartOut:`, apiServerStartOut); + const httpServer = apiServer.getHttpServerApi(); + + const addressInfo = httpServer?.address() as AddressInfo; + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + + // Create VerifierFactory + log.info("Create VerifierFactory with Ethereum Validator..."); + globalVerifierFactory = new VerifierFactory( + [ + { + validatorID: ethereumValidatorId, + validatorType: "QUORUM_2X", + basePath: apiHost, + logLevel: sutLogLevel, + }, + ], + sutLogLevel, + ); + }); + + afterAll(async () => { + log.info("Shutdown the server..."); + if (apiServer) { + await apiServer.shutdown(); + } + + log.info("Stop and destroy the test ledger..."); + if (ethereumTestLedger) { + await ethereumTestLedger.stop(); + await ethereumTestLedger.destroy(); + } + + log.info("Prune docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + }); + + ////////////////////////////////// + // Helper Functions + ////////////////////////////////// + + function monitorAndGetBlock( + options: Record = {}, + ): Promise> { + return new Promise>( + (resolve, reject) => { + const appId = "testMonitor"; + const sut = globalVerifierFactory.getVerifier(ethereumValidatorId); + + const monitor: IVerifierEventListener = { + onEvent(ledgerEvent: LedgerEvent): void { + try { + log.info("Received event:", ledgerEvent); + + if (!ledgerEvent.data) { + throw Error("No block data"); + } + + log.info( + "Listener received ledgerEvent, block number", + ledgerEvent.data.blockHeader?.number, + ); + + sut.stopMonitor(appId); + resolve(ledgerEvent); + } catch (err) { + reject(err); + } + }, + onError(err: any): void { + log.error("Ledger monitoring error:", err); + reject(err); + }, + }; + + sut.startMonitor(appId, options, monitor); + }, + ); + } + + ////////////////////////////////// + // Tests + ////////////////////////////////// + + test("Verifier of EthereumApiClient is created by VerifierFactory", () => { + const sut = globalVerifierFactory.getVerifier(ethereumValidatorId); + expect(sut.ledgerApi.className).toEqual("EthereumApiClient"); + }); + + describe("web3EthContract tests", () => { + let verifier: Verifier; + let contractCommon: { + abi: AbiItem[]; + address: string; + }; + + beforeAll(async () => { + // Setup verifier + verifier = globalVerifierFactory.getVerifier( + ethereumValidatorId, + "ETH_1X", + ); + + // Deploy contract to interact with + const deployOut = await connector.deployContract({ + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + web3SigningCredential: { + ethAccount: connectionProfile.quorum.member2.accountAddress, + secret: connectionProfile.quorum.member2.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + expect(deployOut.transactionReceipt.status).toBeTrue(); + + contractCommon = { + abi: HelloWorldContractJson.abi as AbiItem[], + address: deployOut.transactionReceipt.contractAddress as string, + }; + }); + + test("Invalid web3EthContract calls are rejected by EthereumApiClient", async () => { + // Define correct input parameters + const correctContract: Record = lodash.clone( + contractCommon, + ); + const correctMethod: Record = { + type: "web3EthContract", + command: "call", + function: "getName", + params: [], + }; + const correctArgs: any = {}; + + // Sanity check if correct parameters work + const resultCorrect = await verifier.sendSyncRequest( + correctContract, + correctMethod, + correctArgs, + ); + expect(resultCorrect.status).toEqual(200); + + // Failing: Missing contract ABI + const missingABIContract = lodash.clone(correctContract); + delete missingABIContract.abi; + + expect( + verifier.sendSyncRequest( + missingABIContract, + correctMethod, + correctArgs, + ), + ).toReject(); + + // Failing: Missing contract address + const missingAddressContract = lodash.clone(correctContract); + delete missingAddressContract.address; + + expect( + verifier.sendSyncRequest( + missingAddressContract, + correctMethod, + correctArgs, + ), + ).toReject(); + + // Failing: Unknown invocation method + const unknownMethod = lodash.clone(correctMethod); + unknownMethod.command = "foo"; + expect( + verifier.sendSyncRequest(correctContract, unknownMethod, correctArgs), + ).toReject(); + + // Failing: Empty invocation method + const emptyMethod = lodash.clone(correctMethod); + emptyMethod.command = ""; + expect( + verifier.sendSyncRequest(correctContract, emptyMethod, correctArgs), + ).toReject(); + + // Failing: Empty contract method + const emptyContractFunction = lodash.clone(correctMethod); + emptyContractFunction.function = ""; + expect( + verifier.sendSyncRequest( + correctContract, + emptyContractFunction, + correctArgs, + ), + ).toReject(); + + // Failing: Wrong method params format + const numericParam = lodash.clone(correctMethod); + numericParam.params = 42; + expect( + verifier.sendSyncRequest(correctContract, numericParam, correctArgs), + ).toReject(); + + const objectParam = lodash.clone(correctMethod); + objectParam.params = { arg1: 42 }; + expect( + verifier.sendSyncRequest(correctContract, objectParam, correctArgs), + ).toReject(); + }); + + test("Send unsigned transaction and use call to check results works", async () => { + const newName = "EthereumCactus"; + + // 1. Set new value (send) + // Will use signing key of the node we're connected to (member1) + const methodSend = { + type: "web3EthContract", + command: "send", + function: "setName", + params: [newName], + }; + const argsSend = { + args: { + from: connectionProfile.quorum.member1.accountAddress, + }, + }; + + const resultsSend = await verifier.sendSyncRequest( + contractCommon, + methodSend, + argsSend, + ); + expect(resultsSend.status).toEqual(200); + expect(resultsSend.data.status).toBeTrue(); + + // 2. Get new, updated value (call) + const methodCall = { + type: "web3EthContract", + command: "call", + function: "getName", + params: [], + }; + const argsCall = {}; + + const resultCall = await verifier.sendSyncRequest( + contractCommon, + methodCall, + argsCall, + ); + expect(resultCall.status).toEqual(200); + expect(resultCall.data).toEqual(newName); + }); + + test("encodeABI of transactions gives same results as direct web3 call", async () => { + // Send encodeABI request to connector + const methodEncode = { + type: "web3EthContract", + command: "encodeABI", + function: "setName", + params: ["EthereumCactusEncode"], + }; + const argsEncode = { + args: { + from: connectionProfile.quorum.member1.accountAddress, + }, + }; + + const resultsEncode = await verifier.sendSyncRequest( + contractCommon, + methodEncode, + argsEncode, + ); + expect(resultsEncode.status).toEqual(200); + expect(resultsEncode.data.length).toBeGreaterThan(5); + + // Compare encoded data with direct web3 call + const web3Contract = new web3.eth.Contract( + contractCommon.abi, + contractCommon.address, + ); + const web3Encode = await web3Contract.methods + .setName(...methodEncode.params) + .encodeABI(argsEncode); + expect(resultsEncode.data).toEqual(web3Encode); + }); + + test("estimateGas of transactions gives same results as direct web3 call", async () => { + // Send estimateGas request to connector + const methodEstimateGas = { + type: "web3EthContract", + command: "estimateGas", + function: "setName", + params: ["EthereumCactusGas"], + }; + const argsEstimateGas = {}; + + const resultsEstimateGas = await verifier.sendSyncRequest( + contractCommon, + methodEstimateGas, + argsEstimateGas, + ); + expect(resultsEstimateGas.status).toEqual(200); + expect(resultsEstimateGas.data).toBeGreaterThan(0); + + // Compare gas estimate with direct web3 call + const web3Contract = new web3.eth.Contract( + contractCommon.abi, + contractCommon.address, + ); + const web3Encode = await web3Contract.methods + .setName(...methodEstimateGas.params) + .estimateGas(argsEstimateGas); + expect(resultsEstimateGas.data).toEqual(web3Encode); + }); + + test("Sending transaction with sendAsyncRequest works", async () => { + const newName = "EthereumCactusAsync"; + + // 1. Set new value with async call (send) + // Will use signing key of the node we're connected to (member1) + const methodSendAsync = { + type: "web3EthContract", + command: "send", + function: "setName", + params: [newName], + }; + const argsSendAsync = { + args: { + from: connectionProfile.quorum.member1.accountAddress, + }, + }; + + await verifier.sendAsyncRequest( + contractCommon, + methodSendAsync, + argsSendAsync, + ); + + // 2. Wait for transaction commit + // We assume transaction will be included in the next block + await monitorAndGetBlock(); + + // 3. Get new, updated value (call) + const methodCall = { + type: "web3EthContract", + command: "call", + function: "getName", + params: [], + }; + const argsCall = {}; + + const resultsCall = await verifier.sendSyncRequest( + contractCommon, + methodCall, + argsCall, + ); + expect(resultsCall.status).toEqual(200); + expect(resultsCall.data).toEqual(newName); + }); + }); + + test("Verifier of EthereumApiClient supports web3Eth function", async () => { + // web3Eth.getBalance + const contract = {}; + const method = { type: "web3Eth", command: "getBalance" }; + const args = { args: [connectionProfile.quorum.member2.accountAddress] }; + + const results = await globalVerifierFactory + .getVerifier(ethereumValidatorId) + .sendSyncRequest(contract, method, args); + expect(results.status).toEqual(200); + expect(results.data.length).toBeGreaterThan(0); + }); + + test("Invalid web3Eth calls are rejected by EthereumApiClient", async () => { + // Define correct input parameters + const correctContract = {}; + const correctMethod: Record = { + type: "web3Eth", + command: "getBalance", + }; + const correctArgs: any = { + args: [connectionProfile.quorum.member2.accountAddress], + }; + const verifier = globalVerifierFactory.getVerifier(ethereumValidatorId); + + // Sanity check if correct parameters work + const resultCorrect = await verifier.sendSyncRequest( + correctContract, + correctMethod, + correctArgs, + ); + expect(resultCorrect.status).toEqual(200); + + // Failing: Empty web3.eth method + const emptyMethod = lodash.clone(correctMethod); + emptyMethod.command = ""; + + expect( + verifier.sendSyncRequest(correctContract, emptyMethod, correctArgs), + ).toReject(); + + // Failing: Wrong args format + const numericArgsFormat = lodash.clone(correctArgs); + numericArgsFormat.args = 42; + + expect( + verifier.sendSyncRequest(correctContract, numericArgsFormat, correctArgs), + ).toReject(); + + const objectArgsFormat = lodash.clone(correctArgs); + objectArgsFormat.args = { arg1: 42 }; + + expect( + verifier.sendSyncRequest(correctContract, objectArgsFormat, correctArgs), + ).toReject(); + }); + + test("EthereumApiClient web3Eth throws error on unknown method", async () => { + const contract = {}; + const method = { type: "web3Eth", command: "foo" }; + const args = {}; + + const results = await globalVerifierFactory + .getVerifier(ethereumValidatorId) + .sendSyncRequest(contract, method, args); + + expect(results).toBeTruthy(); + expect(results.status).toEqual(504); + expect(results.errorDetail).toBeTruthy(); + }); + + function assertBlockHeader(header: Web3BlockHeader) { + // Check if defined and with expected type + // Ignore nullable / undefine-able fields + expect(typeof header.parentHash).toEqual("string"); + expect(typeof header.sha3Uncles).toEqual("string"); + expect(typeof header.miner).toEqual("string"); + expect(typeof header.stateRoot).toEqual("string"); + expect(typeof header.logsBloom).toEqual("string"); + expect(typeof header.number).toEqual("number"); + expect(typeof header.gasLimit).toEqual("number"); + expect(typeof header.gasUsed).toEqual("number"); + expect(typeof header.extraData).toEqual("string"); + expect(typeof header.nonce).toEqual("string"); + expect(typeof header.hash).toEqual("string"); + expect(typeof header.difficulty).toEqual("string"); + } + + test("Monitor new blocks headers on Ethereum", async () => { + const ledgerEvent = await monitorAndGetBlock(); + // assert well-formed output + expect(ledgerEvent.id).toEqual(""); + expect(ledgerEvent.verifierId).toEqual(ethereumValidatorId); + expect(ledgerEvent.data).toBeTruthy(); + + // blockData should not be present if called with empty options + expect(ledgerEvent.data?.blockData).toBeUndefined(); + expect(ledgerEvent.data?.blockHeader).toBeTruthy(); + + // check some fields + assertBlockHeader(ledgerEvent.data?.blockHeader as Web3BlockHeader); + }); + + test("Monitor new blocks data on Ethereum", async () => { + const ledgerEvent = await monitorAndGetBlock({ getBlockData: true }); + // assert well-formed output + expect(ledgerEvent.id).toEqual(""); + expect(ledgerEvent.verifierId).toEqual(ethereumValidatorId); + expect(ledgerEvent.data).toBeTruthy(); + + // blockHeader should not be present if called with getBlockData option + expect(ledgerEvent.data?.blockHeader).toBeFalsy(); + expect(ledgerEvent.data?.blockData).toBeTruthy(); + + // check some fields + assertBlockHeader(ledgerEvent.data?.blockData as Web3BlockHeader); + expect(typeof ledgerEvent.data?.blockData?.size).toEqual("number"); + expect(typeof ledgerEvent.data?.blockData?.totalDifficulty).toEqual( + "string", + ); + expect(typeof ledgerEvent.data?.blockData?.uncles).toEqual("object"); + expect(typeof ledgerEvent.data?.blockData?.transactions).toEqual("object"); + }); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts new file mode 100644 index 0000000000..a65ea58247 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-surface.test.ts @@ -0,0 +1,5 @@ +import * as apiSurface from "../../../main/typescript/public-api"; + +test("Library can be loaded", async () => { + expect(apiSurface).toBeTruthy(); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/deploy-contract-via-web-service.test.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/deploy-contract-via-web-service.test.ts new file mode 100644 index 0000000000..ae576303de --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/plugin-ledger-connector-ethereum/deploy-contract/deploy-contract-via-web-service.test.ts @@ -0,0 +1,270 @@ +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; +import convict from "convict"; +import "jest-extended"; +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import { + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { + PluginLedgerConnectorEthereum, + DefaultApi, + Web3SigningCredentialType, + DeployContractSolidityBytecodeV1Request, + EthContractInvocationType, + Configuration, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; + +import { + ApiServer, + AuthorizationProtocol, + ConfigService, + ICactusApiServerOptions, +} from "@hyperledger/cactus-cmd-api-server"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { ICactusPlugin, IPluginKeychain } from "@hyperledger/cactus-core-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { AddressInfo } from "net"; +const testCase = "deploys contract via REST API"; +describe(testCase, () => { + const logLevel: LogLevelDesc = "TRACE"; + const log: Logger = LoggerProvider.getOrCreate({ + label: "test-deploy-contract-via-web-service", + level: logLevel, + }); + const ledger = new QuorumTestLedger(); + + const plugins: ICactusPlugin[] = []; + const pluginRegistry = new PluginRegistry({ plugins }); + const contractName = "HelloWorld"; + let addressInfo: AddressInfo, + configService: ConfigService, + cactusApiServerOptions: ICactusApiServerOptions, + config: convict.Config, + apiServer: ApiServer, + protocol, + basePath: string, + configuration, + rpcApiHttpHost: string, + kvStoragePlugin: IPluginKeychain, + client: DefaultApi, + ethereumGenesisOptions: IQuorumGenesisOptions, + firstHighNetWorthAccount: string, + apiServerStartOut: { + addressInfoCockpit?: AddressInfo; + addressInfoApi: AddressInfo; + addressInfoGrpc: AddressInfo; + }; + + // Instantiate a ledger object + // Gather parameteres needed to run an embedded ApiServer which can connect to/interact with said ledger + + afterAll(async () => await apiServer.shutdown()); + beforeAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + afterAll(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + afterAll(async () => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); + + beforeAll(async () => { + configService = new ConfigService(); + cactusApiServerOptions = await configService.newExampleConfig(); + cactusApiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + cactusApiServerOptions.configFile = ""; + cactusApiServerOptions.apiCorsDomainCsv = "*"; + cactusApiServerOptions.apiTlsEnabled = false; + cactusApiServerOptions.apiPort = 0; + config = await configService.newExampleConfigConvict( + cactusApiServerOptions, + ); + await ledger.start(); + rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + + kvStoragePlugin = new PluginKeychainMemory({ + backend: new Map(), + instanceId: uuidV4(), + keychainId: uuidV4(), + }); + kvStoragePlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + plugins.push(kvStoragePlugin); + + const ledgerConnectorEthereum = new PluginLedgerConnectorEthereum({ + instanceId: uuidV4(), + rpcApiHttpHost, + pluginRegistry: new PluginRegistry({ plugins: [kvStoragePlugin] }), + }); + plugins.push(ledgerConnectorEthereum); + + apiServer = new ApiServer({ + config: config.getProperties(), + pluginRegistry, + }); + + // Start the API server which now is connected to the ethereum ledger + apiServerStartOut = await apiServer.start(); + log.debug(`ApiServer.started OK:`, apiServerStartOut); + const httpServer = apiServer.getHttpServerApi(); + addressInfo = httpServer?.address() as AddressInfo; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + protocol = config.get("apiTlsEnabled") ? "https:" : "http:"; + basePath = `${protocol}//${addressInfo.address}:${addressInfo.port}`; + configuration = new Configuration({ basePath }); + client = new DefaultApi(configuration); + // Find a high net worth account in the genesis object of the ethereum ledger + ethereumGenesisOptions = await ledger.getGenesisJsObject(); + const highNetWorthAccounts: string[] = Object.keys( + ethereumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = ethereumGenesisOptions.alloc[address]; + const balance: number = parseInt(anAccount.balance, 10); + return balance > 10e7; + }); + [firstHighNetWorthAccount] = highNetWorthAccounts; + }); + + test(testCase, async () => { + expect(ethereumGenesisOptions); + expect(ethereumGenesisOptions.alloc); + + // 6. Instantiate the SDK dynamically with whatever port the API server ended up bound to (port 0) + log.debug(`AddressInfo: `, addressInfo); + log.debug(`SDK base path: %s`, basePath); + + // 7. Assemble request to invoke the deploy contract method of the ethereum ledger connector plugin via the REST API + const req: DeployContractSolidityBytecodeV1Request = { + contractName: HelloWorldContractJson.contractName, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + keychainId: kvStoragePlugin.getKeychainId(), + gas: 1000000, + }; + + // 8. Deploy smart contract by issuing REST API call + const res = await client.deployContractSolBytecodeV1(req); + + expect(res).toBeTruthy(); + expect(res.status).toBeWithin(199, 300); + }); + + test("Invoke contract via SDK ApiClient object", async () => { + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + const res1 = await client.runTransactionV1({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + expect(res1).toBeTruthy(); + expect(res1.status).toBeWithin(199, 300); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toEqual(10e9); + + const sayHelloRes = await client.invokeContractV1({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + keychainId: kvStoragePlugin.getKeychainId(), + }); + expect(sayHelloRes).toBeTruthy(); + expect(sayHelloRes.status).toBeWithin(199, 300); + expect(sayHelloRes.data).toBeTruthy(); + expect(sayHelloRes.data.callOutput).toBeTruthy(); + expect(typeof sayHelloRes.data.callOutput).toBeString(); + expect(sayHelloRes.data.callOutput).toBe("Hello World!"); + + const newName = `DrCactus${uuidV4()}`; + const setName1Res = await client.invokeContractV1({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + keychainId: kvStoragePlugin.getKeychainId(), + }); + expect(setName1Res).toBeTruthy(); + expect(setName1Res).toBeTruthy(); + expect(setName1Res.status).toBeWithin(199, 300); + expect(setName1Res.data).toBeTruthy(); + + const getName1Res = await client.invokeContractV1({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + keychainId: kvStoragePlugin.getKeychainId(), + }); + expect(getName1Res).toBeTruthy(); + expect(getName1Res.status).toBeWithin(199, 300); + expect(getName1Res.data).toBeTruthy(); + expect(getName1Res.data.callOutput).toBeTruthy(); + expect(getName1Res.data.callOutput).toBeString(); + expect(getName1Res.data.callOutput).toEqual(newName); + + const getName2Res = await client.invokeContractV1({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + keychainId: kvStoragePlugin.getKeychainId(), + }); + + expect(getName2Res).toBeTruthy(); + expect(getName2Res.status).toBeWithin(199, 300); + expect(getName2Res.data).toBeTruthy(); + expect(getName2Res.data.callOutput).not.toBeTruthy(); + }); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts new file mode 100644 index 0000000000..34aba3a0ae --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/unit/api-surface.test.ts @@ -0,0 +1,6 @@ +import * as apiSurface from "../../../main/typescript/public-api"; +import "jest-extended"; + +test("Library can be loaded", async () => { + expect(apiSurface).toBeTruthy(); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/tsconfig.json b/packages/cactus-test-plugin-ledger-connector-ethereum/tsconfig.json new file mode 100644 index 0000000000..635675a1c8 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/tsconfig.json @@ -0,0 +1,38 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib/", + "declarationDir": "dist/lib", + "resolveJsonModule": true, + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-test-plugin-ledger-connector-ethereum.tsbuildinfo" + }, + "include": [ + "./src", + "src/**/*.json" + ], + "references": [ + { + "path": "../cactus-cmd-api-server/tsconfig.json" + }, + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-core/tsconfig.json" + }, + { + "path": "../cactus-core-api/tsconfig.json" + }, + { + "path": "../cactus-plugin-keychain-memory/tsconfig.json" + }, + { + "path": "../cactus-plugin-ledger-connector-ethereum/tsconfig.json" + }, + { + "path": "../cactus-test-tooling/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/packages/cactus-verifier-client/README.md b/packages/cactus-verifier-client/README.md index 484cbaadb2..856bc7e52b 100644 --- a/packages/cactus-verifier-client/README.md +++ b/packages/cactus-verifier-client/README.md @@ -8,7 +8,8 @@ This package provides `Verifier` and `VerifierFactory` components that can be us | validatorType | cactus ledger connector plugin | | ---------------------- | ----------------------------------------------- | | BESU_1X
BESU_2X | cactus-plugin-ledger-connector-besu | -| QUORUM_2X | cactus-test-plugin-ledger-connector-quorum | +| QUORUM_2X | cactus-plugin-ledger-connector-quorum | +| ETH_1X | cactus-plugin-ledger-connector-ethereum | | CORDA_4X | cactus-plugin-ledger-connector-corda | | IROHA_1X | cactus-plugin-ledger-connector-iroha | | IROHA_2X | cactus-plugin-ledger-connector-iroha2 | diff --git a/packages/cactus-verifier-client/package.json b/packages/cactus-verifier-client/package.json index 9bf6ed5df1..45adccbc9e 100644 --- a/packages/cactus-verifier-client/package.json +++ b/packages/cactus-verifier-client/package.json @@ -47,6 +47,7 @@ "@hyperledger/cactus-core-api": "2.0.0-alpha.1", "@hyperledger/cactus-plugin-ledger-connector-besu": "2.0.0-alpha.1", "@hyperledger/cactus-plugin-ledger-connector-corda": "2.0.0-alpha.1", + "@hyperledger/cactus-plugin-ledger-connector-ethereum": "2.0.0-alpha.1", "@hyperledger/cactus-plugin-ledger-connector-fabric": "2.0.0-alpha.1", "@hyperledger/cactus-plugin-ledger-connector-iroha": "2.0.0-alpha.1", "@hyperledger/cactus-plugin-ledger-connector-iroha2": "2.0.0-alpha.1", diff --git a/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts b/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts index dcf6b62e08..f56b79e326 100644 --- a/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts +++ b/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts @@ -20,6 +20,11 @@ import { QuorumApiClientOptions, } from "@hyperledger/cactus-plugin-ledger-connector-quorum"; +import { + EthereumApiClient, + EthereumApiClientOptions, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; + import { CordaApiClient, CordaApiClientOptions, @@ -63,6 +68,10 @@ export type ClientApiConfig = { in: QuorumApiClientOptions; out: QuorumApiClient; }; + ETH_1X: { + in: EthereumApiClientOptions; + out: EthereumApiClient; + }; CORDA_4X: { in: CordaApiClientOptions; out: CordaApiClient; @@ -102,6 +111,8 @@ export function getValidatorApiClient( return new BesuApiClient(options as BesuApiClientOptions); case "QUORUM_2X": return new QuorumApiClient(options as QuorumApiClientOptions); + case "ETH_1X": + return new EthereumApiClient(options as EthereumApiClientOptions); case "CORDA_4X": return new CordaApiClient(options as CordaApiClientOptions); case "IROHA_1X": diff --git a/packages/cactus-verifier-client/tsconfig.json b/packages/cactus-verifier-client/tsconfig.json index 5a5de1963d..4dd36a75cb 100644 --- a/packages/cactus-verifier-client/tsconfig.json +++ b/packages/cactus-verifier-client/tsconfig.json @@ -26,6 +26,9 @@ { "path": "../cactus-plugin-ledger-connector-quorum/tsconfig.json" }, + { + "path": "../cactus-plugin-ledger-connector-ethereum/tsconfig.json" + }, { "path": "../cactus-plugin-ledger-connector-corda/tsconfig.json" }, diff --git a/tsconfig.json b/tsconfig.json index 924881e8a9..456c61d580 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -55,6 +55,9 @@ { "path": "./packages/cactus-plugin-ledger-connector-corda/tsconfig.json" }, + { + "path": "./packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json" + }, { "path": "./packages/cactus-plugin-ledger-connector-quorum/tsconfig.json" }, @@ -103,6 +106,9 @@ { "path": "./packages/cactus-test-plugin-ledger-connector-besu/tsconfig.json" }, + { + "path": "./packages/cactus-test-plugin-ledger-connector-ethereum/tsconfig.json" + }, { "path": "./packages/cactus-test-plugin-ledger-connector-quorum/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 254c06c1c7..a1df57fc3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:^1.8.8": + version: 1.9.4 + resolution: "@adraffy/ens-normalize@npm:1.9.4" + checksum: 7d7fff58ebe2c4961f7e5e61dad123aa6a63fec0df5c84af1fa41079dc05d398599690be4427b3a94d2baa94084544bcfdf2d51cbed7504b9b0583b0960ad550 + languageName: node + linkType: hard + "@ampproject/remapping@npm:2.2.0": version: 2.2.0 resolution: "@ampproject/remapping@npm:2.2.0" @@ -7079,6 +7086,38 @@ __metadata: languageName: unknown linkType: soft +"@hyperledger/cactus-plugin-ledger-connector-ethereum@2.0.0-alpha.1, @hyperledger/cactus-plugin-ledger-connector-ethereum@workspace:packages/cactus-plugin-ledger-connector-ethereum": + version: 0.0.0-use.local + resolution: "@hyperledger/cactus-plugin-ledger-connector-ethereum@workspace:packages/cactus-plugin-ledger-connector-ethereum" + dependencies: + "@hyperledger/cactus-common": 2.0.0-alpha.1 + "@hyperledger/cactus-core": 2.0.0-alpha.1 + "@hyperledger/cactus-core-api": 2.0.0-alpha.1 + "@hyperledger/cactus-plugin-keychain-memory": 2.0.0-alpha.1 + "@hyperledger/cactus-test-tooling": 2.0.0-alpha.1 + "@types/express": 4.17.13 + "@types/minimist": 1.2.2 + "@types/sanitize-html": 2.6.2 + axios: 0.21.4 + chalk: 4.1.2 + express: 4.17.3 + minimist: 1.2.8 + prom-client: 13.2.0 + run-time-error: 1.4.0 + rxjs: 7.8.1 + sanitize-html: 2.7.0 + socket.io: 4.5.4 + socket.io-client: 4.5.4 + typescript-optional: 2.0.1 + web3: 1.10.0 + web3-eth: 1.10.0 + web3-eth-contract: 1.10.0 + web3-utils: 1.10.0 + bin: + cacti-ethereum-connector-status: dist/lib/scripts/get-ethereum-connector-status.js + languageName: unknown + linkType: soft + "@hyperledger/cactus-plugin-ledger-connector-fabric-socketio@workspace:packages/cactus-plugin-ledger-connector-fabric-socketio": version: 0.0.0-use.local resolution: "@hyperledger/cactus-plugin-ledger-connector-fabric-socketio@workspace:packages/cactus-plugin-ledger-connector-fabric-socketio" @@ -7603,6 +7642,26 @@ __metadata: languageName: unknown linkType: soft +"@hyperledger/cactus-test-plugin-ledger-connector-ethereum@workspace:packages/cactus-test-plugin-ledger-connector-ethereum": + version: 0.0.0-use.local + resolution: "@hyperledger/cactus-test-plugin-ledger-connector-ethereum@workspace:packages/cactus-test-plugin-ledger-connector-ethereum" + dependencies: + "@hyperledger/cactus-cmd-api-server": 2.0.0-alpha.1 + "@hyperledger/cactus-common": 2.0.0-alpha.1 + "@hyperledger/cactus-core": 2.0.0-alpha.1 + "@hyperledger/cactus-core-api": 2.0.0-alpha.1 + "@hyperledger/cactus-plugin-keychain-memory": 2.0.0-alpha.1 + "@hyperledger/cactus-plugin-ledger-connector-ethereum": 2.0.0-alpha.1 + "@hyperledger/cactus-test-tooling": 2.0.0-alpha.1 + "@hyperledger/cactus-verifier-client": 2.0.0-alpha.1 + "@types/convict": 6.1.1 + "@types/lodash": 4.14.195 + lodash: 4.17.21 + web3: 1.10.0 + web3-utils: 1.10.0 + languageName: unknown + linkType: soft + "@hyperledger/cactus-test-plugin-ledger-connector-quorum@workspace:packages/cactus-test-plugin-ledger-connector-quorum": version: 0.0.0-use.local resolution: "@hyperledger/cactus-test-plugin-ledger-connector-quorum@workspace:packages/cactus-test-plugin-ledger-connector-quorum" @@ -7693,6 +7752,7 @@ __metadata: "@hyperledger/cactus-core-api": 2.0.0-alpha.1 "@hyperledger/cactus-plugin-ledger-connector-besu": 2.0.0-alpha.1 "@hyperledger/cactus-plugin-ledger-connector-corda": 2.0.0-alpha.1 + "@hyperledger/cactus-plugin-ledger-connector-ethereum": 2.0.0-alpha.1 "@hyperledger/cactus-plugin-ledger-connector-fabric": 2.0.0-alpha.1 "@hyperledger/cactus-plugin-ledger-connector-iroha": 2.0.0-alpha.1 "@hyperledger/cactus-plugin-ledger-connector-iroha2": 2.0.0-alpha.1 @@ -7784,6 +7844,10 @@ __metadata: ts-loader: 9.4.4 ts-node: 10.9.1 typescript: 4.9.5 + web3: 4.1.1 + web3-core: 4.1.1 + web3-eth: 4.1.1 + web3-utils: 4.0.5 webpack: 5.88.2 webpack-bundle-analyzer: 4.9.0 webpack-cli: 4.10.0 @@ -12475,7 +12539,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.2.2": +"@types/ws@npm:8.5.3, @types/ws@npm:^8.2.2": version: 8.5.3 resolution: "@types/ws@npm:8.5.3" dependencies: @@ -18669,6 +18733,15 @@ __metadata: languageName: node linkType: hard +"crc-32@npm:^1.2.2": + version: 1.2.2 + resolution: "crc-32@npm:1.2.2" + bin: + crc32: bin/crc32.njs + checksum: ad2d0ad0cbd465b75dcaeeff0600f8195b686816ab5f3ba4c6e052a07f728c3e70df2e3ca9fd3d4484dc4ba70586e161ca5a2334ec8bf5a41bf022a6103ff243 + languageName: node + linkType: hard + "crc32-stream@npm:^4.0.2": version: 4.0.2 resolution: "crc32-stream@npm:4.0.2" @@ -28301,6 +28374,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "isstream@npm:0.1.x, isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -45116,6 +45198,25 @@ __metadata: languageName: node linkType: hard +"web3-core@npm:4.1.1, web3-core@npm:^4.1.1": + version: 4.1.1 + resolution: "web3-core@npm:4.1.1" + dependencies: + web3-errors: ^1.1.1 + web3-eth-iban: ^4.0.5 + web3-providers-http: ^4.0.5 + web3-providers-ipc: ^4.0.5 + web3-providers-ws: ^4.0.5 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + dependenciesMeta: + web3-providers-ipc: + optional: true + checksum: 24e44bdc8018d6535b882b45b5a2b2b7d99b7cca9e4b9927898db10e6bd4858ca7e9d4086e155398ba13811188aeacd27bff1fe030ac83d3e86169505946cc5f + languageName: node + linkType: hard + "web3-errors@npm:^1.0.2": version: 1.0.2 resolution: "web3-errors@npm:1.0.2" @@ -45125,6 +45226,15 @@ __metadata: languageName: node linkType: hard +"web3-errors@npm:^1.1.1": + version: 1.1.1 + resolution: "web3-errors@npm:1.1.1" + dependencies: + web3-types: ^1.1.1 + checksum: 1ac088d49e32f0952f4098a618cf1a89b318b052b6b9e58c9d54976e6b43ef5cd7583c2411275c6d6f7b0f3990e9cf6b2c86c1c1023d50b014cbd04fc18ac73e + languageName: node + linkType: hard + "web3-eth-abi@npm:1.10.0": version: 1.10.0 resolution: "web3-eth-abi@npm:1.10.0" @@ -45208,6 +45318,19 @@ __metadata: languageName: node linkType: hard +"web3-eth-abi@npm:^4.1.1": + version: 4.1.1 + resolution: "web3-eth-abi@npm:4.1.1" + dependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + checksum: 99e197707396a1083e46f28d5b6bc3af299ad17d196d00a78ce9b87c6dc4b00ce0b68750c7bc0a7809916f8f1d834b6de5c35dc7e04454b990b30f64beafc82c + languageName: node + linkType: hard + "web3-eth-accounts@npm:1.10.0": version: 1.10.0 resolution: "web3-eth-accounts@npm:1.10.0" @@ -45339,6 +45462,21 @@ __metadata: languageName: node linkType: hard +"web3-eth-accounts@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-eth-accounts@npm:4.0.5" + dependencies: + "@ethereumjs/rlp": ^4.0.1 + crc-32: ^1.2.2 + ethereum-cryptography: ^2.0.0 + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + checksum: 681322374c391391c153011a17e6d00b125fb2d1537f1bd7028610fd09490c985b2fa268517a3b5fe036a1b6a19d70d6a616e587aeb029b8e895ff873a149d1d + languageName: node + linkType: hard + "web3-eth-contract@npm:1.10.0": version: 1.10.0 resolution: "web3-eth-contract@npm:1.10.0" @@ -45451,6 +45589,21 @@ __metadata: languageName: node linkType: hard +"web3-eth-contract@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-eth-contract@npm:4.0.5" + dependencies: + web3-core: ^4.1.1 + web3-errors: ^1.1.1 + web3-eth: ^4.1.1 + web3-eth-abi: ^4.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + checksum: 5b3f7ea8677942b5c0c514f89eb487c6d4f8821ddde4b6d7da0eb7ea7ea5b18a23f7f75002b2d9dd467fa68633351da74f8b2bcaf2de8d94f6d5e4dab24c2b5a + languageName: node + linkType: hard + "web3-eth-ens@npm:1.10.0": version: 1.10.0 resolution: "web3-eth-ens@npm:1.10.0" @@ -45563,6 +45716,23 @@ __metadata: languageName: node linkType: hard +"web3-eth-ens@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-eth-ens@npm:4.0.5" + dependencies: + "@adraffy/ens-normalize": ^1.8.8 + web3-core: ^4.1.1 + web3-errors: ^1.1.1 + web3-eth: ^4.1.1 + web3-eth-contract: ^4.0.5 + web3-net: ^4.0.5 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + checksum: 87e0d34b42d973ec7adff6dce88ccd917b6df5a3d303c1a07c25cd937bf5cd6b2193e5bae7cdbce156e4f79fa16ce46f8633e6d921a56168a4f468d75f146794 + languageName: node + linkType: hard + "web3-eth-iban@npm:1.10.0": version: 1.10.0 resolution: "web3-eth-iban@npm:1.10.0" @@ -45633,6 +45803,18 @@ __metadata: languageName: node linkType: hard +"web3-eth-iban@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-eth-iban@npm:4.0.5" + dependencies: + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + checksum: 305c1c87dfdc41ff8c61d91ac3ec620dac6dbd1a037dde1f252892d9f90fb6d34d7bf6ceb41548f8a7ff036a2ef47cc550426653c11052ac08d50b8e943c0298 + languageName: node + linkType: hard + "web3-eth-personal@npm:1.10.0": version: 1.10.0 resolution: "web3-eth-personal@npm:1.10.0" @@ -45731,6 +45913,20 @@ __metadata: languageName: node linkType: hard +"web3-eth-personal@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-eth-personal@npm:4.0.5" + dependencies: + web3-core: ^4.1.1 + web3-eth: ^4.1.1 + web3-rpc-methods: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + checksum: b56951dae5589b626fc167c5eb68e89386a51513acc272e7ac2245d510fda2ca883ae73feab2d155f1fcc2f409dd934f93aa685fb9574239886d05e2261f9db7 + languageName: node + linkType: hard + "web3-eth@npm:1.10.0": version: 1.10.0 resolution: "web3-eth@npm:1.10.0" @@ -45871,6 +46067,25 @@ __metadata: languageName: node linkType: hard +"web3-eth@npm:4.1.1, web3-eth@npm:^4.1.1": + version: 4.1.1 + resolution: "web3-eth@npm:4.1.1" + dependencies: + setimmediate: ^1.0.5 + web3-core: ^4.1.1 + web3-errors: ^1.1.1 + web3-eth-abi: ^4.1.1 + web3-eth-accounts: ^4.0.5 + web3-net: ^4.0.5 + web3-providers-ws: ^4.0.5 + web3-rpc-methods: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + checksum: 9ffc2800f050e74c98def916249cbb56e314f6aa3eb14b29915a2acf87ab77f4535d472eeb1c23f356f6a9dde9d5c66caeb1cf7c257e432814c4e0c5b90f5b0d + languageName: node + linkType: hard + "web3-net@npm:1.10.0": version: 1.10.0 resolution: "web3-net@npm:1.10.0" @@ -45948,6 +46163,18 @@ __metadata: languageName: node linkType: hard +"web3-net@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-net@npm:4.0.5" + dependencies: + web3-core: ^4.1.1 + web3-rpc-methods: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + checksum: 9c515e8110710637137ee0495e5a749105157027dbd432975d08a53e66280c2779001b496c6fac43590438538381ba6f2c1fc35ae9dcb518002004ad14953724 + languageName: node + linkType: hard + "web3-providers-http@npm:1.10.0": version: 1.10.0 resolution: "web3-providers-http@npm:1.10.0" @@ -46024,6 +46251,18 @@ __metadata: languageName: node linkType: hard +"web3-providers-http@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-providers-http@npm:4.0.5" + dependencies: + cross-fetch: ^3.1.5 + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + checksum: d406e4b9c661a3d8bf687f71fc8663a2433786531b58b3b8224c59335766e61a068bc3bfda7dff6dea441dcc85db082f91d433addff4f1523011d555782e3356 + languageName: node + linkType: hard + "web3-providers-ipc@npm:1.10.0": version: 1.10.0 resolution: "web3-providers-ipc@npm:1.10.0" @@ -46094,6 +46333,17 @@ __metadata: languageName: node linkType: hard +"web3-providers-ipc@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-providers-ipc@npm:4.0.5" + dependencies: + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + checksum: 90746b699e1ea884c816618ff6ac98330557c9f7cc3ca0c9b033dcac9b7979b354acbaf05b8e37d1c0d05f386042b4c54b3f1bbeccf89d67cda45829843ba560 + languageName: node + linkType: hard + "web3-providers-ws@npm:1.10.0": version: 1.10.0 resolution: "web3-providers-ws@npm:1.10.0" @@ -46171,6 +46421,31 @@ __metadata: languageName: node linkType: hard +"web3-providers-ws@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-providers-ws@npm:4.0.5" + dependencies: + "@types/ws": 8.5.3 + isomorphic-ws: ^5.0.0 + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + ws: ^8.8.1 + checksum: 59d0d9cdfd0183744c7b0650cdb219a2a115c0fd5a8ba7e691a44404e2704f906726a04956970092c54ddf915fe6ed75831ffc6ed4dad1175c444fb63cca755a + languageName: node + linkType: hard + +"web3-rpc-methods@npm:^1.1.1": + version: 1.1.1 + resolution: "web3-rpc-methods@npm:1.1.1" + dependencies: + web3-core: ^4.1.1 + web3-types: ^1.1.1 + web3-validator: ^2.0.1 + checksum: ce9ebe6ae06903732246fdf3798d26b834428530cb4d0ad569c3913f955856e4ce8c57210a713946514e8780521b19f4f677725b625c57791140cf849048f074 + languageName: node + linkType: hard + "web3-shh@npm:1.10.0": version: 1.10.0 resolution: "web3-shh@npm:1.10.0" @@ -46262,6 +46537,13 @@ __metadata: languageName: node linkType: hard +"web3-types@npm:^1.1.1": + version: 1.1.1 + resolution: "web3-types@npm:1.1.1" + checksum: d477bf427581ed7350129d9c79958321afdbcb0f2ac9a956fc00ff1c534fc021d8fe8c9b8fb787926f7c487a29074bc85bda7eee3098e5abbe91fbb3e11b89f0 + languageName: node + linkType: hard + "web3-utils@npm:1.10.0, web3-utils@npm:^1.0.0-beta.31": version: 1.10.0 resolution: "web3-utils@npm:1.10.0" @@ -46380,6 +46662,18 @@ __metadata: languageName: node linkType: hard +"web3-utils@npm:4.0.5, web3-utils@npm:^4.0.5": + version: 4.0.5 + resolution: "web3-utils@npm:4.0.5" + dependencies: + ethereum-cryptography: ^2.0.0 + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + web3-validator: ^2.0.1 + checksum: bf4b49b6aaec4444d430e89a84ec4adede828ff363be941c5ba0b13781c8778bbbb4a2f58133173c245b42c8133612e27591ec58558c8507a96f5b0b9c780439 + languageName: node + linkType: hard + "web3-validator@npm:^1.0.2": version: 1.0.2 resolution: "web3-validator@npm:1.0.2" @@ -46393,6 +46687,19 @@ __metadata: languageName: node linkType: hard +"web3-validator@npm:^2.0.1": + version: 2.0.1 + resolution: "web3-validator@npm:2.0.1" + dependencies: + ethereum-cryptography: ^2.0.0 + util: ^0.12.5 + web3-errors: ^1.1.1 + web3-types: ^1.1.1 + zod: ^3.21.4 + checksum: 4382a0d034751ca04d3f919efd4f1115abecee8541585179158662e46dd10b051c4ee29c885067d7aeff618dec971c4a2075baeb7df63249e3ed5b2d5695b782 + languageName: node + linkType: hard + "web3@npm:1.10.0": version: 1.10.0 resolution: "web3@npm:1.10.0" @@ -46483,6 +46790,30 @@ __metadata: languageName: node linkType: hard +"web3@npm:4.1.1": + version: 4.1.1 + resolution: "web3@npm:4.1.1" + dependencies: + web3-core: ^4.1.1 + web3-errors: ^1.1.1 + web3-eth: ^4.1.1 + web3-eth-abi: ^4.1.1 + web3-eth-accounts: ^4.0.5 + web3-eth-contract: ^4.0.5 + web3-eth-ens: ^4.0.5 + web3-eth-iban: ^4.0.5 + web3-eth-personal: ^4.0.5 + web3-net: ^4.0.5 + web3-providers-http: ^4.0.5 + web3-providers-ws: ^4.0.5 + web3-rpc-methods: ^1.1.1 + web3-types: ^1.1.1 + web3-utils: ^4.0.5 + web3-validator: ^2.0.1 + checksum: 572d728920c3cf3a254cda835a8f7dc80de46be48f06814c052f1399c6163a26dfc8e8a565ce17468c1d20761671d40efa77bdeb77cec9efea8e7796b83fd26d + languageName: node + linkType: hard + "web3@npm:^1.2.4": version: 1.10.1 resolution: "web3@npm:1.10.1" @@ -47401,7 +47732,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.4.2": +"ws@npm:^8.4.2, ws@npm:^8.8.1": version: 8.13.0 resolution: "ws@npm:8.13.0" peerDependencies: @@ -48083,6 +48414,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.21.4": + version: 3.22.2 + resolution: "zod@npm:3.22.2" + checksum: 231e2180c8eabb56e88680d80baff5cf6cbe6d64df3c44c50ebe52f73081ecd0229b1c7215b9552537f537a36d9e36afac2737ddd86dc14e3519bdbc777e82b9 + languageName: node + linkType: hard + "zone.js@npm:0.11.4": version: 0.11.4 resolution: "zone.js@npm:0.11.4"