From 2e94ef8d3b34449c7b4d48e37d81245851477a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Augusto?= Date: Tue, 26 Apr 2022 11:56:35 +0100 Subject: [PATCH] feat(odap-plugin): odap crash recovery first implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Addition of retries when making requests * Addition of timeouts when making requests * Addition of endpoints for recovery procedure * Tests with crashes in the first phase of the protocol * Addition of logging in local SQLite database * IPFS is now only to store hashes and signatures of the logs and proofs/claims * Migrate odap-api-call-with-ledger-connector.test from tape to jest * Addition of rollbacks when counter party gateway is and is not crashed * Update README.md with a new protocol image and the description of each test file Signed-off-by: AndrĂ© Augusto --- .cspell.json | 1 + .gitignore | 3 + .taprc | 1 - jest.config.js | 1 - packages/cactus-plugin-odap-hermes/README.md | 149 +- .../cactus-plugin-odap-hermes/knex/knex.js | 4 + .../knex/knexfile.ts | 15 + .../20220331132128_create_logs_table.js | 15 + .../cactus-plugin-odap-hermes/package.json | 2 + .../src/main/json/openapi.json | 802 +++++++--- .../typescript/gateway/client/commit-final.ts | 98 +- .../gateway/client/commit-preparation.ts | 79 +- .../gateway/client/lock-evidence.ts | 89 +- .../gateway/client/transfer-commence.ts | 83 +- .../gateway/client/transfer-complete.ts | 45 +- .../gateway/client/transfer-initialization.ts | 81 +- .../typescript/gateway/plugin-odap-gateway.ts | 1312 ++++++++++++++--- .../gateway/recovery/recover-success.ts | 96 ++ .../gateway/recovery/recover-update-ack.ts | 97 ++ .../gateway/recovery/recover-update.ts | 135 ++ .../typescript/gateway/recovery/recover.ts | 99 ++ .../gateway/recovery/rollback-ack.ts | 92 ++ .../typescript/gateway/recovery/rollback.ts | 94 ++ .../typescript/gateway/server/commit-final.ts | 131 +- .../gateway/server/commit-preparation.ts | 106 +- .../gateway/server/lock-evidence.ts | 122 +- .../gateway/server/transfer-commence.ts | 110 +- .../gateway/server/transfer-complete.ts | 53 +- .../gateway/server/transfer-initialization.ts | 109 +- .../openapi/typescript-axios/.gitignore | 4 + .../.openapi-generator-ignore | 4 +- .../generated/openapi/typescript-axios/api.ts | 811 +++++++++- .../client-side/client-request-endpoint.ts | 6 +- .../commit-final-response-endpoint.ts | 6 +- .../commite-prepare-response-endpoint.ts | 6 +- .../lock-evidence-response-endpoint.ts | 6 +- .../transfer-commence-response-endpoint.ts | 6 +- .../transfer-initiation-response-endpoint.ts | 6 +- .../recovery/recover-message-endpoint.ts | 103 ++ .../recover-success-message-endpoint.ts | 103 ++ .../recover-update-ack-message-endpoint.ts | 105 ++ .../recover-update-message-endpoint.ts | 103 ++ .../recovery/rollback-ack-message-endpoint.ts | 103 ++ .../recovery/rollback-message-endpoint.ts | 103 ++ .../commit-final-request-endpoint.ts | 6 +- .../commite-prepare-request-endpoint.ts | 6 +- .../lock-evidence-request-endpoint.ts | 6 +- .../transfer-commence-request-endpoint.ts | 8 +- .../transfer-complete-request-endpoint.ts | 6 +- .../transfer-initiation-request-endpoint.ts | 6 +- .../lock-asset-contract/LockAsset.json | 1270 +++++++++------- .../lock-asset-contract/lock-asset.sol | 34 +- .../chaincode-typescript/src/asset.ts | 4 +- .../chaincode-typescript/src/assetTransfer.ts | 53 +- .../client-crash-after-delete-asset.test.ts | 920 ++++++++++++ .../client-crash-after-lock-asset.test.ts | 854 +++++++++++ ...nt-crash-after-transfer-initiation.test.ts | 313 ++++ ...dap-api-call-with-ledger-connector.test.ts | 547 ++++--- .../integration/odap-api-call.test.ts | 485 +++--- .../integration/odap-rollback.test.ts | 947 ++++++++++++ .../test/typescript/integration/odap.test.ts | 384 +++++ .../server-crash-after-create-asset.test.ts | 943 ++++++++++++ ...er-crash-after-transfer-initiation.test.ts | 276 ++++ .../src/test/typescript/knex.config.ts | 21 + .../test/typescript/make-checks-ledgers.ts | 85 ++ .../src/test/typescript/make-checks.ts | 286 ++++ .../unit/client/commit-final.test.ts | 183 ++- .../unit/client/commit-preparation.test.ts | 76 +- .../unit/client/lock-evidence.test.ts | 79 +- .../unit/client/transfer-commence.test.ts | 93 +- ...est.ts => transfer-initialization.test.ts} | 86 +- .../typescript/unit/recovery/logging.test.ts | 448 ++++++ .../unit/recovery/recover-success.test.ts | 169 +++ .../unit/recovery/recover-update-ack.test.ts | 171 +++ .../unit/recovery/recover-update.test.ts | 276 ++++ .../typescript/unit/recovery/recover.test.ts | 208 +++ .../unit/server/commit-final.test.ts | 203 ++- .../unit/server/commit-preparation.test.ts | 79 +- .../unit/server/lock-evidence.test.ts | 194 ++- .../unit/server/transfer-commence.test.ts | 91 +- .../unit/server/transfer-complete.test.ts | 29 +- ...est.ts => transfer-initialization.test.ts} | 86 +- 82 files changed, 12947 insertions(+), 2483 deletions(-) create mode 100644 packages/cactus-plugin-odap-hermes/knex/knex.js create mode 100644 packages/cactus-plugin-odap-hermes/knex/knexfile.ts create mode 100644 packages/cactus-plugin-odap-hermes/knex/migrations/20220331132128_create_logs_table.js create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-success.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update-ack.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback-ack.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.gitignore create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-message-endpoint.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-success-message-endpoint.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-update-ack-message-endpoint.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-update-message-endpoint.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/rollback-ack-message-endpoint.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/rollback-message-endpoint.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-delete-asset.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-lock-asset.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-transfer-initiation.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-rollback.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-create-asset.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-transfer-initiation.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/knex.config.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks-ledgers.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks.ts rename packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/{transfer-initiation.test.ts => transfer-initialization.test.ts} (65%) create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/logging.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-success.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update-ack.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update.test.ts create mode 100644 packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover.test.ts rename packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/{transfer-initiation.test.ts => transfer-initialization.test.ts} (68%) diff --git a/.cspell.json b/.cspell.json index e31b15cc66..ea9bf98ec5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -106,6 +106,7 @@ "protoc", "protos", "qscc", + "recoverupdateackmessage", "RUSTC", "Rwset", "sbjpubkey", diff --git a/.gitignore b/.gitignore index fc270357a6..5f2c1cb38d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ coverage/ # next.js build output .next +# sqlite database files +**/*.sqlite3 + # vscode files .vscode/* !.vscode/template.launch.json diff --git a/.taprc b/.taprc index f7214c55a8..30dbedc36f 100644 --- a/.taprc +++ b/.taprc @@ -6,7 +6,6 @@ timeout: 900 ts: true files: - ./packages/cactus-plugin-keychain-aws-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts - - ./packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call-with-ledger-connector.test.ts - ./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/deploy-lock-asset.test.ts - ./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/lock-contract.test.ts - ./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-with-ws-ids.test.ts diff --git a/jest.config.js b/jest.config.js index 2deeedd651..052bbc84f5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,7 +11,6 @@ module.exports = { // Ignore the tests that are still using tap/tape for as their test runner testPathIgnorePatterns: [ `./packages/cactus-plugin-keychain-aws-sm/src/test/typescript/integration/openapi/openapi-validation.test.ts`, - `./packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call-with-ledger-connector.test.ts`, `./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/deploy-lock-asset.test.ts`, `./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/lock-contract.test.ts`, `./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-with-ws-ids.test.ts`, diff --git a/packages/cactus-plugin-odap-hermes/README.md b/packages/cactus-plugin-odap-hermes/README.md index 86e3c638c3..dc71448a0e 100644 --- a/packages/cactus-plugin-odap-hermes/README.md +++ b/packages/cactus-plugin-odap-hermes/README.md @@ -1,16 +1,28 @@ # @hyperledger/cactus-plugin-odap-hermes -The package provides `Cactus` a way to standardize cross-chain transactions between two ledgers (Fabric and Besu in this implementation). Using this we could perform: -- Make unidirectional atomic asset transfer between 2 parties in different ledgers. +The package provides `Cactus` a way to standardize cross-chain transactions between two ledgers (Fabric and Besu in this implementation). Using this we can perform: +- A unidirectional atomic asset transfer between 2 parties in different ledgers. - Lock of the asset in the source ledger and proof is sent to the counterparty. - Extinguishment of the asset in the source blockchain and regeneration of the asset in the recipient blockchain. + +At the moment, we assume a crash-fault environment under some assumptions detailed in section [Assumptions](#assumptions) ## Summary + - [Assumptions](#assumptions) - [Getting Started](#getting-started) - [Architecture](#architecture) - [Use Case](#use-case) - [Running the tests](#running-the-tests) - [Usage](#usage) + +## Assumptions +Regarding the crash recovery procedure in place, at the moment we only support crashes of gateways under certain assumptions detailed as follows: + - Gateways crash only after receiving a message (and before sending the next one) + - Gateways crash only after logging to the Log Storage the previously received message + - Gateways never loose their long term keys + - Gateways do not have byzantine behavior + - Gateways are assumed to always recover from a crash + ## 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 @@ -25,73 +37,133 @@ npm run configure Know how to use the following plugins of the project: -- [cactus-plugin-ledger-connector-fabric](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-ledger-connector-fabric) -- [cactus-plugin-ledger-connector-besu](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-ledger-connector-besu) -- [cactus-plugin-object-store-ipfs](https://github.com/hyperledger/cactus/tree/main/extensions/cactus-plugin-object-store-ipfs) + - [cactus-plugin-ledger-connector-fabric](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-ledger-connector-fabric) + - [cactus-plugin-ledger-connector-besu](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-ledger-connector-besu) + - [cactus-plugin-object-store-ipfs](https://github.com/hyperledger/cactus/tree/main/extensions/cactus-plugin-object-store-ipfs) ## Architecture -The sequence diagram of ODAP is pictured above. +### Entities +Firstly let us identify the different entities involved in the protocol and what is their function: +- Two gateways in each side of the protocol: they implement endpoints to exchange the messages defined in the protocol. +- Ledgers connectors in each side (each connected to a different gateway): they expose the API so that gateways can interact the respective ledgers (e.g., locking, deleting and creating assets). +- SQLite3 database: persistent log and proofs storage in each gateway. +- IPFS connector: is exposed the API so that both gateways have access to the same structure. This is used to store the hashes and signatures of the logs and proofs, so that accountability is guaranteed. -![odap-sequence-diagram](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gICAgcGFydGljaXBhbnQgRW5kVXNlclxuICAgIHBhcnRpY2lwYW50IENsaWVudE9EQVBHYXRld2F5XG4gICAgcGFydGljaXBhbnQgSHlwZXJsZWRnZXJGYWJyaWNcbiAgICBwYXJ0aWNpcGFudCBTZXJ2ZXJPREFQR2F0ZXdheVxuICAgIHBhcnRpY2lwYW50IEh5cGVybGVkZ2VyQmVzdVxuICAgIEVuZFVzZXItPj5DbGllbnRPREFQR2F0ZXdheTogc2VuZCBjbGllbnQgcmVxdWVzdFxuICAgIENsaWVudE9EQVBHYXRld2F5LT4-U2VydmVyT0RBUEdhdGV3YXk6ICB0cmFuc2ZlciBpbml0aWF0aW9uIHJlcXVlc3RcbiAgICBTZXJ2ZXJPREFQR2F0ZXdheS0tPj5DbGllbnRPREFQR2F0ZXdheTogdHJhbnNmZXIgaW5pdGlhdGlvbiBhY2tcbiAgICBDbGllbnRPREFQR2F0ZXdheS0-PlNlcnZlck9EQVBHYXRld2F5OiAgdHJhbnNmZXIgY29tbWVuY2UgcmVxdWVzdFxuICAgIFNlcnZlck9EQVBHYXRld2F5LS0-PkNsaWVudE9EQVBHYXRld2F5OiB0cmFuc2ZlciBjb21tZW5jZSBhY2tcbiAgICBDbGllbnRPREFQR2F0ZXdheS0-Pkh5cGVybGVkZ2VyRmFicmljOiBsb2NrIGFzc2V0XG4gICAgSHlwZXJsZWRnZXJGYWJyaWMtLT4-Q2xpZW50T0RBUEdhdGV3YXk6IHRyYW5zYWN0aW9uIHJlY2VpcHQgZm9yIGxvY2tpbmcgYXNzZXRcbiAgICBDbGllbnRPREFQR2F0ZXdheS0-PlNlcnZlck9EQVBHYXRld2F5OiAgbG9jayBldmlkZW5jZSByZXF1ZXN0XG4gICAgU2VydmVyT0RBUEdhdGV3YXktPj5DbGllbnRPREFQR2F0ZXdheTogbG9jayBldmlkZW5jZSBhY2tcbiAgICBDbGllbnRPREFQR2F0ZXdheS0-PlNlcnZlck9EQVBHYXRld2F5OiAgY29tbWl0IHByZXBhcmUgcmVxdWVzdFxuICAgIFNlcnZlck9EQVBHYXRld2F5LS0-PkNsaWVudE9EQVBHYXRld2F5OiBjb21taXQgcHJlcGFyZSBhY2tcbiAgICBDbGllbnRPREFQR2F0ZXdheS0-Pkh5cGVybGVkZ2VyRmFicmljOiBkZWxldGUgYXNzZXRcbiAgICBIeXBlcmxlZGdlckZhYnJpYy0tPj5DbGllbnRPREFQR2F0ZXdheTogdHJhbnNhY3Rpb24gcmVjZWlwdCBmb3IgZGVsZXRpbmcgYXNzZXRcbiAgICBDbGllbnRPREFQR2F0ZXdheS0-PlNlcnZlck9EQVBHYXRld2F5OiAgY29tbWl0IGZpbmFsIHJlcXVlc3RcbiAgICBTZXJ2ZXJPREFQR2F0ZXdheS0-Pkh5cGVybGVkZ2VyQmVzdTogY3JlYXRlIGFzc2V0XG4gICAgSHlwZXJsZWRnZXJCZXN1LS0-PlNlcnZlck9EQVBHYXRld2F5OiB0cmFuc2FjdGlvbiByZWNlaXB0IGZvciBjcmVhdGluZyBhc3NldFxuICAgIFNlcnZlck9EQVBHYXRld2F5LS0-PkNsaWVudE9EQVBHYXRld2F5OiBjb21taXQgZmluYWwgYWNrXG4gICAgQ2xpZW50T0RBUEdhdGV3YXktPj5TZXJ2ZXJPREFQR2F0ZXdheTogIHRyYW5zZmVyIGNvbXBsZXRlXG4gICAgQ2xpZW50T0RBUEdhdGV3YXktLT4-RW5kVXNlcjogIHNlbmQgY2xpZW50IGFja1xuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOnRydWUsImF1dG9TeW5jIjp0cnVlLCJ1cGRhdGVEaWFncmFtIjp0cnVlfQ "odap-sequence-diagram") +The sequence diagram of ODAP is pictured below. + +![odap-sequence-diagram](https://imgur.com/a/NN62LyL) ### API Endpoints -This plugin uses OpenAPI to generate the API paths. At the moment, only the server endpoints are defined: - -- Phase1TransferInitiationV1 -- Phase2TransferCommenceV1 -- Phase2LockEvidenceV1 -- Phase3CommitPreparationV1 -- Phase3CommitFinalV1 -- Phase3TransferCompleteV1 -- SendClientRequestV1 +This plugin uses OpenAPI to generate the API paths. +There are Client and Server Endpoints for each type of message detailed in the ODAP protocol: + + - TransferInitializationV1Request + - TransferInitializationV1Response + - TransferCommenceV1Request + - TransferCommenceV1Response + - LockEvidenceV1Request + - LockEvidenceV1Response + - CommitPreparationV1Request + - CommitPreparationV1Response + - CommitFinalV1Request + - CommitFinalV1Response + - TransferCompleteV1Request + - ClientV1Request + +There are also defined the endpoints for the crash recovery procedure (there is still missing the endpoint to receive the Rollback mesage): + - RecoverV1Message + - RecoverUpdateV1Message + - RecoverUpdateAckV1Message + - RecoverSuccessV1Message + - RollbackV1Message + ## Use case Alice and Bob, in blockchains A and B, respectively, want to make a transfer of an asset from one to the other. Gateway A represents the gateway connected to Alice's blockchain. Gateway B represents the gateway connected to Bob's blockchain. Alice and Bob will run ODAP, which will execute the transfer of the asset from blockchain A to blockchain B. The above endpoints will be called in sequence. Notice that the asset will first be locked on blockchain A and a proof is sent to the server-side. Afterward, the asset on the original blockchain is extinguished, followed by its regeneration on blockchain B. ## Running the tests -A test with ledger connectors (Fabric and Besu): +For developers that want to test separate steps/phases of the ODAP protocol, please refer to the following test files (client and server side along with the recovery procedure): + +https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/ + +A test of the entire protocol with manual calls to the methods, i.e. without ledger connectors and Open API: + +https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-test.ts + +A test of the entire protocol using Open API but with no ledger connectors: + +https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call.test.ts + +A test of the entire protocol with ledger connectors (Fabric and Besu) and Open API: https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/odap-api-call-with-ledger-connector.test.ts -Without ledger connectors: +A test with a simulated crash of the client gateway after the transfer initiation flow: + +https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/client-crash-after-transfer-initiation.test.ts + +A test with a simulated crash of the client gateway after the lock of the asset in the source blockchain (Fabric): + +https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/client-crash-after-lock-asset.test.ts + +A test with a simulated crash of the client gateway after the deletion of the asset in the source blockchain (Fabric): + +https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/client-crash-after-delete-asset.test.ts + +A test with a simulated crash of the server gateway after the creation of the the asset in the recipient blockchain (Besu): + +https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/server-crash-after-create-asset.test.ts -https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/odap-api-call.test.ts +A test with a simulated crash of the server gateway after the transfer initiation flow: -For developers who would want to test separate steps of odap please refer to other test files in: +https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/server-crash-after-transfer-initiation.test.ts -https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap +A test with a rollback after a timeout (client crashed): + +https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/odap-rollback.test.ts ## Usage -Let us consider: +Let us consider two gateways. The client gateway connected to Hyperledger Fabric and the server gateway connected to Hyperledger Besu. Let us also consider: -- A Hyperledger Fabric API client on URL: http://localhost:8045 -- A Hyperledger Besu API client on URL: http://localhost:8046 -- An IPFS API client on URL: http://localhost:8047 + - A Hyperledger Fabric API client on URL: http://localhost:8045 + - A Hyperledger Besu API client on URL: http://localhost:8046 + - An IPFS API client on URL: http://localhost:8047 + - The local databases configuration provided in the file [knex.config.ts](https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/knex.config.ts) (if running locally both gateways will use the same database file so it is not advised to leave it as the default if that is the case) -Then the ODAP gateway should be created as follows: +Then the ODAP gateways should be created as follows: -``` -const odapPluginOptions: OdapGatewayConstructorOptions = { +```typescript +const clientOdapPluginOptions: OdapGatewayConstructorOptions = { name: "cactus-plugin#odapGateway", - dltIDs: ["supported dlts here"], + dltIDs: ["DLT2"], instanceId: uuidV4(), - ipfsPath: http://localhost:8047, + ipfsPath: "http://localhost:8047", + fabricPath: "http://localhost:8045", + fabricSigningCredential: fabricSigningCredential, + fabricChannelName: fabricChannelName, + fabricContractName: fabricContractName, + fabricAssetID: fabricAssetID, + knexConfig: knexClientConnection, // if not specified the default will be used + }; + +const serverOdapPluginOptions: OdapGatewayConstructorOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidV4(), + ipfsPath: "http://localhost:8047", besuAssetID: "whatever", - besuPath: http://localhost:8046, + besuPath: "http://localhost:8046", besuWeb3SigningCredential: besuWeb3SigningCredential, besuContractName: besuContractName, besuKeychainId: besuKeychainId, - fabricPath: http://localhost:8045, - fabricSigningCredential: fabricSigningCredential, - fabricChannelName: fabricChannelName, - fabricContractName: fabricContractName, - fabricAssetID: fabricAssetID, + knexConfig: knexServerConnection, // if not specified the default will be used }; - const odapGateway = new OdapGateway(odapPluginOptions); + const clientGateway = new OdapGateway(clientOdapPluginOptions); + const serverGateway = new OdapGateway(serverOdapPluginOptions); ``` ## Contributing @@ -101,6 +173,3 @@ Please review [CONTIRBUTING.md](https://github.com/hyperledger/cactus/blob/main/ ## License This distribution is published under the Apache License Version 2.0 found in the [LICENSE ](https://github.com/hyperledger/cactus/blob/main/LICENSE "LICENSE ")file. - - - diff --git a/packages/cactus-plugin-odap-hermes/knex/knex.js b/packages/cactus-plugin-odap-hermes/knex/knex.js new file mode 100644 index 0000000000..2a8d14f785 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/knex/knex.js @@ -0,0 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const environment = process.env.ENVIRONMENT || "development"; +const config = require("../knexfile.js")[environment]; +module.exports = require("knex")(config); diff --git a/packages/cactus-plugin-odap-hermes/knex/knexfile.ts b/packages/cactus-plugin-odap-hermes/knex/knexfile.ts new file mode 100644 index 0000000000..d161578d95 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/knex/knexfile.ts @@ -0,0 +1,15 @@ +import path from "path"; + +// default configuration for knex +module.exports = { + development: { + client: "sqlite3", + connection: { + filename: path.resolve(__dirname, ".dev.sqlite3"), + }, + migrations: { + directory: path.resolve(__dirname, "migrations"), + }, + useNullAsDefault: true, + }, +}; diff --git a/packages/cactus-plugin-odap-hermes/knex/migrations/20220331132128_create_logs_table.js b/packages/cactus-plugin-odap-hermes/knex/migrations/20220331132128_create_logs_table.js new file mode 100644 index 0000000000..94c6d8712f --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/knex/migrations/20220331132128_create_logs_table.js @@ -0,0 +1,15 @@ +exports.up = async (knex) => { + return await knex.schema.createTable("logs", function (table) { + table.string("sessionID").notNullable(); + table.string("type").notNullable(); + table.string("key").notNullable(); + table.string("operation").notNullable(); + table.string("timestamp").notNullable(); + table.string("data").notNullable(); + table.primary("key"); + }); +}; + +exports.down = async (knex) => { + return await knex.schema.dropTable("logs"); +}; diff --git a/packages/cactus-plugin-odap-hermes/package.json b/packages/cactus-plugin-odap-hermes/package.json index acb6fd6720..38131de725 100644 --- a/packages/cactus-plugin-odap-hermes/package.json +++ b/packages/cactus-plugin-odap-hermes/package.json @@ -57,8 +57,10 @@ "@hyperledger/cactus-test-tooling": "1.0.0", "axios": "0.21.4", "crypto-js": "4.0.0", + "knex": "2.0.0", "secp256k1": "4.0.2", "socket.io": "4.4.1", + "sqlite3": "5.0.2", "typescript-optional": "2.0.1", "web3": "1.5.2", "web3-utils": "1.5.2" diff --git a/packages/cactus-plugin-odap-hermes/src/main/json/openapi.json b/packages/cactus-plugin-odap-hermes/src/main/json/openapi.json index 56a6506ffb..f465ea58ed 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/json/openapi.json +++ b/packages/cactus-plugin-odap-hermes/src/main/json/openapi.json @@ -165,162 +165,6 @@ "CommitAcknowledgementClaimFormat":{ "type":"object" }, - "TransferInitializationV1Request":{ - "type":"object", - "properties":{ - "messageType":{ - "type": "string" - }, - "sessionID":{ - "type":"string" - }, - "version":{ - "type":"string" - }, - "developerURN":{ - "type":"string" - }, - "credentialProfile":{ - "$ref":"#/components/schemas/CredentialProfile" - }, - "payloadProfile":{ - "$ref": "#/components/schemas/PayloadProfile" - }, - "applicationProfile":{ - "type": "string" - }, - "loggingProfile":{ - "type": "string" - }, - "accessControlProfile":{ - "type": "string" - }, - "clientSignature":{ - "type":"string" - }, - "sourceGatewayPubkey":{ - "type":"string" - }, - "sourceGatewayDltSystem":{ - "type":"string" - }, - "recipientGatewayPubkey":{ - "type":"string" - }, - "recipientGatewayDltSystem":{ - "type":"string" - }, - "escrowType":{ - "type":"string", - "enum":[ - "FAUCET", - "TIMELOCK", - "HASHLOCK", - "HASHTIMELOCK", - "MULTICLAIMPC", - "DESTROY", - "BURN" - ] - }, - "expiryTime":{ - "type":"string" - }, - "multipleClaimsAllowed":{ - "type":"boolean" - }, - "multipleCancelsAllowed":{ - "type":"boolean" - }, - "permissions":{ - "$ref":"#/components/schemas/Permissions" - }, - "origin":{ - "type":"string" - }, - "destination":{ - "type":"string" - }, - "subsequentCalls":{ - "type":"object" - }, - "histories":{ - "type":"array", - "items":{ - "$ref":"#/components/schemas/History" - } - }, - "sequenceNumber":{ - "type":"integer" - }, - "sourceGatewayPath": { - "type": "string" - } - }, - "required": [ - "messageType", - "sessionID", - "loggingProfile", - "accessControlProfile", - "payloadProfile", - "applicationProfile", - "clientSignature", - "sourceGatewayPubkey", - "sourceGatewayDltSystem", - "recipientGatewayPubkey", - "recipientGatewayDltSystem", - "sequenceNumber" - ] - }, - "TransferInitializationV1Response":{ - "type":"object", - "properties": { - "messageType":{ - "type": "string" - }, - "sessionID":{ - "type":"string" - }, - "sequenceNumber":{ - "type":"number" - }, - "odapPhase":{ - "type":"string", - "enum":[ - "TransferInitialization", - "LockEvidenceVerification", - "CommitmentEstablishment" - ] - }, - "initialRequestMessageHash":{ - "type":"string" - }, - "destination":{ - "type":"string" - }, - "timeStamp":{ - "type":"string" - }, - "processedTimeStamp":{ - "type":"string" - }, - "serverIdentityPubkey":{ - "type":"string" - }, - "serverSignature":{ - "type":"string" - } - }, - "required": [ - "messageType", - "initialRequestMessageHash", - "timeStamp", - "processedTimeStamp", - "sessionID", - "serverIdentityPubkey", - "sequenceNumber", - "serverSignature" - ] - }, "SessionData":{ "type":"object", "properties":{ @@ -462,34 +306,230 @@ "clientSignatureTransferCompleteMessage": { "type":"string" }, + "maxRetries":{ + "type": "number" + }, "besuAssetID": { "type":"string" }, "fabricAssetID": { "type":"string" }, - "isFabricAssetDeleted": { - "type":"boolean" + "fabricAssetSize": { + "type":"string" }, - "isFabricAssetLocked": { - "type":"boolean" + "maxTimeout": { + "type": "number" }, - "isFabricAssetCreated": { - "type":"boolean" + "lastLogEntryTimestamp": { + "type": "string" }, - "isBesuAssetCreated": { - "type":"boolean" + "unlockAssetClaim": { + "type": "string" + }, + "recreateAssetClaim": { + "type": "string" + }, + "deleteAssetClaim": { + "type": "string" }, - "isBesuAssetDeleted": { + "lastMessageReceivedTimestamp": { + "type": "string" + }, + "rollback": { + "type": "boolean" + }, + "rollbackMessageHash": { + "type": "string" + }, + "rollbackProofs": { + "type": "array", + "items": { + "type": "string" + } + }, + "rollbackActionsPerformed": { + "type": "array", + "items": { + "type":"string", + "enum": [ + "CREATE", + "DELETE", + "LOCK", + "UNLOCK" + ] + } + } + } + }, + "TransferInitializationV1Request":{ + "type":"object", + "properties":{ + "messageType":{ + "type": "string" + }, + "sessionID":{ + "type":"string" + }, + "version":{ + "type":"string" + }, + "developerURN":{ + "type":"string" + }, + "credentialProfile":{ + "$ref":"#/components/schemas/CredentialProfile" + }, + "payloadProfile":{ + "$ref": "#/components/schemas/PayloadProfile" + }, + "applicationProfile":{ + "type": "string" + }, + "loggingProfile":{ + "type": "string" + }, + "accessControlProfile":{ + "type": "string" + }, + "signature":{ + "type":"string" + }, + "sourceGatewayPubkey":{ + "type":"string" + }, + "sourceGatewayDltSystem":{ + "type":"string" + }, + "recipientGatewayPubkey":{ + "type":"string" + }, + "recipientGatewayDltSystem":{ + "type":"string" + }, + "escrowType":{ + "type":"string", + "enum":[ + "FAUCET", + "TIMELOCK", + "HASHLOCK", + "HASHTIMELOCK", + "MULTICLAIMPC", + "DESTROY", + "BURN" + ] + }, + "expiryTime":{ + "type":"string" + }, + "multipleClaimsAllowed":{ "type":"boolean" }, - "isBesuAssetLocked": { + "multipleCancelsAllowed":{ "type":"boolean" }, - "fabricAssetSize": { + "permissions":{ + "$ref":"#/components/schemas/Permissions" + }, + "origin":{ + "type":"string" + }, + "destination":{ + "type":"string" + }, + "subsequentCalls":{ + "type":"object" + }, + "histories":{ + "type":"array", + "items":{ + "$ref":"#/components/schemas/History" + } + }, + "sequenceNumber":{ + "type":"integer" + }, + "sourceGatewayPath": { + "type": "string" + }, + "recipientBasePath": { + "type": "string" + }, + "maxRetries": { + "type": "number" + }, + "maxTimeout": { + "type": "number" + } + }, + "required": [ + "messageType", + "sessionID", + "loggingProfile", + "accessControlProfile", + "payloadProfile", + "applicationProfile", + "signature", + "sourceGatewayPubkey", + "sourceGatewayDltSystem", + "recipientGatewayPubkey", + "recipientGatewayDltSystem", + "sequenceNumber", + "sourceBasePath", + "recipientBasePath", + "maxRetries", + "maxTimeout" + ] + }, + "TransferInitializationV1Response":{ + "type":"object", + "properties": { + "messageType":{ + "type": "string" + }, + "sessionID":{ + "type":"string" + }, + "sequenceNumber":{ + "type":"number" + }, + "odapPhase":{ + "type":"string", + "enum":[ + "TransferInitialization", + "LockEvidenceVerification", + "CommitmentEstablishment" + ] + }, + "initialRequestMessageHash":{ + "type":"string" + }, + "destination":{ + "type":"string" + }, + "timeStamp":{ + "type":"string" + }, + "processedTimeStamp":{ + "type":"string" + }, + "serverIdentityPubkey":{ + "type":"string" + }, + "signature":{ "type":"string" } - } + }, + "required": [ + "messageType", + "initialRequestMessageHash", + "timeStamp", + "processedTimeStamp", + "sessionID", + "serverIdentityPubkey", + "sequenceNumber", + "signature" + ] }, "TransferCommenceV1Request":{ "type":"object", @@ -531,7 +571,7 @@ "type":"integer", "nullable": true }, - "clientSignature":{ + "signature":{ "type":"string" }, "sequenceNumber":{ @@ -549,7 +589,7 @@ "serverIdentityPubkey", "hashAssetProfile", "hashPrevMessage", - "clientSignature", + "signature", "sequenceNumber" ] }, @@ -572,7 +612,7 @@ "type":"integer", "nullable":true }, - "serverSignature":{ + "signature":{ "type":"string" }, "messageType":{ @@ -591,7 +631,7 @@ "serverIdentityPubkey", "clientIdentityPubkey", "hashCommenceRequest", - "serverSignature", + "signature", "sequenceNumber" ] }, @@ -624,7 +664,7 @@ "type":"integer", "nullable":true }, - "clientSignature":{ + "signature":{ "type":"string" }, "messageType":{ @@ -645,7 +685,7 @@ "lockEvidenceClaim", "lockEvidenceExpiration", "hashCommenceAckRequest", - "clientSignature", + "signature", "sequenceNumber" ] }, @@ -668,7 +708,7 @@ "type":"integer", "nullable":true }, - "serverSignature":{ + "signature":{ "type":"string" }, "messageType":{ @@ -684,7 +724,7 @@ "clientIdentityPubkey", "serverIdentityPubkey", "hashLockEvidenceRequest", - "serverSignature", + "signature", "sequenceNumber" ] }, @@ -709,7 +749,7 @@ "clientTransferNumber":{ "type":"integer" }, - "clientSignature":{ + "signature":{ "type":"string" }, "sequenceNumber":{ @@ -722,7 +762,7 @@ "clientIdentityPubkey", "serverIdentityPubkey", "hashLockEvidenceAck", - "clientSignature", + "signature", "sequenceNumber" ] }, @@ -747,7 +787,7 @@ "serverTransferNumber":{ "type":"string" }, - "serverSignature":{ + "signature":{ "type":"string" }, "sequenceNumber":{ @@ -760,7 +800,7 @@ "clientIdentityPubkey", "serverIdentityPubkey", "hashCommitPrep", - "serverSignature", + "signature", "sequenceNumber" ] }, @@ -792,7 +832,7 @@ "type":"integer", "nullable": true }, - "clientSignature":{ + "signature":{ "type":"string" }, "sequenceNumber":{ @@ -806,7 +846,7 @@ "serverIdentityPubkey", "commitFinalClaim", "hashCommitPrepareAck", - "clientSignature", + "signature", "sequenceNumber" ] }, @@ -837,7 +877,7 @@ "serverTransferNumber":{ "type":"integer" }, - "serverSignature":{ + "signature":{ "type":"string" }, "sequenceNumber":{ @@ -851,7 +891,7 @@ "serverIdentityPubkey", "commitAcknowledgementClaim", "hashCommitFinal", - "serverSignature", + "signature", "sequenceNumber" ] }, @@ -877,7 +917,7 @@ "type":"integer", "nullable":true }, - "clientSignature":{ + "signature":{ "type":"string" }, "hashTransferCommence":{ @@ -893,7 +933,7 @@ "clientIdentityPubkey", "serverIdentityPubkey", "hashCommitFinalAck", - "clientSignature", + "signature", "hashTransferCommence", "sequenceNumber" ] @@ -970,6 +1010,12 @@ "required": [ "apiHost" ] + }, + "maxRetries": { + "type": "number" + }, + "maxTimeout": { + "type": "number" } }, "required": [ @@ -991,7 +1037,183 @@ "serverDltSystem", "serverGatewayInstanceID", "clientGatewayConfiguration", - "serverGatewayConfiguration" + "serverGatewayConfiguration", + "maxRetries", + "maxTimeout" + ] + }, + "RecoverV1Message":{ + "type":"object", + "properties": { + "sessionID": { + "type":"string" + }, + "odapPhase": { + "type":"string" + }, + "sequenceNumber": { + "type": "number" + }, + "lastLogEntryTimestamp": { + "type": "string" + }, + "signature": { + "type": "string" + } + }, + "required": [ + "sessionID", + "odapPhase", + "sequenceNumber", + "lastLogEntryHash", + "signature" + ] + }, + "RecoverUpdateV1Message":{ + "type":"object", + "properties": { + "sessionID": { + "type":"string" + }, + "recoveredLogs": { + "type": "array", + "items":{ + "$ref": "#/components/schemas/OdapLocalLog" + } + }, + "signature": { + "type":"string" + } + }, + "required": [ + "sessionID", + "recoveredLogs", + "signature" + ] + }, + "RecoverUpdateAckV1Message":{ + "type":"object", + "properties": { + "sessionID": { + "type":"string" + }, + "success": { + "type": "boolean" + }, + "changedEntriesHash": { + "type": "array", + "items":{ + "type": "string" + } + }, + "signature": { + "type":"string" + } + }, + "required": [ + "sessionID", + "success", + "changedEntriesHash", + "signature" + ] + }, + "RecoverSuccessV1Message":{ + "type":"object", + "properties": { + "sessionID": { + "type":"string" + }, + "success": { + "type": "boolean" + }, + "signature": { + "type":"string" + } + }, + "required": [ + "sessionID", + "success", + "signature" + ] + }, + "RollbackV1Message":{ + "type":"object", + "properties": { + "sessionID": { + "type":"string" + }, + "success": { + "type": "boolean" + }, + "actionPerformed": { + "type": "array", + "items":{ + "type": "string" + } + }, + "proofs": { + "type": "array", + "items":{ + "type": "string" + } + }, + "signature": { + "type": "string" + } + }, + "required": [ + "sessionID", + "success", + "actionPerformed", + "proofs", + "signature" + ] + }, + "RollbackAckV1Message":{ + "type":"object", + "properties": { + "sessionID": { + "type":"string" + }, + "success": { + "type": "boolean" + }, + "signature": { + "type": "string" + } + }, + "required": [ + "sessionID", + "success", + "signature" + ] + }, + "OdapLocalLog": { + "type":"object", + "properties": { + "key": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "data": { + "type": "string" + }, + "type": { + "type": "string" + }, + "operation": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "sessionID", + "type", + "operation" ] }, "OdapMessage":{ @@ -1066,12 +1288,12 @@ } }, "paths": { - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationrequest":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationrequest":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationrequest" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationrequest" } }, "operationId": "phase1TransferInitiationRequestV1", @@ -1092,12 +1314,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommencerequest":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommencerequest":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommencerequest" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommencerequest" } }, "operationId": "phase2TransferCommenceRequestV1", @@ -1118,12 +1340,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidencerequest":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidencerequest":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidencerequest" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidencerequest" } }, "operationId": "phase2LockEvidenceRequestV1", @@ -1144,12 +1366,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationrequest":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationrequest":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationrequest" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationrequest" } }, "operationId": "phase3CommitPreparationRequestV1", @@ -1170,12 +1392,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalrequest":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalrequest":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalrequest" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalrequest" } }, "operationId": "phase3CommitFinalRequestV1", @@ -1196,12 +1418,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/transfercompleterequest":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/transfercompleterequest":{ "get":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "get", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/transfercompleterequest" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/transfercompleterequest" } }, "operationId": "phase3TransferCompleteRequestV1", @@ -1222,12 +1444,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/clientrequest":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/clientrequest":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/clientrequest" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/clientrequest" } }, "operationId": "clientRequestV1", @@ -1254,12 +1476,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationresponse":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationresponse":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationresponse" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationresponse" } }, "operationId": "phase1TransferInitiationResponseV1", @@ -1280,12 +1502,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommenceresponse":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommenceresponse":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommenceresponse" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommenceresponse" } }, "operationId": "phase2TransferCommenceResponseV1", @@ -1306,12 +1528,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidenceresponse":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidenceresponse":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidenceresponse" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidenceresponse" } }, "operationId": "phase2LockEvidenceResponseV1", @@ -1332,12 +1554,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationresponse":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationresponse":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationresponse" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationresponse" } }, "operationId": "phase3CommitPreparationResponseV1", @@ -1358,12 +1580,12 @@ } } }, - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalresponse":{ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalresponse":{ "post":{ "x-hyperledger-cactus": { "http": { "verbLowerCase": "post", - "path": "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalresponse" + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalresponse" } }, "operationId": "phase3CommitFinalResponseV1", @@ -1383,6 +1605,162 @@ } } } + }, + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recovermessage":{ + "post":{ + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recovermessage" + } + }, + "operationId": "RecoverV1Message", + "description":"", + "requestBody":{ + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecoverV1Message" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdatemessage":{ + "post":{ + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdatemessage" + } + }, + "operationId": "RecoverUpdateV1Message", + "description":"", + "requestBody":{ + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecoverUpdateV1Message" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdateackmessage":{ + "post":{ + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdateackmessage" + } + }, + "operationId": "RecoverUpdateAckV1Message", + "description":"", + "requestBody":{ + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecoverUpdateAckV1Message" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoversuccessmessage":{ + "post":{ + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoversuccessmessage" + } + }, + "operationId": "RecoverV1Success", + "description":"", + "requestBody":{ + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecoverSuccessV1Message" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackmessage":{ + "post":{ + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackmessage" + } + }, + "operationId": "RollbackV1Message", + "description":"", + "requestBody":{ + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RollbackV1Message" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackackmessage":{ + "post":{ + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackackmessage" + } + }, + "operationId": "RollbackAckV1Message", + "description":"", + "requestBody":{ + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RollbackAckV1Message" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } } - } + } } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-final.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-final.ts index 4dd0bcc6ab..d40917b806 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-final.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-final.ts @@ -14,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendCommitFinalRequest( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendCommitFinalRequest()`; const sessionData = odap.sessions.get(sessionID); @@ -22,6 +23,9 @@ export async function sendCommitFinalRequest( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.commitFinalClaim == undefined || sessionData.recipientBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || @@ -31,37 +35,23 @@ export async function sendCommitFinalRequest( throw new Error(`${fnTag}, session data is not correctly initialized`); } - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "init", - operation: "commit-final", - nodes: `${odap.pubKey}->${sessionData.recipientGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); - - const fabricDeleteAssetProof = await odap.deleteFabricAsset(sessionID); - sessionData.commitFinalClaim = fabricDeleteAssetProof; - const commitFinalRequestMessage: CommitFinalV1Request = { sessionID: sessionID, messageType: OdapMessageType.CommitFinalRequest, clientIdentityPubkey: sessionData.sourceGatewayPubkey, serverIdentityPubkey: sessionData.recipientGatewayPubkey, - commitFinalClaim: fabricDeleteAssetProof, + commitFinalClaim: sessionData.commitFinalClaim, // commit final claim format hashCommitPrepareAck: sessionData.commitPrepareResponseMessageHash, - clientSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - const messageSignature = odap.bufArray2HexStr( + const messageSignature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(commitFinalRequestMessage)), ); - commitFinalRequestMessage.clientSignature = messageSignature; + commitFinalRequestMessage.signature = messageSignature; sessionData.commitFinalRequestMessageHash = SHA256( JSON.stringify(commitFinalRequestMessage), @@ -71,15 +61,26 @@ export async function sendCommitFinalRequest( odap.sessions.set(sessionID, sessionData); - log.info(`${fnTag}, sending CommitFinalRequest...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "init", + operation: "final", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.recipientBasePath) - .phase3CommitFinalRequestV1(commitFinalRequestMessage); + log.info(`${fnTag}, sending CommitFinalRequest...`); - if (response.status != 200) { - throw new Error(`${fnTag}, CommitFinalRequest message failed`); + if (!remote) { + return commitFinalRequestMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.recipientBasePath, + ).phase3CommitFinalRequestV1(commitFinalRequestMessage), + "CommitFinalRequest", + ); } export async function checkValidCommitFinalResponse( @@ -97,67 +98,57 @@ export async function checkValidCommitFinalResponse( } if (response.messageType != OdapMessageType.CommitFinalResponse) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, wrong message type for CommitFinalResponse`); } if (response.sequenceNumber != sessionData.lastSequenceNumber) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, CommitFinalResponse sequence number incorrect`); } if (response.commitAcknowledgementClaim == undefined) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, the claim provided is not valid`); } if (sessionData.commitFinalRequestMessageHash != response.hashCommitFinal) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitFinalResponse previous message hash does not match the one that was sent`, ); } if (sessionData.recipientGatewayPubkey != response.serverIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitFinalResponse serverIdentity public key does not match the one that was sent`, ); } if (sessionData.sourceGatewayPubkey != response.clientIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitFinalResponse clientIdentity public key does not match the one that was sent`, ); } - const commitFinalResponseMessageDataSignature = response.serverSignature; - - const sourceServerSignature = new Uint8Array( - Buffer.from(commitFinalResponseMessageDataSignature, "hex"), - ); + if (!odap.verifySignature(response, sessionData.recipientGatewayPubkey)) { + throw new Error( + `${fnTag}, CommitFinalResponse message signature verification failed`, + ); + } - const sourceServerPubkey = new Uint8Array( - Buffer.from(sessionData.recipientGatewayPubkey, "hex"), + const claimHash = SHA256(response.commitAcknowledgementClaim).toString(); + const retrievedClaim = await odap.getLogFromIPFS( + PluginOdapGateway.getOdapLogKey(sessionID, "proof", "create"), ); - response.serverSignature = ""; - - if ( - !odap.verifySignature( - JSON.stringify(response), - sourceServerSignature, - sourceServerPubkey, - ) - ) { - await odap.Revert(sessionID); + if (claimHash != retrievedClaim.hash) { throw new Error( - `${fnTag}, CommitFinalResponse message signature verification failed`, + `${fnTag}, Commit Acknowledgement Claim hash does not match the one stored in IPFS`, ); } - response.serverSignature = commitFinalResponseMessageDataSignature; + if (!odap.verifySignature(retrievedClaim, response.serverIdentityPubkey)) { + throw new Error( + `${fnTag}, Commit Acknowledgement Claim signature verification failed`, + ); + } storeSessionData(response, odap); @@ -181,16 +172,15 @@ function storeSessionData( ); } - sessionData.step++; - sessionData.commitAcknowledgementClaim = response.commitAcknowledgementClaim; sessionData.commitFinalResponseMessageHash = SHA256( JSON.stringify(response), ).toString(); - sessionData.serverSignatureCommitFinalResponseMessage = - response.serverSignature; + sessionData.serverSignatureCommitFinalResponseMessage = response.signature; + + sessionData.step = 11; odap.sessions.set(sessionData.id, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-preparation.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-preparation.ts index d5706709de..c91ef14360 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-preparation.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/commit-preparation.ts @@ -14,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendCommitPreparationRequest( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendCommitPreparationRequest()`; const sessionData = odap.sessions.get(sessionID); @@ -22,6 +23,8 @@ export async function sendCommitPreparationRequest( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.recipientBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || @@ -31,32 +34,21 @@ export async function sendCommitPreparationRequest( throw new Error(`${fnTag}, session data is not correctly initialized`); } - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "init", - operation: "commit-prepare", - nodes: `${odap.pubKey}->${sessionData.recipientGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); - const commitPrepareRequestMessage: CommitPreparationV1Request = { sessionID: sessionID, messageType: OdapMessageType.CommitPreparationRequest, clientIdentityPubkey: sessionData.sourceGatewayPubkey, serverIdentityPubkey: sessionData.recipientGatewayPubkey, hashLockEvidenceAck: sessionData.lockEvidenceResponseMessageHash, - clientSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - const messageSignature = odap.bufArray2HexStr( + const messageSignature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(commitPrepareRequestMessage)), ); - commitPrepareRequestMessage.clientSignature = messageSignature; + commitPrepareRequestMessage.signature = messageSignature; sessionData.commitPrepareRequestMessageHash = SHA256( JSON.stringify(commitPrepareRequestMessage), @@ -66,15 +58,26 @@ export async function sendCommitPreparationRequest( odap.sessions.set(sessionID, sessionData); - log.info(`${fnTag}, sending CommitPreparationRequest...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "init", + operation: "prepare", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.recipientBasePath) - .phase3CommitPreparationRequestV1(commitPrepareRequestMessage); + log.info(`${fnTag}, sending CommitPreparationRequest...`); - if (response.status != 200) { - throw new Error(`${fnTag}, CommitPreparationRequest message failed`); + if (!remote) { + return commitPrepareRequestMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.recipientBasePath, + ).phase3CommitPreparationRequestV1(commitPrepareRequestMessage), + "CommitPreparationRequest", + ); } export async function checkValidCommitPreparationResponse( @@ -92,67 +95,41 @@ export async function checkValidCommitPreparationResponse( } if (response.messageType != OdapMessageType.CommitPreparationResponse) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, wrong message type for CommitPreparationResponse`, ); } if (response.sequenceNumber != sessionData.lastSequenceNumber) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitPreparationResponse sequence number incorrect`, ); } if (sessionData.commitPrepareRequestMessageHash != response.hashCommitPrep) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitPreparationResponse previous message hash does not match the one that was sent`, ); } if (sessionData.recipientGatewayPubkey != response.serverIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitPreparationResponse serverIdentity public key does not match the one that was sent`, ); } if (sessionData.sourceGatewayPubkey != response.clientIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitPreparationResponse clientIdentity public key does not match the one that was sent`, ); } - const commitPrepareResponseMessageDataSignature = response.serverSignature; - - const sourceServerSignature = new Uint8Array( - Buffer.from(commitPrepareResponseMessageDataSignature, "hex"), - ); - - const sourceServerPubkey = new Uint8Array( - Buffer.from(sessionData.recipientGatewayPubkey, "hex"), - ); - - response.serverSignature = ""; - - if ( - !odap.verifySignature( - JSON.stringify(response), - sourceServerSignature, - sourceServerPubkey, - ) - ) { - await odap.Revert(sessionID); + if (!odap.verifySignature(response, sessionData.recipientGatewayPubkey)) { throw new Error( `${fnTag}, CommitPreparationResponse message signature verification failed`, ); } - response.serverSignature = commitPrepareResponseMessageDataSignature; - storeSessionData(response, odap); log.info(`CommitPreparationResponse passed all checks.`); @@ -175,14 +152,14 @@ function storeSessionData( ); } - sessionData.step++; - sessionData.commitPrepareResponseMessageHash = SHA256( JSON.stringify(response), ).toString(); sessionData.serverSignatureCommitPreparationResponseMessage = - response.serverSignature; + response.signature; + + sessionData.step = 9; odap.sessions.set(sessionData.id, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/lock-evidence.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/lock-evidence.ts index 6bbbf9ebd9..cca3c9d136 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/lock-evidence.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/lock-evidence.ts @@ -14,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendLockEvidenceRequest( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendLockEvidenceRequest()`; const sessionData = odap.sessions.get(sessionID); @@ -22,6 +23,9 @@ export async function sendLockEvidenceRequest( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.lockEvidenceClaim == undefined || sessionData.recipientBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || @@ -31,41 +35,26 @@ export async function sendLockEvidenceRequest( throw new Error(`${fnTag}, session data is not correctly initialized`); } - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "init", - operation: "lock", - nodes: `${odap.pubKey}->${sessionData.recipientGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); - - const fabricLockAssetProof = await odap.lockFabricAsset(sessionID); - - log.info(`${fnTag}, proof of the asset lock: ${fabricLockAssetProof}`); - const lockEvidenceRequestMessage: LockEvidenceV1Request = { sessionID: sessionID, messageType: OdapMessageType.LockEvidenceRequest, clientIdentityPubkey: sessionData.sourceGatewayPubkey, serverIdentityPubkey: sessionData.recipientGatewayPubkey, - lockEvidenceClaim: fabricLockAssetProof, + lockEvidenceClaim: sessionData.lockEvidenceClaim, // lock claim format lockEvidenceExpiration: new Date() .setDate(new Date().getDate() + 1) .toString(), // a day from now hashCommenceAckRequest: sessionData.transferCommenceMessageResponseHash, - clientSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - const messageSignature = odap.bufArray2HexStr( + const messageSignature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(lockEvidenceRequestMessage)), ); - lockEvidenceRequestMessage.clientSignature = messageSignature; + lockEvidenceRequestMessage.signature = messageSignature; sessionData.lockEvidenceRequestMessageHash = SHA256( JSON.stringify(lockEvidenceRequestMessage), @@ -73,19 +62,28 @@ export async function sendLockEvidenceRequest( sessionData.clientSignatureLockEvidenceRequestMessage = messageSignature; - sessionData.lockEvidenceClaim = fabricLockAssetProof; - odap.sessions.set(sessionID, sessionData); - log.info(`${fnTag}, sending LockEvidenceRequest...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "init", + operation: "lock", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.recipientBasePath) - .phase2LockEvidenceRequestV1(lockEvidenceRequestMessage); + log.info(`${fnTag}, sending LockEvidenceRequest...`); - if (response.status != 200) { - throw new Error(`${fnTag}, LockEvidenceRequest message failed`); + if (!remote) { + return lockEvidenceRequestMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.recipientBasePath, + ).phase2LockEvidenceRequestV1(lockEvidenceRequestMessage), + "LockEvidenceRequest", + ); } export async function checkValidLockEvidenceResponse( @@ -103,12 +101,10 @@ export async function checkValidLockEvidenceResponse( } if (response.messageType != OdapMessageType.LockEvidenceResponse) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, wrong message type for LockEvidenceResponse`); } if (response.sequenceNumber != sessionData.lastSequenceNumber) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, LockEvidenceResponse sequence number incorrect`); } @@ -116,53 +112,29 @@ export async function checkValidLockEvidenceResponse( sessionData.lockEvidenceRequestMessageHash != response.hashLockEvidenceRequest ) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, LockEvidenceResponse previous message hash does not match the one that was sent`, ); } if (sessionData.recipientGatewayPubkey != response.serverIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, LockEvidenceResponse serverIdentity public key does not match the one that was sent`, ); } if (sessionData.sourceGatewayPubkey != response.clientIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, LockEvidenceResponse clientIdentity public key does not match the one that was sent`, ); } - const lockEvidenceResponseMesssageDataSignature = response.serverSignature; - - const sourceServerSignature = new Uint8Array( - Buffer.from(lockEvidenceResponseMesssageDataSignature, "hex"), - ); - - const sourceServerPubkey = new Uint8Array( - Buffer.from(sessionData.recipientGatewayPubkey, "hex"), - ); - - response.serverSignature = ""; - - if ( - !odap.verifySignature( - JSON.stringify(response), - sourceServerSignature, - sourceServerPubkey, - ) - ) { - await odap.Revert(sessionID); + if (!odap.verifySignature(response, sessionData.recipientGatewayPubkey)) { throw new Error( `${fnTag}, LockEvidenceResponse message signature verification failed`, ); } - response.serverSignature = lockEvidenceResponseMesssageDataSignature; - storeSessionData(response, odap); log.info(`LockEvidenceResponse passed all checks.`); @@ -185,14 +157,13 @@ function storeSessionData( ); } - sessionData.step++; - sessionData.lockEvidenceResponseMessageHash = SHA256( JSON.stringify(response), ).toString(); - sessionData.serverSignatureLockEvidenceResponseMessage = - response.serverSignature; + sessionData.serverSignatureLockEvidenceResponseMessage = response.signature; + + sessionData.step = 7; odap.sessions.set(sessionData.id, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-commence.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-commence.ts index 3069dc1203..ebbdb47324 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-commence.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-commence.ts @@ -14,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendTransferCommenceRequest( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendTransferCommenceRequest()`; const sessionData = odap.sessions.get(sessionID); @@ -22,10 +23,12 @@ export async function sendTransferCommenceRequest( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.assetProfile == undefined || sessionData.recipientBasePath == undefined || - sessionData.originatorPubkey == undefined || - sessionData.beneficiaryPubkey == undefined || + // sessionData.originatorPubkey == undefined || + // sessionData.beneficiaryPubkey == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || sessionData.recipientGatewayPubkey == undefined || @@ -36,25 +39,16 @@ export async function sendTransferCommenceRequest( throw new Error(`${fnTag}, session data is not correctly initialized`); } - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "init", - operation: "commence", - nodes: `${odap.pubKey}->${sessionData.recipientGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); - const hashAssetProfile = SHA256( JSON.stringify(sessionData.assetProfile), ).toString(); const transferCommenceRequestMessage: TransferCommenceV1Request = { messageType: OdapMessageType.TransferCommenceRequest, - originatorPubkey: sessionData.originatorPubkey, - beneficiaryPubkey: sessionData.beneficiaryPubkey, + // originatorPubkey: sessionData.originatorPubkey, + // beneficiaryPubkey: sessionData.beneficiaryPubkey, + originatorPubkey: "sessionData.originatorPubkey", + beneficiaryPubkey: "sessionData.beneficiaryPubkey", senderDltSystem: sessionData.sourceGatewayDltSystem, recipientDltSystem: sessionData.recipientGatewayDltSystem, sessionID: sessionID, @@ -63,15 +57,15 @@ export async function sendTransferCommenceRequest( hashAssetProfile: hashAssetProfile, hashPrevMessage: sessionData.initializationResponseMessageHash, // clientTransferNumber - clientSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - const messageSignature = odap.bufArray2HexStr( + const messageSignature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(transferCommenceRequestMessage)), ); - transferCommenceRequestMessage.clientSignature = messageSignature; + transferCommenceRequestMessage.signature = messageSignature; sessionData.transferCommenceMessageRequestHash = SHA256( JSON.stringify(transferCommenceRequestMessage), @@ -81,15 +75,26 @@ export async function sendTransferCommenceRequest( odap.sessions.set(sessionID, sessionData); - log.info(`${fnTag}, sending TransferCommenceRequest...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "init", + operation: "commence", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.recipientBasePath) - .phase2TransferCommenceRequestV1(transferCommenceRequestMessage); + log.info(`${fnTag}, sending TransferCommenceRequest...`); - if (response.status != 200) { - throw new Error(`${fnTag}, TransferCommenceRequest message failed`); + if (!remote) { + return transferCommenceRequestMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.recipientBasePath, + ).phase2TransferCommenceRequestV1(transferCommenceRequestMessage), + "TransferCommenceRequest", + ); } export async function checkValidTransferCommenceResponse( @@ -137,32 +142,12 @@ export async function checkValidTransferCommenceResponse( ); } - const transferCommenceResponseDataSignature = response.serverSignature; - - const sourceServerSignature = new Uint8Array( - Buffer.from(transferCommenceResponseDataSignature, "hex"), - ); - - const sourceServerPubkey = new Uint8Array( - Buffer.from(sessionData.recipientGatewayPubkey, "hex"), - ); - - response.serverSignature = ""; - - if ( - !odap.verifySignature( - JSON.stringify(response), - sourceServerSignature, - sourceServerPubkey, - ) - ) { + if (!odap.verifySignature(response, sessionData.recipientGatewayPubkey)) { throw new Error( `${fnTag}, TransferCommenceResponse message signature verification failed`, ); } - response.serverSignature = transferCommenceResponseDataSignature; - storeSessionData(response, odap); log.info(`TransferCommenceResponse passed all checks.`); @@ -185,14 +170,14 @@ function storeSessionData( ); } - sessionData.step++; - sessionData.transferCommenceMessageResponseHash = SHA256( JSON.stringify(response), ).toString(); sessionData.serverSignatureTransferCommenceResponseMessage = - response.serverSignature; + response.signature; + + sessionData.step = 5; odap.sessions.set(sessionData.id, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-complete.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-complete.ts index a88ef0be7b..7fed5cd4c5 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-complete.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-complete.ts @@ -11,7 +11,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendTransferCompleteRequest( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendTransferCompleteRequest()`; const sessionData = odap.sessions.get(sessionID); @@ -19,6 +20,8 @@ export async function sendTransferCompleteRequest( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.recipientBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || @@ -29,17 +32,6 @@ export async function sendTransferCompleteRequest( throw new Error(`${fnTag}, session data is not correctly initialized`); } - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "init", - operation: "complete", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); - const transferCompleteRequestMessage: TransferCompleteV1Request = { sessionID: sessionID, messageType: OdapMessageType.TransferCompleteRequest, @@ -47,15 +39,15 @@ export async function sendTransferCompleteRequest( serverIdentityPubkey: sessionData.recipientGatewayPubkey, hashCommitFinalAck: sessionData.commitFinalResponseMessageHash, hashTransferCommence: sessionData.transferCommenceMessageRequestHash, - clientSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - const messageSignature = odap.bufArray2HexStr( + const messageSignature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(transferCompleteRequestMessage)), ); - transferCompleteRequestMessage.clientSignature = messageSignature; + transferCompleteRequestMessage.signature = messageSignature; sessionData.transferCompleteMessageHash = SHA256( JSON.stringify(transferCompleteRequestMessage), @@ -65,13 +57,24 @@ export async function sendTransferCompleteRequest( odap.sessions.set(sessionID, sessionData); - log.info(`${fnTag}, sending TransferCompleteRequest...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "init", + operation: "complete", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.recipientBasePath) - .phase3TransferCompleteRequestV1(transferCompleteRequestMessage); + log.info(`${fnTag}, sending TransferCompleteRequest...`); - if (response.status != 200) { - throw new Error(`${fnTag}, TransferCompleteRequest message failed`); + if (!remote) { + return transferCompleteRequestMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.recipientBasePath, + ).phase3TransferCompleteRequestV1(transferCompleteRequestMessage), + "TransferCompleteRequest", + ); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-initialization.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-initialization.ts index 5ad5632354..645a150049 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-initialization.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-initialization.ts @@ -14,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendTransferInitializationRequest( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendTransferInitializationRequest()`; const sessionData = odap.sessions.get(sessionID); @@ -24,6 +25,8 @@ export async function sendTransferInitializationRequest( sessionData.id == undefined || sessionData.step == undefined || sessionData.version == undefined || + sessionData.maxRetries == undefined || + sessionData.maxTimeout == undefined || sessionData.payloadProfile == undefined || sessionData.loggingProfile == undefined || sessionData.recipientBasePath == undefined || @@ -43,17 +46,6 @@ export async function sendTransferInitializationRequest( ); } - await odap.storeOdapLog( - { - phase: "p1", - step: sessionData.step.toString(), - type: "init", - operation: "validate", - nodes: `${odap.pubKey}->${sessionData.recipientGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); - const initializationRequestMessage: TransferInitializationV1Request = { messageType: OdapMessageType.InitializationRequest, sessionID: sessionData.id, @@ -64,25 +56,28 @@ export async function sendTransferInitializationRequest( applicationProfile: sessionData.applicationProfile, loggingProfile: sessionData.loggingProfile, accessControlProfile: sessionData.accessControlProfile, - clientSignature: "", + signature: "", sourceGatewayPubkey: odap.pubKey, sourceGatewayDltSystem: sessionData.sourceGatewayDltSystem, recipientGatewayPubkey: sessionData.recipientGatewayPubkey, recipientGatewayDltSystem: sessionData.recipientGatewayDltSystem, sequenceNumber: sessionData.lastSequenceNumber, sourceGatewayPath: sessionData.sourceBasePath, + recipientBasePath: sessionData.recipientBasePath, // escrow type // expiry time (related to the escrow) // multiple claims allowed // multiple cancels allowed // permissions + maxRetries: sessionData.maxRetries, + maxTimeout: sessionData.maxTimeout, }; - const messageSignature = odap.bufArray2HexStr( + const messageSignature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(initializationRequestMessage)), ); - initializationRequestMessage.clientSignature = messageSignature; + initializationRequestMessage.signature = messageSignature; sessionData.initializationRequestMessageHash = SHA256( JSON.stringify(initializationRequestMessage), @@ -92,15 +87,26 @@ export async function sendTransferInitializationRequest( odap.sessions.set(sessionID, sessionData); - log.info(`${fnTag}, sending TransferInitializationRequest...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "init", + operation: "validate", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.recipientBasePath) - .phase1TransferInitiationRequestV1(initializationRequestMessage); + log.info(`${fnTag}, sending TransferInitializationRequest...`); - if (response.status != 200) { - throw new Error(`${fnTag}, TransferInitializationRequest message failed`); + if (!remote) { + return initializationRequestMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.recipientBasePath, + ).phase1TransferInitiationRequestV1(initializationRequestMessage), + "TransferInitializationRequest", + ); } export async function checkValidInitializationResponse( @@ -142,41 +148,21 @@ export async function checkValidInitializationResponse( ); } - const transferInitiationResponseDataSignature = response.serverSignature; - - const sourceServerSignature = new Uint8Array( - Buffer.from(transferInitiationResponseDataSignature, "hex"), - ); - - const sourceServerPubkey = new Uint8Array( - Buffer.from(sessionData.recipientGatewayPubkey, "hex"), - ); - - response.serverSignature = ""; - - if ( - !odap.verifySignature( - JSON.stringify(response), - sourceServerSignature, - sourceServerPubkey, - ) - ) { + if (!odap.verifySignature(response, sessionData.recipientGatewayPubkey)) { throw new Error( `${fnTag}, TransferInitializationResponse message signature verification failed`, ); } - response.serverSignature = transferInitiationResponseDataSignature; - storeSessionData(response, odap); log.info(`TransferInitializationResponse passed all checks.`); } -async function storeSessionData( +function storeSessionData( response: TransferInitializationV1Response, odap: PluginOdapGateway, -): Promise { +): void { const fnTag = `${odap.className}#storeSessionData`; const sessionData = odap.sessions.get(response.sessionID); @@ -188,8 +174,6 @@ async function storeSessionData( const serverIdentityPubkey = response.serverIdentityPubkey; - sessionData.step++; - sessionData.id = response.sessionID; sessionData.recipientGatewayPubkey = serverIdentityPubkey; @@ -198,10 +182,11 @@ async function storeSessionData( JSON.stringify(response), ).toString(); - sessionData.serverSignatureInitializationResponseMessage = - response.serverSignature; + sessionData.serverSignatureInitializationResponseMessage = response.signature; sessionData.fabricAssetSize = "1"; + sessionData.step = 3; + odap.sessions.set(sessionData.id, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/plugin-odap-gateway.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/plugin-odap-gateway.ts index 794b66d31b..4560e6198b 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/plugin-odap-gateway.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/plugin-odap-gateway.ts @@ -4,6 +4,7 @@ import type { Server as SecureServer } from "https"; import { Optional } from "typescript-optional"; import type { Express } from "express"; import { v4 as uuidV4 } from "uuid"; +import knex, { Knex } from "knex"; import OAS from "../../json/openapi.json"; import { Secp256k1Keys, @@ -39,6 +40,13 @@ import { CommitFinalV1Response, TransferCompleteV1Request, TransferInitializationV1Request, + OdapLocalLog, + RecoverV1Message, + RecoverUpdateV1Message, + RecoverUpdateAckV1Message, + RollbackV1Message, + SessionDataRollbackActionsPerformedEnum, + RollbackAckV1Message, } from "../generated/openapi/typescript-axios"; import { CommitFinalRequestEndpointV1 } from "../web-services/server-side/commit-final-request-endpoint"; import { PluginRegistry } from "@hyperledger/cactus-core"; @@ -104,6 +112,42 @@ import { checkValidCommitPreparationRequest, sendCommitPreparationResponse, } from "./server/commit-preparation"; +import { + checkValidRecoverMessage, + sendRecoverMessage, +} from "./recovery/recover"; +import { + checkValidRecoverUpdateMessage, + sendRecoverUpdateMessage, +} from "./recovery/recover-update"; +import { + checkValidRecoverUpdateAckMessage, + sendRecoverUpdateAckMessage, +} from "./recovery/recover-update-ack"; +import { + checkValidRecoverSuccessMessage, + sendRecoverSuccessMessage, +} from "./recovery/recover-success"; +import { SHA256 } from "crypto-js"; +import { RecoverMessageEndpointV1 } from "../web-services/recovery/recover-message-endpoint"; +import { RecoverUpdateMessageEndpointV1 } from "../web-services/recovery/recover-update-message-endpoint"; +import { RecoverUpdateAckMessageEndpointV1 } from "../web-services/recovery/recover-update-ack-message-endpoint"; +import { RecoverSuccessMessageEndpointV1 } from "../web-services/recovery/recover-success-message-endpoint"; +import { RollbackMessageEndpointV1 } from "../web-services/recovery/rollback-message-endpoint"; +import { + checkValidRollbackMessage, + sendRollbackMessage, +} from "./recovery/rollback"; +import { + besuAssetExists, + fabricAssetExists, + isFabricAssetLocked, +} from "../../../test/typescript/make-checks-ledgers"; +import { AxiosResponse } from "axios"; +import { + checkValidRollbackAckMessage, + sendRollbackAckMessage, +} from "./recovery/rollback-ack"; export enum OdapMessageType { InitializationRequest = "urn:ietf:odap:msgtype:init-transfer-msg", @@ -123,31 +167,34 @@ export interface IPluginOdapGatewayConstructorOptions { name: string; dltIDs: string[]; instanceId: string; - ipfsPath?: string; + keyPair?: IOdapGatewayKeyPairs; + ipfsPath?: string; fabricPath?: string; besuPath?: string; + fabricSigningCredential?: FabricSigningCredential; fabricChannelName?: string; fabricContractName?: string; besuContractName?: string; besuWeb3SigningCredential?: Web3SigningCredential; besuKeychainId?: string; - fabricAssetID?: string; fabricAssetSize?: string; besuAssetID?: string; + + knexConfig?: Knex.Config; } export interface IOdapGatewayKeyPairs { publicKey: Uint8Array; privateKey: Uint8Array; } -interface IOdapHermesLog { - phase: string; - step: string; - type: string; - operation: string; - nodes: string; + +export interface IOdapLogIPFS { + key: string; + hash: string; + signature: string; + signerPubKey: string; } export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { @@ -158,10 +205,12 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { public static readonly CLASS_NAME = "OdapGateway"; private readonly log: Logger; private readonly instanceId: string; + public ipfsApi?: ObjectStoreIpfsApi; public fabricApi?: FabricApi; public besuApi?: BesuApi; public pluginRegistry: PluginRegistry; + public database?: Knex; private endpoints: IWebServiceEndpoint[] | undefined; //map[]object, object refer to a state @@ -190,13 +239,17 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { const level = "INFO"; const label = this.className; this.log = LoggerProvider.getOrCreate({ level, label }); + this.instanceId = options.instanceId; + this.name = options.name; this.supportedDltIDs = options.dltIDs; this.sessions = new Map(); - const keyPairs: IOdapGatewayKeyPairs = Secp256k1Keys.generateKeyPairsBuffer(); - this.pubKey = this.bufArray2HexStr(keyPairs.publicKey); - this.privKey = this.bufArray2HexStr(keyPairs.privateKey); + const keyPairs = options.keyPair + ? options.keyPair + : Secp256k1Keys.generateKeyPairsBuffer(); + this.pubKey = PluginOdapGateway.bufArray2HexStr(keyPairs.publicKey); + this.privKey = PluginOdapGateway.bufArray2HexStr(keyPairs.privateKey); const odapSignerOptions: IJsObjectSignerOptions = { privateKey: this.privKey, logLevel: "debug", @@ -211,6 +264,17 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { if (options.ipfsPath != undefined) this.defineIpfsConnection(options); if (options.fabricPath != undefined) this.defineFabricConnection(options); if (options.besuPath != undefined) this.defineBesuConnection(options); + + this.defineKnexConnection(options.knexConfig); + } + + public defineKnexConnection(knexConfig?: Knex.Config): void { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const config = require("../../../../knex/knexfile.ts")[ + process.env.ENVIRONMENT || "development" + ]; + + this.database = knex(knexConfig || config); } private defineIpfsConnection( @@ -272,94 +336,6 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { this.besuAssetID = options.besuAssetID; } - public async Revert(sessionID: string): Promise { - const sessionData = this.sessions.get(sessionID); - if (sessionData == undefined) return; - if ( - sessionData.isFabricAssetDeleted != undefined && - sessionData.isFabricAssetDeleted - ) { - if (this.fabricApi == undefined) return; - await this.fabricApi.runTransactionV1({ - signingCredential: this.fabricSigningCredential, - channelName: this.fabricChannelName, - contractName: this.fabricContractName, - invocationType: FabricContractInvocationType.Send, - methodName: "CreateAsset", - params: [sessionData.fabricAssetID, sessionData.fabricAssetSize], - } as FabricRunTransactionRequest); - } else if ( - sessionData.isFabricAssetLocked != undefined && - sessionData.isFabricAssetLocked - ) { - if (this.fabricApi == undefined) return; - await this.fabricApi.runTransactionV1({ - signingCredential: this.fabricSigningCredential, - channelName: this.fabricChannelName, - contractName: this.fabricContractName, - invocationType: FabricContractInvocationType.Send, - methodName: "UnLockAsset", - params: [sessionData.fabricAssetID], - } as FabricRunTransactionRequest); - } else if ( - sessionData.isFabricAssetCreated != undefined && - sessionData.isFabricAssetCreated - ) { - if (this.fabricApi == undefined) return; - await this.fabricApi.runTransactionV1({ - signingCredential: this.fabricSigningCredential, - channelName: this.fabricChannelName, - contractName: this.fabricContractName, - invocationType: FabricContractInvocationType.Send, - methodName: "CreateAsset", - params: [sessionData.fabricAssetID], - } as FabricRunTransactionRequest); - } else if ( - sessionData.isBesuAssetCreated != undefined && - sessionData.isBesuAssetCreated - ) { - if (this.besuApi == undefined) return; - await this.besuApi.invokeContractV1({ - contractName: this.besuContractName, - invocationType: EthContractInvocationType.Send, - methodName: "deleteAsset", - gas: 1000000, - params: [this.besuAssetID], - signingCredential: this.besuWeb3SigningCredential, - keychainId: this.besuKeychainId, - } as BesuInvokeContractV1Request); - } else if ( - sessionData.isBesuAssetDeleted != undefined && - sessionData.isBesuAssetDeleted - ) { - if (this.besuApi == undefined) return; - await this.besuApi.invokeContractV1({ - contractName: this.besuContractName, - invocationType: EthContractInvocationType.Send, - methodName: "createAsset", - gas: 1000000, - params: [this.besuAssetID], - signingCredential: this.besuWeb3SigningCredential, - keychainId: this.besuKeychainId, - } as BesuInvokeContractV1Request); - } else if ( - sessionData.isBesuAssetLocked != undefined && - sessionData.isBesuAssetLocked - ) { - if (this.besuApi == undefined) return; - await this.besuApi.invokeContractV1({ - contractName: this.besuContractName, - invocationType: EthContractInvocationType.Send, - methodName: "unLockAsset", - gas: 1000000, - params: [this.besuAssetID], - signingCredential: this.besuWeb3SigningCredential, - keychainId: this.besuKeychainId, - } as BesuInvokeContractV1Request); - } - return; - } - public get className(): string { return PluginOdapGateway.CLASS_NAME; } @@ -441,6 +417,27 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { gateway: this, }); + // Recovery endpoints + const recoverEndpoint = new RecoverMessageEndpointV1({ + gateway: this, + }); + + const recoverUpdateEndpoint = new RecoverUpdateMessageEndpointV1({ + gateway: this, + }); + + const recoverUpdateAckEndpoint = new RecoverUpdateAckMessageEndpointV1({ + gateway: this, + }); + + const recoverSuccessEndpoint = new RecoverSuccessMessageEndpointV1({ + gateway: this, + }); + + const rollbackEndpoint = new RollbackMessageEndpointV1({ + gateway: this, + }); + this.endpoints = [ transferInitiationRequestEndpoint, transferCommenceRequestEndpoint, @@ -454,6 +451,11 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { lockEvidenceResponseEndpoint, commitPreparationResponseEndpoint, commitFinalResponseEndpoint, + recoverEndpoint, + recoverUpdateEndpoint, + recoverUpdateAckEndpoint, + recoverSuccessEndpoint, + rollbackEndpoint, ]; return this.endpoints; } @@ -471,56 +473,268 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { } public getPackageName(): string { - return "@hyperledger/cactus-odap-odap-gateway-business-logic-plugin"; + return "@hyperledger/cactus-odap-gateway-business-logic-plugin"; } - public sign(msg: string): Uint8Array { + getDatabaseInstance(): Knex.QueryBuilder { + const fnTag = `${this.className}#getDatabaseInstance()`; + + if (this.database == undefined) { + throw new Error(`${fnTag}, database is undefined`); + } + + return this.database("logs"); + } + + isClientGateway(sessionID: string): boolean { + const fnTag = `${this.className}#isClientGateway()`; + + const sessionData = this.sessions.get(sessionID); + + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + return sessionData.sourceGatewayPubkey == this.pubKey; + } + + sign(msg: string): Uint8Array { return this.odapSigner.sign(msg); } - public verifySignature( - msg: string, - signature: Uint8Array, - pubKey: Uint8Array, - ): boolean { - return this.odapSigner.verify(msg, signature, pubKey); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + verifySignature(obj: any, pubKey: string): boolean { + const sourceSignature = new Uint8Array(Buffer.from(obj.signature, "hex")); + const sourcePubkey = new Uint8Array(Buffer.from(pubKey, "hex")); + + const signature = obj.signature; + obj.signature = ""; + if ( + !this.odapSigner.verify( + JSON.stringify(obj), + sourceSignature, + sourcePubkey, + ) + ) { + return false; + } + + obj.signature = signature; + return true; } - public bufArray2HexStr(array: Uint8Array): string { + static bufArray2HexStr(array: Uint8Array): string { return Buffer.from(array).toString("hex"); } - public async publishOdapProof(ID: string, proof: string): Promise { - if (this.ipfsApi == undefined) return; - const res = await this.ipfsApi.setObjectV1({ - key: ID, - value: proof, - }); - const resStatusOk = res.status > 199 && res.status < 300; - if (!resStatusOk) { - throw new Error("${fnTag}, error when logging to ipfs"); + static getOdapLogKey( + sessionID: string, + type: string, + operation: string, + ): string { + return `${sessionID}-${type}-${operation}`; + } + + async recoverOpenSessions(remote: boolean) { + const fnTag = `${this.className}#recoverOpenSessions()`; + + this.log.info(`${fnTag}, recovering open sessions...`); + + if (this.database == undefined) { + throw new Error(`${fnTag}, database is undefined`); + } + + const logs: OdapLocalLog[] = await this.getDatabaseInstance() + .select( + this.database.raw( + "sessionId, key, data, type, operation, MAX(timestamp) as timestamp", + ), + ) + .whereNot({ type: "proof" }) + .groupBy("sessionID"); + + for (const log of logs) { + const sessionID = log.sessionID; + this.log.info(`${fnTag}, recovering session ${sessionID}...`); + + if (log == undefined || log.data == undefined) { + throw new Error(`${fnTag}, invalid log}`); + } + + const sessionData: SessionData = JSON.parse(log.data); + + sessionData.lastLogEntryTimestamp = log.timestamp; + this.sessions.set(sessionID, sessionData); + if (remote) await sendRecoverMessage(sessionID, this, true); } } - public async storeOdapLog( - odapHermesLog: IOdapHermesLog, - ID: string, - ): Promise { + async storeInDatabase(odapLocalLog: OdapLocalLog) { + const fnTag = `${this.className}#storeInDatabase()`; this.log.info( - `<${odapHermesLog.phase}, ${odapHermesLog.step}, ${odapHermesLog.type}-${odapHermesLog.operation}, ${odapHermesLog.nodes}>`, + `${fnTag}, Storing locally log: ${JSON.stringify(odapLocalLog)}`, ); + + await this.getDatabaseInstance().insert(odapLocalLog); + } + + async storeInIPFS(key: string, hash: string) { + const fnTag = `${this.className}#storeInIPFS()`; + + if (this.ipfsApi == undefined) return; + + const ipfsLog: IOdapLogIPFS = { + key: key, + hash: hash, + signature: "", + signerPubKey: this.pubKey, + }; + + ipfsLog.signature = PluginOdapGateway.bufArray2HexStr( + await this.sign(JSON.stringify(ipfsLog)), + ); + + const logBase64 = Buffer.from(JSON.stringify(ipfsLog)).toString("base64"); + + this.log.info(`${fnTag}, Storing in ipfs log: ${JSON.stringify(ipfsLog)}`); + + const response = await this.ipfsApi.setObjectV1({ + key: key, + value: logBase64, + }); + + if (response.status < 200 && response.status > 299) { + throw new Error(`${fnTag}, error when logging to ipfs`); + } + } + + async storeOdapLog(odapLocalLog: OdapLocalLog): Promise { if (this.ipfsApi == undefined) return; - const res = await this.ipfsApi.setObjectV1({ - key: ID, - value: `${odapHermesLog.phase}, ${odapHermesLog.step}, ${odapHermesLog.type}-${odapHermesLog.operation}, ${odapHermesLog.nodes}`, + + odapLocalLog.key = PluginOdapGateway.getOdapLogKey( + odapLocalLog.sessionID, + odapLocalLog.type, + odapLocalLog.operation, + ); + odapLocalLog.timestamp = Date.now().toString(); + + await this.storeInDatabase(odapLocalLog); + + // Keep the order consistent with the order of the fields in the table + // so that the hash matches when retrieving from the database + const hash = SHA256( + JSON.stringify(odapLocalLog, [ + "sessionID", + "type", + "key", + "operation", + "timestamp", + "data", + ]), + ).toString(); + + await this.storeInIPFS(odapLocalLog.key, hash); + } + + async storeOdapProof(odapLocalLog: OdapLocalLog): Promise { + if (this.ipfsApi == undefined || odapLocalLog.data == undefined) return; + + odapLocalLog.key = PluginOdapGateway.getOdapLogKey( + odapLocalLog.sessionID, + odapLocalLog.type, + odapLocalLog.operation, + ); + odapLocalLog.timestamp = Date.now().toString(); + + await this.storeInDatabase(odapLocalLog); + + const hash = SHA256(odapLocalLog.data).toString(); + + await this.storeInIPFS(odapLocalLog.key, hash); + } + + async getLogFromDatabase(logKey: string): Promise { + const fnTag = `${this.className}#getLogFromDatabase()`; + this.log.info(`${fnTag}, retrieving log with key ${logKey}`); + + return await this.getDatabaseInstance() + .where({ key: logKey }) + .first() + .then((row) => { + this.log.info(`${fnTag}, retrieved log ${JSON.stringify(row)}`); + + return row; + }); + } + + async getLastLogFromDatabase( + sessionID: string, + ): Promise { + const fnTag = `${this.className}#getLastLog()`; + this.log.info(`${fnTag}, retrieving last log from sessionID ${sessionID}`); + + return await this.getDatabaseInstance() + .orderBy("timestamp", "desc") + .where({ sessionID: sessionID }) + .first() + .then((row) => { + this.log.info(`${fnTag}, retrieved log ${JSON.stringify(row)}`); + + return row; + }); + } + + async getLogsMoreRecentThanTimestamp( + timestamp: string, + ): Promise { + const fnTag = `${this.className}#getLogsMoreRecentThanTimestamp()`; + this.log.info(`${fnTag}, retrieving logs more recent than ${timestamp}`); + + const logs: OdapLocalLog[] = await this.getDatabaseInstance() + .where("timestamp", ">", timestamp) + .whereNot("type", "like", "%proof%"); + + if (logs == undefined) { + throw new Error(`${fnTag}, error when retrieving log from database`); + } + + this.log.info(`${fnTag}, there are ${logs.length} more recent logs`); + + return logs; + } + + async getLogFromIPFS(logKey: string): Promise { + const fnTag = `${this.className}#getOdapLogFromIPFS()`; + this.log.info(`Retrieving log with key: <${logKey}>`); + + if (this.ipfsApi == undefined) { + throw new Error(`${fnTag}, ipfs is not defined`); + } + + const response = await this.ipfsApi.getObjectV1({ + key: logKey, }); - const resStatusOk = res.status > 199 && res.status < 300; - if (!resStatusOk) { - throw new Error("${fnTag}, error when logging to ipfs"); + + if (response.status < 200 && response.status > 299) { + throw new Error(`${fnTag}, error when logging to ipfs`); } + + const log: IOdapLogIPFS = JSON.parse( + Buffer.from(response.data.value, "base64").toString(), + ); + + if (log == undefined || log.signature == undefined) { + throw new Error(`${fnTag}, the log or its signature is not defined`); + } + + if (!this.verifySignature(log, log.signerPubKey)) { + throw new Error(`${fnTag}, received log with invalid signature`); + } + + return log; } - public getOdapAPI(basePath: string): OdapApi { + static getOdapAPI(basePath: string): OdapApi { const odapServerApiConfig = new Configuration({ basePath: basePath, }); @@ -528,8 +742,105 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { return new OdapApi(odapServerApiConfig); } + async deleteDatabaseEntries(sessionID: string) { + this.log.info( + `deleting logs from database associated with sessionID: ${sessionID}`, + ); + + await this.getDatabaseInstance().where({ sessionID: sessionID }).del(); + } + + async resumeOdapSession(sessionID: string, remote: boolean) { + const fnTag = `${this.className}#continueOdapSession()`; + const sessionData = this.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.step == undefined || + sessionData.lastSequenceNumber == undefined + ) { + throw new Error(`${fnTag}, session data is undefined`); + } + + // if the other gateway made the rollback, we will do it as well + if (sessionData.rollback) { + await this.rollback(sessionID); + await sendRollbackAckMessage(sessionID, this, true); + return; + } + + this.log.info( + `${fnTag}, recovering session ${sessionID} that was in step ${sessionData.step}`, + ); + + // If step is even then the last log was inserted by the server + // so we need to increase the step + if (this.isClientGateway(sessionID) && sessionData.step % 2 == 0) { + sessionData.step++; + } + + this.sessions.set(sessionID, sessionData); + + switch (sessionData.step) { + case 1: + return await sendTransferInitializationRequest(sessionID, this, remote); + + case 2: + return await sendTransferInitializationResponse( + sessionID, + this, + remote, + ); + + case 3: + return await sendTransferCommenceRequest(sessionID, this, remote); + + case 4: + return await sendTransferCommenceResponse(sessionID, this, remote); + + case 5: + return await sendLockEvidenceRequest(sessionID, this, remote); + + case 6: + return await sendLockEvidenceResponse(sessionID, this, remote); + + case 7: + return await sendCommitPreparationRequest(sessionID, this, remote); + + case 8: + return await sendCommitPreparationResponse(sessionID, this, remote); + + case 9: + return await sendCommitFinalRequest(sessionID, this, remote); + + case 10: + return await sendCommitFinalResponse(sessionID, this, remote); + + case 11: + return await sendTransferCompleteRequest(sessionID, this, remote); + + default: + this.sessions.delete(sessionID); + throw new Error( + `${fnTag}, invalid session data step. A new session should be initiated by the client gateway.`, + ); + } + } + + private updateLastMessageReceivedTimestamp(sessionID: string) { + const fnTag = `${this.className}#updateLastMessageReceivedTimestamp()`; + const sessionData = this.sessions.get(sessionID); + + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + sessionData.lastMessageReceivedTimestamp = new Date().toString(); + this.sessions.set(sessionID, sessionData); + } + //Server-side - public async onTransferInitiationRequestReceived( + async onTransferInitiationRequestReceived( request: TransferInitializationV1Request, ): Promise { const fnTag = `${this.className}#onTransferInitiationRequestReceived()`; @@ -541,10 +852,10 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { ); await checkValidInitializationRequest(request, this); - await sendTransferInitializationResponse(request.sessionID, this); + await sendTransferInitializationResponse(request.sessionID, this, true); } - public async onTransferCommenceRequestReceived( + async onTransferCommenceRequestReceived( request: TransferCommenceV1Request, ): Promise { const fnTag = `${this.className}#onTransferCommenceRequestReceived()`; @@ -555,11 +866,12 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { )}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidtransferCommenceRequest(request, this); - await sendTransferCommenceResponse(request.sessionID, this); + await sendTransferCommenceResponse(request.sessionID, this, true); } - public async onLockEvidenceRequestReceived( + async onLockEvidenceRequestReceived( request: LockEvidenceV1Request, ): Promise { const fnTag = `${this.className}#onLockEvidenceRequestReceived()`; @@ -568,11 +880,12 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { `server gateway received LockEvidenceRequest: ${JSON.stringify(request)}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidLockEvidenceRequest(request, this); - await sendLockEvidenceResponse(request.sessionID, this); + await sendLockEvidenceResponse(request.sessionID, this, true); } - public async onCommitPrepareRequestReceived( + async onCommitPrepareRequestReceived( request: CommitPreparationV1Request, ): Promise { const fnTag = `${this.className}#onCommitPrepareRequestReceived()`; @@ -583,11 +896,12 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { )}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidCommitPreparationRequest(request, this); - await sendCommitPreparationResponse(request.sessionID, this); + await sendCommitPreparationResponse(request.sessionID, this, true); } - public async onCommitFinalRequestReceived( + async onCommitFinalRequestReceived( request: CommitFinalV1Request, ): Promise { const fnTag = `${this.className}#onCommitFinalRequestReceived()`; @@ -596,11 +910,13 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { `server gateway received CommitFinalRequest: ${JSON.stringify(request)}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidCommitFinalRequest(request, this); - await sendCommitFinalResponse(request.sessionID, this); + await this.createBesuAsset(request.sessionID); + await sendCommitFinalResponse(request.sessionID, this, true); } - public async onTransferCompleteRequestReceived( + async onTransferCompleteRequestReceived( request: TransferCompleteV1Request, ): Promise { const fnTag = `${this.className}#onTransferCompleteRequestReceived()`; @@ -611,11 +927,13 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { )}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidTransferCompleteRequest(request, this); + //this.deleteDatabaseEntries(request.sessionID); } //Client-side - public async onTransferInitiationResponseReceived( + async onTransferInitiationResponseReceived( request: TransferInitializationV1Response, ): Promise { const fnTag = `${this.className}#onTransferInitiationResponseReceived()`; @@ -626,11 +944,12 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { )}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidInitializationResponse(request, this); - await sendTransferCommenceRequest(request.sessionID, this); + await sendTransferCommenceRequest(request.sessionID, this, true); } - public async onTransferCommenceResponseReceived( + async onTransferCommenceResponseReceived( request: TransferCommenceV1Response, ): Promise { const fnTag = `${this.className}#onTransferCommenceResponseReceived()`; @@ -641,11 +960,13 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { )}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidTransferCommenceResponse(request, this); - await sendLockEvidenceRequest(request.sessionID, this); + await this.lockFabricAsset(request.sessionID); + await sendLockEvidenceRequest(request.sessionID, this, true); } - public async onLockEvidenceResponseReceived( + async onLockEvidenceResponseReceived( request: LockEvidenceV1Response, ): Promise { const fnTag = `${this.className}#onLockEvidenceResponseReceived()`; @@ -656,21 +977,24 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { )}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidLockEvidenceResponse(request, this); - await sendCommitPreparationRequest(request.sessionID, this); + await sendCommitPreparationRequest(request.sessionID, this, true); } - public async onCommitPrepareResponseReceived( + async onCommitPrepareResponseReceived( request: CommitPreparationV1Response, ): Promise { const fnTag = `${this.className}#onCommitPrepareResponseReceived()`; this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidCommitPreparationResponse(request, this); - await sendCommitFinalRequest(request.sessionID, this); + await this.deleteFabricAsset(request.sessionID); + await sendCommitFinalRequest(request.sessionID, this, true); } - public async onCommitFinalResponseReceived( + async onCommitFinalResponseReceived( request: CommitFinalV1Response, ): Promise { const fnTag = `${this.className}#onCommitFinalResponseReceived()`; @@ -679,11 +1003,94 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { `client gateway received CommitFinalResponse: ${JSON.stringify(request)}`, ); + this.updateLastMessageReceivedTimestamp(request.sessionID); await checkValidCommitFinalResponse(request, this); - await sendTransferCompleteRequest(request.sessionID, this); + await sendTransferCompleteRequest(request.sessionID, this, true); + } + + //Recover + async onRecoverMessageReceived(request: RecoverV1Message): Promise { + const fnTag = `${this.className}#onRecoverMessageReceived()`; + this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); + this.log.info( + `gateway received Recover message: ${JSON.stringify(request)}`, + ); + + this.updateLastMessageReceivedTimestamp(request.sessionID); + await checkValidRecoverMessage(request, this); + await sendRecoverUpdateMessage(request.sessionID, this, true); + } + + async onRecoverUpdateMessageReceived( + request: RecoverUpdateV1Message, + ): Promise { + const fnTag = `${this.className}#onRecoverUpdateMessageReceived()`; + this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); + this.log.info( + `gateway received RecoverUpdate message: ${JSON.stringify(request)}`, + ); + + this.updateLastMessageReceivedTimestamp(request.sessionID); + await checkValidRecoverUpdateMessage(request, this); + await sendRecoverUpdateAckMessage(request.sessionID, this, true); } - public async runOdap(request: ClientV1Request): Promise { + async onRecoverUpdateAckMessageReceived( + request: RecoverUpdateAckV1Message, + ): Promise { + const fnTag = `${this.className}#onRecoverUpdateAckMessageReceived()`; + this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); + this.log.info( + `gateway received RecoverUpdateAck message: ${JSON.stringify(request)}`, + ); + + this.updateLastMessageReceivedTimestamp(request.sessionID); + await checkValidRecoverUpdateAckMessage(request, this); + await sendRecoverSuccessMessage(request.sessionID, this, true); + } + + async onRecoverSuccessMessageReceived( + request: RecoverUpdateAckV1Message, + ): Promise { + const fnTag = `${this.className}#onRecoverSuccessMessageReceived()`; + this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); + this.log.info( + `gateway received RecoverSuccess message: ${JSON.stringify(request)}`, + ); + + this.updateLastMessageReceivedTimestamp(request.sessionID); + await checkValidRecoverSuccessMessage(request, this); + await this.resumeOdapSession(request.sessionID, true); + } + + async onRollbackMessageReceived(request: RollbackV1Message): Promise { + const fnTag = `${this.className}#onRollbackMessageReceived()`; + this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); + this.log.info( + `gateway received Rollback message: ${JSON.stringify(request)}`, + ); + + this.updateLastMessageReceivedTimestamp(request.sessionID); + await checkValidRollbackMessage(request, this); + await this.rollback(request.sessionID); + await sendRollbackAckMessage(request.sessionID, this, true); + } + + async onRollbackAckMessageReceived( + request: RollbackAckV1Message, + ): Promise { + const fnTag = `${this.className}#onRollbackAckMessageReceived()`; + this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); + this.log.info( + `gateway received Rollback Ack message: ${JSON.stringify(request)}`, + ); + + this.updateLastMessageReceivedTimestamp(request.sessionID); + await checkValidRollbackAckMessage(request, this); + //this.deleteDatabaseEntries(request.sessionID); + } + + async runOdap(request: ClientV1Request): Promise { const fnTag = `${this.className}#runOdap()`; this.log.info(`${fnTag}, start processing, time: ${Date.now()}`); this.log.info( @@ -698,10 +1105,10 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { ); } - await sendTransferInitializationRequest(sessionID, this); + await sendTransferInitializationRequest(sessionID, this, true); } - private configureOdapSession(request: ClientV1Request) { + configureOdapSession(request: ClientV1Request) { const sessionData: SessionData = {}; const sessionID = uuidV4(); @@ -724,13 +1131,19 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { sessionData.sourceGatewayDltSystem = request.sourceGatewayDltSystem; sessionData.recipientGatewayPubkey = request.recipientGatewayPubkey; sessionData.recipientGatewayDltSystem = request.recipientGatewayDltSystem; + sessionData.rollbackActionsPerformed = []; + sessionData.rollbackProofs = []; + sessionData.lastMessageReceivedTimestamp = Date.now().toString(); + + sessionData.maxRetries = request.maxRetries; + sessionData.maxTimeout = request.maxTimeout; this.sessions.set(sessionID, sessionData); return sessionID; } - public async lockFabricAsset(sessionID: string) { + async lockFabricAsset(sessionID: string): Promise { const fnTag = `${this.className}#lockFabricAsset()`; const sessionData = this.sessions.get(sessionID); @@ -739,39 +1152,216 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { throw new Error(`${fnTag}, session data is not correctly initialized`); } - if (this.fabricApi == undefined) { - //throw new Error(`${fnTag}, connection to Fabric is not defined`); - return ""; + let fabricLockAssetProof = ""; + + await this.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "lock-asset", + data: JSON.stringify(sessionData), + }); + + if (this.fabricApi != undefined) { + const response = await this.fabricApi.runTransactionV1({ + signingCredential: this.fabricSigningCredential, + channelName: this.fabricChannelName, + contractName: this.fabricContractName, + invocationType: FabricContractInvocationType.Send, + methodName: "LockAsset", + params: [this.fabricAssetID], + } as FabricRunTransactionRequest); + + const receiptLockRes = await this.fabricApi.getTransactionReceiptByTxIDV1( + { + signingCredential: this.fabricSigningCredential, + channelName: this.fabricChannelName, + contractName: "qscc", + invocationType: FabricContractInvocationType.Call, + methodName: "GetBlockByTxID", + params: [this.fabricChannelName, response.data.transactionId], + } as FabricRunTransactionRequest, + ); + + this.log.warn(receiptLockRes.data); + fabricLockAssetProof = JSON.stringify(receiptLockRes.data); } - const lockRes = await this.fabricApi.runTransactionV1({ - signingCredential: this.fabricSigningCredential, - channelName: this.fabricChannelName, - contractName: this.fabricContractName, - invocationType: FabricContractInvocationType.Send, - methodName: "LockAsset", - params: [this.fabricAssetID], - } as FabricRunTransactionRequest); + sessionData.lockEvidenceClaim = fabricLockAssetProof; - const receiptLockRes = await this.fabricApi.getTransactionReceiptByTxIDV1({ - signingCredential: this.fabricSigningCredential, - channelName: this.fabricChannelName, - contractName: "qscc", - invocationType: FabricContractInvocationType.Call, - methodName: "GetBlockByTxID", - params: [this.fabricChannelName, lockRes.data.transactionId], - } as FabricRunTransactionRequest); + this.sessions.set(sessionID, sessionData); + + this.log.info(`${fnTag}, proof of the asset lock: ${fabricLockAssetProof}`); - this.log.warn(receiptLockRes.data); - const fabricLockAssetProof = JSON.stringify(receiptLockRes.data); + await this.storeOdapProof({ + sessionID: sessionID, + type: "proof", + operation: "lock", + data: fabricLockAssetProof, + }); - sessionData.isFabricAssetLocked = true; + await this.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "lock-asset", + data: JSON.stringify(sessionData), + }); return fabricLockAssetProof; } - public async createBesuAsset(sessionID: string) { - const fnTag = `${this.className}#createBesuAsset()`; + async unlockFabricAsset(sessionID: string): Promise { + const fnTag = `${this.className}#unlockFabricAsset()`; + + const sessionData = this.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.rollbackActionsPerformed == undefined || + sessionData.rollbackProofs == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + let fabricUnlockAssetProof = ""; + + await this.storeOdapLog({ + sessionID: sessionID, + type: "exec-rollback", + operation: "unlock-asset", + data: JSON.stringify(sessionData), + }); + + if (this.fabricApi != undefined) { + const response = await this.fabricApi.runTransactionV1({ + signingCredential: this.fabricSigningCredential, + channelName: this.fabricChannelName, + contractName: this.fabricContractName, + invocationType: FabricContractInvocationType.Send, + methodName: "UnlockAsset", + params: [this.fabricAssetID], + } as FabricRunTransactionRequest); + + const receiptUnlock = await this.fabricApi.getTransactionReceiptByTxIDV1({ + signingCredential: this.fabricSigningCredential, + channelName: this.fabricChannelName, + contractName: "qscc", + invocationType: FabricContractInvocationType.Call, + methodName: "GetBlockByTxID", + params: [this.fabricChannelName, response.data.transactionId], + } as FabricRunTransactionRequest); + + this.log.warn(receiptUnlock.data); + fabricUnlockAssetProof = JSON.stringify(receiptUnlock.data); + } + + sessionData.rollbackActionsPerformed.push( + SessionDataRollbackActionsPerformedEnum.Unlock, + ); + sessionData.rollbackProofs.push(fabricUnlockAssetProof); + + this.sessions.set(sessionID, sessionData); + + this.log.info( + `${fnTag}, proof of the asset unlock: ${fabricUnlockAssetProof}`, + ); + + await this.storeOdapProof({ + sessionID: sessionID, + type: "proof-rollback", + operation: "unlock", + data: fabricUnlockAssetProof, + }); + + await this.storeOdapLog({ + sessionID: sessionID, + type: "done-rollback", + operation: "unlock-asset", + data: JSON.stringify(sessionData), + }); + + return fabricUnlockAssetProof; + } + + async createFabricAsset(sessionID: string): Promise { + const fnTag = `${this.className}#createFabricAsset()`; + + const sessionData = this.sessions.get(sessionID); + + if ( + sessionData == undefined || + this.fabricAssetID == undefined || + this.fabricChannelName == undefined || + this.fabricContractName == undefined || + this.fabricSigningCredential == undefined || + sessionData.rollbackProofs == undefined || + sessionData.rollbackActionsPerformed == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + let fabricCreateAssetProof = ""; + + await this.storeOdapLog({ + sessionID: sessionID, + type: "exec-rollback", + operation: "create-asset", + data: JSON.stringify(sessionData), + }); + + if (this.fabricApi != undefined) { + const response = await this.fabricApi.runTransactionV1({ + contractName: this.fabricContractName, + channelName: this.fabricChannelName, + params: [this.fabricAssetID, "19"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: this.fabricSigningCredential, + }); + + const receiptCreate = await this.fabricApi.getTransactionReceiptByTxIDV1({ + signingCredential: this.fabricSigningCredential, + channelName: this.fabricChannelName, + contractName: "qscc", + invocationType: FabricContractInvocationType.Call, + methodName: "GetBlockByTxID", + params: [this.fabricChannelName, response.data.transactionId], + } as FabricRunTransactionRequest); + + this.log.warn(receiptCreate.data); + fabricCreateAssetProof = JSON.stringify(receiptCreate.data); + } + + sessionData.rollbackActionsPerformed.push( + SessionDataRollbackActionsPerformedEnum.Create, + ); + + sessionData.rollbackProofs.push(fabricCreateAssetProof); + + this.sessions.set(sessionID, sessionData); + + this.log.info( + `${fnTag}, proof of the asset creation: ${fabricCreateAssetProof}`, + ); + + await this.storeOdapProof({ + sessionID: sessionID, + type: "proof-rollback", + operation: "create", + data: fabricCreateAssetProof, + }); + + await this.storeOdapLog({ + sessionID: sessionID, + type: "done-rollback", + operation: "create-asset", + data: JSON.stringify(sessionData), + }); + + return fabricCreateAssetProof; + } + + async deleteFabricAsset(sessionID: string): Promise { + const fnTag = `${this.className}#deleteFabricAsset()`; const sessionData = this.sessions.get(sessionID); @@ -779,13 +1369,83 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { throw new Error(`${fnTag}, session data is not correctly initialized`); } - if (this.besuApi == undefined) { - //throw new Error(`${fnTag}, connection to Fabric is not defined`); - return ""; + let fabricDeleteAssetProof = ""; + + await this.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "delete-asset", + data: JSON.stringify(sessionData), + }); + + if (this.fabricApi != undefined) { + const deleteRes = await this.fabricApi.runTransactionV1({ + signingCredential: this.fabricSigningCredential, + channelName: this.fabricChannelName, + contractName: this.fabricContractName, + invocationType: FabricContractInvocationType.Send, + methodName: "DeleteAsset", + params: [this.fabricAssetID], + } as FabricRunTransactionRequest); + + const receiptDeleteRes = await this.fabricApi.getTransactionReceiptByTxIDV1( + { + signingCredential: this.fabricSigningCredential, + channelName: this.fabricChannelName, + contractName: "qscc", + invocationType: FabricContractInvocationType.Call, + methodName: "GetBlockByTxID", + params: [this.fabricChannelName, deleteRes.data.transactionId], + } as FabricRunTransactionRequest, + ); + + this.log.warn(receiptDeleteRes.data); + fabricDeleteAssetProof = JSON.stringify(receiptDeleteRes.data); + } + + sessionData.commitFinalClaim = fabricDeleteAssetProof; + + this.sessions.set(sessionID, sessionData); + + this.log.info( + `${fnTag}, proof of the asset deletion: ${fabricDeleteAssetProof}`, + ); + + await this.storeOdapProof({ + sessionID: sessionID, + type: "proof", + operation: "delete", + data: fabricDeleteAssetProof, + }); + + await this.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "delete-asset", + data: JSON.stringify(sessionData), + }); + + return fabricDeleteAssetProof; + } + + async createBesuAsset(sessionID: string): Promise { + const fnTag = `${this.className}#createBesuAsset()`; + + const sessionData = this.sessions.get(sessionID); + + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is not correctly initialized`); } let besuCreateAssetProof = ""; + await this.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "create-asset", + data: JSON.stringify(sessionData), + }); + if (this.besuApi != undefined) { const besuCreateRes = await this.besuApi.invokeContractV1({ contractName: this.besuContractName, @@ -817,57 +1477,279 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService { const besuCreateAssetReceipt = besuCreateResDataJson.out.transactionReceipt; besuCreateAssetProof = JSON.stringify(besuCreateAssetReceipt); - const besuCreateProofID = `${sessionID}-proof-of-create`; + } - await this.publishOdapProof( - besuCreateProofID, - JSON.stringify(besuCreateAssetReceipt), - ); + sessionData.commitAcknowledgementClaim = besuCreateAssetProof; + + this.sessions.set(sessionID, sessionData); + + this.log.info( + `${fnTag}, proof of the asset creation: ${besuCreateAssetProof}`, + ); + + await this.storeOdapProof({ + sessionID: sessionID, + type: "proof", + operation: "create", + data: besuCreateAssetProof, + }); + + await this.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "create-asset", + data: JSON.stringify(sessionData), + }); - sessionData.isBesuAssetCreated = true; - } return besuCreateAssetProof; } - public async deleteFabricAsset(sessionID: string) { - const fnTag = `${this.className}#deleteFabricAsset()`; + async deleteBesuAsset(sessionID: string): Promise { + const fnTag = `${this.className}#deleteBesuAsset()`; const sessionData = this.sessions.get(sessionID); - if (sessionData == undefined) { + if ( + sessionData == undefined || + sessionData.rollbackActionsPerformed == undefined || + sessionData.rollbackProofs == undefined + ) { throw new Error(`${fnTag}, session data is not correctly initialized`); } - if (this.fabricApi == undefined) { - //throw new Error(`${fnTag}, connection to Fabric is not defined`); - return ""; + let besuDeleteAssetProof = ""; + + await this.storeOdapLog({ + sessionID: sessionID, + type: "exec-rollback", + operation: "delete-asset", + data: JSON.stringify(sessionData), + }); + + if (this.besuApi != undefined) { + // we need to lock the asset first + await this.besuApi.invokeContractV1({ + contractName: this.besuContractName, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + gas: 1000000, + params: [this.besuAssetID], + signingCredential: this.besuWeb3SigningCredential, + keychainId: this.besuKeychainId, + } as BesuInvokeContractV1Request); + + const assetCreationResponse = await this.besuApi.invokeContractV1({ + contractName: this.besuContractName, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + gas: 1000000, + params: [this.besuAssetID], + signingCredential: this.besuWeb3SigningCredential, + keychainId: this.besuKeychainId, + } as BesuInvokeContractV1Request); + + if (assetCreationResponse.status != 200) { + throw new Error(`${fnTag}, besu delete asset error`); + } + + const assetCreationResponseDataJson = JSON.parse( + JSON.stringify(assetCreationResponse.data), + ); + + if (assetCreationResponseDataJson.out == undefined) { + throw new Error(`${fnTag}, besu res data out undefined`); + } + + if (assetCreationResponseDataJson.out.transactionReceipt == undefined) { + throw new Error(`${fnTag}, undefined besu transact receipt`); + } + + const besuCreateAssetReceipt = + assetCreationResponseDataJson.out.transactionReceipt; + besuDeleteAssetProof = JSON.stringify(besuCreateAssetReceipt); } - const deleteRes = await this.fabricApi.runTransactionV1({ - signingCredential: this.fabricSigningCredential, - channelName: this.fabricChannelName, - contractName: this.fabricContractName, - invocationType: FabricContractInvocationType.Send, - methodName: "DeleteAsset", - params: [this.fabricAssetID], - } as FabricRunTransactionRequest); + sessionData.rollbackActionsPerformed.push( + SessionDataRollbackActionsPerformedEnum.Delete, + ); + sessionData.rollbackProofs.push(besuDeleteAssetProof); - const receiptDeleteRes = await this.fabricApi.getTransactionReceiptByTxIDV1( - { - signingCredential: this.fabricSigningCredential, - channelName: this.fabricChannelName, - contractName: "qscc", - invocationType: FabricContractInvocationType.Call, - methodName: "GetBlockByTxID", - params: [this.fabricChannelName, deleteRes.data.transactionId], - } as FabricRunTransactionRequest, + this.sessions.set(sessionID, sessionData); + + this.log.info( + `${fnTag}, proof of the asset deletion: ${besuDeleteAssetProof}`, ); - this.log.warn(receiptDeleteRes.data); - const fabricDeleteAssetProof = JSON.stringify(receiptDeleteRes.data); + await this.storeOdapProof({ + sessionID: sessionID, + type: "proof-rollback", + operation: "delete", + data: besuDeleteAssetProof, + }); - sessionData.isFabricAssetDeleted = true; + await this.storeOdapLog({ + sessionID: sessionID, + type: "done-rollback", + operation: "delete-asset", + data: JSON.stringify(sessionData), + }); - return fabricDeleteAssetProof; + return besuDeleteAssetProof; + } + + async Revert(sessionID: string): Promise { + await this.rollback(sessionID); + await sendRollbackMessage(sessionID, this, true); + } + + async rollback(sessionID: string) { + const fnTag = `${this.className}#rollback()`; + const sessionData = this.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.step == undefined || + sessionData.lastSequenceNumber == undefined + ) { + throw new Error(`${fnTag}, session data is undefined`); + } + + sessionData.rollback = true; + + this.log.info(`${fnTag}, rolling back session ${sessionID}`); + + if (this.isClientGateway(sessionID)) { + if ( + this.fabricApi == undefined || + this.fabricContractName == undefined || + this.fabricChannelName == undefined || + this.fabricAssetID == undefined || + this.fabricSigningCredential == undefined + ) + return; + + if ( + await fabricAssetExists( + this, + this.fabricContractName, + this.fabricChannelName, + this.fabricAssetID, + this.fabricSigningCredential, + ) + ) { + if ( + await isFabricAssetLocked( + this, + this.fabricContractName, + this.fabricChannelName, + this.fabricAssetID, + this.fabricSigningCredential, + ) + ) { + // Rollback locking of the asset + await this.unlockFabricAsset(sessionID); + } + } else { + // Rollback extinguishment of the asset + await this.createFabricAsset(sessionID); + } + } else { + if ( + this.besuApi == undefined || + this.besuContractName == undefined || + this.besuKeychainId == undefined || + this.besuAssetID == undefined || + this.besuWeb3SigningCredential == undefined + ) + return; + + if ( + await besuAssetExists( + this, + this.besuContractName, + this.besuKeychainId, + this.besuAssetID, + this.besuWeb3SigningCredential, + ) + ) { + // Rollback creation of the asset + await this.deleteBesuAsset(sessionID); + } + } + } + + async makeRequest( + sessionID: string, + request: Promise, + message: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise { + const fnTag = `${this.className}#makeRequest()`; + + const sessionData = this.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + let numberOfTries = 0; + let response = undefined; + + while (numberOfTries < sessionData.maxRetries) { + response = await request.catch(() => { + this.log.info(`${fnTag}, ${message} message failed. Trying again...`); + numberOfTries++; + }); + + if (response != void 0) break; + } + + if (response != void 0 && response.status == 200) { + return; + } + + // When rolling back there is no problem of not receiving an answer + if (!message.match("Rollback")) { + this.log.info( + `${fnTag}, ${message} message was not sent. Initiating time...`, + ); + await new Promise((resolve) => + setTimeout(resolve, sessionData.maxTimeout), + ).then(async () => { + // we check if a message was received, otherwise we have a timeout and rollback + const sessionData = this.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.lastMessageReceivedTimestamp == undefined || + sessionData.maxTimeout == undefined + ) { + throw new Error( + `${fnTag}, session data is not correctly initialized`, + ); + } + + const differenceOfTime = + new Date().getTime() - + new Date(sessionData.lastMessageReceivedTimestamp).getTime(); + + if (differenceOfTime > sessionData.maxTimeout) { + this.log.info(`${fnTag}, no response received, rolling back`); + await this.Revert(sessionID); + throw new Error( + `${fnTag}, ${message} message failed. Timeout exceeded. Check connection with server gateway.`, + ); + } + + this.log.info(`${fnTag}, a response was received`); + return; + }); + } + + return response; } } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-success.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-success.ts new file mode 100644 index 0000000000..94df21db1f --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-success.ts @@ -0,0 +1,96 @@ +import { RecoverSuccessV1Message } from "../../generated/openapi/typescript-axios"; +import { LoggerProvider } from "@hyperledger/cactus-common"; +import { PluginOdapGateway } from "../plugin-odap-gateway"; +// import { SHA256 } from "crypto-js"; + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "recover-success-helper", +}); + +export async function sendRecoverSuccessMessage( + sessionID: string, + odap: PluginOdapGateway, + remote: boolean, +): Promise { + const fnTag = `${odap.className}#sendRecoverSuccessMessage()`; + + const sessionData = odap.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.sourceBasePath == undefined || + sessionData.recipientBasePath == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + const recoverSuccessMessage: RecoverSuccessV1Message = { + sessionID: sessionID, + success: true, + signature: "", + }; + + const signature = PluginOdapGateway.bufArray2HexStr( + odap.sign(JSON.stringify(recoverSuccessMessage)), + ); + + recoverSuccessMessage.signature = signature; + + log.info(`${fnTag}, sending RecoverSuccess message...`); + + if (!remote) { + return recoverSuccessMessage; + } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + odap.isClientGateway(sessionID) + ? sessionData.recipientBasePath + : sessionData.sourceBasePath, + ).recoverV1Success(recoverSuccessMessage), + "RecoverSuccess", + ); +} + +export async function checkValidRecoverSuccessMessage( + response: RecoverSuccessV1Message, + odap: PluginOdapGateway, +): Promise { + const fnTag = `${odap.className}#checkValidRecoverSuccessMessage`; + + const sessionID = response.sessionID; + const sessionData = odap.sessions.get(sessionID); + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + const pubKey = odap.isClientGateway(response.sessionID) + ? sessionData.recipientGatewayPubkey + : sessionData.sourceGatewayPubkey; + + if (pubKey == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + // if (response.messageType != OdapMessageType.CommitFinalResponse) { + // throw new Error(`${fnTag}, wrong message type for CommitFinalResponse`); + // } + + if (!response.success) { + throw new Error(`${fnTag}, RecoverSuccess message is invalid`); + } + + if (!odap.verifySignature(response, pubKey)) { + throw new Error( + `${fnTag}, RecoverUpdateAckMessage message signature verification failed`, + ); + } + + // storeSessionData(response, odap); + + log.info(`RecoverSuccessMessage passed all checks.`); +} diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update-ack.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update-ack.ts new file mode 100644 index 0000000000..c393fd99c4 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update-ack.ts @@ -0,0 +1,97 @@ +import { RecoverUpdateAckV1Message } from "../../generated/openapi/typescript-axios"; +import { LoggerProvider } from "@hyperledger/cactus-common"; +import { PluginOdapGateway } from "../plugin-odap-gateway"; +// import { SHA256 } from "crypto-js"; + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "recover-update-ack-helper", +}); + +export async function sendRecoverUpdateAckMessage( + sessionID: string, + odap: PluginOdapGateway, + remote: boolean, +): Promise { + const fnTag = `${odap.className}#sendRecoverUpdateAckMessage()`; + + const sessionData = odap.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.sourceBasePath == undefined || + sessionData.recipientBasePath == undefined || + sessionData.lastSequenceNumber == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + const recoverUpdateMessage: RecoverUpdateAckV1Message = { + sessionID: sessionID, + success: true, + changedEntriesHash: [], + signature: "", + }; + + const signature = PluginOdapGateway.bufArray2HexStr( + odap.sign(JSON.stringify(recoverUpdateMessage)), + ); + + recoverUpdateMessage.signature = signature; + + log.info(`${fnTag}, sending RecoverUpdateAck message...`); + + if (!remote) { + return recoverUpdateMessage; + } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + odap.isClientGateway(sessionID) + ? sessionData.recipientBasePath + : sessionData.sourceBasePath, + ).recoverUpdateAckV1Message(recoverUpdateMessage), + "RecoverUpdateAck", + ); +} + +export async function checkValidRecoverUpdateAckMessage( + response: RecoverUpdateAckV1Message, + odap: PluginOdapGateway, +): Promise { + const fnTag = `${odap.className}#checkValidRecoverUpdateAckMessage`; + + const sessionID = response.sessionID; + const sessionData = odap.sessions.get(sessionID); + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + const pubKey = odap.isClientGateway(response.sessionID) + ? sessionData.recipientGatewayPubkey + : sessionData.sourceGatewayPubkey; + + if (pubKey == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + // if (response.messageType != OdapMessageType.CommitFinalResponse) { + // throw new Error(`${fnTag}, wrong message type for CommitFinalResponse`); + // } + + // check if this is a valid recover update ack message + // check valid recovered logs + + if (!odap.verifySignature(response, pubKey)) { + throw new Error( + `${fnTag}, RecoverUpdateAckMessage message signature verification failed`, + ); + } + + // storeSessionData(response, odap); + + log.info(`RecoverUpdateAckMessage passed all checks.`); +} diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update.ts new file mode 100644 index 0000000000..96e3392069 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update.ts @@ -0,0 +1,135 @@ +import { + OdapLocalLog, + RecoverUpdateV1Message, +} from "../../generated/openapi/typescript-axios"; +import { LoggerProvider } from "@hyperledger/cactus-common"; +import { PluginOdapGateway } from "../plugin-odap-gateway"; +import { SHA256 } from "crypto-js"; +// import { SHA256 } from "crypto-js"; + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "recover-update-helper", +}); + +export async function sendRecoverUpdateMessage( + sessionID: string, + odap: PluginOdapGateway, + remote: boolean, +): Promise { + const fnTag = `${odap.className}#sendRecoverUpdateMessage()`; + + const sessionData = odap.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.sourceBasePath == undefined || + sessionData.recipientBasePath == undefined || + sessionData.lastSequenceNumber == undefined || + sessionData.lastLogEntryTimestamp == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + const recoveredLogs: OdapLocalLog[] = await odap.getLogsMoreRecentThanTimestamp( + sessionData.lastLogEntryTimestamp, + ); + + const recoverUpdateMessage: RecoverUpdateV1Message = { + sessionID: sessionID, + recoveredLogs: recoveredLogs, + signature: "", + }; + + const signature = PluginOdapGateway.bufArray2HexStr( + odap.sign(JSON.stringify(recoverUpdateMessage)), + ); + + recoverUpdateMessage.signature = signature; + + log.info(`${fnTag}, sending RecoverUpdate message...`); + + if (!remote) { + return recoverUpdateMessage; + } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + odap.isClientGateway(sessionID) + ? sessionData.recipientBasePath + : sessionData.sourceBasePath, + ).recoverUpdateV1Message(recoverUpdateMessage), + "RecoverUpdate", + ); +} + +export async function checkValidRecoverUpdateMessage( + response: RecoverUpdateV1Message, + odap: PluginOdapGateway, +): Promise { + const fnTag = `${odap.className}#checkValidRecoverUpdateMessage`; + + const sessionID = response.sessionID; + const sessionData = odap.sessions.get(sessionID); + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + const pubKey = odap.isClientGateway(response.sessionID) + ? sessionData.recipientGatewayPubkey + : sessionData.sourceGatewayPubkey; + + if (pubKey == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + // if (response.messageType != OdapMessageType.CommitFinalResponse) { + // throw new Error(`${fnTag}, wrong message type for CommitFinalResponse`); + // } + + // check if this is a valid recover update message + + if (!odap.verifySignature(response, pubKey)) { + throw new Error( + `${fnTag}, RecoverUpdateMessage message signature verification failed`, + ); + } + + // check logs from counter party gateway + const recoveredLogs = response.recoveredLogs; + let maxTimestamp = "0"; + + for (const recLog of recoveredLogs) { + if (recLog.key == undefined) { + throw new Error(`${fnTag}, the received log is not correctly defined`); + } + + log.info(`${fnTag}, received log: ${JSON.stringify(recLog)}`); + + const ipfsLog = await odap.getLogFromIPFS(recLog.key); + + const hash = SHA256(JSON.stringify(recLog)).toString(); + + if (ipfsLog.hash != hash) { + throw new Error( + `${fnTag}, RecoverUpdateMessage message has invalid recovered logs`, + ); + } + + if (recLog.data == undefined || recLog.timestamp == undefined) { + throw new Error( + `${fnTag}, RecoverUpdateMessage message is not correctly defined`, + ); + } + + if (parseInt(recLog.timestamp) > parseInt(maxTimestamp)) { + maxTimestamp = recLog.timestamp; + odap.sessions.set(sessionID, JSON.parse(recLog.data)); + } + } + + log.info(`RecoverUpdateMessage passed all checks.`); +} diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover.ts new file mode 100644 index 0000000000..672ac9d082 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover.ts @@ -0,0 +1,99 @@ +import { RecoverV1Message } from "../../generated/openapi/typescript-axios"; +import { LoggerProvider } from "@hyperledger/cactus-common"; +import { PluginOdapGateway } from "../plugin-odap-gateway"; + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "recover-helper", +}); + +export async function sendRecoverMessage( + sessionID: string, + odap: PluginOdapGateway, + remote: boolean, +): Promise { + const fnTag = `${odap.className}#sendRecoverMessage()`; + + const sessionData = odap.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.sourceBasePath == undefined || + sessionData.recipientBasePath == undefined || + sessionData.lastSequenceNumber == undefined || + sessionData.lastLogEntryTimestamp == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + const recoverMessage: RecoverV1Message = { + sessionID: sessionID, + odapPhase: "sessionData.odapPhase", + sequenceNumber: sessionData.lastSequenceNumber, + lastLogEntryTimestamp: sessionData.lastLogEntryTimestamp, + signature: "", + }; + + const signature = PluginOdapGateway.bufArray2HexStr( + odap.sign(JSON.stringify(recoverMessage)), + ); + + recoverMessage.signature = signature; + + log.info(`${fnTag}, sending Recover message...`); + + if (!remote) { + return recoverMessage; + } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + odap.isClientGateway(sessionID) + ? sessionData.recipientBasePath + : sessionData.sourceBasePath, + ).recoverV1Message(recoverMessage), + "Recover", + ); +} + +export async function checkValidRecoverMessage( + response: RecoverV1Message, + odap: PluginOdapGateway, +): Promise { + const fnTag = `${odap.className}#checkValidRecoverMessage`; + + const sessionID = response.sessionID; + const sessionData = odap.sessions.get(sessionID); + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + const pubKey = odap.isClientGateway(response.sessionID) + ? sessionData.recipientGatewayPubkey + : sessionData.sourceGatewayPubkey; + + if (pubKey == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + // if (response.messageType != OdapMessageType.CommitFinalResponse) { + // throw new Error(`${fnTag}, wrong message type for CommitFinalResponse`); + // } + + if (response.lastLogEntryTimestamp == undefined) { + throw new Error(`${fnTag}, last log entry timestamp is not valid`); + } + + if (!odap.verifySignature(response, pubKey)) { + throw new Error( + `${fnTag}, RecoverMessage message signature verification failed`, + ); + } + + sessionData.lastLogEntryTimestamp = response.lastLogEntryTimestamp; + + log.info(`RecoverMessage passed all checks.`); +} diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback-ack.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback-ack.ts new file mode 100644 index 0000000000..6710c5144c --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback-ack.ts @@ -0,0 +1,92 @@ +import { RollbackAckV1Message } from "../../generated/openapi/typescript-axios"; +import { LoggerProvider } from "@hyperledger/cactus-common"; +import { PluginOdapGateway } from "../plugin-odap-gateway"; +// import { SHA256 } from "crypto-js"; + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "rollback-ack-helper", +}); + +export async function sendRollbackAckMessage( + sessionID: string, + odap: PluginOdapGateway, + remote: boolean, +): Promise { + const fnTag = `${odap.className}#sendRollbackAckMessage()`; + + const sessionData = odap.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.rollbackProofs == undefined || + sessionData.sourceBasePath == undefined || + sessionData.recipientBasePath == undefined || + sessionData.rollbackActionsPerformed == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + const rollbackAckMessage: RollbackAckV1Message = { + sessionID: sessionID, + success: true, + signature: "", + }; + + const signature = PluginOdapGateway.bufArray2HexStr( + odap.sign(JSON.stringify(rollbackAckMessage)), + ); + + rollbackAckMessage.signature = signature; + + log.info(`${fnTag}, sending Rollback Ack message...`); + + if (!remote) { + return rollbackAckMessage; + } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + odap.isClientGateway(sessionID) + ? sessionData.recipientBasePath + : sessionData.sourceBasePath, + ).rollbackAckV1Message(rollbackAckMessage), + "RollbackAck", + ); +} + +export async function checkValidRollbackAckMessage( + response: RollbackAckV1Message, + odap: PluginOdapGateway, +): Promise { + const fnTag = `${odap.className}#checkValidRollbackAckMessage`; + + const sessionID = response.sessionID; + const sessionData = odap.sessions.get(sessionID); + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + const pubKey = odap.isClientGateway(response.sessionID) + ? sessionData.recipientGatewayPubkey + : sessionData.sourceGatewayPubkey; + + if (pubKey == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + // if (response.messageType != OdapMessageType.CommitFinalResponse) { + // throw new Error(`${fnTag}, wrong message type for CommitFinalResponse`); + // } + + if (!odap.verifySignature(response, pubKey)) { + throw new Error( + `${fnTag}, RollbackAckMessage message signature verification failed`, + ); + } + + log.info(`RollbackAckMessage passed all checks.`); +} diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback.ts new file mode 100644 index 0000000000..19f1429345 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/rollback.ts @@ -0,0 +1,94 @@ +import { RollbackV1Message } from "../../generated/openapi/typescript-axios"; +import { LoggerProvider } from "@hyperledger/cactus-common"; +import { PluginOdapGateway } from "../plugin-odap-gateway"; +// import { SHA256 } from "crypto-js"; + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "rollback-helper", +}); + +export async function sendRollbackMessage( + sessionID: string, + odap: PluginOdapGateway, + remote: boolean, +): Promise { + const fnTag = `${odap.className}#sendRollbackMessage()`; + + const sessionData = odap.sessions.get(sessionID); + + if ( + sessionData == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || + sessionData.rollbackProofs == undefined || + sessionData.sourceBasePath == undefined || + sessionData.recipientBasePath == undefined || + sessionData.rollbackActionsPerformed == undefined + ) { + throw new Error(`${fnTag}, session data is not correctly initialized`); + } + + const rollbackMessage: RollbackV1Message = { + sessionID: sessionID, + success: true, + actionPerformed: sessionData.rollbackActionsPerformed, + proofs: sessionData.rollbackProofs, + signature: "", + }; + + const signature = PluginOdapGateway.bufArray2HexStr( + odap.sign(JSON.stringify(rollbackMessage)), + ); + + rollbackMessage.signature = signature; + + log.info(`${fnTag}, sending Rollback message...`); + + if (!remote) { + return rollbackMessage; + } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + odap.isClientGateway(sessionID) + ? sessionData.recipientBasePath + : sessionData.sourceBasePath, + ).rollbackV1Message(rollbackMessage), + "Rollback", + ); +} + +export async function checkValidRollbackMessage( + response: RollbackV1Message, + odap: PluginOdapGateway, +): Promise { + const fnTag = `${odap.className}#checkValidRollbackMessage`; + + const sessionID = response.sessionID; + const sessionData = odap.sessions.get(sessionID); + if (sessionData == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + const pubKey = odap.isClientGateway(response.sessionID) + ? sessionData.recipientGatewayPubkey + : sessionData.sourceGatewayPubkey; + + if (pubKey == undefined) { + throw new Error(`${fnTag}, session data is undefined`); + } + + // if (response.messageType != OdapMessageType.CommitFinalResponse) { + // throw new Error(`${fnTag}, wrong message type for CommitFinalResponse`); + // } + + if (!odap.verifySignature(response, pubKey)) { + throw new Error( + `${fnTag}, RollbackMessage message signature verification failed`, + ); + } + + log.info(`RollbackMessage passed all checks.`); +} diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-final.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-final.ts index cb69119c34..00d6ba21e9 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-final.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-final.ts @@ -14,37 +14,38 @@ const log = LoggerProvider.getOrCreate({ export async function sendCommitFinalResponse( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendCommitFinalResponse()`; const sessionData = odap.sessions.get(sessionID); if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.sourceBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || sessionData.recipientGatewayPubkey == undefined || + sessionData.commitAcknowledgementClaim == undefined || sessionData.commitFinalRequestMessageHash == undefined ) { throw new Error(`${fnTag}, session data is undefined`); } - const besuCreateAssetProof = await odap.createBesuAsset(sessionID); - sessionData.commitAcknowledgementClaim = besuCreateAssetProof; - const commitFinalResponseMessage: CommitFinalV1Response = { sessionID: sessionID, messageType: OdapMessageType.CommitFinalResponse, clientIdentityPubkey: sessionData.sourceGatewayPubkey, serverIdentityPubkey: sessionData.recipientGatewayPubkey, - commitAcknowledgementClaim: besuCreateAssetProof, + commitAcknowledgementClaim: sessionData.commitAcknowledgementClaim, hashCommitFinal: sessionData.commitFinalRequestMessageHash, - serverSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - commitFinalResponseMessage.serverSignature = odap.bufArray2HexStr( + commitFinalResponseMessage.signature = PluginOdapGateway.bufArray2HexStr( await odap.sign(JSON.stringify(commitFinalResponseMessage)), ); @@ -53,34 +54,30 @@ export async function sendCommitFinalResponse( ).toString(); sessionData.serverSignatureCommitFinalResponseMessage = - commitFinalResponseMessage.serverSignature; - - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "ack", - operation: "commit-prepare", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + commitFinalResponseMessage.signature; - sessionData.step++; + await odap.storeOdapLog({ + sessionID: sessionID, + type: "ack", + operation: "final", + data: JSON.stringify(sessionData), + }); odap.sessions.set(sessionID, sessionData); - // Log init??? - log.info(`${fnTag}, sending CommitFinalResponse...`); - const response = await odap - .getOdapAPI(sessionData.sourceBasePath) - .phase3CommitFinalResponseV1(commitFinalResponseMessage); - - if (response.status != 200) { - throw new Error(`${fnTag}, CommitFinalResponse message failed`); + if (!remote) { + return commitFinalResponseMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.sourceBasePath, + ).phase3CommitFinalResponseV1(commitFinalResponseMessage), + "CommitFinalResponse", + ); } export async function checkValidCommitFinalRequest( @@ -102,41 +99,32 @@ export async function checkValidCommitFinalRequest( ); } - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "exec", - operation: "commit-final", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "final", + data: JSON.stringify(sessionData), + }); if (request.messageType != OdapMessageType.CommitFinalRequest) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, wrong message type for CommitFinalRequest`); } if (request.sequenceNumber != sessionData.lastSequenceNumber + 1) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, CommitFinalRequest sequence number incorrect`); } if (request.commitFinalClaim == undefined) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, claim presented by client is invalid`); } if (sessionData.recipientGatewayPubkey != request.serverIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitFinalRequest serverIdentity public key does not match the one that was sent`, ); } if (sessionData.sourceGatewayPubkey != request.clientIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitFinalRequest clientIdentity public key does not match the one that was sent`, ); @@ -145,48 +133,44 @@ export async function checkValidCommitFinalRequest( if ( sessionData.commitPrepareResponseMessageHash != request.hashCommitPrepareAck ) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, previous message hash does not match`); } - const sourceClientSignature = new Uint8Array( - Buffer.from(request.clientSignature, "hex"), - ); + if (!odap.verifySignature(request, request.clientIdentityPubkey)) { + throw new Error( + `${fnTag}, CommitFinalRequest message signature verification failed`, + ); + } - const sourceClientPubkey = new Uint8Array( - Buffer.from(request.clientIdentityPubkey, "hex"), + // We need to check somewhere if this phase is completed within the asset-lock duration. + const claimHash = SHA256(request.commitFinalClaim).toString(); + const retrievedClaim = await odap.getLogFromIPFS( + PluginOdapGateway.getOdapLogKey(sessionID, "proof", "delete"), ); - const signature = request.clientSignature; - request.clientSignature = ""; - if ( - !odap.verifySignature( - JSON.stringify(request), - sourceClientSignature, - sourceClientPubkey, - ) - ) { - await odap.Revert(sessionID); + if (claimHash != retrievedClaim.hash) { throw new Error( - `${fnTag}, CommitFinalRequest message signature verification failed`, + `${fnTag}, Commit Final Claim hash does not match the one stored in IPFS`, ); } - request.clientSignature = signature; - // We need to check somewhere if this phase is completed within the asset-lock duration. + if (!odap.verifySignature(retrievedClaim, request.clientIdentityPubkey)) { + throw new Error( + `${fnTag}, Commit Final Claim signature verification failed`, + ); + } storeSessionData(request, odap); - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "done", - operation: "commit-final", - nodes: `${odap.pubKey}}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "final", + data: JSON.stringify(sessionData), + }); + + sessionData.step = 10; + odap.sessions.set(sessionID, sessionData); log.info(`CommitFinalRequest passed all checks.`); } @@ -208,8 +192,7 @@ async function storeSessionData( JSON.stringify(request), ).toString(); - sessionData.clientSignatureCommitFinalRequestMessage = - request.clientSignature; + sessionData.clientSignatureCommitFinalRequestMessage = request.signature; odap.sessions.set(request.sessionID, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-preparation.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-preparation.ts index 4394e87c88..18e52f0af1 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-preparation.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/commit-preparation.ts @@ -14,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendCommitPreparationResponse( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendCommitPrepareResponse()`; const sessionData = odap.sessions.get(sessionID); @@ -22,6 +23,8 @@ export async function sendCommitPreparationResponse( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.sourceBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || @@ -37,11 +40,11 @@ export async function sendCommitPreparationResponse( clientIdentityPubkey: sessionData.sourceGatewayPubkey, serverIdentityPubkey: sessionData.recipientGatewayPubkey, hashCommitPrep: sessionData.commitPrepareRequestMessageHash, - serverSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - commitPreparationResponseMessage.serverSignature = odap.bufArray2HexStr( + commitPreparationResponseMessage.signature = PluginOdapGateway.bufArray2HexStr( await odap.sign(JSON.stringify(commitPreparationResponseMessage)), ); @@ -50,28 +53,28 @@ export async function sendCommitPreparationResponse( ).toString(); sessionData.serverSignatureCommitPreparationResponseMessage = - commitPreparationResponseMessage.serverSignature; - - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "ack", - operation: "commit-prepare", - nodes: `${odap.pubKey}->${sessionData.sourceGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + commitPreparationResponseMessage.signature; - log.info(`${fnTag}, sending CommitPreparationResponse...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "ack", + operation: "prepare", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.sourceBasePath) - .phase3CommitPreparationResponseV1(commitPreparationResponseMessage); + log.info(`${fnTag}, sending CommitPreparationResponse...`); - if (response.status != 200) { - throw new Error(`${fnTag}, CommitPreparationResponse message failed`); + if (!remote) { + return commitPreparationResponseMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.sourceBasePath, + ).phase3CommitPreparationResponseV1(commitPreparationResponseMessage), + "CommitPreparationResponse", + ); } export async function checkValidCommitPreparationRequest( @@ -95,7 +98,6 @@ export async function checkValidCommitPreparationRequest( // We need to check somewhere if this phase is completed within the asset-lock duration. if (request.messageType != OdapMessageType.CommitPreparationRequest) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, wrong message type for CommitPreparationRequest`, ); @@ -110,71 +112,45 @@ export async function checkValidCommitPreparationRequest( if ( sessionData.lockEvidenceResponseMessageHash != request.hashLockEvidenceAck ) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, previous message hash does not match`); } if (sessionData.recipientGatewayPubkey != request.serverIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitPreparationRequest serverIdentity public key does not match the one that was sent`, ); } if (sessionData.sourceGatewayPubkey != request.clientIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, CommitPreparationRequest clientIdentity public key does not match the one that was sent`, ); } - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "exec", - operation: "commit-prepare", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); - - const sourceClientSignature = new Uint8Array( - Buffer.from(request.clientSignature, "hex"), - ); - - const sourceClientPubkey = new Uint8Array( - Buffer.from(request.clientIdentityPubkey, "hex"), - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "prepare", + data: JSON.stringify(sessionData), + }); - const signature = request.clientSignature; - request.clientSignature = ""; - if ( - !odap.verifySignature( - JSON.stringify(request), - sourceClientSignature, - sourceClientPubkey, - ) - ) { - await odap.Revert(sessionID); + if (!odap.verifySignature(request, request.clientIdentityPubkey)) { throw new Error( `${fnTag}, CommitPreparationRequest message signature verification failed`, ); } - request.clientSignature = signature; storeSessionData(request, odap); - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "done", - operation: "commit-prepare", - nodes: `${odap.pubKey}}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "prepare", + data: JSON.stringify(sessionData), + }); + + sessionData.step = 8; + odap.sessions.set(sessionID, sessionData); log.info(`CommitPreparationRequest passed all checks.`); } @@ -195,7 +171,7 @@ function storeSessionData( ).toString(); sessionData.clientSignatureCommitPreparationRequestMessage = - request.clientSignature; + request.signature; odap.sessions.set(request.sessionID, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/lock-evidence.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/lock-evidence.ts index 467b4e7140..376be4d3f3 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/lock-evidence.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/lock-evidence.ts @@ -14,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendLockEvidenceResponse( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendLockEvidenceResponse()`; const sessionData = odap.sessions.get(sessionID); @@ -22,6 +23,8 @@ export async function sendLockEvidenceResponse( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.sourceBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || @@ -38,11 +41,11 @@ export async function sendLockEvidenceResponse( serverIdentityPubkey: sessionData.recipientGatewayPubkey, hashLockEvidenceRequest: sessionData.lockEvidenceRequestMessageHash, // server transfer number - serverSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - lockEvidenceResponseMessage.serverSignature = odap.bufArray2HexStr( + lockEvidenceResponseMessage.signature = PluginOdapGateway.bufArray2HexStr( await odap.sign(JSON.stringify(lockEvidenceResponseMessage)), ); @@ -51,28 +54,28 @@ export async function sendLockEvidenceResponse( ).toString(); sessionData.serverSignatureLockEvidenceResponseMessage = - lockEvidenceResponseMessage.serverSignature; - - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "ack", - operation: "lock", - nodes: `${odap.pubKey}->${sessionData.sourceGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + lockEvidenceResponseMessage.signature; - log.info(`${fnTag}, sending LockEvidenceResponse...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "ack", + operation: "lock", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.sourceBasePath) - .phase2LockEvidenceResponseV1(lockEvidenceResponseMessage); + log.info(`${fnTag}, sending LockEvidenceResponse...`); - if (response.status != 200) { - throw new Error(`${fnTag}, LockEvidenceResponse message failed`); + if (!remote) { + return lockEvidenceResponseMessage; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.sourceBasePath, + ).phase2LockEvidenceResponseV1(lockEvidenceResponseMessage), + "LockEvidenceResponse", + ); } export async function checkValidLockEvidenceRequest( @@ -93,24 +96,18 @@ export async function checkValidLockEvidenceRequest( ); } - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "exec", - operation: "lock", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "lock", + data: JSON.stringify(sessionData), + }); if (request.messageType != OdapMessageType.LockEvidenceRequest) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, wrong message type for LockEvidenceRequest`); } if (request.sequenceNumber != sessionData.lastSequenceNumber + 1) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, LockEvidenceRequestMessage sequence number incorrect`, ); @@ -120,21 +117,18 @@ export async function checkValidLockEvidenceRequest( sessionData.transferCommenceMessageResponseHash != request.hashCommenceAckRequest ) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, previous message hash does not match the one that was sent`, ); } if (sessionData.recipientGatewayPubkey != request.serverIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, LockEvidenceRequest serverIdentity public key does not match the one that was sent`, ); } if (sessionData.sourceGatewayPubkey != request.clientIdentityPubkey) { - await odap.Revert(sessionID); throw new Error( `${fnTag}, LockEvidenceRequest clientIdentity public key does not match the one that was sent`, ); @@ -144,46 +138,43 @@ export async function checkValidLockEvidenceRequest( request.lockEvidenceClaim == undefined || new Date() > new Date(request.lockEvidenceExpiration) ) { - await odap.Revert(sessionID); throw new Error(`${fnTag}, invalid or expired lock evidence claim`); } - const sourceClientSignature = new Uint8Array( - Buffer.from(request.clientSignature, "hex"), - ); + if (!odap.verifySignature(request, request.clientIdentityPubkey)) { + throw new Error( + `${fnTag}, LockEvidenceRequest message signature verification failed`, + ); + } - const sourceClientPubkey = new Uint8Array( - Buffer.from(request.clientIdentityPubkey, "hex"), + const claimHash = SHA256(request.lockEvidenceClaim).toString(); + const retrievedClaim = await odap.getLogFromIPFS( + PluginOdapGateway.getOdapLogKey(sessionID, "proof", "lock"), ); - const signature = request.clientSignature; - request.clientSignature = ""; - if ( - !odap.verifySignature( - JSON.stringify(request), - sourceClientSignature, - sourceClientPubkey, - ) - ) { - await odap.Revert(sessionID); + if (claimHash != retrievedClaim.hash) { throw new Error( - `${fnTag}, LockEvidenceRequest message signature verification failed`, + `${fnTag}, LockEvidence Claim hash does not match the one stored in IPFS`, + ); + } + + if (!odap.verifySignature(retrievedClaim, request.clientIdentityPubkey)) { + throw new Error( + `${fnTag}, LockEvidence Claim message signature verification failed`, ); } - request.clientSignature = signature; storeSessionData(request, odap); - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "done", - operation: "lock", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "lock", + data: JSON.stringify(sessionData), + }); + + sessionData.step = 6; + odap.sessions.set(sessionID, sessionData); log.info(`LockEvidenceRequest passed all checks.`); } @@ -203,8 +194,7 @@ function storeSessionData( JSON.stringify(request), ).toString(); - sessionData.clientSignatureLockEvidenceRequestMessage = - request.clientSignature; + sessionData.clientSignatureLockEvidenceRequestMessage = request.signature; sessionData.lockEvidenceClaim = request.lockEvidenceClaim; diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-commence.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-commence.ts index 856ba326e5..ae1f6aa23b 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-commence.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-commence.ts @@ -1,7 +1,5 @@ import { LoggerProvider } from "@hyperledger/cactus-common"; import { - // Configuration, - // DefaultApi as OdapApi, TransferCommenceV1Request, TransferCommenceV1Response, } from "../../generated/openapi/typescript-axios"; @@ -16,7 +14,8 @@ const log = LoggerProvider.getOrCreate({ export async function sendTransferCommenceResponse( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendTransferCommenceResponse()`; const sessionData = odap.sessions.get(sessionID); @@ -24,6 +23,8 @@ export async function sendTransferCommenceResponse( if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.sourceBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.sourceGatewayPubkey == undefined || @@ -40,11 +41,11 @@ export async function sendTransferCommenceResponse( serverIdentityPubkey: sessionData.recipientGatewayPubkey, hashCommenceRequest: sessionData.transferCommenceMessageRequestHash, // serverTransferNumber?? - serverSignature: "", + signature: "", sequenceNumber: ++sessionData.lastSequenceNumber, }; - transferCommenceResponse.serverSignature = odap.bufArray2HexStr( + transferCommenceResponse.signature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(transferCommenceResponse)), ); @@ -53,28 +54,28 @@ export async function sendTransferCommenceResponse( ).toString(); sessionData.serverSignatureTransferCommenceResponseMessage = - transferCommenceResponse.serverSignature; - - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "ack", - operation: "commence", - nodes: `${odap.pubKey}->${sessionData.sourceGatewayPubkey}`, - }, - `${sessionID}-${sessionData.step.toString()}`, - ); + transferCommenceResponse.signature; - log.info(`${fnTag}, sending TransferCommenceResponse...`); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "ack", + operation: "commence", + data: JSON.stringify(sessionData), + }); - const response = await odap - .getOdapAPI(sessionData.sourceBasePath) - .phase2TransferCommenceResponseV1(transferCommenceResponse); + log.info(`${fnTag}, sending TransferCommenceResponse...`); - if (response.status != 200) { - throw new Error(`${fnTag}, TransferCommenceResponse message failed`); + if (!remote) { + return transferCommenceResponse; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.sourceBasePath, + ).phase2TransferCommenceResponseV1(transferCommenceResponse), + "TransferCommenceResponse", + ); } export async function checkValidtransferCommenceRequest( @@ -85,26 +86,18 @@ export async function checkValidtransferCommenceRequest( const sessionID = request.sessionID; const sessionData = odap.sessions.get(sessionID); - if ( - sessionData == undefined || - sessionData.step == undefined || - sessionData.lastSequenceNumber == undefined - ) { + if (sessionData == undefined || sessionData.lastSequenceNumber == undefined) { throw new Error( `${fnTag}, session Id does not correspond to any open session`, ); } - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "exec", - operation: "commence", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "commence", + data: JSON.stringify(sessionData), + }); if (request.messageType != OdapMessageType.TransferCommenceRequest) { throw new Error(`${fnTag}, wrong message type for TransferCommenceRequest`); @@ -143,41 +136,23 @@ export async function checkValidtransferCommenceRequest( throw new Error(`${fnTag}, assetProfile hash not match`); } - const sourceClientSignature = new Uint8Array( - Buffer.from(request.clientSignature, "hex"), - ); - - const sourceClientPubkey = new Uint8Array( - Buffer.from(request.clientIdentityPubkey, "hex"), - ); - - const signature = request.clientSignature; - request.clientSignature = ""; - if ( - !odap.verifySignature( - JSON.stringify(request), - sourceClientSignature, - sourceClientPubkey, - ) - ) { + if (!odap.verifySignature(request, request.clientIdentityPubkey)) { throw new Error( `${fnTag}, TransferCommenceRequest message signature verification failed`, ); } - request.clientSignature = signature; storeSessionData(request, odap); - await odap.storeOdapLog( - { - phase: "p2", - step: sessionData.step.toString(), - type: "done", - operation: "commence", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "commence", + data: JSON.stringify(sessionData), + }); + + sessionData.step = 4; + odap.sessions.set(sessionID, sessionData); log.info(`TransferCommenceRequest passed all checks.`); } @@ -197,8 +172,7 @@ async function storeSessionData( JSON.stringify(request), ).toString(); - sessionData.clientSignatureTransferCommenceRequestMessage = - request.clientSignature; + sessionData.clientSignatureTransferCommenceRequestMessage = request.signature; sessionData.originatorPubkey = request.originatorPubkey; sessionData.beneficiaryPubkey = request.beneficiaryPubkey; diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-complete.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-complete.ts index ba4b403c4d..c755ff3900 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-complete.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-complete.ts @@ -26,16 +26,12 @@ export async function checkValidTransferCompleteRequest( ); } - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "exec", - operation: "complete", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "complete", + data: JSON.stringify(sessionData), + }); if (request.messageType != OdapMessageType.TransferCompleteRequest) { throw new Error(`${fnTag}, wrong message type for TransferCompleteRequest`); @@ -53,41 +49,20 @@ export async function checkValidTransferCompleteRequest( throw new Error(`${fnTag}, previous message hash not match`); } - const sourceClientSignature = new Uint8Array( - Buffer.from(request.clientSignature, "hex"), - ); - - const sourceClientPubkey = new Uint8Array( - Buffer.from(request.clientIdentityPubkey, "hex"), - ); - - const signature = request.clientSignature; - request.clientSignature = ""; - if ( - !odap.verifySignature( - JSON.stringify(request), - sourceClientSignature, - sourceClientPubkey, - ) - ) { + if (!odap.verifySignature(request, request.clientIdentityPubkey)) { throw new Error( `${fnTag}, TransferCompleteRequest message signature verification failed`, ); } - request.clientSignature = signature; storeSessionData(request, odap); - await odap.storeOdapLog( - { - phase: "p3", - step: sessionData.step.toString(), - type: "done", - operation: "complete", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "complete", + data: JSON.stringify(sessionData), + }); log.info(`TransferCompleteRequest passed all checks.`); } @@ -107,7 +82,7 @@ async function storeSessionData( JSON.stringify(request), ).toString(); - sessionData.clientSignatureTransferCompleteMessage = request.clientSignature; + sessionData.clientSignatureTransferCompleteMessage = request.signature; odap.sessions.set(request.sessionID, sessionData); } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-initialization.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-initialization.ts index 9806f38fdd..1d8e80a39d 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-initialization.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-initialization.ts @@ -15,13 +15,16 @@ const log = LoggerProvider.getOrCreate({ export async function sendTransferInitializationResponse( sessionID: string, odap: PluginOdapGateway, -): Promise { + remote: boolean, +): Promise { const fnTag = `${odap.className}#sendTransferInitiationResponse()`; const sessionData = odap.sessions.get(sessionID); if ( sessionData == undefined || sessionData.step == undefined || + sessionData.maxTimeout == undefined || + sessionData.maxRetries == undefined || sessionData.sourceBasePath == undefined || sessionData.lastSequenceNumber == undefined || sessionData.initializationRequestMessageHash == undefined || @@ -40,10 +43,10 @@ export async function sendTransferInitializationResponse( sessionData.initializationRequestMessageProcessedTimeStamp, serverIdentityPubkey: odap.pubKey, sequenceNumber: sessionData.lastSequenceNumber, - serverSignature: "", + signature: "", }; - transferInitializationResponse.serverSignature = odap.bufArray2HexStr( + transferInitializationResponse.signature = PluginOdapGateway.bufArray2HexStr( odap.sign(JSON.stringify(transferInitializationResponse)), ); @@ -52,20 +55,14 @@ export async function sendTransferInitializationResponse( ).toString(); sessionData.serverSignatureInitializationResponseMessage = - transferInitializationResponse.serverSignature; - - await odap.storeOdapLog( - { - phase: "p1", - step: sessionData.step.toString(), - type: "ack", - operation: "validate", - nodes: `${odap.pubKey}->${sessionData.sourceGatewayPubkey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + transferInitializationResponse.signature; - sessionData.step++; + await odap.storeOdapLog({ + sessionID: sessionID, + type: "ack", + operation: "validate", + data: JSON.stringify(sessionData), + }); odap.sessions.set(sessionID, sessionData); @@ -73,13 +70,17 @@ export async function sendTransferInitializationResponse( log.info(`${fnTag}, sending TransferInitializationResponse...`); - const response = await odap - .getOdapAPI(sessionData.sourceBasePath) - .phase1TransferInitiationResponseV1(transferInitializationResponse); - - if (response.status != 200) { - throw new Error(`${fnTag}, TransferInitializationResponse message failed`); + if (!remote) { + return transferInitializationResponse; } + + await odap.makeRequest( + sessionID, + PluginOdapGateway.getOdapAPI( + sessionData.sourceBasePath, + ).phase1TransferInitiationResponseV1(transferInitializationResponse), + "TransferInitializationResponse", + ); } export async function checkValidInitializationRequest( @@ -93,21 +94,17 @@ export async function checkValidInitializationRequest( const sessionID = request.sessionID; sessionData.id = sessionID; - sessionData.step = 0; + sessionData.step = 2; sessionData.initializationRequestMessageRcvTimeStamp = recvTimestamp; odap.sessions.set(sessionID, sessionData); - await odap.storeOdapLog( - { - phase: "p1", - step: sessionData.step.toString(), - type: "exec", - operation: "validate", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "exec", + operation: "validate", + data: JSON.stringify(sessionData), + }); if (request.messageType != OdapMessageType.InitializationRequest) { throw new Error( @@ -115,27 +112,11 @@ export async function checkValidInitializationRequest( ); } - const sourceClientSignature = new Uint8Array( - Buffer.from(request.clientSignature, "hex"), - ); - const sourceClientPubkey = new Uint8Array( - Buffer.from(request.sourceGatewayPubkey, "hex"), - ); - - const signature = request.clientSignature; - request.clientSignature = ""; - if ( - !odap.verifySignature( - JSON.stringify(request), - sourceClientSignature, - sourceClientPubkey, - ) - ) { + if (!odap.verifySignature(request, request.sourceGatewayPubkey)) { throw new Error( `${fnTag}, TransferInitializationRequest message signature verification failed`, ); } - request.clientSignature = signature; if (!odap.supportedDltIDs.includes(request.sourceGatewayDltSystem)) { throw new Error( @@ -151,16 +132,12 @@ export async function checkValidInitializationRequest( storeSessionData(request, odap); - await odap.storeOdapLog( - { - phase: "p1", - step: sessionData.step.toString(), - type: "done", - operation: "validate", - nodes: `${odap.pubKey}`, - }, - `${sessionData.id}-${sessionData.step.toString()}`, - ); + await odap.storeOdapLog({ + sessionID: sessionID, + type: "done", + operation: "validate", + data: JSON.stringify(sessionData), + }); log.info(`TransferInitializationRequest passed all checks.`); } @@ -178,24 +155,30 @@ async function storeSessionData( ); } - sessionData.step = 1; + sessionData.version = request.version; + sessionData.maxRetries = request.maxRetries; + sessionData.maxTimeout = request.maxTimeout; sessionData.sourceBasePath = request.sourceGatewayPath; + sessionData.recipientBasePath = request.recipientBasePath; sessionData.lastSequenceNumber = request.sequenceNumber; sessionData.loggingProfile = request.loggingProfile; sessionData.accessControlProfile = request.accessControlProfile; + sessionData.payloadProfile = request.payloadProfile; sessionData.applicationProfile = request.applicationProfile; sessionData.assetProfile = request.payloadProfile.assetProfile; sessionData.sourceGatewayPubkey = request.sourceGatewayPubkey; sessionData.sourceGatewayDltSystem = request.sourceGatewayDltSystem; sessionData.recipientGatewayPubkey = request.recipientGatewayPubkey; sessionData.recipientGatewayDltSystem = request.recipientGatewayDltSystem; + sessionData.rollbackActionsPerformed = []; + sessionData.rollbackProofs = []; + sessionData.lastMessageReceivedTimestamp = new Date().toString(); sessionData.initializationRequestMessageHash = SHA256( JSON.stringify(request), ).toString(); - sessionData.clientSignatureInitializationRequestMessage = - request.clientSignature; + sessionData.clientSignatureInitializationRequestMessage = request.signature; sessionData.initializationRequestMessageProcessedTimeStamp = Date.now().toString(); diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.gitignore b/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.gitignore new file mode 100644 index 0000000000..205d8013f4 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist \ No newline at end of file diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore b/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore index 6ff76cf80a..ecd97ff37f 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore @@ -22,6 +22,6 @@ # Then explicitly reverse the ignore rule for a single file: #!docs/README.md +git_push.sh .npmignore -.gitignore -git_push.sh \ No newline at end of file +.gitignore \ No newline at end of file diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/api.ts index cc8d64dcb6..21db20e909 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -214,6 +214,18 @@ export interface ClientV1Request { * @memberof ClientV1Request */ serverGatewayConfiguration: ClientV1RequestClientGatewayConfiguration; + /** + * + * @type {number} + * @memberof ClientV1Request + */ + maxRetries: number; + /** + * + * @type {number} + * @memberof ClientV1Request + */ + maxTimeout: number; } /** * @@ -287,7 +299,7 @@ export interface CommitFinalV1Request { * @type {string} * @memberof CommitFinalV1Request */ - clientSignature: string; + signature: string; /** * * @type {number} @@ -354,7 +366,7 @@ export interface CommitFinalV1Response { * @type {string} * @memberof CommitFinalV1Response */ - serverSignature: string; + signature: string; /** * * @type {number} @@ -409,7 +421,7 @@ export interface CommitPreparationV1Request { * @type {string} * @memberof CommitPreparationV1Request */ - clientSignature: string; + signature: string; /** * * @type {number} @@ -464,7 +476,7 @@ export interface CommitPreparationV1Response { * @type {string} * @memberof CommitPreparationV1Response */ - serverSignature: string; + signature: string; /** * * @type {number} @@ -592,7 +604,7 @@ export interface LockEvidenceV1Request { * @type {string} * @memberof LockEvidenceV1Request */ - clientSignature: string; + signature: string; /** * * @type {string} @@ -653,7 +665,7 @@ export interface LockEvidenceV1Response { * @type {string} * @memberof LockEvidenceV1Response */ - serverSignature: string; + signature: string; /** * * @type {string} @@ -667,6 +679,49 @@ export interface LockEvidenceV1Response { */ sequenceNumber: number; } +/** + * + * @export + * @interface OdapLocalLog + */ +export interface OdapLocalLog { + /** + * + * @type {string} + * @memberof OdapLocalLog + */ + key?: string; + /** + * + * @type {string} + * @memberof OdapLocalLog + */ + sessionID: string; + /** + * + * @type {string} + * @memberof OdapLocalLog + */ + data?: string; + /** + * + * @type {string} + * @memberof OdapLocalLog + */ + type: string; + /** + * + * @type {string} + * @memberof OdapLocalLog + */ + operation: string; + /** + * + * @type {string} + * @memberof OdapLocalLog + */ + timestamp?: string; +} /** * * @export @@ -814,6 +869,186 @@ export interface PayloadProfile { */ capabilities?: string; } +/** + * + * @export + * @interface RecoverSuccessV1Message + */ +export interface RecoverSuccessV1Message { + /** + * + * @type {string} + * @memberof RecoverSuccessV1Message + */ + sessionID: string; + /** + * + * @type {boolean} + * @memberof RecoverSuccessV1Message + */ + success: boolean; + /** + * + * @type {string} + * @memberof RecoverSuccessV1Message + */ + signature: string; +} +/** + * + * @export + * @interface RecoverUpdateAckV1Message + */ +export interface RecoverUpdateAckV1Message { + /** + * + * @type {string} + * @memberof RecoverUpdateAckV1Message + */ + sessionID: string; + /** + * + * @type {boolean} + * @memberof RecoverUpdateAckV1Message + */ + success: boolean; + /** + * + * @type {Array} + * @memberof RecoverUpdateAckV1Message + */ + changedEntriesHash: Array; + /** + * + * @type {string} + * @memberof RecoverUpdateAckV1Message + */ + signature: string; +} +/** + * + * @export + * @interface RecoverUpdateV1Message + */ +export interface RecoverUpdateV1Message { + /** + * + * @type {string} + * @memberof RecoverUpdateV1Message + */ + sessionID: string; + /** + * + * @type {Array} + * @memberof RecoverUpdateV1Message + */ + recoveredLogs: Array; + /** + * + * @type {string} + * @memberof RecoverUpdateV1Message + */ + signature: string; +} +/** + * + * @export + * @interface RecoverV1Message + */ +export interface RecoverV1Message { + /** + * + * @type {string} + * @memberof RecoverV1Message + */ + sessionID: string; + /** + * + * @type {string} + * @memberof RecoverV1Message + */ + odapPhase: string; + /** + * + * @type {number} + * @memberof RecoverV1Message + */ + sequenceNumber: number; + /** + * + * @type {string} + * @memberof RecoverV1Message + */ + lastLogEntryTimestamp?: string; + /** + * + * @type {string} + * @memberof RecoverV1Message + */ + signature: string; +} +/** + * + * @export + * @interface RollbackAckV1Message + */ +export interface RollbackAckV1Message { + /** + * + * @type {string} + * @memberof RollbackAckV1Message + */ + sessionID: string; + /** + * + * @type {boolean} + * @memberof RollbackAckV1Message + */ + success: boolean; + /** + * + * @type {string} + * @memberof RollbackAckV1Message + */ + signature: string; +} +/** + * + * @export + * @interface RollbackV1Message + */ +export interface RollbackV1Message { + /** + * + * @type {string} + * @memberof RollbackV1Message + */ + sessionID: string; + /** + * + * @type {boolean} + * @memberof RollbackV1Message + */ + success: boolean; + /** + * + * @type {Array} + * @memberof RollbackV1Message + */ + actionPerformed: Array; + /** + * + * @type {Array} + * @memberof RollbackV1Message + */ + proofs: Array; + /** + * + * @type {string} + * @memberof RollbackV1Message + */ + signature: string; +} /** * * @export @@ -1096,6 +1331,12 @@ export interface SessionData { * @memberof SessionData */ clientSignatureTransferCompleteMessage?: string; + /** + * + * @type {number} + * @memberof SessionData + */ + maxRetries?: number; /** * * @type {string} @@ -1110,47 +1351,83 @@ export interface SessionData { fabricAssetID?: string; /** * - * @type {boolean} + * @type {string} * @memberof SessionData */ - isFabricAssetDeleted?: boolean; + fabricAssetSize?: string; /** * - * @type {boolean} + * @type {number} * @memberof SessionData */ - isFabricAssetLocked?: boolean; + maxTimeout?: number; /** * - * @type {boolean} + * @type {string} * @memberof SessionData */ - isFabricAssetCreated?: boolean; + lastLogEntryTimestamp?: string; /** * - * @type {boolean} + * @type {string} * @memberof SessionData */ - isBesuAssetCreated?: boolean; + unlockAssetClaim?: string; /** * - * @type {boolean} + * @type {string} + * @memberof SessionData + */ + recreateAssetClaim?: string; + /** + * + * @type {string} + * @memberof SessionData + */ + deleteAssetClaim?: string; + /** + * + * @type {string} * @memberof SessionData */ - isBesuAssetDeleted?: boolean; + lastMessageReceivedTimestamp?: string; /** * * @type {boolean} * @memberof SessionData */ - isBesuAssetLocked?: boolean; + rollback?: boolean; /** * * @type {string} * @memberof SessionData */ - fabricAssetSize?: string; + rollbackMessageHash?: string; + /** + * + * @type {Array} + * @memberof SessionData + */ + rollbackProofs?: Array; + /** + * + * @type {Array} + * @memberof SessionData + */ + rollbackActionsPerformed?: Array; } + +/** + * @export + * @enum {string} + */ +export enum SessionDataRollbackActionsPerformedEnum { + Create = 'CREATE', + Delete = 'DELETE', + Lock = 'LOCK', + Unlock = 'UNLOCK' +} + /** * * @export @@ -1234,7 +1511,7 @@ export interface TransferCommenceV1Request { * @type {string} * @memberof TransferCommenceV1Request */ - clientSignature: string; + signature: string; /** * * @type {number} @@ -1283,7 +1560,7 @@ export interface TransferCommenceV1Response { * @type {string} * @memberof TransferCommenceV1Response */ - serverSignature: string; + signature: string; /** * * @type {string} @@ -1350,7 +1627,7 @@ export interface TransferCompleteV1Request { * @type {string} * @memberof TransferCompleteV1Request */ - clientSignature: string; + signature: string; /** * * @type {string} @@ -1429,7 +1706,7 @@ export interface TransferInitializationV1Request { * @type {string} * @memberof TransferInitializationV1Request */ - clientSignature: string; + signature: string; /** * * @type {string} @@ -1520,6 +1797,24 @@ export interface TransferInitializationV1Request { * @memberof TransferInitializationV1Request */ sourceGatewayPath?: string; + /** + * + * @type {string} + * @memberof TransferInitializationV1Request + */ + recipientBasePath: string; + /** + * + * @type {number} + * @memberof TransferInitializationV1Request + */ + maxRetries: number; + /** + * + * @type {number} + * @memberof TransferInitializationV1Request + */ + maxTimeout: number; } /** @@ -1601,7 +1896,7 @@ export interface TransferInitializationV1Response { * @type {string} * @memberof TransferInitializationV1Response */ - serverSignature: string; + signature: string; } /** @@ -1628,7 +1923,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ clientRequestV1: async (clientV1Request?: ClientV1Request, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/clientrequest`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/clientrequest`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1661,7 +1956,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase1TransferInitiationRequestV1: async (transferInitializationV1Request?: TransferInitializationV1Request, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationrequest`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationrequest`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1694,7 +1989,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase1TransferInitiationResponseV1: async (transferInitializationV1Response?: TransferInitializationV1Response, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationresponse`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationresponse`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1727,7 +2022,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase2LockEvidenceRequestV1: async (lockEvidenceV1Request?: LockEvidenceV1Request, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidencerequest`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidencerequest`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1760,7 +2055,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase2LockEvidenceResponseV1: async (lockEvidenceV1Response?: LockEvidenceV1Response, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidenceresponse`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidenceresponse`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1793,7 +2088,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase2TransferCommenceRequestV1: async (transferCommenceV1Request?: TransferCommenceV1Request, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommencerequest`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommencerequest`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1826,7 +2121,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase2TransferCommenceResponseV1: async (transferCommenceV1Response?: TransferCommenceV1Response, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommenceresponse`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommenceresponse`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1859,7 +2154,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase3CommitFinalRequestV1: async (commitFinalV1Request?: CommitFinalV1Request, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalrequest`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalrequest`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1892,7 +2187,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase3CommitFinalResponseV1: async (commitFinalV1Response?: CommitFinalV1Response, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalresponse`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalresponse`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1925,7 +2220,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase3CommitPreparationRequestV1: async (commitPreparationV1Request?: CommitPreparationV1Request, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationrequest`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationrequest`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1958,7 +2253,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase3CommitPreparationResponseV1: async (commitPreparationV1Response?: CommitPreparationV1Response, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationresponse`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationresponse`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -1991,7 +2286,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati * @throws {RequiredError} */ phase3TransferCompleteRequestV1: async (transferCompleteV1Request?: TransferCompleteV1Request, options: any = {}): Promise => { - const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/transfercompleterequest`; + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/transfercompleterequest`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -2017,45 +2312,243 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, - } -}; - -/** - * DefaultApi - functional programming interface - * @export - */ -export const DefaultApiFp = function(configuration?: Configuration) { - const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) - return { - /** - * - * @param {ClientV1Request} [clientV1Request] - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async clientRequestV1(clientV1Request?: ClientV1Request, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.clientRequestV1(clientV1Request, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * - * @param {TransferInitializationV1Request} [transferInitializationV1Request] - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async phase1TransferInitiationRequestV1(transferInitializationV1Request?: TransferInitializationV1Request, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.phase1TransferInitiationRequestV1(transferInitializationV1Request, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * - * @param {TransferInitializationV1Response} [transferInitializationV1Response] + * @param {RecoverUpdateAckV1Message} [recoverUpdateAckV1Message] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async phase1TransferInitiationResponseV1(transferInitializationV1Response?: TransferInitializationV1Response, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.phase1TransferInitiationResponseV1(transferInitializationV1Response, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + recoverUpdateAckV1Message: async (recoverUpdateAckV1Message?: RecoverUpdateAckV1Message, options: any = {}): Promise => { + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdateackmessage`; + // 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, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(recoverUpdateAckV1Message, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {RecoverUpdateV1Message} [recoverUpdateV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recoverUpdateV1Message: async (recoverUpdateV1Message?: RecoverUpdateV1Message, options: any = {}): Promise => { + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdatemessage`; + // 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, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(recoverUpdateV1Message, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {RecoverV1Message} [recoverV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recoverV1Message: async (recoverV1Message?: RecoverV1Message, options: any = {}): Promise => { + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/recovermessage`; + // 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, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(recoverV1Message, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {RecoverSuccessV1Message} [recoverSuccessV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recoverV1Success: async (recoverSuccessV1Message?: RecoverSuccessV1Message, options: any = {}): Promise => { + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoversuccessmessage`; + // 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, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(recoverSuccessV1Message, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {RollbackAckV1Message} [rollbackAckV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rollbackAckV1Message: async (rollbackAckV1Message?: RollbackAckV1Message, options: any = {}): Promise => { + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackackmessage`; + // 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, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(rollbackAckV1Message, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {RollbackV1Message} [rollbackV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rollbackV1Message: async (rollbackV1Message?: RollbackV1Message, options: any = {}): Promise => { + const localVarPath = `/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackmessage`; + // 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, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(rollbackV1Message, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @param {ClientV1Request} [clientV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async clientRequestV1(clientV1Request?: ClientV1Request, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.clientRequestV1(clientV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {TransferInitializationV1Request} [transferInitializationV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async phase1TransferInitiationRequestV1(transferInitializationV1Request?: TransferInitializationV1Request, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.phase1TransferInitiationRequestV1(transferInitializationV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {TransferInitializationV1Response} [transferInitializationV1Response] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async phase1TransferInitiationResponseV1(transferInitializationV1Response?: TransferInitializationV1Response, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.phase1TransferInitiationResponseV1(transferInitializationV1Response, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** * @@ -2147,6 +2640,66 @@ export const DefaultApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.phase3TransferCompleteRequestV1(transferCompleteV1Request, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {RecoverUpdateAckV1Message} [recoverUpdateAckV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async recoverUpdateAckV1Message(recoverUpdateAckV1Message?: RecoverUpdateAckV1Message, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.recoverUpdateAckV1Message(recoverUpdateAckV1Message, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {RecoverUpdateV1Message} [recoverUpdateV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async recoverUpdateV1Message(recoverUpdateV1Message?: RecoverUpdateV1Message, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.recoverUpdateV1Message(recoverUpdateV1Message, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {RecoverV1Message} [recoverV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async recoverV1Message(recoverV1Message?: RecoverV1Message, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.recoverV1Message(recoverV1Message, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {RecoverSuccessV1Message} [recoverSuccessV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async recoverV1Success(recoverSuccessV1Message?: RecoverSuccessV1Message, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.recoverV1Success(recoverSuccessV1Message, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {RollbackAckV1Message} [rollbackAckV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async rollbackAckV1Message(rollbackAckV1Message?: RollbackAckV1Message, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.rollbackAckV1Message(rollbackAckV1Message, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {RollbackV1Message} [rollbackV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async rollbackV1Message(rollbackV1Message?: RollbackV1Message, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.rollbackV1Message(rollbackV1Message, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -2265,6 +2818,60 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa phase3TransferCompleteRequestV1(transferCompleteV1Request?: TransferCompleteV1Request, options?: any): AxiosPromise { return localVarFp.phase3TransferCompleteRequestV1(transferCompleteV1Request, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {RecoverUpdateAckV1Message} [recoverUpdateAckV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recoverUpdateAckV1Message(recoverUpdateAckV1Message?: RecoverUpdateAckV1Message, options?: any): AxiosPromise { + return localVarFp.recoverUpdateAckV1Message(recoverUpdateAckV1Message, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {RecoverUpdateV1Message} [recoverUpdateV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recoverUpdateV1Message(recoverUpdateV1Message?: RecoverUpdateV1Message, options?: any): AxiosPromise { + return localVarFp.recoverUpdateV1Message(recoverUpdateV1Message, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {RecoverV1Message} [recoverV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recoverV1Message(recoverV1Message?: RecoverV1Message, options?: any): AxiosPromise { + return localVarFp.recoverV1Message(recoverV1Message, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {RecoverSuccessV1Message} [recoverSuccessV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recoverV1Success(recoverSuccessV1Message?: RecoverSuccessV1Message, options?: any): AxiosPromise { + return localVarFp.recoverV1Success(recoverSuccessV1Message, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {RollbackAckV1Message} [rollbackAckV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rollbackAckV1Message(rollbackAckV1Message?: RollbackAckV1Message, options?: any): AxiosPromise { + return localVarFp.rollbackAckV1Message(rollbackAckV1Message, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {RollbackV1Message} [rollbackV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rollbackV1Message(rollbackV1Message?: RollbackV1Message, options?: any): AxiosPromise { + return localVarFp.rollbackV1Message(rollbackV1Message, options).then((request) => request(axios, basePath)); + }, }; }; @@ -2406,6 +3013,72 @@ export class DefaultApi extends BaseAPI { public phase3TransferCompleteRequestV1(transferCompleteV1Request?: TransferCompleteV1Request, options?: any) { return DefaultApiFp(this.configuration).phase3TransferCompleteRequestV1(transferCompleteV1Request, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {RecoverUpdateAckV1Message} [recoverUpdateAckV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public recoverUpdateAckV1Message(recoverUpdateAckV1Message?: RecoverUpdateAckV1Message, options?: any) { + return DefaultApiFp(this.configuration).recoverUpdateAckV1Message(recoverUpdateAckV1Message, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {RecoverUpdateV1Message} [recoverUpdateV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public recoverUpdateV1Message(recoverUpdateV1Message?: RecoverUpdateV1Message, options?: any) { + return DefaultApiFp(this.configuration).recoverUpdateV1Message(recoverUpdateV1Message, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {RecoverV1Message} [recoverV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public recoverV1Message(recoverV1Message?: RecoverV1Message, options?: any) { + return DefaultApiFp(this.configuration).recoverV1Message(recoverV1Message, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {RecoverSuccessV1Message} [recoverSuccessV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public recoverV1Success(recoverSuccessV1Message?: RecoverSuccessV1Message, options?: any) { + return DefaultApiFp(this.configuration).recoverV1Success(recoverSuccessV1Message, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {RollbackAckV1Message} [rollbackAckV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public rollbackAckV1Message(rollbackAckV1Message?: RollbackAckV1Message, options?: any) { + return DefaultApiFp(this.configuration).rollbackAckV1Message(rollbackAckV1Message, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {RollbackV1Message} [rollbackV1Message] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public rollbackV1Message(rollbackV1Message?: RollbackV1Message, options?: any) { + return DefaultApiFp(this.configuration).rollbackV1Message(rollbackV1Message, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/client-request-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/client-request-endpoint.ts index aad76588fa..5418c15a3e 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/client-request-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/client-request-endpoint.ts @@ -45,19 +45,19 @@ export class ClientRequestEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = - OAS.paths["/api/v1/@hyperledger/cactus-plugin-odap-hemres/clientrequest"]; + OAS.paths["/api/v1/@hyperledger/cactus-plugin-odap-hermes/clientrequest"]; return apiPath.post["x-hyperledger-cactus"].http.path; } public getVerbLowerCase(): string { const apiPath = - OAS.paths["/api/v1/@hyperledger/cactus-plugin-odap-hemres/clientrequest"]; + OAS.paths["/api/v1/@hyperledger/cactus-plugin-odap-hermes/clientrequest"]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/clientrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/clientrequest" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commit-final-response-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commit-final-response-endpoint.ts index 0df0da6223..a7d67a38d8 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commit-final-response-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commit-final-response-endpoint.ts @@ -46,7 +46,7 @@ export class CommitFinalResponseEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -54,14 +54,14 @@ export class CommitFinalResponseEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalresponse" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commite-prepare-response-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commite-prepare-response-endpoint.ts index 74764c0fbd..8af14a038f 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commite-prepare-response-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/commite-prepare-response-endpoint.ts @@ -49,7 +49,7 @@ export class CommitPreparationResponseEndpointV1 public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -57,14 +57,14 @@ export class CommitPreparationResponseEndpointV1 public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationresponse" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/lock-evidence-response-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/lock-evidence-response-endpoint.ts index dbec7bd546..37d0627768 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/lock-evidence-response-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/lock-evidence-response-endpoint.ts @@ -49,7 +49,7 @@ export class LockEvidenceResponseEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidenceresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidenceresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -57,14 +57,14 @@ export class LockEvidenceResponseEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidenceresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidenceresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidenceresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidenceresponse" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-commence-response-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-commence-response-endpoint.ts index 97178a1886..52c394a452 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-commence-response-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-commence-response-endpoint.ts @@ -47,7 +47,7 @@ export class TransferCommenceResponseEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommenceresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommenceresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -55,14 +55,14 @@ export class TransferCommenceResponseEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommenceresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommenceresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommenceresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommenceresponse" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-initiation-response-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-initiation-response-endpoint.ts index a4a3bb8184..e714789717 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-initiation-response-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/client-side/transfer-initiation-response-endpoint.ts @@ -48,7 +48,7 @@ export class TransferInitiationResponseEndpointV1 public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -56,14 +56,14 @@ export class TransferInitiationResponseEndpointV1 public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationresponse" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationresponse" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationresponse" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-message-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-message-endpoint.ts new file mode 100644 index 0000000000..98f6606df6 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-message-endpoint.ts @@ -0,0 +1,103 @@ +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 { PluginOdapGateway } from "../../gateway/plugin-odap-gateway"; + +import OAS from "../../../json/openapi.json"; + +export interface IRecoverMessageEndpointOptions { + logLevel?: LogLevelDesc; + gateway: PluginOdapGateway; +} + +export class RecoverMessageEndpointV1 implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RecoverMessageEndpointV1"; + + private readonly log: Logger; + + public get className(): string { + return RecoverMessageEndpointV1.CLASS_NAME; + } + + constructor(public readonly options: IRecoverMessageEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.gateway, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getPath(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recovermessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recovermessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recovermessage" + ].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 { + await this.options.gateway.onRecoverMessageReceived(req.body); + res.status(200).json("OK"); + } 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-odap-hermes/src/main/typescript/web-services/recovery/recover-success-message-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-success-message-endpoint.ts new file mode 100644 index 0000000000..aab86eb29b --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-success-message-endpoint.ts @@ -0,0 +1,103 @@ +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 { PluginOdapGateway } from "../../gateway/plugin-odap-gateway"; + +import OAS from "../../../json/openapi.json"; + +export interface IRecoverSuccessMessageEndpointOptions { + logLevel?: LogLevelDesc; + gateway: PluginOdapGateway; +} + +export class RecoverSuccessMessageEndpointV1 implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RecoverSuccessMessageEndpointV1"; + + private readonly log: Logger; + + public get className(): string { + return RecoverSuccessMessageEndpointV1.CLASS_NAME; + } + + constructor(public readonly options: IRecoverSuccessMessageEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.gateway, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getPath(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoversuccessmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoversuccessmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoversuccessmessage" + ].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 { + await this.options.gateway.onRecoverSuccessMessageReceived(req.body); + res.status(200).json("OK"); + } 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-odap-hermes/src/main/typescript/web-services/recovery/recover-update-ack-message-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-update-ack-message-endpoint.ts new file mode 100644 index 0000000000..e362c0643b --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-update-ack-message-endpoint.ts @@ -0,0 +1,105 @@ +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 { PluginOdapGateway } from "../../gateway/plugin-odap-gateway"; + +import OAS from "../../../json/openapi.json"; + +export interface IRecoverUpdateAckMessageEndpointOptions { + logLevel?: LogLevelDesc; + gateway: PluginOdapGateway; +} + +export class RecoverUpdateAckMessageEndpointV1 implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RecoverUpdateAckMessageEndpointV1"; + + private readonly log: Logger; + + public get className(): string { + return RecoverUpdateAckMessageEndpointV1.CLASS_NAME; + } + + constructor( + public readonly options: IRecoverUpdateAckMessageEndpointOptions, + ) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.gateway, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getPath(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdateackmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdateackmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdateackmessage" + ].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 { + await this.options.gateway.onRecoverUpdateAckMessageReceived(req.body); + res.status(200).json("OK"); + } 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-odap-hermes/src/main/typescript/web-services/recovery/recover-update-message-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-update-message-endpoint.ts new file mode 100644 index 0000000000..860242f8fe --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/recover-update-message-endpoint.ts @@ -0,0 +1,103 @@ +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 { PluginOdapGateway } from "../../gateway/plugin-odap-gateway"; + +import OAS from "../../../json/openapi.json"; + +export interface IRecoverUpdateMessageEndpointOptions { + logLevel?: LogLevelDesc; + gateway: PluginOdapGateway; +} + +export class RecoverUpdateMessageEndpointV1 implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RecoverUpdateMessageEndpointV1"; + + private readonly log: Logger; + + public get className(): string { + return RecoverUpdateMessageEndpointV1.CLASS_NAME; + } + + constructor(public readonly options: IRecoverUpdateMessageEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.gateway, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getPath(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdatemessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdatemessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/recoverupdatemessage" + ].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 { + await this.options.gateway.onRecoverUpdateMessageReceived(req.body); + res.status(200).json("OK"); + } 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-odap-hermes/src/main/typescript/web-services/recovery/rollback-ack-message-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/rollback-ack-message-endpoint.ts new file mode 100644 index 0000000000..97d06707f8 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/rollback-ack-message-endpoint.ts @@ -0,0 +1,103 @@ +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 { PluginOdapGateway } from "../../gateway/plugin-odap-gateway"; + +import OAS from "../../../json/openapi.json"; + +export interface IRollbackAckMessageEndpointOptions { + logLevel?: LogLevelDesc; + gateway: PluginOdapGateway; +} + +export class RollbackAckMessageEndpointV1 implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RollbackAckMessageEndpointV1"; + + private readonly log: Logger; + + public get className(): string { + return RollbackAckMessageEndpointV1.CLASS_NAME; + } + + constructor(public readonly options: IRollbackAckMessageEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.gateway, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getPath(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackackmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackackmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackackmessage" + ].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 { + await this.options.gateway.onRollbackAckMessageReceived(req.body); + res.status(200).json("OK"); + } 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-odap-hermes/src/main/typescript/web-services/recovery/rollback-message-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/rollback-message-endpoint.ts new file mode 100644 index 0000000000..947655697f --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/recovery/rollback-message-endpoint.ts @@ -0,0 +1,103 @@ +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 { PluginOdapGateway } from "../../gateway/plugin-odap-gateway"; + +import OAS from "../../../json/openapi.json"; + +export interface IRollbackMessageEndpointOptions { + logLevel?: LogLevelDesc; + gateway: PluginOdapGateway; +} + +export class RollbackMessageEndpointV1 implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RollbackMessageEndpointV1"; + + private readonly log: Logger; + + public get className(): string { + return RollbackMessageEndpointV1.CLASS_NAME; + } + + constructor(public readonly options: IRollbackMessageEndpointOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.gateway, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getPath(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = + OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackmessage" + ]; + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return OAS.paths[ + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/rollbackmessage" + ].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 { + await this.options.gateway.onRollbackMessageReceived(req.body); + res.status(200).json("OK"); + } 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-odap-hermes/src/main/typescript/web-services/server-side/commit-final-request-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/commit-final-request-endpoint.ts index fbaa92de76..e3dda3a167 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/commit-final-request-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/commit-final-request-endpoint.ts @@ -45,7 +45,7 @@ export class CommitFinalRequestEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalrequest" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -53,14 +53,14 @@ export class CommitFinalRequestEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalrequest" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitfinalrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitfinalrequest" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/commite-prepare-request-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/commite-prepare-request-endpoint.ts index 5c974b6fc3..945862ed7a 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/commite-prepare-request-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/commite-prepare-request-endpoint.ts @@ -47,7 +47,7 @@ export class CommitPreparationRequestEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationrequest" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -55,14 +55,14 @@ export class CommitPreparationRequestEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationrequest" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/commitpreparationrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/commitpreparationrequest" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/lock-evidence-request-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/lock-evidence-request-endpoint.ts index 9072abd93c..eec286eaca 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/lock-evidence-request-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/lock-evidence-request-endpoint.ts @@ -49,7 +49,7 @@ export class LockEvidenceRequestEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidencerequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidencerequest" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -57,14 +57,14 @@ export class LockEvidenceRequestEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidencerequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidencerequest" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/lockevidencerequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/lockevidencerequest" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-commence-request-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-commence-request-endpoint.ts index 0da58301f2..5040c9ff3f 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-commence-request-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-commence-request-endpoint.ts @@ -24,7 +24,7 @@ export interface ITransferCommenceRequestEndpointOptions { } export class TransferCommenceRequestEndpointV1 implements IWebServiceEndpoint { - public static readonly CLASS_NAME = "LockEvidencePrepareEndpointV1"; + public static readonly CLASS_NAME = "TransferCommenceRequestEndpointV1"; private readonly log: Logger; @@ -47,7 +47,7 @@ export class TransferCommenceRequestEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommencerequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommencerequest" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -55,14 +55,14 @@ export class TransferCommenceRequestEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommencerequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommencerequest" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase2/transfercommencerequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase2/transfercommencerequest" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-complete-request-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-complete-request-endpoint.ts index ef8ee78711..1ef0e71883 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-complete-request-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-complete-request-endpoint.ts @@ -47,7 +47,7 @@ export class TransferCompleteRequestEndpointV1 implements IWebServiceEndpoint { public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/transfercompleterequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/transfercompleterequest" ]; return apiPath.get["x-hyperledger-cactus"].http.path; } @@ -55,14 +55,14 @@ export class TransferCompleteRequestEndpointV1 implements IWebServiceEndpoint { public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/transfercompleterequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/transfercompleterequest" ]; return apiPath.get["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase3/transfercompleterequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase3/transfercompleterequest" ].get.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-initiation-request-endpoint.ts b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-initiation-request-endpoint.ts index 435a38014b..2edc3edbbf 100644 --- a/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-initiation-request-endpoint.ts +++ b/packages/cactus-plugin-odap-hermes/src/main/typescript/web-services/server-side/transfer-initiation-request-endpoint.ts @@ -48,7 +48,7 @@ export class TransferInitiationRequestEndpointV1 public getPath(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationrequest" ]; return apiPath.post["x-hyperledger-cactus"].http.path; } @@ -56,14 +56,14 @@ export class TransferInitiationRequestEndpointV1 public getVerbLowerCase(): string { const apiPath = OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationrequest" ]; return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; } public getOperationId(): string { return OAS.paths[ - "/api/v1/@hyperledger/cactus-plugin-odap-hemres/phase1/transferinitiationrequest" + "/api/v1/@hyperledger/cactus-plugin-odap-hermes/phase1/transferinitiationrequest" ].post.operationId; } diff --git a/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/LockAsset.json b/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/LockAsset.json index b89c37ba8a..6831689e47 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/LockAsset.json +++ b/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/LockAsset.json @@ -68,6 +68,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "string", + "name": "id", + "type": "string" + } + ], + "name": "isPresent", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -95,27 +114,27 @@ "type": "function" } ], - "metadata": "{\"compiler\":{\"version\":\"0.8.6+commit.11564f7e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"size\",\"type\":\"uint256\"}],\"name\":\"createAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"deleteAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"getAsset\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"creator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isLock\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"size\",\"type\":\"uint256\"}],\"internalType\":\"struct Asset\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"lockAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"unLockAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol\":\"LockAsset\"},\"evmVersion\":\"berlin\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol\":{\"keccak256\":\"0x878fc27f22785593c1b35005ecf333e1f93e6dcf932b3d6e2b24d6be790b996a\",\"urls\":[\"bzz-raw://d8be1567f5e11fb718d8849f4dc9b8a8e467135ecd04c5f796c938b7363daaf2\",\"dweb:/ipfs/QmQuteuiTygZxjDnLiq3GNgJNFmPSRqbE2Q9vm85WgVbox\"]}},\"version\":1}", - "bytecode": "608060405234801561001057600080fd5b50610475806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80635e82d0a61461005c578063cd5286d014610071578063db9cc410146100b5578063def60e0d146100c8578063e24aa37c146100db575b600080fd5b61006f61006a3660046103a1565b6100ee565b005b61008461007f3660046103a1565b610164565b6040805182516001600160a01b03168152602080840151151590820152918101519082015260600160405180910390f35b61006f6100c33660046103e3565b6101dc565b61006f6100d63660046103a1565b610266565b61006f6100e93660046103a1565b6102ad565b6000806000848460405161010392919061042f565b9081526020016040518091039020600101541190508061012257600080fd5b60016000848460405161013692919061042f565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b60408051606081018252600080825260208201819052918101919091526000838360405161019392919061042f565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b600081116101e957600080fd5b80600084846040516101fc92919061042f565b908152602001604051809103902060010181905550336000848460405161022492919061042f565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b0319909316929092179091556000908190610136908690869061042f565b6000806000848460405161027b92919061042f565b9081526020016040518091039020600101541190508061029a57600080fd5b600080848460405161013692919061042f565b600080600084846040516102c292919061042f565b908152602001604051809103902060010154119050806102e157600080fd5b60008084846040516102f492919061042f565b9081526040519081900360200190205460ff600160a01b9091041690508061031b57600080fd5b6000848460405161032d92919061042f565b90815260405190819003602001902080546001600160a81b0319168155600060019091015550505050565b60008083601f84011261036a57600080fd5b50813567ffffffffffffffff81111561038257600080fd5b60208301915083602082850101111561039a57600080fd5b9250929050565b600080602083850312156103b457600080fd5b823567ffffffffffffffff8111156103cb57600080fd5b6103d785828601610358565b90969095509350505050565b6000806000604084860312156103f857600080fd5b833567ffffffffffffffff81111561040f57600080fd5b61041b86828701610358565b909790965060209590950135949350505050565b818382376000910190815291905056fea2646970667358221220ce6b629424abf9eb8bd7a26806025c9b10e1c4cd51cda11b51b0deec64645f4264736f6c63430008060033", - "deployedBytecode": "608060405234801561001057600080fd5b50600436106100575760003560e01c80635e82d0a61461005c578063cd5286d014610071578063db9cc410146100b5578063def60e0d146100c8578063e24aa37c146100db575b600080fd5b61006f61006a3660046103a1565b6100ee565b005b61008461007f3660046103a1565b610164565b6040805182516001600160a01b03168152602080840151151590820152918101519082015260600160405180910390f35b61006f6100c33660046103e3565b6101dc565b61006f6100d63660046103a1565b610266565b61006f6100e93660046103a1565b6102ad565b6000806000848460405161010392919061042f565b9081526020016040518091039020600101541190508061012257600080fd5b60016000848460405161013692919061042f565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b60408051606081018252600080825260208201819052918101919091526000838360405161019392919061042f565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b600081116101e957600080fd5b80600084846040516101fc92919061042f565b908152602001604051809103902060010181905550336000848460405161022492919061042f565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b0319909316929092179091556000908190610136908690869061042f565b6000806000848460405161027b92919061042f565b9081526020016040518091039020600101541190508061029a57600080fd5b600080848460405161013692919061042f565b600080600084846040516102c292919061042f565b908152602001604051809103902060010154119050806102e157600080fd5b60008084846040516102f492919061042f565b9081526040519081900360200190205460ff600160a01b9091041690508061031b57600080fd5b6000848460405161032d92919061042f565b90815260405190819003602001902080546001600160a81b0319168155600060019091015550505050565b60008083601f84011261036a57600080fd5b50813567ffffffffffffffff81111561038257600080fd5b60208301915083602082850101111561039a57600080fd5b9250929050565b600080602083850312156103b457600080fd5b823567ffffffffffffffff8111156103cb57600080fd5b6103d785828601610358565b90969095509350505050565b6000806000604084860312156103f857600080fd5b833567ffffffffffffffff81111561040f57600080fd5b61041b86828701610358565b909790965060209590950135949350505050565b818382376000910190815291905056fea2646970667358221220ce6b629424abf9eb8bd7a26806025c9b10e1c4cd51cda11b51b0deec64645f4264736f6c63430008060033", - "sourceMap": "543:1053:0:-:0;;;;;;;;;;;;;;;;;;;", - "deployedSourceMap": "543:1053:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;946:154;;;;;;:::i;:::-;;:::i;:::-;;798:105;;;;;;:::i;:::-;;:::i;:::-;;;;1753:13:1;;-1:-1:-1;;;;;1749:39:1;1731:58;;1859:4;1847:17;;;1841:24;1834:32;1827:40;1805:20;;;1798:70;1912:17;;;1906:24;1884:20;;;1877:54;1719:2;1704:18;798:105:0;;;;;;;607:188;;;;;;:::i;:::-;;:::i;1144:157::-;;;;;;:::i;:::-;;:::i;1304:289::-;;;;;;:::i;:::-;;:::i;946:154::-;999:16;1034:1;1018:6;1025:2;;1018:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;;:17;999:36;;1051:11;1043:20;;;;;;1091:4;1071:6;1078:2;;1071:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:24;;;;;-1:-1:-1;;;1071:24:0;-1:-1:-1;;;;1071:24:0;;;;;;;;;-1:-1:-1;;;946:154:0:o;798:105::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;888:6:0;895:2;;888:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;881:17;;;;;;;-1:-1:-1;;;;;881:17:0;;;;-1:-1:-1;;;881:17:0;;;;;;;;;;;;;;;;;;;;;;888:10;798:105;-1:-1:-1;;;798:105:0:o;607:188::-;687:1;682:4;:6;674:15;;;;;;714:4;697:6;704:2;;697:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;:21;;;;747:10;726:6;733:2;;726:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:31;;-1:-1:-1;;;;;726:31:0;;;;-1:-1:-1;;;;;;726:31:0;;;;;;;;;;:18;;;;765:10;;772:2;;;;765:10;:::i;1144:157::-;1199:16;1234:1;1218:6;1225:2;;1218:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;;:17;1199:36;;1251:11;1243:20;;;;;;1291:5;1271:6;1278:2;;1271:10;;;;;;;:::i;1304:289::-;1360:16;1395:1;1379:6;1386:2;;1379:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;;:17;1360:36;;1412:11;1404:20;;;;;;1495:18;1516:6;1523:2;;1516:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:17;;-1:-1:-1;;;1516:17:0;;;;;-1:-1:-1;1516:17:0;1541:22;;;;;;1578:6;1585:2;;1578:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;1571:17;;-1:-1:-1;;;;;;1571:17:0;;;1578:10;1571:17;;;;;-1:-1:-1;;;;1304:289:0:o;14:348:1:-;66:8;76:6;130:3;123:4;115:6;111:17;107:27;97:2;;148:1;145;138:12;97:2;-1:-1:-1;171:20:1;;214:18;203:30;;200:2;;;246:1;243;236:12;200:2;283:4;275:6;271:17;259:29;;335:3;328:4;319:6;311;307:19;303:30;300:39;297:2;;;352:1;349;342:12;297:2;87:275;;;;;:::o;367:411::-;438:6;446;499:2;487:9;478:7;474:23;470:32;467:2;;;515:1;512;505:12;467:2;555:9;542:23;588:18;580:6;577:30;574:2;;;620:1;617;610:12;574:2;659:59;710:7;701:6;690:9;686:22;659:59;:::i;:::-;737:8;;633:85;;-1:-1:-1;457:321:1;-1:-1:-1;;;;457:321:1:o;783:479::-;863:6;871;879;932:2;920:9;911:7;907:23;903:32;900:2;;;948:1;945;938:12;900:2;988:9;975:23;1021:18;1013:6;1010:30;1007:2;;;1053:1;1050;1043:12;1007:2;1092:59;1143:7;1134:6;1123:9;1119:22;1092:59;:::i;:::-;1170:8;;1066:85;;-1:-1:-1;1252:2:1;1237:18;;;;1224:32;;890:372;-1:-1:-1;;;;890:372:1:o;1267:273::-;1452:6;1444;1439:3;1426:33;1408:3;1478:16;;1503:13;;;1478:16;1416:124;-1:-1:-1;1416:124:1:o", - "sourcePath": "/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol", + "metadata": "{\"compiler\":{\"version\":\"0.8.0+commit.c7dfd78e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"size\",\"type\":\"uint256\"}],\"name\":\"createAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"deleteAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"getAsset\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"creator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isLock\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"size\",\"type\":\"uint256\"}],\"internalType\":\"struct Asset\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"isPresent\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"lockAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"unLockAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/home/andre_9a/cactus/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol\":\"LockAsset\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/andre_9a/cactus/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol\":{\"keccak256\":\"0x83440612a19f87e0d647fb985ae74dbecfb7d68b8e7b1d1fd466aa8f5a6376b8\",\"urls\":[\"bzz-raw://a84724165527e83488559cda0ed146de435f5287386367ad37aad56377aab8b0\",\"dweb:/ipfs/QmY9L36QWkg7it6uyi7WN2NrWQPbwm8MBmkKkPVVcFxJ8s\"]}},\"version\":1}", + "bytecode": "608060405234801561001057600080fd5b5061057e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80635e82d0a614610067578063bc5482751461007c578063cd5286d0146100a5578063db9cc410146100c5578063def60e0d146100d8578063e24aa37c146100eb575b600080fd5b61007a610075366004610477565b6100fe565b005b61008f61008a366004610477565b610173565b60405161009c9190610511565b60405180910390f35b6100b86100b3366004610477565b6101a2565b60405161009c919061051c565b61007a6100d33660046104b7565b610205565b61007a6100e6366004610477565b6102f0565b61007a6100f9366004610477565b610336565b600060018383604051610112929190610501565b9081526040519081900360200190205460ff1690508061013157600080fd5b600160008484604051610145929190610501565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b600060018383604051610187929190610501565b9081526040519081900360200190205460ff16905092915050565b6101aa610410565b600083836040516101bc929190610501565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b6000811161021257600080fd5b8060008484604051610225929190610501565b908152602001604051809103902060010181905550336000848460405161024d929190610501565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b031990931692909217909155600090819061028f9086908690610501565b9081526040519081900360200181208054921515600160a01b0260ff60a01b199093169290921790915560019081906102cb9086908690610501565b908152604051908190036020019020805491151560ff19909216919091179055505050565b600060018383604051610304929190610501565b9081526040519081900360200190205460ff1690508061032357600080fd5b6000808484604051610145929190610501565b60006001838360405161034a929190610501565b9081526040519081900360200190205460ff1690508061036957600080fd5b600080848460405161037c929190610501565b9081526040519081900360200190205460ff600160a01b909104169050806103a357600080fd5b600084846040516103b5929190610501565b90815260405190819003602001812080546001600160a81b031916815560006001918201819055916103ea9087908790610501565b908152604051908190036020019020805491151560ff1990921691909117905550505050565b604080516060810182526000808252602082018190529181019190915290565b60008083601f840112610441578182fd5b50813567ffffffffffffffff811115610458578182fd5b60208301915083602082850101111561047057600080fd5b9250929050565b60008060208385031215610489578182fd5b823567ffffffffffffffff81111561049f578283fd5b6104ab85828601610430565b90969095509350505050565b6000806000604084860312156104cb578081fd5b833567ffffffffffffffff8111156104e1578182fd5b6104ed86828701610430565b909790965060209590950135949350505050565b6000828483379101908152919050565b901515815260200190565b81516001600160a01b03168152602080830151151590820152604091820151918101919091526060019056fea2646970667358221220b5f775906e8b1a0886fce5dcca04561f86c492a095697545bb2365d7364dd33364736f6c63430008000033", + "deployedBytecode": "608060405234801561001057600080fd5b50600436106100625760003560e01c80635e82d0a614610067578063bc5482751461007c578063cd5286d0146100a5578063db9cc410146100c5578063def60e0d146100d8578063e24aa37c146100eb575b600080fd5b61007a610075366004610477565b6100fe565b005b61008f61008a366004610477565b610173565b60405161009c9190610511565b60405180910390f35b6100b86100b3366004610477565b6101a2565b60405161009c919061051c565b61007a6100d33660046104b7565b610205565b61007a6100e6366004610477565b6102f0565b61007a6100f9366004610477565b610336565b600060018383604051610112929190610501565b9081526040519081900360200190205460ff1690508061013157600080fd5b600160008484604051610145929190610501565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b600060018383604051610187929190610501565b9081526040519081900360200190205460ff16905092915050565b6101aa610410565b600083836040516101bc929190610501565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b6000811161021257600080fd5b8060008484604051610225929190610501565b908152602001604051809103902060010181905550336000848460405161024d929190610501565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b031990931692909217909155600090819061028f9086908690610501565b9081526040519081900360200181208054921515600160a01b0260ff60a01b199093169290921790915560019081906102cb9086908690610501565b908152604051908190036020019020805491151560ff19909216919091179055505050565b600060018383604051610304929190610501565b9081526040519081900360200190205460ff1690508061032357600080fd5b6000808484604051610145929190610501565b60006001838360405161034a929190610501565b9081526040519081900360200190205460ff1690508061036957600080fd5b600080848460405161037c929190610501565b9081526040519081900360200190205460ff600160a01b909104169050806103a357600080fd5b600084846040516103b5929190610501565b90815260405190819003602001812080546001600160a81b031916815560006001918201819055916103ea9087908790610501565b908152604051908190036020019020805491151560ff1990921691909117905550505050565b604080516060810182526000808252602082018190529181019190915290565b60008083601f840112610441578182fd5b50813567ffffffffffffffff811115610458578182fd5b60208301915083602082850101111561047057600080fd5b9250929050565b60008060208385031215610489578182fd5b823567ffffffffffffffff81111561049f578283fd5b6104ab85828601610430565b90969095509350505050565b6000806000604084860312156104cb578081fd5b833567ffffffffffffffff8111156104e1578182fd5b6104ed86828701610430565b909790965060209590950135949350505050565b6000828483379101908152919050565b901515815260200190565b81516001600160a01b03168152602080830151151590820152604091820151918101919091526060019056fea2646970667358221220b5f775906e8b1a0886fce5dcca04561f86c492a095697545bb2365d7364dd33364736f6c63430008000033", + "sourceMap": "543:1223:0:-:0;;;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "543:1223:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1010:144;;;;;;:::i;:::-;;:::i;:::-;;1663:101;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;864:103;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;643:218::-;;;;;;:::i;:::-;;:::i;1199:146::-;;;;;;:::i;:::-;;:::i;1349:310::-;;;;;;:::i;:::-;;:::i;1010:144::-;1064:11;1078;1090:2;;1078:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1078:15:0;1101;;;;;;1145:4;1125:6;1132:2;;1125:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:24;;;;;-1:-1:-1;;;1125:24:0;-1:-1:-1;;;;1125:24:0;;;;;;;;;-1:-1:-1;;;1010:144:0:o;1663:101::-;1723:4;1744:11;1756:2;;1744:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1663:101:0;;;;:::o;864:103::-;923:12;;:::i;:::-;952:6;959:2;;952:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;945:17;;;;;;;-1:-1:-1;;;;;945:17:0;;;;-1:-1:-1;;;945:17:0;;;;;;;;;;;;;;;;;;;;;;952:10;864:103;-1:-1:-1;;;864:103:0:o;643:218::-;723:1;718:4;:6;710:15;;;;;;750:4;733:6;740:2;;733:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;:21;;;;783:10;762:6;769:2;;762:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:31;;-1:-1:-1;;;;;762:31:0;;;;-1:-1:-1;;;;;;762:31:0;;;;;;;;;;:18;;;;801:10;;808:2;;;;801:10;:::i;:::-;;;;;;;;;;;;;;:25;;;;;-1:-1:-1;;;801:25:0;-1:-1:-1;;;;801:25:0;;;;;;;;;;-1:-1:-1;;;;834:15:0;;846:2;;;;834:15;:::i;:::-;;;;;;;;;;;;;;:22;;;;;-1:-1:-1;;834:22:0;;;;;;;;;-1:-1:-1;;;643:218:0:o;1199:146::-;1254:11;1268;1280:2;;1268:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1268:15:0;1291;;;;;;1335:5;1315:6;1322:2;;1315:10;;;;;;;:::i;1349:310::-;1405:11;1419;1431:2;;1419:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1419:15:0;1442;;;;;;1529:18;1550:6;1557:2;;1550:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:17;;-1:-1:-1;;;1550:17:0;;;;;-1:-1:-1;1550:17:0;1575:22;;;;;;1613:6;1620:2;;1613:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;1606:17;;-1:-1:-1;;;;;;1606:17:0;;;1613:10;1606:17;;;;;;;1613:10;1631:15;;1643:2;;;;1631:15;:::i;:::-;;;;;;;;;;;;;;:23;;;;;-1:-1:-1;;1631:23:0;;;;;;;;;-1:-1:-1;;;;1349:310:0:o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:378:1:-;;;132:3;125:4;117:6;113:17;109:27;99:2;;157:8;147;140:26;99:2;-1:-1:-1;187:20:1;;230:18;219:30;;216:2;;;269:8;259;252:26;216:2;313:4;305:6;301:17;289:29;;365:3;358:4;349:6;341;337:19;333:30;330:39;327:2;;;382:1;379;372:12;327:2;89:303;;;;;:::o;397:433::-;;;529:2;517:9;508:7;504:23;500:32;497:2;;;550:6;542;535:22;497:2;595:9;582:23;628:18;620:6;617:30;614:2;;;665:6;657;650:22;614:2;709:61;762:7;753:6;742:9;738:22;709:61;:::i;:::-;789:8;;683:87;;-1:-1:-1;487:343:1;-1:-1:-1;;;;487:343:1:o;835:501::-;;;;984:2;972:9;963:7;959:23;955:32;952:2;;;1005:6;997;990:22;952:2;1050:9;1037:23;1083:18;1075:6;1072:30;1069:2;;;1120:6;1112;1105:22;1069:2;1164:61;1217:7;1208:6;1197:9;1193:22;1164:61;:::i;:::-;1244:8;;1138:87;;-1:-1:-1;1326:2:1;1311:18;;;;1298:32;;942:394;-1:-1:-1;;;;942:394:1:o;1341:275::-;;1526:6;1518;1513:3;1500:33;1552:16;;1577:15;;;1552:16;1490:126;-1:-1:-1;1490:126:1:o;1621:187::-;1786:14;;1779:22;1761:41;;1749:2;1734:18;;1716:92::o;1813:392::-;2021:13;;-1:-1:-1;;;;;2017:39:1;1999:58;;2127:4;2115:17;;;2109:24;2102:32;2095:40;2073:20;;;2066:70;2192:4;2180:17;;;2174:24;2152:20;;;2145:54;;;;1987:2;1972:18;;1954:251::o", + "sourcePath": "/home/andre_9a/cactus/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol", "compiler": { "name": "solc", - "version": "0.8.6+commit.11564f7e" + "version": "0.8.0+commit.c7dfd78e" }, "ast": { - "absolutePath": "/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol", + "absolutePath": "/home/andre_9a/cactus/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol", "exportedSymbols": { "Asset": [ 8 ], "LockAsset": [ - 150 + 169 ] }, - "id": 151, + "id": 170, "nodeType": "SourceUnit", "nodes": [ { @@ -138,7 +157,6 @@ "id": 3, "mutability": "mutable", "name": "creator", - "nameLocation": "464:7:0", "nodeType": "VariableDeclaration", "scope": 8, "src": "456:15:0", @@ -166,7 +184,6 @@ "id": 5, "mutability": "mutable", "name": "isLock", - "nameLocation": "482:6:0", "nodeType": "VariableDeclaration", "scope": 8, "src": "477:11:0", @@ -193,7 +210,6 @@ "id": 7, "mutability": "mutable", "name": "size", - "nameLocation": "499:4:0", "nodeType": "VariableDeclaration", "scope": 8, "src": "494:9:0", @@ -217,9 +233,8 @@ } ], "name": "Asset", - "nameLocation": "445:5:0", "nodeType": "StructDefinition", - "scope": 151, + "scope": 170, "src": "438:68:0", "visibility": "public" }, @@ -229,12 +244,11 @@ "contractDependencies": [], "contractKind": "contract", "fullyImplemented": true, - "id": 150, + "id": 169, "linearizedBaseContracts": [ - 150 + 169 ], "name": "LockAsset", - "nameLocation": "552:9:0", "nodeType": "ContractDefinition", "nodes": [ { @@ -242,10 +256,9 @@ "id": 13, "mutability": "mutable", "name": "assets", - "nameLocation": "597:6:0", "nodeType": "VariableDeclaration", - "scope": 150, - "src": "571:32:0", + "scope": 169, + "src": "566:32:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { @@ -258,14 +271,14 @@ "id": 9, "name": "string", "nodeType": "ElementaryTypeName", - "src": "580:6:0", + "src": "575:6:0", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" } }, "nodeType": "Mapping", - "src": "571:25:0", + "src": "566:25:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string => struct Asset)" @@ -278,10 +291,10 @@ "name": "Asset", "nodeType": "IdentifierPath", "referencedDeclaration": 8, - "src": "590:5:0" + "src": "585:5:0" }, "referencedDeclaration": 8, - "src": "590:5:0", + "src": "585:5:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage_ptr", "typeString": "struct Asset" @@ -290,11 +303,56 @@ }, "visibility": "internal" }, + { + "constant": false, + "id": 17, + "mutability": "mutable", + "name": "assetExists", + "nodeType": "VariableDeclaration", + "scope": 169, + "src": "602:36:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string => bool)" + }, + "typeName": { + "id": 16, + "keyType": { + "id": 14, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "611:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "nodeType": "Mapping", + "src": "602:24:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string => bool)" + }, + "valueType": { + "id": 15, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "621:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + }, + "visibility": "internal" + }, { "body": { - "id": 48, + "id": 58, "nodeType": "Block", - "src": "666:129:0", + "src": "702:159:0", "statements": [ { "expression": { @@ -304,18 +362,18 @@ "typeIdentifier": "t_uint256", "typeString": "uint256" }, - "id": 23, + "id": 27, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { - "id": 21, + "id": 25, "name": "size", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 17, - "src": "682:4:0", + "referencedDeclaration": 21, + "src": "718:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -325,21 +383,21 @@ "operator": ">", "rightExpression": { "hexValue": "30", - "id": 22, + "id": 26, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", - "src": "687:1:0", + "src": "723:1:0", "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, - "src": "682:6:0", + "src": "718:6:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -353,7 +411,7 @@ "typeString": "bool" } ], - "id": 20, + "id": 24, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ @@ -361,13 +419,13 @@ 4294967278 ], "referencedDeclaration": 4294967278, - "src": "674:7:0", + "src": "710:7:0", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, - "id": 24, + "id": 28, "isConstant": false, "isLValue": false, "isPure": false, @@ -375,20 +433,20 @@ "lValueRequested": false, "names": [], "nodeType": "FunctionCall", - "src": "674:15:0", + "src": "710:15:0", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, - "id": 25, + "id": 29, "nodeType": "ExpressionStatement", - "src": "674:15:0" + "src": "710:15:0" }, { "expression": { - "id": 31, + "id": 35, "isConstant": false, "isLValue": false, "isPure": false, @@ -396,25 +454,25 @@ "leftHandSide": { "expression": { "baseExpression": { - "id": 26, + "id": 30, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "697:6:0", + "src": "733:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 28, + "id": 32, "indexExpression": { - "id": 27, + "id": 31, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 15, - "src": "704:2:0", + "referencedDeclaration": 19, + "src": "740:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -425,13 +483,13 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "697:10:0", + "src": "733:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" } }, - "id": 29, + "id": 33, "isConstant": false, "isLValue": true, "isPure": false, @@ -439,7 +497,7 @@ "memberName": "size", "nodeType": "MemberAccess", "referencedDeclaration": 7, - "src": "697:15:0", + "src": "733:15:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -448,30 +506,30 @@ "nodeType": "Assignment", "operator": "=", "rightHandSide": { - "id": 30, + "id": 34, "name": "size", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 17, - "src": "714:4:0", + "referencedDeclaration": 21, + "src": "750:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, - "src": "697:21:0", + "src": "733:21:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, - "id": 32, + "id": 36, "nodeType": "ExpressionStatement", - "src": "697:21:0" + "src": "733:21:0" }, { "expression": { - "id": 39, + "id": 43, "isConstant": false, "isLValue": false, "isPure": false, @@ -479,25 +537,25 @@ "leftHandSide": { "expression": { "baseExpression": { - "id": 33, + "id": 37, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "726:6:0", + "src": "762:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 35, + "id": 39, "indexExpression": { - "id": 34, + "id": 38, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 15, - "src": "733:2:0", + "referencedDeclaration": 19, + "src": "769:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -508,13 +566,13 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "726:10:0", + "src": "762:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" } }, - "id": 36, + "id": 40, "isConstant": false, "isLValue": true, "isPure": false, @@ -522,7 +580,7 @@ "memberName": "creator", "nodeType": "MemberAccess", "referencedDeclaration": 3, - "src": "726:18:0", + "src": "762:18:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" @@ -532,43 +590,43 @@ "operator": "=", "rightHandSide": { "expression": { - "id": 37, + "id": 41, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294967281, - "src": "747:3:0", + "src": "783:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, - "id": 38, + "id": 42, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", - "src": "747:10:0", + "src": "783:10:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, - "src": "726:31:0", + "src": "762:31:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, - "id": 40, + "id": 44, "nodeType": "ExpressionStatement", - "src": "726:31:0" + "src": "762:31:0" }, { "expression": { - "id": 46, + "id": 50, "isConstant": false, "isLValue": false, "isPure": false, @@ -576,25 +634,25 @@ "leftHandSide": { "expression": { "baseExpression": { - "id": 41, + "id": 45, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "765:6:0", + "src": "801:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 43, + "id": 47, "indexExpression": { - "id": 42, + "id": 46, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 15, - "src": "772:2:0", + "referencedDeclaration": 19, + "src": "808:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -605,13 +663,13 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "765:10:0", + "src": "801:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" } }, - "id": 44, + "id": 48, "isConstant": false, "isLValue": true, "isPure": false, @@ -619,7 +677,7 @@ "memberName": "isLock", "nodeType": "MemberAccess", "referencedDeclaration": 5, - "src": "765:17:0", + "src": "801:17:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -629,53 +687,123 @@ "operator": "=", "rightHandSide": { "hexValue": "66616c7365", - "id": 45, + "id": 49, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", - "src": "785:5:0", + "src": "821:5:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, - "src": "765:25:0", + "src": "801:25:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 51, + "nodeType": "ExpressionStatement", + "src": "801:25:0" + }, + { + "expression": { + "id": 56, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 52, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "834:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 54, + "indexExpression": { + "id": 53, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 19, + "src": "846:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "834:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "74727565", + "id": 55, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "852:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "src": "834:22:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, - "id": 47, + "id": 57, "nodeType": "ExpressionStatement", - "src": "765:25:0" + "src": "834:22:0" } ] }, "functionSelector": "db9cc410", - "id": 49, + "id": 59, "implemented": true, "kind": "function", "modifiers": [], "name": "createAsset", - "nameLocation": "616:11:0", "nodeType": "FunctionDefinition", "parameters": { - "id": 18, + "id": 22, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 15, + "id": 19, "mutability": "mutable", "name": "id", - "nameLocation": "645:2:0", "nodeType": "VariableDeclaration", - "scope": 49, - "src": "629:18:0", + "scope": 59, + "src": "665:18:0", "stateVariable": false, "storageLocation": "calldata", "typeDescriptions": { @@ -683,10 +811,10 @@ "typeString": "string" }, "typeName": { - "id": 14, + "id": 18, "name": "string", "nodeType": "ElementaryTypeName", - "src": "629:6:0", + "src": "665:6:0", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" @@ -696,13 +824,12 @@ }, { "constant": false, - "id": 17, + "id": 21, "mutability": "mutable", "name": "size", - "nameLocation": "654:4:0", "nodeType": "VariableDeclaration", - "scope": 49, - "src": "649:9:0", + "scope": 59, + "src": "685:9:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -710,10 +837,10 @@ "typeString": "uint256" }, "typeName": { - "id": 16, + "id": 20, "name": "uint", "nodeType": "ElementaryTypeName", - "src": "649:4:0", + "src": "685:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" @@ -722,48 +849,48 @@ "visibility": "internal" } ], - "src": "627:32:0" + "src": "663:32:0" }, "returnParameters": { - "id": 19, + "id": 23, "nodeType": "ParameterList", "parameters": [], - "src": "666:0:0" + "src": "702:0:0" }, - "scope": 150, - "src": "607:188:0", + "scope": 169, + "src": "643:218:0", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "body": { - "id": 61, + "id": 71, "nodeType": "Block", - "src": "873:30:0", + "src": "937:30:0", "statements": [ { "expression": { "baseExpression": { - "id": 57, + "id": 67, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "888:6:0", + "src": "952:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 59, + "id": 69, "indexExpression": { - "id": 58, + "id": 68, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 51, - "src": "895:2:0", + "referencedDeclaration": 61, + "src": "959:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -774,40 +901,38 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "888:10:0", + "src": "952:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" } }, - "functionReturnParameters": 56, - "id": 60, + "functionReturnParameters": 66, + "id": 70, "nodeType": "Return", - "src": "881:17:0" + "src": "945:17:0" } ] }, "functionSelector": "cd5286d0", - "id": 62, + "id": 72, "implemented": true, "kind": "function", "modifiers": [], "name": "getAsset", - "nameLocation": "807:8:0", "nodeType": "FunctionDefinition", "parameters": { - "id": 52, + "id": 62, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 51, + "id": 61, "mutability": "mutable", "name": "id", - "nameLocation": "832:2:0", "nodeType": "VariableDeclaration", - "scope": 62, - "src": "816:18:0", + "scope": 72, + "src": "882:18:0", "stateVariable": false, "storageLocation": "calldata", "typeDescriptions": { @@ -815,10 +940,10 @@ "typeString": "string" }, "typeName": { - "id": 50, + "id": 60, "name": "string", "nodeType": "ElementaryTypeName", - "src": "816:6:0", + "src": "882:6:0", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" @@ -827,21 +952,20 @@ "visibility": "internal" } ], - "src": "815:20:0" + "src": "881:20:0" }, "returnParameters": { - "id": 56, + "id": 66, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 55, + "id": 65, "mutability": "mutable", "name": "", - "nameLocation": "-1:-1:-1", "nodeType": "VariableDeclaration", - "scope": 62, - "src": "857:12:0", + "scope": 72, + "src": "923:12:0", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { @@ -849,17 +973,17 @@ "typeString": "struct Asset" }, "typeName": { - "id": 54, + "id": 64, "nodeType": "UserDefinedTypeName", "pathNode": { - "id": 53, + "id": 63, "name": "Asset", "nodeType": "IdentifierPath", "referencedDeclaration": 8, - "src": "857:5:0" + "src": "923:5:0" }, "referencedDeclaration": 8, - "src": "857:5:0", + "src": "923:5:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage_ptr", "typeString": "struct Asset" @@ -868,34 +992,33 @@ "visibility": "internal" } ], - "src": "856:14:0" + "src": "922:14:0" }, - "scope": 150, - "src": "798:105:0", + "scope": 169, + "src": "864:103:0", "stateMutability": "view", "virtual": false, "visibility": "public" }, { "body": { - "id": 87, + "id": 94, "nodeType": "Block", - "src": "991:109:0", + "src": "1056:98:0", "statements": [ { "assignments": [ - 68 + 78 ], "declarations": [ { "constant": false, - "id": 68, + "id": 78, "mutability": "mutable", - "name": "assetExsist", - "nameLocation": "1004:11:0", + "name": "exists", "nodeType": "VariableDeclaration", - "scope": 87, - "src": "999:16:0", + "scope": 94, + "src": "1064:11:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -903,10 +1026,10 @@ "typeString": "bool" }, "typeName": { - "id": 67, + "id": 77, "name": "bool", "nodeType": "ElementaryTypeName", - "src": "999:4:0", + "src": "1064:4:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -915,106 +1038,57 @@ "visibility": "internal" } ], - "id": 75, + "id": 82, "initialValue": { - "commonType": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "id": 74, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftExpression": { - "expression": { - "baseExpression": { - "id": 69, - "name": "assets", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 13, - "src": "1018:6:0", - "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", - "typeString": "mapping(string memory => struct Asset storage ref)" - } - }, - "id": 71, - "indexExpression": { - "id": 70, - "name": "id", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 64, - "src": "1025:2:0", - "typeDescriptions": { - "typeIdentifier": "t_string_calldata_ptr", - "typeString": "string calldata" - } - }, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": false, - "nodeType": "IndexAccess", - "src": "1018:10:0", - "typeDescriptions": { - "typeIdentifier": "t_struct$_Asset_$8_storage", - "typeString": "struct Asset storage ref" - } - }, - "id": 72, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": false, - "memberName": "size", - "nodeType": "MemberAccess", - "referencedDeclaration": 7, - "src": "1018:15:0", + "baseExpression": { + "id": 79, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1078:11:0", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" } }, - "nodeType": "BinaryOperation", - "operator": ">", - "rightExpression": { - "hexValue": "30", - "id": 73, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "1034:1:0", + "id": 81, + "indexExpression": { + "id": 80, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 74, + "src": "1090:2:0", "typeDescriptions": { - "typeIdentifier": "t_rational_0_by_1", - "typeString": "int_const 0" - }, - "value": "0" + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } }, - "src": "1018:17:0", + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1078:15:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "VariableDeclarationStatement", - "src": "999:36:0" + "src": "1064:29:0" }, { "expression": { "arguments": [ { - "id": 77, - "name": "assetExsist", + "id": 84, + "name": "exists", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 68, - "src": "1051:11:0", + "referencedDeclaration": 78, + "src": "1109:6:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1028,7 +1102,7 @@ "typeString": "bool" } ], - "id": 76, + "id": 83, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ @@ -1036,13 +1110,13 @@ 4294967278 ], "referencedDeclaration": 4294967278, - "src": "1043:7:0", + "src": "1101:7:0", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, - "id": 78, + "id": 85, "isConstant": false, "isLValue": false, "isPure": false, @@ -1050,20 +1124,20 @@ "lValueRequested": false, "names": [], "nodeType": "FunctionCall", - "src": "1043:20:0", + "src": "1101:15:0", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, - "id": 79, + "id": 86, "nodeType": "ExpressionStatement", - "src": "1043:20:0" + "src": "1101:15:0" }, { "expression": { - "id": 85, + "id": 92, "isConstant": false, "isLValue": false, "isPure": false, @@ -1071,25 +1145,25 @@ "leftHandSide": { "expression": { "baseExpression": { - "id": 80, + "id": 87, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "1071:6:0", + "src": "1125:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 82, + "id": 89, "indexExpression": { - "id": 81, + "id": 88, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 64, - "src": "1078:2:0", + "referencedDeclaration": 74, + "src": "1132:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -1100,13 +1174,13 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "1071:10:0", + "src": "1125:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" } }, - "id": 83, + "id": 90, "isConstant": false, "isLValue": true, "isPure": false, @@ -1114,7 +1188,7 @@ "memberName": "isLock", "nodeType": "MemberAccess", "referencedDeclaration": 5, - "src": "1071:17:0", + "src": "1125:17:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1124,53 +1198,51 @@ "operator": "=", "rightHandSide": { "hexValue": "74727565", - "id": 84, + "id": 91, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", - "src": "1091:4:0", + "src": "1145:4:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "true" }, - "src": "1071:24:0", + "src": "1125:24:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, - "id": 86, + "id": 93, "nodeType": "ExpressionStatement", - "src": "1071:24:0" + "src": "1125:24:0" } ] }, "functionSelector": "5e82d0a6", - "id": 88, + "id": 95, "implemented": true, "kind": "function", "modifiers": [], "name": "lockAsset", - "nameLocation": "955:9:0", "nodeType": "FunctionDefinition", "parameters": { - "id": 65, + "id": 75, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 64, + "id": 74, "mutability": "mutable", "name": "id", - "nameLocation": "981:2:0", "nodeType": "VariableDeclaration", - "scope": 88, - "src": "965:18:0", + "scope": 95, + "src": "1029:18:0", "stateVariable": false, "storageLocation": "calldata", "typeDescriptions": { @@ -1178,10 +1250,10 @@ "typeString": "string" }, "typeName": { - "id": 63, + "id": 73, "name": "string", "nodeType": "ElementaryTypeName", - "src": "965:6:0", + "src": "1029:6:0", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" @@ -1190,40 +1262,39 @@ "visibility": "internal" } ], - "src": "964:20:0" + "src": "1028:20:0" }, "returnParameters": { - "id": 66, + "id": 76, "nodeType": "ParameterList", "parameters": [], - "src": "991:0:0" + "src": "1056:0:0" }, - "scope": 150, - "src": "946:154:0", + "scope": 169, + "src": "1010:144:0", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "body": { - "id": 113, + "id": 117, "nodeType": "Block", - "src": "1191:110:0", + "src": "1246:99:0", "statements": [ { "assignments": [ - 94 + 101 ], "declarations": [ { "constant": false, - "id": 94, + "id": 101, "mutability": "mutable", - "name": "assetExsist", - "nameLocation": "1204:11:0", + "name": "exists", "nodeType": "VariableDeclaration", - "scope": 113, - "src": "1199:16:0", + "scope": 117, + "src": "1254:11:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -1231,10 +1302,10 @@ "typeString": "bool" }, "typeName": { - "id": 93, + "id": 100, "name": "bool", "nodeType": "ElementaryTypeName", - "src": "1199:4:0", + "src": "1254:4:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1243,106 +1314,57 @@ "visibility": "internal" } ], - "id": 101, + "id": 105, "initialValue": { - "commonType": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "id": 100, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftExpression": { - "expression": { - "baseExpression": { - "id": 95, - "name": "assets", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 13, - "src": "1218:6:0", - "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", - "typeString": "mapping(string memory => struct Asset storage ref)" - } - }, - "id": 97, - "indexExpression": { - "id": 96, - "name": "id", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 90, - "src": "1225:2:0", - "typeDescriptions": { - "typeIdentifier": "t_string_calldata_ptr", - "typeString": "string calldata" - } - }, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": false, - "nodeType": "IndexAccess", - "src": "1218:10:0", - "typeDescriptions": { - "typeIdentifier": "t_struct$_Asset_$8_storage", - "typeString": "struct Asset storage ref" - } - }, - "id": 98, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": false, - "memberName": "size", - "nodeType": "MemberAccess", - "referencedDeclaration": 7, - "src": "1218:15:0", + "baseExpression": { + "id": 102, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1268:11:0", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" } }, - "nodeType": "BinaryOperation", - "operator": ">", - "rightExpression": { - "hexValue": "30", - "id": 99, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "1234:1:0", + "id": 104, + "indexExpression": { + "id": 103, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 97, + "src": "1280:2:0", "typeDescriptions": { - "typeIdentifier": "t_rational_0_by_1", - "typeString": "int_const 0" - }, - "value": "0" + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } }, - "src": "1218:17:0", + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1268:15:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "VariableDeclarationStatement", - "src": "1199:36:0" + "src": "1254:29:0" }, { "expression": { "arguments": [ { - "id": 103, - "name": "assetExsist", + "id": 107, + "name": "exists", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 94, - "src": "1251:11:0", + "referencedDeclaration": 101, + "src": "1299:6:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1356,7 +1378,7 @@ "typeString": "bool" } ], - "id": 102, + "id": 106, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ @@ -1364,13 +1386,13 @@ 4294967278 ], "referencedDeclaration": 4294967278, - "src": "1243:7:0", + "src": "1291:7:0", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, - "id": 104, + "id": 108, "isConstant": false, "isLValue": false, "isPure": false, @@ -1378,20 +1400,20 @@ "lValueRequested": false, "names": [], "nodeType": "FunctionCall", - "src": "1243:20:0", + "src": "1291:15:0", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, - "id": 105, + "id": 109, "nodeType": "ExpressionStatement", - "src": "1243:20:0" + "src": "1291:15:0" }, { "expression": { - "id": 111, + "id": 115, "isConstant": false, "isLValue": false, "isPure": false, @@ -1399,25 +1421,25 @@ "leftHandSide": { "expression": { "baseExpression": { - "id": 106, + "id": 110, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "1271:6:0", + "src": "1315:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 108, + "id": 112, "indexExpression": { - "id": 107, + "id": 111, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 90, - "src": "1278:2:0", + "referencedDeclaration": 97, + "src": "1322:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -1428,13 +1450,13 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "1271:10:0", + "src": "1315:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" } }, - "id": 109, + "id": 113, "isConstant": false, "isLValue": true, "isPure": false, @@ -1442,7 +1464,7 @@ "memberName": "isLock", "nodeType": "MemberAccess", "referencedDeclaration": 5, - "src": "1271:17:0", + "src": "1315:17:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1452,53 +1474,51 @@ "operator": "=", "rightHandSide": { "hexValue": "66616c7365", - "id": 110, + "id": 114, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", - "src": "1291:5:0", + "src": "1335:5:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, - "src": "1271:25:0", + "src": "1315:25:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, - "id": 112, + "id": 116, "nodeType": "ExpressionStatement", - "src": "1271:25:0" + "src": "1315:25:0" } ] }, "functionSelector": "def60e0d", - "id": 114, + "id": 118, "implemented": true, "kind": "function", "modifiers": [], "name": "unLockAsset", - "nameLocation": "1153:11:0", "nodeType": "FunctionDefinition", "parameters": { - "id": 91, + "id": 98, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 90, + "id": 97, "mutability": "mutable", "name": "id", - "nameLocation": "1181:2:0", "nodeType": "VariableDeclaration", - "scope": 114, - "src": "1165:18:0", + "scope": 118, + "src": "1220:18:0", "stateVariable": false, "storageLocation": "calldata", "typeDescriptions": { @@ -1506,10 +1526,10 @@ "typeString": "string" }, "typeName": { - "id": 89, + "id": 96, "name": "string", "nodeType": "ElementaryTypeName", - "src": "1165:6:0", + "src": "1220:6:0", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" @@ -1518,40 +1538,39 @@ "visibility": "internal" } ], - "src": "1164:20:0" + "src": "1219:20:0" }, "returnParameters": { - "id": 92, + "id": 99, "nodeType": "ParameterList", "parameters": [], - "src": "1191:0:0" + "src": "1246:0:0" }, - "scope": 150, - "src": "1144:157:0", + "scope": 169, + "src": "1199:146:0", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "body": { - "id": 148, + "id": 155, "nodeType": "Block", - "src": "1352:241:0", + "src": "1397:262:0", "statements": [ { "assignments": [ - 120 + 124 ], "declarations": [ { "constant": false, - "id": 120, + "id": 124, "mutability": "mutable", - "name": "assetExsist", - "nameLocation": "1365:11:0", + "name": "exists", "nodeType": "VariableDeclaration", - "scope": 148, - "src": "1360:16:0", + "scope": 155, + "src": "1405:11:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -1559,10 +1578,10 @@ "typeString": "bool" }, "typeName": { - "id": 119, + "id": 123, "name": "bool", "nodeType": "ElementaryTypeName", - "src": "1360:4:0", + "src": "1405:4:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1571,106 +1590,57 @@ "visibility": "internal" } ], - "id": 127, + "id": 128, "initialValue": { - "commonType": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "id": 126, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftExpression": { - "expression": { - "baseExpression": { - "id": 121, - "name": "assets", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 13, - "src": "1379:6:0", - "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", - "typeString": "mapping(string memory => struct Asset storage ref)" - } - }, - "id": 123, - "indexExpression": { - "id": 122, - "name": "id", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 116, - "src": "1386:2:0", - "typeDescriptions": { - "typeIdentifier": "t_string_calldata_ptr", - "typeString": "string calldata" - } - }, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": false, - "nodeType": "IndexAccess", - "src": "1379:10:0", - "typeDescriptions": { - "typeIdentifier": "t_struct$_Asset_$8_storage", - "typeString": "struct Asset storage ref" - } - }, - "id": 124, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": false, - "memberName": "size", - "nodeType": "MemberAccess", - "referencedDeclaration": 7, - "src": "1379:15:0", + "baseExpression": { + "id": 125, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1419:11:0", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" } }, - "nodeType": "BinaryOperation", - "operator": ">", - "rightExpression": { - "hexValue": "30", - "id": 125, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "1395:1:0", + "id": 127, + "indexExpression": { + "id": 126, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 120, + "src": "1431:2:0", "typeDescriptions": { - "typeIdentifier": "t_rational_0_by_1", - "typeString": "int_const 0" - }, - "value": "0" + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } }, - "src": "1379:17:0", + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1419:15:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "VariableDeclarationStatement", - "src": "1360:36:0" + "src": "1405:29:0" }, { "expression": { "arguments": [ { - "id": 129, - "name": "assetExsist", + "id": 130, + "name": "exists", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 120, - "src": "1412:11:0", + "referencedDeclaration": 124, + "src": "1450:6:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1684,7 +1654,7 @@ "typeString": "bool" } ], - "id": 128, + "id": 129, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ @@ -1692,13 +1662,13 @@ 4294967278 ], "referencedDeclaration": 4294967278, - "src": "1404:7:0", + "src": "1442:7:0", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, - "id": 130, + "id": 131, "isConstant": false, "isLValue": false, "isPure": false, @@ -1706,31 +1676,30 @@ "lValueRequested": false, "names": [], "nodeType": "FunctionCall", - "src": "1404:20:0", + "src": "1442:15:0", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, - "id": 131, + "id": 132, "nodeType": "ExpressionStatement", - "src": "1404:20:0" + "src": "1442:15:0" }, { "assignments": [ - 133 + 134 ], "declarations": [ { "constant": false, - "id": 133, + "id": 134, "mutability": "mutable", "name": "assetIsLocked", - "nameLocation": "1500:13:0", "nodeType": "VariableDeclaration", - "scope": 148, - "src": "1495:18:0", + "scope": 155, + "src": "1529:18:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { @@ -1738,10 +1707,10 @@ "typeString": "bool" }, "typeName": { - "id": 132, + "id": 133, "name": "bool", "nodeType": "ElementaryTypeName", - "src": "1495:4:0", + "src": "1529:4:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1750,29 +1719,29 @@ "visibility": "internal" } ], - "id": 138, + "id": 139, "initialValue": { "expression": { "baseExpression": { - "id": 134, + "id": 135, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "1516:6:0", + "src": "1550:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 136, + "id": 137, "indexExpression": { - "id": 135, + "id": 136, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 116, - "src": "1523:2:0", + "referencedDeclaration": 120, + "src": "1557:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -1783,13 +1752,13 @@ "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", - "src": "1516:10:0", + "src": "1550:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" } }, - "id": 137, + "id": 138, "isConstant": false, "isLValue": true, "isPure": false, @@ -1797,25 +1766,25 @@ "memberName": "isLock", "nodeType": "MemberAccess", "referencedDeclaration": 5, - "src": "1516:17:0", + "src": "1550:17:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "VariableDeclarationStatement", - "src": "1495:38:0" + "src": "1529:38:0" }, { "expression": { "arguments": [ { - "id": 140, + "id": 141, "name": "assetIsLocked", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 133, - "src": "1549:13:0", + "referencedDeclaration": 134, + "src": "1583:13:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" @@ -1829,7 +1798,7 @@ "typeString": "bool" } ], - "id": 139, + "id": 140, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ @@ -1837,13 +1806,13 @@ 4294967278 ], "referencedDeclaration": 4294967278, - "src": "1541:7:0", + "src": "1575:7:0", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, - "id": 141, + "id": 142, "isConstant": false, "isLValue": false, "isPure": false, @@ -1851,20 +1820,20 @@ "lValueRequested": false, "names": [], "nodeType": "FunctionCall", - "src": "1541:22:0", + "src": "1575:22:0", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, - "id": 142, + "id": 143, "nodeType": "ExpressionStatement", - "src": "1541:22:0" + "src": "1575:22:0" }, { "expression": { - "id": 146, + "id": 147, "isConstant": false, "isLValue": false, "isPure": false, @@ -1872,28 +1841,28 @@ "nodeType": "UnaryOperation", "operator": "delete", "prefix": true, - "src": "1571:17:0", + "src": "1606:17:0", "subExpression": { "baseExpression": { - "id": 143, + "id": 144, "name": "assets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 13, - "src": "1578:6:0", + "src": "1613:6:0", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", "typeString": "mapping(string memory => struct Asset storage ref)" } }, - "id": 145, + "id": 146, "indexExpression": { - "id": 144, + "id": 145, "name": "id", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 116, - "src": "1585:2:0", + "referencedDeclaration": 120, + "src": "1620:2:0", "typeDescriptions": { "typeIdentifier": "t_string_calldata_ptr", "typeString": "string calldata" @@ -1904,7 +1873,7 @@ "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", - "src": "1578:10:0", + "src": "1613:10:0", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$8_storage", "typeString": "struct Asset storage ref" @@ -1915,33 +1884,103 @@ "typeString": "tuple()" } }, - "id": 147, + "id": 148, + "nodeType": "ExpressionStatement", + "src": "1606:17:0" + }, + { + "expression": { + "id": 153, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 149, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1631:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 151, + "indexExpression": { + "id": 150, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 120, + "src": "1643:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "1631:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "66616c7365", + "id": 152, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1649:5:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + "src": "1631:23:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 154, "nodeType": "ExpressionStatement", - "src": "1571:17:0" + "src": "1631:23:0" } ] }, "functionSelector": "e24aa37c", - "id": 149, + "id": 156, "implemented": true, "kind": "function", "modifiers": [], "name": "deleteAsset", - "nameLocation": "1313:11:0", "nodeType": "FunctionDefinition", "parameters": { - "id": 117, + "id": 121, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 116, + "id": 120, "mutability": "mutable", "name": "id", - "nameLocation": "1341:2:0", "nodeType": "VariableDeclaration", - "scope": 149, - "src": "1325:18:0", + "scope": 156, + "src": "1370:18:0", "stateVariable": false, "storageLocation": "calldata", "typeDescriptions": { @@ -1949,10 +1988,10 @@ "typeString": "string" }, "typeName": { - "id": 115, + "id": 119, "name": "string", "nodeType": "ElementaryTypeName", - "src": "1325:6:0", + "src": "1370:6:0", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" @@ -1961,45 +2000,176 @@ "visibility": "internal" } ], - "src": "1324:20:0" + "src": "1369:20:0" }, "returnParameters": { - "id": 118, + "id": 122, "nodeType": "ParameterList", "parameters": [], - "src": "1352:0:0" + "src": "1397:0:0" }, - "scope": 150, - "src": "1304:289:0", + "scope": 169, + "src": "1349:310:0", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" + }, + { + "body": { + "id": 167, + "nodeType": "Block", + "src": "1729:35:0", + "statements": [ + { + "expression": { + "baseExpression": { + "id": 163, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1744:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 165, + "indexExpression": { + "id": 164, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 158, + "src": "1756:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1744:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 162, + "id": 166, + "nodeType": "Return", + "src": "1737:22:0" + } + ] + }, + "functionSelector": "bc548275", + "id": 168, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isPresent", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 159, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 158, + "mutability": "mutable", + "name": "id", + "nodeType": "VariableDeclaration", + "scope": 168, + "src": "1682:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 157, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1682:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1681:20:0" + }, + "returnParameters": { + "id": 162, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 161, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 168, + "src": "1723:4:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 160, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1723:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "src": "1722:6:0" + }, + "scope": 169, + "src": "1663:101:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" } ], - "scope": 151, - "src": "543:1053:0", - "usedErrors": [] + "scope": 170, + "src": "543:1223:0" } ], - "src": "413:1184:0" + "src": "413:1354:0" }, "functionHashes": { "createAsset(string,uint256)": "db9cc410", "deleteAsset(string)": "e24aa37c", "getAsset(string)": "cd5286d0", + "isPresent(string)": "bc548275", "lockAsset(string)": "5e82d0a6", "unLockAsset(string)": "def60e0d" }, "gasEstimates": { "creation": { - "codeDepositCost": "228200", - "executionCost": "269", - "totalCost": "228469" + "codeDepositCost": "281200", + "executionCost": "318", + "totalCost": "281518" }, "external": { "createAsset(string,uint256)": "infinite", "deleteAsset(string)": "infinite", "getAsset(string)": "infinite", + "isPresent(string)": "infinite", "lockAsset(string)": "infinite", "unLockAsset(string)": "infinite" } diff --git a/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol b/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol index 4f6ef87386..723bc8f1f2 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol +++ b/packages/cactus-plugin-odap-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol @@ -13,38 +13,50 @@ struct Asset{ } //TODO: DETEMINE CALLDATA VS MEMORY contract LockAsset { - // mapping (string => Asset) assets; + mapping (string => bool) assetExists; + function createAsset( string calldata id, uint size) public{ require(size>0); assets[id].size= size; assets[id].creator = msg.sender; assets[id].isLock = false; + assetExists[id] = true; } - function getAsset(string calldata id) public view returns (Asset memory) - { + + function getAsset(string calldata id) public view returns (Asset memory) { return assets[id]; } //Don't care if it is already locked - function lockAsset(string calldata id) public{ - bool assetExsist = assets[id].size>0; - require(assetExsist); + function lockAsset(string calldata id) public { + bool exists = assetExists[id]; + require(exists); + assets[id].isLock = true; } + //Don't care if it is already unlocked - function unLockAsset(string calldata id) public{ - bool assetExsist = assets[id].size>0; - require(assetExsist); + function unLockAsset(string calldata id) public { + bool exists = assetExists[id]; + require(exists); + assets[id].isLock = false; } + function deleteAsset(string calldata id) public { - bool assetExsist = assets[id].size>0; - require(assetExsist); + bool exists = assetExists[id]; + require(exists); + //an asset could only be deleted if it is already locked bool assetIsLocked = assets[id].isLock; require(assetIsLocked); + delete assets[id]; + assetExists[id] = false; } + function isPresent(string calldata id) public view returns (bool) { + return assetExists[id]; + } } diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/asset.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/asset.ts index aec8d90c52..f93f19710e 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/asset.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/asset.ts @@ -13,8 +13,8 @@ export class Asset { public ID: string; @Property() - public IsLock: boolean; + public isLocked: boolean; @Property() - public Size: number; + public size: number; } diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/assetTransfer.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/assetTransfer.ts index 28898c6553..a98006bfa5 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/assetTransfer.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/assetTransfer.ts @@ -21,13 +21,13 @@ export class AssetTransferContract extends Contract { const assets: Asset[] = [ { ID: "asset1", - Size: 5, - IsLock: false, + size: 5, + isLocked: false, }, { ID: "asset2", - Size: 5, - IsLock: false, + size: 5, + isLocked: false, }, ]; @@ -47,8 +47,8 @@ export class AssetTransferContract extends Contract { ): Promise { const asset: Asset = { ID: id, - Size: size, - IsLock: false, + size: size, + isLocked: false, }; await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); } @@ -75,9 +75,13 @@ export class AssetTransferContract extends Contract { throw new Error(`The asset ${id} does not exist`); } + if (this.IsAssetLocked(ctx, id)) { + throw new Error(`The asset ${id} is locked`); + } + // overwriting original asset with new asset const assetString = await this.ReadAsset(ctx, id); - const asset = JSON.parse(assetString); + const asset: Asset = JSON.parse(assetString); asset.size = size; return ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); } @@ -89,6 +93,7 @@ export class AssetTransferContract extends Contract { if (!exists) { throw new Error(`The asset ${id} does not exist`); } + return ctx.stub.deleteState(id); } @@ -99,29 +104,53 @@ export class AssetTransferContract extends Contract { const assetJSON = await ctx.stub.getState(id); return assetJSON && assetJSON.length > 0; } + + // IsAssetLocked returns true when asset with given ID is locked in world state. + @Transaction(false) + @Returns("boolean") + public async IsAssetLocked(ctx: Context, id: string): Promise { + const assetJSON = await ctx.stub.getState(id); + + if (assetJSON && assetJSON.length > 0) { + const asset = JSON.parse(assetJSON.toString()); + return asset.isLocked; + } else { + throw new Error(`The asset ${id} does not exist`); + } + } + @Transaction(false) @Returns("boolean") public async LockAsset(ctx: Context, id: string): Promise { const exists = await this.AssetExists(ctx, id); + if (!exists) { throw new Error(`The asset ${id} does not exist`); } + + // if (this.IsAssetLocked(ctx, id)) { + // throw new Error(`The asset ${id} is already locked`); + // } + const assetString = await this.ReadAsset(ctx, id); - const asset = JSON.parse(assetString); - asset.isLock = true; + const asset: Asset = JSON.parse(assetString); + asset.isLocked = true; await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); return true; } + @Transaction(false) @Returns("boolean") - public async UnLockAsset(ctx: Context, id: string): Promise { + public async UnlockAsset(ctx: Context, id: string): Promise { const exists = await this.AssetExists(ctx, id); + if (!exists) { throw new Error(`The asset ${id} does not exist`); } + const assetString = await this.ReadAsset(ctx, id); - const asset = JSON.parse(assetString); - asset.isLock = false; + const asset: Asset = JSON.parse(assetString); + asset.isLocked = false; await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); return true; } diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-delete-asset.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-delete-asset.test.ts new file mode 100644 index 0000000000..a9945016e0 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-delete-asset.test.ts @@ -0,0 +1,920 @@ +import fs from "fs-extra"; +import "jest-extended"; +import http, { Server } from "http"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { AssetProfile } from "../../../main/typescript/generated/openapi/typescript-axios"; +import { + Checks, + IListenOptions, + LoggerProvider, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { DiscoveryOptions } from "fabric-network"; +import { + Containers, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, + GoIpfsTestContainer, + BesuTestLedger, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { ClientV1Request } from "../../../main/typescript/public-api"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + PluginImportType, + Constants, +} from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../main/typescript/gateway/plugin-odap-gateway"; +import { + ChainCodeProgrammingLanguage, + DefaultEventHandlerStrategy, + FabricContractInvocationType, + FileBase64, + IPluginLedgerConnectorFabricOptions, + PluginLedgerConnectorFabric, + DefaultApi as FabricApi, + FabricSigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import path from "path"; +import { + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + ReceiptType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import Web3 from "web3"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; +import { makeSessionDataChecks } from "../make-checks"; +import { + checkValidLockEvidenceRequest, + sendLockEvidenceResponse, +} from "../../../main/typescript/gateway/server/lock-evidence"; +import { + sendTransferCommenceRequest, + checkValidTransferCommenceResponse, +} from "../../../main/typescript/gateway/client/transfer-commence"; +import { + sendTransferInitializationRequest, + checkValidInitializationResponse, +} from "../../../main/typescript/gateway/client/transfer-initialization"; +import { + checkValidtransferCommenceRequest, + sendTransferCommenceResponse, +} from "../../../main/typescript/gateway/server/transfer-commence"; +import { + checkValidInitializationRequest, + sendTransferInitializationResponse, +} from "../../../main/typescript/gateway/server/transfer-initialization"; +import { + sendCommitPreparationRequest, + checkValidCommitPreparationResponse, +} from "../../../main/typescript/gateway/client/commit-preparation"; +import { + sendLockEvidenceRequest, + checkValidLockEvidenceResponse, +} from "../../../main/typescript/gateway/client/lock-evidence"; +import { + checkValidCommitPreparationRequest, + sendCommitPreparationResponse, +} from "../../../main/typescript/gateway/server/commit-preparation"; +import { besuAssetExists, fabricAssetExists } from "../make-checks-ledgers"; +/** + * Use this to debug issues with the fabric node SDK + * ```sh + * export HFC_LOGGING='{"debug":"console","info":"console"}' + * ``` + */ +let ipfsApiHost: string; + +let fabricSigningCredential: FabricSigningCredential; +const logLevel: LogLevelDesc = "TRACE"; + +let ipfsServer: Server; +let sourceGatewayServer: Server; +let recipientGatewayServer: Server; +let besuServer: Server; +let fabricServer: Server; + +let ipfsContainer: GoIpfsTestContainer; + +let fabricLedger: FabricTestLedgerV1; +let fabricContractName: string; +let fabricChannelName: string; +let fabricPath: string; + +let besuTestLedger: BesuTestLedger; +let besuPath: string; +let besuContractName: string; +let besuWeb3SigningCredential: Web3SigningCredential; +let besuKeychainId: string; + +let fabricConnector: PluginLedgerConnectorFabric; +let besuConnector: PluginLedgerConnectorBesu; + +let odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let odapClientGatewayApiHost: string; +let odapServerGatewayApiHost: string; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const FABRIC_ASSET_ID = uuidv4(); +const BESU_ASSET_ID = uuidv4(); + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "odapTestWithLedgerConnectors", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + // IPFS configuration + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + } + + { + // Fabric ledger connection + const channelId = "mychannel"; + fabricChannelName = channelId; + + fabricLedger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), + logLevel, + }); + + await fabricLedger.start(); + + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); + expect(connectionProfile).not.toBeUndefined(); + + const enrollAdminOut = await fabricLedger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await fabricLedger.enrollUser(adminWallet); + const sshConfig = await fabricLedger.getSshConfig(); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user2"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + // This is the directory structure of the Fabirc 2.x CLI container (fabric-tools image) + // const orgCfgDir = "/fabric-samples/test-network/organizations/"; + const orgCfgDir = + "/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations/"; + + // these below mirror how the fabric-samples sets up the configuration + const org1Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org1MSP", + + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt`, + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp`, + CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + // these below mirror how the fabric-samples sets up the configuration + const org2Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org2MSP", + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + CORE_PEER_ADDRESS: "peer0.org2.example.com:9051", + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp`, + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt`, + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: org1Env, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + fabricServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3000, + server: fabricServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressApp); + + const apiUrl = `http://${address}:${port}`; + fabricPath = apiUrl; + const config = new Configuration({ basePath: apiUrl }); + + const apiClient = new FabricApi(config); + + fabricContractName = "basic-asset-transfer-2"; + const contractRelPath = + "../fabric-contracts/lock-asset/chaincode-typescript"; + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ └── index.ts + // ├── tsconfig.json + const sourceFiles: FileBase64[] = []; + { + const filename = "./tsconfig.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./package.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./index.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./asset.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./assetTransfer.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + + const response = await apiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: fabricContractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { packageIds, lifecycle, success } = response.data; + expect(response.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); + + const { + approveForMyOrgList, + installList, + queryInstalledList, + commit, + packaging, + queryCommitted, + } = lifecycle; + + Checks.truthy(packageIds, `packageIds truthy OK`); + Checks.truthy( + Array.isArray(packageIds), + `Array.isArray(packageIds) truthy OK`, + ); + Checks.truthy(approveForMyOrgList, `approveForMyOrgList truthy OK`); + Checks.truthy( + Array.isArray(approveForMyOrgList), + `Array.isArray(approveForMyOrgList) truthy OK`, + ); + Checks.truthy(installList, `installList truthy OK`); + Checks.truthy( + Array.isArray(installList), + `Array.isArray(installList) truthy OK`, + ); + Checks.truthy(queryInstalledList, `queryInstalledList truthy OK`); + Checks.truthy( + Array.isArray(queryInstalledList), + `Array.isArray(queryInstalledList) truthy OK`, + ); + Checks.truthy(commit, `commit truthy OK`); + Checks.truthy(packaging, `packaging truthy OK`); + Checks.truthy(queryCommitted, `queryCommitted truthy OK`); + + // FIXME - without this wait it randomly fails with an error claiming that + // the endorsement was impossible to be obtained. The fabric-samples script + // does the same thing, it just waits 10 seconds for good measure so there + // might not be a way for us to avoid doing this, but if there is a way we + // absolutely should not have timeouts like this, anywhere... + await new Promise((resolve) => setTimeout(resolve, 10000)); + + fabricSigningCredential = { + keychainId, + keychainRef: keychainEntryKey, + }; + + const createResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [FABRIC_ASSET_ID, "19"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + + expect(createResponse).not.toBeUndefined(); + expect(createResponse.status).toBeGreaterThan(199); + expect(createResponse.status).toBeLessThan(300); + + log.info( + `BassicAssetTransfer.Create(): ${JSON.stringify(createResponse.data)}`, + ); + } + { + // Besu ledger connection + besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/1.5.1/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + + 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( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + besuConnector = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + besuServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 4000, + server: besuServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await besuConnector.getOrCreateWebServices(); + const wsApi = new SocketIoServer(besuServer, { + path: Constants.SocketIoConnectionPathV1, + }); + await besuConnector.registerWebServices(expressApp, wsApi); + besuPath = `http://${address}:${port}`; + + await besuConnector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).not.toBeUndefined(); + expect(parseInt(balance, 10)).toBe(10e9); + + besuWeb3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }; + + const deployContractResponse = await besuConnector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: besuWeb3SigningCredential, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + + expect(deployContractResponse).not.toBeUndefined(); + expect(deployContractResponse.transactionReceipt).not.toBeUndefined(); + expect( + deployContractResponse.transactionReceipt.contractAddress, + ).not.toBeUndefined(); + + besuKeychainId = keychainPlugin.getKeychainId(); + besuContractName = LockAssetContractJson.contractName; + + const contractAddress: string = deployContractResponse.transactionReceipt + .contractAddress as string; + + expect(typeof contractAddress).toBe("string"); + } + { + // Gateways configuration + odapClientGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + fabricPath: fabricPath, + fabricSigningCredential: fabricSigningCredential, + fabricChannelName: fabricChannelName, + fabricContractName: fabricContractName, + fabricAssetID: FABRIC_ASSET_ID, + knexConfig: knexClientConnection, + }; + + odapServerGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + besuAssetID: BESU_ASSET_ID, + besuPath: besuPath, + besuWeb3SigningCredential: besuWeb3SigningCredential, + besuContractName: besuContractName, + besuKeychainId: besuKeychainId, + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + } + { + // Server Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 5000, + server: recipientGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapServerGatewayApiHost = `http://${address}:${port}`; + + await pluginRecipientGateway.getOrCreateWebServices(); + await pluginRecipientGateway.registerWebServices(expressApp); + } + { + // Client Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3001, + server: sourceGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapClientGatewayApiHost = `http://${address}:${port}`; + + await pluginSourceGateway.getOrCreateWebServices(); + await pluginSourceGateway.registerWebServices(expressApp); + } +}); + +test("client gateway crashes after deleting fabric asset", async () => { + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const odapClientRequest: ClientV1Request = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + + const sessionID = pluginSourceGateway.configureOdapSession(odapClientRequest); + + const transferInitializationRequest = await sendTransferInitializationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferInitializationRequest == void 0) { + expect(false); + return; + } + + await checkValidInitializationRequest( + transferInitializationRequest, + pluginRecipientGateway, + ); + + const transferInitializationResponse = await sendTransferInitializationResponse( + transferInitializationRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferInitializationResponse == void 0) { + expect(false); + return; + } + + await checkValidInitializationResponse( + transferInitializationResponse, + pluginSourceGateway, + ); + + const transferCommenceRequest = await sendTransferCommenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferCommenceRequest == void 0) { + expect(false); + return; + } + + await checkValidtransferCommenceRequest( + transferCommenceRequest, + pluginRecipientGateway, + ); + + const transferCommenceResponse = await sendTransferCommenceResponse( + transferCommenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferCommenceResponse == void 0) { + expect(false); + return; + } + + await checkValidTransferCommenceResponse( + transferCommenceResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.lockFabricAsset(sessionID); + + const lockEvidenceRequest = await sendLockEvidenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (lockEvidenceRequest == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceRequest( + lockEvidenceRequest, + pluginRecipientGateway, + ); + + const lockEvidenceResponse = await sendLockEvidenceResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (lockEvidenceResponse == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceResponse( + lockEvidenceResponse, + pluginSourceGateway, + ); + + const commitPreparationRequest = await sendCommitPreparationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (commitPreparationRequest == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationRequest( + commitPreparationRequest, + pluginRecipientGateway, + ); + + const commitPreparationResponse = await sendCommitPreparationResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (commitPreparationResponse == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationResponse( + commitPreparationResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.deleteFabricAsset(sessionID); + + // now we simulate the crash of the client gateway + pluginSourceGateway.database?.destroy(); + await Servers.shutdown(sourceGatewayServer); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3001, + server: sourceGatewayServer, + }; + + await Servers.listen(listenOptions); + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + await pluginSourceGateway.registerWebServices(expressApp); + + // client gateway self-healed and is back online + await pluginSourceGateway.recoverOpenSessions(true); + + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionID, + ); + + expect( + await fabricAssetExists( + pluginSourceGateway, + fabricContractName, + fabricChannelName, + FABRIC_ASSET_ID, + fabricSigningCredential, + ), + ).toBe(false); + + expect( + await besuAssetExists( + pluginRecipientGateway, + besuContractName, + besuKeychainId, + BESU_ASSET_ID, + besuWeb3SigningCredential, + ), + ).toBe(true); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await fabricLedger.stop(); + await fabricLedger.destroy(); + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); + + await Servers.shutdown(ipfsServer); + await Servers.shutdown(besuServer); + await Servers.shutdown(fabricServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayServer); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-lock-asset.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-lock-asset.test.ts new file mode 100644 index 0000000000..ed1e762754 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-lock-asset.test.ts @@ -0,0 +1,854 @@ +import fs from "fs-extra"; +import "jest-extended"; +import http, { Server } from "http"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { AssetProfile } from "../../../main/typescript/generated/openapi/typescript-axios"; +import { + Checks, + IListenOptions, + LoggerProvider, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { DiscoveryOptions } from "fabric-network"; +import { + Containers, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, + GoIpfsTestContainer, + BesuTestLedger, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { ClientV1Request } from "../../../main/typescript/public-api"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + PluginImportType, + Constants, +} from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../main/typescript/gateway/plugin-odap-gateway"; +import { + ChainCodeProgrammingLanguage, + DefaultEventHandlerStrategy, + FabricContractInvocationType, + FileBase64, + IPluginLedgerConnectorFabricOptions, + PluginLedgerConnectorFabric, + DefaultApi as FabricApi, + FabricSigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import path from "path"; +import { + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + ReceiptType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import Web3 from "web3"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; +import { makeSessionDataChecks } from "../make-checks"; +import { + sendTransferCommenceRequest, + checkValidTransferCommenceResponse, +} from "../../../main/typescript/gateway/client/transfer-commence"; +import { + sendTransferInitializationRequest, + checkValidInitializationResponse, +} from "../../../main/typescript/gateway/client/transfer-initialization"; +import { + checkValidtransferCommenceRequest, + sendTransferCommenceResponse, +} from "../../../main/typescript/gateway/server/transfer-commence"; +import { + checkValidInitializationRequest, + sendTransferInitializationResponse, +} from "../../../main/typescript/gateway/server/transfer-initialization"; +import { + isFabricAssetLocked, + fabricAssetExists, + besuAssetExists, +} from "../make-checks-ledgers"; +/** + * Use this to debug issues with the fabric node SDK + * ```sh + * export HFC_LOGGING='{"debug":"console","info":"console"}' + * ``` + */ +let ipfsApiHost: string; + +let fabricSigningCredential: FabricSigningCredential; +const logLevel: LogLevelDesc = "TRACE"; + +let ipfsServer: Server; +let sourceGatewayServer: Server; +let recipientGatewayServer: Server; +let besuServer: Server; +let fabricServer: Server; + +let ipfsContainer: GoIpfsTestContainer; + +let fabricLedger: FabricTestLedgerV1; +let fabricContractName: string; +let fabricChannelName: string; +let fabricPath: string; + +let besuTestLedger: BesuTestLedger; +let besuPath: string; +let besuContractName: string; +let besuWeb3SigningCredential: Web3SigningCredential; +let besuKeychainId: string; + +let fabricConnector: PluginLedgerConnectorFabric; +let besuConnector: PluginLedgerConnectorBesu; + +let odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let odapClientGatewayApiHost: string; +let odapServerGatewayApiHost: string; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const FABRIC_ASSET_ID = uuidv4(); +const BESU_ASSET_ID = uuidv4(); + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "odapTestWithLedgerConnectors", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + // IPFS configuration + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + } + + { + // Fabric ledger connection + const channelId = "mychannel"; + fabricChannelName = channelId; + + fabricLedger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), + logLevel, + }); + + await fabricLedger.start(); + + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); + expect(connectionProfile).not.toBeUndefined(); + + const enrollAdminOut = await fabricLedger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await fabricLedger.enrollUser(adminWallet); + const sshConfig = await fabricLedger.getSshConfig(); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user2"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + // This is the directory structure of the Fabirc 2.x CLI container (fabric-tools image) + // const orgCfgDir = "/fabric-samples/test-network/organizations/"; + const orgCfgDir = + "/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations/"; + + // these below mirror how the fabric-samples sets up the configuration + const org1Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org1MSP", + + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt`, + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp`, + CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + // these below mirror how the fabric-samples sets up the configuration + const org2Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org2MSP", + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + CORE_PEER_ADDRESS: "peer0.org2.example.com:9051", + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp`, + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt`, + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: org1Env, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + fabricServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3000, + server: fabricServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressApp); + + const apiUrl = `http://${address}:${port}`; + fabricPath = apiUrl; + const config = new Configuration({ basePath: apiUrl }); + + const apiClient = new FabricApi(config); + + fabricContractName = "basic-asset-transfer-2"; + const contractRelPath = + "../fabric-contracts/lock-asset/chaincode-typescript"; + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ └── index.ts + // ├── tsconfig.json + const sourceFiles: FileBase64[] = []; + { + const filename = "./tsconfig.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./package.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./index.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./asset.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./assetTransfer.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + + const response = await apiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: fabricContractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { packageIds, lifecycle, success } = response.data; + expect(response.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); + + const { + approveForMyOrgList, + installList, + queryInstalledList, + commit, + packaging, + queryCommitted, + } = lifecycle; + + Checks.truthy(packageIds, `packageIds truthy OK`); + Checks.truthy( + Array.isArray(packageIds), + `Array.isArray(packageIds) truthy OK`, + ); + Checks.truthy(approveForMyOrgList, `approveForMyOrgList truthy OK`); + Checks.truthy( + Array.isArray(approveForMyOrgList), + `Array.isArray(approveForMyOrgList) truthy OK`, + ); + Checks.truthy(installList, `installList truthy OK`); + Checks.truthy( + Array.isArray(installList), + `Array.isArray(installList) truthy OK`, + ); + Checks.truthy(queryInstalledList, `queryInstalledList truthy OK`); + Checks.truthy( + Array.isArray(queryInstalledList), + `Array.isArray(queryInstalledList) truthy OK`, + ); + Checks.truthy(commit, `commit truthy OK`); + Checks.truthy(packaging, `packaging truthy OK`); + Checks.truthy(queryCommitted, `queryCommitted truthy OK`); + + // FIXME - without this wait it randomly fails with an error claiming that + // the endorsement was impossible to be obtained. The fabric-samples script + // does the same thing, it just waits 10 seconds for good measure so there + // might not be a way for us to avoid doing this, but if there is a way we + // absolutely should not have timeouts like this, anywhere... + await new Promise((resolve) => setTimeout(resolve, 10000)); + + fabricSigningCredential = { + keychainId, + keychainRef: keychainEntryKey, + }; + + const createResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [FABRIC_ASSET_ID, "19"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + + expect(createResponse).not.toBeUndefined(); + expect(createResponse.status).toBeGreaterThan(199); + expect(createResponse.status).toBeLessThan(300); + + log.info( + `BassicAssetTransfer.Create(): ${JSON.stringify(createResponse.data)}`, + ); + } + + { + // Besu ledger connection + besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/1.5.1/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + + 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( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + besuConnector = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + besuServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 4000, + server: besuServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await besuConnector.getOrCreateWebServices(); + const wsApi = new SocketIoServer(besuServer, { + path: Constants.SocketIoConnectionPathV1, + }); + await besuConnector.registerWebServices(expressApp, wsApi); + besuPath = `http://${address}:${port}`; + + await besuConnector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).not.toBeUndefined(); + expect(parseInt(balance, 10)).toBe(10e9); + + besuWeb3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }; + + const deployContractResponse = await besuConnector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: besuWeb3SigningCredential, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + + expect(deployContractResponse).not.toBeUndefined(); + expect(deployContractResponse.transactionReceipt).not.toBeUndefined(); + expect( + deployContractResponse.transactionReceipt.contractAddress, + ).not.toBeUndefined(); + + besuKeychainId = keychainPlugin.getKeychainId(); + besuContractName = LockAssetContractJson.contractName; + + const contractAddress: string = deployContractResponse.transactionReceipt + .contractAddress as string; + + expect(typeof contractAddress).toBe("string"); + } + { + // Gateways configuration + odapClientGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + fabricPath: fabricPath, + fabricSigningCredential: fabricSigningCredential, + fabricChannelName: fabricChannelName, + fabricContractName: fabricContractName, + fabricAssetID: FABRIC_ASSET_ID, + knexConfig: knexClientConnection, + }; + + odapServerGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + besuAssetID: BESU_ASSET_ID, + besuPath: besuPath, + besuWeb3SigningCredential: besuWeb3SigningCredential, + besuContractName: besuContractName, + besuKeychainId: besuKeychainId, + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + } + { + // Server Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 5000, + server: recipientGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapServerGatewayApiHost = `http://${address}:${port}`; + + await pluginRecipientGateway.getOrCreateWebServices(); + await pluginRecipientGateway.registerWebServices(expressApp); + } + { + // Client Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3001, + server: sourceGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapClientGatewayApiHost = `http://${address}:${port}`; + + await pluginSourceGateway.getOrCreateWebServices(); + await pluginSourceGateway.registerWebServices(expressApp); + } +}); + +test("client gateway crashes after lock fabric asset", async () => { + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const odapClientRequest: ClientV1Request = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + + const sessionID = pluginSourceGateway.configureOdapSession(odapClientRequest); + + const transferInitializationRequest = await sendTransferInitializationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferInitializationRequest == void 0) { + expect(false); + return; + } + + await checkValidInitializationRequest( + transferInitializationRequest, + pluginRecipientGateway, + ); + + const transferInitializationResponse = await sendTransferInitializationResponse( + transferInitializationRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferInitializationResponse == void 0) { + expect(false); + return; + } + + await checkValidInitializationResponse( + transferInitializationResponse, + pluginSourceGateway, + ); + + const transferCommenceRequest = await sendTransferCommenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferCommenceRequest == void 0) { + expect(false); + return; + } + + await checkValidtransferCommenceRequest( + transferCommenceRequest, + pluginRecipientGateway, + ); + + const transferCommenceResponse = await sendTransferCommenceResponse( + transferCommenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferCommenceResponse == void 0) { + expect(false); + return; + } + + await checkValidTransferCommenceResponse( + transferCommenceResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.lockFabricAsset(sessionID); + + // check if asset was successfully locked + expect( + await isFabricAssetLocked( + pluginSourceGateway, + fabricContractName, + fabricChannelName, + FABRIC_ASSET_ID, + fabricSigningCredential, + ), + ).toBe(true); + + // now we simulate the crash of the client gateway + pluginSourceGateway.database?.destroy(); + await Servers.shutdown(sourceGatewayServer); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3001, + server: sourceGatewayServer, + }; + + await Servers.listen(listenOptions); + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + await pluginSourceGateway.registerWebServices(expressApp); + + // client gateway self-healed and is back online + await pluginSourceGateway.recoverOpenSessions(true); + + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionID, + ); + + expect( + await fabricAssetExists( + pluginSourceGateway, + fabricContractName, + fabricChannelName, + FABRIC_ASSET_ID, + fabricSigningCredential, + ), + ).toBe(false); + + expect( + await besuAssetExists( + pluginRecipientGateway, + besuContractName, + besuKeychainId, + BESU_ASSET_ID, + besuWeb3SigningCredential, + ), + ).toBe(true); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await fabricLedger.stop(); + await fabricLedger.destroy(); + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); + + await Servers.shutdown(ipfsServer); + await Servers.shutdown(besuServer); + await Servers.shutdown(fabricServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayServer); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-transfer-initiation.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-transfer-initiation.test.ts new file mode 100644 index 0000000000..3499de93d0 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-transfer-initiation.test.ts @@ -0,0 +1,313 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express, { Express } from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { + AssetProfile, + ClientV1Request, +} from "../../../main/typescript/public-api"; +import { + checkValidInitializationResponse, + sendTransferInitializationRequest, +} from "../../../main/typescript/gateway/client/transfer-initialization"; +import { + checkValidInitializationRequest, + sendTransferInitializationResponse, +} from "../../../main/typescript/gateway/server/transfer-initialization"; +import { makeSessionDataChecks } from "../make-checks"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const logLevel: LogLevelDesc = "TRACE"; + +let odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsApiHost: string; +let ipfsServer: Server; + +let sourceGatewayServer: Server; +let recipientGatewayserver: Server; + +let odapServerGatewayApiHost: string; +let odapClientGatewayApiHost: string; + +let odapClientRequest: ClientV1Request; + +let serverExpressApp: Express; +let serverListenOptions: IListenOptions; + +let clientExpressApp: Express; +let clientListenOptions: IListenOptions; + +beforeAll(async () => { + { + // Define IPFS connection + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const ipfsApi = new ObjectStoreIpfsApi(config); + + expect(ipfsApi).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + } + { + // Server Gateway configuration + odapServerGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexServerConnection, + }; + + serverExpressApp = express(); + serverExpressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayserver = http.createServer(serverExpressApp); + serverListenOptions = { + hostname: "localhost", + port: 3000, + server: recipientGatewayserver, + }; + + const addressInfo = (await Servers.listen( + serverListenOptions, + )) as AddressInfo; + + const { address, port } = addressInfo; + odapServerGatewayApiHost = `http://${address}:${port}`; + + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if (pluginRecipientGateway.database == undefined) { + throw new Error("Database is not correctly initialized"); + } + + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + + await pluginRecipientGateway.registerWebServices(serverExpressApp); + } + { + // Client Gateway configuration + odapClientGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexClientConnection, + }; + + clientExpressApp = express(); + clientExpressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(clientExpressApp); + clientListenOptions = { + hostname: "localhost", + port: 2000, + server: sourceGatewayServer, + }; + + const addressInfo = (await Servers.listen( + clientListenOptions, + )) as AddressInfo; + + const { address, port } = addressInfo; + odapClientGatewayApiHost = `http://${address}:${port}`; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + + if (pluginSourceGateway.database == undefined) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + + await pluginSourceGateway.registerWebServices(clientExpressApp); + + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + odapClientRequest = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + } +}); + +beforeEach(async () => { + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); + +test("successful run ODAP after client gateway crashed after after receiving transfer initiation response", async () => { + const sessionID = pluginSourceGateway.configureOdapSession(odapClientRequest); + + const transferInitializationRequest = await sendTransferInitializationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferInitializationRequest == void 0) { + expect(false); + return; + } + + await checkValidInitializationRequest( + transferInitializationRequest, + pluginRecipientGateway, + ); + + const transferInitializationResponse = await sendTransferInitializationResponse( + transferInitializationRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferInitializationResponse == void 0) { + expect(false); + return; + } + + await checkValidInitializationResponse( + transferInitializationResponse, + pluginSourceGateway, + ); + + // now we simulate the crash of the client gateway + pluginSourceGateway.database?.destroy(); + await Servers.shutdown(sourceGatewayServer); + + clientExpressApp = express(); + clientExpressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(clientExpressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 2000, + server: sourceGatewayServer, + }; + + await Servers.listen(listenOptions); + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + await pluginSourceGateway.registerWebServices(clientExpressApp); + + // client gateway self-healed and is back online + await pluginSourceGateway.recoverOpenSessions(true); + + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionID, + ); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayserver); + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call-with-ledger-connector.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call-with-ledger-connector.test.ts index c0401b0ebc..787635caca 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call-with-ledger-connector.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call-with-ledger-connector.test.ts @@ -1,11 +1,9 @@ -import http from "http"; import fs from "fs-extra"; +import "jest-extended"; +import http, { Server } from "http"; import { Server as SocketIoServer } from "socket.io"; import { AddressInfo } from "net"; -import secp256k1 from "secp256k1"; -import test, { Test } from "tape-promise/tape"; import { v4 as uuidv4 } from "uuid"; -import { randomBytes } from "crypto"; import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; import { create } from "ipfs-http-client"; import bodyParser from "body-parser"; @@ -20,7 +18,6 @@ import { Servers, } from "@hyperledger/cactus-common"; import { DiscoveryOptions } from "fabric-network"; - import { Containers, FabricTestLedgerV1, @@ -63,6 +60,9 @@ import { Web3SigningCredential, } from "@hyperledger/cactus-plugin-ledger-connector-besu"; import Web3 from "web3"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; +import { makeSessionDataChecks } from "../make-checks"; +import { besuAssetExists, fabricAssetExists } from "../make-checks-ledgers"; /** * Use this to debug issues with the fabric node SDK * ```sh @@ -70,34 +70,107 @@ import Web3 from "web3"; * ``` */ let ipfsApiHost: string; -const testCase = "runs odap gateway tests via openApi"; + let fabricSigningCredential: FabricSigningCredential; const logLevel: LogLevelDesc = "TRACE"; + +let ipfsServer: Server; +let sourceGatewayServer: Server; +let recipientGatewayServer: Server; +let besuServer: Server; +let fabricServer: Server; + +let ipfsContainer: GoIpfsTestContainer; + let fabricLedger: FabricTestLedgerV1; let fabricContractName: string; let fabricChannelName: string; let fabricPath: string; -let fabricAssetID: string; -let ipfsContainer: GoIpfsTestContainer; + let besuTestLedger: BesuTestLedger; let besuPath: string; let besuContractName: string; let besuWeb3SigningCredential: Web3SigningCredential; let besuKeychainId: string; -const level = "INFO"; -const label = "fabric run transaction test"; -const log = LoggerProvider.getOrCreate({ level, label }); -log.info("setting up containers"); -test("BEFORE " + testCase, async (t: Test) => { - const pruning = pruneDockerAllIfGithubAction({ logLevel }); - await t.doesNotReject(pruning, "Pruning didn't throw OK"); - test.onFailure(async () => { - await Containers.logDiagnostics({ logLevel }); - }); + +let fabricConnector: PluginLedgerConnectorFabric; +let besuConnector: PluginLedgerConnectorBesu; +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let odapClientGatewayApiHost: string; +let odapServerGatewayApiHost: string; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const FABRIC_ASSET_ID = uuidv4(); +const BESU_ASSET_ID = uuidv4(); + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "odapTestWithLedgerConnectors", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + { + // IPFS configuration + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + } + + { + // Fabric ledger connection const channelId = "mychannel"; - const channelName = channelId; - fabricChannelName = channelName; + fabricChannelName = channelId; + fabricLedger = new FabricTestLedgerV1({ emitContainerLogs: true, publishAllPorts: true, @@ -105,9 +178,12 @@ test("BEFORE " + testCase, async (t: Test) => { envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), logLevel, }); + await fabricLedger.start(); + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); - t.ok(connectionProfile, "getConnectionProfileOrg1() out truthy OK"); + expect(connectionProfile).not.toBeUndefined(); + const enrollAdminOut = await fabricLedger.enrollAdmin(); const adminWallet = enrollAdminOut[1]; const [userIdentity] = await fabricLedger.enrollUser(adminWallet); @@ -188,30 +264,30 @@ test("BEFORE " + testCase, async (t: Test) => { commitTimeout: 300, }, }; - const plugin = new PluginLedgerConnectorFabric(pluginOptions); + + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); const expressApp = express(); expressApp.use(bodyParser.json({ limit: "250mb" })); - const server = http.createServer(expressApp); + fabricServer = http.createServer(expressApp); const listenOptions: IListenOptions = { hostname: "localhost", - port: 0, - server, + port: 3000, + server: fabricServer, }; const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - const { port } = addressInfo; - test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressApp); - await plugin.getOrCreateWebServices(); - await plugin.registerWebServices(expressApp); - const apiUrl = `http://localhost:${port}`; + const apiUrl = `http://${address}:${port}`; fabricPath = apiUrl; const config = new Configuration({ basePath: apiUrl }); const apiClient = new FabricApi(config); - const contractName = "basic-asset-transfer-2"; - fabricContractName = contractName; + fabricContractName = "basic-asset-transfer-2"; const contractRelPath = "../fabric-contracts/lock-asset/chaincode-typescript"; const contractDir = path.join(__dirname, contractRelPath); @@ -279,12 +355,11 @@ test("BEFORE " + testCase, async (t: Test) => { }); } - const res = await apiClient.deployContractV1({ + const response = await apiClient.deployContractV1({ channelId, ccVersion: "1.0.0", - // constructorArgs: { Args: ["john", "99"] }, sourceFiles, - ccName: contractName, + ccName: fabricContractName, targetOrganizations: [org1Env, org2Env], caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, ccLabel: "basic-asset-transfer-2", @@ -295,9 +370,10 @@ test("BEFORE " + testCase, async (t: Test) => { connTimeout: 60, }); - const { packageIds, lifecycle, success } = res.data; - t.equal(res.status, 200, "res.status === 200 OK"); - t.true(success, "res.data.success === true"); + const { packageIds, lifecycle, success } = response.data; + expect(response.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); const { approveForMyOrgList, @@ -339,78 +415,30 @@ test("BEFORE " + testCase, async (t: Test) => { // absolutely should not have timeouts like this, anywhere... await new Promise((resolve) => setTimeout(resolve, 10000)); - const assetId = uuidv4(); fabricSigningCredential = { keychainId, keychainRef: keychainEntryKey, }; - fabricAssetID = assetId; - const createRes = await apiClient.runTransactionV1({ - contractName, - channelName, - params: [assetId, "19"], + + const createResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [FABRIC_ASSET_ID, "19"], methodName: "CreateAsset", invocationType: FabricContractInvocationType.Send, signingCredential: fabricSigningCredential, }); - t.ok(createRes, "setRes truthy OK"); - t.true(createRes.status > 199, "createRes status > 199 OK"); - t.true(createRes.status < 300, "createRes status < 300 OK"); - t.comment( - `BassicAssetTransfer.Create(): ${JSON.stringify(createRes.data)}`, - ); - } - ipfsContainer = new GoIpfsTestContainer({ logLevel }); - t.ok(ipfsContainer, "GoIpfsTestContainer instance truthy OK"); - { - const container = await ipfsContainer.start(); - t.ok(container, "Container returned by start() truthy OK"); - t.ok(container, "Started GoIpfsTestContainer OK"); - 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}`; - ipfsApiHost = apiHost; - const config = new Configuration({ basePath: apiHost }); - const apiClient = new ObjectStoreIpfsApi(config); - t.ok(apiClient, "ObjectStoreIpfsApi truthy OK"); + expect(createResponse).not.toBeUndefined(); + expect(createResponse.status).toBeGreaterThan(199); + expect(createResponse.status).toBeLessThan(300); - const ipfsApiUrl = await ipfsContainer.getApiUrl(); - const ipfsGatewayUrl = await ipfsContainer.getWebGatewayUrl(); - t.comment(`Go IPFS Test Container API URL: ${ipfsApiUrl}`); - t.comment(`Go IPFS Test Container Gateway URL: ${ipfsGatewayUrl}`); - - const ipfsClientOrOptions = create({ - url: ipfsApiUrl, - }); - const instanceId = uuidv4(); - const plugin = new PluginObjectStoreIpfs({ - parentDir: `/${uuidv4()}/${uuidv4()}/`, - logLevel, - instanceId, - ipfsClientOrOptions, - }); - - await plugin.getOrCreateWebServices(); - await plugin.registerWebServices(expressApp); - - const packageName = plugin.getPackageName(); - t.ok(packageName, "packageName truthy OK"); - - const theInstanceId = plugin.getInstanceId(); - t.ok(theInstanceId, "theInstanceId truthy OK"); - t.equal(theInstanceId, instanceId, "instanceId === theInstanceId OK"); + log.info( + `BassicAssetTransfer.Create(): ${JSON.stringify(createResponse.data)}`, + ); } { + // Besu ledger connection besuTestLedger = new BesuTestLedger(); await besuTestLedger.start(); @@ -445,37 +473,38 @@ test("BEFORE " + testCase, async (t: Test) => { LockAssetContractJson.contractName, JSON.stringify(LockAssetContractJson), ); + const factory = new PluginFactoryLedgerConnector({ pluginImportType: PluginImportType.Local, }); - const connector: PluginLedgerConnectorBesu = await factory.create({ + + besuConnector = await factory.create({ rpcApiHttpHost, rpcApiWsHost, instanceId: uuidv4(), pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), }); - await connector.onPluginInit(); + const expressApp = express(); expressApp.use(bodyParser.json({ limit: "250mb" })); - const server = http.createServer(expressApp); + besuServer = http.createServer(expressApp); const listenOptions: IListenOptions = { hostname: "localhost", - port: 0, - server, + port: 4000, + server: besuServer, }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - const { port } = addressInfo; - test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; - await connector.getOrCreateWebServices(); - const wsApi = new SocketIoServer(server, { + await besuConnector.getOrCreateWebServices(); + const wsApi = new SocketIoServer(besuServer, { path: Constants.SocketIoConnectionPathV1, }); - await connector.registerWebServices(expressApp, wsApi); - const apiUrl = `http://localhost:${port}`; - // eslint-disable-next-line prefer-const - besuPath = apiUrl; - await connector.transact({ + await besuConnector.registerWebServices(expressApp, wsApi); + besuPath = `http://${address}:${port}`; + + await besuConnector.transact({ web3SigningCredential: { ethAccount: firstHighNetWorthAccount, secret: besuKeyPair.privateKey, @@ -494,15 +523,16 @@ test("BEFORE " + testCase, async (t: Test) => { }); 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"); - // eslint-disable-next-line prefer-const + expect(balance).not.toBeUndefined(); + expect(parseInt(balance, 10)).toBe(10e9); + besuWeb3SigningCredential = { ethAccount: firstHighNetWorthAccount, secret: besuKeyPair.privateKey, type: Web3SigningCredentialType.PrivateKeyHex, }; - const deployOut = await connector.deployContract({ + + const deployContractResponse = await besuConnector.deployContract({ keychainId: keychainPlugin.getKeychainId(), contractName: LockAssetContractJson.contractName, contractAbi: LockAssetContractJson.abi, @@ -511,145 +541,204 @@ test("BEFORE " + testCase, async (t: Test) => { bytecode: LockAssetContractJson.bytecode, gas: 1000000, }); + + expect(deployContractResponse).not.toBeUndefined(); + expect(deployContractResponse.transactionReceipt).not.toBeUndefined(); + expect( + deployContractResponse.transactionReceipt.contractAddress, + ).not.toBeUndefined(); + besuKeychainId = keychainPlugin.getKeychainId(); besuContractName = LockAssetContractJson.contractName; - t.ok(deployOut, "deployContract() output is truthy OK"); - t.ok( - deployOut.transactionReceipt, - "deployContract() output.transactionReceipt is truthy OK", - ); - t.ok( - deployOut.transactionReceipt.contractAddress, - "deployContract() output.transactionReceipt.contractAddress is truthy OK", - ); - const contractAddress: string = deployOut.transactionReceipt + const contractAddress: string = deployContractResponse.transactionReceipt .contractAddress as string; - t.ok( - typeof contractAddress === "string", - "contractAddress typeof string OK", - ); - t.end(); + + expect(typeof contractAddress).toBe("string"); } -}); -test(testCase, async (t: Test) => { - //const logLevel: LogLevelDesc = "TRACE"; - test.onFinish(async () => { - await ipfsContainer.stop(); - await ipfsContainer.destroy(); - await besuTestLedger.stop(); - await besuTestLedger.destroy(); - }); - const tearDown = async () => { - await fabricLedger.stop(); - await fabricLedger.destroy(); - await pruneDockerAllIfGithubAction({ logLevel }); - }; + { + // Gateways configuration + const odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + fabricPath: fabricPath, + fabricSigningCredential: fabricSigningCredential, + fabricChannelName: fabricChannelName, + fabricContractName: fabricContractName, + fabricAssetID: FABRIC_ASSET_ID, + knexConfig: knexClientConnection, + }; - test.onFinish(tearDown); - const odapClientGatewayPluginID = uuidv4(); - const odapPluginOptions: IPluginOdapGatewayConstructorOptions = { - name: "cactus-plugin#odapGateway", - dltIDs: ["dummy"], - instanceId: odapClientGatewayPluginID, - ipfsPath: ipfsApiHost, - fabricPath: fabricPath, - fabricSigningCredential: fabricSigningCredential, - fabricChannelName: fabricChannelName, - fabricContractName: fabricContractName, - fabricAssetID: fabricAssetID, - }; - const clientOdapGateway = new PluginOdapGateway(odapPluginOptions); + const odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + besuAssetID: BESU_ASSET_ID, + besuPath: besuPath, + besuWeb3SigningCredential: besuWeb3SigningCredential, + besuContractName: besuContractName, + besuKeychainId: besuKeychainId, + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } - const odapServerGatewayInstanceID = uuidv4(); - // the block below adds the server odap gateway to the plugin registry - let odapServerGatewayPubKey: string; - let odapServerGatewayApiHost: string; + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + } { + // Server Gateway configuration const expressApp = express(); expressApp.use(bodyParser.json({ limit: "250mb" })); - const server = http.createServer(expressApp); + recipientGatewayServer = http.createServer(expressApp); const listenOptions: IListenOptions = { hostname: "localhost", - port: 0, - server, + port: 5000, + server: recipientGatewayServer, }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; odapServerGatewayApiHost = `http://${address}:${port}`; - const odapPluginOptions: IPluginOdapGatewayConstructorOptions = { - name: "cactus-plugin#odapGateway", - dltIDs: ["dummy"], - instanceId: odapServerGatewayInstanceID, - ipfsPath: ipfsApiHost, - besuAssetID: "whatever", - besuPath: besuPath, - besuWeb3SigningCredential: besuWeb3SigningCredential, - besuContractName: besuContractName, - besuKeychainId: besuKeychainId, - }; - const plugin = new PluginOdapGateway(odapPluginOptions); - odapServerGatewayPubKey = plugin.pubKey; - await plugin.getOrCreateWebServices(); - await plugin.registerWebServices(expressApp); + await pluginRecipientGateway.getOrCreateWebServices(); + await pluginRecipientGateway.registerWebServices(expressApp); } { + // Client Gateway configuration const expressApp = express(); expressApp.use(bodyParser.json({ limit: "250mb" })); - const server = http.createServer(expressApp); + sourceGatewayServer = http.createServer(expressApp); const listenOptions: IListenOptions = { hostname: "localhost", - port: 0, - server, + port: 3001, + server: sourceGatewayServer, }; + 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 OdapApi(apiConfig); - await clientOdapGateway.getOrCreateWebServices(); - await clientOdapGateway.registerWebServices(expressApp); - let dummyPrivKeyBytes = randomBytes(32); - while (!secp256k1.privateKeyVerify(dummyPrivKeyBytes)) { - dummyPrivKeyBytes = randomBytes(32); - } - const dummyPubKeyBytes = secp256k1.publicKeyCreate(dummyPrivKeyBytes); - const dummyPubKey = clientOdapGateway.bufArray2HexStr(dummyPubKeyBytes); - const expiryDate = new Date("23/25/2060").toString(); - const assetProfile: AssetProfile = { expirationDate: expiryDate }; - const odapClientRequest: ClientV1Request = { - clientGatewayConfiguration: { - apiHost: apiHost, - }, - serverGatewayConfiguration: { - apiHost: odapServerGatewayApiHost, - }, - version: "0.0.0", - loggingProfile: "dummy", - accessControlProfile: "dummy", - applicationProfile: "dummy", - payloadProfile: { - assetProfile: assetProfile, - capabilities: "", - }, - assetProfile: assetProfile, - assetControlProfile: "dummy", - beneficiaryPubkey: dummyPubKey, - clientDltSystem: "dummy", - clientIdentityPubkey: clientOdapGateway.pubKey, - originatorPubkey: dummyPubKey, - recipientGatewayDltSystem: "dummy", - recipientGatewayPubkey: odapServerGatewayPubKey, - serverDltSystem: "dummy", - serverIdentityPubkey: dummyPubKey, - sourceGatewayDltSystem: "dummy", - }; - const res = await apiClient.clientRequestV1(odapClientRequest); - t.ok(res); + odapClientGatewayApiHost = `http://${address}:${port}`; + + await pluginSourceGateway.getOrCreateWebServices(); + await pluginSourceGateway.registerWebServices(expressApp); } - t.end(); +}); + +test("runs ODAP between two gateways via openApi", async () => { + const odapApiConfig = new Configuration({ + basePath: odapClientGatewayApiHost, + }); + const apiClient = new OdapApi(odapApiConfig); + + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const odapClientRequest: ClientV1Request = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + + const res = await apiClient.clientRequestV1(odapClientRequest); + expect(res.status).toBe(200); + + expect(pluginSourceGateway.sessions.size).toBe(1); + expect(pluginRecipientGateway.sessions.size).toBe(1); + + const [sessionID] = pluginSourceGateway.sessions.keys(); + + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionID, + ); + + expect( + await fabricAssetExists( + pluginSourceGateway, + fabricContractName, + fabricChannelName, + FABRIC_ASSET_ID, + fabricSigningCredential, + ), + ).toBe(false); + + expect( + await besuAssetExists( + pluginRecipientGateway, + besuContractName, + besuKeychainId, + BESU_ASSET_ID, + besuWeb3SigningCredential, + ), + ).toBe(true); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await fabricLedger.stop(); + await fabricLedger.destroy(); + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); + + await Servers.shutdown(ipfsServer); + await Servers.shutdown(besuServer); + await Servers.shutdown(fabricServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayServer); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); }); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call.test.ts index f780bec207..c309f61f96 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call.test.ts @@ -26,351 +26,200 @@ import { AssetProfile, ClientV1Request, } from "../../../main/typescript/public-api"; +import { makeSessionDataChecks } from "../make-checks"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; -/** - * Use this to debug issues with the fabric node SDK - * ```sh - * export HFC_LOGGING='{"debug":"console","info":"console"}' - * ``` - */ -const testCase = "Run ODAP through OpenAPI"; +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; -describe(testCase, () => { - let ipfsApiHost: string; - let ipfsContainer: GoIpfsTestContainer; +let ipfsApiHost: string; +let ipfsContainer: GoIpfsTestContainer; - const logLevel: LogLevelDesc = "TRACE"; +const logLevel: LogLevelDesc = "TRACE"; - let sourceGatewayServer: Server; - let recipientGatewayserver: Server; - let ipfsServer: Server; +let sourceGatewayServer: Server; +let recipientGatewayserver: Server; +let ipfsServer: Server; - let clientOdapGateway: PluginOdapGateway; - let serverOdapGateway: PluginOdapGateway; +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; - beforeAll(async () => { - ipfsContainer = new GoIpfsTestContainer({ logLevel }); - expect(ipfsContainer).not.toBeUndefined(); +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); - const container = await ipfsContainer.start(); - expect(container).not.toBeUndefined(); + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); - const expressApp = express(); - expressApp.use(bodyParser.json({ limit: "250mb" })); - ipfsServer = http.createServer(expressApp); - const listenOptions: IListenOptions = { - hostname: "localhost", - port: 0, - server: ipfsServer, - }; - - const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - const { address, port } = addressInfo; - ipfsApiHost = `http://${address}:${port}`; - - const config = new Configuration({ basePath: ipfsApiHost }); - const apiClient = new ObjectStoreIpfsApi(config); + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; - expect(apiClient).not.toBeUndefined(); + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; - const ipfsApiUrl = await ipfsContainer.getApiUrl(); - // t.comment(`Go IPFS Test Container API URL: ${ipfsApiUrl}`); + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); - const ipfsClientOrOptions = create({ - url: ipfsApiUrl, - }); - - const instanceId = uuidv4(); - const pluginIpfs = new PluginObjectStoreIpfs({ - parentDir: `/${uuidv4()}/${uuidv4()}/`, - logLevel, - instanceId, - ipfsClientOrOptions, - }); + expect(apiClient).not.toBeUndefined(); - await pluginIpfs.getOrCreateWebServices(); - await pluginIpfs.registerWebServices(expressApp); + const ipfsApiUrl = await ipfsContainer.getApiUrl(); - //const packageName = pluginIpfs.getPackageName(); - - const receivedInstanceId = pluginIpfs.getInstanceId(); - - expect(receivedInstanceId).toBe(instanceId); + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, }); - test("runs ODAP between two gateways via openApi", async () => { - const odapClientGatewayPluginID = uuidv4(); - const odapServerGatewayInstanceID = uuidv4(); + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); - const odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions = { - name: "cactus-plugin#odapGateway", - dltIDs: ["DLT2"], - instanceId: odapClientGatewayPluginID, - ipfsPath: ipfsApiHost, - }; + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); - const odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions = { - name: "cactus-plugin#odapGateway", - dltIDs: ["DLT1"], - instanceId: odapServerGatewayInstanceID, - ipfsPath: ipfsApiHost, +test("runs ODAP between two gateways via openApi", async () => { + const odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + knexConfig: knexClientConnection, + }; + + const odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + + let odapServerGatewayApiHost: string; + + { + // Server Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayserver = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3000, + server: recipientGatewayserver, }; - clientOdapGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); - serverOdapGateway = new PluginOdapGateway(odapServerGatewayPluginOptions); - - let odapServerGatewayApiHost: string; - - { - // Server Gateway configuration - const expressApp = express(); - expressApp.use(bodyParser.json({ limit: "250mb" })); - recipientGatewayserver = http.createServer(expressApp); - const listenOptions: IListenOptions = { - hostname: "localhost", - port: 3000, - server: recipientGatewayserver, - }; - - const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - - const { address, port } = addressInfo; - odapServerGatewayApiHost = `http://${address}:${port}`; - - await serverOdapGateway.getOrCreateWebServices(); - await serverOdapGateway.registerWebServices(expressApp); - } - { - // Client Gateway configuration - const expressApp = express(); - expressApp.use(bodyParser.json({ limit: "250mb" })); - sourceGatewayServer = http.createServer(expressApp); - const listenOptions: IListenOptions = { - hostname: "localhost", - port: 2000, - server: sourceGatewayServer, - }; - - const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - - const { address, port } = addressInfo; - const odapClientGatewayApiHost = `http://${address}:${port}`; - - await clientOdapGateway.getOrCreateWebServices(); - await clientOdapGateway.registerWebServices(expressApp); - - const odapApiConfig = new Configuration({ - basePath: odapClientGatewayApiHost, - }); - const apiClient = new OdapApi(odapApiConfig); - - const expiryDate = new Date(2060, 11, 24).toString(); - const assetProfile: AssetProfile = { expirationDate: expiryDate }; - - const odapClientRequest: ClientV1Request = { - clientGatewayConfiguration: { - apiHost: odapClientGatewayApiHost, - }, - serverGatewayConfiguration: { - apiHost: odapServerGatewayApiHost, - }, - version: "0.0.0", - loggingProfile: "dummyLoggingProfile", - accessControlProfile: "dummyAccessControlProfile", - applicationProfile: "dummyApplicationProfile", - payloadProfile: { - assetProfile: assetProfile, - capabilities: "", - }, - assetProfile: assetProfile, - assetControlProfile: "dummyAssetControlProfile", - beneficiaryPubkey: "dummyPubKey", - clientDltSystem: "DLT1", - originatorPubkey: "dummyPubKey", - recipientGatewayDltSystem: "DLT2", - recipientGatewayPubkey: serverOdapGateway.pubKey, - serverDltSystem: "DLT2", - sourceGatewayDltSystem: "DLT1", - clientIdentityPubkey: "", - serverIdentityPubkey: "", - }; - const res = await apiClient.clientRequestV1(odapClientRequest); - expect(res.status).toBe(200); - } - - expect(clientOdapGateway.sessions.size).toBe(1); - expect(serverOdapGateway.sessions.size).toBe(1); - - const [sessionId] = clientOdapGateway.sessions.keys(); - - const clientSessionData = clientOdapGateway.sessions.get(sessionId); - const serverSessionData = serverOdapGateway.sessions.get(sessionId); - - if (clientSessionData == undefined || serverSessionData == undefined) { - throw new Error("Test Failed"); - } - - expect(clientSessionData.id).toBe(serverSessionData.id); - expect(clientSessionData.id).toBe(sessionId); - - expect(clientSessionData.loggingProfile).toBe( - serverSessionData.loggingProfile, - ); - - expect(clientSessionData.accessControlProfile).toBe( - serverSessionData.accessControlProfile, - ); - - expect(clientSessionData.applicationProfile).toBe( - serverSessionData.applicationProfile, - ); - - expect(JSON.stringify(clientSessionData.assetProfile)).toBe( - JSON.stringify(serverSessionData.assetProfile), - ); - - expect(clientSessionData.originatorPubkey).toBe( - serverSessionData.originatorPubkey, - ); - - expect(clientSessionData.beneficiaryPubkey).toBe( - serverSessionData.beneficiaryPubkey, - ); - - expect(clientSessionData.sourceGatewayPubkey).toBe( - serverSessionData.sourceGatewayPubkey, - ); - - expect(clientSessionData.sourceGatewayDltSystem).toBe( - serverSessionData.sourceGatewayDltSystem, - ); - - expect(clientSessionData.recipientGatewayPubkey).toBe( - serverSessionData.recipientGatewayPubkey, - ); - - expect(clientSessionData.recipientGatewayDltSystem).toBe( - serverSessionData.recipientGatewayDltSystem, - ); - - expect(clientSessionData.initializationRequestMessageHash).toBe( - serverSessionData.initializationRequestMessageHash, - ); - - expect(clientSessionData.initializationResponseMessageHash).toBe( - serverSessionData.initializationResponseMessageHash, - ); - - expect(clientSessionData.clientSignatureInitializationRequestMessage).toBe( - serverSessionData.clientSignatureInitializationRequestMessage, - ); - - expect(clientSessionData.serverSignatureInitializationResponseMessage).toBe( - serverSessionData.serverSignatureInitializationResponseMessage, - ); - - expect(clientSessionData.transferCommenceMessageRequestHash).toBe( - serverSessionData.transferCommenceMessageRequestHash, - ); - - expect(clientSessionData.transferCommenceMessageResponseHash).toBe( - serverSessionData.transferCommenceMessageResponseHash, - ); - - expect( - clientSessionData.clientSignatureTransferCommenceRequestMessage, - ).toBe(serverSessionData.clientSignatureTransferCommenceRequestMessage); - - expect( - clientSessionData.serverSignatureTransferCommenceResponseMessage, - ).toBe(serverSessionData.serverSignatureTransferCommenceResponseMessage); - - expect(clientSessionData.lockEvidenceRequestMessageHash).toBe( - serverSessionData.lockEvidenceRequestMessageHash, - ); - - expect(clientSessionData.lockEvidenceResponseMessageHash).toBe( - serverSessionData.lockEvidenceResponseMessageHash, - ); - - expect(clientSessionData.clientSignatureLockEvidenceRequestMessage).toBe( - serverSessionData.clientSignatureLockEvidenceRequestMessage, - ); - - expect(clientSessionData.serverSignatureLockEvidenceResponseMessage).toBe( - serverSessionData.serverSignatureLockEvidenceResponseMessage, - ); - - expect(clientSessionData.lockEvidenceClaim).toBe( - serverSessionData.lockEvidenceClaim, - ); - - expect(clientSessionData.commitPrepareRequestMessageHash).toBe( - serverSessionData.commitPrepareRequestMessageHash, - ); - - expect(clientSessionData.commitPrepareResponseMessageHash).toBe( - serverSessionData.commitPrepareResponseMessageHash, - ); - - expect( - clientSessionData.clientSignatureCommitPreparationRequestMessage, - ).toBe(serverSessionData.clientSignatureCommitPreparationRequestMessage); - - expect( - clientSessionData.serverSignatureCommitPreparationResponseMessage, - ).toBe(serverSessionData.serverSignatureCommitPreparationResponseMessage); - - expect(clientSessionData.commitFinalRequestMessageHash).toBe( - serverSessionData.commitFinalRequestMessageHash, - ); - - expect(clientSessionData.commitPrepareRequestMessageHash).toBe( - serverSessionData.commitPrepareRequestMessageHash, - ); + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - expect(clientSessionData.commitFinalResponseMessageHash).toBe( - serverSessionData.commitFinalResponseMessageHash, - ); + const { address, port } = addressInfo; + odapServerGatewayApiHost = `http://${address}:${port}`; - expect(clientSessionData.commitFinalClaim).toBe( - serverSessionData.commitFinalClaim, - ); + await pluginRecipientGateway.getOrCreateWebServices(); + await pluginRecipientGateway.registerWebServices(expressApp); + } + { + // Client Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 2000, + server: sourceGatewayServer, + }; - expect(clientSessionData.commitFinalClaimFormat).toBe( - serverSessionData.commitFinalClaimFormat, - ); + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - expect(clientSessionData.commitAcknowledgementClaim).toBe( - serverSessionData.commitAcknowledgementClaim, - ); + const { address, port } = addressInfo; + const odapClientGatewayApiHost = `http://${address}:${port}`; - expect(clientSessionData.commitAcknowledgementClaimFormat).toBe( - serverSessionData.commitAcknowledgementClaimFormat, - ); + await pluginSourceGateway.getOrCreateWebServices(); + await pluginSourceGateway.registerWebServices(expressApp); - expect(clientSessionData.clientSignatureCommitFinalRequestMessage).toBe( - serverSessionData.clientSignatureCommitFinalRequestMessage, - ); + const odapApiConfig = new Configuration({ + basePath: odapClientGatewayApiHost, + }); + const apiClient = new OdapApi(odapApiConfig); + + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const odapClientRequest: ClientV1Request = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + const res = await apiClient.clientRequestV1(odapClientRequest); + expect(res.status).toBe(200); + } - expect(clientSessionData.serverSignatureCommitFinalResponseMessage).toBe( - serverSessionData.serverSignatureCommitFinalResponseMessage, - ); + expect(pluginSourceGateway.sessions.size).toBe(1); + expect(pluginRecipientGateway.sessions.size).toBe(1); - expect(clientSessionData.transferCompleteMessageHash).toBe( - serverSessionData.transferCompleteMessageHash, - ); + const [sessionID] = pluginSourceGateway.sessions.keys(); - expect(clientSessionData.clientSignatureTransferCompleteMessage).toBe( - serverSessionData.clientSignatureTransferCompleteMessage, - ); - }); + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionID, + ); +}); - afterAll(async () => { - await ipfsContainer.stop(); - await ipfsContainer.destroy(); - await Servers.shutdown(ipfsServer); - await Servers.shutdown(sourceGatewayServer); - await Servers.shutdown(recipientGatewayserver); - }); +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayserver); + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); }); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-rollback.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-rollback.test.ts new file mode 100644 index 0000000000..2c6fedeec4 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-rollback.test.ts @@ -0,0 +1,947 @@ +import fs from "fs-extra"; +import "jest-extended"; +import http, { Server } from "http"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { AssetProfile } from "../../../main/typescript/generated/openapi/typescript-axios"; +import { + Checks, + IListenOptions, + LoggerProvider, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { DiscoveryOptions } from "fabric-network"; +import { + Containers, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, + GoIpfsTestContainer, + BesuTestLedger, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { ClientV1Request } from "../../../main/typescript/public-api"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + PluginImportType, + Constants, +} from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../main/typescript/gateway/plugin-odap-gateway"; +import { + ChainCodeProgrammingLanguage, + DefaultEventHandlerStrategy, + FabricContractInvocationType, + FileBase64, + IPluginLedgerConnectorFabricOptions, + PluginLedgerConnectorFabric, + DefaultApi as FabricApi, + FabricSigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import path from "path"; +import { + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + ReceiptType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import Web3 from "web3"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; +import { besuAssetExists, fabricAssetExists } from "../make-checks-ledgers"; +import { + sendLockEvidenceRequest, + checkValidLockEvidenceResponse, +} from "../../../main/typescript/gateway/client/lock-evidence"; +import { + sendTransferCommenceRequest, + checkValidTransferCommenceResponse, +} from "../../../main/typescript/gateway/client/transfer-commence"; +import { + sendTransferInitializationRequest, + checkValidInitializationResponse, +} from "../../../main/typescript/gateway/client/transfer-initialization"; +import { + checkValidLockEvidenceRequest, + sendLockEvidenceResponse, +} from "../../../main/typescript/gateway/server/lock-evidence"; +import { + checkValidtransferCommenceRequest, + sendTransferCommenceResponse, +} from "../../../main/typescript/gateway/server/transfer-commence"; +import { + checkValidInitializationRequest, + sendTransferInitializationResponse, +} from "../../../main/typescript/gateway/server/transfer-initialization"; +import { + sendCommitPreparationRequest, + checkValidCommitPreparationResponse, +} from "../../../main/typescript/gateway/client/commit-preparation"; +import { + checkValidCommitPreparationRequest, + sendCommitPreparationResponse, +} from "../../../main/typescript/gateway/server/commit-preparation"; +import { + checkValidCommitFinalRequest, + sendCommitFinalResponse, +} from "../../../main/typescript/gateway/server/commit-final"; +import { sendCommitFinalRequest } from "../../../main/typescript/gateway/client/commit-final"; + +/** + * Use this to debug issues with the fabric node SDK + * ```sh + * export HFC_LOGGING='{"debug":"console","info":"console"}' + * ``` + */ +let ipfsApiHost: string; + +let fabricSigningCredential: FabricSigningCredential; +const logLevel: LogLevelDesc = "TRACE"; + +let ipfsServer: Server; +let sourceGatewayServer: Server; +let recipientGatewayServer: Server; +let besuServer: Server; +let fabricServer: Server; + +let ipfsContainer: GoIpfsTestContainer; + +let fabricLedger: FabricTestLedgerV1; +let fabricContractName: string; +let fabricChannelName: string; +let fabricPath: string; + +let besuTestLedger: BesuTestLedger; +let besuPath: string; +let besuContractName: string; +let besuWeb3SigningCredential: Web3SigningCredential; +let besuKeychainId: string; + +let fabricConnector: PluginLedgerConnectorFabric; +let besuConnector: PluginLedgerConnectorBesu; +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let odapClientGatewayApiHost: string; +let odapServerGatewayApiHost: string; + +let odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const FABRIC_ASSET_ID = uuidv4(); +const BESU_ASSET_ID = uuidv4(); + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "odap-rollback-after-crash-test", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + // IPFS configuration + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + } + + { + // Fabric ledger connection + const channelId = "mychannel"; + fabricChannelName = channelId; + + fabricLedger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), + logLevel, + }); + + await fabricLedger.start(); + + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); + expect(connectionProfile).not.toBeUndefined(); + + const enrollAdminOut = await fabricLedger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await fabricLedger.enrollUser(adminWallet); + const sshConfig = await fabricLedger.getSshConfig(); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user2"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + // This is the directory structure of the Fabirc 2.x CLI container (fabric-tools image) + // const orgCfgDir = "/fabric-samples/test-network/organizations/"; + const orgCfgDir = + "/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations/"; + + // these below mirror how the fabric-samples sets up the configuration + const org1Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org1MSP", + + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt`, + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp`, + CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + // these below mirror how the fabric-samples sets up the configuration + const org2Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org2MSP", + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + CORE_PEER_ADDRESS: "peer0.org2.example.com:9051", + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp`, + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt`, + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: org1Env, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + fabricServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3000, + server: fabricServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressApp); + + const apiUrl = `http://${address}:${port}`; + fabricPath = apiUrl; + const config = new Configuration({ basePath: apiUrl }); + + const apiClient = new FabricApi(config); + + fabricContractName = "basic-asset-transfer-2"; + const contractRelPath = + "../fabric-contracts/lock-asset/chaincode-typescript"; + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ └── index.ts + // ├── tsconfig.json + const sourceFiles: FileBase64[] = []; + { + const filename = "./tsconfig.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./package.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./index.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./asset.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./assetTransfer.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + + const response = await apiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: fabricContractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { packageIds, lifecycle, success } = response.data; + expect(response.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); + + const { + approveForMyOrgList, + installList, + queryInstalledList, + commit, + packaging, + queryCommitted, + } = lifecycle; + + Checks.truthy(packageIds, `packageIds truthy OK`); + Checks.truthy( + Array.isArray(packageIds), + `Array.isArray(packageIds) truthy OK`, + ); + Checks.truthy(approveForMyOrgList, `approveForMyOrgList truthy OK`); + Checks.truthy( + Array.isArray(approveForMyOrgList), + `Array.isArray(approveForMyOrgList) truthy OK`, + ); + Checks.truthy(installList, `installList truthy OK`); + Checks.truthy( + Array.isArray(installList), + `Array.isArray(installList) truthy OK`, + ); + Checks.truthy(queryInstalledList, `queryInstalledList truthy OK`); + Checks.truthy( + Array.isArray(queryInstalledList), + `Array.isArray(queryInstalledList) truthy OK`, + ); + Checks.truthy(commit, `commit truthy OK`); + Checks.truthy(packaging, `packaging truthy OK`); + Checks.truthy(queryCommitted, `queryCommitted truthy OK`); + + // FIXME - without this wait it randomly fails with an error claiming that + // the endorsement was impossible to be obtained. The fabric-samples script + // does the same thing, it just waits 10 seconds for good measure so there + // might not be a way for us to avoid doing this, but if there is a way we + // absolutely should not have timeouts like this, anywhere... + await new Promise((resolve) => setTimeout(resolve, 10000)); + + fabricSigningCredential = { + keychainId, + keychainRef: keychainEntryKey, + }; + + const createResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [FABRIC_ASSET_ID, "19"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + + expect(createResponse).not.toBeUndefined(); + expect(createResponse.status).toBeGreaterThan(199); + expect(createResponse.status).toBeLessThan(300); + + log.info( + `BassicAssetTransfer.Create(): ${JSON.stringify(createResponse.data)}`, + ); + } + { + // Besu ledger connection + besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/1.5.1/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + + 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( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + besuConnector = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + besuServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 4000, + server: besuServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await besuConnector.getOrCreateWebServices(); + const wsApi = new SocketIoServer(besuServer, { + path: Constants.SocketIoConnectionPathV1, + }); + await besuConnector.registerWebServices(expressApp, wsApi); + besuPath = `http://${address}:${port}`; + + await besuConnector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).not.toBeUndefined(); + expect(parseInt(balance, 10)).toBe(10e9); + + besuWeb3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }; + + const deployContractResponse = await besuConnector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: besuWeb3SigningCredential, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + + expect(deployContractResponse).not.toBeUndefined(); + expect(deployContractResponse.transactionReceipt).not.toBeUndefined(); + expect( + deployContractResponse.transactionReceipt.contractAddress, + ).not.toBeUndefined(); + + besuKeychainId = keychainPlugin.getKeychainId(); + besuContractName = LockAssetContractJson.contractName; + + const contractAddress: string = deployContractResponse.transactionReceipt + .contractAddress as string; + + expect(typeof contractAddress).toBe("string"); + } + { + // Gateways configuration + odapClientGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + fabricPath: fabricPath, + fabricSigningCredential: fabricSigningCredential, + fabricChannelName: fabricChannelName, + fabricContractName: fabricContractName, + fabricAssetID: FABRIC_ASSET_ID, + knexConfig: knexClientConnection, + }; + + odapServerGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + besuAssetID: BESU_ASSET_ID, + besuPath: besuPath, + besuWeb3SigningCredential: besuWeb3SigningCredential, + besuContractName: besuContractName, + besuKeychainId: besuKeychainId, + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + } + { + // Server Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 5000, + server: recipientGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapServerGatewayApiHost = `http://${address}:${port}`; + + await pluginRecipientGateway.getOrCreateWebServices(); + await pluginRecipientGateway.registerWebServices(expressApp); + } + { + // Client Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3001, + server: sourceGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapClientGatewayApiHost = `http://${address}:${port}`; + + await pluginSourceGateway.getOrCreateWebServices(); + await pluginSourceGateway.registerWebServices(expressApp); + } +}); + +test("client sends rollback message at the end of the protocol", async () => { + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const odapClientRequest: ClientV1Request = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + + const sessionID = pluginSourceGateway.configureOdapSession(odapClientRequest); + + const transferInitializationRequest = await sendTransferInitializationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferInitializationRequest == void 0) { + expect(false); + return; + } + + await checkValidInitializationRequest( + transferInitializationRequest, + pluginRecipientGateway, + ); + + const transferInitializationResponse = await sendTransferInitializationResponse( + transferInitializationRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferInitializationResponse == void 0) { + expect(false); + return; + } + + await checkValidInitializationResponse( + transferInitializationResponse, + pluginSourceGateway, + ); + + const transferCommenceRequest = await sendTransferCommenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferCommenceRequest == void 0) { + expect(false); + return; + } + + await checkValidtransferCommenceRequest( + transferCommenceRequest, + pluginRecipientGateway, + ); + + const transferCommenceResponse = await sendTransferCommenceResponse( + transferCommenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferCommenceResponse == void 0) { + expect(false); + return; + } + + await checkValidTransferCommenceResponse( + transferCommenceResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.lockFabricAsset(sessionID); + + const lockEvidenceRequest = await sendLockEvidenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (lockEvidenceRequest == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceRequest( + lockEvidenceRequest, + pluginRecipientGateway, + ); + + const lockEvidenceResponse = await sendLockEvidenceResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (lockEvidenceResponse == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceResponse( + lockEvidenceResponse, + pluginSourceGateway, + ); + + const commitPreparationRequest = await sendCommitPreparationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (commitPreparationRequest == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationRequest( + commitPreparationRequest, + pluginRecipientGateway, + ); + + const commitPreparationResponse = await sendCommitPreparationResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (commitPreparationResponse == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationResponse( + commitPreparationResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.deleteFabricAsset(sessionID); + + const commitFinalRequest = await sendCommitFinalRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (commitFinalRequest == void 0) { + expect(false); + return; + } + + await checkValidCommitFinalRequest( + commitFinalRequest, + pluginRecipientGateway, + ); + + await pluginRecipientGateway.createBesuAsset(sessionID); + + // now we simulate the crash of the client gateway + pluginSourceGateway.database?.destroy(); + await Servers.shutdown(sourceGatewayServer); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // the server gateway sends the message and the + // rollback will be triggered after the timeout + await sendCommitFinalResponse(sessionID, pluginRecipientGateway, true).catch( + (ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }, + ); + + // the client is back online and rollback after seeing the counterparty rollback + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3001, + server: sourceGatewayServer, + }; + + await Servers.listen(listenOptions); + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + await pluginSourceGateway.registerWebServices(expressApp); + + await pluginSourceGateway.recoverOpenSessions(true); + + expect( + await fabricAssetExists( + pluginSourceGateway, + fabricContractName, + fabricChannelName, + FABRIC_ASSET_ID, + fabricSigningCredential, + ), + ).toBe(true); + + expect( + await besuAssetExists( + pluginRecipientGateway, + besuContractName, + besuKeychainId, + BESU_ASSET_ID, + besuWeb3SigningCredential, + ), + ).toBe(false); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await fabricLedger.stop(); + await fabricLedger.destroy(); + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + + await pluginSourceGateway.database?.destroy(); + await pluginRecipientGateway.database?.destroy(); + + await Servers.shutdown(ipfsServer); + await Servers.shutdown(besuServer); + await Servers.shutdown(fabricServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayServer); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap.test.ts new file mode 100644 index 0000000000..4fd8d6c964 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap.test.ts @@ -0,0 +1,384 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { v4 as uuidV4 } from "uuid"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginOdapGateway } from "../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { + AssetProfile, + ClientV1Request, +} from "../../../main/typescript/public-api"; +import { + checkValidInitializationResponse, + sendTransferInitializationRequest, +} from "../../../main/typescript/gateway/client/transfer-initialization"; +import { + checkValidInitializationRequest, + sendTransferInitializationResponse, +} from "../../../main/typescript/gateway/server/transfer-initialization"; +import { + checkValidTransferCommenceResponse, + sendTransferCommenceRequest, +} from "../../../main/typescript/gateway/client/transfer-commence"; +import { + checkValidtransferCommenceRequest, + sendTransferCommenceResponse, +} from "../../../main/typescript/gateway/server/transfer-commence"; +import { + checkValidLockEvidenceResponse, + sendLockEvidenceRequest, +} from "../../../main/typescript/gateway/client/lock-evidence"; +import { + checkValidLockEvidenceRequest, + sendLockEvidenceResponse, +} from "../../../main/typescript/gateway/server/lock-evidence"; +import { + checkValidCommitPreparationResponse, + sendCommitPreparationRequest, +} from "../../../main/typescript/gateway/client/commit-preparation"; +import { + checkValidCommitPreparationRequest, + sendCommitPreparationResponse, +} from "../../../main/typescript/gateway/server/commit-preparation"; +import { + checkValidCommitFinalResponse, + sendCommitFinalRequest, +} from "../../../main/typescript/gateway/client/commit-final"; +import { + checkValidCommitFinalRequest, + sendCommitFinalResponse, +} from "../../../main/typescript/gateway/server/commit-final"; +import { sendTransferCompleteRequest } from "../../../main/typescript/gateway/client/transfer-complete"; +import { checkValidTransferCompleteRequest } from "../../../main/typescript/gateway/server/transfer-complete"; +import { makeSessionDataChecks } from "../make-checks"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const logLevel: LogLevelDesc = "TRACE"; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); + +test("successful run ODAP instance", async () => { + const sourceGatewayConstructor = { + name: "plugin-odap-gateway#sourceGateway", + dltIDs: ["DLT2"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + knexConfig: knexClientConnection, + }; + const recipientGatewayConstructor = { + name: "plugin-odap-gateway#recipientGateway", + dltIDs: ["DLT1"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); + pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + + const dummyPath = { apiHost: "dummyPath" }; + + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const odapClientRequest: ClientV1Request = { + clientGatewayConfiguration: dummyPath, + serverGatewayConfiguration: dummyPath, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + + const sessionID = pluginSourceGateway.configureOdapSession(odapClientRequest); + + const transferInitializationRequest = await sendTransferInitializationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferInitializationRequest == void 0) { + expect(false); + return; + } + + await checkValidInitializationRequest( + transferInitializationRequest, + pluginRecipientGateway, + ); + + const transferInitializationResponse = await sendTransferInitializationResponse( + transferInitializationRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferInitializationResponse == void 0) { + expect(false); + return; + } + + await checkValidInitializationResponse( + transferInitializationResponse, + pluginSourceGateway, + ); + + const transferCommenceRequest = await sendTransferCommenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferCommenceRequest == void 0) { + expect(false); + return; + } + + await checkValidtransferCommenceRequest( + transferCommenceRequest, + pluginRecipientGateway, + ); + + const transferCommenceResponse = await sendTransferCommenceResponse( + transferCommenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferCommenceResponse == void 0) { + expect(false); + return; + } + + await checkValidTransferCommenceResponse( + transferCommenceResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.lockFabricAsset(sessionID); + + const lockEvidenceRequest = await sendLockEvidenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (lockEvidenceRequest == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceRequest( + lockEvidenceRequest, + pluginRecipientGateway, + ); + + const lockEvidenceResponse = await sendLockEvidenceResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (lockEvidenceResponse == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceResponse( + lockEvidenceResponse, + pluginSourceGateway, + ); + + const commitPreparationRequest = await sendCommitPreparationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (commitPreparationRequest == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationRequest( + commitPreparationRequest, + pluginRecipientGateway, + ); + + const commitPreparationResponse = await sendCommitPreparationResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (commitPreparationResponse == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationResponse( + commitPreparationResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.deleteFabricAsset(sessionID); + + const commitFinalRequest = await sendCommitFinalRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (commitFinalRequest == void 0) { + expect(false); + return; + } + + await checkValidCommitFinalRequest( + commitFinalRequest, + pluginRecipientGateway, + ); + + await pluginRecipientGateway.createBesuAsset(sessionID); + + const commitFinalResponse = await sendCommitFinalResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (commitFinalResponse == void 0) { + expect(false); + return; + } + + await checkValidCommitFinalResponse(commitFinalResponse, pluginSourceGateway); + + const transferCompleteRequest = await sendTransferCompleteRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferCompleteRequest == void 0) { + expect(false); + return; + } + + await checkValidTransferCompleteRequest( + transferCompleteRequest, + pluginRecipientGateway, + ); + + expect(pluginSourceGateway.sessions.size).toBe(1); + expect(pluginRecipientGateway.sessions.size).toBe(1); + + const [sessionId] = pluginSourceGateway.sessions.keys(); + + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionId, + ); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-create-asset.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-create-asset.test.ts new file mode 100644 index 0000000000..f8bc436b81 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-create-asset.test.ts @@ -0,0 +1,943 @@ +import fs from "fs-extra"; +import "jest-extended"; +import http, { Server } from "http"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { AssetProfile } from "../../../main/typescript/generated/openapi/typescript-axios"; +import { + Checks, + IListenOptions, + LoggerProvider, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { DiscoveryOptions } from "fabric-network"; +import { + Containers, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, + GoIpfsTestContainer, + BesuTestLedger, +} from "@hyperledger/cactus-test-tooling"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { ClientV1Request } from "../../../main/typescript/public-api"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + PluginImportType, + Constants, +} from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../main/typescript/gateway/plugin-odap-gateway"; +import { + ChainCodeProgrammingLanguage, + DefaultEventHandlerStrategy, + FabricContractInvocationType, + FileBase64, + IPluginLedgerConnectorFabricOptions, + PluginLedgerConnectorFabric, + DefaultApi as FabricApi, + FabricSigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import path from "path"; +import { + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + ReceiptType, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import Web3 from "web3"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; +import { makeSessionDataChecks } from "../make-checks"; +import { + checkValidLockEvidenceRequest, + sendLockEvidenceResponse, +} from "../../../main/typescript/gateway/server/lock-evidence"; +import { + sendTransferCommenceRequest, + checkValidTransferCommenceResponse, +} from "../../../main/typescript/gateway/client/transfer-commence"; +import { + sendTransferInitializationRequest, + checkValidInitializationResponse, +} from "../../../main/typescript/gateway/client/transfer-initialization"; +import { + checkValidtransferCommenceRequest, + sendTransferCommenceResponse, +} from "../../../main/typescript/gateway/server/transfer-commence"; +import { + checkValidInitializationRequest, + sendTransferInitializationResponse, +} from "../../../main/typescript/gateway/server/transfer-initialization"; +import { + sendCommitPreparationRequest, + checkValidCommitPreparationResponse, +} from "../../../main/typescript/gateway/client/commit-preparation"; +import { + sendLockEvidenceRequest, + checkValidLockEvidenceResponse, +} from "../../../main/typescript/gateway/client/lock-evidence"; +import { + checkValidCommitPreparationRequest, + sendCommitPreparationResponse, +} from "../../../main/typescript/gateway/server/commit-preparation"; +import { besuAssetExists, fabricAssetExists } from "../make-checks-ledgers"; +import { sendCommitFinalRequest } from "../../../main/typescript/gateway/client/commit-final"; +import { checkValidCommitFinalRequest } from "../../../main/typescript/gateway/server/commit-final"; +/** + * Use this to debug issues with the fabric node SDK + * ```sh + * export HFC_LOGGING='{"debug":"console","info":"console"}' + * ``` + */ +let ipfsApiHost: string; + +let fabricSigningCredential: FabricSigningCredential; +const logLevel: LogLevelDesc = "TRACE"; + +let ipfsServer: Server; +let sourceGatewayServer: Server; +let recipientGatewayServer: Server; +let besuServer: Server; +let fabricServer: Server; + +let ipfsContainer: GoIpfsTestContainer; + +let fabricLedger: FabricTestLedgerV1; +let fabricContractName: string; +let fabricChannelName: string; +let fabricPath: string; + +let besuTestLedger: BesuTestLedger; +let besuPath: string; +let besuContractName: string; +let besuWeb3SigningCredential: Web3SigningCredential; +let besuKeychainId: string; + +let fabricConnector: PluginLedgerConnectorFabric; +let besuConnector: PluginLedgerConnectorBesu; + +let odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let odapClientGatewayApiHost: string; +let odapServerGatewayApiHost: string; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const FABRIC_ASSET_ID = uuidv4(); +const BESU_ASSET_ID = uuidv4(); + +const log = LoggerProvider.getOrCreate({ + level: "INFO", + label: "odapTestWithLedgerConnectors", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + // IPFS configuration + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + } + { + // Fabric ledger connection + const channelId = "mychannel"; + fabricChannelName = channelId; + + fabricLedger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), + logLevel, + }); + + await fabricLedger.start(); + + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); + expect(connectionProfile).not.toBeUndefined(); + + const enrollAdminOut = await fabricLedger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await fabricLedger.enrollUser(adminWallet); + const sshConfig = await fabricLedger.getSshConfig(); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user2"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + // This is the directory structure of the Fabirc 2.x CLI container (fabric-tools image) + // const orgCfgDir = "/fabric-samples/test-network/organizations/"; + const orgCfgDir = + "/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations/"; + + // these below mirror how the fabric-samples sets up the configuration + const org1Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org1MSP", + + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt`, + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp`, + CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + // these below mirror how the fabric-samples sets up the configuration + const org2Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org2MSP", + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + CORE_PEER_ADDRESS: "peer0.org2.example.com:9051", + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp`, + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt`, + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: org1Env, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + fabricServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3000, + server: fabricServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressApp); + + const apiUrl = `http://${address}:${port}`; + fabricPath = apiUrl; + const config = new Configuration({ basePath: apiUrl }); + + const apiClient = new FabricApi(config); + + fabricContractName = "basic-asset-transfer-2"; + const contractRelPath = + "../fabric-contracts/lock-asset/chaincode-typescript"; + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ └── index.ts + // ├── tsconfig.json + const sourceFiles: FileBase64[] = []; + { + const filename = "./tsconfig.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./package.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./index.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./asset.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./assetTransfer.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + + const response = await apiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: fabricContractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { packageIds, lifecycle, success } = response.data; + expect(response.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); + + const { + approveForMyOrgList, + installList, + queryInstalledList, + commit, + packaging, + queryCommitted, + } = lifecycle; + + Checks.truthy(packageIds, `packageIds truthy OK`); + Checks.truthy( + Array.isArray(packageIds), + `Array.isArray(packageIds) truthy OK`, + ); + Checks.truthy(approveForMyOrgList, `approveForMyOrgList truthy OK`); + Checks.truthy( + Array.isArray(approveForMyOrgList), + `Array.isArray(approveForMyOrgList) truthy OK`, + ); + Checks.truthy(installList, `installList truthy OK`); + Checks.truthy( + Array.isArray(installList), + `Array.isArray(installList) truthy OK`, + ); + Checks.truthy(queryInstalledList, `queryInstalledList truthy OK`); + Checks.truthy( + Array.isArray(queryInstalledList), + `Array.isArray(queryInstalledList) truthy OK`, + ); + Checks.truthy(commit, `commit truthy OK`); + Checks.truthy(packaging, `packaging truthy OK`); + Checks.truthy(queryCommitted, `queryCommitted truthy OK`); + + // FIXME - without this wait it randomly fails with an error claiming that + // the endorsement was impossible to be obtained. The fabric-samples script + // does the same thing, it just waits 10 seconds for good measure so there + // might not be a way for us to avoid doing this, but if there is a way we + // absolutely should not have timeouts like this, anywhere... + await new Promise((resolve) => setTimeout(resolve, 10000)); + + fabricSigningCredential = { + keychainId, + keychainRef: keychainEntryKey, + }; + + const createResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [FABRIC_ASSET_ID, "19"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + + expect(createResponse).not.toBeUndefined(); + expect(createResponse.status).toBeGreaterThan(199); + expect(createResponse.status).toBeLessThan(300); + + log.info( + `BassicAssetTransfer.Create(): ${JSON.stringify(createResponse.data)}`, + ); + } + { + // Besu ledger connection + besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/1.5.1/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + + 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( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + besuConnector = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + besuServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 4000, + server: besuServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await besuConnector.getOrCreateWebServices(); + const wsApi = new SocketIoServer(besuServer, { + path: Constants.SocketIoConnectionPathV1, + }); + await besuConnector.registerWebServices(expressApp, wsApi); + besuPath = `http://${address}:${port}`; + + await besuConnector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).not.toBeUndefined(); + expect(parseInt(balance, 10)).toBe(10e9); + + besuWeb3SigningCredential = { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }; + + const deployContractResponse = await besuConnector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: besuWeb3SigningCredential, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + + expect(deployContractResponse).not.toBeUndefined(); + expect(deployContractResponse.transactionReceipt).not.toBeUndefined(); + expect( + deployContractResponse.transactionReceipt.contractAddress, + ).not.toBeUndefined(); + + besuKeychainId = keychainPlugin.getKeychainId(); + besuContractName = LockAssetContractJson.contractName; + + const contractAddress: string = deployContractResponse.transactionReceipt + .contractAddress as string; + + expect(typeof contractAddress).toBe("string"); + } +}); + +beforeEach(async () => { + { + // Gateways configuration + const odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + fabricPath: fabricPath, + fabricSigningCredential: fabricSigningCredential, + fabricChannelName: fabricChannelName, + fabricContractName: fabricContractName, + fabricAssetID: FABRIC_ASSET_ID, + knexConfig: knexClientConnection, + }; + + odapServerGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + ipfsPath: ipfsApiHost, + besuAssetID: BESU_ASSET_ID, + besuPath: besuPath, + besuWeb3SigningCredential: besuWeb3SigningCredential, + besuContractName: besuContractName, + besuKeychainId: besuKeychainId, + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + } + { + // Server Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 5000, + server: recipientGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapServerGatewayApiHost = `http://${address}:${port}`; + + await pluginRecipientGateway.getOrCreateWebServices(); + await pluginRecipientGateway.registerWebServices(expressApp); + } + { + // Client Gateway configuration + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3001, + server: sourceGatewayServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + + const { address, port } = addressInfo; + odapClientGatewayApiHost = `http://${address}:${port}`; + + await pluginSourceGateway.getOrCreateWebServices(); + await pluginSourceGateway.registerWebServices(expressApp); + } +}); + +test("server gateway crashes after creating besu asset", async () => { + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const odapClientRequest: ClientV1Request = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + + const sessionID = pluginSourceGateway.configureOdapSession(odapClientRequest); + + const transferInitializationRequest = await sendTransferInitializationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferInitializationRequest == void 0) { + expect(false); + return; + } + + await checkValidInitializationRequest( + transferInitializationRequest, + pluginRecipientGateway, + ); + + const transferInitializationResponse = await sendTransferInitializationResponse( + transferInitializationRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferInitializationResponse == void 0) { + expect(false); + return; + } + + await checkValidInitializationResponse( + transferInitializationResponse, + pluginSourceGateway, + ); + + const transferCommenceRequest = await sendTransferCommenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferCommenceRequest == void 0) { + expect(false); + return; + } + + await checkValidtransferCommenceRequest( + transferCommenceRequest, + pluginRecipientGateway, + ); + + const transferCommenceResponse = await sendTransferCommenceResponse( + transferCommenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (transferCommenceResponse == void 0) { + expect(false); + return; + } + + await checkValidTransferCommenceResponse( + transferCommenceResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.lockFabricAsset(sessionID); + + const lockEvidenceRequest = await sendLockEvidenceRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (lockEvidenceRequest == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceRequest( + lockEvidenceRequest, + pluginRecipientGateway, + ); + + const lockEvidenceResponse = await sendLockEvidenceResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (lockEvidenceResponse == void 0) { + expect(false); + return; + } + + await checkValidLockEvidenceResponse( + lockEvidenceResponse, + pluginSourceGateway, + ); + + const commitPreparationRequest = await sendCommitPreparationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (commitPreparationRequest == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationRequest( + commitPreparationRequest, + pluginRecipientGateway, + ); + + const commitPreparationResponse = await sendCommitPreparationResponse( + lockEvidenceRequest.sessionID, + pluginRecipientGateway, + false, + ); + + if (commitPreparationResponse == void 0) { + expect(false); + return; + } + + await checkValidCommitPreparationResponse( + commitPreparationResponse, + pluginSourceGateway, + ); + + await pluginSourceGateway.deleteFabricAsset(sessionID); + + const commitFinalRequest = await sendCommitFinalRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (commitFinalRequest == void 0) { + expect(false); + return; + } + + await checkValidCommitFinalRequest( + commitFinalRequest, + pluginRecipientGateway, + ); + + await pluginRecipientGateway.createBesuAsset(sessionID); + + // now we simulate the crash of the server gateway + pluginRecipientGateway.database?.destroy(); + await Servers.shutdown(recipientGatewayServer); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 5000, + server: recipientGatewayServer, + }; + + await Servers.listen(listenOptions); + + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + await pluginRecipientGateway.registerWebServices(expressApp); + + // client gateway self-healed and is back online + await pluginRecipientGateway.recoverOpenSessions(true); + + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionID, + ); + + expect( + await fabricAssetExists( + pluginSourceGateway, + fabricContractName, + fabricChannelName, + FABRIC_ASSET_ID, + fabricSigningCredential, + ), + ).toBe(false); + + expect( + await besuAssetExists( + pluginRecipientGateway, + besuContractName, + besuKeychainId, + BESU_ASSET_ID, + besuWeb3SigningCredential, + ), + ).toBe(true); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await fabricLedger.stop(); + await fabricLedger.destroy(); + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + + await pluginSourceGateway.database?.destroy(); + await pluginRecipientGateway.database?.destroy(); + + await Servers.shutdown(ipfsServer); + await Servers.shutdown(besuServer); + await Servers.shutdown(fabricServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayServer); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-transfer-initiation.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-transfer-initiation.test.ts new file mode 100644 index 0000000000..830a9ef10c --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-transfer-initiation.test.ts @@ -0,0 +1,276 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express, { Express } from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { + AssetProfile, + ClientV1Request, +} from "../../../main/typescript/public-api"; +import { sendTransferInitializationRequest } from "../../../main/typescript/gateway/client/transfer-initialization"; +import { checkValidInitializationRequest } from "../../../main/typescript/gateway/server/transfer-initialization"; +import { makeSessionDataChecks } from "../make-checks"; +import { knexClientConnection, knexServerConnection } from "../knex.config"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const logLevel: LogLevelDesc = "TRACE"; + +let odapServerGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; +let odapClientGatewayPluginOptions: IPluginOdapGatewayConstructorOptions; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsApiHost: string; +let ipfsServer: Server; + +let sourceGatewayServer: Server; +let recipientGatewayserver: Server; + +let odapServerGatewayApiHost: string; +let odapClientGatewayApiHost: string; + +let odapClientRequest: ClientV1Request; + +let serverExpressApp: Express; +let serverListenOptions: IListenOptions; + +let clientExpressApp: Express; +let clientListenOptions: IListenOptions; + +beforeAll(async () => { + { + // Define IPFS connection + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const ipfsApi = new ObjectStoreIpfsApi(config); + + expect(ipfsApi).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + } + { + // Server Gateway configuration + odapServerGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT1"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexServerConnection, + }; + + serverExpressApp = express(); + serverExpressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayserver = http.createServer(serverExpressApp); + serverListenOptions = { + hostname: "localhost", + port: 3000, + server: recipientGatewayserver, + }; + + const addressInfo = (await Servers.listen( + serverListenOptions, + )) as AddressInfo; + + const { address, port } = addressInfo; + odapServerGatewayApiHost = `http://${address}:${port}`; + + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + + if (pluginRecipientGateway.database == undefined) { + throw new Error("Database is not correctly initialized"); + } + + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + + await pluginRecipientGateway.registerWebServices(serverExpressApp); + } + { + // Client Gateway configuration + odapClientGatewayPluginOptions = { + name: "cactus-plugin#odapGateway", + dltIDs: ["DLT2"], + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexClientConnection, + }; + + clientExpressApp = express(); + clientExpressApp.use(bodyParser.json({ limit: "250mb" })); + sourceGatewayServer = http.createServer(clientExpressApp); + clientListenOptions = { + hostname: "localhost", + port: 2000, + server: sourceGatewayServer, + }; + + const addressInfo = (await Servers.listen( + clientListenOptions, + )) as AddressInfo; + + const { address, port } = addressInfo; + odapClientGatewayApiHost = `http://${address}:${port}`; + + pluginSourceGateway = new PluginOdapGateway(odapClientGatewayPluginOptions); + + if (pluginSourceGateway.database == undefined) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + + await pluginSourceGateway.registerWebServices(clientExpressApp); + + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + odapClientRequest = { + clientGatewayConfiguration: { + apiHost: odapClientGatewayApiHost, + }, + serverGatewayConfiguration: { + apiHost: odapServerGatewayApiHost, + }, + version: "0.0.0", + loggingProfile: "dummyLoggingProfile", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + assetControlProfile: "dummyAssetControlProfile", + beneficiaryPubkey: "dummyPubKey", + clientDltSystem: "DLT1", + originatorPubkey: "dummyPubKey", + recipientGatewayDltSystem: "DLT2", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + serverDltSystem: "DLT2", + sourceGatewayDltSystem: "DLT1", + clientIdentityPubkey: "", + serverIdentityPubkey: "", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + }; + } +}); + +test("server gateway crashes after transfer initiation flow", async () => { + const sessionID = pluginSourceGateway.configureOdapSession(odapClientRequest); + + const transferInitializationRequest = await sendTransferInitializationRequest( + sessionID, + pluginSourceGateway, + false, + ); + + if (transferInitializationRequest == void 0) { + expect(false); + return; + } + + await checkValidInitializationRequest( + transferInitializationRequest, + pluginRecipientGateway, + ); + + // now we simulate the crash of the server gateway + pluginRecipientGateway.database?.destroy(); + await Servers.shutdown(recipientGatewayserver); + + serverExpressApp = express(); + serverExpressApp.use(bodyParser.json({ limit: "250mb" })); + recipientGatewayserver = http.createServer(serverExpressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 3000, + server: recipientGatewayserver, + }; + + await Servers.listen(listenOptions); + + pluginRecipientGateway = new PluginOdapGateway( + odapServerGatewayPluginOptions, + ); + await pluginRecipientGateway.registerWebServices(serverExpressApp); + + // server gateway self-healed and is back online + await pluginRecipientGateway.recoverOpenSessions(true); + + await makeSessionDataChecks( + pluginSourceGateway, + pluginRecipientGateway, + sessionID, + ); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); + + await Servers.shutdown(ipfsServer); + await Servers.shutdown(sourceGatewayServer); + await Servers.shutdown(recipientGatewayserver); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/knex.config.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/knex.config.ts new file mode 100644 index 0000000000..b1b4e7668c --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/knex.config.ts @@ -0,0 +1,21 @@ +export const knexClientConnection = { + client: "sqlite3", + connection: { + filename: "./packages/cactus-plugin-odap-hermes/knex/.dev.client.sqlite3", + }, + migrations: { + directory: "./packages/cactus-plugin-odap-hermes/knex/migrations", + }, + useNullAsDefault: true, +}; + +export const knexServerConnection = { + client: "sqlite3", + connection: { + filename: "./packages/cactus-plugin-odap-hermes/knex/.dev.server.sqlite3", + }, + migrations: { + directory: "./packages/cactus-plugin-odap-hermes/knex/migrations", + }, + useNullAsDefault: true, +}; diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks-ledgers.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks-ledgers.ts new file mode 100644 index 0000000000..315fb3d531 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks-ledgers.ts @@ -0,0 +1,85 @@ +import { PluginOdapGateway } from "../../main/typescript/gateway/plugin-odap-gateway"; +import { + FabricContractInvocationType, + FabricSigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import { + EthContractInvocationType, + InvokeContractV1Request as BesuInvokeContractV1Request, + Web3SigningCredential, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; + +export async function fabricAssetExists( + pluginSourceGateway: PluginOdapGateway, + fabricContractName: string, + fabricChannelName: string, + fabricAssetID: string, + fabricSigningCredential: FabricSigningCredential, +): Promise { + const assetExists = await pluginSourceGateway.fabricApi?.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [fabricAssetID], + methodName: "AssetExists", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + + expect(assetExists).not.toBeUndefined(); + + expect(assetExists?.status).toBeGreaterThan(199); + expect(assetExists?.status).toBeLessThan(300); + + expect(assetExists?.data).not.toBeUndefined(); + return assetExists?.data.functionOutput == "true"; +} + +export async function isFabricAssetLocked( + pluginSourceGateway: PluginOdapGateway, + fabricContractName: string, + fabricChannelName: string, + fabricAssetID: string, + fabricSigningCredential: FabricSigningCredential, +): Promise { + const assetIsLocked = await pluginSourceGateway.fabricApi?.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [fabricAssetID], + methodName: "IsAssetLocked", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + + expect(assetIsLocked).not.toBeUndefined(); + + expect(assetIsLocked?.status).toBeGreaterThan(199); + expect(assetIsLocked?.status).toBeLessThan(300); + + expect(assetIsLocked?.data).not.toBeUndefined(); + return assetIsLocked?.data.functionOutput == "true"; +} + +export async function besuAssetExists( + pluginRecipientGateway: PluginOdapGateway, + besuContractName: string, + besuKeychainId: string, + besuAssetID: string, + besuWeb3SigningCredential: Web3SigningCredential, +): Promise { + const assetExists = await pluginRecipientGateway.besuApi?.invokeContractV1({ + contractName: besuContractName, + invocationType: EthContractInvocationType.Call, + methodName: "isPresent", + gas: 1000000, + params: [besuAssetID], + signingCredential: besuWeb3SigningCredential, + keychainId: besuKeychainId, + } as BesuInvokeContractV1Request); + + expect(assetExists).not.toBeUndefined(); + + expect(assetExists?.status).toBeGreaterThan(199); + expect(assetExists?.status).toBeLessThan(300); + + return assetExists?.data.callOutput == true; +} diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks.ts new file mode 100644 index 0000000000..9eda926f89 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/make-checks.ts @@ -0,0 +1,286 @@ +import { PluginOdapGateway } from "../../main/typescript/gateway/plugin-odap-gateway"; + +export async function makeSessionDataChecks( + pluginSourceGateway: PluginOdapGateway, + pluginRecipientGateway: PluginOdapGateway, + sessionId: string, +): Promise { + const clientSessionData = pluginSourceGateway.sessions.get(sessionId); + const serverSessionData = pluginRecipientGateway.sessions.get(sessionId); + + if (clientSessionData == undefined || serverSessionData == undefined) { + throw new Error("Test Failed"); + } + + if (clientSessionData == undefined || serverSessionData == undefined) { + throw new Error("Test Failed"); + } + + expect(clientSessionData.id).toBe(serverSessionData.id); + expect(clientSessionData.id).toBe(sessionId); + + expect(clientSessionData.loggingProfile).toBe( + serverSessionData.loggingProfile, + ); + + expect(clientSessionData.accessControlProfile).toBe( + serverSessionData.accessControlProfile, + ); + + expect(clientSessionData.applicationProfile).toBe( + serverSessionData.applicationProfile, + ); + + expect(JSON.stringify(clientSessionData.assetProfile)).toBe( + JSON.stringify(serverSessionData.assetProfile), + ); + + expect(clientSessionData.sourceGatewayPubkey).toBe( + serverSessionData.sourceGatewayPubkey, + ); + + expect(clientSessionData.sourceGatewayDltSystem).toBe( + serverSessionData.sourceGatewayDltSystem, + ); + + expect(clientSessionData.recipientGatewayPubkey).toBe( + serverSessionData.recipientGatewayPubkey, + ); + + expect(clientSessionData.recipientGatewayDltSystem).toBe( + serverSessionData.recipientGatewayDltSystem, + ); + + expect(clientSessionData.initializationRequestMessageHash).toBe( + serverSessionData.initializationRequestMessageHash, + ); + + expect(clientSessionData.initializationResponseMessageHash).toBe( + serverSessionData.initializationResponseMessageHash, + ); + + expect(clientSessionData.clientSignatureInitializationRequestMessage).toBe( + serverSessionData.clientSignatureInitializationRequestMessage, + ); + + expect(clientSessionData.serverSignatureInitializationResponseMessage).toBe( + serverSessionData.serverSignatureInitializationResponseMessage, + ); + + expect(clientSessionData.transferCommenceMessageRequestHash).toBe( + serverSessionData.transferCommenceMessageRequestHash, + ); + + expect(clientSessionData.transferCommenceMessageResponseHash).toBe( + serverSessionData.transferCommenceMessageResponseHash, + ); + + expect(clientSessionData.clientSignatureTransferCommenceRequestMessage).toBe( + serverSessionData.clientSignatureTransferCommenceRequestMessage, + ); + + expect(clientSessionData.serverSignatureTransferCommenceResponseMessage).toBe( + serverSessionData.serverSignatureTransferCommenceResponseMessage, + ); + + expect(clientSessionData.lockEvidenceRequestMessageHash).toBe( + serverSessionData.lockEvidenceRequestMessageHash, + ); + + expect(clientSessionData.lockEvidenceResponseMessageHash).toBe( + serverSessionData.lockEvidenceResponseMessageHash, + ); + + expect(clientSessionData.clientSignatureLockEvidenceRequestMessage).toBe( + serverSessionData.clientSignatureLockEvidenceRequestMessage, + ); + + expect(clientSessionData.serverSignatureLockEvidenceResponseMessage).toBe( + serverSessionData.serverSignatureLockEvidenceResponseMessage, + ); + + expect(clientSessionData.lockEvidenceClaim).toBe( + serverSessionData.lockEvidenceClaim, + ); + + expect(clientSessionData.commitPrepareRequestMessageHash).toBe( + serverSessionData.commitPrepareRequestMessageHash, + ); + + expect(clientSessionData.commitPrepareResponseMessageHash).toBe( + serverSessionData.commitPrepareResponseMessageHash, + ); + + expect(clientSessionData.clientSignatureCommitPreparationRequestMessage).toBe( + serverSessionData.clientSignatureCommitPreparationRequestMessage, + ); + + expect( + clientSessionData.serverSignatureCommitPreparationResponseMessage, + ).toBe(serverSessionData.serverSignatureCommitPreparationResponseMessage); + + expect(clientSessionData.commitFinalRequestMessageHash).toBe( + serverSessionData.commitFinalRequestMessageHash, + ); + + expect(clientSessionData.commitPrepareRequestMessageHash).toBe( + serverSessionData.commitPrepareRequestMessageHash, + ); + + expect(clientSessionData.commitFinalResponseMessageHash).toBe( + serverSessionData.commitFinalResponseMessageHash, + ); + + expect(clientSessionData.commitFinalClaim).toBe( + serverSessionData.commitFinalClaim, + ); + + expect(clientSessionData.commitFinalClaimFormat).toBe( + serverSessionData.commitFinalClaimFormat, + ); + + expect(clientSessionData.commitAcknowledgementClaim).toBe( + serverSessionData.commitAcknowledgementClaim, + ); + + expect(clientSessionData.commitAcknowledgementClaimFormat).toBe( + serverSessionData.commitAcknowledgementClaimFormat, + ); + + expect(clientSessionData.clientSignatureCommitFinalRequestMessage).toBe( + serverSessionData.clientSignatureCommitFinalRequestMessage, + ); + + expect(clientSessionData.serverSignatureCommitFinalResponseMessage).toBe( + serverSessionData.serverSignatureCommitFinalResponseMessage, + ); + + expect(clientSessionData.transferCompleteMessageHash).toBe( + serverSessionData.transferCompleteMessageHash, + ); + + expect(clientSessionData.clientSignatureTransferCompleteMessage).toBe( + serverSessionData.clientSignatureTransferCompleteMessage, + ); + + expect( + await pluginSourceGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "init", "validate"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "exec", "validate"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "done", "validate"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "ack", "validate"), + ), + ).not.toBeUndefined(); + + expect( + await pluginSourceGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "init", "commence"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "exec", "commence"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "done", "commence"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "ack", "commence"), + ), + ).not.toBeUndefined(); + + expect( + await pluginSourceGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "init", "lock"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "exec", "lock"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "done", "lock"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "ack", "lock"), + ), + ).not.toBeUndefined(); + + expect( + await pluginSourceGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "init", "prepare"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "exec", "prepare"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "done", "prepare"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "ack", "prepare"), + ), + ).not.toBeUndefined(); + + expect( + await pluginSourceGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "init", "final"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "exec", "final"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "done", "final"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "ack", "final"), + ), + ).not.toBeUndefined(); + + expect( + await pluginSourceGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "init", "complete"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "exec", "complete"), + ), + ).not.toBeUndefined(); + expect( + await pluginRecipientGateway.getLogFromDatabase( + PluginOdapGateway.getOdapLogKey(sessionId, "done", "complete"), + ), + ).not.toBeUndefined(); +} diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-final.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-final.test.ts index 7dfb717abb..273fbd5147 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-final.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-final.test.ts @@ -1,15 +1,38 @@ import { randomInt } from "crypto"; import { SHA256 } from "crypto-js"; -import { v4 as uuidV4 } from "uuid"; -import { checkValidCommitFinalResponse } from "../../../../main/typescript/gateway/client/commit-final"; +import bodyParser from "body-parser"; +import { v4 as uuidv4 } from "uuid"; +import http, { Server } from "http"; +import { create } from "ipfs-http-client"; +import { + checkValidCommitFinalResponse, + sendCommitFinalRequest, +} from "../../../../main/typescript/gateway/client/commit-final"; import { OdapMessageType, PluginOdapGateway, } from "../../../../main/typescript/gateway/plugin-odap-gateway"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; import { CommitFinalV1Response, SessionData, } from "../../../../main/typescript/public-api"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import express from "express"; +import { AddressInfo } from "net"; +import { knexClientConnection, knexServerConnection } from "../../knex.config"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const logLevel: LogLevelDesc = "TRACE"; const COMMIT_FINAL_REQUEST_MESSAGE_HASH = "dummyCommitFinalRequestMessageHash"; const COMMIT_ACK_CLAIM = "dummyCommitAckClaim"; @@ -22,23 +45,86 @@ let sequenceNumber: number; let sessionID: string; let step: number; -beforeEach(() => { +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); + +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], - instanceId: uuidV4(), + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + knexConfig: knexClientConnection, }; recipientGatewayConstructor = { name: "plugin-odap-gateway#recipientGateway", dltIDs: ["DLT1"], - instanceId: uuidV4(), + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + knexConfig: knexServerConnection, }; pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + sequenceNumber = randomInt(100); - sessionID = uuidV4(); + sessionID = uuidv4(); step = 1; const sessionData: SessionData = { @@ -48,9 +134,39 @@ beforeEach(() => { recipientGatewayPubkey: pluginRecipientGateway.pubKey, commitFinalRequestMessageHash: COMMIT_FINAL_REQUEST_MESSAGE_HASH, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); + + await pluginRecipientGateway.storeOdapProof({ + sessionID: sessionID, + type: "proof", + operation: "create", + data: COMMIT_ACK_CLAIM, + }); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); +}); + +afterEach(async () => { + await pluginSourceGateway.database?.destroy(); + await pluginRecipientGateway.database?.destroy(); }); test("valid commit final response", async () => { @@ -61,11 +177,11 @@ test("valid commit final response", async () => { clientIdentityPubkey: pluginSourceGateway.pubKey, commitAcknowledgementClaim: COMMIT_ACK_CLAIM, hashCommitFinal: COMMIT_FINAL_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - commitFinalResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + commitFinalResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign(JSON.stringify(commitFinalResponse)), ); @@ -78,7 +194,6 @@ test("valid commit final response", async () => { if (retrievedSessionData == undefined) throw new Error("Test Failed."); expect(retrievedSessionData.id).toBe(sessionID); - expect(retrievedSessionData.step).toBe(step + 1); expect(retrievedSessionData.commitAcknowledgementClaim).toBe( COMMIT_ACK_CLAIM, ); @@ -96,11 +211,11 @@ test("commit final response invalid because of wrong previous message hash", asy clientIdentityPubkey: pluginSourceGateway.pubKey, commitAcknowledgementClaim: COMMIT_ACK_CLAIM, hashCommitFinal: "wrongMessageHash", - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - commitFinalResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + commitFinalResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign(JSON.stringify(commitFinalResponse)), ); @@ -123,11 +238,11 @@ test("commit final response invalid because of wrong signature", async () => { clientIdentityPubkey: pluginSourceGateway.pubKey, commitAcknowledgementClaim: COMMIT_ACK_CLAIM, hashCommitFinal: COMMIT_FINAL_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - commitFinalResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + commitFinalResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign("somethingWrong"), ); @@ -139,3 +254,45 @@ test("commit final response invalid because of wrong signature", async () => { expect(ex.message).toMatch("message signature verification failed"), ); }); + +test("timeout in commit final request because no server gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "", + recipientBasePath: "http://wrongpath", + lastSequenceNumber: 77, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + commitFinalClaim: "dummyCommitFinalClaim", + commitPrepareResponseMessageHash: "dummyCommitPrepareResponseMessageHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendCommitFinalRequest(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + await pluginSourceGateway.database?.destroy(); + await pluginRecipientGateway.database?.destroy(); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-preparation.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-preparation.test.ts index c73ce6c471..664ccb7065 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-preparation.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/commit-preparation.test.ts @@ -1,7 +1,10 @@ import { randomInt } from "crypto"; import { SHA256 } from "crypto-js"; import { v4 as uuidV4 } from "uuid"; -import { checkValidCommitPreparationResponse } from "../../../../main/typescript/gateway/client/commit-preparation"; +import { + checkValidCommitPreparationResponse, + sendCommitPreparationRequest, +} from "../../../../main/typescript/gateway/client/commit-preparation"; import { OdapMessageType, PluginOdapGateway, @@ -11,6 +14,9 @@ import { SessionData, } from "../../../../main/typescript/public-api"; +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + const COMMIT_PREPARATION_REQUEST_MESSAGE_HASH = "dummyCommitPreparationRequestMessageHash"; @@ -22,7 +28,7 @@ let sequenceNumber: number; let sessionID: string; let step: number; -beforeEach(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -37,6 +43,18 @@ beforeEach(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + sequenceNumber = randomInt(100); sessionID = uuidV4(); step = 1; @@ -48,6 +66,12 @@ beforeEach(() => { recipientGatewayPubkey: pluginRecipientGateway.pubKey, commitPrepareRequestMessageHash: COMMIT_PREPARATION_REQUEST_MESSAGE_HASH, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); @@ -60,11 +84,11 @@ test("valid commit preparation response", async () => { serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashCommitPrep: COMMIT_PREPARATION_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - commitPreparationResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + commitPreparationResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign( JSON.stringify(commitPreparationResponse), ), @@ -84,13 +108,12 @@ test("valid commit preparation response", async () => { if (retrievedSessionData == undefined) throw new Error("Test Failed."); expect(retrievedSessionData.id).toBe(sessionID); - expect(retrievedSessionData.step).toBe(step + 1); expect(retrievedSessionData.commitPrepareResponseMessageHash).toBe( messageHash, ); expect( retrievedSessionData.serverSignatureCommitPreparationResponseMessage, - ).toBe(commitPreparationResponse.serverSignature); + ).toBe(commitPreparationResponse.signature); }); test("commit preparation response invalid because of wrong previous message hash", async () => { @@ -100,11 +123,11 @@ test("commit preparation response invalid because of wrong previous message hash serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashCommitPrep: "wrongMessageHash", - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - commitPreparationResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + commitPreparationResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign( JSON.stringify(commitPreparationResponse), ), @@ -131,11 +154,11 @@ test("commit preparation response invalid because of wrong signature", async () serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashCommitPrep: COMMIT_PREPARATION_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - commitPreparationResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + commitPreparationResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign("somethingWrong"), ); @@ -150,3 +173,36 @@ test("commit preparation response invalid because of wrong signature", async () expect(ex.message).toMatch("message signature verification failed"), ); }); + +test("timeout in commit preparation request because no server gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "", + recipientBasePath: "http://wrongpath", + lastSequenceNumber: 77, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + lockEvidenceResponseMessageHash: "dummyLockEvidenceResponseMessageHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendCommitPreparationRequest(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/lock-evidence.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/lock-evidence.test.ts index 5873e3b0e7..c52c5b75dd 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/lock-evidence.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/lock-evidence.test.ts @@ -1,7 +1,10 @@ import { randomInt } from "crypto"; import { SHA256 } from "crypto-js"; import { v4 as uuidV4 } from "uuid"; -import { checkValidLockEvidenceResponse } from "../../../../main/typescript/gateway/client/lock-evidence"; +import { + checkValidLockEvidenceResponse, + sendLockEvidenceRequest, +} from "../../../../main/typescript/gateway/client/lock-evidence"; import { OdapMessageType, PluginOdapGateway, @@ -11,6 +14,9 @@ import { SessionData, } from "../../../../main/typescript/public-api"; +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + const LOCK_EVIDENCE_REQUEST_MESSAGE_HASH = "dummyLockEvidenceRequestMessageHash"; @@ -22,7 +28,7 @@ let sequenceNumber: number; let sessionID: string; let step: number; -beforeEach(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -37,6 +43,18 @@ beforeEach(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + sequenceNumber = randomInt(100); sessionID = uuidV4(); step = 1; @@ -48,6 +66,12 @@ beforeEach(() => { recipientGatewayPubkey: pluginRecipientGateway.pubKey, lockEvidenceRequestMessageHash: LOCK_EVIDENCE_REQUEST_MESSAGE_HASH, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); @@ -60,11 +84,11 @@ test("valid lock evidence response", async () => { serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashLockEvidenceRequest: LOCK_EVIDENCE_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - lockEvidenceResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + lockEvidenceResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign(JSON.stringify(lockEvidenceResponse)), ); @@ -80,12 +104,11 @@ test("valid lock evidence response", async () => { if (retrievedSessionData == undefined) throw new Error("Test Failed."); expect(retrievedSessionData.id).toBe(sessionID); - expect(retrievedSessionData.step).toBe(step + 1); expect(retrievedSessionData.lockEvidenceResponseMessageHash).toBe( messageHash, ); expect(retrievedSessionData.serverSignatureLockEvidenceResponseMessage).toBe( - lockEvidenceResponse.serverSignature, + lockEvidenceResponse.signature, ); }); @@ -96,11 +119,11 @@ test("lock evidence response invalid because of wrong previous message hash", as serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashLockEvidenceRequest: "wrongMessageHash", - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - lockEvidenceResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + lockEvidenceResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign(JSON.stringify(lockEvidenceResponse)), ); @@ -125,11 +148,11 @@ test("lock evidence response invalid because of wrong signature", async () => { serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashLockEvidenceRequest: LOCK_EVIDENCE_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - lockEvidenceResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + lockEvidenceResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign("somethingWrong"), ); @@ -144,3 +167,39 @@ test("lock evidence response invalid because of wrong signature", async () => { expect(ex.message).toMatch("message signature verification failed"), ); }); + +test("timeout in lock evidence request because no server gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "", + recipientBasePath: "http://wrongpath", + lastSequenceNumber: 77, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + transferCommenceMessageResponseHash: + "dummyTransferCommenceMessageResponseHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await pluginSourceGateway.lockFabricAsset(sessionID); + + await sendLockEvidenceRequest(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-commence.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-commence.test.ts index 275438c381..3156e7ac20 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-commence.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-commence.test.ts @@ -1,16 +1,23 @@ import { randomInt } from "crypto"; import { SHA256 } from "crypto-js"; import { v4 as uuidV4 } from "uuid"; -import { checkValidTransferCommenceResponse } from "../../../../main/typescript/gateway/client/transfer-commence"; +import { + checkValidTransferCommenceResponse, + sendTransferCommenceRequest, +} from "../../../../main/typescript/gateway/client/transfer-commence"; import { OdapMessageType, PluginOdapGateway, } from "../../../../main/typescript/gateway/plugin-odap-gateway"; import { + AssetProfile, SessionData, TransferCommenceV1Response, } from "../../../../main/typescript/public-api"; +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + const COMMENCE_REQUEST_MESSAGE_HASH = "dummyCommenceRequestMessageHash"; let sourceGatewayConstructor; @@ -21,7 +28,7 @@ let sequenceNumber: number; let sessionID: string; let step: number; -beforeEach(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -36,6 +43,18 @@ beforeEach(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + sequenceNumber = randomInt(100); sessionID = uuidV4(); step = 1; @@ -47,6 +66,12 @@ beforeEach(() => { recipientGatewayPubkey: pluginRecipientGateway.pubKey, transferCommenceMessageRequestHash: COMMENCE_REQUEST_MESSAGE_HASH, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); @@ -59,11 +84,11 @@ test("valid transfer commence response", async () => { serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashCommenceRequest: COMMENCE_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - transferCommenceResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + transferCommenceResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign(JSON.stringify(transferCommenceResponse)), ); @@ -81,13 +106,12 @@ test("valid transfer commence response", async () => { if (retrievedSessionData == undefined) throw new Error("Test Failed."); expect(retrievedSessionData.id).toBe(sessionID); - expect(retrievedSessionData.step).toBe(step + 1); expect(retrievedSessionData.transferCommenceMessageResponseHash).toBe( messageHash, ); expect( retrievedSessionData.serverSignatureTransferCommenceResponseMessage, - ).toBe(transferCommenceResponse.serverSignature); + ).toBe(transferCommenceResponse.signature); }); test("transfer commence response invalid because of wrong previous message hash", async () => { @@ -97,11 +121,11 @@ test("transfer commence response invalid because of wrong previous message hash" serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashCommenceRequest: "wrongMessageHash", - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - transferCommenceResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + transferCommenceResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign(JSON.stringify(transferCommenceResponse)), ); @@ -126,11 +150,11 @@ test("transfer commence response invalid because of wrong signature", async () = serverIdentityPubkey: pluginRecipientGateway.pubKey, clientIdentityPubkey: pluginSourceGateway.pubKey, hashCommenceRequest: COMMENCE_REQUEST_MESSAGE_HASH, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - transferCommenceResponse.serverSignature = pluginRecipientGateway.bufArray2HexStr( + transferCommenceResponse.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign("somethingWrong"), ); @@ -145,3 +169,52 @@ test("transfer commence response invalid because of wrong signature", async () = expect(ex.message).toMatch("message signature verification failed"), ); }); + +test("timeout in transfer commence request because no server gateway is connected", async () => { + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const sessionData: SessionData = { + id: sessionID, + step: 1, + version: "0.0.0", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + assetProfile: assetProfile, + loggingProfile: "dummyLoggingProfile", + sourceBasePath: "", + recipientBasePath: "http://wrongpath", + originatorPubkey: "http://wrongpath", + beneficiaryPubkey: "http://wrongpath", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + lastSequenceNumber: 77, + sourceGatewayDltSystem: "DLT1", + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + recipientGatewayDltSystem: "DLT2", + initializationResponseMessageHash: "dummyInitializationResponseMessageHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendTransferCommenceRequest(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-initiation.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-initialization.test.ts similarity index 65% rename from packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-initiation.test.ts rename to packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-initialization.test.ts index 59b51b778a..4e22d0bc1b 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-initiation.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/client/transfer-initialization.test.ts @@ -1,7 +1,10 @@ import { randomInt } from "crypto"; import { SHA256 } from "crypto-js"; import { v4 as uuidV4 } from "uuid"; -import { checkValidInitializationResponse } from "../../../../main/typescript/gateway/client/transfer-initialization"; +import { + checkValidInitializationResponse, + sendTransferInitializationRequest, +} from "../../../../main/typescript/gateway/client/transfer-initialization"; import { OdapMessageType, PluginOdapGateway, @@ -9,8 +12,12 @@ import { import { TransferInitializationV1Response, SessionData, + AssetProfile, } from "../../../../main/typescript/public-api"; +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + const INITIALIZATION_REQUEST_MESSAGE_HASH = "dummyInitializationRequestMessageHash"; @@ -22,7 +29,7 @@ let sequenceNumber: number; let sessionID: string; let step: number; -beforeEach(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -37,6 +44,18 @@ beforeEach(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + sequenceNumber = randomInt(100); sessionID = uuidV4(); step = 1; @@ -48,6 +67,12 @@ beforeEach(() => { recipientGatewayPubkey: pluginRecipientGateway.pubKey, initializationRequestMessageHash: INITIALIZATION_REQUEST_MESSAGE_HASH, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); @@ -61,11 +86,11 @@ test("valid transfer initiation response", async () => { timeStamp: Date.now().toString(), processedTimeStamp: Date.now().toString(), serverIdentityPubkey: pluginRecipientGateway.pubKey, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - initializationResponseMessage.serverSignature = pluginRecipientGateway.bufArray2HexStr( + initializationResponseMessage.signature = PluginOdapGateway.bufArray2HexStr( await pluginRecipientGateway.sign( JSON.stringify(initializationResponseMessage), ), @@ -85,7 +110,6 @@ test("valid transfer initiation response", async () => { if (retrievedSessionData == undefined) throw new Error("Test Failed."); expect(retrievedSessionData.id).toBe(sessionID); - expect(retrievedSessionData.step).toBe(step + 1); expect(retrievedSessionData.recipientGatewayPubkey).toBe( pluginRecipientGateway.pubKey, ); @@ -108,11 +132,11 @@ test("transfer initiation response invalid because of wrong previous message has timeStamp: Date.now().toString(), processedTimeStamp: Date.now().toString(), serverIdentityPubkey: pluginRecipientGateway.pubKey, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - initializationResponseMessage.serverSignature = pluginSourceGateway.bufArray2HexStr( + initializationResponseMessage.signature = PluginOdapGateway.bufArray2HexStr( await pluginSourceGateway.sign( JSON.stringify(initializationResponseMessage), ), @@ -140,11 +164,11 @@ test("transfer initiation response invalid because it does not match transfer in timeStamp: Date.now().toString(), processedTimeStamp: Date.now().toString(), serverIdentityPubkey: pluginRecipientGateway.pubKey, - serverSignature: "", + signature: "", sequenceNumber: sequenceNumber, }; - initializationResponseMessage.serverSignature = pluginSourceGateway.bufArray2HexStr( + initializationResponseMessage.signature = PluginOdapGateway.bufArray2HexStr( await pluginSourceGateway.sign( JSON.stringify(initializationResponseMessage), ), @@ -161,3 +185,47 @@ test("transfer initiation response invalid because it does not match transfer in expect(ex.message).toMatch("session data is undefined"), ); }); + +test("timeout in transfer initiation request because no server gateway is connected", async () => { + const expiryDate = new Date(2060, 11, 24).toString(); + const assetProfile: AssetProfile = { expirationDate: expiryDate }; + + const sessionData: SessionData = { + id: sessionID, + step: 1, + version: "0.0.0", + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + payloadProfile: { + assetProfile: assetProfile, + capabilities: "", + }, + loggingProfile: "dummyLoggingProfile", + sourceBasePath: "", + recipientBasePath: "http://wrongpath", + accessControlProfile: "dummyAccessControlProfile", + applicationProfile: "dummyApplicationProfile", + lastSequenceNumber: 77, + sourceGatewayDltSystem: "DLT1", + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + recipientGatewayDltSystem: "DLT2", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendTransferInitializationRequest(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/logging.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/logging.test.ts new file mode 100644 index 0000000000..165ecdd14b --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/logging.test.ts @@ -0,0 +1,448 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { v4 as uuidV4 } from "uuid"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { + OdapLocalLog, + SessionData, +} from "../../../../main/typescript/public-api"; +import { SHA256 } from "crypto-js"; +import { knexClientConnection, knexServerConnection } from "../../knex.config"; + +const logLevel: LogLevelDesc = "TRACE"; + +let sourceGatewayConstructor: IPluginOdapGatewayConstructorOptions; +let recipientGatewayConstructor: IPluginOdapGatewayConstructorOptions; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; +let sessionID: string; +let step: number; +let type: string; +let type2: string; +let type3: string; +let type4: string; +let operation: string; +let odapLog: OdapLocalLog; +let odapLog2: OdapLocalLog; +let odapLog3: OdapLocalLog; +let odapLog4: OdapLocalLog; +let sessionData: SessionData; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + + sourceGatewayConstructor = { + name: "plugin-odap-gateway#sourceGateway", + dltIDs: ["DLT2"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexClientConnection, + }; + + recipientGatewayConstructor = { + name: "plugin-odap-gateway#recipientGateway", + dltIDs: ["DLT1"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexServerConnection, + }; +}); + +beforeEach(async () => { + sessionID = uuidv4(); + step = 3; + type = "type1"; + operation = "operation1"; + + pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); + pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + + sessionData = { + id: sessionID, + step: step, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + }; + + odapLog = { + sessionID: sessionID, + type: type, + operation: operation, + data: JSON.stringify(sessionData), + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + pluginRecipientGateway.sessions.set(sessionID, sessionData); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); + +test("successful translation of log keys", async () => { + expect(PluginOdapGateway.getOdapLogKey(sessionID, type, operation)).toBe( + `${sessionID}-${type}-${operation}`, + ); +}); + +test("successful logging of proof to ipfs and sqlite", async () => { + const claim = "claim"; + const odapLogKey = PluginOdapGateway.getOdapLogKey( + sessionID, + "proof", + "lock", + ); + + await pluginSourceGateway.storeOdapProof({ + sessionID, + type: "proof", + operation: "lock", + data: claim, + }); + + const retrievedLogIPFS = await pluginSourceGateway.getLogFromIPFS(odapLogKey); + const retrievedLogDB = await pluginSourceGateway.getLogFromDatabase( + odapLogKey, + ); + + if (retrievedLogDB == undefined || retrievedLogIPFS == undefined) { + throw new Error("Test Failed"); + } + + expect(retrievedLogIPFS.key).toBe(odapLogKey); + expect(retrievedLogDB.key).toBe(odapLogKey); + expect(retrievedLogIPFS.hash).toBe(SHA256(claim).toString()); + expect( + pluginRecipientGateway.verifySignature( + retrievedLogIPFS, + pluginSourceGateway.pubKey, + ), + ).toBe(true); + + expect(1).toBe(1); +}); + +test("successful logging to ipfs and sqlite", async () => { + const odapLogKey = PluginOdapGateway.getOdapLogKey( + sessionID, + type, + operation, + ); + + await pluginSourceGateway.storeOdapLog(odapLog); + + const retrievedLogIPFS = await pluginSourceGateway.getLogFromIPFS(odapLogKey); + const retrievedLogDB = await pluginSourceGateway.getLogFromDatabase( + odapLogKey, + ); + + if ( + retrievedLogIPFS == undefined || + retrievedLogDB == undefined || + retrievedLogDB.data == undefined || + odapLog.data == undefined + ) { + throw new Error("Test failed"); + } + + expect(retrievedLogIPFS.signerPubKey).toBe(pluginSourceGateway.pubKey); + expect(retrievedLogIPFS.hash).toBe( + SHA256( + JSON.stringify(odapLog, [ + "sessionID", + "type", + "key", + "operation", + "timestamp", + "data", + ]), + ).toString(), + ); + expect(retrievedLogIPFS.key).toBe(odapLogKey); + + expect(retrievedLogDB.type).toBe(odapLog.type); + expect(retrievedLogDB.operation).toBe(odapLog.operation); + expect(retrievedLogDB.data).toBe(odapLog.data); + + expect(retrievedLogDB.timestamp).toBe(odapLog.timestamp); + expect(retrievedLogDB.type).toBe(odapLog.type); + expect(retrievedLogDB.operation).toBe(odapLog.operation); + expect(retrievedLogDB.sessionID).toBe(odapLog.sessionID); + expect(retrievedLogDB.key).toBe(odapLogKey); +}); + +test("successful retrieval of last log", async () => { + type2 = type + "2"; + type3 = type + "3"; + + odapLog2 = { + sessionID: sessionID, + type: type2, + operation: operation, + data: JSON.stringify(sessionData), + }; + + odapLog3 = { + sessionID: sessionID, + type: type3, + operation: operation, + data: JSON.stringify(sessionData), + }; + + await pluginSourceGateway.storeOdapLog(odapLog2); + await pluginSourceGateway.storeOdapLog(odapLog3); + + const lastLog = await pluginSourceGateway.getLastLogFromDatabase(sessionID); + + if ( + lastLog == undefined || + odapLog3 == undefined || + lastLog.data == undefined || + odapLog3.data == undefined + ) { + throw new Error("Test failed"); + } + + expect(lastLog.type).toBe(odapLog3.type); + expect(lastLog.operation).toBe(odapLog3.operation); + expect(lastLog.data).toBe(odapLog3.data); + + expect(lastLog.timestamp).toBe(odapLog3.timestamp); + expect(lastLog.type).toBe(odapLog3.type); + expect(lastLog.operation).toBe(odapLog3.operation); + expect(lastLog.sessionID).toBe(odapLog3.sessionID); + expect(lastLog.key).toBe( + PluginOdapGateway.getOdapLogKey(sessionID, type3, operation), + ); +}); + +test("successful retrieval of logs more recent than another log", async () => { + type2 = type + "2"; + type3 = type + "3"; + + odapLog2 = { + sessionID: sessionID, + type: type2, + operation: operation, + data: JSON.stringify(sessionData), + }; + + odapLog3 = { + sessionID: sessionID, + type: type3, + operation: operation, + data: JSON.stringify(sessionData), + }; + + await pluginSourceGateway.storeOdapLog(odapLog2); + + const referenceTimestamp = Date.now().toString(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await pluginSourceGateway.storeOdapLog(odapLog); + await pluginSourceGateway.storeOdapLog(odapLog3); + + const moreRecentLogs = await pluginSourceGateway.getLogsMoreRecentThanTimestamp( + referenceTimestamp, + ); + + if ( + moreRecentLogs == undefined || + moreRecentLogs.length != 2 || + moreRecentLogs[0].data == undefined || + moreRecentLogs[1].data == undefined || + odapLog.data == undefined || + odapLog3.data == undefined + ) { + throw new Error("Test failed"); + } + + expect(moreRecentLogs[0].type).toBe(odapLog.type); + expect(moreRecentLogs[0].operation).toBe(odapLog.operation); + expect(moreRecentLogs[0].data).toBe(odapLog.data); + + expect(moreRecentLogs[0].timestamp).toBe(odapLog.timestamp); + expect(moreRecentLogs[0].type).toBe(odapLog.type); + expect(moreRecentLogs[0].operation).toBe(odapLog.operation); + expect(moreRecentLogs[0].sessionID).toBe(odapLog.sessionID); + expect(moreRecentLogs[0].key).toBe( + PluginOdapGateway.getOdapLogKey(sessionID, type, operation), + ); + + expect(moreRecentLogs[1].type).toBe(odapLog3.type); + expect(moreRecentLogs[1].operation).toBe(odapLog3.operation); + expect(moreRecentLogs[1].data).toBe(odapLog3.data); + + expect(moreRecentLogs[1].timestamp).toBe(odapLog3.timestamp); + expect(moreRecentLogs[1].type).toBe(odapLog3.type); + expect(moreRecentLogs[1].operation).toBe(odapLog3.operation); + expect(moreRecentLogs[1].sessionID).toBe(odapLog3.sessionID); + expect(moreRecentLogs[1].key).toBe( + PluginOdapGateway.getOdapLogKey(sessionID, type3, operation), + ); +}); + +test("successful retrieval of logs when there are no more recent logs", async () => { + const moreRecentLogs = await pluginSourceGateway.getLogsMoreRecentThanTimestamp( + Date.now().toString(), + ); + + expect(moreRecentLogs).not.toBeUndefined(); + expect(moreRecentLogs?.length).toBe(0); +}); + +test("successful recover of sessions after crash", async () => { + const newSessionID = uuidv4(); + const newStep = 4; + + type2 = type + "2"; + type3 = type + "3"; + type4 = type + "4"; + + const data = { + id: newSessionID, + step: newStep, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + }; + + odapLog2 = { + sessionID: sessionID, + type: type2, + operation: operation, + data: JSON.stringify(sessionData), + }; + + odapLog3 = { + sessionID: sessionID, + type: type3, + operation: operation, + data: JSON.stringify(sessionData), + }; + + odapLog4 = { + sessionID: newSessionID, + type: type4, + operation: operation, + data: JSON.stringify(data), + }; + + pluginSourceGateway.sessions.set(newSessionID, data); + + await pluginSourceGateway.storeOdapLog(odapLog); + await pluginSourceGateway.storeOdapLog(odapLog3); + await pluginSourceGateway.storeOdapLog(odapLog2); + await pluginSourceGateway.storeOdapLog(odapLog4); + + // simulate the crash of one gateway + pluginSourceGateway.database?.destroy(); + const newPluginSourceGateway = new PluginOdapGateway( + sourceGatewayConstructor, + ); + + await newPluginSourceGateway.recoverOpenSessions(false); + + const sessions = newPluginSourceGateway.sessions.values(); + + expect(newPluginSourceGateway.sessions.size).toBe(2); + + for (const session of sessions) { + if (session.id == sessionID) { + expect(session.step).toBe(step); + } else if (session.id == newSessionID) { + expect(session.step).toBe(newStep); + } else { + throw new Error("Test failed."); + } + + expect(data.sourceGatewayPubkey).toBe(newPluginSourceGateway.pubKey); + expect(data.recipientGatewayPubkey).toBe(pluginRecipientGateway.pubKey); + } + + newPluginSourceGateway.database?.destroy(); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-success.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-success.test.ts new file mode 100644 index 0000000000..890fca5615 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-success.test.ts @@ -0,0 +1,169 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { v4 as uuidV4 } from "uuid"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginOdapGateway } from "../../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { + RecoverSuccessV1Message, + SessionData, +} from "../../../../main/typescript/public-api"; +import { randomInt } from "crypto"; +import { checkValidRecoverSuccessMessage } from "../../../../main/typescript/gateway/recovery/recover-success"; + +const logLevel: LogLevelDesc = "TRACE"; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; +let sessionID: string; +let sessionData: SessionData; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +let sequenceNumber: number; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + // t.comment(`Go IPFS Test Container API URL: ${ipfsApiUrl}`); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); + +beforeEach(async () => { + const sourceGatewayConstructor = { + name: "plugin-odap-gateway#sourceGateway", + dltIDs: ["DLT2"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + }; + const recipientGatewayConstructor = { + name: "plugin-odap-gateway#recipientGateway", + dltIDs: ["DLT1"], + instanceId: uuidV4(), + }; + + pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); + pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + + sessionID = uuidv4(); + sequenceNumber = randomInt(100); + + sessionData = { + lastSequenceNumber: sequenceNumber, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + pluginRecipientGateway.sessions.set(sessionID, sessionData); +}); + +test("valid recover success message from client", async () => { + const recoverSuccessMessage: RecoverSuccessV1Message = { + sessionID: sessionID, + signature: "", + success: true, + }; + + recoverSuccessMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginSourceGateway.sign(JSON.stringify(recoverSuccessMessage)), + ); + + await checkValidRecoverSuccessMessage( + recoverSuccessMessage, + pluginRecipientGateway, + ); +}); + +test("valid recover success message from server", async () => { + const recoverSuccessMessage: RecoverSuccessV1Message = { + sessionID: sessionID, + signature: "", + success: true, + }; + + recoverSuccessMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginRecipientGateway.sign(JSON.stringify(recoverSuccessMessage)), + ); + + await checkValidRecoverSuccessMessage( + recoverSuccessMessage, + pluginSourceGateway, + ).catch(() => { + throw new Error("Test failed"); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update-ack.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update-ack.test.ts new file mode 100644 index 0000000000..ae49cc9088 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update-ack.test.ts @@ -0,0 +1,171 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { v4 as uuidV4 } from "uuid"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginOdapGateway } from "../../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { + RecoverUpdateAckV1Message, + SessionData, +} from "../../../../main/typescript/public-api"; +import { randomInt } from "crypto"; +import { checkValidRecoverUpdateAckMessage } from "../../../../main/typescript/gateway/recovery/recover-update-ack"; + +const logLevel: LogLevelDesc = "TRACE"; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; +let sessionID: string; +let sessionData: SessionData; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +let sequenceNumber: number; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + // t.comment(`Go IPFS Test Container API URL: ${ipfsApiUrl}`); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); + +beforeEach(async () => { + const sourceGatewayConstructor = { + name: "plugin-odap-gateway#sourceGateway", + dltIDs: ["DLT2"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + }; + const recipientGatewayConstructor = { + name: "plugin-odap-gateway#recipientGateway", + dltIDs: ["DLT1"], + instanceId: uuidV4(), + }; + + pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); + pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + + sessionID = uuidv4(); + sequenceNumber = randomInt(100); + + sessionData = { + lastSequenceNumber: sequenceNumber, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + pluginRecipientGateway.sessions.set(sessionID, sessionData); +}); + +test("valid recover update ack message from client", async () => { + const recoverUpdateAckMessage: RecoverUpdateAckV1Message = { + sessionID: sessionID, + signature: "", + success: true, + changedEntriesHash: [], + }; + + recoverUpdateAckMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginSourceGateway.sign(JSON.stringify(recoverUpdateAckMessage)), + ); + + await checkValidRecoverUpdateAckMessage( + recoverUpdateAckMessage, + pluginRecipientGateway, + ); +}); + +test("valid recover update ack message from server", async () => { + const recoverUpdateAckMessage: RecoverUpdateAckV1Message = { + sessionID: sessionID, + signature: "", + success: true, + changedEntriesHash: [], + }; + + recoverUpdateAckMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginRecipientGateway.sign(JSON.stringify(recoverUpdateAckMessage)), + ); + + await checkValidRecoverUpdateAckMessage( + recoverUpdateAckMessage, + pluginSourceGateway, + ).catch(() => { + throw new Error("Test failed"); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update.test.ts new file mode 100644 index 0000000000..291c0370b3 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover-update.test.ts @@ -0,0 +1,276 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { v4 as uuidV4 } from "uuid"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginOdapGateway } from "../../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { + OdapLocalLog, + RecoverUpdateV1Message, + RecoverV1Message, + SessionData, +} from "../../../../main/typescript/public-api"; +import { randomInt } from "crypto"; +import { + checkValidRecoverUpdateMessage, + sendRecoverUpdateMessage, +} from "../../../../main/typescript/gateway/recovery/recover-update"; +import { knexClientConnection, knexServerConnection } from "../../knex.config"; +import { checkValidRecoverMessage } from "../../../../main/typescript/gateway/recovery/recover"; + +const logLevel: LogLevelDesc = "TRACE"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; +let sessionID: string; +let sessionData: SessionData; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +let sequenceNumber: number; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + // t.comment(`Go IPFS Test Container API URL: ${ipfsApiUrl}`); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); + +beforeEach(async () => { + const sourceGatewayConstructor = { + name: "plugin-odap-gateway#sourceGateway", + dltIDs: ["DLT2"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexClientConnection, + }; + const recipientGatewayConstructor = { + name: "plugin-odap-gateway#recipientGateway", + dltIDs: ["DLT1"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexServerConnection, + }; + + pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); + pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + + sessionID = uuidv4(); + sequenceNumber = randomInt(100); + + sessionData = { + lastSequenceNumber: sequenceNumber, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + pluginRecipientGateway.sessions.set(sessionID, sessionData); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); + +test("valid recover update message from server", async () => { + const recoverUpdateMessage: RecoverUpdateV1Message = { + sessionID: sessionID, + recoveredLogs: [], + signature: "", + }; + + recoverUpdateMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginRecipientGateway.sign(JSON.stringify(recoverUpdateMessage)), + ); + + await checkValidRecoverUpdateMessage( + recoverUpdateMessage, + pluginSourceGateway, + ).catch(() => { + throw new Error("Test failed"); + }); +}); + +test("check valid built of recover update message", async () => { + const sessionData1: SessionData = { + id: sessionID, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "", + recipientBasePath: "", + lastSequenceNumber: 1, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData1); + + const sessionData2 = { + id: sessionID, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "", + recipientBasePath: "", + lastSequenceNumber: 2, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + }; + + pluginRecipientGateway.sessions.set(sessionID, sessionData2); + + const odapLog1: OdapLocalLog = { + sessionID: sessionID, + type: "init", + operation: "validate", + data: JSON.stringify(sessionData), + }; + + new Promise((resolve) => setTimeout(resolve, 1000)); + const firstTimestamp = Date.now().toString(); + new Promise((resolve) => setTimeout(resolve, 1000)); + + await pluginSourceGateway.storeOdapLog(odapLog1); + + const odapLog2: OdapLocalLog = { + sessionID: sessionID, + type: "exec", + operation: "validate", + data: JSON.stringify(sessionData), + }; + + await pluginRecipientGateway.storeOdapLog(odapLog2); + + const odapLog3: OdapLocalLog = { + sessionID: sessionID, + type: "done", + operation: "validate", + data: JSON.stringify(sessionData), + }; + + await pluginRecipientGateway.storeOdapLog(odapLog3); + + const odapLog4: OdapLocalLog = { + sessionID: sessionID, + type: "ack", + operation: "validate", + data: JSON.stringify(sessionData), + }; + + await pluginRecipientGateway.storeOdapLog(odapLog4); + + const recoverMessage: RecoverV1Message = { + sessionID: sessionID, + odapPhase: "1", + sequenceNumber: sequenceNumber, + lastLogEntryTimestamp: firstTimestamp, + signature: "", + }; + + expect(1).toBe(1); + recoverMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginSourceGateway.sign(JSON.stringify(recoverMessage)), + ); + + await checkValidRecoverMessage(recoverMessage, pluginRecipientGateway); + + const recoverUpdateMessage: RecoverUpdateV1Message | void = await sendRecoverUpdateMessage( + sessionID, + pluginRecipientGateway, + false, + ); + + if (recoverUpdateMessage == void 0) { + throw new Error("Test Failed"); + } + + expect(recoverUpdateMessage.recoveredLogs.length).toBe(3); + + await checkValidRecoverUpdateMessage( + recoverUpdateMessage, + pluginSourceGateway, + ); + + expect(pluginSourceGateway.sessions.size).toBe(1); + + const [sessionId] = pluginSourceGateway.sessions.keys(); + + expect(pluginRecipientGateway.sessions.get(sessionId)).toBe(sessionData2); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover.test.ts new file mode 100644 index 0000000000..4a491a55a9 --- /dev/null +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/recovery/recover.test.ts @@ -0,0 +1,208 @@ +import http, { Server } from "http"; +import type { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import "jest-extended"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { create } from "ipfs-http-client"; +import bodyParser from "body-parser"; +import express from "express"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { + IListenOptions, + LogLevelDesc, + Secp256k1Keys, + Servers, +} from "@hyperledger/cactus-common"; +import { v4 as uuidV4 } from "uuid"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { + IPluginOdapGatewayConstructorOptions, + PluginOdapGateway, +} from "../../../../main/typescript/gateway/plugin-odap-gateway"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import { RecoverV1Message } from "../../../../main/typescript/public-api"; +import { randomInt } from "crypto"; +import { checkValidRecoverMessage } from "../../../../main/typescript/gateway/recovery/recover"; +import { knexClientConnection, knexServerConnection } from "../../knex.config"; + +const logLevel: LogLevelDesc = "TRACE"; + +let sourceGatewayConstructor: IPluginOdapGatewayConstructorOptions; +let recipientGatewayConstructor: IPluginOdapGatewayConstructorOptions; + +let pluginSourceGateway: PluginOdapGateway; +let pluginRecipientGateway: PluginOdapGateway; +let sessionID: string; + +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +let sequenceNumber: number; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); + + sourceGatewayConstructor = { + name: "plugin-odap-gateway#sourceGateway", + dltIDs: ["DLT2"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexClientConnection, + }; + recipientGatewayConstructor = { + name: "plugin-odap-gateway#recipientGateway", + dltIDs: ["DLT1"], + instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + keyPair: Secp256k1Keys.generateKeyPairsBuffer(), + knexConfig: knexServerConnection, + }; +}); + +beforeEach(async () => { + sessionID = uuidv4(); + sequenceNumber = randomInt(100); + + pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); + pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + + const sessionData = { + lastSequenceNumber: sequenceNumber, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + step: 0, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + pluginRecipientGateway.sessions.set(sessionID, sessionData); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); + +test("valid recover message request from client", async () => { + const recoverMessage: RecoverV1Message = { + sessionID: sessionID, + odapPhase: "1", + sequenceNumber: sequenceNumber, + lastLogEntryTimestamp: "sometimestamp", + signature: "", + }; + + recoverMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginSourceGateway.sign(JSON.stringify(recoverMessage)), + ); + + await checkValidRecoverMessage(recoverMessage, pluginRecipientGateway); +}); + +test("valid recover message request from server", async () => { + const recoverMessage: RecoverV1Message = { + sessionID: sessionID, + odapPhase: "1", + sequenceNumber: sequenceNumber, + lastLogEntryTimestamp: "sometimestamp", + signature: "", + }; + + recoverMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginRecipientGateway.sign(JSON.stringify(recoverMessage)), + ); + + await checkValidRecoverMessage(recoverMessage, pluginSourceGateway).catch( + () => { + throw new Error("Test failed"); + }, + ); +}); + +test("recover message request from client with wrong signature", async () => { + const recoverMessage: RecoverV1Message = { + sessionID: sessionID, + odapPhase: "1", + sequenceNumber: sequenceNumber, + lastLogEntryTimestamp: "sometimestamp", + signature: "", + }; + + recoverMessage.signature = PluginOdapGateway.bufArray2HexStr( + pluginRecipientGateway.sign(JSON.stringify("wrongRecoverMessage")), + ); + + await checkValidRecoverMessage(recoverMessage, pluginSourceGateway) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => + expect(ex.message).toMatch("message signature verification failed"), + ); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-final.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-final.test.ts index 36ef97e1bc..1fc5685e4f 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-final.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-final.test.ts @@ -1,16 +1,41 @@ +import { randomInt } from "crypto"; +import { v4 as uuidv4 } from "uuid"; +import bodyParser from "body-parser"; +import http, { Server } from "http"; +import { create } from "ipfs-http-client"; +import { SHA256 } from "crypto-js"; import { IPluginOdapGatewayConstructorOptions, OdapMessageType, PluginOdapGateway, } from "../../../../main/typescript/gateway/plugin-odap-gateway"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; import { CommitFinalV1Request, SessionData, } from "../../../../main/typescript/generated/openapi/typescript-axios/api"; -import { v4 as uuidV4 } from "uuid"; -import { SHA256 } from "crypto-js"; -import { randomInt } from "crypto"; -import { checkValidCommitFinalRequest } from "../../../../main/typescript/gateway/server/commit-final"; +import { + checkValidCommitFinalRequest, + sendCommitFinalResponse, +} from "../../../../main/typescript/gateway/server/commit-final"; +import { + IListenOptions, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import express from "express"; +import { AddressInfo } from "net"; +import { knexClientConnection, knexServerConnection } from "../../knex.config"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const logLevel: LogLevelDesc = "TRACE"; + +const COMMIT_FINAL_CLAIM = "dummyCommitFinalClaim"; let sourceGatewayConstructor: IPluginOdapGatewayConstructorOptions; let recipientGatewayConstructor: IPluginOdapGatewayConstructorOptions; @@ -21,26 +46,90 @@ let sessionData: SessionData; let sessionID: string; let sequenceNumber: number; -beforeEach(() => { +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); + +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], - instanceId: uuidV4(), + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + knexConfig: knexClientConnection, }; + recipientGatewayConstructor = { name: "plugin-odap-gateway#recipientGateway", dltIDs: ["DLT1"], - instanceId: uuidV4(), + instanceId: uuidv4(), + ipfsPath: ipfsApiHost, + knexConfig: knexServerConnection, }; pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + dummyCommitPreparationResponseMessageHash = SHA256( "commitPreparationResponseMessageData", ).toString(); - sessionID = uuidV4(); + sessionID = uuidv4(); sequenceNumber = randomInt(100); sessionData = { @@ -50,10 +139,40 @@ beforeEach(() => { commitPrepareResponseMessageHash: dummyCommitPreparationResponseMessageHash, step: 2, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); pluginRecipientGateway.sessions.set(sessionID, sessionData); + + await pluginSourceGateway.storeOdapProof({ + sessionID: sessionID, + type: "proof", + operation: "delete", + data: COMMIT_FINAL_CLAIM, + }); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); +}); + +afterEach(async () => { + await pluginSourceGateway.database?.destroy(); + await pluginRecipientGateway.database?.destroy(); }); test("valid commit final request", async () => { @@ -62,13 +181,13 @@ test("valid commit final request", async () => { messageType: OdapMessageType.CommitFinalRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommitPrepareAck: dummyCommitPreparationResponseMessageHash, - commitFinalClaim: "dummyFinalClaim", + commitFinalClaim: COMMIT_FINAL_CLAIM, sequenceNumber: sequenceNumber + 1, }; - commitFinalRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitFinalRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitFinalRequestMessage)), ); @@ -85,30 +204,30 @@ test("valid commit final request", async () => { if (sessionInfo == null) throw new Error("Test Failed"); - expect(sessionInfo.commitFinalClaim).toBe("dummyFinalClaim"); + expect(sessionInfo.commitFinalClaim).toBe(COMMIT_FINAL_CLAIM); expect(sessionInfo.commitFinalRequestMessageHash).toBe(requestHash); expect(sessionInfo.clientSignatureCommitFinalRequestMessage).toBe( - commitFinalRequestMessage.clientSignature, + commitFinalRequestMessage.signature, ); }); test("commit final request with wrong sessionId", async () => { - const wrongSessionId = uuidV4(); + const wrongSessionId = uuidv4(); const commitFinalRequestMessage: CommitFinalV1Request = { sessionID: wrongSessionId, messageType: OdapMessageType.CommitFinalRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommitPrepareAck: dummyCommitPreparationResponseMessageHash, commitFinalClaim: "", sequenceNumber: sequenceNumber + 1, }; - commitFinalRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitFinalRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitFinalRequestMessage)), ); @@ -132,13 +251,13 @@ test("commit final request with wrong message type", async () => { messageType: OdapMessageType.CommitFinalResponse, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommitPrepareAck: dummyCommitPreparationResponseMessageHash, commitFinalClaim: "", sequenceNumber: sequenceNumber + 1, }; - commitFinalRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitFinalRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitFinalRequestMessage)), ); @@ -160,13 +279,13 @@ test("commit final request with wrong previous message hash", async () => { messageType: OdapMessageType.CommitFinalRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommitPrepareAck: "dummyCommitPreparationResponseMessageHash", - commitFinalClaim: "", + commitFinalClaim: COMMIT_FINAL_CLAIM, sequenceNumber: sequenceNumber + 1, }; - commitFinalRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitFinalRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitFinalRequestMessage)), ); @@ -181,3 +300,45 @@ test("commit final request with wrong previous message hash", async () => { expect(ex.message).toMatch("previous message hash does not match"), ); }); + +test("timeout in commit final response because no client gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "http://wrongpath", + recipientBasePath: "", + lastSequenceNumber: 77, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + commitAcknowledgementClaim: "dummyCommitAcknowledgementClaim", + commitFinalRequestMessageHash: "dummyCommitFinalRequestMessageHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendCommitFinalResponse(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + await pluginSourceGateway.database?.destroy(); + await pluginRecipientGateway.database?.destroy(); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-preparation.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-preparation.test.ts index 07fae0aee8..ea068ecdb4 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-preparation.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/commit-preparation.test.ts @@ -10,7 +10,13 @@ import { } from "../../../../main/typescript/generated/openapi/typescript-axios/api"; import { v4 as uuidV4 } from "uuid"; import { SHA256 } from "crypto-js"; -import { checkValidCommitPreparationRequest } from "../../../../main/typescript/gateway/server/commit-preparation"; +import { + checkValidCommitPreparationRequest, + sendCommitPreparationResponse, +} from "../../../../main/typescript/gateway/server/commit-preparation"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; let sourceGatewayConstructor: IPluginOdapGatewayConstructorOptions; let recipientGatewayConstructor: IPluginOdapGatewayConstructorOptions; @@ -21,7 +27,7 @@ let sessionData: SessionData; let sessionID: string; let sequenceNumber: number; -beforeEach(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -36,6 +42,18 @@ beforeEach(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + dummyLockEvidenceResponseMessageHash = SHA256( "lockEvidenceResponseMessageData", ).toString(); @@ -50,6 +68,12 @@ beforeEach(() => { lockEvidenceResponseMessageHash: dummyLockEvidenceResponseMessageHash, step: 2, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); @@ -62,12 +86,12 @@ test("valid commit prepare request", async () => { messageType: OdapMessageType.CommitPreparationRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashLockEvidenceAck: dummyLockEvidenceResponseMessageHash, sequenceNumber: sequenceNumber + 1, }; - commitPrepareRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitPrepareRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitPrepareRequestMessage)), ); @@ -87,7 +111,7 @@ test("valid commit prepare request", async () => { expect(sessionInfo.commitPrepareRequestMessageHash).toBe(requestHash); expect(sessionInfo.clientSignatureCommitPreparationRequestMessage).toBe( - commitPrepareRequestMessage.clientSignature, + commitPrepareRequestMessage.signature, ); }); @@ -99,12 +123,12 @@ test("commit prepare request with wrong sessionId", async () => { messageType: OdapMessageType.CommitPreparationRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashLockEvidenceAck: dummyLockEvidenceResponseMessageHash, sequenceNumber: sequenceNumber + 1, }; - commitPrepareRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitPrepareRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitPrepareRequestMessage)), ); @@ -128,12 +152,12 @@ test("commit prepare request with wrong message type", async () => { messageType: OdapMessageType.CommitFinalResponse, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashLockEvidenceAck: dummyLockEvidenceResponseMessageHash, sequenceNumber: sequenceNumber + 1, }; - commitPrepareRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitPrepareRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitPrepareRequestMessage)), ); @@ -157,12 +181,12 @@ test("commit prepare request with wrong previous message hash", async () => { messageType: OdapMessageType.CommitPreparationRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashLockEvidenceAck: "wrongLockEvidenceResponseMessageHash", sequenceNumber: sequenceNumber + 1, }; - commitPrepareRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + commitPrepareRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(commitPrepareRequestMessage)), ); @@ -177,3 +201,36 @@ test("commit prepare request with wrong previous message hash", async () => { expect(ex.message).toMatch("previous message hash does not match"), ); }); + +test("timeout in commit preparation response because no client gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "http://wrongpath", + recipientBasePath: "", + lastSequenceNumber: 77, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + commitPrepareRequestMessageHash: "dummyCommitPrepareRequestMessageHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendCommitPreparationResponse(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/lock-evidence.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/lock-evidence.test.ts index 44d8743e46..1b8c7e329e 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/lock-evidence.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/lock-evidence.test.ts @@ -1,4 +1,8 @@ import { randomInt } from "crypto"; +import { v4 as uuidv4 } from "uuid"; +import bodyParser from "body-parser"; +import http, { Server } from "http"; +import { create } from "ipfs-http-client"; import { IPluginOdapGatewayConstructorOptions, OdapMessageType, @@ -8,9 +12,31 @@ import { LockEvidenceV1Request, SessionData, } from "../../../../main/typescript/generated/openapi/typescript-axios/api"; +import { DefaultApi as ObjectStoreIpfsApi } from "@hyperledger/cactus-plugin-object-store-ipfs"; import { v4 as uuidV4 } from "uuid"; import { SHA256 } from "crypto-js"; -import { checkValidLockEvidenceRequest } from "../../../../main/typescript/gateway/server/lock-evidence"; +import { + checkValidLockEvidenceRequest, + sendLockEvidenceResponse, +} from "../../../../main/typescript/gateway/server/lock-evidence"; +import { + IListenOptions, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginObjectStoreIpfs } from "@hyperledger/cactus-plugin-object-store-ipfs"; +import { GoIpfsTestContainer } from "@hyperledger/cactus-test-tooling"; +import express from "express"; +import { AddressInfo } from "net"; +import { knexClientConnection, knexServerConnection } from "../../knex.config"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + +const logLevel: LogLevelDesc = "TRACE"; + +const LOCK_EVIDENCE_CLAIM = "dummyLockEvidenceClaim"; let sourceGatewayConstructor: IPluginOdapGatewayConstructorOptions; let recipientGatewayConstructor: IPluginOdapGatewayConstructorOptions; @@ -22,40 +48,134 @@ let lockExpiryDate: string; let sessionID: string; let sequenceNumber: number; -beforeEach(() => { +let ipfsContainer: GoIpfsTestContainer; +let ipfsServer: Server; +let ipfsApiHost: string; + +beforeAll(async () => { + ipfsContainer = new GoIpfsTestContainer({ logLevel }); + expect(ipfsContainer).not.toBeUndefined(); + + const container = await ipfsContainer.start(); + expect(container).not.toBeUndefined(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + ipfsServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server: ipfsServer, + }; + + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + ipfsApiHost = `http://${address}:${port}`; + + const config = new Configuration({ basePath: ipfsApiHost }); + const apiClient = new ObjectStoreIpfsApi(config); + + expect(apiClient).not.toBeUndefined(); + + const ipfsApiUrl = await ipfsContainer.getApiUrl(); + + const ipfsClientOrOptions = create({ + url: ipfsApiUrl, + }); + + const instanceId = uuidv4(); + const pluginIpfs = new PluginObjectStoreIpfs({ + parentDir: `/${uuidv4()}/${uuidv4()}/`, + logLevel, + instanceId, + ipfsClientOrOptions, + }); + + await pluginIpfs.getOrCreateWebServices(); + await pluginIpfs.registerWebServices(expressApp); +}); + +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + knexConfig: knexClientConnection, }; recipientGatewayConstructor = { name: "plugin-odap-gateway#recipientGateway", dltIDs: ["DLT1"], instanceId: uuidV4(), + ipfsPath: ipfsApiHost, + knexConfig: knexServerConnection, }; pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + dummyTransferCommenceResponseMessageHash = SHA256( "transferCommenceResponseMessageData", ).toString(); + lockExpiryDate = new Date().setDate(new Date().getDate() + 1).toString(); + sessionID = uuidV4(); sequenceNumber = randomInt(100); sessionData = { + id: sessionID, sourceGatewayPubkey: pluginSourceGateway.pubKey, recipientGatewayPubkey: pluginRecipientGateway.pubKey, transferCommenceMessageResponseHash: dummyTransferCommenceResponseMessageHash, step: 2, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; - lockExpiryDate = new Date().setDate(new Date().getDate() + 1).toString(); - pluginSourceGateway.sessions.set(sessionID, sessionData); pluginRecipientGateway.sessions.set(sessionID, sessionData); + + await pluginSourceGateway.storeOdapProof({ + sessionID: sessionID, + type: "proof", + operation: "lock", + data: LOCK_EVIDENCE_CLAIM, + }); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); }); test("valid lock evidence request", async () => { @@ -64,14 +184,14 @@ test("valid lock evidence request", async () => { messageType: OdapMessageType.LockEvidenceRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommenceAckRequest: dummyTransferCommenceResponseMessageHash, - lockEvidenceClaim: lockExpiryDate.toString(), + lockEvidenceClaim: LOCK_EVIDENCE_CLAIM, lockEvidenceExpiration: new Date(2060, 11, 24).toString(), sequenceNumber: sequenceNumber + 1, }; - lockEvidenceRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + lockEvidenceRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(lockEvidenceRequestMessage)), ); @@ -92,7 +212,7 @@ test("valid lock evidence request", async () => { expect(sessionInfo.lockEvidenceResponseMessageHash).not.toBe(""); expect(sessionInfo.clientSignatureLockEvidenceRequestMessage).toBe( - lockEvidenceRequestMessage.clientSignature, + lockEvidenceRequestMessage.signature, ); }); @@ -104,14 +224,14 @@ test("lock evidence request with wrong sessionId", async () => { messageType: OdapMessageType.LockEvidenceRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommenceAckRequest: dummyTransferCommenceResponseMessageHash, lockEvidenceClaim: lockExpiryDate.toString(), lockEvidenceExpiration: new Date(2060, 11, 24).toString(), sequenceNumber: sequenceNumber + 1, }; - lockEvidenceRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + lockEvidenceRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(lockEvidenceRequestMessage)), ); @@ -135,14 +255,14 @@ test("lock evidence request with wrong message type", async () => { messageType: OdapMessageType.LockEvidenceResponse, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommenceAckRequest: dummyTransferCommenceResponseMessageHash, lockEvidenceClaim: lockExpiryDate.toString(), lockEvidenceExpiration: new Date(2060, 11, 24).toString(), sequenceNumber: sequenceNumber + 1, }; - lockEvidenceRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + lockEvidenceRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(lockEvidenceRequestMessage)), ); @@ -164,14 +284,14 @@ test("lock evidence request with wrong previous message hash", async () => { messageType: OdapMessageType.LockEvidenceRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommenceAckRequest: "wrongPrevMessageHash", lockEvidenceClaim: lockExpiryDate.toString(), lockEvidenceExpiration: new Date(2060, 11, 24).toString(), sequenceNumber: sequenceNumber + 1, }; - lockEvidenceRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + lockEvidenceRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(lockEvidenceRequestMessage)), ); @@ -193,14 +313,14 @@ test("transfer commence flow with invalid claim", async () => { messageType: OdapMessageType.LockEvidenceRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashCommenceAckRequest: dummyTransferCommenceResponseMessageHash, lockEvidenceClaim: "", lockEvidenceExpiration: new Date(2020, 11, 24).toString(), sequenceNumber: sequenceNumber + 1, }; - lockEvidenceRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + lockEvidenceRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(lockEvidenceRequestMessage)), ); @@ -215,3 +335,45 @@ test("transfer commence flow with invalid claim", async () => { expect(ex.message).toMatch("invalid or expired lock evidence claim"), ); }); + +test("timeout in lock evidence response because no client gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "http://wrongpath", + recipientBasePath: "", + lastSequenceNumber: 77, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + lockEvidenceClaim: "dummyLockEvidenceClaim", + lockEvidenceRequestMessageHash: "dummyLockEvidenceRequestMessageHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendLockEvidenceResponse(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterAll(async () => { + await ipfsContainer.stop(); + await ipfsContainer.destroy(); + await Servers.shutdown(ipfsServer); + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-commence.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-commence.test.ts index 8e0c453fb6..46d0f73eb3 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-commence.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-commence.test.ts @@ -11,7 +11,13 @@ import { } from "../../../../main/typescript/generated/openapi/typescript-axios/api"; import { v4 as uuidV4 } from "uuid"; import { SHA256 } from "crypto-js"; -import { checkValidtransferCommenceRequest } from "../../../../main/typescript/gateway/server/transfer-commence"; +import { + checkValidtransferCommenceRequest, + sendTransferCommenceResponse, +} from "../../../../main/typescript/gateway/server/transfer-commence"; + +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; let sourceGatewayConstructor: IPluginOdapGatewayConstructorOptions; let recipientGatewayConstructor: IPluginOdapGatewayConstructorOptions; @@ -25,7 +31,7 @@ let sessionData: SessionData; let sessionID: string; let sequenceNumber: number; -beforeEach(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -40,10 +46,25 @@ beforeEach(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + dummyInitializationResponseMessageHash = SHA256( "initializationResponseMessageData", ).toString(); + sessionID = uuidV4(); + sequenceNumber = randomInt(100); + expiryDate = new Date(2060, 11, 24).toString(); assetProfile = { expirationDate: expiryDate }; assetProfileHash = SHA256(JSON.stringify(assetProfile)).toString(); @@ -61,6 +82,12 @@ beforeEach(() => { assetProfile: assetProfile, step: 1, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); @@ -79,11 +106,11 @@ test("valid transfer commence request", async () => { hashAssetProfile: assetProfileHash, senderDltSystem: "dummy", recipientDltSystem: "dummy", - clientSignature: "", + signature: "", sequenceNumber: sequenceNumber + 1, }; - transferCommenceRequest.clientSignature = pluginSourceGateway.bufArray2HexStr( + transferCommenceRequest.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(transferCommenceRequest)), ); @@ -102,7 +129,7 @@ test("valid transfer commence request", async () => { expect(sessionInfo.transferCommenceMessageRequestHash).toBe(requestHash); expect(sessionInfo.clientSignatureTransferCommenceRequestMessage).toBe( - transferCommenceRequest.clientSignature, + transferCommenceRequest.signature, ); expect(sessionInfo.serverSignatureTransferCommenceResponseMessage).not.toBe( "", @@ -124,11 +151,11 @@ test("transfer commence request with wrong sessionId", async () => { hashAssetProfile: assetProfileHash, senderDltSystem: "dummy", recipientDltSystem: "dummy", - clientSignature: "", + signature: "", sequenceNumber: sequenceNumber + 1, }; - transferCommenceRequest.clientSignature = pluginSourceGateway.bufArray2HexStr( + transferCommenceRequest.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(transferCommenceRequest)), ); @@ -158,11 +185,11 @@ test("transfer commence request with wrong message type", async () => { hashAssetProfile: assetProfileHash, senderDltSystem: "dummy", recipientDltSystem: "dummy", - clientSignature: "", + signature: "", sequenceNumber: sequenceNumber + 1, }; - transferCommenceRequest.clientSignature = pluginSourceGateway.bufArray2HexStr( + transferCommenceRequest.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(transferCommenceRequest)), ); @@ -192,11 +219,11 @@ test("transfer commence request with wrong signature", async () => { hashAssetProfile: assetProfileHash, senderDltSystem: "dummy", recipientDltSystem: "dummy", - clientSignature: "", + signature: "", sequenceNumber: sequenceNumber + 1, }; - transferCommenceRequest.clientSignature = pluginSourceGateway.bufArray2HexStr( + transferCommenceRequest.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify("wrongData")), ); @@ -226,11 +253,11 @@ test("transfer commence request with wrong previous message hash", async () => { hashAssetProfile: assetProfileHash, senderDltSystem: "dummy", recipientDltSystem: "dummy", - clientSignature: "", + signature: "", sequenceNumber: sequenceNumber + 1, }; - transferCommenceRequest.clientSignature = pluginSourceGateway.bufArray2HexStr( + transferCommenceRequest.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(transferCommenceRequest)), ); @@ -260,11 +287,11 @@ test("transfer commence request with wrong asset profile hash", async () => { hashAssetProfile: "wrongAssetProfileHash", senderDltSystem: "dummy", recipientDltSystem: "dummy", - clientSignature: "", + signature: "", sequenceNumber: sequenceNumber + 1, }; - transferCommenceRequest.clientSignature = pluginSourceGateway.bufArray2HexStr( + transferCommenceRequest.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(transferCommenceRequest)), ); @@ -279,3 +306,37 @@ test("transfer commence request with wrong asset profile hash", async () => { expect(ex.message).toMatch("assetProfile hash not match"), ); }); + +test("timeout in transfer commence response because no client gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "http://wrongpath", + recipientBasePath: "", + lastSequenceNumber: 77, + sourceGatewayPubkey: pluginSourceGateway.pubKey, + recipientGatewayPubkey: pluginRecipientGateway.pubKey, + transferCommenceMessageRequestHash: + "dummyTransferCommenceMessageRequestHash", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendTransferCommenceResponse(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-complete.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-complete.test.ts index 0d3247a4f4..74e25b093a 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-complete.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-complete.test.ts @@ -22,7 +22,7 @@ let sessionData: SessionData; let sessionID: string; let sequenceNumber: number; -beforeEach(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -37,6 +37,18 @@ beforeEach(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + dummyCommitFinalResponseMessageHash = SHA256( "commitFinalResponseMessageData", ).toString(); @@ -56,6 +68,12 @@ beforeEach(() => { transferCommenceMessageRequestHash: dummyTransferCommenceResponseMessageHash, step: 2, lastSequenceNumber: sequenceNumber, + maxTimeout: 0, + maxRetries: 0, + rollbackProofs: [], + sourceBasePath: "", + recipientBasePath: "", + rollbackActionsPerformed: [], }; pluginSourceGateway.sessions.set(sessionID, sessionData); @@ -68,13 +86,13 @@ test("dummy test for transfer complete flow", async () => { messageType: OdapMessageType.TransferCompleteRequest, clientIdentityPubkey: pluginSourceGateway.pubKey, serverIdentityPubkey: pluginRecipientGateway.pubKey, - clientSignature: "", + signature: "", hashTransferCommence: dummyTransferCommenceResponseMessageHash, hashCommitFinalAck: dummyCommitFinalResponseMessageHash, sequenceNumber: sequenceNumber + 1, }; - transferCompleteRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + transferCompleteRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(transferCompleteRequestMessage)), ); @@ -85,3 +103,8 @@ test("dummy test for transfer complete flow", async () => { throw new Error("Test failed"); }); }); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +}); diff --git a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-initiation.test.ts b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-initialization.test.ts similarity index 68% rename from packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-initiation.test.ts rename to packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-initialization.test.ts index 2a7049f8bf..ffddb03c12 100644 --- a/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-initiation.test.ts +++ b/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/server/transfer-initialization.test.ts @@ -5,12 +5,19 @@ import { OdapMessageType, PluginOdapGateway, } from "../../../../main/typescript/gateway/plugin-odap-gateway"; -import { checkValidInitializationRequest } from "../../../../main/typescript/gateway/server/transfer-initialization"; +import { + checkValidInitializationRequest, + sendTransferInitializationResponse, +} from "../../../../main/typescript/gateway/server/transfer-initialization"; import { TransferInitializationV1Request, AssetProfile, + SessionData, } from "../../../../main/typescript/public-api"; +const MAX_RETRIES = 5; +const MAX_TIMEOUT = 5000; + let sourceGatewayConstructor; let recipientGatewayConstructor; let pluginSourceGateway: PluginOdapGateway; @@ -20,7 +27,7 @@ let assetProfile: AssetProfile; let sequenceNumber: number; let sessionID: string; -beforeAll(() => { +beforeEach(async () => { sourceGatewayConstructor = { name: "plugin-odap-gateway#sourceGateway", dltIDs: ["DLT2"], @@ -34,6 +41,19 @@ beforeAll(() => { pluginSourceGateway = new PluginOdapGateway(sourceGatewayConstructor); pluginRecipientGateway = new PluginOdapGateway(recipientGatewayConstructor); + + if ( + pluginSourceGateway.database == undefined || + pluginRecipientGateway.database == undefined + ) { + throw new Error("Database is not correctly initialized"); + } + + await pluginSourceGateway.database.migrate.rollback(); + await pluginSourceGateway.database.migrate.latest(); + await pluginRecipientGateway.database.migrate.rollback(); + await pluginRecipientGateway.database.migrate.latest(); + expiryDate = new Date(2060, 11, 24).toString(); assetProfile = { expirationDate: expiryDate }; @@ -43,6 +63,8 @@ beforeAll(() => { test("valid transfer initiation request", async () => { const initializationRequestMessage: TransferInitializationV1Request = { + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, messageType: OdapMessageType.InitializationRequest, sessionID: sessionID, version: "0.0.0", @@ -53,15 +75,17 @@ test("valid transfer initiation request", async () => { assetProfile: assetProfile, capabilities: "", }, - clientSignature: "", + signature: "", sourceGatewayPubkey: pluginSourceGateway.pubKey, sourceGatewayDltSystem: "DLT1", recipientGatewayPubkey: pluginRecipientGateway.pubKey, recipientGatewayDltSystem: "DLT2", sequenceNumber: sequenceNumber, + recipientBasePath: "", + sourceGatewayPath: "", }; - initializationRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + initializationRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( await pluginSourceGateway.sign( JSON.stringify(initializationRequestMessage), ), @@ -98,12 +122,14 @@ test("valid transfer initiation request", async () => { expect(sessionData.initializationRequestMessageHash).toBe(messageHash); expect(sessionData.clientSignatureInitializationRequestMessage).toBe( - initializationRequestMessage.clientSignature, + initializationRequestMessage.signature, ); }); test("transfer initiation request invalid because of incompatible DLTs", async () => { const initializationRequestMessage: TransferInitializationV1Request = { + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, messageType: OdapMessageType.InitializationRequest, sessionID: sessionID, version: "0.0.0", @@ -114,15 +140,17 @@ test("transfer initiation request invalid because of incompatible DLTs", async ( assetProfile: assetProfile, capabilities: "", }, - clientSignature: "", + signature: "", sourceGatewayPubkey: pluginSourceGateway.pubKey, sourceGatewayDltSystem: "DLT2", recipientGatewayPubkey: pluginRecipientGateway.pubKey, recipientGatewayDltSystem: "DLT1", sequenceNumber: sequenceNumber, + recipientBasePath: "", + sourceGatewayPath: "", }; - initializationRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + initializationRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( await pluginSourceGateway.sign( JSON.stringify(initializationRequestMessage), ), @@ -147,6 +175,8 @@ test("transfer initiation request invalid because of asset expired", async () => assetProfile = { expirationDate: expiryDate }; const initializationRequestMessage: TransferInitializationV1Request = { + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, messageType: OdapMessageType.InitializationRequest, sessionID: sessionID, version: "0.0.0", @@ -157,15 +187,17 @@ test("transfer initiation request invalid because of asset expired", async () => assetProfile: assetProfile, capabilities: "", }, - clientSignature: "", + signature: "", sourceGatewayPubkey: pluginSourceGateway.pubKey, sourceGatewayDltSystem: "DLT1", recipientGatewayPubkey: pluginRecipientGateway.pubKey, recipientGatewayDltSystem: "DLT2", sequenceNumber: sequenceNumber, + recipientBasePath: "", + sourceGatewayPath: "", }; - initializationRequestMessage.clientSignature = pluginSourceGateway.bufArray2HexStr( + initializationRequestMessage.signature = PluginOdapGateway.bufArray2HexStr( pluginSourceGateway.sign(JSON.stringify(initializationRequestMessage)), ); @@ -178,3 +210,39 @@ test("transfer initiation request invalid because of asset expired", async () => }) .catch((ex: Error) => expect(ex.message).toMatch("asset has expired")); }); + +test("timeout in commit final response because no client gateway is connected", async () => { + const sessionData: SessionData = { + id: sessionID, + step: 1, + maxRetries: MAX_RETRIES, + maxTimeout: MAX_TIMEOUT, + sourceBasePath: "http://wrongpath", + recipientBasePath: "", + lastSequenceNumber: 77, + initializationRequestMessageHash: + "dummyInitializationRequestMessageProcessedTimeStamp", + initializationRequestMessageRcvTimeStamp: + "dummyInitializationRequestMessageProcessedTimeStamp", + initializationRequestMessageProcessedTimeStamp: + "dummyInitializationRequestMessageProcessedTimeStamp", + lastMessageReceivedTimestamp: new Date().toString(), + rollbackProofs: [], + rollbackActionsPerformed: [], + }; + + pluginSourceGateway.sessions.set(sessionID, sessionData); + + await sendTransferInitializationResponse(sessionID, pluginSourceGateway, true) + .then(() => { + throw new Error("Test Failed"); + }) + .catch((ex: Error) => { + expect(ex.message).toMatch("Timeout exceeded."); + }); +}); + +afterEach(() => { + pluginSourceGateway.database?.destroy(); + pluginRecipientGateway.database?.destroy(); +});