From 3fa909346074ed91175bfd4b9c81023fa77a6678 Mon Sep 17 00:00:00 2001 From: tomasz awramski Date: Tue, 10 Jan 2023 11:38:49 +0100 Subject: [PATCH] feat(connector-go-ethereum): add getBlock and getTransactionReceipt methods to connector - getBlock and getTransactionReceipt added in go-ethereum-socketio-connector - Added nullish coalescing in monitor options Closes: #2255 Signed-off-by: tomasz awramski --- .../main/typescript/common/core/bin/www.ts | 27 ++-- .../main/typescript/connector/ServerPlugin.ts | 139 +++++++++++++++++- .../src/main/typescript/public-api.ts | 3 +- .../go-ethereum-socketio-connector.test.ts | 115 ++++++++++++++- 4 files changed, 262 insertions(+), 22 deletions(-) diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/common/core/bin/www.ts b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/common/core/bin/www.ts index bc44111af9..832147b51f 100644 --- a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/common/core/bin/www.ts +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/common/core/bin/www.ts @@ -252,19 +252,20 @@ export async function startGoEthereumSocketIOConnector() { * startMonitor: starting block generation event monitoring **/ client.on("startMonitor", function (monitorOptions) { - - monitorOptions = monitorOptions ?? {allBlocks: false}; - logger.debug("monitorOptions", monitorOptions); - Smonitor.startMonitor(client.id, monitorOptions.allBlocks, (event) => { - let emitType = ""; - if (event.status == 200) { - emitType = "eventReceived"; - logger.info("event data callbacked."); - } else { - emitType = "monitor_error"; - } - client.emit(emitType, event); - }); + Smonitor.startMonitor( + client.id, + monitorOptions?.allBlocks ?? false, + (event) => { + let emitType = ""; + if (event.status == 200) { + emitType = "eventReceived"; + logger.info("event data callbacked."); + } else { + emitType = "monitor_error"; + } + client.emit(emitType, event); + }, + ); }); /** diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/connector/ServerPlugin.ts b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/connector/ServerPlugin.ts index 74085bfeb6..a385369245 100644 --- a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/connector/ServerPlugin.ts +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/connector/ServerPlugin.ts @@ -25,9 +25,10 @@ import * as SplugUtil from "./PluginUtil"; // Load libraries, SDKs, etc. according to specifications of endchains as needed import Web3 from "web3"; import { AbiItem } from "web3-utils"; +import { BlockNumber } from "web3-core"; import { safeStringifyException } from "@hyperledger/cactus-common"; -var WEB3_HTTP_PROVIDER_OPTIONS = { +const WEB3_HTTP_PROVIDER_OPTIONS = { keepAlive: true, }; @@ -60,7 +61,12 @@ function getWeb3Provider(host: string) { } } -const web3 = new Web3(getWeb3Provider(configRead("ledgerUrl"))); +const web3Provider = getWeb3Provider(configRead("ledgerUrl")); +const web3 = new Web3(web3Provider); + +export function shutdown() { + web3Provider.disconnect(); +} /* * ServerPlugin @@ -93,6 +99,135 @@ export class ServerPlugin { } } + /* + * getBlock + * + * @param {String|Number} block hash, block number or string:"earliest", "latest" or "pending" + * + * @return {Object} JSON object + */ + + async getBlock(args: any) { + logger.debug("getBlock start"); + + const blockID: BlockNumber = args.args.args[0]; + const returnTransactionObjects = args.args.args[1] ?? false; + const reqID = args["reqID"]; + + if (!blockID) { + const emsg = "JSON parse error!"; + logger.warn(emsg); + throw { + resObj: { + status: 504, + errorDetail: emsg, + }, + id: reqID, + }; + } + + try { + const blockData = await web3.eth.getBlock( + blockID, + returnTransactionObjects, + ); + const result = { + blockData: blockData, + }; + logger.debug(`getBlock(): result: ${result}`); + + const signedResults = signMessageJwt({ result }); + logger.debug(`getBlock(): signedResults: ${signedResults}`); + + return { + resObj: { + status: 200, + data: signedResults, + }, + id: reqID, + }; + } catch (e) { + const retObj = { + resObj: { + status: 504, + errorDetail: safeStringifyException(e), + }, + id: reqID, + }; + + logger.error(`##getBlock: retObj: ${JSON.stringify(retObj)}`); + + throw retObj; + } + } + + /* + * getTransactionReceipt + * + * @param {String} transaction hash + * + * @return {Object} JSON object + */ + + async getTransactionReceipt(args: any) { + logger.debug("getTransactionReceipt start"); + + const txHash: string = args.args.args[0]; + const reqID = args["reqID"]; + + if (txHash === undefined) { + const emsg = "JSON parse error!"; + logger.warn(emsg); + throw { + resObj: { + status: 504, + errorDetail: emsg, + }, + id: reqID, + }; + } + + try { + const txReceipt = await web3.eth.getTransactionReceipt(txHash); + logger.info(`getTransactionReceipt(): txReceipt: ${txReceipt}`); + + const result = { + txReceipt: txReceipt, + }; + logger.debug(`getTransactionReceipt(): result: ${result}`); + + const signedResults = signMessageJwt({ result: result }); + logger.debug(`getTransactionReceipt(): signedResults: ${signedResults}`); + + const retObj = { + resObj: { + status: 200, + data: signedResults, + }, + id: reqID, + }; + + logger.debug( + `##getTransactionReceipt: retObj: ${JSON.stringify(retObj)}`, + ); + return retObj; + } catch (e) { + const retObj = { + resObj: { + status: 504, + errorDetail: safeStringifyException(e), + }, + id: reqID, + }; + + logger.error( + `##getTransactionReceipt: retObj: ${JSON.stringify(retObj)}`, + ); + + throw retObj; + } + } + // Define an arbitrary function and implement it according to specifications of end-chains /** * getNumericBalance diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/public-api.ts index 34f93f03a0..8018f5172e 100644 --- a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/public-api.ts +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/public-api.ts @@ -1 +1,2 @@ -export { startGoEthereumSocketIOConnector } from "./common/core/bin/www" +export { startGoEthereumSocketIOConnector } from "./common/core/bin/www"; +export { shutdown } from "./connector/ServerPlugin"; diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/typescript/integration/go-ethereum-socketio-connector.test.ts b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/typescript/integration/go-ethereum-socketio-connector.test.ts index f3ea1c1c3a..12c1ff0fae 100644 --- a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/typescript/integration/go-ethereum-socketio-connector.test.ts +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/typescript/integration/go-ethereum-socketio-connector.test.ts @@ -55,13 +55,17 @@ describe("Go-Ethereum-SocketIO connector tests", () => { let connectorServer: HttpsServer; let apiClient: SocketIOApiClient; let constTestAcc: Account; + let connectorModule: any; const constTestAccBalance = 5 * 1000000; ////////////////////////////////// // Environment Setup ////////////////////////////////// - async function deploySmartContract(): Promise { + async function deploySmartContract(): Promise<{ + contractAddress: string; + blockNumber: number; + }> { const txReceipt = await ledger.deployContract( HelloWorldContractJson.abi as any, "0x" + HelloWorldContractJson.bytecode, @@ -74,7 +78,11 @@ describe("Go-Ethereum-SocketIO connector tests", () => { "Deployed test smart contract, TX on block number", txReceipt.blockNumber, ); - return txReceipt.contractAddress ?? ""; + + return { + contractAddress: txReceipt.contractAddress ?? "", + blockNumber: txReceipt.blockNumber, + }; } beforeAll(async () => { @@ -99,7 +107,8 @@ describe("Go-Ethereum-SocketIO connector tests", () => { web3 = new Web3(ledgerRpcUrl); // Deploy test smart contract - contractAddress = await deploySmartContract(); + const deployOutput = await deploySmartContract(); + contractAddress = deployOutput.contractAddress; // Generate connector private key and certificate const pkiGenerator = new SelfSignedPkiGenerator(); @@ -125,7 +134,7 @@ describe("Go-Ethereum-SocketIO connector tests", () => { process.env["NODE_CONFIG"] = configJson; // Load connector module - const connectorModule = await import("../../../main/typescript/index"); + connectorModule = await import("../../../main/typescript/index"); // Run the connector connectorServer = await connectorModule.startGoEthereumSocketIOConnector(); @@ -160,6 +169,10 @@ describe("Go-Ethereum-SocketIO connector tests", () => { afterAll(async () => { log.info("FINISHING THE TESTS"); + if (connectorModule) { + connectorModule.shutdown(); + } + if (apiClient) { log.info("Close ApiClient connection..."); apiClient.close(); @@ -200,6 +213,97 @@ describe("Go-Ethereum-SocketIO connector tests", () => { expect(balance).toEqual(constTestAccBalance.toString()); }); + test("getBlock returns valid block data for different methods", async () => { + const getBlock = async (param: string | number) => { + const method = { type: "function", command: "getBlock" }; + const args = { args: [param] }; + const response = await apiClient.sendSyncRequest({}, method, args); + expect(response).toBeTruthy(); + return response; + }; + + const latestBlock = await getBlock("latest"); + expect(latestBlock.status).toEqual(200); + const { data: blockDataByHash } = await getBlock( + latestBlock.data.blockData.hash, + ); + expect(blockDataByHash.blockData.hash).toEqual( + latestBlock.data.blockData.hash, + ); + const { data: blockDataByNumber } = await getBlock( + latestBlock.data.blockData.number, + ); + expect(blockDataByNumber.blockData.hash).toEqual( + blockDataByHash.blockData.hash, + ); + + // Assert transaction data is not returned + const { blockNumber } = await deploySmartContract(); + const blockWithTx = await getBlock(blockNumber); + const firstTx = blockWithTx.data.blockData.transactions[0]; + expect(firstTx).toBeTruthy(); + // Only string hashes are returned when flag is false + expect(typeof firstTx).toEqual("string"); + }); + + test("getBlock returns transaction data when requested", async () => { + const method = { type: "function", command: "getBlock" }; + const { blockNumber } = await deploySmartContract(); + const args = { args: [blockNumber, true] }; + + const response = await apiClient.sendSyncRequest({}, method, args); + + // Assert correct response + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + + // Assert valid block data + const block = response.data.blockData; + expect(block).toBeTruthy(); + expect(block.hash).toBeTruthy(); + + // Assert transaction data was returned as requested + expect(block.transactions.length).toBeGreaterThan(0); + const firstTx = block.transactions[0]; + expect(firstTx).toBeTruthy(); + expect(firstTx.hash).toBeTruthy(); + }); + + test("Function getTransactionReceipt returns transaction receipt of given transaction", async () => { + async function getTransactionHash() { + const fromAccInitBalance = 1500; + const toAccInitBalance = 1500; + const transferAmount = 500; + //creating two accounts to perform transaction on them + const fromAddress = await ledger.createEthTestAccount(fromAccInitBalance); + const toAcc = await ledger.createEthTestAccount(toAccInitBalance); + // adding account using a private key to the wallet + web3.eth.accounts.wallet.add(fromAddress.privateKey); + + const signedTx = await fromAddress.signTransaction({ + from: fromAddress.address, + to: toAcc.address, + value: transferAmount, + gas: 1000000, + }); + const method = { type: "function", command: "sendRawTransaction" }; + const args = { args: [{ serializedTx: signedTx.rawTransaction }] }; + // transfering funds to trigger transaction + const response = await apiClient.sendSyncRequest({}, method, args); + // returning only transaction hash + return response.data.txid; + } + + const transactionHash = await getTransactionHash(); + const method = { type: "function", command: "getTransactionReceipt" }; + const args = { args: [transactionHash] }; + + const response = await apiClient.sendSyncRequest({}, method, args); + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + expect(response.data.txReceipt.transactionHash).toEqual(transactionHash); + }); + /** * Test ServerPlugin getNumericBalance function. */ @@ -294,7 +398,6 @@ describe("Go-Ethereum-SocketIO connector tests", () => { gas: 1000000, }); expect(signedTx).toBeTruthy(); - log.warn(signedTx); const method = { type: "function", command: "sendRawTransaction" }; const args = { args: [{ serializedTx: signedTx.rawTransaction }] }; @@ -377,7 +480,7 @@ describe("Go-Ethereum-SocketIO connector tests", () => { /** * Test ServerMonitorPlugin startMonitor/stopMonitor functions. */ - test.only( + test( "Monitoring returns new block", async () => { // Create monitoring promise and subscription