diff --git a/.cspell.json b/.cspell.json index 43cbe52804..20ac6f181e 100644 --- a/.cspell.json +++ b/.cspell.json @@ -22,6 +22,7 @@ "Corda", "Cordapp", "couchdb", + "data", "dclm", "DHTAPI", "DockerOde", @@ -92,7 +93,9 @@ "protobuf", "protoc", "protos", + "qscc", "RUSTC", + "Rwset", "sbjpubkey", "Secp", "shrn", @@ -109,6 +112,7 @@ "thream", "tlsca", "tlscacerts", + "txid", "txqueue", "Uisrs", "Unmarshal", 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 3197a0ed65..a796b94849 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 @@ -11,6 +11,45 @@ }, "components": { "schemas": { + "TransactReceiptTransactionEndorsement":{ + "type":"object", + "properties": { + "mspid":{ + "type":"string" + }, + "endorserID":{ + "type":"string" + }, + "signature":{ + "type":"string" + } + } + }, + "TransactReceiptTransactionCreator":{ + "type":"object", + "properties": { + "mspid":{ + "type":"string" + }, + "creatorID":{ + "type":"string" + } + } + }, + "TransactReceiptBlockMetaData":{ + "type":"object", + "properties": { + "mspid":{ + "type":"string" + }, + "blockCreatorID":{ + "type":"string" + }, + "signature":{ + "type":"string" + } + } + }, "VaultTransitKey" : { "type": "object", "nullable": false, @@ -422,7 +461,8 @@ "type": "object", "required": [ "functionOutput", - "success" + "success", + "transactionId" ], "properties": { "functionOutput": { @@ -432,9 +472,51 @@ "success": { "type": "boolean", "nullable": false + }, + "transactionId":{ + "type": "string", + "nullable": false } } }, + "GetTransactionReceiptResponse": { + "type": "object", + "properties": { + "blockNumber":{ + "type":"string" + }, + "channelID":{ + "type": "string" + }, + "transactionCreator":{ + "$ref": "#/components/schemas/TransactReceiptTransactionCreator" + }, + "transactionEndorsement":{ + "type":"array", + "items":{ + "$ref": "#/components/schemas/TransactReceiptTransactionEndorsement" + } + }, + "blockMetaData":{ + "$ref": "#/components/schemas/TransactReceiptBlockMetaData" + }, + "chainCodeName":{ + "type":"string" + }, + "chainCodeVersion":{ + "type":"string" + }, + "responseStatus":{ + "type":"string" + }, + "rwsetKey":{ + "type":"string" + }, + "rwsetWriteData":{ + "type": "string" + } + } + }, "DeploymentTargetOrganization": { "type": "object", "required": [ @@ -883,6 +965,45 @@ } } }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-transaction-receipt-by-txid": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-transaction-receipt-by-txid" + } + }, + "operationId": "getTransactionReceiptByTxIDV1", + "summary": "get a transaction receipt by tx id on a Fabric ledger.", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunTransactionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTransactionReceiptResponse" + } + } + } + }, + "404": { + "description": "" + } + } + } + }, "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/deploy-contract-go-source": { "post": { "operationId": "deployContractGoSourceV1", diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/get-transaction-receipt-by-tx-id.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/get-transaction-receipt-by-tx-id.ts new file mode 100644 index 0000000000..09a4025794 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/get-transaction-receipt-by-tx-id.ts @@ -0,0 +1,202 @@ +import { LoggerProvider, LogLevelDesc } from "@hyperledger/cactus-common"; +import { Gateway } from "fabric-network"; +import { + GetTransactionReceiptResponse, + TransactReceiptTransactionEndorsement, + TransactReceiptTransactionCreator, + TransactReceiptBlockMetaData, +} from "../generated/openapi/typescript-axios"; +import { common } from "fabric-protos"; +const { BlockDecoder } = require("fabric-common"); +export interface IGetTransactionReceiptByTxIDOptions { + readonly logLevel?: LogLevelDesc; + readonly gateway: Gateway; + readonly channelName: string; + readonly params: string[]; +} +export async function getTransactionReceiptByTxID( + req: IGetTransactionReceiptByTxIDOptions, +): Promise { + const fnTag = `getTransactionReceiptForLockContractByTxID`; + const log = LoggerProvider.getOrCreate({ + label: fnTag, + level: req.logLevel || "INFO", + }); + log.info(`${fnTag}, start getting fabric transact receipt`); + const { gateway } = req; + + const contractName = "qscc"; + const methodName = "GetBlockByTxID"; + if (req.params.length != 2) { + throw new Error(`${fnTag}, should have 2 params`); + } + const network = await gateway.getNetwork(req.channelName); + + const contract = network.getContract(contractName); + const out: Buffer = await contract.evaluateTransaction( + methodName, + ...req.params, + ); + const reqTxID = req.params[1]; + const block: common.Block = BlockDecoder.decode(out); + const blockJson = JSON.parse(JSON.stringify(block)); + + const transactReceipt: GetTransactionReceiptResponse = {}; + transactReceipt.blockNumber = blockJson.header.number; + const txIDs = []; + if (!block.data) { + throw new Error(`${fnTag} block.data is null`); + } + const blockData = block.data; + if (!blockData.data) { + throw new Error(`${fnTag} block.data.data is null`); + } + const blockDataArr = blockData.data; + for (let i = 0; i < blockDataArr.length; i++) { + const blockData = JSON.parse(JSON.stringify(blockDataArr[i])); + if (!blockData.payload) { + throw new Error(`${fnTag}, blockData.payload undefine`); + } + if (!blockData.payload.header) continue; + if (!blockData.payload.header.channel_header) continue; + + const payloadChannelHeader = blockData.payload.header.channel_header; + if (payloadChannelHeader.tx_id) { + txIDs.push(payloadChannelHeader.tx_id); + if (payloadChannelHeader.tx_id != reqTxID) continue; + transactReceipt.channelID = payloadChannelHeader.channel_id; + if (!blockData.payload.data) continue; + if (!blockData.payload.data.actions) continue; + const payloadDataActions = blockData.payload.data.actions; + + if (!payloadDataActions[0].header || !payloadDataActions[0].payload) + continue; + const actionsHeader = payloadDataActions[0].header; + const actionsPayload = payloadDataActions[0].payload; + const creator = actionsHeader.creator; + const creatorMspId = creator.mspid; + const creatorId = String.fromCharCode.apply(null, creator.id_bytes.data); + const transactReceiptCreator: TransactReceiptTransactionCreator = { + mspid: creatorMspId, + creatorID: creatorId, + }; + transactReceipt.transactionCreator = transactReceiptCreator; + if (actionsPayload.chaincode_proposal_payload == undefined) continue; + const chainCodeProposal = actionsPayload.chaincode_proposal_payload; + if ( + !chainCodeProposal.input || + !chainCodeProposal.input.chaincode_spec || + !chainCodeProposal.input.chaincode_spec.chaincode_id + ) + continue; + if ( + !actionsPayload.action || + !actionsPayload.action.proposal_response_payload || + !actionsPayload.action.endorsements + ) + continue; + const proposalResponsePayload = + actionsPayload.action.proposal_response_payload; + const actionEndorsements = actionsPayload.action.endorsements; + const endorsements = []; + for (let i = 0; i < actionEndorsements.length; i++) { + const endorser = actionEndorsements[i].endorser; + const mspId = endorser.mspid; + const idBytes = endorser.id_bytes; + const idBytesData = idBytes.data; + const endorserId = String.fromCharCode.apply(null, idBytesData); + const signatureData = actionEndorsements[i].signature.data; + const signature = String.fromCharCode.apply(null, signatureData); + const endorsement: TransactReceiptTransactionEndorsement = { + mspid: mspId, + endorserID: endorserId, + signature: signature, + }; + endorsements.push(endorsement); + } + transactReceipt.transactionEndorsement = endorsements; + if (!proposalResponsePayload.extension) continue; + + const responseExtension = proposalResponsePayload.extension; + if (!responseExtension.chaincode_id) continue; + const extensionChainCodeID = responseExtension.chaincode_id; + transactReceipt.chainCodeName = extensionChainCodeID; + transactReceipt.chainCodeName = extensionChainCodeID.name; + transactReceipt.chainCodeVersion = extensionChainCodeID.version; + if ( + !responseExtension.response || + !responseExtension.response.payload || + !responseExtension.response.status + ) + continue; + const responseStatus = responseExtension.response.status; + transactReceipt.responseStatus = responseStatus; + if ( + !responseExtension.results || + !responseExtension.results.ns_rwset || + responseExtension.results.ns_rwset.length < 2 + ) { + continue; + } + const extensionNsRwset = responseExtension.results.ns_rwset[1]; + if (!extensionNsRwset.rwset) continue; + + const rwset = extensionNsRwset.rwset; + if (!rwset.writes) continue; + const rwsetWrite = rwset.writes; + if (!rwsetWrite[0].key) continue; + const rwsetKey = rwsetWrite[0].key; + transactReceipt.rwsetKey = rwsetKey; + if (!rwsetWrite[0].value || !rwsetWrite[0].value.data) continue; + const rwSetWriteData = rwsetWrite[0].value.data; + // eslint-disable-next-line prefer-spread + const rwSetWriteDataStr = String.fromCharCode.apply( + String, + rwSetWriteData, + ); + transactReceipt.rwsetWriteData = rwSetWriteDataStr; + break; + } + } + if (!block.metadata) { + throw new Error(`${fnTag}, block.metadata undefined`); + } + if (!block.metadata.metadata) { + throw new Error(`${fnTag}, block.metadata.metadata undefined`); + } + + const metadata = JSON.parse(JSON.stringify(block.metadata.metadata[0])); + if (!metadata.signatures) { + throw new Error(`${fnTag}, metadata signature undefined`); + } + if (!metadata.signatures[0].signature_header) { + throw new Error( + `${fnTag}, metadata.signatures.signature_header is undefined`, + ); + } + const metadataSignatureCreator = + metadata.signatures[0].signature_header.creator; + const metadataMspId = metadataSignatureCreator.mspid; + if (!metadataSignatureCreator.id_bytes) { + throw new Error(`${fnTag}, metadataSignatureCreator.id_bytes`); + } + const metadataCreatorId = String.fromCharCode.apply( + null, + metadataSignatureCreator.id_bytes.data, + ); + if (!metadata.signatures[0].signature) { + throw new Error(`${fnTag}, metadata.signatures[0].signature undefined`); + } + const metedataSignature = String.fromCharCode.apply( + null, + metadata.signatures[0].signature.data, + ); + const transactionReceiptBlockMetadata: TransactReceiptBlockMetaData = { + mspid: metadataMspId, + blockCreatorID: metadataCreatorId, + signature: metedataSignature, + }; + transactReceipt.blockMetaData = transactionReceiptBlockMetadata; + + return transactReceipt; +} 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 221a5844be..22fea9cc69 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 @@ -688,6 +688,73 @@ export interface GatewayOptionsWallet { */ json?: string; } +/** + * + * @export + * @interface GetTransactionReceiptResponse + */ +export interface GetTransactionReceiptResponse { + /** + * + * @type {string} + * @memberof GetTransactionReceiptResponse + */ + blockNumber?: string; + /** + * + * @type {string} + * @memberof GetTransactionReceiptResponse + */ + channelID?: string; + /** + * + * @type {TransactReceiptTransactionCreator} + * @memberof GetTransactionReceiptResponse + */ + transactionCreator?: TransactReceiptTransactionCreator; + /** + * + * @type {Array} + * @memberof GetTransactionReceiptResponse + */ + transactionEndorsement?: Array; + /** + * + * @type {TransactReceiptBlockMetaData} + * @memberof GetTransactionReceiptResponse + */ + blockMetaData?: TransactReceiptBlockMetaData; + /** + * + * @type {string} + * @memberof GetTransactionReceiptResponse + */ + chainCodeName?: string; + /** + * + * @type {string} + * @memberof GetTransactionReceiptResponse + */ + chainCodeVersion?: string; + /** + * + * @type {string} + * @memberof GetTransactionReceiptResponse + */ + responseStatus?: string; + /** + * + * @type {string} + * @memberof GetTransactionReceiptResponse + */ + rwsetKey?: string; + /** + * + * @type {string} + * @memberof GetTransactionReceiptResponse + */ + rwsetWriteData?: string; +} /** * * @export @@ -786,6 +853,12 @@ export interface RunTransactionResponse { * @memberof RunTransactionResponse */ success: boolean; + /** + * + * @type {string} + * @memberof RunTransactionResponse + */ + transactionId: string; } /** * @@ -818,6 +891,75 @@ export interface SSHExecCommandResponse { */ signal: string | null; } +/** + * + * @export + * @interface TransactReceiptBlockMetaData + */ +export interface TransactReceiptBlockMetaData { + /** + * + * @type {string} + * @memberof TransactReceiptBlockMetaData + */ + mspid?: string; + /** + * + * @type {string} + * @memberof TransactReceiptBlockMetaData + */ + blockCreatorID?: string; + /** + * + * @type {string} + * @memberof TransactReceiptBlockMetaData + */ + signature?: string; +} +/** + * + * @export + * @interface TransactReceiptTransactionCreator + */ +export interface TransactReceiptTransactionCreator { + /** + * + * @type {string} + * @memberof TransactReceiptTransactionCreator + */ + mspid?: string; + /** + * + * @type {string} + * @memberof TransactReceiptTransactionCreator + */ + creatorID?: string; +} +/** + * + * @export + * @interface TransactReceiptTransactionEndorsement + */ +export interface TransactReceiptTransactionEndorsement { + /** + * + * @type {string} + * @memberof TransactReceiptTransactionEndorsement + */ + mspid?: string; + /** + * + * @type {string} + * @memberof TransactReceiptTransactionEndorsement + */ + endorserID?: string; + /** + * + * @type {string} + * @memberof TransactReceiptTransactionEndorsement + */ + signature?: string; +} /** * vault key details for signing fabric message with private key stored with transit engine. * @export @@ -942,6 +1084,42 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @summary get a transaction receipt by tx id on a Fabric ledger. + * @param {RunTransactionRequest} runTransactionRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTransactionReceiptByTxIDV1: async (runTransactionRequest: RunTransactionRequest, options: any = {}): Promise => { + // verify required parameter 'runTransactionRequest' is not null or undefined + assertParamExists('getTransactionReceiptByTxIDV1', 'runTransactionRequest', runTransactionRequest) + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-transaction-receipt-by-txid`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(runTransactionRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary Runs a transaction on a Fabric ledger. @@ -1020,6 +1198,17 @@ export const DefaultApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getPrometheusMetricsV1(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @summary get a transaction receipt by tx id on a Fabric ledger. + * @param {RunTransactionRequest} runTransactionRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTransactionReceiptByTxIDV1(runTransactionRequest: RunTransactionRequest, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTransactionReceiptByTxIDV1(runTransactionRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @summary Runs a transaction on a Fabric ledger. @@ -1070,6 +1259,16 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa getPrometheusMetricsV1(options?: any): AxiosPromise { return localVarFp.getPrometheusMetricsV1(options).then((request) => request(axios, basePath)); }, + /** + * + * @summary get a transaction receipt by tx id on a Fabric ledger. + * @param {RunTransactionRequest} runTransactionRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTransactionReceiptByTxIDV1(runTransactionRequest: RunTransactionRequest, options?: any): AxiosPromise { + return localVarFp.getTransactionReceiptByTxIDV1(runTransactionRequest, options).then((request) => request(axios, basePath)); + }, /** * * @summary Runs a transaction on a Fabric ledger. @@ -1125,6 +1324,18 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration).getPrometheusMetricsV1(options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary get a transaction receipt by tx id on a Fabric ledger. + * @param {RunTransactionRequest} runTransactionRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getTransactionReceiptByTxIDV1(runTransactionRequest: RunTransactionRequest, options?: any) { + return DefaultApiFp(this.configuration).getTransactionReceiptByTxIDV1(runTransactionRequest, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Runs a transaction on a Fabric ledger. diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-transaction-receipt/get-transaction-receipt-by-txid-endpoint-v1.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-transaction-receipt/get-transaction-receipt-by-txid-endpoint-v1.ts new file mode 100644 index 0000000000..f6315ad3bd --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/get-transaction-receipt/get-transaction-receipt-by-txid-endpoint-v1.ts @@ -0,0 +1,103 @@ +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 { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorFabric } from "../plugin-ledger-connector-fabric"; +import { RunTransactionRequest } from "../generated/openapi/typescript-axios"; +import OAS from "../../json/openapi.json"; + +export interface IRunTransactionEndpointV1Options { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorFabric; +} + +export class GetTransactionReceiptByTxIDEndpointV1 + implements IWebServiceEndpoint { + private readonly log: Logger; + + constructor(public readonly opts: IRunTransactionEndpointV1Options) { + const fnTag = "GetTransactionReceiptByTxIDEndpointV1#constructor()"; + + Checks.truthy(opts, `${fnTag} options`); + Checks.truthy(opts.connector, `${fnTag} options.connector`); + + this.log = LoggerProvider.getOrCreate({ + label: "get-transaction-receipt-by-TxID-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-transaction-receipt-by-txid"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/get-transaction-receipt-by-txid" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = "GetTransactionReceiptByTxIDEndpointV1#handleRequest()"; + this.log.debug(`POST ${this.getPath()}`); + + try { + const reqBody = req.body as RunTransactionRequest; + const resBody = await this.opts.connector.getTransactionReceiptByTxID( + reqBody, + ); + res.status(200); + res.json(resBody); + } catch (ex) { + this.log.error(`${fnTag} failed to serve request`, ex); + res.status(500); + res.statusMessage = ex.message; + res.json({ error: ex.stack }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-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 967031e5aa..8262281741 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 @@ -71,6 +71,7 @@ import { FabricSigningCredential, DefaultEventHandlerStrategy, FabricSigningCredentialType, + GetTransactionReceiptResponse, } from "./generated/openapi/typescript-axios/index"; import { @@ -101,6 +102,11 @@ import { CertDatastore, IIdentityData, } from "./identity/internal/cert-datastore"; +import { GetTransactionReceiptByTxIDEndpointV1 } from "./get-transaction-receipt/get-transaction-receipt-by-txid-endpoint-v1"; +import { + getTransactionReceiptByTxID, + IGetTransactionReceiptByTxIDOptions, +} from "./common/get-transaction-receipt-by-tx-id"; /** * Constant value holding the default $GOPATH in the Fabric CLI container as * observed on fabric deployments that are produced by the official examples @@ -814,6 +820,14 @@ export class PluginLedgerConnectorFabric const endpoint = new RunTransactionEndpointV1(opts); endpoints.push(endpoint); } + { + const opts: IRunTransactionEndpointV1Options = { + connector: this, + logLevel: this.opts.logLevel, + }; + const endpoint = new GetTransactionReceiptByTxIDEndpointV1(opts); + endpoints.push(endpoint); + } { const opts: IGetPrometheusExporterMetricsEndpointV1Options = { @@ -954,6 +968,7 @@ export class PluginLedgerConnectorFabric let out: Buffer; let success: boolean; + let transactionId = ""; switch (invocationType) { case FabricContractInvocationType.Call: { out = await contract.evaluateTransaction(fnName, ...params); @@ -992,6 +1007,7 @@ export class PluginLedgerConnectorFabric tx.setEndorsingPeers(endorsers); } out = await tx.submit(...params); + transactionId = tx.getTransactionId(); success = true; break; } @@ -1039,6 +1055,7 @@ export class PluginLedgerConnectorFabric const res: RunTransactionResponse = { functionOutput: outUtf8, success, + transactionId: transactionId, }; gateway.disconnect(); this.log.debug(`transact() response: %o`, res); @@ -1050,6 +1067,17 @@ export class PluginLedgerConnectorFabric throw new Error(`${fnTag} Unable to run transaction: ${ex.message}`); } } + public async getTransactionReceiptByTxID( + req: RunTransactionRequest, + ): Promise { + const gateway = await this.createGateway(req); + const options: IGetTransactionReceiptByTxIDOptions = { + channelName: req.channelName, + params: req.params, + gateway: gateway, + }; + return await getTransactionReceiptByTxID(options); + } /** * @param caId The key of the CA in the Fabric connection profile's diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts index b1394b814f..e87ca778b1 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts @@ -18,6 +18,7 @@ import { IListenOptions, LogLevelDesc, Servers, + LoggerProvider, } from "@hyperledger/cactus-common"; import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; @@ -55,6 +56,9 @@ test("BEFORE " + testCase, async (t: Test) => { test(testCase, async (t: Test) => { const logLevel: LogLevelDesc = "TRACE"; + const level = "INFO"; + const label = "fabric run transaction test"; + const log = LoggerProvider.getOrCreate({ level, label }); test.onFailure(async () => { await Containers.logDiagnostics({ logLevel }); @@ -187,7 +191,18 @@ test(testCase, async (t: Test) => { const res = await apiClient.runTransactionV1(req); t.ok(res); t.ok(res.data); + t.ok(res.data.transactionId); t.equal(res.status, 200); + const res2 = await apiClient.getTransactionReceiptByTxIDV1({ + signingCredential, + channelName, + contractName: "qscc", + invocationType: FabricContractInvocationType.Call, + methodName: "GetBlockByTxID", + params: [channelName, res.data.transactionId], + } as RunTransactionRequest); + t.ok(res2); + log.info(res2.data); } { @@ -222,12 +237,14 @@ test(testCase, async (t: Test) => { '{type="' + K_CACTUS_FABRIC_TOTAL_TX_COUNT + '"} 3'; + t.comment(promMetricsOutput); t.ok(res); t.ok(res.data); + t.comment(res.data); t.equal(res.status, 200); t.true( res.data.includes(promMetricsOutput), - "Total Transaction Count of 3 recorded as expected. RESULT OK", + "Total Transaction Count of 4 recorded as expected. RESULT OK", ); } diff --git a/packages/cactus-plugin-ledger-connector-fabric/tsconfig.json b/packages/cactus-plugin-ledger-connector-fabric/tsconfig.json index f180a13630..f808a210c3 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/tsconfig.json +++ b/packages/cactus-plugin-ledger-connector-fabric/tsconfig.json @@ -13,7 +13,7 @@ "src/**/*.json" ], "exclude": [ - "./src/test/typescript/fixtures/go/basic-asset-transfer/chaincode-typescript/**/*.ts" + "./src/test/typescript/fixtures/go/basic-asset-transfer/chaincode-typescript/**/*.ts", ], "references": [ {