From db9d01756fa6f2090ab13562b6480922d654df46 Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Mon, 12 Jul 2021 16:21:00 -0700 Subject: [PATCH] feat(connector-fabric): identity json signing credentials #1130 Introduces a new, optional (for now) parameter on the run tx endpoint's request object called gatewayOptions which is capable of including everything that one needs to instantiate a gateway object of the underlying Fabric Node SDK. This change makes it possible to not need the keychain plugin at all when someone does not need/want it. Fixes #1130 Depends on #1124 Signed-off-by: Peter Somogyvari --- .../src/main/json/openapi.json | 49 +++++++- .../main/typescript/common/create-gateway.ts | 92 ++++++++++++++ .../generated/openapi/typescript-axios/api.ts | 68 +++++++++++ .../plugin-ledger-connector-fabric.ts | 114 ++++++++++++------ .../run-transaction-endpoint-v1.test.ts | 33 +++++ 5 files changed, 315 insertions(+), 41 deletions(-) create mode 100644 packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts 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 4f92b325c5..3b058acea2 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 @@ -55,7 +55,9 @@ "ChainCodeLifeCycleCommandResponses": { "type": "object", "required": [ - "queryInstalledList", "approveForMyOrgList", "installList" + "queryInstalledList", + "approveForMyOrgList", + "installList" ], "properties": { "packaging": { @@ -184,6 +186,42 @@ } } }, + "GatewayOptions": { + "type": "object", + "required": ["identity", "wallet"], + "properties": { + "connectionProfile": { + "$ref": "#/components/schemas/ConnectionProfile" + }, + "discovery": { + "$ref": "#/components/schemas/GatewayDiscoveryOptions", + "nullable": false + }, + "eventHandlerOptions": { + "$ref": "#/components/schemas/GatewayEventHandlerOptions", + "nullable": false + }, + "identity": { + "type": "string" + }, + "wallet": { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "keychain": { + "$ref": "#/components/schemas/FabricSigningCredential" + }, + "json": { + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 65535 + } + } + } + } + }, "DefaultEventHandlerStrategy": { "type": "string", "enum": [ @@ -204,6 +242,10 @@ "type": "number", "nullable": false }, + "endorseTimeout": { + "type": "number", + "nullable": false + }, "strategy": { "description": "The name of the strategy to be used when looking up the TxEventHandlerFactory to pass in to the Fabric Gateway as the strategy property of the discovery options.", "$ref": "#/components/schemas/DefaultEventHandlerStrategy" @@ -293,6 +335,10 @@ "type": "object", "nullable": true }, + "gatewayOptions": { + "$ref": "#/components/schemas/GatewayOptions", + "nullable": false + }, "signingCredential": { "$ref": "#/components/schemas/FabricSigningCredential", "nullable": false @@ -329,7 +375,6 @@ "nullable": true } }, - "endorsingParties": { "type": "array", "nullable": false, diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts new file mode 100644 index 0000000000..22090011b5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts @@ -0,0 +1,92 @@ +import { DefaultEventHandlerOptions } from "fabric-network"; +import { DefaultEventHandlerStrategies, Wallets } from "fabric-network"; +import { Gateway } from "fabric-network"; +import { GatewayOptions as FabricGatewayOptions } from "fabric-network"; +import { Checks, LoggerProvider } from "@hyperledger/cactus-common"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { ConnectionProfile } from "../generated/openapi/typescript-axios/index"; +import { GatewayDiscoveryOptions } from "../generated/openapi/typescript-axios/index"; +import { GatewayEventHandlerOptions } from "../generated/openapi/typescript-axios/index"; +import { GatewayOptions } from "../generated/openapi/typescript-axios/index"; + +export interface ICreateGatewayContext { + readonly logLevel?: LogLevelDesc; + readonly pluginRegistry: PluginRegistry; + readonly defaultConnectionProfile: ConnectionProfile; + readonly defaultDiscoveryOptions: GatewayDiscoveryOptions; + readonly defaultEventHandlerOptions: GatewayEventHandlerOptions; + readonly gatewayOptions: GatewayOptions; +} + +export const E_CREATE_GATEWAY_WALLET = + "Invalid opts.gatewayOptions.wallet. Need json or keychain, none provided."; + +export async function createGateway( + ctx: ICreateGatewayContext, +): Promise { + const log = LoggerProvider.getOrCreate({ + label: "create-gateway", + level: ctx?.logLevel || "INFO", + }); + log.debug("Creating Fabric Node SDK Gateway object..."); + + Checks.truthy(ctx, "createGateway#ctx"); + Checks.truthy(ctx.gatewayOptions, "createGateway#ctx.gatewayOptions"); + + const { defaultConnectionProfile } = ctx; + const cp = ctx.gatewayOptions.connectionProfile || defaultConnectionProfile; + + const wallet = await Wallets.newInMemoryWallet(); + + let identity; + if (ctx.gatewayOptions.wallet.json) { + log.debug("Parsing wallet from JSON representation..."); + identity = JSON.parse(ctx.gatewayOptions.wallet.json); + } else if (ctx.gatewayOptions.wallet.keychain) { + log.debug("Fetching wallet from JSON keychain..."); + const keychain = ctx.pluginRegistry.findOneByKeychainId( + ctx.gatewayOptions.wallet.keychain.keychainId, + ); + identity = await keychain.get( + ctx.gatewayOptions.wallet.keychain.keychainRef, + ); + } else { + throw new Error(E_CREATE_GATEWAY_WALLET); + } + + await wallet.put(ctx.gatewayOptions.identity, identity); + log.debug(`Imported identity ${ctx.gatewayOptions.identity} to wallet OK`); + + const eventHandlerOptions: DefaultEventHandlerOptions = { + commitTimeout: ctx.gatewayOptions.eventHandlerOptions?.commitTimeout || 300, + endorseTimeout: + ctx.gatewayOptions.eventHandlerOptions?.endorseTimeout || 300, + }; + + const strategy = + ctx.gatewayOptions.eventHandlerOptions?.strategy || + ctx.defaultEventHandlerOptions.strategy; + + if (strategy) { + eventHandlerOptions.strategy = DefaultEventHandlerStrategies[strategy]; + } + + log.debug(`Gateway EventHandlerOptions: `, eventHandlerOptions); + + const gatewayOptions: FabricGatewayOptions = { + discovery: ctx.gatewayOptions.discovery || ctx.defaultDiscoveryOptions, + eventHandlerOptions, + identity: ctx.gatewayOptions.identity, + wallet, + }; + + log.debug("Instantiating and connecting gateway..."); + + const gateway = new Gateway(); + await gateway.connect(cp, gatewayOptions); + + log.debug("Connection established by gateway OK"); + + return gateway; +} 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 87a42c5b00..7572dcd8c7 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 @@ -592,6 +592,12 @@ export interface GatewayEventHandlerOptions { * @memberof GatewayEventHandlerOptions */ commitTimeout?: number; + /** + * + * @type {number} + * @memberof GatewayEventHandlerOptions + */ + endorseTimeout?: number; /** * * @type {DefaultEventHandlerStrategy} @@ -599,6 +605,62 @@ export interface GatewayEventHandlerOptions { */ strategy: DefaultEventHandlerStrategy; } +/** + * + * @export + * @interface GatewayOptions + */ +export interface GatewayOptions { + /** + * + * @type {ConnectionProfile} + * @memberof GatewayOptions + */ + connectionProfile?: ConnectionProfile; + /** + * + * @type {GatewayDiscoveryOptions} + * @memberof GatewayOptions + */ + discovery?: GatewayDiscoveryOptions; + /** + * + * @type {GatewayEventHandlerOptions} + * @memberof GatewayOptions + */ + eventHandlerOptions?: GatewayEventHandlerOptions; + /** + * + * @type {string} + * @memberof GatewayOptions + */ + identity: string; + /** + * + * @type {GatewayOptionsWallet} + * @memberof GatewayOptions + */ + wallet: GatewayOptionsWallet; +} +/** + * + * @export + * @interface GatewayOptionsWallet + */ +export interface GatewayOptionsWallet { + /** + * + * @type {FabricSigningCredential} + * @memberof GatewayOptionsWallet + */ + keychain?: FabricSigningCredential; + /** + * + * @type {string} + * @memberof GatewayOptionsWallet + */ + json?: string; +} /** * * @export @@ -624,6 +686,12 @@ export interface RunTransactionRequest { * @memberof RunTransactionRequest */ transientData?: object | null; + /** + * + * @type {GatewayOptions} + * @memberof RunTransactionRequest + */ + gatewayOptions?: GatewayOptions; /** * * @type {FabricSigningCredential} 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 3675838157..8a1b4a64c4 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 @@ -69,6 +69,8 @@ import { RunTransactionResponse, ChainCodeProgrammingLanguage, ChainCodeLifeCycleCommandResponses, + FabricSigningCredential, + DefaultEventHandlerStrategy, } from "./generated/openapi/typescript-axios/index"; import { @@ -85,6 +87,7 @@ import { } from "./deploy-contract/deploy-contract-endpoint-v1"; import { sourceLangToRuntimeLang } from "./peer/source-lang-to-runtime-lang"; import FabricCAServices from "fabric-ca-client"; +import { createGateway } from "./common/create-gateway"; /** * Constant value holding the default $GOPATH in the Fabric CLI container as @@ -802,25 +805,35 @@ export class PluginLedgerConnectorFabric return endpoints; } - public async transact( - req: RunTransactionRequest, - ): Promise { - const fnTag = `${this.className}#transact()`; + protected async createGateway(req: RunTransactionRequest): Promise { + if (req.gatewayOptions) { + return createGateway({ + logLevel: this.opts.logLevel, + pluginRegistry: this.opts.pluginRegistry, + defaultConnectionProfile: this.opts.connectionProfile, + defaultDiscoveryOptions: this.opts.discoveryOptions || { + enabled: true, + asLocalhost: true, + }, + defaultEventHandlerOptions: this.opts.eventHandlerOptions || { + endorseTimeout: 300, + commitTimeout: 300, + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + }, + gatewayOptions: req.gatewayOptions, + }); + } else { + return this.createGatewayLegacy(req.signingCredential); + } + } + protected async createGatewayLegacy( + signingCredential: FabricSigningCredential, + ): Promise { const { connectionProfile, eventHandlerOptions: eho } = this.opts; - const { - signingCredential, - channelName, - contractName, - invocationType, - methodName: fnName, - params, - transientData, - endorsingParties, - } = req; - const gateway = new Gateway(); const wallet = await Wallets.newInMemoryWallet(); + const keychain = this.opts.pluginRegistry.findOneByKeychainId( signingCredential.keychainId, ); @@ -838,34 +851,57 @@ export class PluginLedgerConnectorFabric ); const identity = JSON.parse(fabricX509IdentityJson); - try { - await wallet.put(signingCredential.keychainRef, identity); - this.log.debug("transact() imported identity to in-memory wallet OK"); + await wallet.put(signingCredential.keychainRef, identity); + this.log.debug("transact() imported identity to in-memory wallet OK"); - const eventHandlerOptions: DefaultEventHandlerOptions = { - commitTimeout: this.opts.eventHandlerOptions?.commitTimeout || 300, - endorseTimeout: 300, - }; - if (eho?.strategy) { - eventHandlerOptions.strategy = - DefaultEventHandlerStrategies[eho.strategy]; - } + const eventHandlerOptions: DefaultEventHandlerOptions = { + commitTimeout: this.opts.eventHandlerOptions?.commitTimeout || 300, + endorseTimeout: 300, + }; + if (eho?.strategy) { + eventHandlerOptions.strategy = + DefaultEventHandlerStrategies[eho.strategy]; + } - const gatewayOptions: GatewayOptions = { - discovery: this.opts.discoveryOptions, - eventHandlerOptions, - identity: signingCredential.keychainRef, - wallet, - }; + const gatewayOptions: GatewayOptions = { + discovery: this.opts.discoveryOptions, + eventHandlerOptions, + identity: signingCredential.keychainRef, + wallet, + }; - this.log.debug(`discovery=%o`, gatewayOptions.discovery); - this.log.debug(`eventHandlerOptions=%o`, eventHandlerOptions); - await gateway.connect( - connectionProfile as ConnectionProfile, - gatewayOptions, - ); - this.log.debug("transact() gateway connection established OK"); + this.log.debug(`discovery=%o`, gatewayOptions.discovery); + this.log.debug(`eventHandlerOptions=%o`, eventHandlerOptions); + + const gateway = new Gateway(); + + await gateway.connect( + connectionProfile as ConnectionProfile, + gatewayOptions, + ); + + this.log.debug("transact() gateway connection established OK"); + + return gateway; + } + + public async transact( + req: RunTransactionRequest, + ): Promise { + const fnTag = `${this.className}#transact()`; + + const { + channelName, + contractName, + invocationType, + methodName: fnName, + params, + transientData, + endorsingParties, + } = req; + try { + const gateway = await this.createGateway(req); const network = await gateway.getNetwork(channelName); // const channel = network.getChannel(); // const endorsers = channel.getEndorsers(); 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 d56fcfb640..47a66a6c22 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 @@ -230,5 +230,38 @@ test(testCase, async (t: Test) => { "Total Transaction Count of 3 recorded as expected. RESULT OK", ); } + + { + const res = await apiClient.runTransactionV1({ + gatewayOptions: { + connectionProfile, + discovery: discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + endorseTimeout: 300, + }, + identity: keychainEntryKey, + wallet: { + json: keychainEntryValue, + }, + }, + signingCredential, + channelName, + contractName, + invocationType: FabricContractInvocationType.Call, + methodName: "GetAllAssets", + params: [], + } as RunTransactionRequest); + t.ok(res); + t.ok(res.data); + t.equal(res.status, 200); + const assets = JSON.parse(res.data.functionOutput); + const asset277 = assets.find((c: { ID: string }) => c.ID === assetId); + t.ok(asset277, "Located Asset record by its ID OK"); + t.ok(asset277.owner, `Asset object has "owner" property OK`); + t.equal(asset277.owner, assetOwner, `Asset has expected owner OK`); + } + t.end(); });