diff --git a/packages/cactus-core-api/src/main/typescript/plugin/ledger-connector/i-socket-api-client.ts b/packages/cactus-core-api/src/main/typescript/plugin/ledger-connector/i-socket-api-client.ts index f4350de682..451a4fa0c6 100644 --- a/packages/cactus-core-api/src/main/typescript/plugin/ledger-connector/i-socket-api-client.ts +++ b/packages/cactus-core-api/src/main/typescript/plugin/ledger-connector/i-socket-api-client.ts @@ -8,17 +8,17 @@ import type { Observable } from "rxjs"; */ export interface ISocketApiClient { sendAsyncRequest?( - args: any, - method: Record, - baseConfig?: any, contract?: Record, + method?: Record, + args?: any, + baseConfig?: any, ): void; sendSyncRequest?( - args: any, - method: Record, - baseConfig?: any, contract?: Record, + method?: Record, + args?: any, + baseConfig?: any, ): Promise; watchBlocksV1?( diff --git a/packages/cactus-plugin-ledger-connector-iroha/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-iroha/src/main/json/openapi.json index 4de9d0ab50..14dbc3f1ba 100644 --- a/packages/cactus-plugin-ledger-connector-iroha/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-iroha/src/main/json/openapi.json @@ -983,16 +983,14 @@ } } }, - "IrohaSocketSessionEvent": { + "WatchBlocksV1": { "type": "string", "enum": [ - "org.hyperledger.cactus.api.async.iroha.SocketSession.Subscribe", - "org.hyperledger.cactus.api.async.iroha.SocketSession.Next", - "org.hyperledger.cactus.api.async.iroha.SocketSession.Unsubscribe", - "org.hyperledger.cactus.api.async.iroha.SocketSession.Error", - "org.hyperledger.cactus.api.async.iroha.SocketSession.Complete", - "org.hyperledger.cactus.api.async.iroha.SocketSession.SendAsyncRequest", - "org.hyperledger.cactus.api.async.iroha.SocketSession.SendSyncRequest" + "org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Subscribe", + "org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Next", + "org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Unsubscribe", + "org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Error", + "org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Complete" ], "x-enum-varnames": [ "Subscribe", @@ -1004,6 +1002,17 @@ "SendSyncRequest" ] }, + "IrohaSocketIOTransactV1": { + "type": "string", + "enum": [ + "org.hyperledger.cactus.api.async.iroha.IrohaSocketIOTransactV1.SendAsyncRequest", + "org.hyperledger.cactus.api.async.iroha.IrohaSocketIOTransactV1.SendSyncRequest" + ], + "x-enum-varnames": [ + "SendAsyncRequest", + "SendSyncRequest" + ] + }, "IrohaBlockResponse": { "type": "object", "required": [ diff --git a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/api-client/iroha-api-client.ts b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/api-client/iroha-api-client.ts index a195a9b886..a3db61bc94 100644 --- a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/api-client/iroha-api-client.ts +++ b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/api-client/iroha-api-client.ts @@ -6,17 +6,37 @@ import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common"; import { Constants, ISocketApiClient } from "@hyperledger/cactus-core-api"; import { DefaultApi, - IrohaSocketSessionEvent, + WatchBlocksV1, + IrohaSocketIOTransactV1, IrohaBlockProgress, IrohaBaseConfig, } from "../generated/openapi/typescript-axios"; -import { Configuration } from "../generated/openapi/typescript-axios/configuration"; +import { + Configuration, + ConfigurationParameters, +} from "../generated/openapi/typescript-axios/configuration"; import { RuntimeError } from "run-time-error"; +export interface IrohaApiClientParameters extends ConfigurationParameters { + logLevel?: LogLevelDesc; + wsApiHost?: string; + wsApiPath?: string; + timeoutLimit?: number; +} + export class IrohaApiClientOptions extends Configuration { readonly logLevel?: LogLevelDesc; readonly wsApiHost?: string; readonly wsApiPath?: string; + readonly timeoutLimit?: number; + + constructor(param: IrohaApiClientParameters = {}) { + super(param); + this.logLevel = param.logLevel; + this.wsApiHost = param.wsApiHost; + this.wsApiPath = param.wsApiPath; + this.timeoutLimit = param.timeoutLimit; + } } export class IrohaApiClient @@ -45,6 +65,7 @@ export class IrohaApiClient this.log.debug(`wsApiHost=${this.wsApiHost}`); this.log.debug(`wsApiPath=${this.wsApiPath}`); this.log.debug(`basePath=${this.options.basePath}`); + this.log.debug(`timeoutLimit=${this.options.timeoutLimit}`); Checks.nonBlankString( this.wsApiHost, @@ -67,13 +88,13 @@ export class IrohaApiClient const socket: Socket = io(this.wsApiHost, { path: this.wsApiPath }); const subject = new ReplaySubject(0); this.log.debug(monitorOptions); - socket.on(IrohaSocketSessionEvent.Next, (data: IrohaBlockProgress) => { + socket.on(WatchBlocksV1.Next, (data: IrohaBlockProgress) => { subject.next(data); }); socket.on("connect", () => { this.log.debug("connected OK..."); - socket.emit(IrohaSocketSessionEvent.Subscribe, monitorOptions); + socket.emit(WatchBlocksV1.Subscribe, monitorOptions); }); socket.connect(); @@ -90,10 +111,16 @@ export class IrohaApiClient subject.error(err); }); + socket.on("error", (err: unknown) => { + this.log.error("Error: ", err); + socket.disconnect(); + subject.error(err); + }); + return subject.pipe( finalize(() => { this.log.info("FINALIZE - unsubscribing from the stream..."); - socket.emit(IrohaSocketSessionEvent.Unsubscribe); + socket.emit(WatchBlocksV1.Unsubscribe); socket.disconnect(); }), share(), @@ -105,11 +132,11 @@ export class IrohaApiClient * @param args - arguments. * @param method - function / method to be executed by validator. * @param baseConfig - baseConfig needed to properly connect to ledger - + */ public sendAsyncRequest( - args: any, method: Record, + args: any, baseConfig?: IrohaBaseConfig, ): void { this.log.debug(`inside: sendAsyncRequest()`); @@ -117,24 +144,19 @@ export class IrohaApiClient this.log.debug(`methodName=${method.methodName}`); this.log.debug(`args=${args}`); - if (baseConfig === undefined || baseConfig === {}) { + if (!baseConfig) { throw new RuntimeError("baseConfig object must exist and not be empty"); } - if ( - baseConfig.privKey === undefined || - baseConfig.creatorAccountId === undefined || - baseConfig.irohaHost === undefined || - baseConfig.irohaPort === undefined || - baseConfig.quorum === undefined || - baseConfig.timeoutLimit === undefined - ) { - throw new RuntimeError("Some fields in baseConfig are undefined"); - } - - if (method.methodName === undefined || method.methodName === "") { - throw new RuntimeError("methodName parameter must be specified"); - } + Checks.truthy(baseConfig.privKey, "privKey in baseConfig"); + Checks.truthy( + baseConfig.creatorAccountId, + "creatorAccountId in baseConfig", + ); + Checks.truthy(baseConfig.irohaHost, "irohaHost in baseConfig"); + Checks.truthy(baseConfig.irohaPort, "irohaPort in baseConfig"); + Checks.truthy(baseConfig.quorum, "quorum in baseConfig"); + Checks.nonBlankString(method.methodName, "methodName"); const socket: Socket = io(this.wsApiHost, { path: this.wsApiPath }); const asyncRequestData = { @@ -146,7 +168,15 @@ export class IrohaApiClient this.log.debug("requestData:", asyncRequestData); try { - socket.emit(IrohaSocketSessionEvent.SendAsyncRequest, asyncRequestData); + socket.emit(IrohaSocketIOTransactV1.SendAsyncRequest, asyncRequestData); + + // Connector should disconnect us after receiving this request. + // If he doesn't, disconnect after specified amount of time. + setTimeout(() => { + if (socket.connected) { + socket.disconnect(); + } + }, this.options.timeoutLimit ?? 10 * 1000); } catch (err) { this.log.error("Exception in: sendAsyncRequest(): ", err); throw err; @@ -161,8 +191,8 @@ export class IrohaApiClient * @returns Promise that will resolve with response from the ledger, or reject when error occurred. */ public sendSyncRequest( - args: any, method: Record, + args: any, baseConfig?: IrohaBaseConfig, ): Promise { this.log.debug(`inside: sendSyncRequest()`); @@ -170,24 +200,19 @@ export class IrohaApiClient this.log.debug(`method=${method}`); this.log.debug(`args=${args}`); - if (baseConfig === undefined || baseConfig === {}) { + if (!baseConfig) { throw new RuntimeError("baseConfig object must exist and not be empty"); } - if ( - baseConfig.privKey === undefined || - baseConfig.creatorAccountId === undefined || - baseConfig.irohaHost === undefined || - baseConfig.irohaPort === undefined || - baseConfig.quorum === undefined || - baseConfig.timeoutLimit === undefined - ) { - throw new RuntimeError("Some fields in baseConfig are undefined"); - } - - if (method.methodName === undefined || method.methodName === "") { - throw new RuntimeError("methodName parameter must be specified"); - } + Checks.truthy(baseConfig.privKey, "privKey in baseConfig"); + Checks.truthy( + baseConfig.creatorAccountId, + "creatorAccountId in baseConfig", + ); + Checks.truthy(baseConfig.irohaHost, "irohaHost in baseConfig"); + Checks.truthy(baseConfig.irohaPort, "irohaPort in baseConfig"); + Checks.truthy(baseConfig.quorum, "quorum in baseConfig"); + Checks.nonBlankString(method.methodName, "methodName"); const socket: Socket = io(this.wsApiHost, { path: this.wsApiPath }); @@ -207,14 +232,20 @@ export class IrohaApiClient reject(err); }); + socket.on("error", (err: unknown) => { + socket.disconnect(); + reject(err); + }); + socket.on("response", (result: any) => { - this.log.debug("#[recv]response, res:", result); responseFlag = true; + this.log.debug("#[recv]response, res:", result); const resultObj = { status: result.status, data: result.txHash, }; this.log.debug("resultObj =", resultObj); + socket.disconnect(); resolve(resultObj); }); @@ -227,7 +258,7 @@ export class IrohaApiClient this.log.debug("requestData:", syncRequestData); try { - socket.emit(IrohaSocketSessionEvent.SendSyncRequest, syncRequestData); + socket.emit(IrohaSocketIOTransactV1.SendSyncRequest, syncRequestData); } catch (err) { this.log.error("Exception in: sendAsyncRequest(): ", err); throw err; @@ -235,9 +266,10 @@ export class IrohaApiClient setTimeout(() => { if (responseFlag === false) { + socket.disconnect(); resolve({ status: 504 }); } - }, baseConfig.timeoutLimit); + }, this.options.timeoutLimit); } catch (err) { this.log.error("Exception in: sendSyncRequest(): ", err); reject(err); diff --git a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/generated/openapi/typescript-axios/api.ts index 29c8a07dbc..1bb259f5ad 100644 --- a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -950,14 +950,9 @@ export enum IrohaQuery { * @enum {string} */ -export enum IrohaSocketSessionEvent { - Subscribe = 'org.hyperledger.cactus.api.async.iroha.SocketSession.Subscribe', - Next = 'org.hyperledger.cactus.api.async.iroha.SocketSession.Next', - Unsubscribe = 'org.hyperledger.cactus.api.async.iroha.SocketSession.Unsubscribe', - Error = 'org.hyperledger.cactus.api.async.iroha.SocketSession.Error', - Complete = 'org.hyperledger.cactus.api.async.iroha.SocketSession.Complete', - SendAsyncRequest = 'org.hyperledger.cactus.api.async.iroha.SocketSession.SendAsyncRequest', - SendSyncRequest = 'org.hyperledger.cactus.api.async.iroha.SocketSession.SendSyncRequest' +export enum IrohaSocketIOTransactV1 { + SendAsyncRequest = 'org.hyperledger.cactus.api.async.iroha.IrohaSocketIOTransactV1.SendAsyncRequest', + SendSyncRequest = 'org.hyperledger.cactus.api.async.iroha.IrohaSocketIOTransactV1.SendSyncRequest' } /** @@ -1187,6 +1182,20 @@ export interface TransferAssetRequestParameters { */ amount: number; } +/** + * + * @export + * @enum {string} + */ + +export enum WatchBlocksV1 { + Subscribe = 'org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Subscribe', + Next = 'org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Next', + Unsubscribe = 'org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Unsubscribe', + Error = 'org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Error', + Complete = 'org.hyperledger.cactus.api.async.iroha.WatchBlocksV1.Complete' +} + /** * DefaultApi - axios parameter creator diff --git a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/iroha-transaction-wrapper.ts b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/iroha-transaction-wrapper.ts index 1222a81fdb..226bab7d57 100644 --- a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/iroha-transaction-wrapper.ts +++ b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/iroha-transaction-wrapper.ts @@ -1,10 +1,11 @@ -import { Logger } from "@hyperledger/cactus-common"; +import { Checks, Logger } from "@hyperledger/cactus-common"; import { LoggerProvider, LogLevelDesc, Http405NotAllowedError, } from "@hyperledger/cactus-common"; import { + IrohaBaseConfig, IrohaCommand, IrohaQuery, RunTransactionRequestV1, @@ -17,13 +18,13 @@ import * as grpc from "grpc"; import { GrantablePermission, GrantablePermissionMap, -} from "iroha-helpers-ts/lib/proto/primitive_pb"; +} from "iroha-helpers/lib/proto/primitive_pb"; -import { CommandService_v1Client as CommandService } from "iroha-helpers-ts/lib/proto/endpoint_grpc_pb"; -import { QueryService_v1Client as QueryService } from "iroha-helpers-ts/lib/proto/endpoint_grpc_pb"; +import { CommandService_v1Client as CommandService } from "iroha-helpers/lib/proto/endpoint_grpc_pb"; +import { QueryService_v1Client as QueryService } from "iroha-helpers/lib/proto/endpoint_grpc_pb"; -import commands from "iroha-helpers-ts/lib/commands/index"; -import queries from "iroha-helpers-ts/lib/queries"; +import commands from "iroha-helpers/lib/commands/index"; +import queries from "iroha-helpers/lib/queries"; export interface IIrohaTransactionWrapperOptions { logLevel?: LogLevelDesc; @@ -43,23 +44,20 @@ export class IrohaTransactionWrapper { this.log = LoggerProvider.getOrCreate({ level, label }); } - public async transact( - req: RunTransactionRequestV1, - ): Promise { - const { baseConfig } = req; - if ( - !baseConfig || - !baseConfig.privKey || - !baseConfig.creatorAccountId || - !baseConfig.irohaHost || - !baseConfig.irohaPort || - !baseConfig.quorum || - !baseConfig.timeoutLimit - ) { - this.log.debug( - "Certain field within the Iroha basic configuration is missing!", - ); - throw new RuntimeError("Some fields in baseConfig are undefined"); + /** + * Create instances of Iroha SDK CommandService and QueryService from input base config. + * + * @param baseConfig iroha configuration from request, must contain Iroha URL information. + * @returns {commandService, queryService} + */ + public static getIrohaServices( + baseConfig: IrohaBaseConfig, + ): { + commandService: CommandService; + queryService: QueryService; + } { + if (!baseConfig || !baseConfig.irohaHost || !baseConfig.irohaPort) { + throw new RuntimeError("Missing Iroha URL information."); } const irohaHostPort = `${baseConfig.irohaHost}:${baseConfig.irohaPort}`; @@ -69,12 +67,40 @@ export class IrohaTransactionWrapper { } else { grpcCredentials = grpc.credentials.createInsecure(); } + const commandService = new CommandService( irohaHostPort, //TODO:do something in the production environment grpcCredentials, ); const queryService = new QueryService(irohaHostPort, grpcCredentials); + + return { commandService, queryService }; + } + + public async transact( + req: RunTransactionRequestV1, + ): Promise { + const { baseConfig } = req; + Checks.truthy(baseConfig, "baseConfig"); + Checks.truthy(baseConfig.privKey, "privKey in baseConfig"); + Checks.truthy( + baseConfig.creatorAccountId, + "creatorAccountId in baseConfig", + ); + Checks.truthy(baseConfig.quorum, "quorum in baseConfig"); + Checks.truthy(baseConfig.timeoutLimit, "timeoutLimit in baseConfig"); + + if (!baseConfig.privKey || !baseConfig.timeoutLimit) { + // narrow the types + throw new Error("Should never happen - Checks should catch this first"); + } + + const { + commandService, + queryService, + } = IrohaTransactionWrapper.getIrohaServices(baseConfig); + const commandOptions = { privateKeys: baseConfig.privKey, //need an array of keys for command creatorAccountId: baseConfig.creatorAccountId, @@ -82,6 +108,7 @@ export class IrohaTransactionWrapper { commandService: commandService, timeoutLimit: baseConfig.timeoutLimit, }; + const queryOptions = { privateKey: baseConfig.privKey[0], //only need 1 key for query creatorAccountId: baseConfig.creatorAccountId as string, @@ -90,402 +117,644 @@ export class IrohaTransactionWrapper { }; switch (req.commandName) { - case IrohaCommand.CreateAccount: { + case IrohaCommand.AddAssetQuantity: { try { - const response = await commands.createAccount(commandOptions, { - accountName: req.params[0], - domainId: req.params[1], - publicKey: req.params[2], - }); + let params; + if (Array.isArray(req.params)) { + params = { + assetId: req.params[0], + amount: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.addAssetQuantity( + commandOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.SetAccountDetail: { + case IrohaCommand.AddPeer: { try { - const response = await commands.setAccountDetail(commandOptions, { - accountId: req.params[0], - key: req.params[1], - value: req.params[2], - }); + let params; + if (Array.isArray(req.params)) { + params = { + address: req.params[0], + peerKey: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.addPeer(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.CompareAndSetAccountDetail: { + case IrohaCommand.AddSignatory: { try { - const response = await commands.compareAndSetAccountDetail( - commandOptions, - { + let params; + if (Array.isArray(req.params)) { + params = { accountId: req.params[0], - key: req.params[1], - value: req.params[2], - oldValue: req.params[3], - }, - ); + publicKey: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.addSignatory(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.CreateAsset: { + case IrohaCommand.AppendRole: { try { - const response = await commands // (coolcoin#test; precision:3) - .createAsset(commandOptions, { - assetName: req.params[0], - domainId: req.params[1], - precision: req.params[2], - }); + let params; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + roleName: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.appendRole(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.CreateDomain: { + case IrohaCommand.CallEngine: { try { - const response = await commands.createDomain(commandOptions, { - domainId: req.params[0], - defaultRole: req.params[1], - }); + let params: any; + if (Array.isArray(req.params)) { + params = { + type: req.params[0], + caller: req.params[1], + callee: req.params[2], + input: req.params[3], + }; + } else { + params = req.params; + } + const response = await commands.callEngine(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.SetAccountQuorum: { + case IrohaCommand.CreateAccount: { try { - const response = await commands.setAccountQuorum(commandOptions, { - accountId: req.params[0], - quorum: req.params[1], - }); + let params; + if (Array.isArray(req.params)) { + params = { + accountName: req.params[0], + domainId: req.params[1], + publicKey: req.params[2], + }; + } else { + params = req.params; + } + const response = await commands.createAccount(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.AddAssetQuantity: { + case IrohaCommand.CreateAsset: { try { - const response = await commands.addAssetQuantity(commandOptions, { - assetId: req.params[0], - amount: req.params[1], - }); + let params; + if (Array.isArray(req.params)) { + params = { + assetName: req.params[0], + domainId: req.params[1], + precision: req.params[2], + }; + } else { + params = req.params; + } + const response = await commands.createAsset(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.SubtractAssetQuantity: { + case IrohaCommand.CreateDomain: { try { - const response = await commands.subtractAssetQuantity( - commandOptions, - { - assetId: req.params[0], - amount: req.params[1], - }, - ); + let params; + if (Array.isArray(req.params)) { + params = { + domainId: req.params[0], + defaultRole: req.params[1], + }; + params.domainId = req.params[0]; + params.defaultRole = req.params[1]; + } else { + params = req.params; + } + const response = await commands.createDomain(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.TransferAsset: { + case IrohaCommand.CreateRole: { try { - const response = await commands.transferAsset(commandOptions, { - srcAccountId: req.params[0], - destAccountId: req.params[1], - assetId: req.params[2], - description: req.params[3], - amount: req.params[4], - }); + let params; + if (Array.isArray(req.params)) { + params = { + roleName: req.params[0], + permissionsList: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.createRole(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetSignatories: { + case IrohaCommand.DetachRole: { try { - const queryRes = await queries.getSignatories(queryOptions, { - accountId: req.params[0], - }); - return { transactionReceipt: queryRes }; - } catch (err: any) { - throw new RuntimeError(err); + let params; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + roleName: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.detachRole(commandOptions, params); + return { transactionReceipt: response }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetAccount: { + case IrohaCommand.GrantPermission: { try { - const queryRes = await queries.getAccount(queryOptions, { - accountId: req.params[0], - }); - return { transactionReceipt: queryRes }; - } catch (err: any) { - throw new RuntimeError(err); + let params; + type permission = keyof GrantablePermissionMap; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + permission: GrantablePermission[req.params[1] as permission], + }; + } else { + params = req.params; + if ("permission" in params) { + params["permission"] = + GrantablePermission[params["permission"] as permission]; + } + } + const response = await commands.grantPermission( + commandOptions, + params, + ); + return { transactionReceipt: response }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetAccountDetail: { + case IrohaCommand.RemovePeer: { try { - const queryRes = await queries.getAccountDetail(queryOptions, { - accountId: req.params[0], - key: req.params[1], - writer: req.params[2], - pageSize: req.params[3], - paginationKey: req.params[4], - paginationWriter: req.params[5], - }); - return { transactionReceipt: queryRes }; - } catch (err: any) { - throw new RuntimeError(err); + let params: any; + if (Array.isArray(req.params)) { + params = { + publicKey: req.params[0], + }; + } else { + params = req.params; + } + const response = await commands.removePeer(commandOptions, params); + return { transactionReceipt: response }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetAssetInfo: { + case IrohaCommand.RemoveSignatory: { try { - const queryRes = await queries.getAssetInfo(queryOptions, { - assetId: req.params[0], - }); - return { transactionReceipt: queryRes }; - } catch (err: any) { - throw new RuntimeError(err); + let params; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + publicKey: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.removeSignatory( + commandOptions, + params, + ); + return { transactionReceipt: response }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetAccountAssets: { + case IrohaCommand.RevokePermission: { try { - const queryRes = await queries.getAccountAssets(queryOptions, { - accountId: req.params[0], - pageSize: req.params[1], - firstAssetId: req.params[2], - }); - return { transactionReceipt: queryRes }; - } catch (err: any) { - throw new RuntimeError(err); + let params; + type permission = keyof GrantablePermissionMap; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + permission: GrantablePermission[req.params[1] as permission], + }; + } else { + params = req.params; + if ("permission" in params) { + params["permission"] = + GrantablePermission[params["permission"] as permission]; + } + } + const response = await commands.revokePermission( + commandOptions, + params, + ); + return { transactionReceipt: response }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.AddSignatory: { + case IrohaCommand.SetAccountDetail: { try { - const response = await commands.addSignatory(commandOptions, { - accountId: req.params[0], - publicKey: req.params[1], - }); + let params; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + key: req.params[1], + value: req.params[2], + }; + } else { + params = req.params; + } + const response = await commands.setAccountDetail( + commandOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.RemoveSignatory: { + case IrohaCommand.SetAccountQuorum: { try { - const response = await commands.removeSignatory(commandOptions, { - accountId: req.params[0], - publicKey: req.params[1], - }); + let params; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + quorum: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.setAccountQuorum( + commandOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetRoles: { + case IrohaCommand.SubtractAssetQuantity: { try { - const response = await queries.getRoles(queryOptions); + let params; + if (Array.isArray(req.params)) { + params = { + assetId: req.params[0], + amount: req.params[1], + }; + } else { + params = req.params; + } + const response = await commands.subtractAssetQuantity( + commandOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.CreateRole: { + case IrohaCommand.TransferAsset: { try { - const response = await commands.createRole(commandOptions, { - roleName: req.params[0], - permissionsList: req.params[1], - }); + let params; + if (Array.isArray(req.params)) { + params = { + srcAccountId: req.params[0], + destAccountId: req.params[1], + assetId: req.params[2], + description: req.params[3], + amount: req.params[4], + }; + } else { + params = req.params; + } + const response = await commands.transferAsset(commandOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.AppendRole: { + case IrohaCommand.CompareAndSetAccountDetail: { try { - const response = await commands.appendRole(commandOptions, { - accountId: req.params[0], - roleName: req.params[1], - }); + let params: any; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + key: req.params[1], + value: req.params[2], + oldValue: req.params[3], + checkEmpty: req.params[4], + }; + } else { + params = req.params; + } + const response = await commands.compareAndSetAccountDetail( + commandOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.DetachRole: { + case IrohaCommand.SetSettingValue: { + throw new Http405NotAllowedError("SetSettingValue is not supported."); + } + case IrohaQuery.GetEngineReceipts: { try { - const response = await commands.detachRole(commandOptions, { - accountId: req.params[0], - roleName: req.params[1], - }); + let params; + if (Array.isArray(req.params)) { + params = { + txHash: req.params[0], + }; + } else { + params = req.params; + } + const response = await queries.getEngineReceipts( + queryOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetRolePermissions: { + case IrohaQuery.GetAccount: { try { - const response = await queries.getRolePermissions(queryOptions, { - roleId: req.params[0], - }); - return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + let params; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + }; + } else { + params = req.params; + } + const queryRes = await queries.getAccount(queryOptions, params); + return { transactionReceipt: queryRes }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.GrantPermission: { + case IrohaQuery.GetBlock: { try { - type permission = keyof GrantablePermissionMap; - const response = await commands.grantPermission(commandOptions, { - accountId: req.params[0], - permission: GrantablePermission[req.params[1] as permission], - }); + let params; + if (Array.isArray(req.params)) { + params = { + height: req.params[0], + }; + } else { + params = req.params; + } + const response = await queries.getBlock(queryOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.RevokePermission: { + case IrohaQuery.GetSignatories: { try { - type permission = keyof GrantablePermissionMap; - const response = await commands.revokePermission(commandOptions, { - accountId: req.params[0], - permission: GrantablePermission[req.params[1] as permission], - }); - return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + let params; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + }; + } else { + params = req.params; + } + const queryRes = await queries.getSignatories(queryOptions, params); + return { transactionReceipt: queryRes }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.SetSettingValue: { - throw new Http405NotAllowedError("SetSettingValue is not supported."); - } case IrohaQuery.GetTransactions: { try { - const response = await queries.getTransactions(queryOptions, { - txHashesList: req.params[0], - }); + let params; + if (Array.isArray(req.params)) { + params = { + txHashesList: req.params[0], + }; + } else { + params = req.params; + } + const response = await queries.getTransactions(queryOptions, params); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } case IrohaQuery.GetPendingTransactions: { try { - const response = await queries.getPendingTransactions(queryOptions, { - pageSize: req.params[0], - firstTxHash: req.params[1], - }); + let params: any; + if (Array.isArray(req.params)) { + params = { + pageSize: req.params[0], + firstTxHash: req.params[1], + firstTxTime: req.params[2], + lastTxTime: req.params[3], + firstTxHeight: req.params[4], + lastTxHeight: req.params[5], + ordering: { + field: req.params[6], + direction: req.params[7], + }, + }; + } else { + params = req.params; + } + const response = await queries.getPendingTransactions( + queryOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } case IrohaQuery.GetAccountTransactions: { try { - const response = await queries.getAccountTransactions(queryOptions, { - accountId: req.params[0], - pageSize: req.params[1], - firstTxHash: req.params[2], - }); + let params: any; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + pageSize: req.params[1], + firstTxHash: req.params[2], + firstTxTime: req.params[3], + lastTxTime: req.params[4], + firstTxHeight: req.params[5], + lastTxHeight: req.params[6], + ordering: { + field: req.params[7], + direction: req.params[8], + }, + }; + } else { + params = req.params; + } + const response = await queries.getAccountTransactions( + queryOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } case IrohaQuery.GetAccountAssetTransactions: { try { - const response = await queries.getAccountAssetTransactions( - queryOptions, - { + let params: any; + if (Array.isArray(req.params)) { + params = { accountId: req.params[0], assetId: req.params[1], pageSize: req.params[2], firstTxHash: req.params[3], - }, + firstTxTime: req.params[4], + lastTxTime: req.params[5], + firstTxHeight: req.params[6], + lastTxHeight: req.params[7], + ordering: { + field: req.params[8], + direction: req.params[9], + }, + }; + } else { + params = req.params; + } + const response = await queries.getAccountAssetTransactions( + queryOptions, + params, ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetBlock: { + case IrohaQuery.GetAccountAssets: { try { - const response = await queries.getBlock(queryOptions, { - height: req.params[0], - }); - return { transactionReceipt: response }; - } catch (err: any) { - if ( - "monitorModeEnabled" in baseConfig && - baseConfig.monitorModeEnabled === true - ) { - return { transactionReceipt: err }; + let params: any; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + pageSize: req.params[1], + firstAssetId: req.params[2], + }; } else { - this.log.error(err); - throw new RuntimeError(err); + params = req.params; } + const queryRes = await queries.getAccountAssets(queryOptions, params); + return { transactionReceipt: queryRes }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.CallEngine: { + case IrohaQuery.GetAccountDetail: { try { - const response = await commands.callEngine(commandOptions, { - type: req.params[0], - caller: req.params[1], - callee: req.params[2], - input: req.params[3], - }); - return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + let params: any; + if (Array.isArray(req.params)) { + params = { + accountId: req.params[0], + key: req.params[1], + writer: req.params[2], + pageSize: req.params[3], + paginationKey: req.params[4], + paginationWriter: req.params[5], + }; + } else { + params = req.params; + } + const queryRes = await queries.getAccountDetail(queryOptions, params); + return { transactionReceipt: queryRes }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetEngineReceipts: { + case IrohaQuery.GetAssetInfo: { try { - const response = await queries.getEngineReceipts(queryOptions, { - txHash: req.params[0], - }); - return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + let params; + if (Array.isArray(req.params)) { + params = { + assetId: req.params[0], + }; + } else { + params = req.params; + } + const queryRes = await queries.getAssetInfo(queryOptions, params); + return { transactionReceipt: queryRes }; + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.FetchCommits: { + case IrohaQuery.GetRoles: { try { - const response = await queries.fetchCommits(queryOptions); + const response = await queries.getRoles(queryOptions); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.AddPeer: { + case IrohaQuery.GetRolePermissions: { try { - const response = await commands.addPeer(commandOptions, { - address: req.params[0], - peerKey: req.params[1], - }); + let params; + if (Array.isArray(req.params)) { + params = { + roleId: req.params[0], + }; + } else { + params = req.params; + } + const response = await queries.getRolePermissions( + queryOptions, + params, + ); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaCommand.RemovePeer: { + case IrohaQuery.GetPeers: { try { - const response = await commands.removePeer(commandOptions, { - publicKey: req.params[0], - }); + const response = await queries.getPeers(queryOptions); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } - case IrohaQuery.GetPeers: { + case IrohaQuery.FetchCommits: { try { - const response = await queries.getPeers(queryOptions); + const response = await queries.fetchCommits(queryOptions); return { transactionReceipt: response }; - } catch (err: any) { - throw new RuntimeError(err); + } catch (err) { + throw new RuntimeError(err as any); } } default: { diff --git a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/plugin-ledger-connector-iroha.ts b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/plugin-ledger-connector-iroha.ts index 217f2718f8..5b50fc6c51 100644 --- a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/plugin-ledger-connector-iroha.ts +++ b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/plugin-ledger-connector-iroha.ts @@ -1,17 +1,5 @@ -import * as grpc from "grpc"; -import { CommandService_v1Client as CommandService } from "iroha-helpers/lib/proto/endpoint_grpc_pb"; -import { QueryService_v1Client as QueryService } from "iroha-helpers/lib/proto/endpoint_grpc_pb"; import { Transaction } from "iroha-helpers/lib/proto/transaction_pb"; -import commands from "iroha-helpers/lib/commands/index"; -import queries from "iroha-helpers/lib/queries"; import { TxBuilder } from "iroha-helpers/lib/chain"; -import { - GrantablePermission, - GrantablePermissionMap, -} from "iroha-helpers/lib/proto/primitive_pb"; - -import { Server } from "http"; -import { Server as SecureServer } from "https"; import type { Server as SocketIoServer } from "socket.io"; import type { Socket as SocketIoSocket } from "socket.io"; import type { Express } from "express"; @@ -44,11 +32,13 @@ import { import { RuntimeError } from "run-time-error"; import { + IrohaCommand, RunTransactionRequestV1, RunTransactionSignedRequestV1, GenerateTransactionRequestV1, RunTransactionResponse, - IrohaSocketSessionEvent, + WatchBlocksV1, + IrohaSocketIOTransactV1, } from "./generated/openapi/typescript-axios"; import { RunTransactionEndpoint } from "./web-services/run-transaction-endpoint"; @@ -65,7 +55,7 @@ export const E_KEYCHAIN_NOT_FOUND = "cactus.connector.iroha.keychain_not_found"; export interface IPluginLedgerConnectorIrohaOptions extends ICactusPluginOptions { rpcToriiPortHost: string; //http host:port - rpcApiWsHost: string; + rpcApiWsHost?: string; pluginRegistry: PluginRegistry; prometheusExporter?: PrometheusExporter; logLevel?: LogLevelDesc; @@ -87,11 +77,8 @@ export class PluginLedgerConnectorIroha private readonly log: Logger; private endpoints: IWebServiceEndpoint[] | undefined; - private readonly pluginRegistry: PluginRegistry; - private httpServer: Server | SecureServer | null = null; public static readonly CLASS_NAME = "PluginLedgerConnectorIroha"; - private socketSessionDictionary: { [char: string]: IrohaSocketIOEndpoint }; public get className(): string { return PluginLedgerConnectorIroha.CLASS_NAME; @@ -100,7 +87,6 @@ export class PluginLedgerConnectorIroha constructor(public readonly options: IPluginLedgerConnectorIrohaOptions) { const fnTag = `${this.className}#constructor()`; Checks.truthy(options, `${fnTag} arg options`); - Checks.truthy(options.rpcApiWsHost, `${fnTag} options.rpcApiWsHost`); Checks.truthy( options.rpcToriiPortHost, `${fnTag} options.rpcToriiPortHost`, @@ -119,7 +105,6 @@ export class PluginLedgerConnectorIroha this.prometheusExporter, `${fnTag} options.prometheusExporter`, ); - this.socketSessionDictionary = {}; this.prometheusExporter.startMetricsCollection(); } @@ -155,61 +140,61 @@ export class PluginLedgerConnectorIroha async registerWebServices( app: Express, - wsApi: SocketIoServer, + wsApi?: SocketIoServer, ): Promise { const { logLevel } = this.options; const webServices = await this.getOrCreateWebServices(); await Promise.all(webServices.map((ws) => ws.registerExpress(app))); - wsApi.on("connection", (socket: SocketIoSocket) => { - let irohaSocketEndpoint: IrohaSocketIOEndpoint; - - if (socket.id in this.socketSessionDictionary) { - this.log.debug(`Connected to existing socket session ID=${socket.id}`); - irohaSocketEndpoint = this.socketSessionDictionary[socket.id]; - } else { + if (wsApi) { + wsApi.on("connection", (socket: SocketIoSocket) => { this.log.debug(`New Socket connected. ID=${socket.id}`); - irohaSocketEndpoint = new IrohaSocketIOEndpoint({ socket, logLevel }); - this.socketSessionDictionary[socket.id] = irohaSocketEndpoint; - } - - let monitorFlag: boolean; - - socket.on(IrohaSocketSessionEvent.Subscribe, (monitorOptions: any) => { - this.log.debug(`Caught event: Subscribe`); - monitorFlag = true; - irohaSocketEndpoint.startMonitor(monitorOptions); - }); - - socket.on(IrohaSocketSessionEvent.Unsubscribe, () => { - this.log.debug(`Caught event: Unsubscribe`); - irohaSocketEndpoint.stopMonitor(); - }); - - socket.on( - IrohaSocketSessionEvent.SendAsyncRequest, - (asyncRequestData: any) => { - this.log.debug(`Caught event: SendAsyncRequest`); - irohaSocketEndpoint.sendRequest(asyncRequestData, true); - }, - ); - - socket.on( - IrohaSocketSessionEvent.SendSyncRequest, - (syncRequestData: any) => { - this.log.debug(`Caught event: SendSyncRequest`); - irohaSocketEndpoint.sendRequest(syncRequestData, false); - }, - ); - - socket.on("disconnect", async (reason: string) => { - this.log.info(`Session: ${socket.id} disconnected. Reason: ${reason}`); - if (monitorFlag) { + const irohaSocketEndpoint = new IrohaSocketIOEndpoint({ + socket, + logLevel, + }); + let monitorFlag: boolean; + + socket.on(WatchBlocksV1.Subscribe, (monitorOptions: any) => { + this.log.debug(`Caught event: Subscribe`); + monitorFlag = true; + irohaSocketEndpoint.startMonitor(monitorOptions); + }); + + socket.on(WatchBlocksV1.Unsubscribe, () => { + this.log.debug(`Caught event: Unsubscribe`); irohaSocketEndpoint.stopMonitor(); - monitorFlag = false; - } + }); + + socket.on( + IrohaSocketIOTransactV1.SendAsyncRequest, + (asyncRequestData: any) => { + this.log.debug(`Caught event: SendAsyncRequest`); + socket.disconnect(true); + irohaSocketEndpoint.sendRequest(asyncRequestData, true); + }, + ); + + socket.on( + IrohaSocketIOTransactV1.SendSyncRequest, + (syncRequestData: any) => { + this.log.debug(`Caught event: SendSyncRequest`); + irohaSocketEndpoint.sendRequest(syncRequestData, false); + }, + ); + + socket.on("disconnect", async (reason: string) => { + this.log.info( + `Session: ${socket.id} disconnected. Reason: ${reason}`, + ); + if (monitorFlag) { + irohaSocketEndpoint.stopMonitor(); + monitorFlag = false; + } + }); }); - }); + } + return webServices; } @@ -282,17 +267,19 @@ export class PluginLedgerConnectorIroha * Run Iroha transaction based on already signed transaction received from the client. * * @param req RunTransactionSignedRequestV1 - * @param commandService Iroha SDK `CommandService_v1Client` instance * @returns `Promise` */ private async transactSigned( req: RunTransactionSignedRequestV1, - commandService: CommandService, ): Promise { if (!req.baseConfig || !req.baseConfig.timeoutLimit) { throw new RuntimeError("baseConfig.timeoutLimit is undefined"); } + const { commandService } = IrohaTransactionWrapper.getIrohaServices( + req.baseConfig, + ); + try { const transactionBinary = Uint8Array.from( Object.values(req.signedTransaction), @@ -325,30 +312,13 @@ export class PluginLedgerConnectorIroha public async transact( req: RunTransactionSignedRequestV1 | RunTransactionRequestV1, ): Promise { - const { baseConfig } = req; - if (!baseConfig || !baseConfig.irohaHost || !baseConfig.irohaPort) { - throw new RuntimeError("Missing Iroha URL information."); - } - const irohaHostPort = `${baseConfig.irohaHost}:${baseConfig.irohaPort}`; - - let grpcCredentials; - if (baseConfig.tls) { - throw new RuntimeError("TLS option is not supported"); - } else { - grpcCredentials = grpc.credentials.createInsecure(); - } - - const commandService = new CommandService( - irohaHostPort, - //TODO:do something in the production environment - grpcCredentials, - ); - const queryService = new QueryService(irohaHostPort, grpcCredentials); - if ("signedTransaction" in req) { - return this.transactSigned(req, commandService); + return this.transactSigned(req); } else { - return this.transactRequest(req, commandService, queryService); + const transaction = new IrohaTransactionWrapper({ + logLevel: this.options.logLevel, + }); + return await transaction.transact(req); } } diff --git a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/web-services/iroha-socketio-endpoint.ts b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/web-services/iroha-socketio-endpoint.ts index 70296b818c..ba6c506163 100644 --- a/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/web-services/iroha-socketio-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-iroha/src/main/typescript/web-services/iroha-socketio-endpoint.ts @@ -4,13 +4,11 @@ import { Logger, Checks } from "@hyperledger/cactus-common"; import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common"; import { RunTransactionRequestV1, - RunTransactionResponse, - IrohaBlockProgress, IrohaCommand, IrohaBaseConfig, } from "../generated/openapi/typescript-axios"; import { - IrohaSocketSessionEvent, + WatchBlocksV1, IrohaQuery, } from "../generated/openapi/typescript-axios"; @@ -71,68 +69,89 @@ export class IrohaSocketIOEndpoint { return requestBody; } + private isLastBlockDetected(irohaResponse: unknown) { + const irohaResponseStr = JSON.stringify(irohaResponse).replace(/\\/g, ""); + const responseMatch = irohaResponseStr.match(/Reason: ({.*?})/); + if (!responseMatch || !responseMatch[1]) { + this.log.debug( + "Could not match error reason in response:", + irohaResponseStr, + ); + return false; + } + + const responseObject = JSON.parse(responseMatch[1]); + if (!responseObject) { + this.log.debug( + "Could not parse error object in response:", + irohaResponseStr, + ); + return false; + } + + if (responseObject.reason === 1 && responseObject.errorCode === 3) { + this.log.info(`Initial max block height is: ${this.currentBlockHeight}`); + return true; + } else { + throw responseObject; + } + } + private async getInitialMaxBlockHeight(requestData: any): Promise { this.log.debug("Checking max block height..."); const methodName: string = IrohaQuery.GetBlock; let args: Array; let requestBody: RunTransactionRequestV1; - let response: RunTransactionResponse; - let str_response: string; - - while (true) { - args = [this.currentBlockHeight]; - requestBody = this.createRequestBody(requestData, methodName, args); - this.log.debug(`Iroha requestBody: ${requestBody}`); + try { + while (true) { + args = [this.currentBlockHeight]; - response = await this.transaction.transact(requestBody); - str_response = String(response.transactionReceipt); - - if (str_response.includes("Query response error")) { - if (str_response.includes(`"errorCode":3`)) { - this.log.info( - `Initial max block height is: ${this.currentBlockHeight}`, - ); - break; - } else { - this.log.error(`Runtime error caught: ${str_response}`); + requestBody = this.createRequestBody(requestData, methodName, args); + this.log.debug(`Iroha requestBody: ${requestBody}`); + const response = await this.transaction.transact(requestBody); + if (this.isLastBlockDetected(response.transactionReceipt)) { break; } + this.currentBlockHeight++; + } + } catch (error) { + if (!this.isLastBlockDetected(error)) { + throw error; } - this.currentBlockHeight++; } } private async monitoringRoutine(baseConfig: any) { - let transactionReceipt: any; - let next: IrohaBlockProgress; - - const args = [this.currentBlockHeight]; - const methodName: string = IrohaQuery.GetBlock; - this.log.debug(`Current block: ${this.currentBlockHeight}`); + try { + const args = [this.currentBlockHeight]; + const methodName: string = IrohaQuery.GetBlock; + this.log.debug(`Current block: ${this.currentBlockHeight}`); - const requestBody = this.createRequestBody(baseConfig, methodName, args); - const response = await this.transaction.transact(requestBody); - const str_response = String(response.transactionReceipt); + const requestBody = this.createRequestBody(baseConfig, methodName, args); + const response = await this.transaction.transact(requestBody); - if (str_response.includes("Query response error")) { - if (str_response.includes(`"errorCode":3`)) { + if (this.isLastBlockDetected(response.transactionReceipt)) { this.log.debug("Waiting for new blocks..."); } else { - this.log.error(`Runtime error caught: ${str_response}`); + this.log.debug(`New block found`); + const transactionReceipt = response.transactionReceipt; + const next = { transactionReceipt }; + this.socket.emit(WatchBlocksV1.Next, next); + this.currentBlockHeight++; + } + } catch (error) { + if (this.isLastBlockDetected(error)) { + this.log.debug("Waiting for new blocks..."); + } else { + throw error; } - } else { - this.log.debug(`New block found`); - transactionReceipt = response.transactionReceipt; - next = { transactionReceipt }; - this.socket.emit(IrohaSocketSessionEvent.Next, next); - this.currentBlockHeight++; } } public async startMonitor(monitorOptions: any): Promise { - this.log.debug(`${IrohaSocketSessionEvent.Subscribe} => ${this.socket.id}`); + this.log.debug(`${WatchBlocksV1.Subscribe} => ${this.socket.id}`); this.log.info(`Starting monitoring blocks...`); this.monitorModeEnabled = true; diff --git a/packages/cactus-plugin-ledger-connector-iroha/src/test/typescript/integration/socket-api.test.ts b/packages/cactus-plugin-ledger-connector-iroha/src/test/typescript/integration/iroha-socketio-endpoint.test.ts similarity index 90% rename from packages/cactus-plugin-ledger-connector-iroha/src/test/typescript/integration/socket-api.test.ts rename to packages/cactus-plugin-ledger-connector-iroha/src/test/typescript/integration/iroha-socketio-endpoint.test.ts index c41fd556f7..e6353c322c 100644 --- a/packages/cactus-plugin-ledger-connector-iroha/src/test/typescript/integration/socket-api.test.ts +++ b/packages/cactus-plugin-ledger-connector-iroha/src/test/typescript/integration/iroha-socketio-endpoint.test.ts @@ -21,7 +21,7 @@ import { import { v4 as internalIpV4 } from "internal-ip"; import { v4 as uuidv4 } from "uuid"; import { RuntimeError } from "run-time-error"; -import cryptoHelper from "iroha-helpers-ts/lib/cryptoHelper"; +import cryptoHelper from "iroha-helpers/lib/cryptoHelper"; import { IrohaBlockProgress, IrohaBlockResponse, @@ -43,10 +43,12 @@ import bodyParser from "body-parser"; import http from "http"; import { Server as SocketIoServer } from "socket.io"; -const logLevel: LogLevelDesc = "DEBUG"; +const logLevel: LogLevelDesc = "info"; + +const requestTimoutLimit = 10 * 1000; // 10 seconds const log: Logger = LoggerProvider.getOrCreate({ - label: "socket-api.test", + label: "iroha-socketio-endpoint.test", level: logLevel, }); @@ -106,7 +108,7 @@ async function setupIrohaTestLedger(postgres: any): Promise { }); log.debug("Starting Iroha test ledger"); - await iroha.start(true); + await iroha.start(); const adminAccount = iroha.getDefaultAdminAccount(); const irohaHost = await internalIpV4(); @@ -154,6 +156,7 @@ describe("Iroha SocketIo TestSuite", () => { let iroha: IrohaLedgerInfo; let apiClient: IrohaApiClient; let server: http.Server; + let wsApi: SocketIoServer; beforeAll(async () => { const pruning = await pruneDockerAllIfGithubAction({ logLevel }); @@ -197,7 +200,7 @@ describe("Iroha SocketIo TestSuite", () => { expressApp.use(bodyParser.json({ limit: "250mb" })); server = http.createServer(expressApp); - const wsApi = new SocketIoServer(server, { + wsApi = new SocketIoServer(server, { path: Constants.SocketIoConnectionPathV1, }); @@ -216,6 +219,7 @@ describe("Iroha SocketIo TestSuite", () => { const irohaApiClientOptions = new IrohaApiClientOptions({ basePath: apiHost, + timeoutLimit: requestTimoutLimit, }); apiClient = new IrohaApiClient(irohaApiClientOptions); @@ -289,7 +293,7 @@ describe("Iroha SocketIo TestSuite", () => { creatorAccountId: `${iroha.adminAccount}@${iroha.domain}`, privKey: [iroha.adminPriv], quorum: 1, - timeoutLimit: 5000, + timeoutLimit: requestTimoutLimit, tls: false, }, params: [assetId, iroha.domain, 3], @@ -305,7 +309,7 @@ describe("Iroha SocketIo TestSuite", () => { creatorAccountId: `${iroha.adminAccount}@${iroha.domain}`, privKey: [iroha.adminPriv], quorum: 1, - timeoutLimit: 10000, + timeoutLimit: requestTimoutLimit, }, pollTime: 5000, }; @@ -358,7 +362,7 @@ describe("Iroha SocketIo TestSuite", () => { creatorAccountId: `${iroha.adminAccount}@${iroha.domain}`, privKey: [iroha.adminPriv], quorum: 1, - timeoutLimit: 10000, + timeoutLimit: requestTimoutLimit, }, pollTime: 3000, }; @@ -374,12 +378,12 @@ describe("Iroha SocketIo TestSuite", () => { creatorAccountId: `${iroha.adminAccount}@${iroha.domain}`, privKey: [iroha.adminPriv], quorum: 1, - timeoutLimit: 5000, + timeoutLimit: requestTimoutLimit, }; const params = [assetID, iroha.domain, 3]; log.debug(`Sending Async Request with ${commandName} command.`); - apiClient.sendAsyncRequest(params, commandName, baseConfig); + apiClient.sendAsyncRequest(commandName, params, baseConfig); const arrivedBlock = await new Promise( (resolve, reject) => { @@ -413,33 +417,51 @@ describe("Iroha SocketIo TestSuite", () => { creatorAccountId: `${iroha.adminAccount}@${iroha.domain}`, privKey: [iroha.adminPriv], quorum: 1, - timeoutLimit: 10000, + timeoutLimit: requestTimoutLimit, }; const params = [assetID, iroha.domain, 3]; log.debug(`Sending Sync Request with ${commandName} command.`); const response = await apiClient.sendSyncRequest( - params, commandName, + params, baseConfig, ); expect(response).not.toBe(undefined || " "); expect(Object.keys(response)).toContain("status"); expect(Object.keys(response)).toContain("data"); - expect(response.status).toBe("COMMITTED"); + expect(response.status).toEqual(["COMMITTED"]); log.debug("Sync call successfully completed"); }); afterAll(async () => { - // Remove Iroha after all tests are done - await iroha.testLedger.stop(); - await iroha.testLedger.destroy(); + if (wsApi) { + log.info("Stop the SocketIO server connector..."); + await new Promise((resolve) => wsApi.close(() => resolve())); + } - await postgres.container.stop(); - await postgres.container.destroy(); + if (server) { + log.info("Stop the HTTP server connector..."); + await new Promise((resolve) => server.close(() => resolve())); + } + + if (iroha) { + log.debug("Stop Iroha ledger..."); + await iroha.testLedger.stop(); + } + + if (postgres) { + log.debug("Stop Postgres container..."); + await postgres.container.stop(); + } const pruning = await pruneDockerAllIfGithubAction({ logLevel }); expect(pruning).toBeTruthy(); + + log.debug("Wait for send request timeouts to expire..."); + await new Promise((resolve) => setTimeout(resolve, 2 * requestTimoutLimit)); + + log.debug("All done."); }); }); diff --git a/packages/cactus-test-tooling/src/main/typescript/iroha/iroha-test-ledger.ts b/packages/cactus-test-tooling/src/main/typescript/iroha/iroha-test-ledger.ts index 4b104a2dff..65a04a49e1 100644 --- a/packages/cactus-test-tooling/src/main/typescript/iroha/iroha-test-ledger.ts +++ b/packages/cactus-test-tooling/src/main/typescript/iroha/iroha-test-ledger.ts @@ -293,6 +293,8 @@ export class IrohaTestLedger implements ITestLedger { { ExposedPorts: { [`${this.rpcToriiPort}/tcp`]: {}, // Iroha RPC - Torii + [`${this.toriiTlsPort}/tcp`]: {}, // Iroha TLS RPC + [`${this.rpcApiWsPort}/tcp`]: {}, // Iroha RPC WS }, Env: this.envVars, Healthcheck: { diff --git a/packages/cactus-verifier-client/README.md b/packages/cactus-verifier-client/README.md index a4d8d3f40b..af01a8c051 100644 --- a/packages/cactus-verifier-client/README.md +++ b/packages/cactus-verifier-client/README.md @@ -10,6 +10,7 @@ This package provides `Verifier` and `VerifierFactory` components that can be us | BESU_1X
BESU_2X | cactus-plugin-ledger-connector-besu | | QUORUM_2X | cactus-test-plugin-ledger-connector-quorum | | CORDA_4X | cactus-plugin-ledger-connector-corda | +| IROHA_1X | cactus-plugin-ledger-connector-iroha | | legacy-socketio | cactus-plugin-ledger-connector-fabric-socketio
cactus-plugin-ledger-connector-go-ethereum-socketio
cactus-plugin-ledger-connector-sawtooth-socketio | ## VerifierFactory diff --git a/packages/cactus-verifier-client/package.json b/packages/cactus-verifier-client/package.json index b6e25e5f6a..8c14c5e3e9 100644 --- a/packages/cactus-verifier-client/package.json +++ b/packages/cactus-verifier-client/package.json @@ -55,6 +55,7 @@ "@hyperledger/cactus-plugin-ledger-connector-besu": "1.1.2", "@hyperledger/cactus-plugin-ledger-connector-corda": "1.1.2", "@hyperledger/cactus-plugin-ledger-connector-quorum": "1.1.2", + "@hyperledger/cactus-plugin-ledger-connector-iroha": "1.1.2", "jest-extended": "0.11.5", "rxjs": "7.3.0" } diff --git a/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts b/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts index e13270d446..1f1d700980 100644 --- a/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts +++ b/packages/cactus-verifier-client/src/main/typescript/get-validator-api-client.ts @@ -57,7 +57,7 @@ export type ClientApiConfig = { in: CordaApiClientOptions; out: CordaApiClient; }; - IROHA: { + IROHA_1X: { in: IrohaApiClientOptions; out: IrohaApiClient; }; @@ -86,7 +86,7 @@ export function getValidatorApiClient( return new QuorumApiClient(options as QuorumApiClientOptions); case "CORDA_4X": return new CordaApiClient(options as CordaApiClientOptions); - case "IROHA": + case "IROHA_1X": return new IrohaApiClient(options as IrohaApiClientOptions); default: // Will not compile if any ClientApiConfig key was not handled by this switch diff --git a/packages/cactus-verifier-client/tsconfig.json b/packages/cactus-verifier-client/tsconfig.json index 9171a7bf61..59cbd3d106 100644 --- a/packages/cactus-verifier-client/tsconfig.json +++ b/packages/cactus-verifier-client/tsconfig.json @@ -22,6 +22,15 @@ }, { "path": "../cactus-plugin-ledger-connector-besu/tsconfig.json" + }, + { + "path": "../cactus-plugin-ledger-connector-quorum/tsconfig.json" + }, + { + "path": "../cactus-plugin-ledger-connector-corda/tsconfig.json" + }, + { + "path": "../cactus-plugin-ledger-connector-iroha/tsconfig.json" } ] -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 569299c918..ddcddcdccc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2814,11 +2814,6 @@ secp256k1 "4.0.2" sha3 "2.1.4" -"@improbable-eng/grpc-web-node-http-transport@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.13.0.tgz#a8680c7a8bce4c2b44fe48ba4b7c55b320cf5f54" - integrity sha512-Ev8pfMs7FbsBWc4FAY8N4dd8xQRowHFyu2AzEHl++8orrB4KSx6NonMqlsLDPBHLKwlYs7EEI6uxGwpjnYiS2Q== - "@improbable-eng/grpc-web@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web/-/grpc-web-0.12.0.tgz#9b10a7edf2a1d7672f8997e34a60e7b70e49738f" @@ -13001,13 +12996,12 @@ ipfs-utils@^8.1.2, ipfs-utils@^8.1.4: react-native-fetch-api "^2.0.0" stream-to-it "^0.2.2" -iroha-helpers-ts@0.9.25-ss: - version "0.9.25-ss" - resolved "https://registry.yarnpkg.com/iroha-helpers-ts/-/iroha-helpers-ts-0.9.25-ss.tgz#3091e4ee3009ef3ddd13ae9134e55f1b9edd308c" - integrity sha512-bbmhgrlf1B/TlWyBNd9EQVJLYfL/mXWohxsVu9jDVV1L21B4uM0CtSdllGsEncKbNBheLjYQ4Z7zgxKDxn6+zA== +iroha-helpers@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/iroha-helpers/-/iroha-helpers-1.5.0.tgz#91a50e260853e2f78b643fefe40217fddbe1aa16" + integrity sha512-RdZC4V3/94a4+jh+mwBVm/bGb7V4wC1ENCVmnPUjUaSSg4aFgf4jYTfCuS0p2ioVd23UZoClB2qymLRofKoSxw== dependencies: "@improbable-eng/grpc-web" "^0.12.0" - "@improbable-eng/grpc-web-node-http-transport" "^0.13.0" babel-preset-minify "^0.5.1" buffer "^5.4.0" ed25519.js "^1.3.0"