From 487b4c271034c514d94cefe9c84daa4ddb7cb35c Mon Sep 17 00:00:00 2001 From: Michal Bajer Date: Mon, 9 Oct 2023 12:20:00 +0000 Subject: [PATCH] feat(cactus-plugin-ledger-connector-ethereum): add json-rpc proxy - Add proxy endpoint handling. Requests sent to this endpoint will be passed directly to the ethereum node. - Add test to verify that proxy is working. - Update README of ethereum connector. Co-authored-by: Peter Somogyvari Signed-off-by: Michal Bajer Signed-off-by: Peter Somogyvari --- .../README.md | 13 ++++ .../package.json | 1 + .../src/main/json/openapi.json | 3 + .../plugin-ledger-connector-ethereum.ts | 22 +++++++ ...eploy-and-invoke-using-keychain-v1.test.ts | 9 +-- .../geth-invoke-web3-method-v1.test.ts | 66 ++++++++++++++++--- yarn.lock | 3 +- 7 files changed, 104 insertions(+), 13 deletions(-) diff --git a/packages/cactus-plugin-ledger-connector-ethereum/README.md b/packages/cactus-plugin-ledger-connector-ethereum/README.md index bfe021dec7..9e23ad4ac6 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/README.md +++ b/packages/cactus-plugin-ledger-connector-ethereum/README.md @@ -147,6 +147,19 @@ args: { }, ``` +## JSON-RPC Proxy +- Connector can be used with web3js to send any JSON-RPC request to the ethereum node. + +### Example +``` typescript + const proxyUrl = new URL( + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/json-rpc", + apiHost, + ); + const web3ProxyClient = new Web3(proxyUrl.toString()); + const gasPrice = await web3ProxyClient.eth.getGasPrice(); +``` + ## Running the tests To check that all has been installed correctly and that the pugin has no errors run jest test suites. diff --git a/packages/cactus-plugin-ledger-connector-ethereum/package.json b/packages/cactus-plugin-ledger-connector-ethereum/package.json index b719903630..ea3cddb072 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/package.json +++ b/packages/cactus-plugin-ledger-connector-ethereum/package.json @@ -70,6 +70,7 @@ "@hyperledger/cactus-core-api": "2.0.0-alpha.2", "axios": "0.21.4", "express": "4.17.3", + "http-proxy-middleware": "2.0.6", "minimist": "1.2.8", "prom-client": "13.2.0", "run-time-error": "1.4.0", diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json index aeb4b33052..0f9f8824dc 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/json/openapi.json @@ -1159,6 +1159,9 @@ } } } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/json-rpc": { + "summary": "Proxy endpoint to pass JSON-RPC requests to remote ethereum node." } } } 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 1211a1da06..fb5104b973 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 @@ -78,6 +78,8 @@ import { } from "./types/model-type-guards"; import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter"; import { RuntimeError } from "run-time-error"; +import { createProxyMiddleware, fixRequestBody } from "http-proxy-middleware"; + import { Web3StringReturnFormat, convertWeb3ReceiptStatusToBool, @@ -339,6 +341,26 @@ export class PluginLedgerConnectorEthereum }); } + // Register JSON-RPC proxy to pass requests directly to ethereum node + if (this.options.rpcApiHttpHost) { + const proxyUrl = + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/json-rpc"; + const targetUrl = this.options.rpcApiHttpHost; + app.use( + proxyUrl, + createProxyMiddleware({ + target: targetUrl, + changeOrigin: true, + pathRewrite: { + [".*"]: "", + }, + onProxyReq: fixRequestBody, + logLevel: "error", + }), + ); + this.log.info(`Registered proxy from ${proxyUrl} to ${targetUrl}`); + } + return webServices; } diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts index a0342a05d1..227f9c62cc 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts @@ -63,7 +63,7 @@ describe("Ethereum contract deploy and invoke using keychain tests", () => { address: string, port: number, contractAddress: string, - apiHost, + apiHost: string, apiConfig, ledger: GethTestLedger, apiClient: EthereumApi, @@ -129,6 +129,9 @@ describe("Ethereum contract deploy and invoke using keychain tests", () => { logLevel: testLogLevel, pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); }); afterAll(async () => { @@ -143,9 +146,6 @@ describe("Ethereum contract deploy and invoke using keychain tests", () => { test("setup ethereum connector", async () => { // Instantiate connector with the keychain plugin that already has the // private key we want to use for one of our tests - await connector.getOrCreateWebServices(); - await connector.registerWebServices(expressApp, wsApi); - const initTransferValue = web3.utils.toWei(5000, "ether"); await apiClient.runTransactionV1({ web3SigningCredential: { @@ -360,6 +360,7 @@ describe("Ethereum contract deploy and invoke using keychain tests", () => { maxPriorityFeePerGas: 0, maxFeePerGas: 0x40000000, gasLimit: 21000, + type: 2, }, testEthAccount.privateKey, ); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-method-v1.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-method-v1.test.ts index 0702064ff8..0d5e1e47f5 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-method-v1.test.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-invoke-web3-method-v1.test.ts @@ -11,16 +11,29 @@ const testLogLevel = "info"; const sutLogLevel = "info"; import "jest-extended"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import Web3 from "web3"; import { v4 as uuidv4 } from "uuid"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; + import { PluginRegistry } from "@hyperledger/cactus-core"; -import { PluginLedgerConnectorEthereum } from "../../../main/typescript/index"; +import { Constants } from "@hyperledger/cactus-core-api"; +import { + IListenOptions, + Logger, + LoggerProvider, + Servers, +} from "@hyperledger/cactus-common"; import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; -import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; import { GethTestLedger, WHALE_ACCOUNT_ADDRESS, } from "@hyperledger/cactus-test-geth-ledger"; -import Web3 from "web3"; + +import { PluginLedgerConnectorEthereum } from "../../../main/typescript/index"; // Unit Test logger setup const log: Logger = LoggerProvider.getOrCreate({ @@ -36,6 +49,13 @@ describe("invokeRawWeb3EthMethod Tests", () => { let ethereumTestLedger: GethTestLedger; let connector: PluginLedgerConnectorEthereum; let web3: Web3; + let apiHost: string; + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); ////////////////////////////////// // Environment Setup @@ -64,21 +84,51 @@ describe("invokeRawWeb3EthMethod Tests", () => { pluginRegistry: new PluginRegistry(), }); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + apiHost = `http://${address}:${port}`; + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + web3 = new Web3(rpcApiHttpHost); }); afterAll(async () => { - log.info("Shutdown connector"); - await connector.shutdown(); + log.info("Shutdown server"); + await Servers.shutdown(server); - log.info("Stop and destroy the test ledger..."); - await ethereumTestLedger.stop(); - await ethereumTestLedger.destroy(); + if (connector) { + log.info("Shutdown connector"); + await connector.shutdown(); + } + + if (ethereumTestLedger) { + log.info("Stop and destroy the test ledger..."); + await ethereumTestLedger.stop(); + await ethereumTestLedger.destroy(); + } log.info("Prune docker..."); await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); }); + test("invoke method using json-rpc proxy", async () => { + const proxyUrl = new URL( + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-ethereum/json-rpc", + apiHost, + ); + const web3ProxyClient = new Web3(proxyUrl.toString()); + const gasPrice = await web3ProxyClient.eth.getGasPrice(); + expect(gasPrice).toBeTruthy(); + expect(Number(gasPrice)).toBeGreaterThan(0); + }); + test("invokeRawWeb3EthMethod with 0-argument method works (getGasPrice)", async () => { const connectorResponse = await connector.invokeRawWeb3EthMethod({ methodName: "getGasPrice", diff --git a/yarn.lock b/yarn.lock index 5fd4164ea8..25e5b39dcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6488,6 +6488,7 @@ __metadata: axios: 0.21.4 chalk: 4.1.2 express: 4.17.3 + http-proxy-middleware: 2.0.6 js-yaml: 4.1.0 minimist: 1.2.8 prom-client: 13.2.0 @@ -24695,7 +24696,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.3": +"http-proxy-middleware@npm:2.0.6, http-proxy-middleware@npm:^2.0.3": version: 2.0.6 resolution: "http-proxy-middleware@npm:2.0.6" dependencies: