diff --git a/.gitignore b/.gitignore index 370fd77fb5..1f691faf22 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ site/ tools/docker/geth-testnet/data-geth1/ .history/ +.manual-geth-artillery-config.yaml \ No newline at end of file diff --git a/packages/cactus-common/src/main/typescript/error-utils.ts b/packages/cactus-common/src/main/typescript/error-utils.ts index 43e5ebf60a..7c705fad58 100644 --- a/packages/cactus-common/src/main/typescript/error-utils.ts +++ b/packages/cactus-common/src/main/typescript/error-utils.ts @@ -24,10 +24,6 @@ export class CodedError extends Error { * @returns Safe string representation of an error. */ export function safeStringifyException(error: unknown): string { - if (error instanceof Error) { - return sanitizeHtml(error.stack || error.message); - } - // Axios and possibly other lib errors produce nicer output with toJSON() method. // Use it if available if ( @@ -36,7 +32,11 @@ export function safeStringifyException(error: unknown): string { "toJSON" in error && typeof error.toJSON === "function" ) { - return sanitizeHtml(error.toJSON()); + return sanitizeHtml(safeStringify(error.toJSON())); + } + + if (error instanceof Error) { + return sanitizeHtml(error.stack || error.message); } return sanitizeHtml(safeStringify(error)); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/README.md b/packages/cactus-plugin-ledger-connector-ethereum/README.md index ed08b27370..bfe021dec7 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/README.md +++ b/packages/cactus-plugin-ledger-connector-ethereum/README.md @@ -157,6 +157,32 @@ To check that all has been installed correctly and that the pugin has no errors npx jest cactus-plugin-ledger-connector-ethereum ``` +### Stess test +- Use CLI for manual setup of test environment and geneartion of artillery config. +- `artillery` must be installed separately (we do not recommend running it if they are any known open vulnerabilities) + +#### Setup + +``` sh +# Start the test environment +node ./packages/cactus-plugin-ledger-connector-ethereum/dist/lib/test/typescript/benchmark/cli/run-benchmark-environment.js +# Wait until `> artillery run ./.manual-geth-artillery-config.yaml` is printed + +# Review artillery config - change scenarios weights or load configuration, adjust target if running on separate machine etc... +vim ./.manual-geth-artillery-config.yaml # config is created in cwd() when starting the environment + +# Run artillery +artillery run ./.manual-geth-artillery-config.yaml +``` + +#### Files +- `./src/test/typescript/benchmark/setup` + - `geth-benchmark-env.ts` contains helper file for setting up an environment used by both CLI and jest test. + - `geth-benchmark-config.yaml` template artillery configuration. You can modify test load and scenarios there. + - `artillery-helper-functions.js` request handlers used by artillery to correcty process some response codes. +- `./src/test/typescript/benchmark/cli` + - `run-benchmark-environment.ts` CLI for starting test environment and patching template artillery config + ### Building/running the container image locally In the Cactus project root say: diff --git a/packages/cactus-plugin-ledger-connector-ethereum/package.json b/packages/cactus-plugin-ledger-connector-ethereum/package.json index c3cb33242c..b719903630 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/package.json +++ b/packages/cactus-plugin-ledger-connector-ethereum/package.json @@ -54,6 +54,8 @@ "scripts": { "codegen": "run-p 'codegen:*'", "codegen:openapi": "npm run generate-sdk", + "build:dev:backend:postbuild": "npm run copy-artillery-config", + "copy-artillery-config": "cp -af ./src/test/typescript/benchmark/setup/geth-benchmark-config.yaml ./src/test/typescript/benchmark/setup/artillery-helper-functions.js ./dist/lib/test/typescript/benchmark/setup", "generate-sdk": "run-p 'generate-sdk:*'", "generate-sdk:typescript-axios": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/ --reserved-words-mappings protected=protected", "watch": "npm-watch", @@ -84,10 +86,13 @@ "@hyperledger/cactus-test-geth-ledger": "2.0.0-alpha.2", "@hyperledger/cactus-test-tooling": "2.0.0-alpha.2", "@types/express": "4.17.13", + "@types/js-yaml": "4.0.5", "@types/minimist": "1.2.2", "@types/sanitize-html": "2.6.2", "chalk": "4.1.2", - "socket.io": "4.5.4" + "js-yaml": "4.1.0", + "socket.io": "4.5.4", + "web3-eth-accounts": "4.0.6" }, "engines": { "node": ">=10", diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts index 698d61b5e0..1211a1da06 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts @@ -97,10 +97,27 @@ type RunContractDeploymentInput = { value?: string; }; +export type HttpProviderOptions = ConstructorParameters[1]; +export type WsProviderSocketOptions = ConstructorParameters< + typeof WebSocketProvider +>[1]; +export type WsProviderReconnectOptions = ConstructorParameters< + typeof WebSocketProvider +>[2]; + +const defaultWsProviderReconnectOptions: WsProviderReconnectOptions = { + delay: 500, + autoReconnect: true, + maxAttempts: 20, +}; + export interface IPluginLedgerConnectorEthereumOptions extends ICactusPluginOptions { - rpcApiHttpHost: string; + rpcApiHttpHost?: string; + rpcApiHttpOptions?: HttpProviderOptions; rpcApiWsHost?: string; + rpcApiWsSocketOptions?: WsProviderSocketOptions; + rpcApiWsReconnectOptions?: WsProviderReconnectOptions; logLevel?: LogLevelDesc; prometheusExporter?: PrometheusExporter; pluginRegistry: PluginRegistry; @@ -122,6 +139,7 @@ export class PluginLedgerConnectorEthereum private readonly instanceId: string; private readonly log: Logger; private readonly web3: Web3; + private readonly web3WatchBlock?: Web3; private endpoints: IWebServiceEndpoint[] | undefined; public static readonly CLASS_NAME = "PluginLedgerConnectorEthereum"; private watchBlocksSubscriptions: Map = @@ -131,24 +149,54 @@ export class PluginLedgerConnectorEthereum return PluginLedgerConnectorEthereum.CLASS_NAME; } - private getWeb3Provider() { - if (!this.options.rpcApiWsHost) { - return new HttpProvider(this.options.rpcApiHttpHost); + private createWeb3WsProvider() { + if (this.options.rpcApiWsHost) { + return new WebSocketProvider( + this.options.rpcApiWsHost, + this.options.rpcApiWsSocketOptions, + this.options.rpcApiWsReconnectOptions, + ); + } else { + throw new Error( + "Can't instantiate WebSocketProvider without a valid rpcApiWsHost!", + ); + } + } + + private createWeb3Provider() { + if (this.options.rpcApiHttpHost) { + this.log.debug( + "Using Web3 HttpProvider because rpcApiHttpHost was provided", + ); + return new HttpProvider( + this.options.rpcApiHttpHost, + this.options.rpcApiHttpOptions, + ); + } else if (this.options.rpcApiWsHost) { + this.log.debug( + "Using Web3 WebSocketProvider because rpcApiHttpHost is missing but rpcApiWsHost was provided", + ); + return this.createWeb3WsProvider(); + } else { + throw new Error( + "Missing web3js RPC Api host (either HTTP or WS is required)", + ); } - return new WebSocketProvider(this.options.rpcApiWsHost); } constructor(public readonly options: IPluginLedgerConnectorEthereumOptions) { const fnTag = `${this.className}#constructor()`; Checks.truthy(options, `${fnTag} arg options`); - Checks.truthy(options.rpcApiHttpHost, `${fnTag} options.rpcApiHttpHost`); Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); Checks.truthy(options.pluginRegistry, `${fnTag} options.pluginRegistry`); const level = this.options.logLevel || "INFO"; const label = this.className; this.log = LoggerProvider.getOrCreate({ level, label }); - this.web3 = new Web3(this.getWeb3Provider()); + this.web3 = new Web3(this.createWeb3Provider()); + if (this.options.rpcApiWsHost) { + this.web3WatchBlock = new Web3(this.createWeb3WsProvider()); + } this.instanceId = options.instanceId; this.pluginRegistry = options.pluginRegistry as PluginRegistry; @@ -160,6 +208,10 @@ export class PluginLedgerConnectorEthereum `${fnTag} options.prometheusExporter`, ); + if (!this.options.rpcApiWsReconnectOptions) { + this.options.rpcApiWsReconnectOptions = defaultWsProviderReconnectOptions; + } + this.prometheusExporter.startMetricsCollection(); } @@ -196,18 +248,9 @@ export class PluginLedgerConnectorEthereum } } - public async shutdown(): Promise { - this.log.info(`Shutting down ${this.className}...`); - - this.log.debug("Remove any remaining web3js subscriptions"); - for (const socketId of this.watchBlocksSubscriptions.keys()) { - this.log.debug(`${WatchBlocksV1.Unsubscribe} shutdown`); - await this.removeWatchBlocksSubscriptionForSocket(socketId); - } - + private async closeWeb3jsConnection(wsProvider?: WebSocketProvider) { try { - const wsProvider = this.web3.currentProvider as WebSocketProvider; - if (!wsProvider || !typeof wsProvider.SocketConnection === undefined) { + if (!wsProvider || typeof wsProvider.SocketConnection === "undefined") { this.log.debug("Non-WS provider found - finish"); return; } @@ -233,10 +276,27 @@ export class PluginLedgerConnectorEthereum // Disconnect the socket provider wsProvider.disconnect(); } catch (error) { - this.log.error("Error when disconnecting web3js WS provider!", error); + this.log.error("Error when disconnecting web3js provider!", error); } } + public async shutdown(): Promise { + this.log.info(`Shutting down ${this.className}...`); + + this.log.debug("Remove any remaining web3js subscriptions"); + for (const socketId of this.watchBlocksSubscriptions.keys()) { + this.log.debug(`${WatchBlocksV1.Unsubscribe} shutdown`); + await this.removeWatchBlocksSubscriptionForSocket(socketId); + } + + await this.closeWeb3jsConnection( + this.web3.currentProvider as WebSocketProvider, + ); + await this.closeWeb3jsConnection( + this.web3WatchBlock?.currentProvider as WebSocketProvider, + ); + } + public async onPluginInit(): Promise { return; } @@ -245,38 +305,39 @@ export class PluginLedgerConnectorEthereum app: Express, wsApi: SocketIoServer, ): Promise { - const { web3 } = this; const { logLevel } = this.options; const webServices = await this.getOrCreateWebServices(); await Promise.all(webServices.map((ws) => ws.registerExpress(app))); - wsApi.on("connection", (socket: SocketIoSocket) => { - this.log.debug(`New Socket connected. ID=${socket.id}`); + if (this.web3WatchBlock) { + this.log.debug(`WebSocketProvider created for socketio endpoints`); + wsApi.on("connection", (socket: SocketIoSocket) => { + this.log.info(`New Socket connected. ID=${socket.id}`); - socket.on( - WatchBlocksV1.Subscribe, - async (options?: WatchBlocksV1Options) => { - const watchBlocksEndpoint = new WatchBlocksV1Endpoint({ - web3, + socket.on(WatchBlocksV1.Subscribe, (options?: WatchBlocksV1Options) => { + new WatchBlocksV1Endpoint({ + web3: this.web3WatchBlock as typeof this.web3WatchBlock, socket, logLevel, options, - }); - this.watchBlocksSubscriptions.set( - socket.id, - await watchBlocksEndpoint.subscribe(), - ); - - socket.on("disconnect", async (reason: string) => { - this.log.debug( - `${WatchBlocksV1.Unsubscribe} disconnect reason=%o`, - reason, - ); - await this.removeWatchBlocksSubscriptionForSocket(socket.id); - }); - }, + }).subscribe(); + }); + }); + } else { + this.log.info( + `WebSocketProvider was NOT created for socketio endpoints! Socket.IO will not be handled!`, ); - }); + wsApi.on("connection", (socket: SocketIoSocket) => { + this.log.info( + "Socket connected but no async endpoint is supported - disconnecting...", + ); + socket.emit( + WatchBlocksV1.Error, + "Missing rpcApiWsHost - can't listen for new blocks on HTTP provider", + ); + socket.disconnect(); + }); + } return webServices; } @@ -457,7 +518,7 @@ export class PluginLedgerConnectorEthereum } /** - * Invoke contract method using contract instance stored in a kechain plugin. + * Invoke contract method using contract instance stored in a keychain plugin. * * @param req contract method and transaction definition * @param contract contract keychain reference @@ -663,7 +724,7 @@ export class PluginLedgerConnectorEthereum * Throws if timeout expires. * * @param txHash sent transaction hash - * @param timeoutMs timeout in miliseconds + * @param timeoutMs timeout in milliseconds * @returns transaction receipt. */ public async pollForTxReceipt( @@ -795,6 +856,64 @@ export class PluginLedgerConnectorEthereum }); } + /** + * Convert connector transaction config to web3js transaction object. + * + * @param txConfig connector transaction config + * @returns web3js transaction + */ + private async getTransactionFromTxConfig( + txConfig: EthereumTransactionConfig, + ): Promise { + const tx: Transaction = { + from: txConfig.from, + to: txConfig.to, + value: txConfig.value, + nonce: txConfig.nonce, + data: txConfig.data, + }; + + // Apply gas config to the transaction + if (txConfig.gasConfig) { + if (isGasTransactionConfigLegacy(txConfig.gasConfig)) { + if (isGasTransactionConfigEIP1559(txConfig.gasConfig)) { + throw new RuntimeError( + `Detected mixed gasConfig! Use either legacy or EIP-1559 mode. gasConfig - ${JSON.stringify( + txConfig.gasConfig, + )}`, + ); + } + tx.maxPriorityFeePerGas = txConfig.gasConfig.gasPrice; + tx.maxFeePerGas = txConfig.gasConfig.gasPrice; + tx.gasLimit = txConfig.gasConfig.gas; + } else { + tx.maxPriorityFeePerGas = txConfig.gasConfig.maxPriorityFeePerGas; + tx.maxFeePerGas = txConfig.gasConfig.maxFeePerGas; + tx.gasLimit = txConfig.gasConfig.gasLimit; + } + } + + if (tx.maxPriorityFeePerGas && !tx.maxFeePerGas) { + tx.maxFeePerGas = await this.estimateMaxFeePerGas( + tx.maxPriorityFeePerGas.toString(), + ); + this.log.info( + `Estimated maxFeePerGas of ${tx.maxFeePerGas} because maxPriorityFeePerGas was provided.`, + ); + } + + // Fill missing gas fields (do it last) + if (!tx.gasLimit) { + const estimatedGas = await this.web3.eth.estimateGas(tx); + this.log.debug( + `Gas not specified in the transaction values, estimated ${estimatedGas.toString()}`, + ); + tx.gasLimit = estimatedGas; + } + + return tx; + } + //////////////////////////// // Contract Deployment //////////////////////////// @@ -1003,61 +1122,4 @@ export class PluginLedgerConnectorEthereum args.invocationParams, ); } - - /** - * Convert connector transaction config to web3js transaction object. - * @param txConfig connector transaction config - * @returns web3js transaction - */ - private async getTransactionFromTxConfig( - txConfig: EthereumTransactionConfig, - ): Promise { - const tx: Transaction = { - from: txConfig.from, - to: txConfig.to, - value: txConfig.value, - nonce: txConfig.nonce, - data: txConfig.data, - }; - - // Apply gas config to the transaction - if (txConfig.gasConfig) { - if (isGasTransactionConfigLegacy(txConfig.gasConfig)) { - if (isGasTransactionConfigEIP1559(txConfig.gasConfig)) { - throw new RuntimeError( - `Detected mixed gasConfig! Use either legacy or EIP-1559 mode. gasConfig - ${JSON.stringify( - txConfig.gasConfig, - )}`, - ); - } - tx.maxPriorityFeePerGas = txConfig.gasConfig.gasPrice; - tx.maxFeePerGas = txConfig.gasConfig.gasPrice; - tx.gasLimit = txConfig.gasConfig.gas; - } else { - tx.maxPriorityFeePerGas = txConfig.gasConfig.maxPriorityFeePerGas; - tx.maxFeePerGas = txConfig.gasConfig.maxFeePerGas; - tx.gasLimit = txConfig.gasConfig.gasLimit; - } - } - - if (tx.maxPriorityFeePerGas && !tx.maxFeePerGas) { - tx.maxFeePerGas = await this.estimateMaxFeePerGas( - tx.maxPriorityFeePerGas.toString(), - ); - this.log.info( - `Estimated maxFeePerGas of ${tx.maxFeePerGas} because maxPriorityFeePerGas was provided.`, - ); - } - - // Fill missing gas fields (do it last) - if (!tx.gasLimit) { - const estimatedGas = await this.web3.eth.estimateGas(tx); - this.log.debug( - `Gas not specified in the transaction values, estimated ${estimatedGas.toString()}`, - ); - tx.gasLimit = estimatedGas; - } - - return tx; - } } diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/types/model-type-guards.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/types/model-type-guards.ts index d2b24fec0c..0bab759826 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/types/model-type-guards.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/types/model-type-guards.ts @@ -1,3 +1,4 @@ +import { Web3Error } from "web3"; import { ContractJsonDefinition, ContractKeychainDefinition, @@ -94,3 +95,10 @@ export function isContractKeychainDefinition( typeof typedContract.keychainId !== "undefined" ); } + +export function isWeb3Error(error: unknown): error is Web3Error { + return ( + (error as Web3Error).name !== undefined && + (error as Web3Error).code !== undefined + ); +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts index af2cd074b5..b05396bdac 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts @@ -19,6 +19,8 @@ import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; import OAS from "../../json/openapi.json"; +import { ERR_INVALID_RESPONSE } from "web3"; +import { isWeb3Error } from "../public-api"; export interface IDeployContractSolidityBytecodeOptions { logLevel?: LogLevelDesc; @@ -92,6 +94,16 @@ export class DeployContractEndpoint implements IWebServiceEndpoint { .json(await this.options.connector.deployContract(req.body)); } catch (ex) { this.log.error(`Crash while serving ${reqTag}`, ex); + + // Return errors responses from ethereum node as user errors + if (isWeb3Error(ex) && ex.code === ERR_INVALID_RESPONSE) { + res.status(400).json({ + message: "Invalid Response Error", + error: safeStringifyException(ex), + }); + return; + } + res.status(500).json({ message: "Internal Server Error", error: safeStringifyException(ex), diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-v1-endpoint.ts index 5ffc284515..a98591e7d2 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-contract-v1-endpoint.ts @@ -18,6 +18,8 @@ import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; import OAS from "../../json/openapi.json"; +import { ERR_INVALID_RESPONSE } from "web3"; +import { isWeb3Error } from "../public-api"; export interface IInvokeContractEndpointOptions { logLevel?: LogLevelDesc; @@ -89,6 +91,16 @@ export class InvokeContractEndpoint implements IWebServiceEndpoint { res.json(await this.options.connector.invokeContract(req.body)); } catch (ex) { this.log.error(`Crash while serving ${reqTag}`, ex); + + // Return errors responses from ethereum node as user errors + if (isWeb3Error(ex) && ex.code === ERR_INVALID_RESPONSE) { + res.status(400).json({ + message: "Invalid Response Error", + error: safeStringifyException(ex), + }); + return; + } + res.status(500).json({ message: "Internal Server Error", error: safeStringifyException(ex), diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts index e4fdf66b28..11f5225f97 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-contract-v1-endpoint.ts @@ -16,6 +16,8 @@ import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; import OAS from "../../json/openapi.json"; import { InvokeRawWeb3EthContractV1Response } from "../generated/openapi/typescript-axios"; +import { ERR_INVALID_RESPONSE } from "web3"; +import { isWeb3Error } from "../public-api"; export interface IInvokeRawWeb3EthContractEndpointOptions { logLevel?: LogLevelDesc; @@ -96,6 +98,16 @@ export class InvokeRawWeb3EthContractEndpoint implements IWebServiceEndpoint { res.json(response); } catch (ex) { this.log.error(`Crash while serving ${reqTag}`, ex); + + // Return errors responses from ethereum node as user errors + if (isWeb3Error(ex) && ex.code === ERR_INVALID_RESPONSE) { + res.status(400).json({ + message: "Invalid Response Error", + error: safeStringifyException(ex), + }); + return; + } + res.status(500).json({ message: "Internal Server Error", error: safeStringifyException(ex), diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts index 6d9a144dd7..49e77093ad 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/invoke-raw-web3eth-method-v1-endpoint.ts @@ -15,7 +15,8 @@ import { import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; import OAS from "../../json/openapi.json"; -import { InvokeRawWeb3EthMethodV1Response } from "../public-api"; +import { InvokeRawWeb3EthMethodV1Response, isWeb3Error } from "../public-api"; +import { ERR_INVALID_RESPONSE } from "web3"; export interface IInvokeRawWeb3EthMethodEndpointOptions { logLevel?: LogLevelDesc; @@ -94,6 +95,16 @@ export class InvokeRawWeb3EthMethodEndpoint implements IWebServiceEndpoint { res.json(response); } catch (ex) { this.log.error(`Crash while serving ${reqTag}`, ex); + + // Return errors responses from ethereum node as user errors + if (isWeb3Error(ex) && ex.code === ERR_INVALID_RESPONSE) { + res.status(400).json({ + message: "Invalid Response Error", + error: safeStringifyException(ex), + }); + return; + } + res.status(500).json({ message: "Internal Server Error", error: safeStringifyException(ex), diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-v1-endpoint.ts index a5b461f4de..edd766d3b8 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/run-transaction-v1-endpoint.ts @@ -18,6 +18,8 @@ import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; import { PluginLedgerConnectorEthereum } from "../plugin-ledger-connector-ethereum"; import OAS from "../../json/openapi.json"; +import { ERR_INVALID_RESPONSE } from "web3"; +import { isWeb3Error } from "../public-api"; export interface IRunTransactionEndpointOptions { logLevel?: LogLevelDesc; @@ -89,6 +91,16 @@ export class RunTransactionEndpoint implements IWebServiceEndpoint { res.json(await this.options.connector.transact(req.body)); } catch (ex) { this.log.error(`Crash while serving ${reqTag}`, ex); + + // Return errors responses from ethereum node as user errors + if (isWeb3Error(ex) && ex.code === ERR_INVALID_RESPONSE) { + res.status(400).json({ + message: "Invalid Response Error", + error: safeStringifyException(ex), + }); + return; + } + res.status(500).json({ message: "Internal Server Error", error: safeStringifyException(ex), diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts index 4a82275af4..6bd1522b09 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts @@ -59,7 +59,7 @@ export class WatchBlocksV1Endpoint { public async subscribe(): Promise { const { socket, log, web3, isGetBlockData } = this; - log.debug(`${WatchBlocksV1.Subscribe} => ${socket.id}`); + log.info(`${WatchBlocksV1.Subscribe} => ${socket.id}`); const newBlocksSubscription = await web3.eth.subscribe( "newBlockHeaders", @@ -116,7 +116,7 @@ export class WatchBlocksV1Endpoint { log.debug("Subscribing to Web3 new block headers event..."); socket.on("disconnect", async (reason: string) => { - log.debug("WebSocket:disconnect reason=%o", reason); + log.info("WebSocket:disconnect reason=%o", reason); await newBlocksSubscription.unsubscribe(); }); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/cli/run-benchmark-environment.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/cli/run-benchmark-environment.ts new file mode 100644 index 0000000000..88c6ed9a49 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/cli/run-benchmark-environment.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * CLI for running the benchmark test environment manually. + * Can be used to run the environment and artillery on two separate machines / containers. + * The CLI will create matching artillery configuration in CWD of a caller. + * + * How to use: + * > node ./packages/cactus-plugin-ledger-connector-ethereum/dist/lib/test/typescript/benchmark/cli/run-benchmark-environment.js + */ + +import fs from "fs"; +import yaml from "js-yaml"; +import { + cleanupBenchmarkEnvironment, + getDefaultArtilleryConfigPath, + getDefaultArtilleryFunctionsPath, + setupBenchmarkEnvironment, +} from "../setup/geth-benchmark-env"; + +/** + * Function with main logic, will start the environment in current window and freeze until Ctrl + C or other method is used. + */ +async function run() { + // Start the environment + const envConfig = await setupBenchmarkEnvironment(); + + // Read, patch, and save artillery config for this environment + const artilleryConfig = yaml.load( + fs.readFileSync(getDefaultArtilleryConfigPath(), "utf-8"), + ) as any; + artilleryConfig.config.target = envConfig.target; + artilleryConfig.config.processor = getDefaultArtilleryFunctionsPath(); + artilleryConfig.config.variables = envConfig.variables; + const newConfig = yaml.dump(artilleryConfig); + fs.writeFileSync(".manual-geth-artillery-config.yaml", newConfig); + + console.log("Benchmark environment started..."); + console.log( + "To start the test run the following (in separate console window):", + ); + console.log("> artillery run ./.manual-geth-artillery-config.yaml"); +} + +/** + * Called on exit to cleanup the resources. + * @param exitCode process exit code + */ +async function finish(exitCode = 0) { + await cleanupBenchmarkEnvironment(); + console.log(`Done! (exit code ${exitCode})`); + process.exit(exitCode); +} + +// Cleanups +process.on("SIGTERM", async () => await finish()); +process.on("SIGINT", async () => await finish()); +process.on("uncaughtException", async (err) => { + console.error(`Uncaught error: ${err.message}`); + await finish(1); +}); + +// Main logic +run(); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/artillery-helper-functions.js b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/artillery-helper-functions.js new file mode 100644 index 0000000000..c2481892f1 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/artillery-helper-functions.js @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Helper functions used by directly by artillery framework + */ + +module.exports = { + checkInvokeContractWithPrivateKeyResponse: + checkInvokeContractWithPrivateKeyResponse, +}; + +/** + * Check response from invoke-contract endpoint when private key is used. + * It handles false-positive errors that occur due to frequent sending of transactions (not connectors fault), + * i.e. nonce to low or replacement transaction underpriced. + * + * Warning! + * Errors will be shown in report but will not fail the stress test (since they are not integrated with expect plugin) + */ +function checkInvokeContractWithPrivateKeyResponse( + requestParams, + response, + context, + ee, + next, +) { + const responseBody = JSON.parse(response.body); + if (response.statusCode === 200 && responseBody.success) { + return next(); + } + + if ( + response.statusCode === 400 && + (responseBody.error.includes("replacement transaction underpriced") || + responseBody.error.includes("nonce too low")) + ) { + return next(); + } + + console.error(`Wrong response [${response.statusCode}]: ${response.body}`); + return next(new Error(responseBody.error)); +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/geth-benchmark-config.yaml b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/geth-benchmark-config.yaml new file mode 100644 index 0000000000..5afb03b759 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/geth-benchmark-config.yaml @@ -0,0 +1,183 @@ +# Copyright 2023 Hyperledger Cactus Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Sample artillery config for stress testing ethereum connector +# You must override target and variables before running manually, e.g. (on linux): +# > artillery run geth-benchmark-config.yaml --target http://0.0.0.0:42237 --variables '{"hello_world_contract_name":["HelloWorld"],"keychain_id":["d2e2f1f8-949a-496f-ac16-ca32b6541b70"],"sender_account":["0x6a2ec8c50ba1a9ce47c52d1cb5b7136ee9d0ccc0"]} + +config: + target: http://0.0.0.0:80 + socketio: + path: /api/v1/async/socket-io/connect + http: + timeout: 300 + processor: "./artillery-helper-functions.js" + phases: + - duration: 60 + arrivalRate: 5 + rampTo: 10 + name: Sample load + plugins: + expect: + expectDefault200: false # TODO - status codes + reportFailuresAsErrors: true + variables: + hello_world_contract_name: + - HelloWorld + keychain_id: + - "0" + sender_account: + - "0x0" + sender_private_key: + - "0x0" +scenarios: + - name: contractCallSendWithPrivateKey + weight: 10 + flow: + - post: + url: >- + /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract + json: + contract: + contractName: "{{ hello_world_contract_name }}" + keychainId: "{{ keychain_id }}" + invocationType: CALL + methodName: getName + params: [] + gasConfig: + maxPriorityFeePerGas: 2000000000 + web3SigningCredential: + ethAccount: "{{ sender_account }}" + secret: "{{ sender_private_key }}" + type: PRIVATE_KEY_HEX + headers: + Content-Type: application/json + capture: + - json: $.success + as: success + expect: + - contentType: json + - hasProperty: success + - equals: + - "true" + - "{{ success }}" + - post: + url: >- + /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract + afterResponse: "checkInvokeContractWithPrivateKeyResponse" + json: + contract: + contractName: "{{ hello_world_contract_name }}" + keychainId: "{{ keychain_id }}" + invocationType: SEND + methodName: setName + params: + - "{{ $randomString() }}" + gasConfig: + maxPriorityFeePerGas: 2000000000 + web3SigningCredential: + ethAccount: "{{ sender_account }}" + secret: "{{ sender_private_key }}" + type: PRIVATE_KEY_HEX + headers: + Content-Type: application/json + expect: + - contentType: json + - statusCode: + - 200 + - 400 # Some errors are expected, other will be reported by afterResponse callback + - name: contractCallSendWithGethKeychain + weight: 3 + flow: + - post: + url: >- + /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract + json: + contract: + contractName: "{{ hello_world_contract_name }}" + keychainId: "{{ keychain_id }}" + invocationType: CALL + methodName: getName + params: [] + gasConfig: + maxPriorityFeePerGas: 2000000000 + web3SigningCredential: + ethAccount: "{{ sender_account }}" + secret: "" + type: GETH_KEYCHAIN_PASSWORD + headers: + Content-Type: application/json + capture: + - json: $.success + as: success + expect: + - contentType: json + - hasProperty: success + - equals: + - "true" + - "{{ success }}" + - post: + url: >- + /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/invoke-contract + afterResponse: "checkInvokeContractWithPrivateKeyResponse" + json: + contract: + contractName: "{{ hello_world_contract_name }}" + keychainId: "{{ keychain_id }}" + invocationType: SEND + methodName: setName + params: + - "{{ $randomString() }}" + gasConfig: + maxPriorityFeePerGas: 2000000000 + web3SigningCredential: + ethAccount: "{{ sender_account }}" + secret: "" + type: GETH_KEYCHAIN_PASSWORD + headers: + Content-Type: application/json + capture: + - json: $.success + as: success + expect: + - contentType: json + - hasProperty: success + - equals: + - "true" + - "{{ success }}" + - name: etherTransferWithGethKeychain + weight: 1 + flow: + - post: + url: >- + /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/run-transaction + json: + web3SigningCredential: + ethAccount: "{{ sender_account }}" + secret: "" + type: GETH_KEYCHAIN_PASSWORD + transactionConfig: + from: "{{ sender_account }}" + to: "0x000000000000000000000000000000000000dEaD" + value: 500 + headers: + Content-Type: application/json + capture: + - json: $.transactionReceipt + as: transactionReceipt + expect: + - contentType: json + - hasProperty: transactionReceipt + - name: getPrometheusMetrics + weight: 1 + flow: + - get: + url: >- + /api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/get-prometheus-exporter-metrics + - name: watchBlocksFor30Seconds + weight: 1 + engine: socketio + flow: + - emit: + channel: org.hyperledger.cacti.api.async.ethereum.WatchBlocksV1.Subscribe + - think: 30 diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/geth-benchmark-env.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/geth-benchmark-env.ts new file mode 100644 index 0000000000..2583387cdb --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/benchmark/setup/geth-benchmark-env.ts @@ -0,0 +1,202 @@ +/* + * Copyright 2023 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Helper functions for setting up test geth environment for benchmark testing with artillery. + */ + +// Constants +const logLevel = "info"; +const containerImageName = "cactus_geth_all_in_one"; +const containerImageVersion = "latest"; +const artilleryConfigFileName = "geth-benchmark-config.yaml"; +const artilleryFunctionsFileName = "artillery-helper-functions.js"; + +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import path from "path"; +import { AddressInfo } from "net"; +import { Server as SocketIoServer } from "socket.io"; +import { v4 as uuidv4 } from "uuid"; + +import { + LogLevelDesc, + Logger, + LoggerProvider, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Constants } from "@hyperledger/cactus-core-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; +import { + GethTestLedger, + WHALE_ACCOUNT_ADDRESS, + WHALE_ACCOUNT_PRIVATE_KEY, +} from "@hyperledger/cactus-test-geth-ledger"; + +import { + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, +} from "../../../../main/typescript/index"; +import HelloWorldContractJson from "../../../solidity/hello-world-contract/HelloWorld.json"; + +const log: Logger = LoggerProvider.getOrCreate({ + label: "geth-benchmark-env", + level: logLevel, +}); + +// Global variables +let ethereumTestLedger: GethTestLedger | undefined; +let ethereumConnector: PluginLedgerConnectorEthereum | undefined; +let httpServer: http.Server | undefined; +let wsServer: SocketIoServer | undefined; + +/** + * Overwrites for artillery test config + */ +export type BenchmarkEnvironmentConfig = { + target: string; + variables: Record; +}; + +export function getDefaultArtilleryConfigPath() { + return path.resolve(path.join(__dirname, artilleryConfigFileName)); +} + +export function getDefaultArtilleryFunctionsPath() { + return path.resolve(path.join(__dirname, artilleryFunctionsFileName)); +} + +/** + * Setup new test environment (ledger and connector). + * + * @param envLogLevel log level for test environment. + * @returns configuration overwrites for newly created environment + */ +export async function setupBenchmarkEnvironment( + envLogLevel: LogLevelDesc = logLevel, +): Promise { + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel }); + + log.info("Start GethTestLedger..."); + // log.debug("Ethereum version:", containerImageVersion); + ethereumTestLedger = new GethTestLedger({ + containerImageName, + containerImageVersion, + }); + await ethereumTestLedger.start(true, ["--verbosity", "1", "--cache", "8192"]); + + const rpcApiHttpHost = await ethereumTestLedger.getRpcApiHttpHost(); + log.debug("rpcApiHttpHost:", rpcApiHttpHost); + const rpcApiWsHost = await ethereumTestLedger.getRpcApiWebSocketHost(); + log.debug("rpcApiWsHost:", rpcApiWsHost); + + log.info("Create PluginKeychainMemory..."); + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + logLevel: envLogLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + + log.info("Create PluginLedgerConnectorEthereum..."); + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "500mb" })); + httpServer = http.createServer(expressApp); + wsServer = new SocketIoServer(httpServer, { + path: Constants.SocketIoConnectionPathV1, + }); + const addressInfo = (await Servers.listen({ + hostname: "0.0.0.0", + port: 0, + server: httpServer, + })) as AddressInfo; + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + + ethereumConnector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost, + rpcApiWsHost, + rpcApiWsSocketOptions: { + timeout: 1000 * 60 * 2, // 2 minutes + }, + logLevel: envLogLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + await ethereumConnector.getOrCreateWebServices(); + await ethereumConnector.registerWebServices(expressApp, wsServer); + + log.info("Deploy HelloWorld contract to interact with..."); + const deployOut = await ethereumConnector.deployContract({ + contract: { + contractName: HelloWorldContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + if (!deployOut.transactionReceipt.contractAddress) { + throw new Error(`Could not deploy test contract: ${deployOut}`); + } + + return { + target: apiHost, + variables: { + hello_world_contract_name: [HelloWorldContractJson.contractName], + keychain_id: [keychainPlugin.getKeychainId()], + sender_account: [WHALE_ACCOUNT_ADDRESS], + sender_private_key: [WHALE_ACCOUNT_PRIVATE_KEY], + }, + }; +} + +/** + * Cleanup test environment (stop the ledger, close the server) + */ +export async function cleanupBenchmarkEnvironment() { + log.info("cleanupBenchmarkEnvironment() started..."); + + if (ethereumConnector) { + log.debug("Stopping the ethereum connector..."); + await ethereumConnector.shutdown(); + ethereumConnector = undefined; + } + + if (httpServer) { + log.debug("Stopping the ethereum connector HTTP server..."); + await Servers.shutdown(httpServer); + httpServer = undefined; + } + + if (wsServer) { + log.debug("Stopping the ethereum connector WS server..."); + wsServer.removeAllListeners(); + wsServer.close(); + wsServer = undefined; + } + + if (ethereumTestLedger) { + try { + log.debug("Stopping the ethereum ledger..."); + await ethereumTestLedger.stop(); + await ethereumTestLedger.destroy(); + } catch (error) { + log.warn("Error when closing ethereum ledger:", error); + } finally { + ethereumTestLedger = undefined; + } + } + + await pruneDockerAllIfGithubAction({ logLevel }); + log.info("cleanupBenchmarkEnvironment() done!"); +} diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-contract-v1.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-contract-v1.test.ts index cb5cc2cb9f..a88bd4c136 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-contract-v1.test.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-contract-v1.test.ts @@ -134,9 +134,8 @@ describe("invokeRawWeb3EthContract Tests", () => { contractMethodArgs: [newName], }; - const resultsSend = await connector.invokeRawWeb3EthContract( - sendInvokeArgs, - ); + const resultsSend = + await connector.invokeRawWeb3EthContract(sendInvokeArgs); expect(resultsSend).toBeTruthy(); expect(resultsSend.status.toString()).toEqual("1"); @@ -148,9 +147,8 @@ describe("invokeRawWeb3EthContract Tests", () => { contractMethod: "getName", }; - const resultsCall = await connector.invokeRawWeb3EthContract( - callInvokeArgs, - ); + const resultsCall = + await connector.invokeRawWeb3EthContract(callInvokeArgs); expect(resultsCall).toBeTruthy(); expect(resultsCall).toEqual(newName); }); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts index 46b04dd374..4f28cbbb96 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/unit/model-type-guards.test.ts @@ -5,6 +5,7 @@ import { isDeployedContractJsonDefinition, isGasTransactionConfigEIP1559, isGasTransactionConfigLegacy, + isWeb3Error, isWeb3SigningCredentialGethKeychainPassword, isWeb3SigningCredentialNone, isWeb3SigningCredentialPrivateKeyHex, @@ -180,4 +181,30 @@ describe("Type guards for OpenAPI spec model type definitions", () => { ).toBe(false); expect(isContractKeychainDefinition({})).toBe(false); }); + + test("isWeb3Error()", () => { + expect( + isWeb3Error({ + name: "Test", + code: 123, + }), + ).toBe(true); + + expect( + isWeb3Error({ + name: "Test", + }), + ).toBe(false); + expect( + isWeb3Error({ + code: 123, + }), + ).toBe(false); + expect( + isWeb3Error({ + foo: "bar", + }), + ).toBe(false); + expect(isWeb3Error({})).toBe(false); + }); }); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json b/packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json index 85fe5ef910..bc70f24a84 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json +++ b/packages/cactus-plugin-ledger-connector-ethereum/tsconfig.json @@ -8,10 +8,7 @@ "rootDir": "./src", "tsBuildInfoFile": "../../.build-cache/cactus-plugin-ledger-connector-ethereum.tsbuildinfo" }, - "include": [ - "./src", - "src/**/*.json" - ], + "include": ["./src", "src/**/*.json"], "references": [ { "path": "../cactus-common/tsconfig.json" @@ -32,4 +29,4 @@ "path": "../cactus-test-geth-ledger/tsconfig.json" } ] -} \ No newline at end of file +} diff --git a/packages/cactus-test-geth-ledger/src/main/typescript/geth-test-ledger.ts b/packages/cactus-test-geth-ledger/src/main/typescript/geth-test-ledger.ts index de3c277770..dbb5e87e70 100644 --- a/packages/cactus-test-geth-ledger/src/main/typescript/geth-test-ledger.ts +++ b/packages/cactus-test-geth-ledger/src/main/typescript/geth-test-ledger.ts @@ -130,7 +130,7 @@ export class GethTestLedger { * @param omitPull Don't pull docker image from upstream if true. * @returns Promise */ - public async start(omitPull = false): Promise { + public async start(omitPull = false, cmd: string[] = []): Promise { if (this.useRunningLedger) { this.log.info( "Search for already running Geth Test Ledger because 'useRunningLedger' flag is enabled.", @@ -168,7 +168,7 @@ export class GethTestLedger { const docker = new Docker(); const eventEmitter: EventEmitter = docker.run( this.fullContainerImageName, - [], + cmd, [], { ExposedPorts: { diff --git a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts index 3ae0613198..37cfbf3170 100644 --- a/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts +++ b/packages/cactus-test-plugin-ledger-connector-ethereum/src/test/typescript/integration/api-client/verifier-integration-with-ethereum-connector.test.ts @@ -277,11 +277,11 @@ describe("Verifier integration with ethereum connector tests", () => { const correctArgs: any = {}; // Sanity check if correct parameters work - const resultCorrect = await verifier.sendSyncRequest( + const resultCorrect = (await verifier.sendSyncRequest( correctContract, correctMethod, correctArgs, - ) as ISendRequestResultV1; + )) as ISendRequestResultV1; expect(resultCorrect.status).toEqual(200); // Failing: Missing contract ABI @@ -364,11 +364,11 @@ describe("Verifier integration with ethereum connector tests", () => { }, }; - const resultsSend = await verifier.sendSyncRequest( + const resultsSend = (await verifier.sendSyncRequest( contractCommon, methodSend, argsSend, - ) as ISendRequestResultV1<{ readonly status: string }>; + )) as ISendRequestResultV1<{ readonly status: string }>; expect(resultsSend.status).toEqual(200); expect(resultsSend.data.status).toEqual("1"); @@ -381,11 +381,11 @@ describe("Verifier integration with ethereum connector tests", () => { }; const argsCall = {}; - const resultCall = await verifier.sendSyncRequest( + const resultCall = (await verifier.sendSyncRequest( contractCommon, methodCall, argsCall, - ) as ISendRequestResultV1; + )) as ISendRequestResultV1; expect(resultCall.status).toEqual(200); expect(resultCall.data).toEqual(newName); }); @@ -404,11 +404,11 @@ describe("Verifier integration with ethereum connector tests", () => { }, }; - const resultsEncode = await verifier.sendSyncRequest( + const resultsEncode = (await verifier.sendSyncRequest( contractCommon, methodEncode, argsEncode, - ) as ISendRequestResultV1; + )) as ISendRequestResultV1; expect(resultsEncode.status).toEqual(200); expect(resultsEncode.data.length).toBeGreaterThan(5); @@ -434,11 +434,11 @@ describe("Verifier integration with ethereum connector tests", () => { }; const argsEstimateGas = {}; - const resultsEstimateGas = await verifier.sendSyncRequest( + const resultsEstimateGas = (await verifier.sendSyncRequest( contractCommon, methodEstimateGas, argsEstimateGas, - ) as ISendRequestResultV1; + )) as ISendRequestResultV1; expect(resultsEstimateGas.status).toEqual(200); expect(Number(resultsEstimateGas.data)).toBeGreaterThan(0); @@ -492,11 +492,11 @@ describe("Verifier integration with ethereum connector tests", () => { }; const argsCall = {}; - const resultsCall = await verifier.sendSyncRequest( + const resultsCall = (await verifier.sendSyncRequest( contractCommon, methodCall, argsCall, - ) as ISendRequestResultV1; + )) as ISendRequestResultV1; expect(resultsCall.status).toEqual(200); expect(resultsCall.data).toEqual(newName); }); @@ -508,9 +508,9 @@ describe("Verifier integration with ethereum connector tests", () => { const method = { type: "web3Eth", command: "getBalance" }; const args = { args: [WHALE_ACCOUNT_ADDRESS] }; - const results = await globalVerifierFactory + const results = (await globalVerifierFactory .getVerifier(ethereumValidatorId) - .sendSyncRequest(contract, method, args) as ISendRequestResultV1; + .sendSyncRequest(contract, method, args)) as ISendRequestResultV1; expect(results.status).toEqual(200); expect(results.data.length).toBeGreaterThan(0); }); @@ -528,11 +528,11 @@ describe("Verifier integration with ethereum connector tests", () => { const verifier = globalVerifierFactory.getVerifier(ethereumValidatorId); // Sanity check if correct parameters work - const resultCorrect = await verifier.sendSyncRequest( + const resultCorrect = (await verifier.sendSyncRequest( correctContract, correctMethod, correctArgs, - ) as ISendRequestResultV1<{}>; + )) as ISendRequestResultV1; expect(resultCorrect.status).toEqual(200); // Failing: Empty web3.eth method diff --git a/tools/docker/geth-all-in-one/Dockerfile b/tools/docker/geth-all-in-one/Dockerfile index 1353e2235a..d0f751deb0 100644 --- a/tools/docker/geth-all-in-one/Dockerfile +++ b/tools/docker/geth-all-in-one/Dockerfile @@ -20,25 +20,23 @@ VOLUME [ "/root/data" ] ENTRYPOINT [ \ "geth", \ "--datadir", "/root/data", \ + "--networkid", "10", \ "--nodiscover", \ "--http", \ "--http.addr", "0.0.0.0", \ "--http.port", "8545", \ "--http.corsdomain", "*", \ "--http.vhosts", "*", \ + "--http.api", "eth,personal,web3,net,admin,debug", \ "--ws", \ "--ws.addr", "0.0.0.0", \ "--ws.port", "8546", \ "--ws.origins", "*", \ + "--ws.api", "eth,personal,web3,net,admin,debug", \ "--unlock", "0x6A2EC8c50BA1a9cE47c52d1cb5B7136Ee9d0cCC0", \ "--mine", \ "--password", "/dev/null", \ "--allow-insecure-unlock", \ "--miner.etherbase", "0x6A2EC8c50BA1a9cE47c52d1cb5B7136Ee9d0cCC0", \ "--verbosity", "5" \ - ] -CMD [ \ - "--networkid", "10", \ - "--http.api", "eth,personal,web3,net,admin,debug", \ - "--ws.api", "eth,personal,web3,net,admin,debug" \ ] \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f6b0646aa7..5fd4164ea8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6482,11 +6482,13 @@ __metadata: "@hyperledger/cactus-test-geth-ledger": 2.0.0-alpha.2 "@hyperledger/cactus-test-tooling": 2.0.0-alpha.2 "@types/express": 4.17.13 + "@types/js-yaml": 4.0.5 "@types/minimist": 1.2.2 "@types/sanitize-html": 2.6.2 axios: 0.21.4 chalk: 4.1.2 express: 4.17.3 + js-yaml: 4.1.0 minimist: 1.2.8 prom-client: 13.2.0 run-time-error: 1.4.0 @@ -6497,6 +6499,7 @@ __metadata: typescript-optional: 2.0.1 web3: 4.1.2 web3-eth: 4.2.0 + web3-eth-accounts: 4.0.6 web3-eth-contract: 4.1.0 bin: cacti-ethereum-connector-status: dist/lib/scripts/get-ethereum-connector-status.js @@ -43267,6 +43270,21 @@ __metadata: languageName: node linkType: hard +"web3-eth-accounts@npm:4.0.6, web3-eth-accounts@npm:^4.0.6": + version: 4.0.6 + resolution: "web3-eth-accounts@npm:4.0.6" + dependencies: + "@ethereumjs/rlp": ^4.0.1 + crc-32: ^1.2.2 + ethereum-cryptography: ^2.0.0 + web3-errors: ^1.1.2 + web3-types: ^1.2.0 + web3-utils: ^4.0.6 + web3-validator: ^2.0.2 + checksum: 07fc3f5b6ccb862696ea2c34cc104323ad2b1e85c54bb8bd8dcb61154b09f592a2cc899ff0ac7bdd2a2e6f39afe3a4cdf2e7630ddc9b4150bf0b45e7004fe209 + languageName: node + linkType: hard + "web3-eth-accounts@npm:^4.0.3, web3-eth-accounts@npm:^4.0.5": version: 4.0.5 resolution: "web3-eth-accounts@npm:4.0.5" @@ -43282,21 +43300,6 @@ __metadata: languageName: node linkType: hard -"web3-eth-accounts@npm:^4.0.6": - version: 4.0.6 - resolution: "web3-eth-accounts@npm:4.0.6" - dependencies: - "@ethereumjs/rlp": ^4.0.1 - crc-32: ^1.2.2 - ethereum-cryptography: ^2.0.0 - web3-errors: ^1.1.2 - web3-types: ^1.2.0 - web3-utils: ^4.0.6 - web3-validator: ^2.0.2 - checksum: 07fc3f5b6ccb862696ea2c34cc104323ad2b1e85c54bb8bd8dcb61154b09f592a2cc899ff0ac7bdd2a2e6f39afe3a4cdf2e7630ddc9b4150bf0b45e7004fe209 - languageName: node - linkType: hard - "web3-eth-contract@npm:1.10.0": version: 1.10.0 resolution: "web3-eth-contract@npm:1.10.0"