diff --git a/packages/cactus-plugin-bungee-hermes/package.json b/packages/cactus-plugin-bungee-hermes/package.json index 519af8fb72..7fff8ec202 100644 --- a/packages/cactus-plugin-bungee-hermes/package.json +++ b/packages/cactus-plugin-bungee-hermes/package.json @@ -35,6 +35,11 @@ "name": "André Augusto", "email": "andre.augusto@tecnico.ulisboa.pt", "url": "https://github.com/AndreAugusto11" + }, + { + "name": "Carlos Amaro", + "email": "carlosrscamaro@tecnico.ulisboa.pt", + "url": "https://github.com/LordKubaya" } ], "main": "dist/lib/main/typescript/index.js", @@ -64,6 +69,7 @@ "axios": "1.7.2", "body-parser": "1.20.2", "fs-extra": "11.2.0", + "http-errors-enhanced-cjs": "2.0.1", "key-encoder": "2.0.3", "merkletreejs": "0.3.11", "typescript-optional": "2.0.1", diff --git a/packages/cactus-plugin-bungee-hermes/src/main/typescript/plugin-bungee-hermes.ts b/packages/cactus-plugin-bungee-hermes/src/main/typescript/plugin-bungee-hermes.ts index a660eab159..d8373dce38 100644 --- a/packages/cactus-plugin-bungee-hermes/src/main/typescript/plugin-bungee-hermes.ts +++ b/packages/cactus-plugin-bungee-hermes/src/main/typescript/plugin-bungee-hermes.ts @@ -48,6 +48,7 @@ import { MergeViewsEndpointV1 } from "./web-services/merge-views-endpoint"; import { ProcessViewEndpointV1 } from "./web-services/process-view-endpoint"; import { PrivacyPolicies } from "./view-creation/privacy-policies"; +import { PluginRegistry } from "@hyperledger/cactus-core"; export interface IKeyPair { publicKey: Uint8Array; @@ -56,6 +57,7 @@ export interface IKeyPair { export interface IPluginBungeeHermesOptions extends ICactusPluginOptions { instanceId: string; + readonly pluginRegistry: PluginRegistry; keyPair?: IKeyPair; logLevel?: LogLevelDesc; diff --git a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/obtain-ledger-strategy.ts b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/obtain-ledger-strategy.ts index eeb1b68aa1..d4f412116c 100644 --- a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/obtain-ledger-strategy.ts +++ b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/obtain-ledger-strategy.ts @@ -1,8 +1,10 @@ import { Logger } from "@hyperledger/cactus-common"; import { State } from "../view-creation/state"; +import { IPluginLedgerConnector } from "@hyperledger/cactus-core-api/src/main/typescript/plugin/ledger-connector/i-plugin-ledger-connector"; export interface NetworkDetails { - connectorApiPath: string; + connectorApiPath?: string; + connector?: IPluginLedgerConnector; participant: string; } export interface ObtainLedgerStrategy { diff --git a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-besu.ts b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-besu.ts index cf26e702bd..9178e8f2fe 100644 --- a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-besu.ts +++ b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-besu.ts @@ -10,10 +10,12 @@ import { EthContractInvocationType, EvmBlock, EvmLog, + EvmTransaction, GetBlockV1Request, GetPastLogsV1Request, GetTransactionV1Request, InvokeContractV1Request, + PluginLedgerConnectorBesu, Web3SigningCredential, } from "@hyperledger/cactus-plugin-ledger-connector-besu"; import { State } from "../view-creation/state"; @@ -23,12 +25,14 @@ import { Transaction } from "../view-creation/transaction"; import Web3 from "web3"; import { Proof } from "../view-creation/proof"; import { TransactionProof } from "../view-creation/transaction-proof"; +import { BadRequestError, InternalServerError } from "http-errors-enhanced-cjs"; export interface BesuNetworkDetails extends NetworkDetails { signingCredential: Web3SigningCredential; keychainId: string; contractName: string; contractAddress: string; } + export class StrategyBesu implements ObtainLedgerStrategy { public static readonly CLASS_NAME = "StrategyBesu"; @@ -44,25 +48,40 @@ export class StrategyBesu implements ObtainLedgerStrategy { stateIds: string[], networkDetails: BesuNetworkDetails, ): Promise> { - const fnTag = `${StrategyBesu.CLASS_NAME}#generateLedgerStates()`; + const fn = `${StrategyBesu.CLASS_NAME}#generateLedgerStates()`; this.log.debug(`Generating ledger snapshot`); - Checks.truthy(networkDetails, `${fnTag} networkDetails`); + Checks.truthy(networkDetails, `${fn} networkDetails`); - const config = new Configuration({ - basePath: networkDetails.connectorApiPath, - }); - const besuApi = new BesuApi(config); + let besuApi: BesuApi | undefined; + let connector: PluginLedgerConnectorBesu | undefined; + + if (networkDetails.connector) { + connector = networkDetails.connector as PluginLedgerConnectorBesu; + } else if (networkDetails.connectorApiPath) { + const config = new Configuration({ + basePath: networkDetails.connectorApiPath, + }); + besuApi = new BesuApi(config); + } else { + throw new Error( + `${fn} networkDetails must have either connector or connectorApiPath`, + ); + } + const connectorOrApiClient = networkDetails.connector ? connector : besuApi; + if (!connectorOrApiClient) { + throw new InternalServerError(`${fn} got neither connector nor BesuAPI`); + } const ledgerStates = new Map(); const assetsKey = stateIds.length == 0 - ? await this.getAllAssetsKey(networkDetails, besuApi) + ? await this.getAllAssetsKey(networkDetails, connectorOrApiClient) : stateIds; this.log.debug("Current assets detected to capture: " + assetsKey); for (const assetKey of assetsKey) { const { transactions, values, blocks } = await this.getAllInfoByKey( assetKey, networkDetails, - besuApi, + connectorOrApiClient, ); const state = new State(assetKey, values, transactions); @@ -92,7 +111,7 @@ export class StrategyBesu implements ObtainLedgerStrategy { async getAllAssetsKey( networkDetails: BesuNetworkDetails, - api: BesuApi, + connectorOrApiClient: PluginLedgerConnectorBesu | BesuApi, ): Promise { const parameters = { contractName: networkDetails.contractName, @@ -103,29 +122,17 @@ export class StrategyBesu implements ObtainLedgerStrategy { signingCredential: networkDetails.signingCredential, gas: 1000000, }; - const response = await api.invokeContractV1( + const response = await this.invokeContract( parameters as InvokeContractV1Request, + connectorOrApiClient, ); - - if (response.status >= 200 && response.status < 300) { - if (response.data.callOutput) { - return response.data.callOutput as string[]; - } else { - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllAssetsKey: contract ${networkDetails.contractName} method getAllAssetsIDs output is falsy`, - ); - } - } - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllAssetsKey: BesuAPI error with status ${response.status}: ` + - response.data, - ); + return response; } async getAllInfoByKey( key: string, networkDetails: BesuNetworkDetails, - api: BesuApi, + connectorOrApiClient: PluginLedgerConnectorBesu | BesuApi, ): Promise<{ transactions: Transaction[]; values: string[]; @@ -137,57 +144,27 @@ export class StrategyBesu implements ObtainLedgerStrategy { address: networkDetails.contractAddress, topics: [[null], [Web3.utils.keccak256(key)]], //filter logs by asset key }; - const response = await api.getPastLogsV1(req as GetPastLogsV1Request); - if (response.status < 200 || response.status >= 300) { - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllInfoByKey: BesuAPI getPastLogsV1 error with status ${response.status}: ` + - response.data, - ); - } - if (!response.data.logs) { - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllInfoByKey: BesuAPI getPastLogsV1 API call successfull but output data is falsy`, - ); - } - const decoded = response.data.logs as EvmLog[]; + const decoded = await this.getPastLogs(req, connectorOrApiClient); const transactions: Transaction[] = []; const blocks: Map = new Map(); const values: string[] = []; this.log.debug("Getting transaction logs for asset: " + key); for (const log of decoded) { - const txTx = await api.getTransactionV1({ - transactionHash: log.transactionHash, - } as GetTransactionV1Request); - - if (txTx.status < 200 || txTx.status >= 300) { - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllInfoByKey: BesuAPI getTransactionV1 error with status ${txTx.status}: ` + - txTx.data, - ); - } - if (!txTx.data.transaction) { - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllInfoByKey: BesuAPI getTransactionV1 call successfull but output data is falsy`, - ); - } - - const txBlock = await api.getBlockV1({ - blockHashOrBlockNumber: log.blockHash, - } as GetBlockV1Request); + const txTx = await this.getTransaction( + { + transactionHash: log.transactionHash, + } as GetTransactionV1Request, + connectorOrApiClient, + ); - if (txBlock.status < 200 || txBlock.status >= 300) { - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllInfoByKey: BesuAPI getBlockV1 error with status ${txBlock.status}: ` + - txBlock.data, - ); - } - if (!txBlock.data.block) { - throw new Error( - `${StrategyBesu.CLASS_NAME}#getAllInfoByKey: BesuAPI getBlockV1 call successfull but output data is falsy`, - ); - } + const txBlock = await this.getBlock( + { + blockHashOrBlockNumber: log.blockHash, + } as GetBlockV1Request, + connectorOrApiClient, + ); this.log.debug( "Transaction: " + @@ -197,24 +174,256 @@ export class StrategyBesu implements ObtainLedgerStrategy { "\n =========== \n", ); const proof = new Proof({ - creator: txTx.data.transaction.from as string, //no sig for besu + creator: txTx.from as string, //no sig for besu }); const transaction: Transaction = new Transaction( log.transactionHash, - txBlock.data.block.timestamp, + txBlock.timestamp, new TransactionProof(proof, log.transactionHash), ); transaction.setStateId(key); transaction.setTarget(networkDetails.contractAddress as string); - transaction.setPayload( - txTx.data.transaction.input ? txTx.data.transaction.input : "", - ); //FIXME: payload = transaction input ? + transaction.setPayload(txTx.input ? txTx.input : ""); //FIXME: payload = transaction input ? transactions.push(transaction); values.push(JSON.stringify(log.data)); - blocks.set(transaction.getId(), txBlock.data.block); + blocks.set(transaction.getId(), txBlock); } return { transactions: transactions, values: values, blocks: blocks }; } + + async invokeContract( + parameters: InvokeContractV1Request, + connectorOrApiClient: PluginLedgerConnectorBesu | BesuApi, + ): Promise { + const fn = `${StrategyBesu.CLASS_NAME}#invokeContract()`; + if (!connectorOrApiClient) { + // throw BadRequestError because it is not our fault that we did not get + // all the needed parameters, e.g. we are signaling that this is a "user error" + // where the "user" is the other developer who called our function. + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorBesu) { + const connector: PluginLedgerConnectorBesu = connectorOrApiClient; + const response = await connector.invokeContract(parameters); + if (!response) { + // We throw an InternalServerError because the user is not responsible + // for us not being able to obtain a result from the contract invocation. + // They provided us parameters for the call (which we then validated and + // accepted) an therefore now if something goes wrong we have to throw + // an exception accordingly (e.g. us "admitting fault") + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.callOutput) { + throw new InternalServerError(`${fn} response.callOutput is falsy`); + } + const { callOutput } = response; + if (!Array.isArray(callOutput)) { + throw new InternalServerError(`${fn} callOutput not an array`); + } + const allItemsAreStrings = callOutput.every((x) => typeof x === "string"); + if (!allItemsAreStrings) { + throw new InternalServerError(`${fn} callOutput has non-string items`); + } + return response.callOutput as string[]; + } else if (connectorOrApiClient instanceof BesuApi) { + const api: BesuApi = connectorOrApiClient; + const response = await api.invokeContractV1(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + // We log the error here on the debug level so that later on we can inspect the contents + // of it in the logs if we need to. The reason that this is important is because we do not + // want to dump the full response onto our own error response that is going back to the caller + // due to that potentially being a security issue that we are exposing internal data via the + // error responses. + // With that said, we still need to make sure that we can determine the root cause of any + // issues after the fact and therefore we must save the error response details somewhere (the logs) + this.log.debug("BesuAPI non-2xx HTTP response:", data, status, config); + + // For the caller/client we just send back a generic error admitting that we somehow messed up: + const errorMessage = `${fn} BesuAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + if (!data.callOutput) { + throw new InternalServerError(`${fn} data.callOutput is falsy`); + } + const { callOutput } = data; + if (!Array.isArray(callOutput)) { + throw new InternalServerError(`${fn} callOutput not an array`); + } + const allItemsAreStrings = callOutput.every((x) => typeof x === "string"); + if (!allItemsAreStrings) { + throw new InternalServerError(`${fn} callOutput has non-string items`); + } + return response.data.callOutput; + } + throw new InternalServerError(`${fn}: neither BesuAPI nor Connector given`); + } + + async getPastLogs( + req: GetPastLogsV1Request, + connectorOrApiClient: PluginLedgerConnectorBesu | BesuApi, + ): Promise { + const fn = `${StrategyBesu.CLASS_NAME}#getPastLogs()`; + if (!connectorOrApiClient) { + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorBesu) { + const connector: PluginLedgerConnectorBesu = connectorOrApiClient; + const response = await connector.getPastLogs(req); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.logs) { + throw new InternalServerError(`${fn} response.logs is falsy`); + } + const { logs } = response; + if (!Array.isArray(logs)) { + throw new InternalServerError(`${fn} logs not an array`); + } + const allItemsAreEvmLog = logs.every((x) => this.isEvmLog(x)); + if (!allItemsAreEvmLog) { + throw new InternalServerError(`${fn} logs has non-EvmLog items`); + } + return response.logs as EvmLog[]; + } else if (connectorOrApiClient instanceof BesuApi) { + const api: BesuApi = connectorOrApiClient; + const response = await api.getPastLogsV1(req); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug("BesuAPI non-2xx HTTP response:", data, status, config); + const errorMessage = `${fn} BesuAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + if (!data.logs) { + throw new InternalServerError(`${fn} data.logs is falsy`); + } + const { logs } = data; + if (!Array.isArray(logs)) { + throw new InternalServerError(`${fn} logs not an array`); + } + const allItemsAreEvmLog = logs.every((x) => this.isEvmLog(x)); + if (!allItemsAreEvmLog) { + throw new InternalServerError(`${fn} logs has non-EvmLog items`); + } + return response.data.logs; + } + throw new InternalServerError(`${fn}: neither BesuAPI nor Connector given`); + } + + isEvmLog(x: any): x is EvmLog { + return ( + "address" in x && + "data" in x && + "blockHash" in x && + "transactionHash" in x && + "topics" in x && + "blockNumber" in x && + "logIndex" in x && + "transactionIndex" in x + ); + } + + async getTransaction( + req: GetTransactionV1Request, + connectorOrApiClient: PluginLedgerConnectorBesu | BesuApi, + ): Promise { + const fn = `${StrategyBesu.CLASS_NAME}#getTransaction()`; + if (!connectorOrApiClient) { + } else if (connectorOrApiClient instanceof PluginLedgerConnectorBesu) { + const connector: PluginLedgerConnectorBesu = connectorOrApiClient; + const response = await connector.getTransaction(req); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.transaction) { + throw new InternalServerError(`${fn} response.transaction is falsy`); + } + return response.transaction; + } else if (connectorOrApiClient instanceof BesuApi) { + const api: BesuApi = connectorOrApiClient; + const response = await api.getTransactionV1(req); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug("BesuAPI non-2xx HTTP response:", data, status, config); + + const errorMessage = `${fn} BesuAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + if (!data.transaction) { + throw new InternalServerError(`${fn} data.transaction is falsy`); + } + return response.data.transaction; + } + throw new InternalServerError(`${fn}: neither BesuAPI nor Connector given`); + } + + async getBlock( + req: GetBlockV1Request, + connectorOrApiClient: PluginLedgerConnectorBesu | BesuApi, + ): Promise { + const fn = `${StrategyBesu.CLASS_NAME}#getBlock()`; + if (!connectorOrApiClient) { + } else if (connectorOrApiClient instanceof PluginLedgerConnectorBesu) { + const connector: PluginLedgerConnectorBesu = connectorOrApiClient; + const response = await connector.getBlock(req); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.block) { + throw new InternalServerError(`${fn} response.block is falsy`); + } + return response.block; + } else if (connectorOrApiClient instanceof BesuApi) { + const api: BesuApi = connectorOrApiClient; + const response = await api.getBlockV1(req); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug("BesuAPI non-2xx HTTP response:", data, status, config); + + const errorMessage = `${fn} BesuAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + if (!data.block) { + throw new InternalServerError(`${fn} data.block is falsy`); + } + return data.block; + } + throw new InternalServerError(`${fn}: neither BesuAPI nor Connector given`); + } } diff --git a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-ethereum.ts b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-ethereum.ts index c77824b727..28e189c415 100644 --- a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-ethereum.ts +++ b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-ethereum.ts @@ -10,6 +10,8 @@ import { EthContractInvocationType, InvokeContractV1Request, InvokeRawWeb3EthMethodV1Request, + PluginLedgerConnectorEthereum, + InvokeRawWeb3EthMethodV1Response, } from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; import { NetworkDetails, ObtainLedgerStrategy } from "./obtain-ledger-strategy"; import { Configuration } from "@hyperledger/cactus-core-api"; @@ -19,6 +21,7 @@ import Web3 from "web3"; import { Proof } from "../view-creation/proof"; import { TransactionProof } from "../view-creation/transaction-proof"; import { Transaction } from "../view-creation/transaction"; +import { BadRequestError, InternalServerError } from "http-errors-enhanced-cjs"; interface EvmLog { address: string; @@ -73,26 +76,44 @@ export class StrategyEthereum implements ObtainLedgerStrategy { stateIds: string[], networkDetails: EthereumNetworkDetails, ): Promise> { - const fnTag = `${StrategyEthereum.CLASS_NAME}#generateLedgerStates()`; + const fn = `${StrategyEthereum.CLASS_NAME}#generateLedgerStates()`; this.log.debug(`Generating ledger snapshot`); - Checks.truthy(networkDetails, `${fnTag} networkDetails`); + Checks.truthy(networkDetails, `${fn} networkDetails`); - const config = new Configuration({ - basePath: networkDetails.connectorApiPath, - }); - const ethereumApi = new EthereumApi(config); + let ethereumApi: EthereumApi | undefined; + let connector: PluginLedgerConnectorEthereum | undefined; + + if (networkDetails.connector) { + connector = networkDetails.connector as PluginLedgerConnectorEthereum; + } else if (networkDetails.connectorApiPath) { + const config = new Configuration({ + basePath: networkDetails.connectorApiPath, + }); + ethereumApi = new EthereumApi(config); + } else { + throw new Error( + `${StrategyEthereum.CLASS_NAME}#generateLedgerStates: networkDetails must have either connector or connectorApiPath`, + ); + } + + const connectorOrApiClient = connector ? connector : ethereumApi; + if (!connectorOrApiClient) { + throw new InternalServerError( + `${fn} got neither connector nor EthereumApi`, + ); + } const ledgerStates = new Map(); const assetsKey = stateIds.length == 0 - ? await this.getAllAssetsKey(networkDetails, ethereumApi) + ? await this.getAllAssetsKey(networkDetails, connectorOrApiClient) : stateIds; this.log.debug("Current assets detected to capture: " + assetsKey); for (const assetKey of assetsKey) { const { transactions, values, blocks } = await this.getAllInfoByKey( assetKey, networkDetails, - ethereumApi, + connectorOrApiClient, ); const state = new State(assetKey, values, transactions); @@ -122,7 +143,7 @@ export class StrategyEthereum implements ObtainLedgerStrategy { async getAllAssetsKey( networkDetails: EthereumNetworkDetails, - api: EthereumApi, + connectorOrApiClient: PluginLedgerConnectorEthereum | EthereumApi, ): Promise { const parameters = { contract: { @@ -134,28 +155,17 @@ export class StrategyEthereum implements ObtainLedgerStrategy { params: [], signingCredential: networkDetails.signingCredential, }; - const response = await api.invokeContractV1( + const response = await this.invokeContract( parameters as InvokeContractV1Request, + connectorOrApiClient, ); - if (response.status >= 200 && response.status < 300) { - if (response.data.callOutput) { - return response.data.callOutput as string[]; - } else { - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllAssetsKey: contract ${networkDetails.contractName} method getAllAssetsIDs output is falsy`, - ); - } - } - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllAssetsKey: EthereumAPI error with status ${response.status}: ` + - response.data, - ); + return response; } async getAllInfoByKey( key: string, networkDetails: EthereumNetworkDetails, - api: EthereumApi, + connectorOrApiClient: PluginLedgerConnectorEthereum | EthereumApi, ): Promise<{ transactions: Transaction[]; values: string[]; @@ -171,19 +181,11 @@ export class StrategyEthereum implements ObtainLedgerStrategy { methodName: "getPastLogs", params: [filter], }; - const response = await api.invokeWeb3EthMethodV1(getLogsReq); - if (response.status < 200 || response.status >= 300) { - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllInfoByKey: BesuAPI getPastLogsV1 error with status ${response.status}: ` + - response.data, - ); - } - if (!response.data.data) { - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllInfoByKey: BesuAPI getPastLogsV1 API call successfull but output data is falsy`, - ); - } - const decoded = response.data.data as EvmLog[]; + const response = await this.invokeWeb3EthMethod( + getLogsReq, + connectorOrApiClient, + ); + const decoded = response.data as EvmLog[]; const transactions: Transaction[] = []; const blocks: Map = new Map(); const values: string[] = []; @@ -194,37 +196,19 @@ export class StrategyEthereum implements ObtainLedgerStrategy { methodName: "getTransaction", params: [log.transactionHash], }; - const txTx = await api.invokeWeb3EthMethodV1(getTransactionReq); - - if (txTx.status < 200 || txTx.status >= 300) { - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllInfoByKey: EthereumAPI invokeWeb3EthMethodV1 getTransaction error with status ${txTx.status}: ` + - txTx.data, - ); - } - if (!txTx.data.data) { - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllInfoByKey: EthereumAPI invokeWeb3EthMethodV1 getTransaction successfull but output data is falsy`, - ); - } + const txTx = await this.invokeWeb3EthMethod( + getTransactionReq, + connectorOrApiClient, + ); const getBlockReq: InvokeRawWeb3EthMethodV1Request = { methodName: "getBlock", params: [log.blockHash], }; - const txBlock = await api.invokeWeb3EthMethodV1(getBlockReq); - - if (txBlock.status < 200 || txBlock.status >= 300) { - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllInfoByKey: EthereumAPI invokeWeb3EthMethodV1 getBlock error with status ${txBlock.status}: ` + - txBlock.data, - ); - } - if (!txBlock.data.data) { - throw new Error( - `${StrategyEthereum.CLASS_NAME}#getAllInfoByKey: EthereumAPI invokeWeb3EthMethodV1 getBlock successfull but output data is falsy`, - ); - } + const txBlock = await this.invokeWeb3EthMethod( + getBlockReq, + connectorOrApiClient, + ); this.log.debug( "Transaction: " + @@ -234,22 +218,157 @@ export class StrategyEthereum implements ObtainLedgerStrategy { "\n =========== \n", ); const proof = new Proof({ - creator: txTx.data.data.from as string, //no sig for ethereum + creator: txTx.data.from as string, //no sig for ethereum }); const transaction: Transaction = new Transaction( log.transactionHash, - txBlock.data.data.timestamp, + txBlock.data.timestamp, new TransactionProof(proof, log.transactionHash), ); transaction.setStateId(key); transaction.setTarget(networkDetails.contractAddress as string); - transaction.setPayload(txTx.data.data.input ? txTx.data.data.input : ""); //FIXME: payload = transaction input ? + transaction.setPayload(txTx.data.input ? txTx.data.input : ""); //FIXME: payload = transaction input ? transactions.push(transaction); values.push(JSON.stringify(log.data)); - blocks.set(transaction.getId(), txBlock.data.data); + blocks.set(transaction.getId(), txBlock.data); } return { transactions: transactions, values: values, blocks: blocks }; } + + async invokeContract( + parameters: InvokeContractV1Request, + connectorOrApiClient: PluginLedgerConnectorEthereum | EthereumApi, + ): Promise { + const fn = `${StrategyEthereum.CLASS_NAME}#invokeContract()`; + if (!connectorOrApiClient) { + // throw BadRequestError because it is not our fault that we did not get + // all the needed parameters, e.g. we are signaling that this is a "user error" + // where the "user" is the other developer who called our function. + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorEthereum) { + const connector: PluginLedgerConnectorEthereum = connectorOrApiClient; + const response = await connector.invokeContract(parameters); + if (!response) { + // We throw an InternalServerError because the user is not responsible + // for us not being able to obtain a result from the contract invocation. + // They provided us parameters for the call (which we then validated and + // accepted) an therefore now if something goes wrong we have to throw + // an exception accordingly (e.g. us "admitting fault") + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.callOutput) { + throw new InternalServerError(`${fn} response.callOutput is falsy`); + } + const { callOutput } = response; + if (!Array.isArray(callOutput)) { + throw new InternalServerError(`${fn} callOutput not an array`); + } + const allItemsAreStrings = callOutput.every((x) => typeof x === "string"); + if (!allItemsAreStrings) { + throw new InternalServerError(`${fn} callOutput has non-string items`); + } + return response.callOutput as string[]; + } else if (connectorOrApiClient instanceof EthereumApi) { + const api: EthereumApi = connectorOrApiClient; + const response = await api.invokeContractV1(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + // We log the error here on the debug level so that later on we can inspect the contents + // of it in the logs if we need to. The reason that this is important is because we do not + // want to dump the full response onto our own error response that is going back to the caller + // due to that potentially being a security issue that we are exposing internal data via the + // error responses. + // With that said, we still need to make sure that we can determine the root cause of any + // issues after the fact and therefore we must save the error response details somewhere (the logs) + this.log.debug( + "EthereumApi non-2xx HTTP response:", + data, + status, + config, + ); + + // For the caller/client we just send back a generic error admitting that we somehow messed up: + const errorMessage = `${fn} EthereumApi error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + if (!data.callOutput) { + throw new InternalServerError(`${fn} data.callOutput is falsy`); + } + const { callOutput } = data; + if (!Array.isArray(callOutput)) { + throw new InternalServerError(`${fn} callOutput not an array`); + } + const allItemsAreStrings = callOutput.every((x) => typeof x === "string"); + if (!allItemsAreStrings) { + throw new InternalServerError(`${fn} callOutput has non-string items`); + } + return response.data.callOutput; + } + throw new InternalServerError( + `${fn}: neither EthereumApi nor Connector given`, + ); + } + + async invokeWeb3EthMethod( + parameters: InvokeRawWeb3EthMethodV1Request, + connectorOrApiClient: PluginLedgerConnectorEthereum | EthereumApi, + ): Promise { + const fn = `${StrategyEthereum.CLASS_NAME}#invokeContract()`; + if (!connectorOrApiClient) { + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorEthereum) { + const connector: PluginLedgerConnectorEthereum = connectorOrApiClient; + const response = { + data: await connector.invokeRawWeb3EthMethod(parameters), + } as InvokeRawWeb3EthMethodV1Response; + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + return response as InvokeRawWeb3EthMethodV1Response; + } else if (connectorOrApiClient instanceof EthereumApi) { + const api: EthereumApi = connectorOrApiClient; + const response = await api.invokeWeb3EthMethodV1(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug( + "EthereumAPI non-2xx HTTP response:", + data, + status, + config, + ); + const errorMessage = `${fn} EthereumAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + if (!data.data) { + throw new InternalServerError(`${fn} data.data is falsy`); + } + return data; + } + throw new InternalServerError( + `${fn}: neither EthereumAPI nor Connector given`, + ); + } } diff --git a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts index a08bbbff46..c7ccbf60cc 100644 --- a/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts +++ b/packages/cactus-plugin-bungee-hermes/src/main/typescript/strategy/strategy-fabric.ts @@ -4,7 +4,7 @@ import { Configuration, FabricContractInvocationType, RunTransactionRequest, - GetBlockResponseTypeV1, + PluginLedgerConnectorFabric, } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; import { NetworkDetails, ObtainLedgerStrategy } from "./obtain-ledger-strategy"; import { @@ -18,6 +18,7 @@ import { State } from "../view-creation/state"; import { StateProof } from "../view-creation/state-proof"; import { Proof } from "../view-creation/proof"; import { TransactionProof } from "../view-creation/transaction-proof"; +import { BadRequestError, InternalServerError } from "http-errors-enhanced-cjs"; export interface FabricNetworkDetails extends NetworkDetails { signingCredential: FabricSigningCredential; @@ -40,18 +41,38 @@ export class StrategyFabric implements ObtainLedgerStrategy { stateIds: string[], networkDetails: FabricNetworkDetails, ): Promise> { - const fnTag = `${StrategyFabric.CLASS_NAME}#generateLedgerStates()`; + const fn = `${StrategyFabric.CLASS_NAME}#generateLedgerStates()`; this.log.debug(`Generating ledger snapshot`); - Checks.truthy(networkDetails, `${fnTag} networkDetails`); + Checks.truthy(networkDetails, `${fn} networkDetails`); - const config = new Configuration({ - basePath: networkDetails.connectorApiPath, - }); - const fabricApi = new FabricApi(config); + let fabricApi: FabricApi | undefined; + let connector: PluginLedgerConnectorFabric | undefined; + if (networkDetails.connector) { + connector = networkDetails.connector as PluginLedgerConnectorFabric; + } else if (networkDetails.connectorApiPath) { + const config = new Configuration({ + basePath: networkDetails.connectorApiPath, + }); + fabricApi = new FabricApi(config); + } else { + throw new Error( + `${fn} networkDetails must have either connector or connectorApiPath`, + ); + } + const connectorOrApiClient = networkDetails.connector + ? connector + : fabricApi; + if (!connectorOrApiClient) { + throw new InternalServerError( + `${fn} got neither connector nor FabricAPI`, + ); + } const assetsKey = stateIds.length == 0 - ? (await this.getAllAssetsKey(fabricApi, networkDetails)).split(",") + ? ( + await this.getAllAssetsKey(networkDetails, connectorOrApiClient) + ).split(",") : stateIds; const ledgerStates = new Map(); //For each key in ledgerAssetsKey @@ -59,15 +80,19 @@ export class StrategyFabric implements ObtainLedgerStrategy { const assetValues: string[] = []; const txWithTimeS: Transaction[] = []; - const txs = await this.getAllTxByKey(assetKey, fabricApi, networkDetails); + const txs = await this.getAllTxByKey( + networkDetails, + assetKey, + connectorOrApiClient, + ); //For each tx get receipt let last_receipt; for (const tx of txs) { const receipt = JSON.parse( await this.fabricGetTxReceiptByTxIDV1( - tx.getId(), - fabricApi, networkDetails, + tx.getId(), + connectorOrApiClient, ), ); tx.getProof().setCreator( @@ -96,9 +121,9 @@ export class StrategyFabric implements ObtainLedgerStrategy { last_receipt = receipt; } const block = await this.fabricGetBlockByTxID( - txs[txs.length - 1].getId(), - fabricApi, networkDetails, + txs[txs.length - 1].getId(), + connectorOrApiClient, ); const state = new State(assetKey, assetValues, txWithTimeS); ledgerStates.set(assetKey, state); @@ -123,39 +148,74 @@ export class StrategyFabric implements ObtainLedgerStrategy { } async fabricGetTxReceiptByTxIDV1( - transactionId: string, - api: FabricApi, networkDetails: FabricNetworkDetails, + transactionId: string, + connectorOrApiClient: PluginLedgerConnectorFabric | FabricApi, ): Promise { - const receiptLockRes = await api.getTransactionReceiptByTxIDV1({ + const fn = `${StrategyFabric.CLASS_NAME}#fabricGetTxReceiptByTxIDV1()`; + const parameters = { signingCredential: networkDetails.signingCredential, channelName: networkDetails.channelName, contractName: "qscc", invocationType: FabricContractInvocationType.Call, methodName: "GetBlockByTxID", params: [networkDetails.channelName, transactionId], - } as RunTransactionRequest); + } as RunTransactionRequest; - if (receiptLockRes.status >= 200 && receiptLockRes.status < 300) { - if (receiptLockRes.data) { - return JSON.stringify(receiptLockRes.data); - } else { - throw new Error( - `${StrategyFabric.CLASS_NAME}#fabricGetTxReceiptByTxIDV1: contract qscc method GetBlockByTxID invocation output is falsy`, + if (!connectorOrApiClient) { + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorFabric) { + const connector: PluginLedgerConnectorFabric = connectorOrApiClient; + const response = await connector.getTransactionReceiptByTxID(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + const receiptLockRes = JSON.stringify(response); + if (!receiptLockRes) { + throw new InternalServerError(`${fn} receiptLockRes is falsy`); + } + return receiptLockRes; + } else if (connectorOrApiClient instanceof FabricApi) { + const api: FabricApi = connectorOrApiClient; + const response = await api.getTransactionReceiptByTxIDV1(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug( + "FabricAPI non-2xx HTTP response:", + data, + status, + config, ); + const errorMessage = `${fn} FabricAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); } + + const receiptLockRes = JSON.stringify(data); + if (!receiptLockRes) { + throw new InternalServerError(`${fn} receiptLockRes is falsy`); + } + return receiptLockRes; } - throw new Error( - `${StrategyFabric.CLASS_NAME}#fabricGetTxReceiptByTxIDV1: FabricAPI error with status 500: ` + - receiptLockRes.data, + throw new InternalServerError( + `${fn}: neither FabricAPI nor Connector given`, ); } async fabricGetBlockByTxID( - txId: string, - api: FabricApi, networkDetails: FabricNetworkDetails, + txId: string, + connectorOrApiClient: PluginLedgerConnectorFabric | FabricApi, ): Promise<{ hash: string; signers: string[] }> { + const fn = `${StrategyFabric.CLASS_NAME}#fabricGetBlockByTxID()`; const gatewayOptions = { identity: networkDetails.signingCredential.keychainRef, wallet: { @@ -171,26 +231,51 @@ export class StrategyFabric implements ObtainLedgerStrategy { query: { transactionId: txId, }, - type: GetBlockResponseTypeV1.Full, + skipDecode: false, }; - const getBlockResponse = await api.getBlockV1(getBlockReq); + let block_data; - if (getBlockResponse.status < 200 || getBlockResponse.status >= 300) { - throw new Error( - `${StrategyFabric.CLASS_NAME}#fabricGetTxReceiptByTxIDV1: FabricAPI getBlockV1 error with status ${getBlockResponse.status}: ` + - getBlockResponse.data, - ); - } - if (!getBlockResponse.data) { - throw new Error( - `${StrategyFabric.CLASS_NAME}#fabricGetBlockByTxID: getBlockV1 API call output data is falsy`, + if (!connectorOrApiClient) { + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorFabric) { + const connector: PluginLedgerConnectorFabric = connectorOrApiClient; + const response = await connector.getBlock(getBlockReq); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + block_data = response; + } else if (connectorOrApiClient instanceof FabricApi) { + const api: FabricApi = connectorOrApiClient; + const response = await api.getBlockV1(getBlockReq); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug( + "FabricAPI non-2xx HTTP response:", + data, + status, + config, + ); + const errorMessage = `${fn} FabricAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + block_data = response.data; + } else { + throw new InternalServerError( + `${fn}: neither FabricAPI nor Connector given`, ); } - const block = JSON.parse( - JSON.stringify(getBlockResponse?.data), - ).decodedBlock; + const block = JSON.parse(JSON.stringify(block_data)).decodedBlock; const blockSig = block.metadata.metadata[0].signatures; const sigs = []; @@ -213,59 +298,133 @@ export class StrategyFabric implements ObtainLedgerStrategy { } async getAllAssetsKey( - api: FabricApi, networkDetails: FabricNetworkDetails, + connectorOrApiClient: PluginLedgerConnectorFabric | FabricApi, ): Promise { - const response = await api.runTransactionV1({ + const fn = `${StrategyFabric.CLASS_NAME}#getAllAssetsKey()`; + const parameters = { signingCredential: networkDetails.signingCredential, channelName: networkDetails.channelName, contractName: networkDetails.contractName, methodName: "GetAllAssetsKey", invocationType: FabricContractInvocationType.Call, params: [], - } as RunTransactionRequest); + } as RunTransactionRequest; - if (response.status >= 200 && response.status < 300) { - if (response.data.functionOutput) { - return response.data.functionOutput; - } else { - throw new Error( - `${StrategyFabric.CLASS_NAME}#getAllAssetsKey: contract ${networkDetails.contractName} method GetAllAssetsKey invocation output is falsy`, + if (!connectorOrApiClient) { + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorFabric) { + const connector: PluginLedgerConnectorFabric = connectorOrApiClient; + const response = await connector.transact(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.functionOutput) { + throw new InternalServerError(`${fn} response.functionOutput is falsy`); + } + const { functionOutput } = response; + if (!(typeof functionOutput === "string")) { + throw new InternalServerError( + `${fn} response.functionOutput is not a string`, + ); + } + return functionOutput; + } else if (connectorOrApiClient instanceof FabricApi) { + const api: FabricApi = connectorOrApiClient; + const response = await api.runTransactionV1(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug( + "FabricAPI non-2xx HTTP response:", + data, + status, + config, ); + const errorMessage = `${fn} FabricAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); } + if (!data.functionOutput) { + throw new InternalServerError(`${fn} response.functionOutput is falsy`); + } + const { functionOutput } = data; + if (!(typeof functionOutput === "string")) { + throw new InternalServerError( + `${fn} response.functionOutput is not a string`, + ); + } + return functionOutput; } - throw new Error( - `${StrategyFabric.CLASS_NAME}#getAllAssetsKey: FabricAPI error with status 500: ` + - response.data, + throw new InternalServerError( + `${fn}: neither FabricAPI nor Connector given`, ); } async getAllTxByKey( - key: string, - api: FabricApi, networkDetails: FabricNetworkDetails, + key: string, + connectorOrApiClient: PluginLedgerConnectorFabric | FabricApi, ): Promise { - const response = await api.runTransactionV1({ + const fn = `${StrategyFabric.CLASS_NAME}#getAllTxByKey()`; + const parameters = { signingCredential: networkDetails.signingCredential, channelName: networkDetails.channelName, contractName: networkDetails.contractName, methodName: "GetAllTxByKey", invocationType: FabricContractInvocationType.Call, params: [key], - } as RunTransactionRequest); + } as RunTransactionRequest; - if (response.status >= 200 && response.status < 300) { - if (response.data.functionOutput) { - return this.txsStringToTxs(response.data.functionOutput); - } else { - throw new Error( - `${StrategyFabric.CLASS_NAME}#getAllTxByKey: contract ${networkDetails.contractName} method GetAllTxByKey invocation output is falsy`, + if (!connectorOrApiClient) { + throw new BadRequestError(`${fn} connectorOrApiClient is falsy`); + } else if (connectorOrApiClient instanceof PluginLedgerConnectorFabric) { + const connector: PluginLedgerConnectorFabric = connectorOrApiClient; + const response = await connector.transact(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.functionOutput) { + throw new InternalServerError(`${fn} response.functionOutput is falsy`); + } + return this.txsStringToTxs(response.functionOutput); + } else if (connectorOrApiClient instanceof FabricApi) { + const api: FabricApi = connectorOrApiClient; + const response = await api.runTransactionV1(parameters); + if (!response) { + throw new InternalServerError(`${fn} response is falsy`); + } + if (!response.status) { + throw new InternalServerError(`${fn} response.status is falsy`); + } + const { status, data, statusText, config } = response; + if (response.status < 200 || response.status > 300) { + this.log.debug( + "FabricAPI non-2xx HTTP response:", + data, + status, + config, ); + const errorMessage = `${fn} FabricAPI error status: ${status}: ${statusText}`; + throw new InternalServerError(errorMessage); + } + if (!data) { + throw new InternalServerError(`${fn} response.data is falsy`); + } + if (!data.functionOutput) { + throw new InternalServerError(`${fn} response.functionOutput is falsy`); } + return this.txsStringToTxs(data.functionOutput); } - throw new Error( - `${StrategyFabric.CLASS_NAME}#getAllTxByKey: FabricAPI error with status 500: ` + - response.data, + throw new InternalServerError( + `${fn}: neither FabricAPI nor Connector given`, ); } diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-basic.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-basic.test.ts index b9967b9c7e..b0211e41bb 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-basic.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-basic.test.ts @@ -73,7 +73,9 @@ let bungeeContractAddress: string; let keychainPlugin: PluginKeychainMemory; -beforeAll(async () => { +let networkDetailsList: BesuNetworkDetails[]; + +beforeEach(async () => { pruneDockerAllIfGithubAction({ logLevel }) .then(() => { log.info("Pruning throw OK"); @@ -235,118 +237,147 @@ beforeAll(async () => { .contractAddress as string; pluginBungeeHermesOptions = { + pluginRegistry, keyPair: Secp256k1Keys.generateKeyPairsBuffer(), instanceId: uuidv4(), logLevel, }; } + networkDetailsList = [ + { + signingCredential: bungeeSigningCredential, + contractName, + connectorApiPath: besuPath, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + { + signingCredential: bungeeSigningCredential, + contractName, + connector: connector, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + ]; }); -test("test creation of views for different timeframes and states", async () => { - const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); - const strategy = "BESU"; - bungee.addStrategy(strategy, new StrategyBesu("INFO")); - const networkDetails: BesuNetworkDetails = { - signingCredential: bungeeSigningCredential, - contractName, - connectorApiPath: besuPath, - keychainId: bungeeKeychainId, - contractAddress: bungeeContractAddress, - participant: firstHighNetWorthAccount, - }; - - const snapshot = await bungee.generateSnapshot([], strategy, networkDetails); - const view = bungee.generateView( - snapshot, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view.view).toBeTruthy(); - expect(view.signature).toBeTruthy(); - - //expect the view to have capture the new asset BESU_ASSET_ID, and attributes to match - expect(snapshot.getStateBins().length).toEqual(1); - expect(snapshot.getStateBins()[0].getId()).toEqual(BESU_ASSET_ID); - expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); - - const view1 = bungee.generateView(snapshot, "0", "9999", undefined); - - //expects nothing to limit time of 9999 - expect(view1.view).toBeUndefined(); - expect(view1.signature).toBeUndefined(); - - //changing BESU_ASSET_ID value - const lockAsset = await connector.invokeContract({ - contractName, - keychainId: keychainPlugin.getKeychainId(), - invocationType: EthContractInvocationType.Send, - methodName: "lockAsset", - params: [BESU_ASSET_ID], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: besuKeyPair.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - gas: 1000000, - }); - expect(lockAsset).not.toBeUndefined(); - expect(lockAsset.success).toBeTrue(); - - //creating new asset - const new_asset_id = uuidv4(); - const depNew = await connector.invokeContract({ - contractName, - keychainId: keychainPlugin.getKeychainId(), - invocationType: EthContractInvocationType.Send, - methodName: "createAsset", - params: [new_asset_id, 10], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: besuKeyPair.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - gas: 1000000, - }); - expect(depNew).not.toBeUndefined(); - expect(depNew.success).toBeTrue(); - - const snapshot1 = await bungee.generateSnapshot([], strategy, networkDetails); - const view2 = bungee.generateView( - snapshot1, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view2.view).toBeTruthy(); - expect(view2.signature).toBeTruthy(); - - const stateBins = snapshot1.getStateBins(); - expect(stateBins.length).toEqual(2); //expect to have captured state for both assets - - const bins = [stateBins[0].getId(), stateBins[1].getId()]; - - //checks if values match: - // - new value of BESU_ASSET_ID state in new snapshot different than value from old snapshot) - // - successfully captured transaction that created the new asset - if (bins[0] === BESU_ASSET_ID) { - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); - expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( - snapshot.getStateBins()[0].getValue(), +test.each([{ apiPath: true }, { apiPath: false }])( + //test for both BesuApiPath and BesuConnector + "test creation of views for different timeframes and states using", + async ({ apiPath }) => { + let networkDetails: BesuNetworkDetails; + if (apiPath) { + networkDetails = networkDetailsList[0]; + } else { + networkDetails = networkDetailsList[1]; + } + const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); + const strategy = "BESU"; + bungee.addStrategy(strategy, new StrategyBesu("INFO")); + + const snapshot = await bungee.generateSnapshot( + [], + strategy, + networkDetails, ); - expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(1); - } else { - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); - expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(2); - expect(snapshot1.getStateBins()[1].getValue()).not.toEqual( - snapshot.getStateBins()[0].getValue(), + const view = bungee.generateView( + snapshot, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, ); - } -}); + //expect to return a view + expect(view.view).toBeTruthy(); + expect(view.signature).toBeTruthy(); + + //expect the view to have capture the new asset BESU_ASSET_ID, and attributes to match + expect(snapshot.getStateBins().length).toEqual(1); + expect(snapshot.getStateBins()[0].getId()).toEqual(BESU_ASSET_ID); + expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); + + const view1 = bungee.generateView(snapshot, "0", "9999", undefined); -afterAll(async () => { + //expects nothing to limit time of 9999 + expect(view1.view).toBeUndefined(); + expect(view1.signature).toBeUndefined(); + + //changing BESU_ASSET_ID value + const lockAsset = await connector?.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: [BESU_ASSET_ID], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockAsset).not.toBeUndefined(); + expect(lockAsset.success).toBeTrue(); + + //creating new asset + const new_asset_id = uuidv4(); + const depNew = await connector?.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: [new_asset_id, 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(depNew).not.toBeUndefined(); + expect(depNew.success).toBeTrue(); + + const snapshot1 = await bungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view2 = bungee.generateView( + snapshot1, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + //expect to return a view + expect(view2.view).toBeTruthy(); + expect(view2.signature).toBeTruthy(); + + const stateBins = snapshot1.getStateBins(); + expect(stateBins.length).toEqual(2); //expect to have captured state for both assets + + const bins = [stateBins[0].getId(), stateBins[1].getId()]; + + //checks if values match: + // - new value of BESU_ASSET_ID state in new snapshot different than value from old snapshot) + // - successfully captured transaction that created the new asset + if (bins[0] === BESU_ASSET_ID) { + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); + expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( + snapshot.getStateBins()[0].getValue(), + ); + expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(1); + } else { + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); + expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(2); + expect(snapshot1.getStateBins()[1].getValue()).not.toEqual( + snapshot.getStateBins()[0].getValue(), + ); + } + }, +); + +afterEach(async () => { await Servers.shutdown(besuServer); await besuLedger.stop(); await besuLedger.destroy(); diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-pruning.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-pruning.test.ts index 4bd8df5822..95fae810ef 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-pruning.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/besu-test-pruning.test.ts @@ -72,7 +72,9 @@ let bungeeContractAddress: string; let keychainPlugin: PluginKeychainMemory; -beforeAll(async () => { +let networkDetailsList: BesuNetworkDetails[]; + +beforeEach(async () => { pruneDockerAllIfGithubAction({ logLevel }) .then(() => { log.info("Pruning throw OK"); @@ -234,89 +236,119 @@ beforeAll(async () => { .contractAddress as string; pluginBungeeHermesOptions = { + pluginRegistry, keyPair: Secp256k1Keys.generateKeyPairsBuffer(), instanceId: uuidv4(), logLevel, }; } + networkDetailsList = [ + { + signingCredential: bungeeSigningCredential, + contractName, + connectorApiPath: besuPath, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + { + signingCredential: bungeeSigningCredential, + contractName, + connector: connector, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + ]; }); -test("test creation of views for specific timeframes", async () => { - const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); - const strategy = "BESU"; - bungee.addStrategy(strategy, new StrategyBesu("INFO")); - const netwokDetails: BesuNetworkDetails = { - signingCredential: bungeeSigningCredential, - contractName, - connectorApiPath: besuPath, - keychainId: bungeeKeychainId, - contractAddress: bungeeContractAddress, - participant: firstHighNetWorthAccount, - }; - - const snapshot = await bungee.generateSnapshot([], strategy, netwokDetails); - const view = bungee.generateView( - snapshot, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - - //expect to return a view - expect(view.view).toBeTruthy(); - expect(view.signature).toBeTruthy(); - - //expect the view to have capture the new asset BESU_ASSET_ID, and attributes to match - expect(snapshot.getStateBins().length).toEqual(1); - expect(snapshot.getStateBins()[0].getId()).toEqual(BESU_ASSET_ID); - expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); - - //changing BESU_ASSET_ID value - const lockAsset = await connector.invokeContract({ - contractName, - keychainId: keychainPlugin.getKeychainId(), - invocationType: EthContractInvocationType.Send, - methodName: "lockAsset", - params: [BESU_ASSET_ID], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: besuKeyPair.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - gas: 1000000, - }); - expect(lockAsset).not.toBeUndefined(); - expect(lockAsset.success).toBeTrue(); - - const snapshot1 = await bungee.generateSnapshot([], strategy, netwokDetails); - - //tI is the time of the first transaction + 1 - const tI = ( - parseInt(snapshot.getStateBins()[0].getTransactions()[0].getTimeStamp()) + 1 - ).toString(); - - const view1 = bungee.generateView( - snapshot1, - tI, - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - - //expect to return a view - expect(view1.view).toBeTruthy(); - expect(view1.signature).toBeTruthy(); - - expect(snapshot1.getStateBins().length).toEqual(1); - expect(snapshot1.getStateBins()[0].getId()).toEqual(BESU_ASSET_ID); - //expect the view to not include first transaction (made before tI) - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); - //expect old and new snapshot state values to differ - expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( - snapshot.getStateBins()[0].getValue(), - ); -}); +test.each([{ apiPath: true }, { apiPath: false }])( + //test for both BesuApiPath and BesuConnector + "test creation of views for specific timeframes", + async ({ apiPath }) => { + let networkDetails: BesuNetworkDetails; + if (apiPath) { + networkDetails = networkDetailsList[0]; + } else { + networkDetails = networkDetailsList[1]; + } + const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); + const strategy = "BESU"; + bungee.addStrategy(strategy, new StrategyBesu("INFO")); + + const snapshot = await bungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view = bungee.generateView( + snapshot, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + + //expect to return a view + expect(view.view).toBeTruthy(); + expect(view.signature).toBeTruthy(); + + //expect the view to have capture the new asset BESU_ASSET_ID, and attributes to match + expect(snapshot.getStateBins().length).toEqual(1); + expect(snapshot.getStateBins()[0].getId()).toEqual(BESU_ASSET_ID); + expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); + + //changing BESU_ASSET_ID value + const lockAsset = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: [BESU_ASSET_ID], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockAsset).not.toBeUndefined(); + expect(lockAsset.success).toBeTrue(); + + const snapshot1 = await bungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + + //tI is the time of the first transaction + 1 + const tI = ( + parseInt(snapshot.getStateBins()[0].getTransactions()[0].getTimeStamp()) + + 1 + ).toString(); + + const view1 = bungee.generateView( + snapshot1, + tI, + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + + //expect to return a view + expect(view1.view).toBeTruthy(); + expect(view1.signature).toBeTruthy(); + + expect(snapshot1.getStateBins().length).toEqual(1); + expect(snapshot1.getStateBins()[0].getId()).toEqual(BESU_ASSET_ID); + //expect the view to not include first transaction (made before tI) + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); + //expect old and new snapshot state values to differ + expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( + snapshot.getStateBins()[0].getValue(), + ); + }, +); -afterAll(async () => { +afterEach(async () => { await Servers.shutdown(besuServer); await besuLedger.stop(); await besuLedger.destroy(); diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-api-test.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-api-test.test.ts index 355da3bbe8..5526886ecd 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-api-test.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-api-test.test.ts @@ -56,14 +56,6 @@ import { } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; import path from "path"; import { DiscoveryOptions } from "fabric-network"; -import { - FabricNetworkDetails, - StrategyFabric, -} from "../../../main/typescript/strategy/strategy-fabric"; -import { - BesuNetworkDetails, - StrategyBesu, -} from "../../../main/typescript/strategy/strategy-besu"; import { PluginLedgerConnectorEthereum, DefaultApi as EthereumApi, @@ -72,10 +64,33 @@ import { GethTestLedger, WHALE_ACCOUNT_ADDRESS, } from "@hyperledger/cactus-test-geth-ledger"; -import { - EthereumNetworkDetails, - StrategyEthereum, -} from "../../../main/typescript/strategy/strategy-ethereum"; +import { StrategyEthereum } from "../../../main/typescript/strategy/strategy-ethereum"; +import { StrategyFabric } from "../../../main/typescript/strategy/strategy-fabric"; +import { StrategyBesu } from "../../../main/typescript/strategy/strategy-besu"; + +interface BesuNetworkDetails { + connectorApiPath: string; + participant: string; + signingCredential: Web3SigningCredential; + keychainId: string; + contractName: string; + contractAddress: string; +} +interface FabricNetworkDetails { + connectorApiPath: string; + participant: string; + signingCredential: FabricSigningCredential; + contractName: string; + channelName: string; +} +interface EthereumNetworkDetails { + connectorApiPath: string; + participant: string; + signingCredential: Web3SigningCredential; + keychainId: string; + contractName: string; + contractAddress: string; +} const logLevel: LogLevelDesc = "INFO"; @@ -101,10 +116,10 @@ const log = LoggerProvider.getOrCreate({ label: "BUNGEE - Hermes", }); -let ethereumSigningCredential; +let ethereumPath: string; +let ethereumSigningCredential: Web3SigningCredential; let ethereumKeychainId: string; let ethereumContractAddress: string; -let ethereumNetworkDetails: EthereumNetworkDetails; let ethereumServer: Server; let ethereumLedger: GethTestLedger; @@ -145,13 +160,16 @@ beforeAll(async () => { }); test("tests bungee api using different strategies", async () => { + const pluginRegistry = new PluginRegistry({ logLevel, plugins: [] }); const keyPair = Secp256k1Keys.generateKeyPairsBuffer(); pluginBungeeHermesOptions = { + pluginRegistry, keyPair, instanceId: uuidv4(), logLevel, }; const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); + pluginRegistry.add(bungee); //add strategies to BUNGEE - Hermes bungee.addStrategy(FABRIC_STRATEGY, new StrategyFabric("INFO")); @@ -175,6 +193,15 @@ test("tests bungee api using different strategies", async () => { participant: "Org1MSP", }; + const ethereumNetworkDetails: EthereumNetworkDetails = { + signingCredential: ethereumSigningCredential, + contractName: LockAssetContractJson.contractName, + connectorApiPath: ethereumPath, + keychainId: ethereumKeychainId, + contractAddress: ethereumContractAddress, + participant: WHALE_ACCOUNT_ADDRESS, + }; + const expressApp = express(); expressApp.use(bodyParser.json({ limit: "250mb" })); bungeeServer = http.createServer(expressApp); @@ -755,8 +782,8 @@ async function setupEthereumTestLedger(): Promise { }; const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; const { address, port } = addressInfo; - const apiHost = `http://${address}:${port}`; - const apiConfig = new Configuration({ basePath: apiHost }); + ethereumPath = `http://${address}:${port}`; + const apiConfig = new Configuration({ basePath: ethereumPath }); const apiClient = new EthereumApi(apiConfig); const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); const web3 = new Web3(rpcApiHttpHost); @@ -872,15 +899,6 @@ async function setupEthereumTestLedger(): Promise { ethereumContractAddress = deployOut.data.transactionReceipt .contractAddress as string; - ethereumNetworkDetails = { - signingCredential: ethereumSigningCredential, - contractName: LockAssetContractJson.contractName, - connectorApiPath: apiHost, - keychainId: ethereumKeychainId, - contractAddress: ethereumContractAddress, - participant: WHALE_ACCOUNT_ADDRESS, - }; - return "Ethereum Network setup successful"; } diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-merge-views.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-merge-views.test.ts index b47eb21d64..8ddbfdec8d 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-merge-views.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-merge-views.test.ts @@ -76,7 +76,9 @@ let bungeeServer: Server; let keychainPlugin: PluginKeychainMemory; -beforeAll(async () => { +let networkDetailsList: BesuNetworkDetails[]; + +beforeEach(async () => { pruneDockerAllIfGithubAction({ logLevel }) .then(() => { log.info("Pruning throw OK"); @@ -238,178 +240,209 @@ beforeAll(async () => { .contractAddress as string; pluginBungeeHermesOptions = { + pluginRegistry, keyPair: Secp256k1Keys.generateKeyPairsBuffer(), instanceId: uuidv4(), logLevel, }; } + networkDetailsList = [ + { + signingCredential: bungeeSigningCredential, + contractName, + connectorApiPath: besuPath, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + { + signingCredential: bungeeSigningCredential, + contractName, + connector: connector, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + ]; }); -test("test merging views, and integrated view proofs", async () => { - const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); - const strategy = "BESU"; - bungee.addStrategy(strategy, new StrategyBesu("INFO")); - const networkDetails: BesuNetworkDetails = { - signingCredential: bungeeSigningCredential, - contractName, - connectorApiPath: besuPath, - keychainId: bungeeKeychainId, - contractAddress: bungeeContractAddress, - participant: firstHighNetWorthAccount, - }; - - const snapshot = await bungee.generateSnapshot([], strategy, networkDetails); - const view = bungee.generateView( - snapshot, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view.view).toBeTruthy(); - expect(view.signature).toBeTruthy(); - - //changing BESU_ASSET_ID value - const lockAsset = await connector.invokeContract({ - contractName, - keychainId: keychainPlugin.getKeychainId(), - invocationType: EthContractInvocationType.Send, - methodName: "lockAsset", - params: [BESU_ASSET_ID], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: besuKeyPair.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - gas: 1000000, - }); - expect(lockAsset).not.toBeUndefined(); - expect(lockAsset.success).toBeTrue(); - - //creating new asset - const new_asset_id = uuidv4(); - const depNew = await connector.invokeContract({ - contractName, - keychainId: keychainPlugin.getKeychainId(), - invocationType: EthContractInvocationType.Send, - methodName: "createAsset", - params: [new_asset_id, 10], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: besuKeyPair.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - gas: 1000000, - }); - expect(depNew).not.toBeUndefined(); - expect(depNew.success).toBeTrue(); - - const snapshot1 = await bungee.generateSnapshot([], strategy, networkDetails); - const view2 = bungee.generateView( - snapshot1, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view2.view).toBeTruthy(); - expect(view2.signature).toBeTruthy(); - - const expressApp = express(); - expressApp.use(bodyParser.json({ limit: "250mb" })); - bungeeServer = http.createServer(expressApp); - const listenOptions: IListenOptions = { - hostname: "127.0.0.1", - port: 3000, - server: bungeeServer, - }; - const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - const { address, port } = addressInfo; - - await bungee.getOrCreateWebServices(); - await bungee.registerWebServices(expressApp); - const bungeePath = `http://${address}:${port}`; - - const config = new Configuration({ basePath: bungeePath }); - const bungeeApi = new BungeeApi(config); - - const mergeViewsNoPolicyReq = await bungeeApi.mergeViewsV1({ - serializedViews: [ - JSON.stringify({ - view: JSON.stringify(view.view as View), - signature: view.signature, - }), - // eslint-disable-next-line prettier/prettier - JSON.stringify({ - view: JSON.stringify(view2.view as View), - signature: view2.signature, - }), - ], - mergePolicy: MergePolicyOpts.NONE, - }); - expect(mergeViewsNoPolicyReq.status).toBe(200); - - expect(mergeViewsNoPolicyReq.data.integratedView).toBeTruthy(); - expect(mergeViewsNoPolicyReq.data.signature).toBeTruthy(); - - const mergeViewsNoPolicy = bungee.mergeViews( - [view.view as View, view2.view as View], - [view.signature as string, view2.signature as string], - MergePolicyOpts.NONE, - [], - ); - //1 transaction captured in first view, and 3 in the second - expect(mergeViewsNoPolicy.integratedView.getAllTransactions().length).toBe(4); - //1 state captured in first view, and 2 in the second - expect(mergeViewsNoPolicy.integratedView.getAllStates().length).toBe(3); - - const transactionReceipts: string[] = []; - - mergeViewsNoPolicy.integratedView.getAllTransactions().forEach((t) => { - transactionReceipts.push(JSON.stringify(t.getProof())); - }); - expect( - ( - await bungeeApi.verifyMerkleRoot({ - input: transactionReceipts, - root: mergeViewsNoPolicy.integratedView.getIntegratedViewProof() - .transactionsMerkleRoot, - }) - ).data.result, - ).toBeTrue(); - - const mergeViewsWithPolicy = bungee.mergeViews( - [view.view as View, view2.view as View], - [view.signature as string, view2.signature as string], - MergePolicyOpts.PruneState, - [BESU_ASSET_ID], //should remove all states related to this asset - ); - - //0 transactions captured in first view, and 1 in the second (because of policy) - // eslint-disable-next-line prettier/prettier - expect(mergeViewsWithPolicy.integratedView.getAllTransactions().length).toBe( - 1, - ); - //0 state captured in first view, and 1 in the second (because of policy) - expect(mergeViewsWithPolicy.integratedView.getAllStates().length).toBe(1); - - const mergeViewsWithPolicy2 = bungee.mergeViews( - [view.view as View, view2.view as View], - [view.signature as string, view2.signature as string], - MergePolicyOpts.PruneStateFromView, - [BESU_ASSET_ID, view2.view?.getKey() as string], //should remove all states related to this asset - ); - - //1 transactions captured in first view, and 1 in the second (because of policy) - // eslint-disable-next-line prettier/prettier - expect(mergeViewsWithPolicy2.integratedView.getAllTransactions().length).toBe( - 2, - ); - //1 state captured in first view, and only 1 in the second (because of policy) - expect(mergeViewsWithPolicy2.integratedView.getAllStates().length).toBe(2); -}); +test.each([{ apiPath: true }, { apiPath: false }])( + //test for both BesuApiPath and BesuConnector + "test merging views, and integrated view proofs", + async ({ apiPath }) => { + let networkDetails: BesuNetworkDetails; + if (apiPath) { + networkDetails = networkDetailsList[0]; + } else { + networkDetails = networkDetailsList[1]; + } + const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); + const strategy = "BESU"; + bungee.addStrategy(strategy, new StrategyBesu("INFO")); + + const snapshot = await bungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view = bungee.generateView( + snapshot, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + //expect to return a view + expect(view.view).toBeTruthy(); + expect(view.signature).toBeTruthy(); + + //changing BESU_ASSET_ID value + const lockAsset = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: [BESU_ASSET_ID], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockAsset).not.toBeUndefined(); + expect(lockAsset.success).toBeTrue(); + + //creating new asset + const new_asset_id = uuidv4(); + const depNew = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: [new_asset_id, 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(depNew).not.toBeUndefined(); + expect(depNew.success).toBeTrue(); + + const snapshot1 = await bungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view2 = bungee.generateView( + snapshot1, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + //expect to return a view + expect(view2.view).toBeTruthy(); + expect(view2.signature).toBeTruthy(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + bungeeServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 3000, + server: bungeeServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await bungee.getOrCreateWebServices(); + await bungee.registerWebServices(expressApp); + const bungeePath = `http://${address}:${port}`; + + const config = new Configuration({ basePath: bungeePath }); + const bungeeApi = new BungeeApi(config); + + const mergeViewsNoPolicyReq = await bungeeApi.mergeViewsV1({ + serializedViews: [ + JSON.stringify({ + view: JSON.stringify(view.view as View), + signature: view.signature, + }), + // eslint-disable-next-line prettier/prettier + JSON.stringify({ + view: JSON.stringify(view2.view as View), + signature: view2.signature, + }), + ], + mergePolicy: MergePolicyOpts.NONE, + }); + expect(mergeViewsNoPolicyReq.status).toBe(200); + + expect(mergeViewsNoPolicyReq.data.integratedView).toBeTruthy(); + expect(mergeViewsNoPolicyReq.data.signature).toBeTruthy(); + + const mergeViewsNoPolicy = bungee.mergeViews( + [view.view as View, view2.view as View], + [view.signature as string, view2.signature as string], + MergePolicyOpts.NONE, + [], + ); + //1 transaction captured in first view, and 3 in the second + expect(mergeViewsNoPolicy.integratedView.getAllTransactions().length).toBe( + 4, + ); + //1 state captured in first view, and 2 in the second + expect(mergeViewsNoPolicy.integratedView.getAllStates().length).toBe(3); + + const transactionReceipts: string[] = []; + + mergeViewsNoPolicy.integratedView.getAllTransactions().forEach((t) => { + transactionReceipts.push(JSON.stringify(t.getProof())); + }); + expect( + ( + await bungeeApi.verifyMerkleRoot({ + input: transactionReceipts, + root: mergeViewsNoPolicy.integratedView.getIntegratedViewProof() + .transactionsMerkleRoot, + }) + ).data.result, + ).toBeTrue(); + + const mergeViewsWithPolicy = bungee.mergeViews( + [view.view as View, view2.view as View], + [view.signature as string, view2.signature as string], + MergePolicyOpts.PruneState, + [BESU_ASSET_ID], //should remove all states related to this asset + ); + + //0 transactions captured in first view, and 1 in the second (because of policy) + // eslint-disable-next-line prettier/prettier + expect( + mergeViewsWithPolicy.integratedView.getAllTransactions().length, + ).toBe(1); + //0 state captured in first view, and 1 in the second (because of policy) + expect(mergeViewsWithPolicy.integratedView.getAllStates().length).toBe(1); + + const mergeViewsWithPolicy2 = bungee.mergeViews( + [view.view as View, view2.view as View], + [view.signature as string, view2.signature as string], + MergePolicyOpts.PruneStateFromView, + [BESU_ASSET_ID, view2.view?.getKey() as string], //should remove all states related to this asset + ); -afterAll(async () => { + //1 transactions captured in first view, and 1 in the second (because of policy) + // eslint-disable-next-line prettier/prettier + expect( + mergeViewsWithPolicy2.integratedView.getAllTransactions().length, + ).toBe(2); + //1 state captured in first view, and only 1 in the second (because of policy) + expect(mergeViewsWithPolicy2.integratedView.getAllStates().length).toBe(2); + }, +); + +afterEach(async () => { await Servers.shutdown(besuServer); await Servers.shutdown(bungeeServer); await besuLedger.stop(); diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-process-views.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-process-views.test.ts index 2daeba4518..9902ede8b8 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-process-views.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/bungee-process-views.test.ts @@ -78,7 +78,9 @@ let bungeeServer: Server; let keychainPlugin: PluginKeychainMemory; -beforeAll(async () => { +let networkDetailsList: BesuNetworkDetails[]; + +beforeEach(async () => { pruneDockerAllIfGithubAction({ logLevel }) .then(() => { log.info("Pruning throw OK"); @@ -275,82 +277,108 @@ beforeAll(async () => { .contractAddress as string; pluginBungeeHermesOptions = { + pluginRegistry, keyPair: Secp256k1Keys.generateKeyPairsBuffer(), instanceId: uuidv4(), logLevel, }; } + networkDetailsList = [ + { + signingCredential: bungeeSigningCredential, + contractName, + connectorApiPath: besuPath, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + { + signingCredential: bungeeSigningCredential, + contractName, + connector: connector, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: firstHighNetWorthAccount, + } as BesuNetworkDetails, + ]; }); -test("test merging views, and integrated view proofs", async () => { - const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); - const strategy = "BESU"; - bungee.addStrategy(strategy, new StrategyBesu("INFO")); - const networkDetails: BesuNetworkDetails = { - signingCredential: bungeeSigningCredential, - contractName, - connectorApiPath: besuPath, - keychainId: bungeeKeychainId, - contractAddress: bungeeContractAddress, - participant: firstHighNetWorthAccount, - }; - - const snapshot = await bungee.generateSnapshot([], strategy, networkDetails); - const view = bungee.generateView( - snapshot, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view.view).toBeTruthy(); - expect(view.signature).toBeTruthy(); - - const expressApp = express(); - expressApp.use(bodyParser.json({ limit: "250mb" })); - bungeeServer = http.createServer(expressApp); - const listenOptions: IListenOptions = { - hostname: "127.0.0.1", - port: 3000, - server: bungeeServer, - }; - const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; - const { address, port } = addressInfo; - - await bungee.getOrCreateWebServices(); - await bungee.registerWebServices(expressApp); - const bungeePath = `http://${address}:${port}`; - - const config = new Configuration({ basePath: bungeePath }); - const bungeeApi = new BungeeApi(config); - - const processed = await bungeeApi.processViewV1({ - serializedView: JSON.stringify({ - view: JSON.stringify(view.view as View), - signature: view.signature, - }), - policyId: PrivacyPolicyOpts.PruneState, - policyArguments: [BESU_ASSET_ID], - }); - - expect(processed.status).toBe(200); - expect(processed.data.view).toBeTruthy(); - expect(processed.data.signature).toBeTruthy(); - - const processedView = deserializeView(JSON.stringify(processed.data)); - - //check view deserializer - expect(JSON.stringify(processedView)).toEqual(processed.data.view); - - expect(processedView.getPolicy()).toBeTruthy(); - expect(processedView.getOldVersionsMetadata().length).toBe(1); - expect(processedView.getOldVersionsMetadata()[0].signature).toBe( - view.signature, - ); - expect(processedView.getAllTransactions().length).toBe(1); -}); +test.each([{ apiPath: true }, { apiPath: false }])( + //test for both BesuApiPath and BesuConnector + "test merging views, and integrated view proofs", + async ({ apiPath }) => { + let networkDetails: BesuNetworkDetails; + if (apiPath) { + networkDetails = networkDetailsList[0]; + } else { + networkDetails = networkDetailsList[1]; + } + + const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); + const strategy = "BESU"; + bungee.addStrategy(strategy, new StrategyBesu("INFO")); + + const snapshot = await bungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view = bungee.generateView( + snapshot, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + //expect to return a view + expect(view.view).toBeTruthy(); + expect(view.signature).toBeTruthy(); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + bungeeServer = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 3000, + server: bungeeServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + + await bungee.getOrCreateWebServices(); + await bungee.registerWebServices(expressApp); + const bungeePath = `http://${address}:${port}`; + + const config = new Configuration({ basePath: bungeePath }); + const bungeeApi = new BungeeApi(config); + + const processed = await bungeeApi.processViewV1({ + serializedView: JSON.stringify({ + view: JSON.stringify(view.view as View), + signature: view.signature, + }), + policyId: PrivacyPolicyOpts.PruneState, + policyArguments: [BESU_ASSET_ID], + }); + + expect(processed.status).toBe(200); + expect(processed.data.view).toBeTruthy(); + expect(processed.data.signature).toBeTruthy(); + + const processedView = deserializeView(JSON.stringify(processed.data)); + + //check view deserializer + expect(JSON.stringify(processedView)).toEqual(processed.data.view); + + expect(processedView.getPolicy()).toBeTruthy(); + expect(processedView.getOldVersionsMetadata().length).toBe(1); + expect(processedView.getOldVersionsMetadata()[0].signature).toBe( + view.signature, + ); + expect(processedView.getAllTransactions().length).toBe(1); + }, +); -afterAll(async () => { +afterEach(async () => { await Servers.shutdown(besuServer); await Servers.shutdown(bungeeServer); await besuLedger.stop(); diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/ethereum-test-basic.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/ethereum-test-basic.test.ts index f4a6ec2987..12a504aba5 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/ethereum-test-basic.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/ethereum-test-basic.test.ts @@ -14,9 +14,9 @@ const testLogLevel: LogLevelDesc = "info"; import "jest-extended"; import express from "express"; import bodyParser from "body-parser"; -import http from "http"; import { v4 as uuidV4 } from "uuid"; import { AddressInfo } from "net"; +import http, { Server } from "http"; import { Server as SocketIoServer } from "socket.io"; import Web3 from "web3"; @@ -88,23 +88,16 @@ describe("Ethereum contract deploy and invoke using keychain", () => { let bungeeContractAddress: string; let pluginBungeeHermesOptions: IPluginBungeeHermesOptions; const ETH_ASSET_NAME = uuidV4(); - const expressApp = express(); - expressApp.use(bodyParser.json({ limit: "250mb" })); - const server = http.createServer(expressApp); - // set to address Type Error returned by Response.json() - // "Can't serialize BigInt" - expressApp.set("json replacer", stringifyBigIntReplacer); - - const wsApi = new SocketIoServer(server, { - path: Constants.SocketIoConnectionPathV1, - }); + let server: Server; ////////////////////////////////// // Setup ////////////////////////////////// - beforeAll(async () => { + let networkDetailsList: EthereumNetworkDetails[]; + + beforeEach(async () => { pruneDockerAllIfGithubAction({ logLevel: testLogLevel }) .then(() => { log.info("Pruning throw OK"); @@ -120,6 +113,15 @@ describe("Ethereum contract deploy and invoke using keychain", () => { }); await ledger.start(); + const expressApp = express(); + + expressApp.use(bodyParser.json({ limit: "250mb" })); + // set to address Type Error returned by Response.json() + // "Can't serialize BigInt" + expressApp.set("json replacer", stringifyBigIntReplacer); + + server = http.createServer(expressApp); + const listenOptions: IListenOptions = { hostname: "127.0.0.1", port: 5000, @@ -148,16 +150,21 @@ describe("Ethereum contract deploy and invoke using keychain", () => { LockAssetContractJson.contractName, JSON.stringify(LockAssetContractJson), ); + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); connector = new PluginLedgerConnectorEthereum({ instanceId: uuidV4(), rpcApiHttpHost, logLevel: testLogLevel, - pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + pluginRegistry, }); // Instantiate connector with the keychain plugin that already has the // private key we want to use for one of our tests await connector.getOrCreateWebServices(); + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + await connector.registerWebServices(expressApp, wsApi); const initTransferValue = web3.utils.toWei("5000", "ether"); @@ -228,134 +235,170 @@ describe("Ethereum contract deploy and invoke using keychain", () => { .contractAddress as string; pluginBungeeHermesOptions = { + pluginRegistry, keyPair: Secp256k1Keys.generateKeyPairsBuffer(), instanceId: uuidV4(), logLevel: testLogLevel, }; + networkDetailsList = [ + { + signingCredential: bungeeSigningCredential, + contractName: LockAssetContractJson.contractName, + connectorApiPath: apiHost, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: WHALE_ACCOUNT_ADDRESS, + } as EthereumNetworkDetails, + { + signingCredential: bungeeSigningCredential, + contractName: LockAssetContractJson.contractName, + connector: connector, + keychainId: bungeeKeychainId, + contractAddress: bungeeContractAddress, + participant: WHALE_ACCOUNT_ADDRESS, + } as EthereumNetworkDetails, + ]; }); - afterAll(async () => { + afterEach(async () => { + await Servers.shutdown(server); await ledger.stop(); await ledger.destroy(); - await Servers.shutdown(server); - const pruning = pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); - await expect(pruning).resolves.toBeTruthy(); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel: testLogLevel }); + fail("Pruning didn't throw OK"); + }); }); - test("test creation of views for different timeframes and states", async () => { - const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); - const strategy = "ETH"; - bungee.addStrategy(strategy, new StrategyEthereum("INFO")); - const networkDetails: EthereumNetworkDetails = { - signingCredential: bungeeSigningCredential, - contractName: LockAssetContractJson.contractName, - connectorApiPath: apiHost, - keychainId: bungeeKeychainId, - contractAddress: bungeeContractAddress, - participant: WHALE_ACCOUNT_ADDRESS, - }; - - const snapshot = await bungee.generateSnapshot( - [], - strategy, - networkDetails, - ); - const view = bungee.generateView( - snapshot, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - - //expect to return a view - expect(view.view).toBeTruthy(); - expect(view.signature).toBeTruthy(); - - //expect the view to have capture the new asset ETH_ASSET_NAME, and attributes to match - expect(snapshot.getStateBins().length).toEqual(1); - expect(snapshot.getStateBins()[0].getId()).toEqual(ETH_ASSET_NAME); - expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); - - const view1 = bungee.generateView(snapshot, "0", "9999", undefined); - - //expects nothing to limit time of 9999 - expect(view1.view).toBeUndefined(); - expect(view1.signature).toBeUndefined(); - - //changing ETH_ASSET_NAME value - const lockAsset = await apiClient.invokeContractV1({ - contract: { - contractName: LockAssetContractJson.contractName, - keychainId: keychainPlugin.getKeychainId(), - }, - invocationType: EthContractInvocationType.Send, - methodName: "lockAsset", - params: [ETH_ASSET_NAME], - web3SigningCredential: { - ethAccount: WHALE_ACCOUNT_ADDRESS, - secret: "", - type: Web3SigningCredentialType.GethKeychainPassword, - }, - }); - expect(lockAsset).not.toBeUndefined(); - expect(lockAsset.status).toBe(200); + test.each([{ apiPath: true }, { apiPath: false }])( + //test for both EthereumApiPath and EthereumConnector + "test creation of views for different timeframes and states", + async ({ apiPath }) => { + if (!apiPath) { + // set to address Type Error returned by Response.json() when using the connector by it self + // "Can't serialize BigInt" + const originalStringify = JSON.stringify; + const mock = jest.spyOn(JSON, "stringify"); + mock.mockImplementation((value: any) => { + return originalStringify(value, stringifyBigIntReplacer); + }); + } + + let networkDetails: EthereumNetworkDetails; + if (apiPath) { + networkDetails = networkDetailsList[0]; + } else { + networkDetails = networkDetailsList[1]; + } + const bungee = new PluginBungeeHermes(pluginBungeeHermesOptions); + const strategy = "ETH"; + bungee.addStrategy(strategy, new StrategyEthereum("INFO")); + const snapshot = await bungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view = bungee.generateView( + snapshot, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); - //changing ETH_ASSET_NAME value - const new_asset_id = uuidV4(); - const depNew = await apiClient.invokeContractV1({ - contract: { - contractName: LockAssetContractJson.contractName, - keychainId: keychainPlugin.getKeychainId(), - }, - invocationType: EthContractInvocationType.Send, - methodName: "createAsset", - params: [new_asset_id, 10], - web3SigningCredential: { - ethAccount: WHALE_ACCOUNT_ADDRESS, - secret: "", - type: Web3SigningCredentialType.GethKeychainPassword, - }, - }); - expect(depNew).not.toBeUndefined(); - expect(depNew.status).toBe(200); + //expect to return a view + expect(view.view).toBeTruthy(); + expect(view.signature).toBeTruthy(); + + //expect the view to have capture the new asset ETH_ASSET_NAME, and attributes to match + expect(snapshot.getStateBins().length).toEqual(1); + expect(snapshot.getStateBins()[0].getId()).toEqual(ETH_ASSET_NAME); + expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); + + const view1 = bungee.generateView(snapshot, "0", "9999", undefined); + + //expects nothing to limit time of 9999 + expect(view1.view).toBeUndefined(); + expect(view1.signature).toBeUndefined(); + + //changing ETH_ASSET_NAME value + const lockAsset = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: [ETH_ASSET_NAME], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockAsset).not.toBeUndefined(); + expect(lockAsset.status).toBe(200); + + //changing ETH_ASSET_NAME value + const new_asset_id = uuidV4(); + const depNew = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: [new_asset_id, 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(depNew).not.toBeUndefined(); + expect(depNew.status).toBe(200); - const snapshot1 = await bungee.generateSnapshot( - [], - strategy, - networkDetails, - ); - const view2 = bungee.generateView( - snapshot1, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view2.view).toBeTruthy(); - expect(view2.signature).toBeTruthy(); - - const stateBins = snapshot1.getStateBins(); - expect(stateBins.length).toEqual(2); //expect to have captured state for both assets - - const bins = [stateBins[0].getId(), stateBins[1].getId()]; - - //checks if values match: - // - new value of ETH_ASSET_NAME state in new snapshot different than value from old snapshot) - // - successfully captured transaction that created the new asset - if (bins[0] === ETH_ASSET_NAME) { - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); - expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( - snapshot.getStateBins()[0].getValue(), + const snapshot1 = await bungee.generateSnapshot( + [], + strategy, + networkDetails, ); - expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(1); - } else { - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); - expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(2); - expect(snapshot1.getStateBins()[1].getValue()).not.toEqual( - snapshot.getStateBins()[0].getValue(), + const view2 = bungee.generateView( + snapshot1, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, ); - } - }); + //expect to return a view + expect(view2.view).toBeTruthy(); + expect(view2.signature).toBeTruthy(); + + const stateBins = snapshot1.getStateBins(); + expect(stateBins.length).toEqual(2); //expect to have captured state for both assets + + const bins = [stateBins[0].getId(), stateBins[1].getId()]; + + //checks if values match: + // - new value of ETH_ASSET_NAME state in new snapshot different than value from old snapshot) + // - successfully captured transaction that created the new asset + if (bins[0] === ETH_ASSET_NAME) { + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); + expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( + snapshot.getStateBins()[0].getValue(), + ); + expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(1); + } else { + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); + expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(2); + expect(snapshot1.getStateBins()[1].getValue()).not.toEqual( + snapshot.getStateBins()[0].getValue(), + ); + } + }, + ); }); function stringifyBigIntReplacer(key: string, value: bigint): string { diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-basic.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-basic.test.ts index 08c49cbf6a..43cc97dd51 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-basic.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-basic.test.ts @@ -68,14 +68,14 @@ let pluginBungeeFabricOptions: IPluginBungeeHermesOptions; let pluginBungee: PluginBungeeHermes; const FABRIC_ASSET_ID = uuidv4(); -let networkDetails: FabricNetworkDetails; +let networkDetailsList: FabricNetworkDetails[]; const log = LoggerProvider.getOrCreate({ level: logLevel, label: "BUNGEE - Hermes", }); -beforeAll(async () => { +beforeEach(async () => { pruneDockerAllIfGithubAction({ logLevel }) .then(() => { log.info("Pruning throw OK"); @@ -329,124 +329,148 @@ beforeAll(async () => { ); pluginBungeeFabricOptions = { + pluginRegistry, keyPair: Secp256k1Keys.generateKeyPairsBuffer(), instanceId: uuidv4(), }; - networkDetails = { - connectorApiPath: fabricPath, - signingCredential: fabricSigningCredential, - channelName: fabricChannelName, - contractName: fabricContractName, - participant: "Org1MSP", - }; + networkDetailsList = [ + { + connectorApiPath: fabricPath, + signingCredential: fabricSigningCredential, + channelName: fabricChannelName, + contractName: fabricContractName, + participant: "Org1MSP", + }, + { + connector: fabricConnector, + signingCredential: fabricSigningCredential, + channelName: fabricChannelName, + contractName: fabricContractName, + participant: "Org1MSP", + }, + ]; pluginBungee = new PluginBungeeHermes(pluginBungeeFabricOptions); } }); -test("test creation of views for different timeframes and states", async () => { - const strategy = "FABRIC"; - pluginBungee.addStrategy(strategy, new StrategyFabric("INFO")); - - const snapshot = await pluginBungee.generateSnapshot( - [], - strategy, - networkDetails, - ); - const view = pluginBungee.generateView( - snapshot, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - - //expect to return a view - expect(view.view).toBeTruthy(); - expect(view.signature).toBeTruthy(); - - //expect the view to have capture the new asset Fabric_ASSET_ID, and attributes to match - expect(snapshot.getStateBins().length).toEqual(1); - expect(snapshot.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); - expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); - - //fabric transaction proofs include endorsements - expect( - snapshot.getStateBins()[0].getTransactions()[0].getProof().getEndorsements() - ?.length, - ).toEqual(2); - - //no valid states for this time frame - const view1 = pluginBungee.generateView(snapshot, "0", "9999", undefined); - expect(view1.view).toBeUndefined(); - expect(view1.signature).toBeUndefined(); - - //creating new asset - const new_asset_id = uuidv4(); - const createResponse = await apiClient.runTransactionV1({ - contractName: fabricContractName, - channelName: fabricChannelName, - params: [new_asset_id, "10"], - methodName: "CreateAsset", - invocationType: FabricContractInvocationType.Send, - signingCredential: fabricSigningCredential, - }); - expect(createResponse).not.toBeUndefined(); - expect(createResponse.status).toBeGreaterThan(199); - expect(createResponse.status).toBeLessThan(300); - - //changing FABRIC_ASSET_ID value - const modifyResponse = await apiClient.runTransactionV1({ - contractName: fabricContractName, - channelName: fabricChannelName, - params: [FABRIC_ASSET_ID, "18"], - methodName: "UpdateAsset", - invocationType: FabricContractInvocationType.Send, - signingCredential: fabricSigningCredential, - }); - expect(modifyResponse).not.toBeUndefined(); - expect(modifyResponse.status).toBeGreaterThan(199); - expect(modifyResponse.status).toBeLessThan(300); - - const snapshot1 = await pluginBungee.generateSnapshot( - [], - strategy, - networkDetails, - ); - const view2 = pluginBungee.generateView( - snapshot1, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - - //expect to return a view - expect(view2.view).toBeTruthy(); - expect(view2.signature).toBeTruthy(); - - //expect to have captured state for both assets - const stateBins = snapshot1.getStateBins(); - expect(stateBins.length).toEqual(2); - const bins = [stateBins[0].getId(), stateBins[1].getId()]; - - expect(bins.includes(FABRIC_ASSET_ID)).toBeTrue(); - expect(bins.includes(new_asset_id)).toBeTrue(); - - //checks if values match: - // - new value of FABRIC_ASSET_ID state in new snapshot equals to new value) - // - successfully captured transaction that created the new asset - if (bins[0] === FABRIC_ASSET_ID) { - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); - expect(snapshot1.getStateBins()[0].getValue()).toEqual("18"); - expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(1); - } else { - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); - expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(2); - expect(snapshot1.getStateBins()[1].getValue()).toEqual("18"); - } -}); +test.each([{ apiPath: true }, { apiPath: false }])( + //test for both FabricApiPath and FabricConnector + "test creation of views for different timeframes and states", + async ({ apiPath }) => { + let networkDetails: FabricNetworkDetails; + if (apiPath) { + networkDetails = networkDetailsList[0]; + } else { + networkDetails = networkDetailsList[1]; + } + + const strategy = "FABRIC"; + pluginBungee.addStrategy(strategy, new StrategyFabric("INFO")); + + const snapshot = await pluginBungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view = pluginBungee.generateView( + snapshot, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + + //expect to return a view + expect(view.view).toBeTruthy(); + expect(view.signature).toBeTruthy(); + + //expect the view to have capture the new asset Fabric_ASSET_ID, and attributes to match + expect(snapshot.getStateBins().length).toEqual(1); + expect(snapshot.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); + expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); + + //fabric transaction proofs include endorsements + expect( + snapshot + .getStateBins()[0] + .getTransactions()[0] + .getProof() + .getEndorsements()?.length, + ).toEqual(2); + + //no valid states for this time frame + const view1 = pluginBungee.generateView(snapshot, "0", "9999", undefined); + expect(view1.view).toBeUndefined(); + expect(view1.signature).toBeUndefined(); + + //creating new asset + const new_asset_id = uuidv4(); + const createResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [new_asset_id, "10"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResponse).not.toBeUndefined(); + expect(createResponse.status).toBeGreaterThan(199); + expect(createResponse.status).toBeLessThan(300); + + //changing FABRIC_ASSET_ID value + const modifyResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [FABRIC_ASSET_ID, "18"], + methodName: "UpdateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(modifyResponse).not.toBeUndefined(); + expect(modifyResponse.status).toBeGreaterThan(199); + expect(modifyResponse.status).toBeLessThan(300); + + const snapshot1 = await pluginBungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view2 = pluginBungee.generateView( + snapshot1, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + + //expect to return a view + expect(view2.view).toBeTruthy(); + expect(view2.signature).toBeTruthy(); + + //expect to have captured state for both assets + const stateBins = snapshot1.getStateBins(); + expect(stateBins.length).toEqual(2); + const bins = [stateBins[0].getId(), stateBins[1].getId()]; + + expect(bins.includes(FABRIC_ASSET_ID)).toBeTrue(); + expect(bins.includes(new_asset_id)).toBeTrue(); + + //checks if values match: + // - new value of FABRIC_ASSET_ID state in new snapshot equals to new value) + // - successfully captured transaction that created the new asset + if (bins[0] === FABRIC_ASSET_ID) { + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); + expect(snapshot1.getStateBins()[0].getValue()).toEqual("18"); + expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(1); + } else { + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); + expect(snapshot1.getStateBins()[1].getTransactions().length).toEqual(2); + expect(snapshot1.getStateBins()[1].getValue()).toEqual("18"); + } + }, +); -afterAll(async () => { +afterEach(async () => { await fabricLedger.stop(); await fabricLedger.destroy(); await Servers.shutdown(fabricServer); diff --git a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-pruning.test.ts b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-pruning.test.ts index 0a810c00ff..2bc9783690 100644 --- a/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-pruning.test.ts +++ b/packages/cactus-plugin-bungee-hermes/src/test/typescript/integration/fabric-test-pruning.test.ts @@ -68,14 +68,14 @@ let pluginBungeeFabricOptions: IPluginBungeeHermesOptions; let pluginBungee: PluginBungeeHermes; const FABRIC_ASSET_ID = uuidv4(); -let networkDetails: FabricNetworkDetails; +let networkDetailsList: FabricNetworkDetails[]; const log = LoggerProvider.getOrCreate({ level: logLevel, label: "BUNGEE - Hermes", }); -beforeAll(async () => { +beforeEach(async () => { pruneDockerAllIfGithubAction({ logLevel }) .then(() => { log.info("Pruning throw OK"); @@ -329,103 +329,127 @@ beforeAll(async () => { ); pluginBungeeFabricOptions = { + pluginRegistry, keyPair: Secp256k1Keys.generateKeyPairsBuffer(), instanceId: uuidv4(), }; - networkDetails = { - connectorApiPath: fabricPath, - signingCredential: fabricSigningCredential, - channelName: fabricChannelName, - contractName: fabricContractName, - participant: "Org1MSP", - }; + networkDetailsList = [ + { + connectorApiPath: fabricPath, + signingCredential: fabricSigningCredential, + channelName: fabricChannelName, + contractName: fabricContractName, + participant: "Org1MSP", + }, + { + connector: fabricConnector, + signingCredential: fabricSigningCredential, + channelName: fabricChannelName, + contractName: fabricContractName, + participant: "Org1MSP", + }, + ]; pluginBungee = new PluginBungeeHermes(pluginBungeeFabricOptions); } }); -test("test creation of views for specific timeframes", async () => { - const strategy = "FABRIC"; - pluginBungee.addStrategy(strategy, new StrategyFabric("INFO")); - - const snapshot = await pluginBungee.generateSnapshot( - [], - strategy, - networkDetails, - ); - const view = pluginBungee.generateView( - snapshot, - "0", - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view.view).toBeTruthy(); - expect(view.signature).toBeTruthy(); - - //expect the view to have capture the new asset FABRIC_ASSET_ID, and attributes to match - expect(snapshot.getStateBins().length).toEqual(1); - expect(snapshot.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); - expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); - //fabric transaction proofs include endorsements - expect( - snapshot.getStateBins()[0].getTransactions()[0].getProof().getEndorsements() - ?.length, - ).toEqual(2); - - //changing FABRIC_ASSET_ID value - const modifyResponse = await apiClient.runTransactionV1({ - contractName: fabricContractName, - channelName: fabricChannelName, - params: [FABRIC_ASSET_ID, "18"], - methodName: "UpdateAsset", - invocationType: FabricContractInvocationType.Send, - signingCredential: fabricSigningCredential, - }); - expect(modifyResponse).not.toBeUndefined(); - expect(modifyResponse.status).toBeGreaterThan(199); - expect(modifyResponse.status).toBeLessThan(300); - - const snapshot1 = await pluginBungee.generateSnapshot( - [], - strategy, - networkDetails, - ); - - //tI is the time of the first transaction +1 - const tI = ( - BigInt(snapshot.getStateBins()[0].getTransactions()[0].getTimeStamp()) + - BigInt(1) - ).toString(); - - expect(snapshot1.getStateBins().length).toEqual(1); - expect(snapshot1.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); - expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( - snapshot.getStateBins()[0].getValue(), - ); - const view1 = pluginBungee.generateView( - snapshot1, - tI, - Number.MAX_SAFE_INTEGER.toString(), - undefined, - ); - //expect to return a view - expect(view1.view).toBeTruthy(); - expect(view1.signature).toBeTruthy(); - - expect(snapshot1.getStateBins().length).toEqual(1); - expect(snapshot1.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); - //expect the view to not include first transaction (made before tI) - expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); - //expect old and new snapshot state values to differ - expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( - snapshot.getStateBins()[0].getValue(), - ); -}); +test.each([{ apiPath: true }, { apiPath: false }])( + //test for both FabricApiPath and FabricConnector + "test creation of views for specific timeframes", + async ({ apiPath }) => { + let networkDetails: FabricNetworkDetails; + if (apiPath) { + networkDetails = networkDetailsList[0]; + } else { + networkDetails = networkDetailsList[1]; + } + + const strategy = "FABRIC"; + pluginBungee.addStrategy(strategy, new StrategyFabric("INFO")); + + const snapshot = await pluginBungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + const view = pluginBungee.generateView( + snapshot, + "0", + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + //expect to return a view + expect(view.view).toBeTruthy(); + expect(view.signature).toBeTruthy(); + + //expect the view to have capture the new asset FABRIC_ASSET_ID, and attributes to match + expect(snapshot.getStateBins().length).toEqual(1); + expect(snapshot.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); + expect(snapshot.getStateBins()[0].getTransactions().length).toEqual(1); + //fabric transaction proofs include endorsements + expect( + snapshot + .getStateBins()[0] + .getTransactions()[0] + .getProof() + .getEndorsements()?.length, + ).toEqual(2); + + //changing FABRIC_ASSET_ID value + const modifyResponse = await apiClient.runTransactionV1({ + contractName: fabricContractName, + channelName: fabricChannelName, + params: [FABRIC_ASSET_ID, "18"], + methodName: "UpdateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(modifyResponse).not.toBeUndefined(); + expect(modifyResponse.status).toBeGreaterThan(199); + expect(modifyResponse.status).toBeLessThan(300); + + const snapshot1 = await pluginBungee.generateSnapshot( + [], + strategy, + networkDetails, + ); + + //tI is the time of the first transaction +1 + const tI = ( + BigInt(snapshot.getStateBins()[0].getTransactions()[0].getTimeStamp()) + + BigInt(1) + ).toString(); + + expect(snapshot1.getStateBins().length).toEqual(1); + expect(snapshot1.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(2); + expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( + snapshot.getStateBins()[0].getValue(), + ); + const view1 = pluginBungee.generateView( + snapshot1, + tI, + Number.MAX_SAFE_INTEGER.toString(), + undefined, + ); + //expect to return a view + expect(view1.view).toBeTruthy(); + expect(view1.signature).toBeTruthy(); + + expect(snapshot1.getStateBins().length).toEqual(1); + expect(snapshot1.getStateBins()[0].getId()).toEqual(FABRIC_ASSET_ID); + //expect the view to not include first transaction (made before tI) + expect(snapshot1.getStateBins()[0].getTransactions().length).toEqual(1); + //expect old and new snapshot state values to differ + expect(snapshot1.getStateBins()[0].getValue()).not.toEqual( + snapshot.getStateBins()[0].getValue(), + ); + }, +); -afterAll(async () => { +afterEach(async () => { await fabricLedger.stop(); await fabricLedger.destroy(); await Servers.shutdown(fabricServer); diff --git a/yarn.lock b/yarn.lock index ef7c6a3f17..3051f96449 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9931,6 +9931,7 @@ __metadata: express: "npm:4.19.2" fabric-network: "npm:2.2.20" fs-extra: "npm:11.2.0" + http-errors-enhanced-cjs: "npm:2.0.1" key-encoder: "npm:2.0.3" merkletreejs: "npm:0.3.11" socket.io: "npm:4.6.2"