diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json index e7d96b563e..1518154df5 100644 --- a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json @@ -2,6 +2,9 @@ "name": "@hyperledger/cactus-plugin-ledger-connector-go-ethereum-socketio", "version": "1.1.2", "license": "Apache-2.0", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "start": "cd ./dist && node common/core/bin/www.js", "debug": "nodemon --inspect ./dist/common/core/bin/www.js", @@ -28,6 +31,9 @@ "web3": "0.20.7" }, "devDependencies": { + "@hyperledger/cactus-test-tooling": "1.1.2", + "@hyperledger/cactus-common": "1.1.2", + "@hyperledger/cactus-api-client": "1.1.2", "@types/config": "0.0.41" } } 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 e1bed78a32..dc7e6c6fb4 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 @@ -1,7 +1,7 @@ #!/usr/bin/env node /* - * Copyright 2021 Hyperledger Cactus Contributors + * Copyright 2022 Hyperledger Cactus Contributors * SPDX-License-Identifier: Apache-2.0 * * www.js @@ -11,12 +11,7 @@ * Connector: a part independent of end-chains */ -/** - * Module dependencies. - */ - import app from "../app"; -const debug = require("debug")("connector:server"); import https = require("https"); // Overwrite config read path @@ -38,46 +33,11 @@ logger.level = configRead('logLevel', 'info'); // implementation class of a part dependent of end-chains (server plugin) import { ServerPlugin } from "../../../connector/ServerPlugin"; -const Splug = new ServerPlugin(); // destination dependency (MONITOR) implementation class import { ServerMonitorPlugin } from "../../../connector/ServerMonitorPlugin"; -const Smonitor = new ServerMonitorPlugin(); - -/** - * Get port from environment and store in Express. - */ - -const sslport = normalizePort(process.env.PORT || configRead('sslParam.port')); -app.set("port", sslport); - -// Specify private key and certificate -const sslParam = { - key: fs.readFileSync(configRead('sslParam.key')), - cert: fs.readFileSync(configRead('sslParam.cert')), -}; - -/** - * Create HTTPS server. - */ - -const server = https.createServer(sslParam, app); // Start as an https server. -const io = new Server(server); - -/** - * Listen on provided port, on all network interfaces. - */ - -server.listen(sslport, function () { - console.log("listening on *:" + sslport); -}); -server.on("error", onError); -server.on("listening", onListening); - -/** - * Normalize a port into a number, string, or false. - */ +// Normalize a port into a number, string, or false. function normalizePort(val: string) { const port = parseInt(val, 10); @@ -94,148 +54,75 @@ function normalizePort(val: string) { return false; } -/** - * Event listener for HTTPS server "error" event. - */ - -function onError(error: any) { - if (error.syscall !== "listen") { - throw error; +export async function startGoEthereumSocketIOConnector() { + const Splug = new ServerPlugin(); + const Smonitor = new ServerMonitorPlugin(); + + // Get port from environment and store in Express. + const sslport = normalizePort(process.env.PORT || configRead('sslParam.port')); + app.set("port", sslport); + + // Specify private key and certificate + let keyString: string; + let certString: string; + try { + keyString = configRead('sslParam.keyValue'); + certString = configRead('sslParam.certValue'); + } catch { + keyString = fs.readFileSync(configRead('sslParam.key'), "ascii"); + certString = fs.readFileSync(configRead('sslParam.cert'), "ascii"); } - const bind = - typeof sslport === "string" ? "Pipe " + sslport : "Port " + sslport; + // Create HTTPS server. + const server = https.createServer({ + key: keyString, + cert: certString, + }, app); // Start as an https server. + const io = new Server(server); - // handle specific listen errors with friendly messages - switch (error.code) { - case "EACCES": - console.error(bind + " requires elevated privileges"); - process.exit(1); - break; - case "EADDRINUSE": - console.error(bind + " is already in use"); - process.exit(1); - break; - default: + // Event listener for HTTPS server "error" event. + server.on("error", (error: any) => { + if (error.syscall !== "listen") { throw error; - } -} - -/** - * Event listener for HTTPS server "listening" event. - */ - -function onListening() { - const addr = server.address(); - - if (!addr) { - logger.error("Could not get running server address - exit."); - process.exit(1); - } - - const bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port; - debug("Listening on " + bind); -} + } -io.on("connection", function (client) { - logger.info("Client " + client.id + " connected."); - - /** - * request: The server plugin's request to execute a function - * @param {JSON} data: Request Body (following format) - * JSON: { - * "func": (string) Function name ,// For example : "transferNumericAsset" - * "args": (Object) argument// for example , {"from" : "xxx" , "to" : "yyy" , "value" : "10,000"} - * } - **/ - client.on("request", function (data) { - const func = data.func; - const args = data.args; - console.log("##[HL-BC] Invoke smart contract to transfer asset(D1)"); - logger.info("*** REQUEST ***"); - logger.info("Client ID :" + client.id); - logger.info("Data :" + JSON.stringify(data)); - - // Check for the existence of the specified function and call it if it exists. - if (Splug.isExistFunction(func)) { - // Can be called with Server plugin function name. - (Splug as any)[func](args) - .then((respObj: unknown) => { - logger.info("*** RESPONSE ***"); - logger.info("Client ID :" + client.id); - logger.info("Response :" + JSON.stringify(respObj)); - client.emit("response", respObj); - }) - .catch((errObj: unknown) => { - logger.error("*** ERROR ***"); - logger.error("Client ID :" + client.id); - logger.error("Detail :" + JSON.stringify(errObj)); - client.emit("connector_error", errObj); - }); - } else { - // No such function - const emsg = "Function " + func + " not found!"; - logger.error(emsg); - const retObj = { - status: 504, - errorDetail: emsg, - }; - client.emit("connector_error", retObj); + const bind = + typeof sslport === "string" ? "Pipe " + sslport : "Port " + sslport; + + // handle specific listen errors with friendly messages + switch (error.code) { + case "EACCES": + console.error(bind + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + console.error(bind + " is already in use"); + process.exit(1); + break; + default: + throw error; } }); - // TODO: "request2" -> "request" - client.on("request2", function (data) { - const methodType = data.method.type; - let args: Record = { - contract: data.contract, - method: data.method, - args: data.args, - }; - - if (data.reqID !== undefined) { - logger.info(`##add reqID: ${data.reqID}`); - args["reqID"] = data.reqID; - } + io.on("connection", function (client) { + logger.info("Client " + client.id + " connected."); + + /** + * request: The server plugin's request to execute a function + * @param {JSON} data: Request Body (following format) + * JSON: { + * "func": (string) Function name ,// For example : "transferNumericAsset" + * "args": (Object) argument// for example , {"from" : "xxx" , "to" : "yyy" , "value" : "10,000"} + * } + **/ + client.on("request", function (data) { + const func = data.func; + const args = data.args; + console.log("##[HL-BC] Invoke smart contract to transfer asset(D1)"); + logger.info("*** REQUEST ***"); + logger.info("Client ID :" + client.id); + logger.info("Data :" + JSON.stringify(data)); - console.log("##[HL-BC] Invoke smart contract to transfer asset(D1)"); - logger.info("*** REQUEST ***"); - logger.info("Client ID :" + client.id); - logger.info("Data :" + JSON.stringify(data)); - - // Check for the existence of the specified function and call it if it exists. - if (methodType === "web3Eth") { - // Can be called with Server plugin function name. - Splug.web3Eth(args) - .then((respObj) => { - logger.info("*** RESPONSE ***"); - logger.info("Client ID :" + client.id); - logger.info("Response :" + JSON.stringify(respObj)); - client.emit("response", respObj); - }) - .catch((errObj) => { - logger.error("*** ERROR ***"); - logger.error("Client ID :" + client.id); - logger.error("Detail :" + JSON.stringify(errObj)); - client.emit("connector_error", errObj); - }); - } else if (methodType === "contract") { - // Can be called with Server plugin function name. - Splug.contract(args) - .then((respObj) => { - logger.info("*** RESPONSE ***"); - logger.info("Client ID :" + client.id); - logger.info("Response :" + JSON.stringify(respObj)); - client.emit("response", respObj); - }) - .catch((errObj) => { - logger.error("*** ERROR ***"); - logger.error("Client ID :" + client.id); - logger.error("Detail :" + JSON.stringify(errObj)); - client.emit("connector_error", errObj); - }); - } else if (methodType === "function") { - const func = args["method"].command; // Check for the existence of the specified function and call it if it exists. if (Splug.isExistFunction(func)) { // Can be called with Server plugin function name. @@ -262,47 +149,148 @@ io.on("connection", function (client) { }; client.emit("connector_error", retObj); } - } else { - // No such function - const emsg = "method.type " + methodType + " not found!"; - logger.error(emsg); - const retObj = { - status: 504, - errorDetail: emsg, + }); + + // TODO: "request2" -> "request" + client.on("request2", function (data) { + const methodType = data.method.type; + let args: Record = { + contract: data.contract, + method: data.method, + args: data.args, }; - client.emit("connector_error", retObj); - } - }); - /** - * startMonitor: starting block generation event monitoring - **/ - client.on("startMonitor", function () { - Smonitor.startMonitor(client.id, (event) => { - let emitType = ""; - if (event.status == 200) { - emitType = "eventReceived"; - logger.info("event data callbacked."); + if (data.reqID !== undefined) { + logger.info(`##add reqID: ${data.reqID}`); + args["reqID"] = data.reqID; + } + + console.log("##[HL-BC] Invoke smart contract to transfer asset(D1)"); + logger.info("*** REQUEST ***"); + logger.info("Client ID :" + client.id); + logger.info("Data :" + JSON.stringify(data)); + + // Check for the existence of the specified function and call it if it exists. + if (methodType === "web3Eth") { + // Can be called with Server plugin function name. + Splug.web3Eth(args) + .then((respObj) => { + logger.info("*** RESPONSE ***"); + logger.info("Client ID :" + client.id); + logger.info("Response :" + JSON.stringify(respObj)); + client.emit("response", respObj); + }) + .catch((errObj) => { + logger.error("*** ERROR ***"); + logger.error("Client ID :" + client.id); + logger.error("Detail :" + JSON.stringify(errObj)); + client.emit("connector_error", errObj); + }); + } else if (methodType === "contract") { + // Can be called with Server plugin function name. + Splug.contract(args) + .then((respObj) => { + logger.info("*** RESPONSE ***"); + logger.info("Client ID :" + client.id); + logger.info("Response :" + JSON.stringify(respObj)); + client.emit("response", respObj); + }) + .catch((errObj) => { + logger.error("*** ERROR ***"); + logger.error("Client ID :" + client.id); + logger.error("Detail :" + JSON.stringify(errObj)); + client.emit("connector_error", errObj); + }); + } else if (methodType === "function") { + const func = args["method"].command; + // Check for the existence of the specified function and call it if it exists. + if (Splug.isExistFunction(func)) { + // Can be called with Server plugin function name. + (Splug as any)[func](args) + .then((respObj: unknown) => { + logger.info("*** RESPONSE ***"); + logger.info("Client ID :" + client.id); + logger.info("Response :" + JSON.stringify(respObj)); + client.emit("response", respObj); + }) + .catch((errObj: unknown) => { + logger.error("*** ERROR ***"); + logger.error("Client ID :" + client.id); + logger.error("Detail :" + JSON.stringify(errObj)); + client.emit("connector_error", errObj); + }); + } else { + // No such function + const emsg = "Function " + func + " not found!"; + logger.error(emsg); + const retObj = { + status: 504, + errorDetail: emsg, + }; + client.emit("connector_error", retObj); + } } else { - emitType = "monitor_error"; + // No such function + const emsg = "method.type " + methodType + " not found!"; + logger.error(emsg); + const retObj = { + status: 504, + errorDetail: emsg, + }; + client.emit("connector_error", retObj); } - client.emit(emitType, event); }); - }); - /** - * stopMonitor: block generation events monitoring stopping - **/ - // I think it is more common to stop from the disconnect described later, but I will prepare for it. - client.on("stopMonitor", function (reason) { - Smonitor.stopMonitor(client.id); + /** + * startMonitor: starting block generation event monitoring + **/ + client.on("startMonitor", function () { + Smonitor.startMonitor(client.id, (event) => { + let emitType = ""; + if (event.status == 200) { + emitType = "eventReceived"; + logger.info("event data callbacked."); + } else { + emitType = "monitor_error"; + } + client.emit(emitType, event); + }); + }); + + /** + * stopMonitor: block generation events monitoring stopping + **/ + // I think it is more common to stop from the disconnect described later, but I will prepare for it. + client.on("stopMonitor", function (reason) { + Smonitor.stopMonitor(client.id); + }); + + client.on("disconnect", function (reason) { + // Unexpected disconnect as well as explicit disconnect request can be received here + logger.info("Client " + client.id + " disconnected."); + logger.info("Reason :" + reason); + // Stop monitoring if disconnected client is for event monitoring + Smonitor.stopMonitor(client.id); + }); }); - client.on("disconnect", function (reason) { - // Unexpected disconnect as well as explicit disconnect request can be received here - logger.info("Client " + client.id + " disconnected."); - logger.info("Reason :" + reason); - // Stop monitoring if disconnected client is for event monitoring - Smonitor.stopMonitor(client.id); + // Listen on provided port, on all network interfaces. + return new Promise((resolve) => server.listen(sslport, () => resolve(server))); +} + +if (require.main === module) { + // When this file executed as a script, not loaded as module - run the connector + startGoEthereumSocketIOConnector().then((server) => { + const addr = server.address(); + + if (!addr) { + logger.error("Could not get running server address - exit."); + process.exit(1); + } + + const bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port; + logger.debug("Listening on " + bind); + }).catch((err) => { + logger.error("Could not start go-ethereum-socketio connector:", err); }); -}); +} 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 3ef15c090d..937d369007 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 @@ -125,7 +125,9 @@ export class ServerPlugin { }); } - /* + /** + * @deprecated - Not usable, can't unlock account remotely at the moment. + * * transferNumericAsset * Transfer numerical asset * @@ -397,43 +399,54 @@ export class ServerPlugin { logger.info("sendRawTransaction(start"); let retObj: Record; - const sendArgs = {}; - const sendFunction = "sendTransaction"; const funcParam = args.args.args[0]; - - if (funcParam["serializedTx"] == undefined) { - const emsg = "JSON parse error!"; - logger.error(emsg); - retObj = { - status: 504, - errorDetail: emsg, - }; - return reject(retObj); - } - - const serializedTx = funcParam["serializedTx"]; - logger.info("serializedTx :" + serializedTx); + const reqID = args["reqID"]; // Handle the exception once to absorb the difference of interest. try { + if (!funcParam || funcParam["serializedTx"] == undefined) { + throw "JSON parse error!"; + } + + const serializedTx = funcParam["serializedTx"]; + logger.info("serializedTx :" + serializedTx); + const web3 = new Web3(); web3.setProvider( new web3.providers.HttpProvider(configRead("ledgerUrl")), ); const res = web3.eth.sendRawTransaction(serializedTx); + const result = { + txid: res, + }; + const signedResults = signMessageJwt({ result }); + logger.debug(`sendRawTransaction(): signedResults: ${signedResults}`); retObj = { - status: 200, - txid: res, + resObj: { + status: 200, + data: signedResults, + }, }; + if (reqID !== undefined) { + retObj["id"] = reqID; + } + logger.debug(`##sendRawTransaction: retObj: ${JSON.stringify(retObj)}`); return resolve(retObj); } catch (e) { retObj = { - status: 504, - errorDetail: safeStringify(e), + resObj: { + status: 504, + errorDetail: safeStringify(e), + }, }; logger.error(retObj); + if (reqID !== undefined) { + retObj["id"] = reqID; + } + logger.debug(`##sendRawTransaction: retObj: ${JSON.stringify(retObj)}`); + return reject(retObj); } }); diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/index.ts b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/index.ts new file mode 100644 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; 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 new file mode 100644 index 0000000000..34f93f03a0 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/main/typescript/public-api.ts @@ -0,0 +1 @@ +export { startGoEthereumSocketIOConnector } from "./common/core/bin/www" diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/solidity/hello-world-contract/HelloWorld.json b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/solidity/hello-world-contract/HelloWorld.json new file mode 100644 index 0000000000..29f7eb9fc7 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/solidity/hello-world-contract/HelloWorld.json @@ -0,0 +1,418 @@ +{ + "contractName": "HelloWorld", + "abi": [ + { + "inputs": [], + "name": "getName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sayHello", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "newName", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.7.2+commit.51b20bc0\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"getName\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sayHello\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"newName\",\"type\":\"string\"}],\"name\":\"setName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":\"HelloWorld\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":{\"keccak256\":\"0x0b78fa11f33f7936a80da194c49f04198e38947e3f98f3a7b765b4adb4c455c1\",\"urls\":[\"bzz-raw://12697aa12341c70ed7a411a27a17398dcb2d4336a14dac51845e2123acf174c7\",\"dweb:/ipfs/QmPhH1UbHtUeeen9W2qMDwEVVWAtVJSMN29Nch5q8Gax1D\"]}},\"version\":1}", + "bytecode": "60c0604052600d60808190526c4361707461696e43616374757360981b60a090815261002e9160009190610041565b5034801561003b57600080fd5b506100d4565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008257805160ff19168380011785556100af565b828001600101855582156100af579182015b828111156100af578251825591602001919060010190610094565b506100bb9291506100bf565b5090565b5b808211156100bb57600081556001016100c0565b61030f806100e36000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220b72c0e77fdf429e47051940ba62646ae01296865473f15795ffca6619fe80f9964736f6c63430007020033", + "deployedBytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220b72c0e77fdf429e47051940ba62646ae01296865473f15795ffca6619fe80f9964736f6c63430007020033", + "sourceMap": "463:37:0:-:0;439:322;463:37;;439:322;463:37;;;-1:-1:-1;;;463:37:0;;;;;;-1:-1:-1;;463:37:0;;:::i;:::-;;439:322;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;439:322:0;;;-1:-1:-1;439:322:0;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "439:322:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;683:76;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;683:76:0;;-1:-1:-1;683:76:0;;-1:-1:-1;;;;;683:76:0:i;:::-;;505:89;;;:::i;598:81::-;670:4;663:11;;;;;;;;-1:-1:-1;;663:11:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;638:13;;663:11;;670:4;;663:11;;670:4;663:11;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;:::o;683:76::-;740:14;;;;:4;;:14;;;;;:::i;:::-;;683:76;:::o;505:89::-;568:21;;;;;;;;;;;;-1:-1:-1;;;568:21:0;;;;505:89;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;", + "sourcePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", + "compiler": { + "name": "solc", + "version": "0.7.2+commit.51b20bc0" + }, + "networks":{}, + "ast": { + "absolutePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-test-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", + "exportedSymbols": { + "HelloWorld": [ + 31 + ] + }, + "id": 32, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + ">=", + "0.7", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "413:24:0" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 31, + "linearizedBaseContracts": [ + 31 + ], + "name": "HelloWorld", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": false, + "id": 4, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 31, + "src": "463:37:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string" + }, + "typeName": { + "id": 2, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "463:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": { + "hexValue": "4361707461696e436163747573", + "id": 3, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "485:15:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bdd2f21877c99489ddcc32737686677f40d460368c7982ce22ce4f72b41b0312", + "typeString": "literal_string \"CaptainCactus\"" + }, + "value": "CaptainCactus" + }, + "visibility": "private" + }, + { + "body": { + "id": 11, + "nodeType": "Block", + "src": "562:32:0", + "statements": [ + { + "expression": { + "hexValue": "48656c6c6f20576f726c6421", + "id": 9, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "575:14:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0", + "typeString": "literal_string \"Hello World!\"" + }, + "value": "Hello World!" + }, + "functionReturnParameters": 8, + "id": 10, + "nodeType": "Return", + "src": "568:21:0" + } + ] + }, + "functionSelector": "ef5fb05b", + "id": 12, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sayHello", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 5, + "nodeType": "ParameterList", + "parameters": [], + "src": "523:2:0" + }, + "returnParameters": { + "id": 8, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 7, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 12, + "src": "547:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 6, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "547:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "546:15:0" + }, + "scope": 31, + "src": "505:89:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 19, + "nodeType": "Block", + "src": "655:24:0", + "statements": [ + { + "expression": { + "id": 17, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "670:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "functionReturnParameters": 16, + "id": 18, + "nodeType": "Return", + "src": "663:11:0" + } + ] + }, + "functionSelector": "17d7de7c", + "id": 20, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 13, + "nodeType": "ParameterList", + "parameters": [], + "src": "614:2:0" + }, + "returnParameters": { + "id": 16, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 20, + "src": "638:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 14, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "638:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "637:15:0" + }, + "scope": 31, + "src": "598:81:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 29, + "nodeType": "Block", + "src": "732:27:0", + "statements": [ + { + "expression": { + "id": 27, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 25, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "740:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 26, + "name": "newName", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 22, + "src": "747:7:0", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + "src": "740:14:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "id": 28, + "nodeType": "ExpressionStatement", + "src": "740:14:0" + } + ] + }, + "functionSelector": "c47f0027", + "id": 30, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 23, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 22, + "mutability": "mutable", + "name": "newName", + "nodeType": "VariableDeclaration", + "scope": 30, + "src": "700:21:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 21, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "700:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "699:23:0" + }, + "returnParameters": { + "id": 24, + "nodeType": "ParameterList", + "parameters": [], + "src": "732:0:0" + }, + "scope": 31, + "src": "683:76:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 32, + "src": "439:322:0" + } + ], + "src": "413:349:0" + }, + "functionHashes": { + "getName()": "17d7de7c", + "sayHello()": "ef5fb05b", + "setName(string)": "c47f0027" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "156600", + "executionCost": "infinite", + "totalCost": "infinite" + }, + "external": { + "getName()": "infinite", + "sayHello()": "infinite", + "setName(string)": "infinite" + } + } +} \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/solidity/hello-world-contract/HelloWorld.sol b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/solidity/hello-world-contract/HelloWorld.sol new file mode 100644 index 0000000000..befd843936 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/solidity/hello-world-contract/HelloWorld.sol @@ -0,0 +1,26 @@ +// ***************************************************************************** +// IMPORTANT: If you update this code then make sure to recompile +// it and update the .json file as well so that they +// remain in sync for consistent test executions. +// With that said, there shouldn't be any reason to recompile this, like ever... +// ***************************************************************************** + +pragma solidity >=0.7.0; + +contract HelloWorld { + string private name = "CaptainCactus"; + + function sayHello () public pure returns (string memory) { + return 'Hello World!'; + } + + function getName() public view returns (string memory) + { + return name; + } + + function setName(string memory newName) public + { + name = newName; + } +} 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 new file mode 100644 index 0000000000..583d545b11 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/src/test/typescript/integration/go-ethereum-socketio-connector.test.ts @@ -0,0 +1,409 @@ +/** + * Functional test of basic operations on go-ethereum validator (packages/cactus-plugin-ledger-connector-go-ethereum-socketio). + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const testLogLevel: LogLevelDesc = "info"; +const sutLogLevel: LogLevelDesc = "info"; +const testTimeout = 1000 * 60; // 1 minute timeout for some tests +const setupTimeout = 1000 * 60; // 1 minute timeout for setup + +// Ledger settings +const imageName = "openethereum/openethereum"; +const imageVersion = "v3.3.5"; + +// ApiClient settings +const syncReqTimeout = 1000 * 10; // 10 seconds + +import { + OpenEthereumTestLedger, + pruneDockerAllIfGithubAction, + SelfSignedPkiGenerator, +} from "@hyperledger/cactus-test-tooling"; + +import { + LogLevelDesc, + LoggerProvider, + Logger, +} from "@hyperledger/cactus-common"; + +import { SocketIOApiClient } from "@hyperledger/cactus-api-client"; + +import HelloWorldContractJson from "../../solidity/hello-world-contract/HelloWorld.json"; + +import "jest-extended"; +import { Server as HttpsServer } from "https"; +import { Account } from "web3-core"; + +/** + * @todo Replace with TS import when validators web3 client is upgraded. + * Current typing has very poor and wrong type definitions, better ignore them alltogether. + */ +const Web3 = require("web3"); + +// Logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "go-ethereum-socketio-connector.test", + level: testLogLevel, +}); + +describe("Go-Ethereum-SocketIO connector tests", () => { + let ledger: OpenEthereumTestLedger; + let web3: any; + let contractAddress: string; + let connectorCertValue: string; + let connectorPrivKeyValue: string; + let connectorServer: HttpsServer; + let apiClient: SocketIOApiClient; + let constTestAcc: Account; + const constTestAccBalance = 5 * 1000000; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + async function deploySmartContract(): Promise { + const txReceipt = await ledger.deployContract(HelloWorldContractJson.abi as any, "0x" + HelloWorldContractJson.bytecode); + expect(txReceipt.contractAddress).toBeTruthy(); + expect(txReceipt.status).toBeTrue(); + expect(txReceipt.blockHash).toBeTruthy(); + expect(txReceipt.blockNumber).toBeGreaterThan(1); + log.debug("Deployed test smart contract, TX on block number", txReceipt.blockNumber); + return txReceipt.contractAddress ?? ""; + } + + beforeAll(async () => { + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + + log.info(`Start Ledger ${imageName}:${imageVersion}...`); + ledger = new OpenEthereumTestLedger({ + imageName, + imageVersion, + emitContainerLogs: true, + logLevel: sutLogLevel + }); + await ledger.start(); + const ledgerRpcUrl = await ledger.getRpcApiHttpHost(); + log.info(`Ledger started, RPC: ${ledgerRpcUrl}`); + + // Create Test Account + constTestAcc = await ledger.createEthTestAccount(constTestAccBalance); + + // Create separate Web3 provider + web3 = new Web3(); + web3.setProvider( + new web3.providers.HttpProvider(ledgerRpcUrl), + ); + + // Deploy test smart contract + contractAddress = await deploySmartContract(); + + // Generate connector private key and certificate + const pkiGenerator = new SelfSignedPkiGenerator(); + const pki = pkiGenerator.create("localhost"); + connectorPrivKeyValue = pki.privateKeyPem; + connectorCertValue = pki.certificatePem; + const jwtAlgo = "RS512"; + + const connectorConfig: any = { + sslParam: { + port: 0, // random port + keyValue: connectorPrivKeyValue, + certValue: connectorCertValue, + jwtAlgo: jwtAlgo, + }, + logLevel: sutLogLevel, + ledgerUrl: ledgerRpcUrl, + }; + const configJson = JSON.stringify(connectorConfig); + log.debug("Connector Config:", configJson); + + log.info("Export connector config before loading the module..."); + process.env["NODE_CONFIG"] = configJson; + + // Load connector module + const connectorModule = await import("../../../main/typescript/index"); + + // Run the connector + connectorServer = await connectorModule.startGoEthereumSocketIOConnector(); + expect(connectorServer).toBeTruthy(); + const connectorAddress = connectorServer.address(); + if (!connectorAddress || typeof connectorAddress === "string") { + throw new Error("Unexpected go-ethereum connector AddressInfo type"); + } + log.info( + "Go-Ethereum-SocketIO Connector started on:", + `${connectorAddress.address}:${connectorAddress.port}`, + ); + + // Create ApiClient instance + const apiConfigOptions = { + validatorID: "go-eth-socketio-test", + validatorURL: `https://localhost:${connectorAddress.port}`, + validatorKeyValue: connectorCertValue, + logLevel: sutLogLevel, + maxCounterRequestID: 1000, + syncFunctionTimeoutMillisecond: syncReqTimeout, + socketOptions: { + rejectUnauthorized: false, + reconnection: false, + timeout: syncReqTimeout * 2, + }, + }; + log.debug("ApiClient config:", apiConfigOptions); + apiClient = new SocketIOApiClient(apiConfigOptions); + }, setupTimeout); + + afterAll(async () => { + log.info("FINISHING THE TESTS"); + + if (apiClient) { + log.info("Close ApiClient connection..."); + apiClient.close(); + } + + if (connectorServer) { + log.info("Stop the fabric connector..."); + await new Promise((resolve) => + connectorServer.close(() => resolve()), + ); + } + + if (ledger) { + log.info("Stop the ethereum ledger..."); + await ledger.stop(); + await ledger.destroy(); + } + + // SocketIOApiClient has timeout running for each request which is not cancellable at the moment. + // Wait timeout amount of seconds to make sure all handles are closed. + await new Promise((resolve) => setTimeout(resolve, syncReqTimeout)) + + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + }, setupTimeout); + + ////////////////////////////////// + // Tests + ////////////////////////////////// + + /** + * Simple test to see if test ethereum ledger is running correctly. + * Doesn't use apiClient or validator. + */ + test("Sanity check ledger connection", async () => { + const balance = web3.eth.getBalance(constTestAcc.address); + expect(balance).toBeTruthy(); + expect(balance.valueOf()).toEqual(constTestAccBalance.toString()); + }); + + /** + * Test ServerPlugin getNumericBalance function. + */ + test("Function getNumericBalance returns const account balance", async () => { + const method = { type: "function", command: "getNumericBalance" }; + const argsParam = { + args: [constTestAcc.address] + }; + + const response = await apiClient.sendSyncRequest( + {}, + method, + argsParam, + ); + + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + expect(response.amount).toEqual(constTestAccBalance); + }); + + /** + * Test ServerPlugin transferNumericAsset function. + * @deprecated - Not usable, can't unlock account remotely at the moment. + */ + test.skip("Function transferNumericAsset transfers asset between two accounts", async () => { + // Setup Accounts + const fromAccInitBalance = 1000; + const toAccInitBalance = 1000; + const transferAmount = 500; + const fromAcc = await ledger.createEthTestAccount(fromAccInitBalance); + const toAcc = await ledger.createEthTestAccount(toAccInitBalance); + + const method = { type: "function", command: "transferNumericAsset" }; + const argsParam = { + args: [{ + fromAddress: fromAcc.address, + toAddress: toAcc.address, + amount: transferAmount, + }] + }; + + const response = await apiClient.sendSyncRequest( + {}, + method, + argsParam, + ); + + expect(response).toBeTruthy(); + }); + + /** + * Test ServerPlugin getNonce function. + */ + test("Function getNonce returns const account nonce which should be 0", async () => { + const method = { type: "function", command: "getNonce" }; + const args = { args: { args: [constTestAcc.address] } }; + + const response = await apiClient.sendSyncRequest( + {}, + method, + args, + ); + + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + expect(response.data).toBeTruthy(); + expect(response.data.nonce).toEqual(0); + expect(response.data.nonceHex).toEqual("0x0"); + }); + + /** + * Test ServerPlugin toHex function. + */ + test("Function toHex returns converted value", async () => { + const value = 7365235; + const method = { type: "function", command: "toHex" }; + const args = { args: { args: [value] } }; + + const response = await apiClient.sendSyncRequest( + {}, + method, + args, + ); + + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + expect(response.data).toBeTruthy(); + expect(response.data.hexStr).toEqual("0x" + value.toString(16)); + }); + + /** + * Test ServerPlugin sendRawTransaction function. + */ + test("Function sendRawTransaction registers new transfer transaction", async () => { + // Setup Accounts + const fromAccInitBalance = 1000; + const toAccInitBalance = 1000; + const transferAmount = 500; + const fromAcc = await ledger.createEthTestAccount(fromAccInitBalance); + const toAcc = await ledger.createEthTestAccount(toAccInitBalance); + + const signedTx = await fromAcc.signTransaction({ + from: fromAcc.address, + to: toAcc.address, + value: transferAmount, + gas: 1000000, + }); + expect(signedTx).toBeTruthy(); + log.warn(signedTx); + + const method = { type: "function", command: "sendRawTransaction" }; + const args = { args: [{ serializedTx: signedTx.rawTransaction }] }; + + const response = await apiClient.sendSyncRequest( + {}, + method, + args, + ); + + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + expect(response.data).toBeTruthy(); + expect(response.data.txid.length).toBeGreaterThan(0); + expect(response.data.txid).toStartWith("0x"); + }); + + /** + * Test ServerPlugin web3Eth function. + */ + test("Function web3Eth returns results of web3.eth.getBalance", async () => { + const method = { type: "web3Eth", command: "getBalance" }; + const args = { args: [constTestAcc.address] }; + + const response = await apiClient.sendSyncRequest( + {}, + method, + args, + ); + + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + expect(response.data).toBeTruthy(); + expect(response.data).toEqual(constTestAccBalance.toString()); + }); + + /** + * Test ServerPlugin contract function. + */ + test("Calling pure smart contract method works", async () => { + const contract = { abi: HelloWorldContractJson.abi, address: contractAddress }; + const method = { type: "contract", command: "sayHello", function: "call" }; + const args = { args: [] }; + + const response = await apiClient.sendSyncRequest( + contract, + method, + args, + ); + + expect(response).toBeTruthy(); + expect(response.status).toEqual(200); + expect(response.data).toBeTruthy(); + expect(response.data).toEqual("Hello World!"); + }); + + /** + * Test ServerMonitorPlugin startMonitor/stopMonitor functions. + */ + test("Monitoring returns new block", async () => { + // Create monitoring promise and subscription + let monitorSub: any; + const newBlockPromise = new Promise((resolve, reject) => { + monitorSub = apiClient.watchBlocksV1().subscribe({ + next: block => resolve(block), + error: err => reject(err), + complete: () => reject("Unexpected watchBlocksV1 completion - reject."), + }); + }); + + try { + // Repeat deploySmartContract until block was received + while (true) { + const deployPromise = deploySmartContract(); + const resolvedValue = await Promise.race([newBlockPromise, deployPromise]); + log.debug("Monitor: resolvedValue", resolvedValue); + if (resolvedValue && resolvedValue.blockData) { + log.info("Resolved watchBlock promise"); + expect(resolvedValue.status).toEqual(200); + expect(resolvedValue.blockData.number).toBeGreaterThan(1); + expect(resolvedValue.blockData.transactions.length).toBeGreaterThan(0); + break; + } + // Sleep 1 second and try again + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + } catch (error) { + throw error; + } finally { + if (monitorSub) { + monitorSub.unsubscribe(); + } else { + log.warn("monitorSub was not valid, could not unsubscribe"); + } + } + }, testTimeout); +}); diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/tsconfig.json b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/tsconfig.json index 5280213808..336b4d803f 100644 --- a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/tsconfig.json +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/tsconfig.json @@ -13,11 +13,21 @@ "./src/main/typescript/common/core/*.ts", "./src/main/typescript/common/core/bin/*.ts", "./src/main/typescript/common/core/config/*.ts", - "./src/main/typescript/connector/*.ts" + "./src/main/typescript/connector/*.ts", + "./src/main/typescript/*.ts" ], "references": [ { "path": "../cactus-cmd-socketio-server/tsconfig.json" + }, + { + "path": "../cactus-test-tooling/tsconfig.json" + }, + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-api-client/tsconfig.json" } ] } diff --git a/packages/cactus-test-tooling/src/main/typescript/openethereum/openethereum-test-ledger.ts b/packages/cactus-test-tooling/src/main/typescript/openethereum/openethereum-test-ledger.ts index 6457019ce0..c87380c1a4 100644 --- a/packages/cactus-test-tooling/src/main/typescript/openethereum/openethereum-test-ledger.ts +++ b/packages/cactus-test-tooling/src/main/typescript/openethereum/openethereum-test-ledger.ts @@ -2,7 +2,8 @@ import { EventEmitter } from "events"; import Docker, { Container } from "dockerode"; import { v4 as internalIpV4 } from "internal-ip"; import Web3 from "web3"; -import { Account } from "web3-core"; +import { AbiItem } from "web3-utils"; +import { Account, TransactionReceipt } from "web3-core"; import { v4 as uuidv4 } from "uuid"; import { @@ -14,6 +15,7 @@ import { } from "@hyperledger/cactus-common"; import { Containers } from "../common/containers"; +import { RuntimeError } from "run-time-error"; export interface IOpenEthereumTestLedgerOptions { envVars?: string[]; @@ -55,6 +57,7 @@ export class OpenEthereumTestLedger { private readonly httpPort: number; private _container: Container | undefined; private _containerId: string | undefined; + private _web3: Web3 | undefined; public get imageFqn(): string { return `${this.imageName}:${this.imageVersion}`; @@ -72,6 +75,16 @@ export class OpenEthereumTestLedger { } } + private get web3(): Web3 { + if (this._web3) { + return this._web3; + } else { + throw new Error( + "Invalid state: web3 client is missing, start the ledger container first.", + ); + } + } + constructor(public readonly options: IOpenEthereumTestLedgerOptions) { const fnTag = `${this.className}#constructor()`; Checks.truthy(options, `${fnTag} arg options`); @@ -123,7 +136,7 @@ export class OpenEthereumTestLedger { "--jsonrpc-cors=all", "--jsonrpc-interface=all", "--jsonrpc-hosts=all", - "--jsonrpc-apis=web3,eth,net,parity", + "--jsonrpc-apis=web3,eth,personal,net,parity", "--ws-interface=all", "--ws-apis=web3,eth,net,parity,pubsub", "--ws-origins=all", @@ -166,6 +179,7 @@ export class OpenEthereumTestLedger { try { await Containers.waitForHealthCheck(this._containerId); + this._web3 = new Web3(await this.getRpcApiHttpHost()); resolve(container); } catch (ex) { reject(ex); @@ -178,20 +192,67 @@ export class OpenEthereumTestLedger { * Creates a new ETH account from scratch on the ledger and then sends it a * little seed money to get things started. * + * Uses `web3.eth.accounts.create` + * * @param [seedMoney=10e8] The amount of money to seed the new test account with. */ public async createEthTestAccount(seedMoney = 10e8): Promise { - const fnTag = `${this.className}#getEthTestAccount()`; + const ethTestAccount = this.web3.eth.accounts.create(uuidv4()); + + const receipt = await this.transferAssetFromCoinbase( + ethTestAccount.address, + seedMoney, + ); + + if (receipt instanceof Error) { + throw new RuntimeError("Error in createEthTestAccount", receipt); + } else { + return ethTestAccount; + } + } + + /** + * Creates a new personal ethereum account with specified initial money and password. + * + * Uses `web3.eth.personal.newAccount` + * + * @param seedMoney Initial money to transfer to this account + * @param password Personal account password + * @returns New account address + */ + public async newEthPersonalAccount( + seedMoney = 10e8, + password = "test", + ): Promise { + const account = await this.web3.eth.personal.newAccount(password); + + const receipt = await this.transferAssetFromCoinbase(account, seedMoney); + + if (receipt instanceof Error) { + throw new RuntimeError("Error in newEthPersonalAccount", receipt); + } else { + return account; + } + } - const rpcApiHttpHost = await this.getRpcApiHttpHost(); - const web3 = new Web3(rpcApiHttpHost); - const ethTestAccount = web3.eth.accounts.create(uuidv4()); + /** + * Seed `targetAccount` with money from coin base account. + * + * @param targetAccount Ethereum account to send money to. + * @param value Amount of money. + * @returns Transfer `TransactionReceipt` + */ + public async transferAssetFromCoinbase( + targetAccount: string, + value: number, + ): Promise { + const fnTag = `${this.className}#transferAssetFromCoinbase()`; - const tx = await web3.eth.accounts.signTransaction( + const tx = await this.web3.eth.accounts.signTransaction( { from: K_DEV_WHALE_ACCOUNT_PUBLIC_KEY, - to: ethTestAccount.address, - value: seedMoney, + to: targetAccount, + value: value, gas: 1000000, }, K_DEV_WHALE_ACCOUNT_PRIVATE_KEY, @@ -201,13 +262,49 @@ export class OpenEthereumTestLedger { throw new Error(`${fnTag} Signing transaction failed, reason unknown.`); } - const receipt = await web3.eth.sendSignedTransaction(tx.rawTransaction); + return await this.web3.eth.sendSignedTransaction(tx.rawTransaction); + } - if (receipt instanceof Error) { - throw receipt; - } else { - return ethTestAccount; + /** + * Deploy contract from coin base account to the ledger. + * + * @param abi - JSON interface of the contract. + * @param bytecode - Compiled code of the contract. + * @param args - Contract arguments. + * @returns Contract deployment `TransactionReceipt` + */ + public async deployContract( + abi: AbiItem | AbiItem[], + bytecode: string, + args?: any[], + ): Promise { + // Encode ABI + const contractProxy = new this.web3.eth.Contract(abi); + const encodedDeployReq = contractProxy + .deploy({ + data: bytecode, + arguments: args, + }) + .encodeABI(); + + // Send TX + const signedTx = await this.web3.eth.accounts.signTransaction( + { + from: K_DEV_WHALE_ACCOUNT_PUBLIC_KEY, + data: encodedDeployReq, + gas: 1000000, + nonce: await this.web3.eth.getTransactionCount( + K_DEV_WHALE_ACCOUNT_PUBLIC_KEY, + ), + }, + K_DEV_WHALE_ACCOUNT_PRIVATE_KEY, + ); + + if (!signedTx.rawTransaction) { + throw new Error(`Signing transaction failed, reason unknown.`); } + + return await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction); } public async getRpcApiHttpHost( @@ -223,12 +320,14 @@ export class OpenEthereumTestLedger { public async stop(): Promise { if (this._container) { await Containers.stop(this.container); + this._web3 = undefined; } } public destroy(): Promise { const fnTag = `${this.className}#destroy()`; if (this._container) { + this._web3 = undefined; return this._container.remove(); } else { const ex = new Error(`${fnTag} Container not found, nothing to destroy.`);