From 3ac2cbb6d86c4709636ab3cd05a893626b508c1f Mon Sep 17 00:00:00 2001 From: Michal Bajer Date: Thu, 6 Jun 2024 13:38:15 +0200 Subject: [PATCH] feat(fabric-connector): add getChainInfo, improve getBlock output - Add new method `getChainInfo` for quering chain information from qscc. - Add `GetChainInfoEndpointV1` to allow calling `getChainInfo` remotely. - Refactor `getBlock` so it can return same custom block formats as `WatchBlocks`. Default remains the same (full decode block). BREAKING CHANGE: It accepts `type` instead of `skipDecode` flag. - Move common block formatting logic to `cacti-block-formatters.ts`. - Add tests for new features. Move test common to quering `qscc` to single file to increase CI speed. Signed-off-by: Michal Bajer --- .../business-logic-asset-trade.ts | 10 +- .../typescript/strategy/strategy-fabric.ts | 3 +- .../README.md | 11 +- .../package.json | 1 + .../src/main/json/openapi.json | 357 +++++++++++- .../kotlin-client/.openapi-generator/FILES | 14 +- .../generated/openapi/kotlin-client/README.md | 15 +- .../openapitools/client/apis/DefaultApi.kt | 74 +++ .../client/models/CactiBlockFullEventV1.kt | 52 ++ .../client/models/CactiBlockFullResponseV1.kt | 36 ++ ...tV1.kt => CactiBlockTransactionEventV1.kt} | 2 +- ...kt => CactiBlockTransactionsResponseV1.kt} | 10 +- .../models/FabricCertificateIdentityV1.kt | 40 ++ .../client/models/FabricX509CertificateV1.kt | 59 ++ .../models/FullBlockTransactionActionV1.kt | 53 ++ .../FullBlockTransactionEndorsementV1.kt | 40 ++ .../models/FullBlockTransactionEventV1.kt | 60 ++ .../client/models/GetBlockRequestV1.kt | 8 +- .../client/models/GetBlockResponseTypeV1.kt | 69 +++ .../client/models/GetBlockResponseV1.kt | 13 + .../client/models/GetChainInfoRequestV1.kt | 46 ++ .../client/models/GetChainInfoResponseV1.kt | 46 ++ .../models/WatchBlocksListenerTypeV1.kt | 11 +- .../client/models/WatchBlocksResponseV1.kt | 16 +- .../api-client/fabric-api-client.ts | 78 ++- .../common/query-system-chain-code.ts | 16 +- .../src/main/typescript/common/utils.ts | 24 + .../generated/openapi/typescript-axios/api.ts | 461 +++++++++++++-- .../get-block/cacti-block-formatters.ts | 202 +++++++ .../get-block/get-block-endpoint-v1.ts | 13 +- .../get-chain-info-endpoint-v1.ts | 97 ++++ .../plugin-ledger-connector-fabric.ts | 90 ++- .../watch-blocks/watch-blocks-v1-endpoint.ts | 90 ++- .../delegate-signing-methods.test.ts | 6 +- ...-blocks-delegated-sign-v1-endpoint.test.ts | 71 ++- .../fabric-watch-blocks-v1-endpoint.test.ts | 75 ++- .../fabric-v2-2-x/get-block.test.ts | 431 -------------- .../query-system-chain-methods.test.ts | 539 ++++++++++++++++++ .../typescript/plugin-persistence-fabric.ts | 3 +- yarn.lock | 15 +- 40 files changed, 2584 insertions(+), 673 deletions(-) create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullEventV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullResponseV1.kt rename packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/{WatchBlocksCactusTransactionsEventV1.kt => CactiBlockTransactionEventV1.kt} (95%) rename packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/{WatchBlocksCactusTransactionsResponseV1.kt => CactiBlockTransactionsResponseV1.kt} (62%) create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricCertificateIdentityV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricX509CertificateV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionActionV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEndorsementV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEventV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseTypeV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoRequestV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoResponseV1.kt create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/cacti-block-formatters.ts create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-chain-info/get-chain-info-endpoint-v1.ts delete mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/get-block.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/query-system-chain-methods.test.ts diff --git a/examples/cactus-example-discounted-asset-trade/business-logic-asset-trade.ts b/examples/cactus-example-discounted-asset-trade/business-logic-asset-trade.ts index 9b23867198e..d9c09a7a1e1 100644 --- a/examples/cactus-example-discounted-asset-trade/business-logic-asset-trade.ts +++ b/examples/cactus-example-discounted-asset-trade/business-logic-asset-trade.ts @@ -23,7 +23,7 @@ import { import { json2str } from "@hyperledger/cactus-cmd-socketio-server"; import { AssetTradeStatus } from "./define"; import { - WatchBlocksCactusTransactionsEventV1 as FabricWatchBlocksCactusTransactionsEventV1, + CactiBlockTransactionEventV1, WatchBlocksListenerTypeV1 as FabricWatchBlocksListenerTypeV1, WatchBlocksResponseV1 as FabricWatchBlocksResponseV1, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; @@ -242,19 +242,19 @@ export class BusinessLogicAssetTrade extends BusinessLogicBase { const fabricApiClient = getFabricApiClient(); const watchObservable = fabricApiClient.watchBlocksDelegatedSignV1({ channelName: config.assetTradeInfo.fabric.channelName, - type: FabricWatchBlocksListenerTypeV1.CactusTransactions, + type: FabricWatchBlocksListenerTypeV1.CactiTransactions, signerCertificate: getSignerIdentity().credentials.certificate, signerMspID: getSignerIdentity().mspId, uniqueTransactionData: createSigningToken("watchBlock"), }); const watchSub = watchObservable.subscribe({ next: (event: FabricWatchBlocksResponseV1) => { - if (!("cactusTransactionsEvents" in event)) { + if (!("cactiTransactionsEvents" in event)) { logger.error("Wrong input block format!", event); return; } - for (const ev of event.cactusTransactionsEvents) { + for (const ev of event.cactiTransactionsEvents) { logger.debug(`##in onEventFabric()`); try { @@ -363,7 +363,7 @@ export class BusinessLogicAssetTrade extends BusinessLogicBase { } executeNextTransaction( - txInfo: FabricWatchBlocksCactusTransactionsEventV1 | Web3TransactionReceipt, + txInfo: CactiBlockTransactionEventV1 | Web3TransactionReceipt, txId: string, ): void { let transactionInfo: TransactionInfo | null = null; diff --git a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts index 106958db7ba..a08bbbff46f 100644 --- a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts +++ b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts @@ -4,6 +4,7 @@ import { Configuration, FabricContractInvocationType, RunTransactionRequest, + GetBlockResponseTypeV1, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; import { NetworkDetails, ObtainLedgerStrategy } from "./obtain-ledger-strategy"; import { @@ -170,7 +171,7 @@ export class StrategyFabric implements ObtainLedgerStrategy { query: { transactionId: txId, }, - skipDecode: false, + type: GetBlockResponseTypeV1.Full, }; const getBlockResponse = await api.getBlockV1(getBlockReq); diff --git a/packages/cactus-plugin-ledger-connector-fabric/README.md b/packages/cactus-plugin-ledger-connector-fabric/README.md index 90f14a0986c..940c2a5be0a 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/README.md +++ b/packages/cactus-plugin-ledger-connector-fabric/README.md @@ -331,6 +331,11 @@ Corresponds directly to `BlockType` from `fabric-common`: - `WatchBlocksListenerTypeV1.Full`, - `WatchBlocksListenerTypeV1.Private`, +##### Cacti (custom) +Parses the data and returns custom formatted block. +- `WatchBlocksListenerTypeV1.CactiTransactions`: Returns transactions summary. Compatible with legacy `fabric-socketio` monitoring operation. +- `WatchBlocksListenerTypeV1.CactiFullBlock`: Returns full block summary. + ### 1.6 Delegated Signature - Custom signature callback can be used when increased security is needed or currently available options are not sufficient. - Signature callback is used whenever fabric request must be signed. @@ -365,17 +370,13 @@ await apiClient.runDelegatedSignTransactionV1({ // Monitor for transactions: apiClient.watchBlocksDelegatedSignV1({ - type: WatchBlocksListenerTypeV1.CactusTransactions, + type: WatchBlocksListenerTypeV1.CactiTransactions, signerCertificate: adminIdentity.credentials.certificate, signerMspID: adminIdentity.mspId, channelName: ledgerChannelName, }) ``` -##### Cactus (custom) -Parses the data and returns custom formatted block. -- `WatchBlocksListenerTypeV1.CactusTransactions`: Returns transactions summary. Compatible with legacy `fabric-socketio` monitoring operation. - ## 2. Architecture The sequence diagrams for various endpoints are mentioned below diff --git a/packages/cactus-plugin-ledger-connector-fabric/package.json b/packages/cactus-plugin-ledger-connector-fabric/package.json index e777e79a37c..869eed6e84d 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/package.json +++ b/packages/cactus-plugin-ledger-connector-fabric/package.json @@ -72,6 +72,7 @@ "form-data": "4.0.0", "http-status-codes": "2.1.4", "jsrsasign": "11.0.0", + "long": "5.2.3", "multer": "1.4.5-lts.1", "ngo": "2.7.0", "node-ssh": "13.1.0", diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json index 5fbf93276c2..68568b348e6 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json @@ -1056,10 +1056,35 @@ } } }, - "skipDecode": { - "type": "boolean", - "description": "If true, encoded buffer will be returned. Otherwise, entire block object is returned.", - "default": false, + "type": { + "$ref": "#/components/schemas/GetBlockResponseTypeV1", + "description": "Type of response block to return.", + "nullable": false + } + } + }, + "GetChainInfoRequestV1": { + "description": "Request for GetChainInfo endpoint.", + "type": "object", + "required": ["channelName", "gatewayOptions"], + "properties": { + "channelName": { + "type": "string", + "description": "Fabric channel which we want to query.", + "minLength": 1, + "maxLength": 100, + "nullable": false + }, + "connectionChannelName": { + "type": "string", + "description": "Fabric channel we want to connect to. If not provided, then one from channelName parameter will be used", + "minLength": 1, + "maxLength": 100, + "nullable": false + }, + "gatewayOptions": { + "$ref": "#/components/schemas/GatewayOptions", + "description": "Fabric SDK gateway options.", "nullable": false } } @@ -1091,6 +1116,12 @@ "GetBlockResponseV1": { "description": "Response from GetBlock endpoint.", "oneOf": [ + { + "$ref": "#/components/schemas/CactiBlockTransactionsResponseV1" + }, + { + "$ref": "#/components/schemas/CactiBlockFullResponseV1" + }, { "$ref": "#/components/schemas/GetBlockResponseDecodedV1" }, @@ -1099,6 +1130,28 @@ } ] }, + "GetChainInfoResponseV1": { + "type": "object", + "description": "Response from GetChainInfo endpoint.", + "required": ["height", "currentBlockHash", "previousBlockHash"], + "properties": { + "height": { + "description": "Current height of fabric ledger", + "type": "number", + "nullable": false + }, + "currentBlockHash": { + "description": "Current block hash of fabric ledger", + "type": "string", + "nullable": false + }, + "previousBlockHash": { + "description": "Previous block hash of fabric ledger", + "type": "string", + "nullable": false + } + } + }, "ErrorExceptionResponseV1": { "type": "object", "required": ["message", "error"], @@ -1139,9 +1192,33 @@ }, "WatchBlocksListenerTypeV1": { "type": "string", - "description": "Response type from WatchBlocks. 'Cactus*' are custom views, others correspond to fabric SDK call.", - "enum": ["filtered", "full", "private", "cactus:transactions"], - "x-enum-varnames": ["Filtered", "Full", "Private", "CactusTransactions"] + "description": "Response type from WatchBlocks. 'Cacti*' are custom views, others correspond to fabric SDK call.", + "enum": [ + "filtered", + "full", + "private", + "cacti:transactions", + "cacti:full-block" + ], + "x-enum-varnames": [ + "Filtered", + "Full", + "Private", + "CactiTransactions", + "CactiFullBlock" + ] + }, + "GetBlockResponseTypeV1": { + "type": "string", + "default": "full", + "description": "Response type from GetBlock.", + "enum": ["full", "encoded", "cacti:transactions", "cacti:full-block"], + "x-enum-varnames": [ + "Full", + "Encoded", + "CactiTransactions", + "CactiFullBlock" + ] }, "WatchBlocksOptionsV1": { "type": "object", @@ -1211,7 +1288,7 @@ } } }, - "WatchBlocksCactusTransactionsEventV1": { + "CactiBlockTransactionEventV1": { "type": "object", "description": "Transaction summary from commited block.", "required": [ @@ -1247,21 +1324,33 @@ } } }, - "WatchBlocksCactusTransactionsResponseV1": { + "CactiBlockTransactionsResponseV1": { "type": "object", "description": "Custom response containing block transactions summary. Compatible with legacy fabric-socketio connector monitoring.", - "required": ["cactusTransactionsEvents"], + "required": ["cactiTransactionsEvents"], "properties": { - "cactusTransactionsEvents": { + "cactiTransactionsEvents": { "description": "List of transactions summary", "type": "array", "items": { - "$ref": "#/components/schemas/WatchBlocksCactusTransactionsEventV1", + "$ref": "#/components/schemas/CactiBlockTransactionEventV1", "nullable": false } } } }, + "CactiBlockFullResponseV1": { + "type": "object", + "description": "Custom response containing full block summary.", + "required": ["cactiFullEvents"], + "properties": { + "cactiFullEvents": { + "description": "Full parsed block with transactions.", + "$ref": "#/components/schemas/CactiBlockFullEventV1", + "nullable": false + } + } + }, "WatchBlocksFullResponseV1": { "type": "object", "description": "Response that corresponds to Fabric SDK 'full' EventType.", @@ -1295,6 +1384,200 @@ } } }, + "FabricX509CertificateV1": { + "type": "object", + "description": "Transaction endorser certificate object", + "required": [ + "issuer", + "serialNumber", + "subject", + "subjectAltName", + "validFrom", + "validTo", + "pem" + ], + "properties": { + "issuer": { + "nullable": false, + "type": "string" + }, + "serialNumber": { + "nullable": false, + "type": "string" + }, + "subject": { + "nullable": false, + "type": "string" + }, + "subjectAltName": { + "nullable": false, + "type": "string" + }, + "validFrom": { + "nullable": false, + "type": "string" + }, + "validTo": { + "nullable": false, + "type": "string" + }, + "pem": { + "nullable": false, + "type": "string" + } + } + }, + "FabricCertificateIdentityV1": { + "type": "object", + "description": "Combination of certificate and it's MSP ID used to identify fabric actors.", + "required": ["mspid", "cert"], + "properties": { + "mspid": { + "nullable": false, + "type": "string" + }, + "cert": { + "$ref": "#/components/schemas/FabricX509CertificateV1", + "nullable": false + } + } + }, + "FullBlockTransactionEndorsementV1": { + "type": "object", + "description": "Transaction endorsment object returned from fabric block.", + "required": ["signer", "signature"], + "properties": { + "signer": { + "$ref": "#/components/schemas/FabricCertificateIdentityV1", + "nullable": false + }, + "signature": { + "nullable": false, + "type": "string" + } + } + }, + "FullBlockTransactionActionV1": { + "type": "object", + "description": "Transaction action returned from fabric block.", + "required": [ + "functionName", + "functionArgs", + "chaincodeId", + "creator", + "endorsements" + ], + "properties": { + "functionName": { + "nullable": false, + "type": "string" + }, + "functionArgs": { + "type": "array", + "items": { + "nullable": false, + "type": "string" + } + }, + "chaincodeId": { + "nullable": false, + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/FabricCertificateIdentityV1", + "nullable": false + }, + "endorsements": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FullBlockTransactionEndorsementV1", + "nullable": false + } + } + } + }, + "FullBlockTransactionEventV1": { + "type": "object", + "description": "Transaction returned from fabric block.", + "required": [ + "hash", + "channelId", + "timestamp", + "protocolVersion", + "type", + "epoch", + "actions" + ], + "properties": { + "hash": { + "nullable": false, + "type": "string" + }, + "channelId": { + "nullable": false, + "type": "string" + }, + "timestamp": { + "nullable": false, + "type": "string" + }, + "protocolVersion": { + "nullable": false, + "type": "number" + }, + "type": { + "nullable": false, + "type": "string" + }, + "epoch": { + "nullable": false, + "type": "number" + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FullBlockTransactionActionV1", + "nullable": false + } + } + } + }, + "CactiBlockFullEventV1": { + "type": "object", + "description": "Custom format full fabric block with transactions", + "required": [ + "blockNumber", + "blockHash", + "previousBlockHash", + "transactionCount", + "cactiTransactionsEvents" + ], + "properties": { + "blockNumber": { + "nullable": false, + "type": "number" + }, + "blockHash": { + "nullable": false, + "type": "string" + }, + "previousBlockHash": { + "nullable": false, + "type": "string" + }, + "transactionCount": { + "nullable": false, + "type": "number" + }, + "cactiTransactionsEvents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FullBlockTransactionEventV1", + "nullable": false + } + } + } + }, "WatchBlocksCactusErrorResponseV1": { "type": "object", "description": "Error response from WatchBlocks operation.", @@ -1314,7 +1597,11 @@ "description": "Response block from WatchBlocks endpoint. Depends on 'type' passed in subscription options.", "oneOf": [ { - "$ref": "#/components/schemas/WatchBlocksCactusTransactionsResponseV1", + "$ref": "#/components/schemas/CactiBlockTransactionsResponseV1", + "nullable": false + }, + { + "$ref": "#/components/schemas/CactiBlockFullResponseV1", "nullable": false }, { @@ -1617,6 +1904,50 @@ } } }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-chain-info": { + "post": { + "operationId": "getChainInfoV1", + "summary": "Get fabric ledger chain info.", + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-chain-info" + } + }, + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetChainInfoRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetChainInfoResponseV1" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorExceptionResponseV1" + } + } + } + } + } + } + }, "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-prometheus-exporter-metrics": { "get": { "x-hyperledger-cacti": { diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES index 60dc2bf86b3..b88f5e88106 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES @@ -21,6 +21,10 @@ src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt +src/main/kotlin/org/openapitools/client/models/CactiBlockFullEventV1.kt +src/main/kotlin/org/openapitools/client/models/CactiBlockFullResponseV1.kt +src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionEventV1.kt +src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionsResponseV1.kt src/main/kotlin/org/openapitools/client/models/ChainCodeLanguageRuntime.kt src/main/kotlin/org/openapitools/client/models/ChainCodeLifeCycleCommandResponses.kt src/main/kotlin/org/openapitools/client/models/ChainCodeProgrammingLanguage.kt @@ -36,10 +40,15 @@ src/main/kotlin/org/openapitools/client/models/DeployContractV1Response.kt src/main/kotlin/org/openapitools/client/models/DeploymentTargetOrgFabric2x.kt src/main/kotlin/org/openapitools/client/models/DeploymentTargetOrganization.kt src/main/kotlin/org/openapitools/client/models/ErrorExceptionResponseV1.kt +src/main/kotlin/org/openapitools/client/models/FabricCertificateIdentityV1.kt src/main/kotlin/org/openapitools/client/models/FabricContractInvocationType.kt src/main/kotlin/org/openapitools/client/models/FabricSigningCredential.kt src/main/kotlin/org/openapitools/client/models/FabricSigningCredentialType.kt +src/main/kotlin/org/openapitools/client/models/FabricX509CertificateV1.kt src/main/kotlin/org/openapitools/client/models/FileBase64.kt +src/main/kotlin/org/openapitools/client/models/FullBlockTransactionActionV1.kt +src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEndorsementV1.kt +src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEventV1.kt src/main/kotlin/org/openapitools/client/models/GatewayDiscoveryOptions.kt src/main/kotlin/org/openapitools/client/models/GatewayEventHandlerOptions.kt src/main/kotlin/org/openapitools/client/models/GatewayOptions.kt @@ -49,7 +58,10 @@ src/main/kotlin/org/openapitools/client/models/GetBlockRequestV1Query.kt src/main/kotlin/org/openapitools/client/models/GetBlockRequestV1QueryBlockHash.kt src/main/kotlin/org/openapitools/client/models/GetBlockResponseDecodedV1.kt src/main/kotlin/org/openapitools/client/models/GetBlockResponseEncodedV1.kt +src/main/kotlin/org/openapitools/client/models/GetBlockResponseTypeV1.kt src/main/kotlin/org/openapitools/client/models/GetBlockResponseV1.kt +src/main/kotlin/org/openapitools/client/models/GetChainInfoRequestV1.kt +src/main/kotlin/org/openapitools/client/models/GetChainInfoResponseV1.kt src/main/kotlin/org/openapitools/client/models/GetTransactionReceiptResponse.kt src/main/kotlin/org/openapitools/client/models/RunDelegatedSignTransactionRequest.kt src/main/kotlin/org/openapitools/client/models/RunTransactionRequest.kt @@ -61,8 +73,6 @@ src/main/kotlin/org/openapitools/client/models/TransactReceiptTransactionCreator src/main/kotlin/org/openapitools/client/models/TransactReceiptTransactionEndorsement.kt src/main/kotlin/org/openapitools/client/models/VaultTransitKey.kt src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusErrorResponseV1.kt -src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsEventV1.kt -src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsResponseV1.kt src/main/kotlin/org/openapitools/client/models/WatchBlocksDelegatedSignOptionsV1.kt src/main/kotlin/org/openapitools/client/models/WatchBlocksFilteredResponseV1.kt src/main/kotlin/org/openapitools/client/models/WatchBlocksFullResponseV1.kt diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/README.md b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/README.md index 7bd284cb031..e8ba705b683 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/README.md +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/README.md @@ -47,6 +47,7 @@ Class | Method | HTTP request | Description *DefaultApi* | [**deployContractGoSourceV1**](docs/DefaultApi.md#deploycontractgosourcev1) | **POST** /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/deploy-contract-go-source | Deploys a chaincode contract in the form of a go sources. *DefaultApi* | [**deployContractV1**](docs/DefaultApi.md#deploycontractv1) | **POST** /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/deploy-contract | Deploys a chaincode contract from a set of source files. Note: This endpoint only supports Fabric 2.x. The 'v1' suffix in the method name refers to the Cactus API version, not the supported Fabric ledger version. *DefaultApi* | [**getBlockV1**](docs/DefaultApi.md#getblockv1) | **POST** /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-block | Get block from the channel using one of selectors from the input. Works only on Fabric 2.x. +*DefaultApi* | [**getChainInfoV1**](docs/DefaultApi.md#getchaininfov1) | **POST** /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-chain-info | Get fabric ledger chain info. *DefaultApi* | [**getPrometheusMetricsV1**](docs/DefaultApi.md#getprometheusmetricsv1) | **GET** /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-prometheus-exporter-metrics | Get the Prometheus Metrics *DefaultApi* | [**getTransactionReceiptByTxIDV1**](docs/DefaultApi.md#gettransactionreceiptbytxidv1) | **POST** /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-transaction-receipt-by-txid | get a transaction receipt by tx id on a Fabric ledger. *DefaultApi* | [**runDelegatedSignTransactionV1**](docs/DefaultApi.md#rundelegatedsigntransactionv1) | **POST** /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/run-delegated-sign-transaction | Runs a transaction on a Fabric ledger using user-provided signing callback. @@ -56,6 +57,10 @@ Class | Method | HTTP request | Description ## Documentation for Models + - [org.openapitools.client.models.CactiBlockFullEventV1](docs/CactiBlockFullEventV1.md) + - [org.openapitools.client.models.CactiBlockFullResponseV1](docs/CactiBlockFullResponseV1.md) + - [org.openapitools.client.models.CactiBlockTransactionEventV1](docs/CactiBlockTransactionEventV1.md) + - [org.openapitools.client.models.CactiBlockTransactionsResponseV1](docs/CactiBlockTransactionsResponseV1.md) - [org.openapitools.client.models.ChainCodeLanguageRuntime](docs/ChainCodeLanguageRuntime.md) - [org.openapitools.client.models.ChainCodeLifeCycleCommandResponses](docs/ChainCodeLifeCycleCommandResponses.md) - [org.openapitools.client.models.ChainCodeProgrammingLanguage](docs/ChainCodeProgrammingLanguage.md) @@ -71,10 +76,15 @@ Class | Method | HTTP request | Description - [org.openapitools.client.models.DeploymentTargetOrgFabric2x](docs/DeploymentTargetOrgFabric2x.md) - [org.openapitools.client.models.DeploymentTargetOrganization](docs/DeploymentTargetOrganization.md) - [org.openapitools.client.models.ErrorExceptionResponseV1](docs/ErrorExceptionResponseV1.md) + - [org.openapitools.client.models.FabricCertificateIdentityV1](docs/FabricCertificateIdentityV1.md) - [org.openapitools.client.models.FabricContractInvocationType](docs/FabricContractInvocationType.md) - [org.openapitools.client.models.FabricSigningCredential](docs/FabricSigningCredential.md) - [org.openapitools.client.models.FabricSigningCredentialType](docs/FabricSigningCredentialType.md) + - [org.openapitools.client.models.FabricX509CertificateV1](docs/FabricX509CertificateV1.md) - [org.openapitools.client.models.FileBase64](docs/FileBase64.md) + - [org.openapitools.client.models.FullBlockTransactionActionV1](docs/FullBlockTransactionActionV1.md) + - [org.openapitools.client.models.FullBlockTransactionEndorsementV1](docs/FullBlockTransactionEndorsementV1.md) + - [org.openapitools.client.models.FullBlockTransactionEventV1](docs/FullBlockTransactionEventV1.md) - [org.openapitools.client.models.GatewayDiscoveryOptions](docs/GatewayDiscoveryOptions.md) - [org.openapitools.client.models.GatewayEventHandlerOptions](docs/GatewayEventHandlerOptions.md) - [org.openapitools.client.models.GatewayOptions](docs/GatewayOptions.md) @@ -84,7 +94,10 @@ Class | Method | HTTP request | Description - [org.openapitools.client.models.GetBlockRequestV1QueryBlockHash](docs/GetBlockRequestV1QueryBlockHash.md) - [org.openapitools.client.models.GetBlockResponseDecodedV1](docs/GetBlockResponseDecodedV1.md) - [org.openapitools.client.models.GetBlockResponseEncodedV1](docs/GetBlockResponseEncodedV1.md) + - [org.openapitools.client.models.GetBlockResponseTypeV1](docs/GetBlockResponseTypeV1.md) - [org.openapitools.client.models.GetBlockResponseV1](docs/GetBlockResponseV1.md) + - [org.openapitools.client.models.GetChainInfoRequestV1](docs/GetChainInfoRequestV1.md) + - [org.openapitools.client.models.GetChainInfoResponseV1](docs/GetChainInfoResponseV1.md) - [org.openapitools.client.models.GetTransactionReceiptResponse](docs/GetTransactionReceiptResponse.md) - [org.openapitools.client.models.RunDelegatedSignTransactionRequest](docs/RunDelegatedSignTransactionRequest.md) - [org.openapitools.client.models.RunTransactionRequest](docs/RunTransactionRequest.md) @@ -96,8 +109,6 @@ Class | Method | HTTP request | Description - [org.openapitools.client.models.TransactReceiptTransactionEndorsement](docs/TransactReceiptTransactionEndorsement.md) - [org.openapitools.client.models.VaultTransitKey](docs/VaultTransitKey.md) - [org.openapitools.client.models.WatchBlocksCactusErrorResponseV1](docs/WatchBlocksCactusErrorResponseV1.md) - - [org.openapitools.client.models.WatchBlocksCactusTransactionsEventV1](docs/WatchBlocksCactusTransactionsEventV1.md) - - [org.openapitools.client.models.WatchBlocksCactusTransactionsResponseV1](docs/WatchBlocksCactusTransactionsResponseV1.md) - [org.openapitools.client.models.WatchBlocksDelegatedSignOptionsV1](docs/WatchBlocksDelegatedSignOptionsV1.md) - [org.openapitools.client.models.WatchBlocksFilteredResponseV1](docs/WatchBlocksFilteredResponseV1.md) - [org.openapitools.client.models.WatchBlocksFullResponseV1](docs/WatchBlocksFullResponseV1.md) diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt index 7f3d897bb57..121dac05917 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt @@ -27,6 +27,8 @@ import org.openapitools.client.models.DeployContractV1Response import org.openapitools.client.models.ErrorExceptionResponseV1 import org.openapitools.client.models.GetBlockRequestV1 import org.openapitools.client.models.GetBlockResponseV1 +import org.openapitools.client.models.GetChainInfoRequestV1 +import org.openapitools.client.models.GetChainInfoResponseV1 import org.openapitools.client.models.GetTransactionReceiptResponse import org.openapitools.client.models.RunDelegatedSignTransactionRequest import org.openapitools.client.models.RunTransactionRequest @@ -272,6 +274,78 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient ) } + /** + * Get fabric ledger chain info. + * + * @param getChainInfoRequestV1 (optional) + * @return GetChainInfoResponseV1 + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun getChainInfoV1(getChainInfoRequestV1: GetChainInfoRequestV1? = null) : GetChainInfoResponseV1 { + val localVarResponse = getChainInfoV1WithHttpInfo(getChainInfoRequestV1 = getChainInfoRequestV1) + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as GetChainInfoResponseV1 + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * Get fabric ledger chain info. + * + * @param getChainInfoRequestV1 (optional) + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun getChainInfoV1WithHttpInfo(getChainInfoRequestV1: GetChainInfoRequestV1?) : ApiResponse { + val localVariableConfig = getChainInfoV1RequestConfig(getChainInfoRequestV1 = getChainInfoRequestV1) + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation getChainInfoV1 + * + * @param getChainInfoRequestV1 (optional) + * @return RequestConfig + */ + fun getChainInfoV1RequestConfig(getChainInfoRequestV1: GetChainInfoRequestV1?) : RequestConfig { + val localVariableBody = getChainInfoRequestV1 + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/json" + localVariableHeaders["Accept"] = "application/json" + + return RequestConfig( + method = RequestMethod.POST, + path = "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-chain-info", + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + /** * Get the Prometheus Metrics * diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullEventV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullEventV1.kt new file mode 100644 index 00000000000..787a3429aee --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullEventV1.kt @@ -0,0 +1,52 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.FullBlockTransactionEventV1 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Custom format full fabric block with transactions + * + * @param blockNumber + * @param blockHash + * @param previousBlockHash + * @param transactionCount + * @param cactiTransactionsEvents + */ + + +data class CactiBlockFullEventV1 ( + + @Json(name = "blockNumber") + val blockNumber: java.math.BigDecimal, + + @Json(name = "blockHash") + val blockHash: kotlin.String, + + @Json(name = "previousBlockHash") + val previousBlockHash: kotlin.String, + + @Json(name = "transactionCount") + val transactionCount: java.math.BigDecimal, + + @Json(name = "cactiTransactionsEvents") + val cactiTransactionsEvents: kotlin.collections.List + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullResponseV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullResponseV1.kt new file mode 100644 index 00000000000..f01837075df --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockFullResponseV1.kt @@ -0,0 +1,36 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.CactiBlockFullEventV1 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Custom response containing full block summary. + * + * @param cactiFullEvents + */ + + +data class CactiBlockFullResponseV1 ( + + @Json(name = "cactiFullEvents") + val cactiFullEvents: CactiBlockFullEventV1 + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsEventV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionEventV1.kt similarity index 95% rename from packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsEventV1.kt rename to packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionEventV1.kt index c504d8dc1aa..b8769ce0af0 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsEventV1.kt +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionEventV1.kt @@ -29,7 +29,7 @@ import com.squareup.moshi.JsonClass */ -data class WatchBlocksCactusTransactionsEventV1 ( +data class CactiBlockTransactionEventV1 ( /* ChainCode containing function that was executed. */ @Json(name = "chaincodeId") diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsResponseV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionsResponseV1.kt similarity index 62% rename from packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsResponseV1.kt rename to packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionsResponseV1.kt index c79b366efb0..cde44f8ed03 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsResponseV1.kt +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CactiBlockTransactionsResponseV1.kt @@ -15,7 +15,7 @@ package org.openapitools.client.models -import org.openapitools.client.models.WatchBlocksCactusTransactionsEventV1 +import org.openapitools.client.models.CactiBlockTransactionEventV1 import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @@ -23,15 +23,15 @@ import com.squareup.moshi.JsonClass /** * Custom response containing block transactions summary. Compatible with legacy fabric-socketio connector monitoring. * - * @param cactusTransactionsEvents List of transactions summary + * @param cactiTransactionsEvents List of transactions summary */ -data class WatchBlocksCactusTransactionsResponseV1 ( +data class CactiBlockTransactionsResponseV1 ( /* List of transactions summary */ - @Json(name = "cactusTransactionsEvents") - val cactusTransactionsEvents: kotlin.collections.List + @Json(name = "cactiTransactionsEvents") + val cactiTransactionsEvents: kotlin.collections.List ) diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricCertificateIdentityV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricCertificateIdentityV1.kt new file mode 100644 index 00000000000..f08a5362aa0 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricCertificateIdentityV1.kt @@ -0,0 +1,40 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.FabricX509CertificateV1 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Combination of certificate and it's MSP ID used to identify fabric actors. + * + * @param mspid + * @param cert + */ + + +data class FabricCertificateIdentityV1 ( + + @Json(name = "mspid") + val mspid: kotlin.String, + + @Json(name = "cert") + val cert: FabricX509CertificateV1 + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricX509CertificateV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricX509CertificateV1.kt new file mode 100644 index 00000000000..e995cc65f0d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FabricX509CertificateV1.kt @@ -0,0 +1,59 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Transaction endorser certificate object + * + * @param issuer + * @param serialNumber + * @param subject + * @param subjectAltName + * @param validFrom + * @param validTo + * @param pem + */ + + +data class FabricX509CertificateV1 ( + + @Json(name = "issuer") + val issuer: kotlin.String, + + @Json(name = "serialNumber") + val serialNumber: kotlin.String, + + @Json(name = "subject") + val subject: kotlin.String, + + @Json(name = "subjectAltName") + val subjectAltName: kotlin.String, + + @Json(name = "validFrom") + val validFrom: kotlin.String, + + @Json(name = "validTo") + val validTo: kotlin.String, + + @Json(name = "pem") + val pem: kotlin.String + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionActionV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionActionV1.kt new file mode 100644 index 00000000000..1e0dd13b4e0 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionActionV1.kt @@ -0,0 +1,53 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.FabricCertificateIdentityV1 +import org.openapitools.client.models.FullBlockTransactionEndorsementV1 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Transaction action returned from fabric block. + * + * @param functionName + * @param functionArgs + * @param chaincodeId + * @param creator + * @param endorsements + */ + + +data class FullBlockTransactionActionV1 ( + + @Json(name = "functionName") + val functionName: kotlin.String, + + @Json(name = "functionArgs") + val functionArgs: kotlin.collections.List, + + @Json(name = "chaincodeId") + val chaincodeId: kotlin.String, + + @Json(name = "creator") + val creator: FabricCertificateIdentityV1, + + @Json(name = "endorsements") + val endorsements: kotlin.collections.List + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEndorsementV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEndorsementV1.kt new file mode 100644 index 00000000000..e85c781fca2 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEndorsementV1.kt @@ -0,0 +1,40 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.FabricCertificateIdentityV1 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Transaction endorsment object returned from fabric block. + * + * @param signer + * @param signature + */ + + +data class FullBlockTransactionEndorsementV1 ( + + @Json(name = "signer") + val signer: FabricCertificateIdentityV1, + + @Json(name = "signature") + val signature: kotlin.String + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEventV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEventV1.kt new file mode 100644 index 00000000000..2d27765dd56 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/FullBlockTransactionEventV1.kt @@ -0,0 +1,60 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.FullBlockTransactionActionV1 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Transaction returned from fabric block. + * + * @param hash + * @param channelId + * @param timestamp + * @param protocolVersion + * @param type + * @param epoch + * @param actions + */ + + +data class FullBlockTransactionEventV1 ( + + @Json(name = "hash") + val hash: kotlin.String, + + @Json(name = "channelId") + val channelId: kotlin.String, + + @Json(name = "timestamp") + val timestamp: kotlin.String, + + @Json(name = "protocolVersion") + val protocolVersion: java.math.BigDecimal, + + @Json(name = "type") + val type: kotlin.String, + + @Json(name = "epoch") + val epoch: java.math.BigDecimal, + + @Json(name = "actions") + val actions: kotlin.collections.List + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockRequestV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockRequestV1.kt index bdb130a8690..74a36121d76 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockRequestV1.kt +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockRequestV1.kt @@ -17,6 +17,7 @@ package org.openapitools.client.models import org.openapitools.client.models.GatewayOptions import org.openapitools.client.models.GetBlockRequestV1Query +import org.openapitools.client.models.GetBlockResponseTypeV1 import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @@ -28,7 +29,7 @@ import com.squareup.moshi.JsonClass * @param gatewayOptions * @param query * @param connectionChannelName Fabric channel we want to connect to. If not provided, then one from channelName parameter will be used - * @param skipDecode If true, encoded buffer will be returned. Otherwise, entire block object is returned. + * @param type */ @@ -48,9 +49,8 @@ data class GetBlockRequestV1 ( @Json(name = "connectionChannelName") val connectionChannelName: kotlin.String? = null, - /* If true, encoded buffer will be returned. Otherwise, entire block object is returned. */ - @Json(name = "skipDecode") - val skipDecode: kotlin.Boolean? = false + @Json(name = "type") + val type: GetBlockResponseTypeV1? = GetBlockResponseTypeV1.Full ) diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseTypeV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseTypeV1.kt new file mode 100644 index 00000000000..22f8ae1050c --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseTypeV1.kt @@ -0,0 +1,69 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Response type from GetBlock. + * + * Values: Full,Encoded,CactiTransactions,CactiFullBlock + */ + +@JsonClass(generateAdapter = false) +enum class GetBlockResponseTypeV1(val value: kotlin.String) { + + @Json(name = "full") + Full("full"), + + @Json(name = "encoded") + Encoded("encoded"), + + @Json(name = "cacti:transactions") + CactiTransactions("cacti:transactions"), + + @Json(name = "cacti:full-block") + CactiFullBlock("cacti:full-block"); + + /** + * Override [toString()] to avoid using the enum variable name as the value, and instead use + * the actual value defined in the API spec file. + * + * This solves a problem when the variable name and its value are different, and ensures that + * the client sends the correct enum values to the server always. + */ + override fun toString(): String = value + + companion object { + /** + * Converts the provided [data] to a [String] on success, null otherwise. + */ + fun encode(data: kotlin.Any?): kotlin.String? = if (data is GetBlockResponseTypeV1) "$data" else null + + /** + * Returns a valid [GetBlockResponseTypeV1] for [data], null otherwise. + */ + fun decode(data: kotlin.Any?): GetBlockResponseTypeV1? = data?.let { + val normalizedData = "$it".lowercase() + values().firstOrNull { value -> + it == value || normalizedData == "$value".lowercase() + } + } + } +} + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseV1.kt index 967461afd3e..c19a2c8cbe5 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseV1.kt +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetBlockResponseV1.kt @@ -15,6 +15,10 @@ package org.openapitools.client.models +import org.openapitools.client.models.CactiBlockFullEventV1 +import org.openapitools.client.models.CactiBlockFullResponseV1 +import org.openapitools.client.models.CactiBlockTransactionEventV1 +import org.openapitools.client.models.CactiBlockTransactionsResponseV1 import org.openapitools.client.models.GetBlockResponseDecodedV1 import org.openapitools.client.models.GetBlockResponseEncodedV1 @@ -24,6 +28,8 @@ import com.squareup.moshi.JsonClass /** * Response from GetBlock endpoint. * + * @param cactiTransactionsEvents List of transactions summary + * @param cactiFullEvents * @param decodedBlock Full hyperledger fabric block data. * @param encodedBlock */ @@ -31,6 +37,13 @@ import com.squareup.moshi.JsonClass data class GetBlockResponseV1 ( + /* List of transactions summary */ + @Json(name = "cactiTransactionsEvents") + val cactiTransactionsEvents: kotlin.collections.List, + + @Json(name = "cactiFullEvents") + val cactiFullEvents: CactiBlockFullEventV1, + /* Full hyperledger fabric block data. */ @Json(name = "decodedBlock") val decodedBlock: kotlin.Any?, diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoRequestV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoRequestV1.kt new file mode 100644 index 00000000000..71b1d92cf3d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoRequestV1.kt @@ -0,0 +1,46 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + +import org.openapitools.client.models.GatewayOptions + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Request for GetChainInfo endpoint. + * + * @param channelName Fabric channel which we want to query. + * @param gatewayOptions + * @param connectionChannelName Fabric channel we want to connect to. If not provided, then one from channelName parameter will be used + */ + + +data class GetChainInfoRequestV1 ( + + /* Fabric channel which we want to query. */ + @Json(name = "channelName") + val channelName: kotlin.String, + + @Json(name = "gatewayOptions") + val gatewayOptions: GatewayOptions, + + /* Fabric channel we want to connect to. If not provided, then one from channelName parameter will be used */ + @Json(name = "connectionChannelName") + val connectionChannelName: kotlin.String? = null + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoResponseV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoResponseV1.kt new file mode 100644 index 00000000000..81201ab2222 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/GetChainInfoResponseV1.kt @@ -0,0 +1,46 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package org.openapitools.client.models + + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Response from GetChainInfo endpoint. + * + * @param height Current height of fabric ledger + * @param currentBlockHash Current block hash of fabric ledger + * @param previousBlockHash Previous block hash of fabric ledger + */ + + +data class GetChainInfoResponseV1 ( + + /* Current height of fabric ledger */ + @Json(name = "height") + val height: java.math.BigDecimal, + + /* Current block hash of fabric ledger */ + @Json(name = "currentBlockHash") + val currentBlockHash: kotlin.String, + + /* Previous block hash of fabric ledger */ + @Json(name = "previousBlockHash") + val previousBlockHash: kotlin.String + +) + diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksListenerTypeV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksListenerTypeV1.kt index 42a5adf2c8c..5656a4b7ec8 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksListenerTypeV1.kt +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksListenerTypeV1.kt @@ -20,9 +20,9 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Response type from WatchBlocks. 'Cactus*' are custom views, others correspond to fabric SDK call. + * Response type from WatchBlocks. 'Cacti*' are custom views, others correspond to fabric SDK call. * - * Values: Filtered,Full,Private,CactusTransactions + * Values: Filtered,Full,Private,CactiTransactions,CactiFullBlock */ @JsonClass(generateAdapter = false) @@ -37,8 +37,11 @@ enum class WatchBlocksListenerTypeV1(val value: kotlin.String) { @Json(name = "private") Private("private"), - @Json(name = "cactus:transactions") - CactusTransactions("cactus:transactions"); + @Json(name = "cacti:transactions") + CactiTransactions("cacti:transactions"), + + @Json(name = "cacti:full-block") + CactiFullBlock("cacti:full-block"); /** * Override [toString()] to avoid using the enum variable name as the value, and instead use diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksResponseV1.kt b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksResponseV1.kt index 4939f4c2f14..0fff884ce6a 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksResponseV1.kt +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/WatchBlocksResponseV1.kt @@ -15,9 +15,11 @@ package org.openapitools.client.models +import org.openapitools.client.models.CactiBlockFullEventV1 +import org.openapitools.client.models.CactiBlockFullResponseV1 +import org.openapitools.client.models.CactiBlockTransactionEventV1 +import org.openapitools.client.models.CactiBlockTransactionsResponseV1 import org.openapitools.client.models.WatchBlocksCactusErrorResponseV1 -import org.openapitools.client.models.WatchBlocksCactusTransactionsEventV1 -import org.openapitools.client.models.WatchBlocksCactusTransactionsResponseV1 import org.openapitools.client.models.WatchBlocksFilteredResponseV1 import org.openapitools.client.models.WatchBlocksFullResponseV1 import org.openapitools.client.models.WatchBlocksPrivateResponseV1 @@ -28,7 +30,8 @@ import com.squareup.moshi.JsonClass /** * Response block from WatchBlocks endpoint. Depends on 'type' passed in subscription options. * - * @param cactusTransactionsEvents List of transactions summary + * @param cactiTransactionsEvents List of transactions summary + * @param cactiFullEvents * @param fullBlock Full commited block. * @param filteredBlock Filtered commited block. * @param privateBlock Private commited block. @@ -40,8 +43,11 @@ import com.squareup.moshi.JsonClass data class WatchBlocksResponseV1 ( /* List of transactions summary */ - @Json(name = "cactusTransactionsEvents") - val cactusTransactionsEvents: kotlin.collections.List, + @Json(name = "cactiTransactionsEvents") + val cactiTransactionsEvents: kotlin.collections.List, + + @Json(name = "cactiFullEvents") + val cactiFullEvents: CactiBlockFullEventV1, /* Full commited block. */ @Json(name = "fullBlock") diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/api-client/fabric-api-client.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/api-client/fabric-api-client.ts index c3ae330dde9..c43a0476413 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/api-client/fabric-api-client.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/api-client/fabric-api-client.ts @@ -15,8 +15,8 @@ import { WatchBlocksOptionsV1, WatchBlocksResponseV1, WatchBlocksDelegatedSignOptionsV1, - WatchBlocksCactusTransactionsResponseV1, - WatchBlocksCactusTransactionsEventV1, + CactiBlockTransactionsResponseV1, + CactiBlockTransactionEventV1, } from "../generated/openapi/typescript-axios"; import { Configuration } from "../generated/openapi/typescript-axios/configuration"; @@ -181,56 +181,54 @@ export class FabricApiClient * @warning: Remember to use timeout mechanism on production * * @param txId transactionId to wait for. - * @param monitorObservable Block observable in CactusTransactions mode (important - other mode will not work!) - * @returns `WatchBlocksCactusTransactionsEventV1` of specified transaction. + * @param monitorObservable Block observable in CactiTransactions mode (important - other mode will not work!) + * @returns `CactiBlockTransactionEventV1` of specified transaction. */ public async waitForTransactionCommit( txId: string, - monitorObservable: Observable, + monitorObservable: Observable, ) { this.log.info("waitForTransactionCommit()", txId); - return new Promise( - (resolve, reject) => { - const subscription = monitorObservable.subscribe({ - next: (event) => { - try { - this.log.debug( - "waitForTransactionCommit() Received event:", - JSON.stringify(event), - ); - if (!("cactusTransactionsEvents" in event)) { - throw new Error("Invalid event type received!"); - } + return new Promise((resolve, reject) => { + const subscription = monitorObservable.subscribe({ + next: (event) => { + try { + this.log.debug( + "waitForTransactionCommit() Received event:", + JSON.stringify(event), + ); + if (!("cactiTransactionsEvents" in event)) { + throw new Error("Invalid event type received!"); + } - const foundTransaction = event.cactusTransactionsEvents.find( - (tx) => tx.transactionId === txId, - ); - if (foundTransaction) { - this.log.info( - "waitForTransactionCommit() Found transaction with txId", - txId, - ); - subscription.unsubscribe(); - resolve(foundTransaction); - } - } catch (err) { - this.log.error( - "waitForTransactionCommit() event check error:", - err, + const foundTransaction = event.cactiTransactionsEvents.find( + (tx) => tx.transactionId === txId, + ); + if (foundTransaction) { + this.log.info( + "waitForTransactionCommit() Found transaction with txId", + txId, ); subscription.unsubscribe(); - reject(err); + resolve(foundTransaction); } - }, - error: (err) => { - this.log.error("waitForTransactionCommit() error:", err); + } catch (err) { + this.log.error( + "waitForTransactionCommit() event check error:", + err, + ); subscription.unsubscribe(); reject(err); - }, - }); - }, - ); + } + }, + error: (err) => { + this.log.error("waitForTransactionCommit() error:", err); + subscription.unsubscribe(); + reject(err); + }, + }); + }); } /** diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/query-system-chain-code.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/query-system-chain-code.ts index da84cd81f06..0b26fca3844 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/query-system-chain-code.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/query-system-chain-code.ts @@ -1,6 +1,4 @@ import { Gateway } from "fabric-network"; -// BlockDecoder is not exported in ts definition so we need to use legacy import. -const { BlockDecoder } = require("fabric-common"); const QSCC_ContractName = "qscc"; @@ -10,7 +8,6 @@ const QSCC_ContractName = "qscc"; export interface QuerySystemChainCodeConfig { gateway: Gateway; connectionChannelName: string; // used to connect to the network - skipDecode?: boolean; } /** @@ -19,14 +16,15 @@ export interface QuerySystemChainCodeConfig { * @param config Configuration of `querySystemChainCode` method itself. * @param functionName Method on `qscc` to call. * @param args Args to method from `functionName` - * @returns Encoded `Buffer` or decoded `JSON string` response from the ledger. + * + * @returns Encoded `Buffer` response from the ledger. */ export async function querySystemChainCode( config: QuerySystemChainCodeConfig, functionName: string, ...args: (string | Buffer)[] -): Promise { - const { gateway, connectionChannelName, skipDecode } = config; +): Promise { + const { gateway, connectionChannelName } = config; const network = await gateway.getNetwork(connectionChannelName); const contract = network.getContract(QSCC_ContractName); @@ -38,9 +36,5 @@ export async function querySystemChainCode( throw new Error(`Received empty response from qscc call ${functionName}`); } - if (skipDecode) { - return resultBuffer; - } - - return BlockDecoder.decode(resultBuffer); + return resultBuffer; } diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/utils.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/utils.ts index ac71ac1cedd..b1679192f3a 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/utils.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/utils.ts @@ -1,3 +1,5 @@ +import Long from "long"; + /** * Check if provided variable is a function. Throws otherwise. * To be used with unsafe `require()` imports from fabric SDK packages. @@ -29,3 +31,25 @@ export function asBuffer(bytes: Uint8Array | null | undefined): Buffer { return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength); // Create a Buffer view to avoid copying } + +export type FabricLong = { + low: number; + hight: number | undefined; + unsigned: boolean | undefined; +}; + +/** + * Convert Long value returned by some low-level fabric API to regular number. + * + * @param longNumberObject Long object (with low and hight fields) + * + * @returns number + */ +export function fabricLongToNumber(longNumberObject: FabricLong) { + const longValue = new Long( + longNumberObject.low, + longNumberObject.hight, + longNumberObject.unsigned, + ); + return longValue.toNumber(); +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts index 062222a01e6..bd0168e44c0 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -23,6 +23,100 @@ import type { RequestArgs } from './base'; // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base'; +/** + * Custom format full fabric block with transactions + * @export + * @interface CactiBlockFullEventV1 + */ +export interface CactiBlockFullEventV1 { + /** + * + * @type {number} + * @memberof CactiBlockFullEventV1 + */ + 'blockNumber': number; + /** + * + * @type {string} + * @memberof CactiBlockFullEventV1 + */ + 'blockHash': string; + /** + * + * @type {string} + * @memberof CactiBlockFullEventV1 + */ + 'previousBlockHash': string; + /** + * + * @type {number} + * @memberof CactiBlockFullEventV1 + */ + 'transactionCount': number; + /** + * + * @type {Array} + * @memberof CactiBlockFullEventV1 + */ + 'cactiTransactionsEvents': Array; +} +/** + * Custom response containing full block summary. + * @export + * @interface CactiBlockFullResponseV1 + */ +export interface CactiBlockFullResponseV1 { + /** + * + * @type {CactiBlockFullEventV1} + * @memberof CactiBlockFullResponseV1 + */ + 'cactiFullEvents': CactiBlockFullEventV1; +} +/** + * Transaction summary from commited block. + * @export + * @interface CactiBlockTransactionEventV1 + */ +export interface CactiBlockTransactionEventV1 { + /** + * ChainCode containing function that was executed. + * @type {string} + * @memberof CactiBlockTransactionEventV1 + */ + 'chaincodeId': string; + /** + * Transaction identifier. + * @type {string} + * @memberof CactiBlockTransactionEventV1 + */ + 'transactionId': string; + /** + * Function name that was executed. + * @type {string} + * @memberof CactiBlockTransactionEventV1 + */ + 'functionName': string; + /** + * List of function arguments. + * @type {Array} + * @memberof CactiBlockTransactionEventV1 + */ + 'functionArgs': Array; +} +/** + * Custom response containing block transactions summary. Compatible with legacy fabric-socketio connector monitoring. + * @export + * @interface CactiBlockTransactionsResponseV1 + */ +export interface CactiBlockTransactionsResponseV1 { + /** + * List of transactions summary + * @type {Array} + * @memberof CactiBlockTransactionsResponseV1 + */ + 'cactiTransactionsEvents': Array; +} /** * Enumerates the supported programming language runtimes of Hyperledger Fabric * @export @@ -554,6 +648,25 @@ export interface ErrorExceptionResponseV1 { */ 'error': string; } +/** + * Combination of certificate and it\'s MSP ID used to identify fabric actors. + * @export + * @interface FabricCertificateIdentityV1 + */ +export interface FabricCertificateIdentityV1 { + /** + * + * @type {string} + * @memberof FabricCertificateIdentityV1 + */ + 'mspid': string; + /** + * + * @type {FabricX509CertificateV1} + * @memberof FabricCertificateIdentityV1 + */ + 'cert': FabricX509CertificateV1; +} /** * * @export @@ -623,6 +736,55 @@ export const FabricSigningCredentialType = { export type FabricSigningCredentialType = typeof FabricSigningCredentialType[keyof typeof FabricSigningCredentialType]; +/** + * Transaction endorser certificate object + * @export + * @interface FabricX509CertificateV1 + */ +export interface FabricX509CertificateV1 { + /** + * + * @type {string} + * @memberof FabricX509CertificateV1 + */ + 'issuer': string; + /** + * + * @type {string} + * @memberof FabricX509CertificateV1 + */ + 'serialNumber': string; + /** + * + * @type {string} + * @memberof FabricX509CertificateV1 + */ + 'subject': string; + /** + * + * @type {string} + * @memberof FabricX509CertificateV1 + */ + 'subjectAltName': string; + /** + * + * @type {string} + * @memberof FabricX509CertificateV1 + */ + 'validFrom': string; + /** + * + * @type {string} + * @memberof FabricX509CertificateV1 + */ + 'validTo': string; + /** + * + * @type {string} + * @memberof FabricX509CertificateV1 + */ + 'pem': string; +} /** * Represents a file-system file that has a name and a body which holds the file contents as a Base64 encoded string * @export @@ -648,6 +810,111 @@ export interface FileBase64 { */ 'filepath'?: string; } +/** + * Transaction action returned from fabric block. + * @export + * @interface FullBlockTransactionActionV1 + */ +export interface FullBlockTransactionActionV1 { + /** + * + * @type {string} + * @memberof FullBlockTransactionActionV1 + */ + 'functionName': string; + /** + * + * @type {Array} + * @memberof FullBlockTransactionActionV1 + */ + 'functionArgs': Array; + /** + * + * @type {string} + * @memberof FullBlockTransactionActionV1 + */ + 'chaincodeId': string; + /** + * + * @type {FabricCertificateIdentityV1} + * @memberof FullBlockTransactionActionV1 + */ + 'creator': FabricCertificateIdentityV1; + /** + * + * @type {Array} + * @memberof FullBlockTransactionActionV1 + */ + 'endorsements': Array; +} +/** + * Transaction endorsment object returned from fabric block. + * @export + * @interface FullBlockTransactionEndorsementV1 + */ +export interface FullBlockTransactionEndorsementV1 { + /** + * + * @type {FabricCertificateIdentityV1} + * @memberof FullBlockTransactionEndorsementV1 + */ + 'signer': FabricCertificateIdentityV1; + /** + * + * @type {string} + * @memberof FullBlockTransactionEndorsementV1 + */ + 'signature': string; +} +/** + * Transaction returned from fabric block. + * @export + * @interface FullBlockTransactionEventV1 + */ +export interface FullBlockTransactionEventV1 { + /** + * + * @type {string} + * @memberof FullBlockTransactionEventV1 + */ + 'hash': string; + /** + * + * @type {string} + * @memberof FullBlockTransactionEventV1 + */ + 'channelId': string; + /** + * + * @type {string} + * @memberof FullBlockTransactionEventV1 + */ + 'timestamp': string; + /** + * + * @type {number} + * @memberof FullBlockTransactionEventV1 + */ + 'protocolVersion': number; + /** + * + * @type {string} + * @memberof FullBlockTransactionEventV1 + */ + 'type': string; + /** + * + * @type {number} + * @memberof FullBlockTransactionEventV1 + */ + 'epoch': number; + /** + * + * @type {Array} + * @memberof FullBlockTransactionEventV1 + */ + 'actions': Array; +} /** * * @export @@ -781,12 +1048,14 @@ export interface GetBlockRequestV1 { */ 'query': GetBlockRequestV1Query; /** - * If true, encoded buffer will be returned. Otherwise, entire block object is returned. - * @type {boolean} + * + * @type {GetBlockResponseTypeV1} * @memberof GetBlockRequestV1 */ - 'skipDecode'?: boolean; + 'type'?: GetBlockResponseTypeV1; } + + /** * Query selector, caller must provide at least one of them. First found will be used, rest will be ignored, so it\'s recommended to pass single selector. * @export @@ -857,13 +1126,79 @@ export interface GetBlockResponseEncodedV1 { */ 'encodedBlock': string; } +/** + * Response type from GetBlock. + * @export + * @enum {string} + */ + +export const GetBlockResponseTypeV1 = { + Full: 'full', + Encoded: 'encoded', + CactiTransactions: 'cacti:transactions', + CactiFullBlock: 'cacti:full-block' +} as const; + +export type GetBlockResponseTypeV1 = typeof GetBlockResponseTypeV1[keyof typeof GetBlockResponseTypeV1]; + + /** * @type GetBlockResponseV1 * Response from GetBlock endpoint. * @export */ -export type GetBlockResponseV1 = GetBlockResponseDecodedV1 | GetBlockResponseEncodedV1; +export type GetBlockResponseV1 = CactiBlockFullResponseV1 | CactiBlockTransactionsResponseV1 | GetBlockResponseDecodedV1 | GetBlockResponseEncodedV1; +/** + * Request for GetChainInfo endpoint. + * @export + * @interface GetChainInfoRequestV1 + */ +export interface GetChainInfoRequestV1 { + /** + * Fabric channel which we want to query. + * @type {string} + * @memberof GetChainInfoRequestV1 + */ + 'channelName': string; + /** + * Fabric channel we want to connect to. If not provided, then one from channelName parameter will be used + * @type {string} + * @memberof GetChainInfoRequestV1 + */ + 'connectionChannelName'?: string; + /** + * + * @type {GatewayOptions} + * @memberof GetChainInfoRequestV1 + */ + 'gatewayOptions': GatewayOptions; +} +/** + * Response from GetChainInfo endpoint. + * @export + * @interface GetChainInfoResponseV1 + */ +export interface GetChainInfoResponseV1 { + /** + * Current height of fabric ledger + * @type {number} + * @memberof GetChainInfoResponseV1 + */ + 'height': number; + /** + * Current block hash of fabric ledger + * @type {string} + * @memberof GetChainInfoResponseV1 + */ + 'currentBlockHash': string; + /** + * Previous block hash of fabric ledger + * @type {string} + * @memberof GetChainInfoResponseV1 + */ + 'previousBlockHash': string; +} /** * * @export @@ -1258,50 +1593,6 @@ export interface WatchBlocksCactusErrorResponseV1 { */ 'errorMessage': string; } -/** - * Transaction summary from commited block. - * @export - * @interface WatchBlocksCactusTransactionsEventV1 - */ -export interface WatchBlocksCactusTransactionsEventV1 { - /** - * ChainCode containing function that was executed. - * @type {string} - * @memberof WatchBlocksCactusTransactionsEventV1 - */ - 'chaincodeId': string; - /** - * Transaction identifier. - * @type {string} - * @memberof WatchBlocksCactusTransactionsEventV1 - */ - 'transactionId': string; - /** - * Function name that was executed. - * @type {string} - * @memberof WatchBlocksCactusTransactionsEventV1 - */ - 'functionName': string; - /** - * List of function arguments. - * @type {Array} - * @memberof WatchBlocksCactusTransactionsEventV1 - */ - 'functionArgs': Array; -} -/** - * Custom response containing block transactions summary. Compatible with legacy fabric-socketio connector monitoring. - * @export - * @interface WatchBlocksCactusTransactionsResponseV1 - */ -export interface WatchBlocksCactusTransactionsResponseV1 { - /** - * List of transactions summary - * @type {Array} - * @memberof WatchBlocksCactusTransactionsResponseV1 - */ - 'cactusTransactionsEvents': Array; -} /** * Options passed when subscribing to block monitoring with delegated signing. * @export @@ -1374,7 +1665,7 @@ export interface WatchBlocksFullResponseV1 { 'fullBlock': any; } /** - * Response type from WatchBlocks. \'Cactus*\' are custom views, others correspond to fabric SDK call. + * Response type from WatchBlocks. \'Cacti*\' are custom views, others correspond to fabric SDK call. * @export * @enum {string} */ @@ -1383,7 +1674,8 @@ export const WatchBlocksListenerTypeV1 = { Filtered: 'filtered', Full: 'full', Private: 'private', - CactusTransactions: 'cactus:transactions' + CactiTransactions: 'cacti:transactions', + CactiFullBlock: 'cacti:full-block' } as const; export type WatchBlocksListenerTypeV1 = typeof WatchBlocksListenerTypeV1[keyof typeof WatchBlocksListenerTypeV1]; @@ -1440,7 +1732,7 @@ export interface WatchBlocksPrivateResponseV1 { * Response block from WatchBlocks endpoint. Depends on \'type\' passed in subscription options. * @export */ -export type WatchBlocksResponseV1 = WatchBlocksCactusErrorResponseV1 | WatchBlocksCactusTransactionsResponseV1 | WatchBlocksFilteredResponseV1 | WatchBlocksFullResponseV1 | WatchBlocksPrivateResponseV1; +export type WatchBlocksResponseV1 = CactiBlockFullResponseV1 | CactiBlockTransactionsResponseV1 | WatchBlocksCactusErrorResponseV1 | WatchBlocksFilteredResponseV1 | WatchBlocksFullResponseV1 | WatchBlocksPrivateResponseV1; /** * Websocket requests for monitoring new blocks. @@ -1588,6 +1880,40 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @summary Get fabric ledger chain info. + * @param {GetChainInfoRequestV1} [getChainInfoRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getChainInfoV1: async (getChainInfoRequestV1?: GetChainInfoRequestV1, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-chain-info`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(getChainInfoRequestV1, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary Get the Prometheus Metrics @@ -1769,6 +2095,17 @@ export const DefaultApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getBlockV1(getBlockRequestV1, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @summary Get fabric ledger chain info. + * @param {GetChainInfoRequestV1} [getChainInfoRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getChainInfoV1(getChainInfoRequestV1?: GetChainInfoRequestV1, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getChainInfoV1(getChainInfoRequestV1, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @summary Get the Prometheus Metrics @@ -1852,6 +2189,16 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa getBlockV1(getBlockRequestV1?: GetBlockRequestV1, options?: any): AxiosPromise { return localVarFp.getBlockV1(getBlockRequestV1, options).then((request) => request(axios, basePath)); }, + /** + * + * @summary Get fabric ledger chain info. + * @param {GetChainInfoRequestV1} [getChainInfoRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getChainInfoV1(getChainInfoRequestV1?: GetChainInfoRequestV1, options?: any): AxiosPromise { + return localVarFp.getChainInfoV1(getChainInfoRequestV1, options).then((request) => request(axios, basePath)); + }, /** * * @summary Get the Prometheus Metrics @@ -1937,6 +2284,18 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration).getBlockV1(getBlockRequestV1, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary Get fabric ledger chain info. + * @param {GetChainInfoRequestV1} [getChainInfoRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getChainInfoV1(getChainInfoRequestV1?: GetChainInfoRequestV1, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getChainInfoV1(getChainInfoRequestV1, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Get the Prometheus Metrics diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/cacti-block-formatters.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/cacti-block-formatters.ts new file mode 100644 index 00000000000..508edfea3b8 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/cacti-block-formatters.ts @@ -0,0 +1,202 @@ +import { BinaryLike, X509Certificate } from "node:crypto"; +import fabricProtos from "fabric-protos"; + +import { + LoggerProvider, + safeStringifyException, +} from "@hyperledger/cactus-common"; + +import { + CactiBlockFullResponseV1, + CactiBlockTransactionEventV1, + CactiBlockTransactionsResponseV1, + FabricX509CertificateV1, + FullBlockTransactionActionV1, + FullBlockTransactionEndorsementV1, + FullBlockTransactionEventV1, +} from "../generated/openapi/typescript-axios"; +import { fabricLongToNumber } from "../common/utils"; + +const level = "INFO"; +const label = "cacti-block-formatters"; +const log = LoggerProvider.getOrCreate({ level, label }); + +/** + * Convert certificate binary received from fabric ledger to object representation. + * + * @param certBuffer fabric X.509 certificate buffer + * + * @returns `FabricX509CertificateV1` + */ +function parseX509CertToObject( + certBuffer: BinaryLike, +): FabricX509CertificateV1 { + const cert = new X509Certificate(certBuffer); + + return { + serialNumber: cert.serialNumber, + subject: cert.subject, + issuer: cert.issuer, + subjectAltName: cert.subjectAltName ?? "", + validFrom: cert.validFrom, + validTo: cert.validTo, + pem: cert.toString(), + }; +} + +/** + * Extract full block summary from a fabric. More data can be added here in the future + * if there's a need for it. + * + * @param blockEvent full block event + * + * @returns parsed block data including transactions, actions, endorsements, signatures. + */ +export function formatCactiFullBlockResponse( + blockEvent: fabricProtos.common.IBlock, +): CactiBlockFullResponseV1 { + const transactionData = (blockEvent.data?.data ?? []) as any[]; + + const header = blockEvent.header; + if (!header) { + log.warn( + "Received block event without a header:", + JSON.stringify(blockEvent), + ); + } + const blockNumber = header ? fabricLongToNumber(header.number) : -1; + const blockHash = + "0x" + Buffer.from(blockEvent.header?.data_hash ?? "").toString("hex"); + const previousBlockHash = + "0x" + Buffer.from(blockEvent.header?.previous_hash ?? "").toString("hex"); + const transactionCount = transactionData.length; + + const transactions: FullBlockTransactionEventV1[] = []; + for (const data of transactionData) { + try { + const payload = data.payload; + const channelHeader = payload.header.channel_header; + const transaction = payload.data; + + const transactionActions: FullBlockTransactionActionV1[] = []; + for (const action of transaction.actions) { + const actionPayload = action.payload; + const proposalPayload = actionPayload.chaincode_proposal_payload; + const invocationSpec = proposalPayload.input; + const actionCreatorCert = parseX509CertToObject( + action.header.creator.id_bytes, + ); + const actionCreatorMspId = action.header.creator.mspid; + + // Decode args and function name + const rawArgs = invocationSpec.chaincode_spec.input.args as Buffer[]; + const decodedArgs = rawArgs.map((arg: Buffer) => arg.toString("utf8")); + const functionName = decodedArgs.shift() ?? ""; + const chaincodeId = invocationSpec.chaincode_spec.chaincode_id.name; + + const endorsements = actionPayload.action.endorsements.map((e: any) => { + return { + signer: { + mspid: e.endorser.mspid, + cert: parseX509CertToObject(e.endorser.id_bytes), + }, + signature: "0x" + Buffer.from(e.signature).toString("hex"), + } as FullBlockTransactionEndorsementV1; + }); + + transactionActions.push({ + functionName, + functionArgs: decodedArgs, + chaincodeId, + creator: { + mspid: actionCreatorMspId, + cert: actionCreatorCert, + }, + endorsements, + }); + } + + transactions.push({ + hash: channelHeader.tx_id, + channelId: channelHeader.channel_id, + timestamp: channelHeader.timestamp, + protocolVersion: channelHeader.version, + type: channelHeader.typeString, + epoch: fabricLongToNumber(channelHeader.epoch), + actions: transactionActions, + }); + } catch (error) { + log.warn( + "Could not retrieve transaction from received block. Error:", + safeStringifyException(error), + ); + } + } + + return { + cactiFullEvents: { + blockNumber, + blockHash, + previousBlockHash, + transactionCount, + cactiTransactionsEvents: transactions, + }, + }; +} + +/** + * Extract transaction summary from a fabric block. + * + * @param blockEvent full block event + * + * @returns actions summary, including function name, args, and chaincode. + */ +export function formatCactiTransactionsBlockResponse( + blockEvent: fabricProtos.common.IBlock, +): CactiBlockTransactionsResponseV1 { + const transactionData = blockEvent.data?.data as any[]; + if (!transactionData) { + log.debug("Block transaction data empty - ignore..."); + return { + cactiTransactionsEvents: [], + }; + } + + const transactions: CactiBlockTransactionEventV1[] = []; + for (const data of transactionData) { + try { + const payload = data.payload; + const transaction = payload.data; + for (const action of transaction.actions) { + const actionPayload = action.payload; + const proposalPayload = actionPayload.chaincode_proposal_payload; + const invocationSpec = proposalPayload.input; + + // Decode args and function name + const rawArgs = invocationSpec.chaincode_spec.input.args as Buffer[]; + const decodedArgs = rawArgs.map((arg: Buffer) => arg.toString("utf8")); + const functionName = decodedArgs.shift() ?? ""; + + const chaincodeId = invocationSpec.chaincode_spec.chaincode_id.name; + const channelHeader = payload.header.channel_header; + const transactionId = channelHeader.tx_id; + + transactions.push({ + chaincodeId, + transactionId, + functionName, + functionArgs: decodedArgs, + }); + } + } catch (error) { + log.warn( + "Could not retrieve transaction from received block. Error:", + safeStringifyException(error), + ); + } + } + + return { + cactiTransactionsEvents: transactions, + }; +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/get-block-endpoint-v1.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/get-block-endpoint-v1.ts index 763ed05573b..b9399d5f515 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/get-block-endpoint-v1.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-block/get-block-endpoint-v1.ts @@ -6,7 +6,6 @@ import { LogLevelDesc, Checks, IAsyncProvider, - safeStringifyException, } from "@hyperledger/cactus-common"; import { @@ -15,7 +14,10 @@ import { IEndpointAuthzOptions, } from "@hyperledger/cactus-core-api"; -import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; +import { + handleRestEndpointException, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; import { PluginLedgerConnectorFabric } from "../plugin-ledger-connector-fabric"; import OAS from "../../json/openapi.json"; @@ -88,11 +90,8 @@ export class GetBlockEndpointV1 implements IWebServiceEndpoint { try { res.status(200).send(await this.opts.connector.getBlock(req.body)); } catch (error) { - this.log.error(`Crash while serving ${fnTag}`, error); - res.status(500).json({ - message: "Internal Server Error", - error: safeStringifyException(error), - }); + const errorMsg = `Crash while serving ${fnTag}`; + handleRestEndpointException({ errorMsg, log: this.log, error, res }); } } } diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-chain-info/get-chain-info-endpoint-v1.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-chain-info/get-chain-info-endpoint-v1.ts new file mode 100644 index 00000000000..122439dde96 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-chain-info/get-chain-info-endpoint-v1.ts @@ -0,0 +1,97 @@ +import { Express, Request, Response } from "express"; + +import { + Logger, + LoggerProvider, + LogLevelDesc, + Checks, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + handleRestEndpointException, + registerWebServiceEndpoint, +} from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorFabric } from "../plugin-ledger-connector-fabric"; +import OAS from "../../json/openapi.json"; + +export interface IGetChainInfoEndpointV1Options { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorFabric; +} + +export class GetChainInfoEndpointV1 implements IWebServiceEndpoint { + private readonly log: Logger; + + constructor(public readonly opts: IGetChainInfoEndpointV1Options) { + const fnTag = "GetChainInfoEndpointV1#constructor()"; + + Checks.truthy(opts, `${fnTag} options`); + Checks.truthy(opts.connector, `${fnTag} options.connector`); + + this.log = LoggerProvider.getOrCreate({ + label: "get-chain-info-endpoint-v1", + level: opts.logLevel || "INFO", + }); + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public getOasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-chain-info"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-chain-info" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = "GetChainInfoEndpointV1#handleRequest()"; + this.log.debug(`POST ${this.getPath()}`); + + try { + res.status(200).send(await this.opts.connector.getChainInfo(req.body)); + } catch (error) { + const errorMsg = `Crash while serving ${fnTag}`; + handleRestEndpointException({ errorMsg, log: this.log, error, res }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts index 6302c3ad858..97d45fd9169 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts @@ -35,6 +35,9 @@ import { Endorser, ICryptoKey, } from "fabric-common"; +// BlockDecoder is not exported in ts definition so we need to use legacy import. +const { BlockDecoder } = require("fabric-common"); +import fabricProtos from "fabric-protos"; import { ConsensusAlgorithmFamily, @@ -95,12 +98,15 @@ import { GetTransactionReceiptResponse, GatewayOptions, GetBlockRequestV1, - GetBlockResponseV1, WatchBlocksV1, WatchBlocksOptionsV1, RunDelegatedSignTransactionRequest, RunTransactionResponseType, WatchBlocksDelegatedSignOptionsV1, + GetBlockResponseTypeV1, + GetBlockResponseV1, + GetChainInfoRequestV1, + GetChainInfoResponseV1, } from "./generated/openapi/typescript-axios/index"; import { @@ -137,10 +143,19 @@ import { getTransactionReceiptByTxID, IGetTransactionReceiptByTxIDOptions, } from "./common/get-transaction-receipt-by-tx-id"; +import { + formatCactiFullBlockResponse, + formatCactiTransactionsBlockResponse, +} from "./get-block/cacti-block-formatters"; import { GetBlockEndpointV1 } from "./get-block/get-block-endpoint-v1"; +import { GetChainInfoEndpointV1 } from "./get-chain-info/get-chain-info-endpoint-v1"; import { querySystemChainCode } from "./common/query-system-chain-code"; import { isSshExecOk } from "./common/is-ssh-exec-ok"; -import { asBuffer, assertFabricFunctionIsAvailable } from "./common/utils"; +import { + asBuffer, + assertFabricFunctionIsAvailable, + fabricLongToNumber, +} from "./common/utils"; import { findAndReplaceFabricLoggingSpec } from "./common/find-and-replace-fabric-logging-spec"; import { deployContractGoSourceImplFabricV256 } from "./deploy-contract-go-source/deploy-contract-go-source-impl-fabric-v2-5-6"; @@ -836,6 +851,14 @@ export class PluginLedgerConnectorFabric endpoints.push(endpoint); } + { + const endpoint = new GetChainInfoEndpointV1({ + connector: this, + logLevel: this.opts.logLevel, + }); + endpoints.push(endpoint); + } + { const opts: IGetPrometheusExporterMetricsEndpointV1Options = { connector: this, @@ -1506,15 +1529,14 @@ export class PluginLedgerConnectorFabric ); const gateway = await this.createGatewayWithOptions(req.gatewayOptions); - const { channelName, skipDecode } = req; + const { channelName, type } = req; const connectionChannelName = req.connectionChannelName ?? channelName; const queryConfig = { gateway, connectionChannelName, - skipDecode, }; - let responseData: unknown; + let responseData: Buffer; if (req.query.blockNumber) { this.log.debug("getBlock by it's blockNumber:", req.query.blockNumber); responseData = await querySystemChainCode( @@ -1554,25 +1576,69 @@ export class PluginLedgerConnectorFabric ); } - this.log.debug("responseData:", responseData); if (!responseData) { const eMsg = `${fnTag} - expected string as GetBlockByTxID response from Fabric system chaincode but received a falsy value instead...`; throw new RuntimeError(eMsg); } - if (skipDecode) { - if (!(responseData instanceof Buffer)) { - const eMsg = `${fnTag} - expected Buffer as GetBlockByTxID response from Fabric system chaincode but received ${typeof responseData} instead...`; - throw new RuntimeError(eMsg); - } + if (type === GetBlockResponseTypeV1.Encoded) { const encodedBlockB64 = responseData.toString("base64"); return { encodedBlock: encodedBlockB64, }; } + const decodedBlock = BlockDecoder.decode(responseData); + switch (type) { + case GetBlockResponseTypeV1.CactiTransactions: + return formatCactiTransactionsBlockResponse(decodedBlock); + case GetBlockResponseTypeV1.CactiFullBlock: + return formatCactiFullBlockResponse(decodedBlock); + case GetBlockResponseTypeV1.Full: + default: + return { + decodedBlock, + }; + } + } + + /** + * Get fabric block from a channel, using one of selectors. + * + * @param req input parameters + * @returns Entire block object or encoded buffer (if req.skipDecode is true) + */ + public async getChainInfo( + req: GetChainInfoRequestV1, + ): Promise { + const { channelName } = req; + this.log.debug("getChainInfo() called, channelName:", channelName); + + const gateway = await this.createGatewayWithOptions(req.gatewayOptions); + const connectionChannelName = req.connectionChannelName ?? channelName; + const queryConfig = { + gateway, + connectionChannelName, + }; + + const responseData = await querySystemChainCode( + queryConfig, + "GetChainInfo", + channelName, + ); + + const decodedResponse = + fabricProtos.common.BlockchainInfo.decode(responseData); + if (!decodedResponse) { + throw new RuntimeError("Could not decode BlockchainInfo"); + } + return { - decodedBlock: responseData, + height: fabricLongToNumber(decodedResponse.height), + currentBlockHash: + "0x" + Buffer.from(decodedResponse.currentBlockHash).toString("hex"), + previousBlockHash: + "0x" + Buffer.from(decodedResponse.previousBlockHash).toString("hex"), }; } diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/watch-blocks/watch-blocks-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/watch-blocks/watch-blocks-v1-endpoint.ts index f48f4e62ca7..733214c9267 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/watch-blocks/watch-blocks-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/watch-blocks/watch-blocks-v1-endpoint.ts @@ -25,9 +25,12 @@ import { WatchBlocksResponseV1, WatchBlocksListenerTypeV1, WatchBlocksOptionsV1, - WatchBlocksCactusTransactionsEventV1, WatchBlocksDelegatedSignOptionsV1, } from "../generated/openapi/typescript-axios"; +import { + formatCactiFullBlockResponse, + formatCactiTransactionsBlockResponse, +} from "../get-block/cacti-block-formatters"; const { newFilteredBlockEvent, @@ -82,66 +85,53 @@ export class WatchBlocksV1Endpoint { } /** - * Callback executed when receiving block with custom cactus type "cactus:transactions" + * Callback executed when receiving block with custom cacti type "cacti:full-block" * Sends WatchBlocksV1.Next with new block to the client. * * @param blockEvent full block * * @returns Nothing. */ - private monitorCactusTransactionsCallback(blockEvent: BlockEvent) { + private monitorCactiFullBlockCallback(blockEvent: BlockEvent) { const { socket, log } = this; const clientId = socket.id; log.debug( - `CactusTransactions BlockEvent received: #${blockEvent.blockNumber.toString()}, client: ${clientId}`, + `CactiFullBlock BlockEvent received: #${blockEvent.blockNumber.toString()}, client: ${clientId}`, ); if (!("data" in blockEvent.blockData)) { - log.error("Wrong blockEvent type received - should not happen!"); - return; + throw new Error("Wrong blockEvent type received - should not happen!"); } - const blockData = blockEvent.blockData.data?.data as any; - if (!blockData) { - log.debug("Block data empty - ignore..."); - return; - } + socket.emit( + WatchBlocksV1.Next, + formatCactiFullBlockResponse(blockEvent.blockData), + ); + } - const transactions: WatchBlocksCactusTransactionsEventV1[] = []; - for (const data of blockData) { - try { - const payload = data.payload; - const transaction = payload.data; - const actionPayload = transaction.actions[0].payload; - const proposalPayload = actionPayload.chaincode_proposal_payload; - const invocationSpec = proposalPayload.input; - - // Decode args and function name - const rawArgs = invocationSpec.chaincode_spec.input.args as Buffer[]; - const decodedArgs = rawArgs.map((arg: Buffer) => arg.toString("utf8")); - const functionName = decodedArgs.shift() ?? ""; - - const chaincodeId = invocationSpec.chaincode_spec.chaincode_id.name; - const channelHeader = payload.header.channel_header; - const transactionId = channelHeader.tx_id; - - transactions.push({ - chaincodeId, - transactionId, - functionName, - functionArgs: decodedArgs, - }); - } catch (error) { - log.warn( - "Could not retrieve transaction from received block. Error:", - safeStringifyException(error), - ); - } + /** + * Callback executed when receiving block with custom cacti type "cacti:transactions" + * Sends WatchBlocksV1.Next with new block to the client. + * + * @param blockEvent full block + * + * @returns Nothing. + */ + private monitorCactiTransactionsCallback(blockEvent: BlockEvent) { + const { socket, log } = this; + const clientId = socket.id; + log.debug( + `CactiTransactions BlockEvent received: #${blockEvent.blockNumber.toString()}, client: ${clientId}`, + ); + + if (!("data" in blockEvent.blockData)) { + throw new Error("Wrong blockEvent type received - should not happen!"); } - socket.emit(WatchBlocksV1.Next, { - cactusTransactionsEvents: transactions, - }); + socket.emit( + WatchBlocksV1.Next, + formatCactiTransactionsBlockResponse(blockEvent.blockData), + ); } /** @@ -223,7 +213,7 @@ export class WatchBlocksV1Endpoint { * Get block listener callback and listener type it's expect. * Returns separate function object each time it's called (this is required y fabric node SDK). * - * @param type requested listener type (including custom Cactus ones). + * @param type requested listener type (including custom Cacti ones). * * @returns listener: BlockListener; * @returns listenerType: BlockType; @@ -247,11 +237,17 @@ export class WatchBlocksV1Endpoint { this.monitorPrivateCallback(blockEvent); listenerType = "private"; break; - case WatchBlocksListenerTypeV1.CactusTransactions: + case WatchBlocksListenerTypeV1.CactiTransactions: listener = async (blockEvent) => - this.monitorCactusTransactionsCallback(blockEvent); + this.monitorCactiTransactionsCallback(blockEvent); listenerType = "full"; break; + case WatchBlocksListenerTypeV1.CactiFullBlock: + listener = async (blockEvent) => + this.monitorCactiFullBlockCallback(blockEvent); + listenerType = "full"; + break; + default: // Will not compile if any type was not handled by above switch. const unknownType: never = type; diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/delegate-signing-methods.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/delegate-signing-methods.test.ts index c6964fccd1a..3b995767f2f 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/delegate-signing-methods.test.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/delegate-signing-methods.test.ts @@ -59,8 +59,8 @@ import { FabricApiClient, signProposal, WatchBlocksListenerTypeV1, - WatchBlocksCactusTransactionsResponseV1, FabricSigningCredential, + CactiBlockTransactionsResponseV1, } from "../../../../main/typescript/public-api"; import { Observable } from "rxjs"; @@ -296,11 +296,11 @@ describe("Delegated signing tests", () => { const committedTx = await apiClient.waitForTransactionCommit( txId, apiClient.watchBlocksDelegatedSignV1({ - type: WatchBlocksListenerTypeV1.CactusTransactions, + type: WatchBlocksListenerTypeV1.CactiTransactions, signerCertificate: adminIdentity.credentials.certificate, signerMspID: adminIdentity.mspId, channelName: ledgerChannelName, - }) as Observable, + }) as Observable, ); mockSignCallback.mockClear(); return committedTx; diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-delegated-sign-v1-endpoint.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-delegated-sign-v1-endpoint.test.ts index d45b2c2fa98..b0750830b00 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-delegated-sign-v1-endpoint.test.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-delegated-sign-v1-endpoint.test.ts @@ -386,22 +386,22 @@ describe("watchBlocksDelegatedSignV1 of fabric connector tests", () => { }); /** - * Check Cactus custom transactions summary block monitoring. + * Check Cacti custom transactions summary block monitoring. */ - test("Monitoring with type CactusTransactions returns transactions summary", async () => { + test("Monitoring with type CactiTransactions returns transactions summary", async () => { const monitorPromise = testWatchBlock( - "CactusTransactionsTest", - WatchBlocksListenerTypeV1.CactusTransactions, + "CactiTransactionsTest", + WatchBlocksListenerTypeV1.CactiTransactions, (event) => { expect(event).toBeTruthy(); - if (!("cactusTransactionsEvents" in event)) { + if (!("cactiTransactionsEvents" in event)) { throw new Error( `Unexpected response from the connector: ${JSON.stringify(event)}`, ); } - const eventData = event.cactusTransactionsEvents; + const eventData = event.cactiTransactionsEvents; expect(eventData.length).toBeGreaterThan(0); expect(eventData[0].chaincodeId).toBeTruthy(); expect(eventData[0].transactionId).toBeTruthy(); @@ -413,9 +413,66 @@ describe("watchBlocksDelegatedSignV1 of fabric connector tests", () => { await monitorPromise; }); + /** + * Check Cacti custom full block summary block monitoring. + */ + test("Monitoring with type CactiFullBlock returns block summary", async () => { + const monitorPromise = testWatchBlock( + "CactiFullBlockTest", + WatchBlocksListenerTypeV1.CactiFullBlock, + (event) => { + expect(event).toBeTruthy(); + + if (!("cactiFullEvents" in event)) { + throw new Error( + `Unexpected response from the connector: ${JSON.stringify(event)}`, + ); + } + + const cactiFullBlock = event.cactiFullEvents; + + // Check block fields + expect(cactiFullBlock).toBeTruthy(); + expect(cactiFullBlock.blockNumber).toBeDefined(); + expect(cactiFullBlock.blockHash).toBeTruthy(); + expect(cactiFullBlock.previousBlockHash).toBeTruthy(); + expect(cactiFullBlock.transactionCount).toBeDefined(); + + // Check transaction fields + for (const tx of cactiFullBlock.cactiTransactionsEvents) { + expect(tx.hash).toBeTruthy(); + expect(tx.channelId).toBeTruthy(); + expect(tx.timestamp).toBeTruthy(); + expect(tx.type).toBeTruthy(); + expect(tx.protocolVersion).not.toBeUndefined(); + expect(tx.epoch).not.toBeUndefined(); + + // Check transaction actions fields + for (const action of tx.actions) { + expect(action.functionName).toBeTruthy(); + expect(action.functionArgs).toBeTruthy(); + expect(action.functionArgs.length).toEqual(5); + expect(action.chaincodeId).toBeTruthy(); + expect(action.creator.mspid).toBeTruthy(); + expect(action.creator.cert).toBeTruthy(); + + // Check transaction action endorsement fields + for (const endorsement of action.endorsements) { + expect(endorsement.signature).toBeTruthy(); + expect(endorsement.signer.mspid).toBeTruthy(); + expect(endorsement.signer.cert).toBeTruthy(); + } + } + } + }, + ); + + await monitorPromise; + }); + test("Invalid WatchBlocksListenerTypeV1 value gets knocked down", async () => { const monitorPromise = testWatchBlock( - "CactusTransactionsTest", + "InvalidTypeTest", "Some_INVALID_WatchBlocksListenerTypeV1" as WatchBlocksListenerTypeV1, () => undefined, // will never reach this because it is meant to error out false, diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-v1-endpoint.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-v1-endpoint.test.ts index 1b85a6930b4..94e1b4c2ffe 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-v1-endpoint.test.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/fabric-watch-blocks-v1-endpoint.test.ts @@ -60,8 +60,8 @@ const ledgerChannelName = "mychannel"; const ledgerContractName = "basic"; // Log settings -const testLogLevel: LogLevelDesc = "TRACE"; // default: info -const sutLogLevel: LogLevelDesc = "TRACE"; // default: info +const testLogLevel: LogLevelDesc = "info"; // default: info +const sutLogLevel: LogLevelDesc = "info"; // default: info // Logger setup const log: Logger = LoggerProvider.getOrCreate({ @@ -384,22 +384,22 @@ describe("watchBlocksV1 of fabric connector tests", () => { }); /** - * Check Cactus custom transactions summary block monitoring. + * Check Cacti custom transactions summary block monitoring. */ - test("Monitoring with type CactusTransactions returns transactions summary", async () => { + test("Monitoring with type CactiTransactions returns transactions summary", async () => { const monitorPromise = testWatchBlock( - "CactusTransactionsTest", - WatchBlocksListenerTypeV1.CactusTransactions, + "CactiTransactionsTest", + WatchBlocksListenerTypeV1.CactiTransactions, (event) => { expect(event).toBeTruthy(); - if (!("cactusTransactionsEvents" in event)) { + if (!("cactiTransactionsEvents" in event)) { throw new Error( `Unexpected response from the connector: ${JSON.stringify(event)}`, ); } - const eventData = event.cactusTransactionsEvents; + const eventData = event.cactiTransactionsEvents; expect(eventData.length).toBeGreaterThan(0); expect(eventData[0].chaincodeId).toBeTruthy(); expect(eventData[0].transactionId).toBeTruthy(); @@ -411,9 +411,66 @@ describe("watchBlocksV1 of fabric connector tests", () => { await monitorPromise; }); + /** + * Check Cacti custom full block summary block monitoring. + */ + test("Monitoring with type CactiFullBlock returns block summary", async () => { + const monitorPromise = testWatchBlock( + "CactiFullBlockTest", + WatchBlocksListenerTypeV1.CactiFullBlock, + (event) => { + expect(event).toBeTruthy(); + + if (!("cactiFullEvents" in event)) { + throw new Error( + `Unexpected response from the connector: ${JSON.stringify(event)}`, + ); + } + + const cactiFullBlock = event.cactiFullEvents; + + // Check block fields + expect(cactiFullBlock).toBeTruthy(); + expect(cactiFullBlock.blockNumber).toBeDefined(); + expect(cactiFullBlock.blockHash).toBeTruthy(); + expect(cactiFullBlock.previousBlockHash).toBeTruthy(); + expect(cactiFullBlock.transactionCount).toBeDefined(); + + // Check transaction fields + for (const tx of cactiFullBlock.cactiTransactionsEvents) { + expect(tx.hash).toBeTruthy(); + expect(tx.channelId).toBeTruthy(); + expect(tx.timestamp).toBeTruthy(); + expect(tx.type).toBeTruthy(); + expect(tx.protocolVersion).not.toBeUndefined(); + expect(tx.epoch).not.toBeUndefined(); + + // Check transaction actions fields + for (const action of tx.actions) { + expect(action.functionName).toBeTruthy(); + expect(action.functionArgs).toBeTruthy(); + expect(action.functionArgs.length).toEqual(5); + expect(action.chaincodeId).toBeTruthy(); + expect(action.creator.mspid).toBeTruthy(); + expect(action.creator.cert).toBeTruthy(); + + // Check transaction action endorsement fields + for (const endorsement of action.endorsements) { + expect(endorsement.signature).toBeTruthy(); + expect(endorsement.signer.mspid).toBeTruthy(); + expect(endorsement.signer.cert).toBeTruthy(); + } + } + } + }, + ); + + await monitorPromise; + }); + test("Invalid WatchBlocksListenerTypeV1 value gets knocked down", async () => { const monitorPromise = testWatchBlock( - "CactusTransactionsTest", + "CactiInvalidTest", "Some_INVALID_WatchBlocksListenerTypeV1" as WatchBlocksListenerTypeV1, () => undefined, // will never reach this because it is meant to error out false, diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/get-block.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/get-block.test.ts deleted file mode 100644 index 37fbdbec515..00000000000 --- a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/get-block.test.ts +++ /dev/null @@ -1,431 +0,0 @@ -import "jest-extended"; -import http from "http"; -import { AddressInfo } from "net"; -import { v4 as uuidv4 } from "uuid"; -import bodyParser from "body-parser"; -import express from "express"; -import { DiscoveryOptions } from "fabric-network"; -// BlockDecoder is not exported in ts definition so we need to use legacy import. -const { BlockDecoder } = require("fabric-common"); - -import { - DEFAULT_FABRIC_2_AIO_IMAGE_NAME, - FABRIC_25_LTS_AIO_FABRIC_VERSION, - FABRIC_25_LTS_AIO_IMAGE_VERSION, - FabricTestLedgerV1, - pruneDockerAllIfGithubAction, -} from "@hyperledger/cactus-test-tooling"; - -import { - LogLevelDesc, - LoggerProvider, - Logger, - IListenOptions, - Servers, -} from "@hyperledger/cactus-common"; - -import { Configuration } from "@hyperledger/cactus-core-api"; - -import { PluginRegistry } from "@hyperledger/cactus-core"; - -import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; - -import { - PluginLedgerConnectorFabric, - DefaultEventHandlerStrategy, - DefaultApi as FabricApi, - GatewayOptions, - GetBlockRequestV1, - FabricContractInvocationType, - FabricSigningCredential, -} from "../../../../main/typescript/public-api"; - -/** - * Functional test of GetBlockEndpointV1 on connector-fabric (packages/cactus-plugin-ledger-connector-fabric) - * Assumes sample CC was already deployed on the test ledger. - */ - -////////////////////////////////// -// Constants -////////////////////////////////// - -// Ledger settings -const imageName = DEFAULT_FABRIC_2_AIO_IMAGE_NAME; -const imageVersion = FABRIC_25_LTS_AIO_IMAGE_VERSION; -const fabricEnvVersion = FABRIC_25_LTS_AIO_FABRIC_VERSION; -const fabricEnvCAVersion = "1.4.9"; -const ledgerChannelName = "mychannel"; -const ledgerContractName = "basic"; - -// Log settings -const testLogLevel: LogLevelDesc = "debug"; // default: info -const sutLogLevel: LogLevelDesc = "debug"; // default: info - -// Logger setup -const log: Logger = LoggerProvider.getOrCreate({ - label: "get-block.test", - level: testLogLevel, -}); - -/** - * Main test suite - */ -describe("Get Block endpoint tests", () => { - let ledger: FabricTestLedgerV1; - let gatewayOptions: GatewayOptions; - let fabricConnectorPlugin: PluginLedgerConnectorFabric; - let connectorServer: http.Server; - let apiClient: FabricApi; - - ////////////////////////////////// - // Environment Setup - ////////////////////////////////// - - beforeAll(async () => { - log.info("Prune Docker..."); - await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); - - // Start Ledger - log.info("Start FabricTestLedgerV1..."); - log.debug("Version:", fabricEnvVersion, "CA Version:", fabricEnvCAVersion); - ledger = new FabricTestLedgerV1({ - emitContainerLogs: false, - publishAllPorts: true, - logLevel: testLogLevel, - imageName, - imageVersion, - envVars: new Map([ - ["FABRIC_VERSION", fabricEnvVersion], - ["CA_VERSION", fabricEnvCAVersion], - ]), - }); - log.debug("Fabric image:", ledger.getContainerImageName()); - await ledger.start({ omitPull: false }); - - // Get connection profile - log.info("Get fabric connection profile for Org1..."); - const connectionProfile = await ledger.getConnectionProfileOrg1(); - expect(connectionProfile).toBeTruthy(); - - // Enroll admin and user - const enrollAdminOut = await ledger.enrollAdmin(); - const adminWallet = enrollAdminOut[1]; - const [userIdentity] = await ledger.enrollUser(adminWallet); - - // Create Keychain Plugin - const keychainId = uuidv4(); - const keychainEntryKey = "user2"; - const keychainPlugin = new PluginKeychainMemory({ - instanceId: uuidv4(), - keychainId, - logLevel: sutLogLevel, - backend: new Map([[keychainEntryKey, JSON.stringify(userIdentity)]]), - }); - - gatewayOptions = { - identity: keychainEntryKey, - wallet: { - keychain: { - keychainId, - keychainRef: keychainEntryKey, - }, - }, - }; - - // Create Connector Plugin - const discoveryOptions: DiscoveryOptions = { - enabled: true, - asLocalhost: true, - }; - fabricConnectorPlugin = new PluginLedgerConnectorFabric({ - instanceId: uuidv4(), - pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), - sshConfig: await ledger.getSshConfig(), - cliContainerEnv: {}, - peerBinary: "/fabric-samples/bin/peer", - logLevel: sutLogLevel, - connectionProfile, - discoveryOptions, - eventHandlerOptions: { - strategy: DefaultEventHandlerStrategy.NetworkScopeAnyfortx, - commitTimeout: 300, - }, - }); - - // Run http server - const expressApp = express(); - expressApp.use(bodyParser.json({ limit: "250mb" })); - connectorServer = http.createServer(expressApp); - const listenOptions: IListenOptions = { - hostname: "127.0.0.1", - port: 0, - server: connectorServer, - }; - const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - const apiHost = `http://${addressInfo.address}:${addressInfo.port}`; - - // Register services - await fabricConnectorPlugin.getOrCreateWebServices(); - await fabricConnectorPlugin.registerWebServices(expressApp); - - // Create ApiClient - const apiConfig = new Configuration({ basePath: apiHost }); - apiClient = new FabricApi(apiConfig); - }); - - afterAll(async () => { - log.info("FINISHING THE TESTS"); - - if (fabricConnectorPlugin) { - log.info("Close ApiClient connections..."); - fabricConnectorPlugin.shutdown(); - } - - if (connectorServer) { - log.info("Stop the HTTP server connector..."); - await new Promise((resolve) => - connectorServer.close(() => resolve()), - ); - } - - if (ledger) { - log.info("Stop the fabric ledger..."); - await ledger.stop(); - await ledger.destroy(); - } - - log.info("Prune Docker..."); - await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); - }); - - ////////////////////////////////// - // Helpers - ////////////////////////////////// - - /** - * Run get block endpoint using block number, do basic response checks. - * Can be reused throughout the tests. - * - * @param blockNumber string number of the block - * @param skipDecode true to return encoded, false to return decoded - * @returns block object / block buffer - */ - async function getBlockByNumber( - blockNumber = "0", - skipDecode = false, - ): Promise { - const getBlockReq = { - channelName: ledgerChannelName, - gatewayOptions, - query: { - blockNumber, - }, - skipDecode, - }; - - const getBlockResponse = await apiClient.getBlockV1(getBlockReq); - log.debug("getBlockResponse=%o", getBlockResponse); - - expect(getBlockResponse).toBeTruthy(); - expect(getBlockResponse.status).toEqual(200); - expect(getBlockResponse.data).toBeTruthy(); - - if (!skipDecode) { - // Decoded check - if (!("decodedBlock" in getBlockResponse.data)) { - throw new Error( - "Wrong response received - expected decoded, received encoded.", - ); - } - expect(getBlockResponse.data.decodedBlock).toBeTruthy(); - return getBlockResponse.data.decodedBlock; - } else { - // Encoded check - if (!("encodedBlock" in getBlockResponse.data)) { - throw new Error( - "Wrong response received - expected encoded, received decoded.", - ); - } - expect(getBlockResponse.data.encodedBlock).toBeTruthy(); - return getBlockResponse.data.encodedBlock; - } - } - - /** - * Create new asset on the ledger to trigger new transaction creation. - * - * @param assetName unique asset name to create - * @returns committed transaction id. - */ - async function sendTransactionOnFabric(assetName: string) { - const createAssetResponse = await apiClient.runTransactionV1({ - signingCredential: gatewayOptions.wallet - .keychain as FabricSigningCredential, - channelName: ledgerChannelName, - invocationType: FabricContractInvocationType.Send, - contractName: ledgerContractName, - methodName: "CreateAsset", - params: [assetName, "green", "111", "someOwner", "299"], - }); - expect(createAssetResponse).toBeTruthy(); - expect(createAssetResponse.status).toEqual(200); - expect(createAssetResponse.data).toBeTruthy(); - const txId = createAssetResponse.data.transactionId; - expect(txId).toBeTruthy(); - - log.debug("Crated new transaction, txId:", txId); - return txId; - } - - ////////////////////////////////// - // Tests - ////////////////////////////////// - - /** - * GetBlock endpoint using block number - */ - test("Get first block by it's number - decoded.", async () => { - // Check decoded - const decodedFirstBlock = await getBlockByNumber("0", false); - log.debug("Received decodedFirstBlock:", decodedFirstBlock); - expect(decodedFirstBlock.header).toBeTruthy(); - expect(decodedFirstBlock.header.number.low).toBe(0); - expect(decodedFirstBlock.header.number.high).toBe(0); - expect(decodedFirstBlock.data).toBeTruthy(); - expect(decodedFirstBlock.metadata).toBeTruthy(); - }); - - test("Get first block by it's number - encoded.", async () => { - // Check decoded - const encodedFirstBlock = await getBlockByNumber("0", true); - const decodedFirstBlockBuffer = Buffer.from(encodedFirstBlock, "base64"); - const decodedFirstBlock = BlockDecoder.decode(decodedFirstBlockBuffer); - log.debug("Received decodedFirstBlock:", decodedFirstBlock); - expect(decodedFirstBlock.header).toBeTruthy(); - expect(decodedFirstBlock.header.number.low).toBe(0); - expect(decodedFirstBlock.header.number.high).toBe(0); - expect(decodedFirstBlock.data).toBeTruthy(); - expect(decodedFirstBlock.metadata).toBeTruthy(); - }); - - /** - * GetBlock endpoint using transactionId - */ - test("Get a block by transactionId it contains", async () => { - // Run some transaction - const txId = await sendTransactionOnFabric("getBlockTx"); - - // Get block using transactionId we've just sent - const getBlockByTxId = { - channelName: ledgerChannelName, - gatewayOptions, - query: { - transactionId: txId, - }, - }; - - const getBlockResponse = await apiClient.getBlockV1(getBlockByTxId); - if (!("decodedBlock" in getBlockResponse.data)) { - // narrow the type - throw new Error( - "Wrong response received - expected decoded, received encoded.", - ); - } - const { decodedBlock } = getBlockResponse.data; - expect(decodedBlock).toBeTruthy(); - expect(decodedBlock.header).toBeTruthy(); - expect(decodedBlock.data).toBeTruthy(); - expect(decodedBlock.metadata).toBeTruthy(); - }); - - /** - * GetBlock endpoint using block hash - */ - test("Get block by it's hash.", async () => { - // Run transaction to ensure more than one block is present - await sendTransactionOnFabric("txForNewBlock"); - - // Get second block by it's number - const decodedSecondBlock = await getBlockByNumber("1", false); - expect(decodedSecondBlock.header).toBeTruthy(); - const firstBlockHashJSON = decodedSecondBlock.header.previous_hash; - expect(firstBlockHashJSON).toBeTruthy(); - - // Get using default JSON hash representation - log.info("Get by JSON hash:", firstBlockHashJSON); - const getBlockByJsonHashReq = { - channelName: ledgerChannelName, - gatewayOptions, - query: { - blockHash: { - buffer: firstBlockHashJSON, - }, - }, - }; - const getBlockByJsonHashResponse = await apiClient.getBlockV1( - getBlockByJsonHashReq, - ); - if (!("decodedBlock" in getBlockByJsonHashResponse.data)) { - // narrow the type - throw new Error( - "Wrong response received - expected decoded, received encoded.", - ); - } - const { decodedBlock } = getBlockByJsonHashResponse.data; - expect(decodedBlock).toBeTruthy(); - expect(decodedBlock.header).toBeTruthy(); - expect(decodedBlock.header.number.low).toBe(0); - expect(decodedBlock.header.number.high).toBe(0); - expect(decodedBlock.data).toBeTruthy(); - expect(decodedBlock.metadata).toBeTruthy(); - - // Get using HEX encoded hash representation - const firstBlockHashHex = Buffer.from(firstBlockHashJSON).toString("hex"); - log.info("Get by HEX hash:", firstBlockHashHex); - const getBlockByHexHashReq: GetBlockRequestV1 = { - channelName: ledgerChannelName, - gatewayOptions, - query: { - blockHash: { - encoding: "hex", - buffer: firstBlockHashHex, - }, - }, - }; - const getBlockByHexHashResponse = - await apiClient.getBlockV1(getBlockByHexHashReq); - if (!("decodedBlock" in getBlockByHexHashResponse.data)) { - // narrow the type - throw new Error( - "Wrong response received - expected decoded, received encoded.", - ); - } - const decodedBlockHex = getBlockByHexHashResponse.data.decodedBlock; - expect(decodedBlockHex).toBeTruthy(); - expect(decodedBlockHex.header).toBeTruthy(); - expect(decodedBlockHex.header.number.low).toBe(0); - expect(decodedBlockHex.header.number.high).toBe(0); - expect(decodedBlockHex.data).toBeTruthy(); - expect(decodedBlockHex.metadata).toBeTruthy(); - }); - - /** - * Check error handling - */ - test("Reading block with invalid number returns an error.", async () => { - const getBlockReq = { - channelName: ledgerChannelName, - gatewayOptions, - query: { - blockNumber: "foo", // non existent block - }, - }; - - try { - await apiClient.getBlockV1(getBlockReq); - expect(true).toBe(false); // above call should always throw - } catch (err) { - expect(err).toBeTruthy(); - } - }); -}); diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/query-system-chain-methods.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/query-system-chain-methods.test.ts new file mode 100644 index 00000000000..2cb96d975c7 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/query-system-chain-methods.test.ts @@ -0,0 +1,539 @@ +import "jest-extended"; +import http from "http"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import bodyParser from "body-parser"; +import express from "express"; +import { DiscoveryOptions } from "fabric-network"; +// BlockDecoder is not exported in ts definition so we need to use legacy import. +const { BlockDecoder } = require("fabric-common"); + +import { + DEFAULT_FABRIC_2_AIO_IMAGE_NAME, + FABRIC_25_LTS_AIO_FABRIC_VERSION, + FABRIC_25_LTS_AIO_IMAGE_VERSION, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + LogLevelDesc, + LoggerProvider, + Logger, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +import { + PluginLedgerConnectorFabric, + DefaultEventHandlerStrategy, + DefaultApi as FabricApi, + GatewayOptions, + FabricContractInvocationType, + FabricSigningCredential, + GetBlockResponseTypeV1, + GetBlockRequestV1Query, + CactiBlockFullEventV1, +} from "../../../../main/typescript/public-api"; + +/** + * Functional test of GetBlockEndpointV1 on connector-fabric (packages/cactus-plugin-ledger-connector-fabric) + * Assumes sample CC was already deployed on the test ledger. + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +// Ledger settings +const imageName = DEFAULT_FABRIC_2_AIO_IMAGE_NAME; +const imageVersion = FABRIC_25_LTS_AIO_IMAGE_VERSION; +const fabricEnvVersion = FABRIC_25_LTS_AIO_FABRIC_VERSION; +const fabricEnvCAVersion = "1.4.9"; +const ledgerChannelName = "mychannel"; +const ledgerContractName = "basic"; + +// For development on local sawtooth network +// 1. leaveLedgerRunning = true, useRunningLedger = false to run ledger and leave it running after test finishes. +// 2. leaveLedgerRunning = true, useRunningLedger = true to use that ledger in future runs. +const useRunningLedger = false; +const leaveLedgerRunning = false; + +// Log settings +const testLogLevel: LogLevelDesc = "info"; // default: info +const sutLogLevel: LogLevelDesc = "info"; // default: info + +// Logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "query-system-chain-methods.test", + level: testLogLevel, +}); + +/** + * Main test suite + */ +describe("Query system chain methods and endpoints tests", () => { + let ledger: FabricTestLedgerV1; + let gatewayOptions: GatewayOptions; + let fabricConnectorPlugin: PluginLedgerConnectorFabric; + let connectorServer: http.Server; + let apiClient: FabricApi; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + + // Start Ledger + log.info("Start FabricTestLedgerV1..."); + log.debug("Version:", fabricEnvVersion, "CA Version:", fabricEnvCAVersion); + ledger = new FabricTestLedgerV1({ + emitContainerLogs: false, + publishAllPorts: true, + logLevel: testLogLevel, + imageName, + imageVersion, + envVars: new Map([ + ["FABRIC_VERSION", fabricEnvVersion], + ["CA_VERSION", fabricEnvCAVersion], + ]), + useRunningLedger, + }); + log.debug("Fabric image:", ledger.getContainerImageName()); + await ledger.start({ omitPull: false }); + + // Get connection profile + log.info("Get fabric connection profile for Org1..."); + const connectionProfile = await ledger.getConnectionProfileOrg1(); + expect(connectionProfile).toBeTruthy(); + + // Enroll admin and user + const userOrg = "org1"; + const enrollAdminOut = await ledger.enrollAdminV2({ + organization: userOrg, + }); + log.debug("Enrolled admin OK."); + const adminWallet = enrollAdminOut[1]; + const userId = `testUser_${(Math.random() + 1).toString(36).substring(2)}`; + const [userIdentity] = await ledger.enrollUserV2({ + enrollmentID: userId, + organization: userOrg, + wallet: adminWallet, + }); + log.debug(`Enrolled user '${userId}' OK.`); + + // Create Keychain Plugin + const keychainId = uuidv4(); + const keychainEntryKey = userId; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId, + logLevel: sutLogLevel, + backend: new Map([[keychainEntryKey, JSON.stringify(userIdentity)]]), + }); + + gatewayOptions = { + identity: keychainEntryKey, + wallet: { + keychain: { + keychainId, + keychainRef: keychainEntryKey, + }, + }, + }; + + // Create Connector Plugin + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + fabricConnectorPlugin = new PluginLedgerConnectorFabric({ + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + sshConfig: await ledger.getSshConfig(), + cliContainerEnv: {}, + peerBinary: "/fabric-samples/bin/peer", + logLevel: sutLogLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAnyfortx, + commitTimeout: 300, + }, + }); + + // Run http server + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + connectorServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server: connectorServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const apiHost = `http://${addressInfo.address}:${addressInfo.port}`; + + // Register services + await fabricConnectorPlugin.getOrCreateWebServices(); + await fabricConnectorPlugin.registerWebServices(expressApp); + + // Create ApiClient + const apiConfig = new Configuration({ basePath: apiHost }); + apiClient = new FabricApi(apiConfig); + }); + + afterAll(async () => { + log.info("FINISHING THE TESTS"); + + if (fabricConnectorPlugin) { + log.info("Close ApiClient connections..."); + fabricConnectorPlugin.shutdown(); + } + + if (connectorServer) { + log.info("Stop the HTTP server connector..."); + await new Promise((resolve) => + connectorServer.close(() => resolve()), + ); + } + + if (ledger && !leaveLedgerRunning) { + log.info("Stop the fabric ledger..."); + await ledger.stop(); + await ledger.destroy(); + } + + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + }); + + ////////////////////////////////// + // Helpers + ////////////////////////////////// + + /** + * Run get block endpoint using a query, do basic response checks. + * Can be reused throughout the tests. + * + * @param query how to find requested block + * @param type response type requested + * + * @returns block object / block buffer + */ + async function getBlock( + query: GetBlockRequestV1Query, + type: GetBlockResponseTypeV1 = GetBlockResponseTypeV1.Full, + ): Promise { + const getBlockReq = { + channelName: ledgerChannelName, + gatewayOptions, + query, + type, + }; + + const getBlockResponse = await apiClient.getBlockV1(getBlockReq); + log.debug( + "getBlockResponse = ", + getBlockResponse.status, + getBlockResponse.data, + ); + + expect(getBlockResponse).toBeTruthy(); + expect(getBlockResponse.status).toEqual(200); + expect(getBlockResponse.data).toBeTruthy(); + + switch (type) { + case GetBlockResponseTypeV1.Full: + if (!("decodedBlock" in getBlockResponse.data)) { + throw new Error( + `Wrong response received - expected decoded, got: ${getBlockResponse.data}`, + ); + } + expect(getBlockResponse.data.decodedBlock).toBeTruthy(); + return getBlockResponse.data.decodedBlock; + case GetBlockResponseTypeV1.Encoded: + if (!("encodedBlock" in getBlockResponse.data)) { + throw new Error( + `Wrong response received - expected encoded, got: ${getBlockResponse.data}`, + ); + } + expect(getBlockResponse.data.encodedBlock).toBeTruthy(); + return getBlockResponse.data.encodedBlock; + case GetBlockResponseTypeV1.CactiTransactions: + if (!("cactiTransactionsEvents" in getBlockResponse.data)) { + throw new Error( + `Wrong response received - expected CactiTransactions, got: ${getBlockResponse.data}`, + ); + } + expect(getBlockResponse.data.cactiTransactionsEvents).toBeTruthy(); + return getBlockResponse.data.cactiTransactionsEvents; + case GetBlockResponseTypeV1.CactiFullBlock: + if (!("cactiFullEvents" in getBlockResponse.data)) { + throw new Error( + `Wrong response received - expected CactiFullBlock, got: ${getBlockResponse.data}`, + ); + } + expect(getBlockResponse.data.cactiFullEvents).toBeTruthy(); + return getBlockResponse.data.cactiFullEvents; + default: + // Will not compile if any type was not handled by above switch. + const unknownType: never = type; + const validTypes = Object.keys(GetBlockResponseTypeV1).join(";"); + const errorMessage = `Unknown get block response type '${unknownType}'. Accepted types for GetBlockResponseTypeV1 are: [${validTypes}]`; + throw new Error(errorMessage); + } + } + + /** + * Create new asset on the ledger to trigger new transaction creation. + * + * @param assetName unique asset name to create + * @returns committed transaction id. + */ + async function sendTransactionOnFabric(assetName: string) { + const createAssetResponse = await apiClient.runTransactionV1({ + signingCredential: gatewayOptions.wallet + .keychain as FabricSigningCredential, + channelName: ledgerChannelName, + invocationType: FabricContractInvocationType.Send, + contractName: ledgerContractName, + methodName: "CreateAsset", + params: [assetName, "green", "111", "someOwner", "299"], + }); + expect(createAssetResponse).toBeTruthy(); + expect(createAssetResponse.status).toEqual(200); + expect(createAssetResponse.data).toBeTruthy(); + const txId = createAssetResponse.data.transactionId; + expect(txId).toBeTruthy(); + + log.debug("Crated new transaction, txId:", txId); + return txId; + } + + ////////////////////////////////// + // GetBlockV1 Endpoint Tests + ////////////////////////////////// + + describe("GetBlockV1 endpoint tests", () => { + /** + * GetBlock endpoint using block number + */ + test("Get first block by it's number - decoded.", async () => { + // Check decoded + const decodedFirstBlock = await getBlock( + { blockNumber: "0" }, + GetBlockResponseTypeV1.Full, + ); + log.debug("Received decodedFirstBlock:", decodedFirstBlock); + expect(decodedFirstBlock.header).toBeTruthy(); + expect(decodedFirstBlock.header.number.low).toBe(0); + expect(decodedFirstBlock.header.number.high).toBe(0); + expect(decodedFirstBlock.data).toBeTruthy(); + expect(decodedFirstBlock.metadata).toBeTruthy(); + }); + + test("Get first block by it's number - encoded.", async () => { + // Check decoded + const encodedFirstBlock = await getBlock( + { blockNumber: "0" }, + GetBlockResponseTypeV1.Encoded, + ); + const decodedFirstBlockBuffer = Buffer.from(encodedFirstBlock, "base64"); + const decodedFirstBlock = BlockDecoder.decode(decodedFirstBlockBuffer); + log.debug("Received decodedFirstBlock:", decodedFirstBlock); + expect(decodedFirstBlock.header).toBeTruthy(); + expect(decodedFirstBlock.header.number.low).toBe(0); + expect(decodedFirstBlock.header.number.high).toBe(0); + expect(decodedFirstBlock.data).toBeTruthy(); + expect(decodedFirstBlock.metadata).toBeTruthy(); + }); + + /** + * GetBlock endpoint using transactionId + */ + test("Get a block by transactionId it contains", async () => { + // Run some transaction + const assetName = `getBlockTx_${(Math.random() + 1).toString(36).substring(2)}`; + const txId = await sendTransactionOnFabric(assetName); + + // Get block using transactionId we've just sent + const blockByTx = await getBlock( + { transactionId: txId }, + GetBlockResponseTypeV1.Full, + ); + expect(blockByTx).toBeTruthy(); + expect(blockByTx.header).toBeTruthy(); + expect(blockByTx.data).toBeTruthy(); + expect(blockByTx.metadata).toBeTruthy(); + }); + + test("Get a block by transactionId it contains - cacti transactions summary", async () => { + // Run some transaction + const assetName = `cactiTx_${(Math.random() + 1).toString(36).substring(2)}`; + const txId = await sendTransactionOnFabric(assetName); + + // Get block using transactionId we've just sent + const cactiTxList = await getBlock( + { transactionId: txId }, + GetBlockResponseTypeV1.CactiTransactions, + ); + expect(cactiTxList).toBeTruthy(); + expect(cactiTxList.length).toBeGreaterThanOrEqual(1); + const cactiTx = cactiTxList[0]; + expect(cactiTx).toBeTruthy(); + expect(cactiTx.chaincodeId).toBeTruthy(); + expect(cactiTx.transactionId).toBeTruthy(); + expect(cactiTx.functionName).toBeTruthy(); + expect(cactiTx.functionArgs).toBeTruthy(); + expect(cactiTx.functionArgs.length).toEqual(5); + }); + + test("Get a block by transactionId it contains - cacti full block summary", async () => { + // Run some transaction + const assetName = `cactiTx_${(Math.random() + 1).toString(36).substring(2)}`; + const txId = await sendTransactionOnFabric(assetName); + + // Get block using transactionId we've just sent + const cactiFullBlock = (await getBlock( + { transactionId: txId }, + GetBlockResponseTypeV1.CactiFullBlock, + )) as CactiBlockFullEventV1; + + // Check block fields + expect(cactiFullBlock).toBeTruthy(); + expect(cactiFullBlock.blockNumber).toBeDefined(); + expect(cactiFullBlock.blockHash).toBeTruthy(); + expect(cactiFullBlock.previousBlockHash).toBeTruthy(); + expect(cactiFullBlock.transactionCount).toBeGreaterThanOrEqual(1); + + // Check transaction fields + for (const tx of cactiFullBlock.cactiTransactionsEvents) { + expect(tx.hash).toBeTruthy(); + expect(tx.channelId).toBeTruthy(); + expect(tx.timestamp).toBeTruthy(); + expect(tx.type).toBeTruthy(); + expect(tx.protocolVersion).not.toBeUndefined(); + expect(tx.epoch).not.toBeUndefined(); + + // Check transaction actions fields + for (const action of tx.actions) { + expect(action.functionName).toBeTruthy(); + expect(action.functionArgs).toBeTruthy(); + expect(action.functionArgs.length).toEqual(5); + expect(action.chaincodeId).toBeTruthy(); + expect(action.creator.mspid).toBeTruthy(); + expect(action.creator.cert).toBeTruthy(); + + // Check transaction action endorsement fields + for (const endorsement of action.endorsements) { + expect(endorsement.signature).toBeTruthy(); + expect(endorsement.signer.mspid).toBeTruthy(); + expect(endorsement.signer.cert).toBeTruthy(); + } + } + } + }); + + /** + * GetBlock endpoint using block hash + */ + test("Get block by it's hash.", async () => { + // Run transaction to ensure more than one block is present + const assetName = `txForNewBlock_${(Math.random() + 1).toString(36).substring(2)}`; + await sendTransactionOnFabric(assetName); + + // Get second block by it's number + const decodedSecondBlock = await getBlock( + { blockNumber: "1" }, + GetBlockResponseTypeV1.Full, + ); + expect(decodedSecondBlock.header).toBeTruthy(); + const firstBlockHashJSON = decodedSecondBlock.header.previous_hash; + expect(firstBlockHashJSON).toBeTruthy(); + + // Get using default JSON hash representation + log.info("Get by JSON hash:", firstBlockHashJSON); + + const decodedFirstBlock = await getBlock( + { + blockHash: { + buffer: firstBlockHashJSON, + }, + }, + GetBlockResponseTypeV1.Full, + ); + expect(decodedFirstBlock).toBeTruthy(); + expect(decodedFirstBlock.header).toBeTruthy(); + expect(decodedFirstBlock.header.number.low).toBe(0); + expect(decodedFirstBlock.header.number.high).toBe(0); + expect(decodedFirstBlock.data).toBeTruthy(); + expect(decodedFirstBlock.metadata).toBeTruthy(); + + // Get using HEX encoded hash representation + const firstBlockHashHex = Buffer.from(firstBlockHashJSON).toString("hex"); + log.info("Get by HEX hash:", firstBlockHashHex); + + const decodedBlockHex = await getBlock( + { + blockHash: { + encoding: "hex", + buffer: firstBlockHashHex, + }, + }, + GetBlockResponseTypeV1.Full, + ); + expect(decodedBlockHex).toBeTruthy(); + expect(decodedBlockHex.header).toBeTruthy(); + expect(decodedBlockHex.header.number.low).toBe(0); + expect(decodedBlockHex.header.number.high).toBe(0); + expect(decodedBlockHex.data).toBeTruthy(); + expect(decodedBlockHex.metadata).toBeTruthy(); + }); + + /** + * Check error handling + */ + test("Reading block with invalid number returns an error.", async () => { + const getBlockReq = { + channelName: ledgerChannelName, + gatewayOptions, + query: { + blockNumber: "foo", // non existent block + }, + }; + + try { + await apiClient.getBlockV1(getBlockReq); + expect(true).toBe(false); // above call should always throw + } catch (err) { + expect(err).toBeTruthy(); + } + }); + }); + + ////////////////////////////////// + // GetChainInfoV1 Endpoint Tests + ////////////////////////////////// + + describe("GetChainInfoV1 endpoint tests", () => { + test("Get test ledger chain info.", async () => { + const chainInfoResponse = await apiClient.getChainInfoV1({ + channelName: ledgerChannelName, + gatewayOptions, + }); + + const chainInfo = chainInfoResponse.data; + expect(chainInfoResponse.status).toBe(200); + expect(chainInfo).toBeTruthy; + expect(chainInfo.height).toBeGreaterThanOrEqual(1); + expect(chainInfo.currentBlockHash).toBeTruthy; + expect(chainInfo.previousBlockHash).toBeTruthy; + }); + }); +}); diff --git a/packages/cactus-plugin-persistence-fabric/src/main/typescript/plugin-persistence-fabric.ts b/packages/cactus-plugin-persistence-fabric/src/main/typescript/plugin-persistence-fabric.ts index 7aae96952ed..1f81201d023 100644 --- a/packages/cactus-plugin-persistence-fabric/src/main/typescript/plugin-persistence-fabric.ts +++ b/packages/cactus-plugin-persistence-fabric/src/main/typescript/plugin-persistence-fabric.ts @@ -23,6 +23,7 @@ import { FabricApiClient, GetBlockResponseV1, RunTransactionResponseType, + GetBlockResponseTypeV1, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; import PostgresDatabaseClient from "./db-client/db-client"; @@ -416,7 +417,7 @@ export class PluginPersistenceFabric query: { blockNumber, }, - skipDecode: false, + type: GetBlockResponseTypeV1.Full, }); const tempBlockParse = block.data; diff --git a/yarn.lock b/yarn.lock index 06be8ed609f..48b70dcdc97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8699,6 +8699,7 @@ __metadata: http-status-codes: "npm:2.1.4" internal-ip: "npm:6.2.0" jsrsasign: "npm:11.0.0" + long: "npm:5.2.3" multer: "npm:1.4.5-lts.1" ngo: "npm:2.7.0" node-ssh: "npm:13.1.0" @@ -35655,6 +35656,13 @@ __metadata: languageName: node linkType: hard +"long@npm:5.2.3, long@npm:^5.0.0, long@npm:^5.2.3": + version: 5.2.3 + resolution: "long@npm:5.2.3" + checksum: 10/9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 + languageName: node + linkType: hard + "long@npm:^4.0.0": version: 4.0.0 resolution: "long@npm:4.0.0" @@ -35662,13 +35670,6 @@ __metadata: languageName: node linkType: hard -"long@npm:^5.0.0, long@npm:^5.2.3": - version: 5.2.3 - resolution: "long@npm:5.2.3" - checksum: 10/9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 - languageName: node - linkType: hard - "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0"