diff --git a/.cspell.json b/.cspell.json index 57a07132243..56ca30c37a9 100644 --- a/.cspell.json +++ b/.cspell.json @@ -12,6 +12,7 @@ "approveformyorg", "Authz", "authzn", + "azaharac", "Besu", "Bools", "cafile", diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-deploy-contract-from-json.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-deploy-contract-from-json.test.ts new file mode 100644 index 00000000000..971da160d63 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-deploy-contract-from-json.test.ts @@ -0,0 +1,495 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import { Server as SocketIoServer } from "socket.io"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + EthContractInvocationType, + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + Web3SigningCredentialCactusKeychainRef, + ReceiptType, + BesuApiClient, + WatchBlocksV1Progress, + Web3BlockHeader, +} from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { Constants, PluginImportType } from "@hyperledger/cactus-core-api"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { K_CACTUS_BESU_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; +import { BesuApiClientOptions } from "../../../../../main/typescript/api-client/besu-api-client"; + +const testCase = "deploys contract via .json file"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/21.1.6/config/src/main/resources/dev.json + */ + + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-prometheus-exporter-metrics`, + ); + + const wsBasePath = apiHost + Constants.SocketIoConnectionPathV1; + t.comment("WS base path: " + wsBasePath); + const besuApiClientOptions = new BesuApiClientOptions({ basePath: apiHost }); + const apiClient = new BesuApiClient(besuApiClientOptions); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + timeoutMs: 60000, + }, + }); + + const blocks = await apiClient.watchBlocksV1(); + + const aBlockHeader = await new Promise((resolve, reject) => { + let done = false; + const timerId = setTimeout(() => { + if (!done) { + reject("Waiting for block header notification to arrive timed out"); + } + }, 10000); + const subscription = blocks.subscribe((res: WatchBlocksV1Progress) => { + subscription.unsubscribe(); + done = true; + clearTimeout(timerId); + resolve(res.blockHeader); + }); + }); + t.ok(aBlockHeader, "Web3BlockHeader truthy OK"); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + constructorArgs: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidv4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + timeoutMs: 60000, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + const setNameOut = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ifError(setNameOutInvalid); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + const response = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "deposit", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + value: 10, + }); + t2.ok(response, "deposit() payable invocation output is truthy OK"); + + const { callOutput } = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getNameByIndex", + params: [0], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.equal( + callOutput, + newName, + `getNameByIndex() output reflects the update OK`, + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.CactusKeychainRef", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + + const signingCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CactusKeychainRef, + }; + + const setNameOut = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + t2.ifError(setNameOutInvalid); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const { callOutput: getNameOut } = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + const response = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "deposit", + params: [], + gas: 1000000, + signingCredential, + value: 10, + }); + t2.ok(response, "deposit() payable invocation output is truthy OK"); + + const { callOutput } = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getNameByIndex", + params: [1], + gas: 1000000, + signingCredential, + }); + t2.equal( + callOutput, + newName, + `getNameByIndex() output reflects the update OK`, + ); + + t2.end(); + }); + + test("get prometheus exporter metrics", async (t2: Test) => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_BESU_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_BESU_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_BESU_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_BESU_TOTAL_TX_COUNT + + '"} 9'; + t2.ok(res, "Response truthy OK"); + t2.ok(res.data); + t2.equal(res.status, 200); + t2.true( + res.data.includes(promMetricsOutput), + "Total Transaction Count equals 9 OK.", + ); + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-balance.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-balance.test.ts new file mode 100644 index 00000000000..cad733a4993 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-balance.test.ts @@ -0,0 +1,73 @@ +import test, { Test } from "tape"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + GetBalanceV1Request, +} from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { BesuTestLedger } from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; + +test("can get balance of an account", async (t: Test) => { + const logLevel: LogLevelDesc = "TRACE"; + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/21.1.6/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const req: GetBalanceV1Request = { address: firstHighNetWorthAccount }; + const currentBalance = await connector.getBalance(req); + t.comment(JSON.stringify(currentBalance)); + //makes the information in to string + t.ok(currentBalance, " Balance response is OK :-)"); + t.equal(typeof currentBalance, "object", "Balance response type is OK :-)"); + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-block.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-block.test.ts new file mode 100644 index 00000000000..35b5434ad77 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-block.test.ts @@ -0,0 +1,66 @@ +import test, { Test } from "tape"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + GetBlockV1Request, +} from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { BesuTestLedger } from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; + +test("can get block from blockchain", async (t: Test) => { + const logLevel: LogLevelDesc = "TRACE"; + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const request: GetBlockV1Request = { blockHashOrBlockNumber: 0 }; + const currentBlock = await connector.getBlock(request); + t.comment(JSON.stringify(currentBlock)); + //makes the information in to string + t.ok(currentBlock, " Block response is OK :-)"); + t.equal(typeof currentBlock, "object", "Block response type is OK :-)"); + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-past-logs.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-past-logs.test.ts new file mode 100644 index 00000000000..09b9bda1000 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-past-logs.test.ts @@ -0,0 +1,74 @@ +import test, { Test } from "tape"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + GetPastLogsV1Request, +} from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { BesuTestLedger } from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; + +test("can get past logs of an account", async (t: Test) => { + const logLevel: LogLevelDesc = "TRACE"; + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/21.1.6/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const req: GetPastLogsV1Request = { address: firstHighNetWorthAccount }; + const pastLogs = await connector.getPastLogs(req); + t.comment(JSON.stringify(pastLogs)); + t.ok(pastLogs, "Past logs response is OK :-)"); + t.equal(typeof pastLogs, "object", "Past logs response type is OK :-)"); + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-record-locator.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-record-locator.test.ts new file mode 100644 index 00000000000..bcfed905614 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-record-locator.test.ts @@ -0,0 +1,344 @@ +import test, { Test } from "tape-promise/tape"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + EthContractInvocationType, + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + ReceiptType, + InvokeContractV1Request, + BesuApiClientOptions, + BesuApiClient, + GetBesuRecordV1Request, +} from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { Constants, PluginImportType } from "@hyperledger/cactus-core-api"; +import { AddressInfo } from "net"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { Server as SocketIoServer } from "socket.io"; + +const testCase = "get record locator"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/21.1.6/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-prometheus-exporter-metrics`, + ); + + const wsBasePath = apiHost + Constants.SocketIoConnectionPathV1; + t.comment("WS base path: " + wsBasePath); + const besuApiClientOptions = new BesuApiClientOptions({ basePath: apiHost }); + const api = new BesuApiClient(besuApiClientOptions); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract", async (t2: Test) => { + const deployOut = await connector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + constructorArgs: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("getBesuRecord test 1", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidv4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + const transactionReceipt = await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const request: GetBesuRecordV1Request = { + transactionHash: transactionReceipt.transactionReceipt.transactionHash, + }; + const getInputData = await api.getBesuRecordV1(request); + t2.ok(getInputData, "API response object is truthy"); + t2.end(); + }); + + test("getBesuRecord test 2", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + const setNameOut = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ifError(setNameOutInvalid); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const req: InvokeContractV1Request = { + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }; + const { callOutput: getNameOut } = await connector.getBesuRecord({ + invokeCall: req, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const response = await connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "deposit", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + value: 10, + }); + t2.ok(response, "deposit() payable invocation output is truthy OK"); + + const req2: InvokeContractV1Request = { + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getNameByIndex", + params: [0], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }; + + const { callOutput } = await connector.getBesuRecord({ invokeCall: req2 }); + t2.equal( + callOutput, + newName, + `getNameByIndex() output reflects the update OK`, + ); + + t2.end(); + }); + + t.end(); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-invoke-contract.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-invoke-contract.test.ts new file mode 100644 index 00000000000..a421100d68d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-invoke-contract.test.ts @@ -0,0 +1,381 @@ +import test, { Test } from "tape"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + EthContractInvocationType, + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + Web3SigningCredentialCactusKeychainRef, + ReceiptType, +} from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { BesuTestLedger } from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; + +test("deploys contract via .json file", async (t: Test) => { + const logLevel: LogLevelDesc = "TRACE"; + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/21.1.6/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + const contractName = "HelloWorld"; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + constructorArgs: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidv4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + const setNameOut = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + const response = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "deposit", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + value: 10, + }); + t2.ok(response, "deposit() payable invocation output is truthy OK"); + + const { callOutput } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getNameByIndex", + params: [0], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + t2.equal( + callOutput, + newName, + `getNameByIndex() output reflects the update OK`, + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.CactusKeychainRef", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + const signingCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CactusKeychainRef, + }; + + const setNameOut = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const { callOutput: getNameOut } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + const response = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "deposit", + params: [], + gas: 1000000, + signingCredential, + value: 10, + }); + t2.ok(response, "deposit() payable invocation output is truthy OK"); + + const { callOutput: callOut } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getNameByIndex", + params: [1], + gas: 1000000, + signingCredential, + }); + t2.equal( + callOut, + newName, + `getNameByIndex() output reflects the update OK`, + ); + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/v21-besu-get-transaction.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/v21-besu-get-transaction.test.ts new file mode 100644 index 00000000000..0066b5d1602 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/v21-besu-get-transaction.test.ts @@ -0,0 +1,103 @@ +import test, { Test } from "tape"; +import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + ReceiptType, + Web3SigningCredentialType, +} from "../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { BesuTestLedger } from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; +import { GetTransactionV1Request } from "../../../../main/typescript/generated/openapi/typescript-axios/api"; + +test("can get past logs of an account", async (t: Test) => { + const logLevel: LogLevelDesc = "TRACE"; + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/21.1.6/config/src/main/resources/dev.json + */ + + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + const privateKey = await besuTestLedger.getGenesisAccountPrivKey(); + const { transactionReceipt } = await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.LedgerBlockAck, + timeoutMs: 60000, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + + const req: GetTransactionV1Request = { + transactionHash: transactionReceipt.transactionHash, + }; + const response = await connector.getTransaction(req); + t.comment(JSON.stringify(response.transaction)); + t.ok(response.transaction, "getTransaction response is OK :-)"); + t.equal( + typeof response.transaction, + "object", + "getTransaction response type is OK :-)", + ); + t.end(); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-balance-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-balance-endpoint.test.ts new file mode 100644 index 00000000000..736372c6520 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-balance-endpoint.test.ts @@ -0,0 +1,179 @@ +import test, { Test } from "tape-promise/tape"; + +import { v4 as uuidv4 } from "uuid"; +import { createServer } from "http"; +import KeyEncoder from "key-encoder"; +import { AddressInfo } from "net"; +import Web3 from "web3"; +import EEAClient, { IWeb3InstanceExtended } from "web3-eea"; + +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + Secp256k1Keys, + KeyFormat, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +import { + BesuApiClientOptions, + BesuApiClient, + IPluginLedgerConnectorBesuOptions, + PluginLedgerConnectorBesu, + GetBalanceV1Request, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +const testCase = "API Client can call getBalance via network"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const keyEncoder: KeyEncoder = new KeyEncoder("secp256k1"); + const keychainId = uuidv4(); + const keychainRef = uuidv4(); + + const { privateKey } = Secp256k1Keys.generateKeyPairsBuffer(); + const keyHex = privateKey.toString("hex"); + const pem = keyEncoder.encodePrivate(keyHex, KeyFormat.Raw, KeyFormat.PEM); + + const keychain = new PluginKeychainMemory({ + backend: new Map([[keychainRef, pem]]), + keychainId, + logLevel, + instanceId: uuidv4(), + }); + + const httpServer1 = createServer(); + await new Promise((resolve, reject) => { + httpServer1.once("error", reject); + httpServer1.once("listening", resolve); + httpServer1.listen(0, "127.0.0.1"); + }); + const addressInfo1 = httpServer1.address() as AddressInfo; + t.comment(`HttpServer1 AddressInfo: ${JSON.stringify(addressInfo1)}`); + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + t.comment(`Cactus Node 1 Host: ${node1Host}`); + + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + const tearDown = async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }; + + test.onFinish(tearDown); + const testAccount = await besuTestLedger.createEthTestAccount(); + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [keychain] }); + + // 3. Instantiate the web service consortium plugin + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + const pluginValidatorBesu = new PluginLedgerConnectorBesu(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginValidatorBesu); + + const apiServer = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 7. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + t.comment(`AddressInfo: ${JSON.stringify(addressInfo1)}`); + + const web3Provider = new Web3.providers.HttpProvider(rpcApiHttpHost); + const web3 = new Web3(web3Provider); + const web3Eea: IWeb3InstanceExtended = EEAClient(web3, 1337); + + const orionKeyPair = await besuTestLedger.getOrionKeyPair(); + const besuKeyPair = await besuTestLedger.getBesuKeyPair(); + + const besuPrivateKey = besuKeyPair.privateKey.toLowerCase().startsWith("0x") + ? besuKeyPair.privateKey.substring(2) + : besuKeyPair.privateKey; // besu node's private key + + const contractOptions = { + data: `0x123`, + // privateFrom : Orion public key of the sender. + privateFrom: orionKeyPair.publicKey, + // privateFor : Orion public keys of recipients or privacyGroupId: Privacy group to receive the transaction + privateFor: [orionKeyPair.publicKey], + // privateKey: Ethereum private key with which to sign the transaction. + privateKey: besuPrivateKey, + }; + + const transactionHash = await web3Eea.eea.sendRawTransaction(contractOptions); + await web3.eth.getTransaction(transactionHash); + + /* + const transaction = await web3.eth.getTransaction(transactionHash); + const singData = jsObjectSigner.sign(transaction.input); + const signDataHex = Buffer.from(singData).toString("hex"); + */ + + const request: GetBalanceV1Request = { + //is it suppose to be GetBalanceV1Request? + address: testAccount.address, + }; + + const configuration = new BesuApiClientOptions({ basePath: node1Host }); + const api = new BesuApiClient(configuration); + + // Test for 200 valid response test case + const res = await api.getBalanceV1(request); + t.ok(res, "API response object is truthy"); + t.true(typeof res.data.balance === "string", "Response is String ok"); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-block-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-block-endpoint.test.ts new file mode 100644 index 00000000000..f63fee91fae --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-block-endpoint.test.ts @@ -0,0 +1,150 @@ +import test, { Test } from "tape-promise/tape"; + +import { v4 as uuidv4 } from "uuid"; +import { createServer } from "http"; +import KeyEncoder from "key-encoder"; +import { AddressInfo } from "net"; + +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + Secp256k1Keys, + KeyFormat, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +import { + BesuApiClientOptions, + BesuApiClient, + IPluginLedgerConnectorBesuOptions, + PluginLedgerConnectorBesu, + GetBlockV1Request, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +const testCase = "Test sign transaction endpoint"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const keyEncoder: KeyEncoder = new KeyEncoder("secp256k1"); + const keychainId = uuidv4(); + const keychainRef = uuidv4(); + + const { privateKey } = Secp256k1Keys.generateKeyPairsBuffer(); + const keyHex = privateKey.toString("hex"); + const pem = keyEncoder.encodePrivate(keyHex, KeyFormat.Raw, KeyFormat.PEM); + + const keychain = new PluginKeychainMemory({ + backend: new Map([[keychainRef, pem]]), + keychainId, + logLevel, + instanceId: uuidv4(), + }); + + const httpServer1 = createServer(); + await new Promise((resolve, reject) => { + httpServer1.once("error", reject); + httpServer1.once("listening", resolve); + httpServer1.listen(0, "127.0.0.1"); + }); + const addressInfo1 = httpServer1.address() as AddressInfo; + t.comment(`HttpServer1 AddressInfo: ${JSON.stringify(addressInfo1)}`); + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + t.comment(`Cactus Node 1 Host: ${node1Host}`); + + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + const tearDown = async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }; + + test.onFinish(tearDown); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [keychain] }); + + // 3. Instantiate the web service consortium plugin + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + const pluginValidatorBesu = new PluginLedgerConnectorBesu(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginValidatorBesu); + + const apiServer = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 7. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + t.comment(`AddressInfo: ${JSON.stringify(addressInfo1)}`); + + const request: GetBlockV1Request = { + blockHashOrBlockNumber: 0, + }; + + const configuration = new BesuApiClientOptions({ basePath: node1Host }); + const api = new BesuApiClient(configuration); + + // Test for 200 valid response test case + const res = await api.getBlockV1(request); + + const { status, data } = res; + t.true(status >= 200, "status GTE 200 OK"); + t.true(status < 300, "status LT 300 OK"); + t.ok(data, "GetBlockResponse Truthy OK"); + t.true(typeof data.block === "object", "Response data is OK"); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-past-logs-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-past-logs-endpoint.test.ts new file mode 100644 index 00000000000..76da5d646fb --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-past-logs-endpoint.test.ts @@ -0,0 +1,174 @@ +import test, { Test } from "tape-promise/tape"; + +import { v4 as uuidv4 } from "uuid"; +import { createServer } from "http"; +import KeyEncoder from "key-encoder"; +import { AddressInfo } from "net"; +import Web3 from "web3"; +import EEAClient, { IWeb3InstanceExtended } from "web3-eea"; + +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + Secp256k1Keys, + KeyFormat, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +import { + BesuApiClientOptions, + BesuApiClient, + IPluginLedgerConnectorBesuOptions, + PluginLedgerConnectorBesu, + GetPastLogsV1Request, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +const testCase = "API client can call getPastLogs via network"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const keyEncoder: KeyEncoder = new KeyEncoder("secp256k1"); + const keychainId = uuidv4(); + const keychainRef = uuidv4(); + + const { privateKey } = Secp256k1Keys.generateKeyPairsBuffer(); + const keyHex = privateKey.toString("hex"); + const pem = keyEncoder.encodePrivate(keyHex, KeyFormat.Raw, KeyFormat.PEM); + + const keychain = new PluginKeychainMemory({ + backend: new Map([[keychainRef, pem]]), + keychainId, + logLevel, + instanceId: uuidv4(), + }); + + const httpServer1 = createServer(); + await new Promise((resolve, reject) => { + httpServer1.once("error", reject); + httpServer1.once("listening", resolve); + httpServer1.listen(0, "127.0.0.1"); + }); + const addressInfo1 = httpServer1.address() as AddressInfo; + t.comment(`HttpServer1 AddressInfo: ${JSON.stringify(addressInfo1)}`); + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + t.comment(`Cactus Node 1 Host: ${node1Host}`); + + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + const tearDown = async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }; + + test.onFinish(tearDown); + const testAccount = await besuTestLedger.createEthTestAccount(); + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [keychain] }); + + // 3. Instantiate the web service consortium plugin + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + const pluginValidatorBesu = new PluginLedgerConnectorBesu(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginValidatorBesu); + + const apiServer = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 7. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + t.comment(`AddressInfo: ${JSON.stringify(addressInfo1)}`); + + const web3Provider = new Web3.providers.HttpProvider(rpcApiHttpHost); + const web3 = new Web3(web3Provider); + const web3Eea: IWeb3InstanceExtended = EEAClient(web3, 1337); + + const orionKeyPair = await besuTestLedger.getOrionKeyPair(); + const besuKeyPair = await besuTestLedger.getBesuKeyPair(); + + const besuPrivateKey = besuKeyPair.privateKey.toLowerCase().startsWith("0x") + ? besuKeyPair.privateKey.substring(2) + : besuKeyPair.privateKey; // besu node's private key + + const contractOptions = { + data: `0x123`, + // privateFrom : Orion public key of the sender. + privateFrom: orionKeyPair.publicKey, + // privateFor : Orion public keys of recipients or privacyGroupId: Privacy group to receive the transaction + privateFor: [orionKeyPair.publicKey], + // privateKey: Ethereum private key with which to sign the transaction. + privateKey: besuPrivateKey, + }; + + const transactionHash = await web3Eea.eea.sendRawTransaction(contractOptions); + + await web3.eth.getTransaction(transactionHash); + + const request: GetPastLogsV1Request = { + address: testAccount.address, + }; + + const configuration = new BesuApiClientOptions({ basePath: node1Host }); + const api = new BesuApiClient(configuration); + + const res = await api.getPastLogsV1(request); + // const { } = res; + t.ok(res, "API response object is truthy"); + t.ok(res.data.logs, "Response.logs is truthy ok"); + t.true(Array.isArray(res.data.logs), "Response.logs is Array ok"); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-transaction-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-transaction-endpoint.test.ts new file mode 100644 index 00000000000..ad161d11a2e --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-get-transaction-endpoint.test.ts @@ -0,0 +1,179 @@ +import test, { Test } from "tape-promise/tape"; + +import { v4 as uuidv4 } from "uuid"; +import { createServer } from "http"; +import KeyEncoder from "key-encoder"; +import { AddressInfo } from "net"; +import Web3 from "web3"; +import EEAClient, { IWeb3InstanceExtended } from "web3-eea"; + +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + Secp256k1Keys, + KeyFormat, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +import { + BesuApiClientOptions, + BesuApiClient, + IPluginLedgerConnectorBesuOptions, + PluginLedgerConnectorBesu, + GetTransactionV1Request, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +const testCase = "API client can call get-transaction via network"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const keyEncoder: KeyEncoder = new KeyEncoder("secp256k1"); + const keychainId = uuidv4(); + const keychainRef = uuidv4(); + + const { privateKey } = Secp256k1Keys.generateKeyPairsBuffer(); + const keyHex = privateKey.toString("hex"); + const pem = keyEncoder.encodePrivate(keyHex, KeyFormat.Raw, KeyFormat.PEM); + + const keychain = new PluginKeychainMemory({ + backend: new Map([[keychainRef, pem]]), + keychainId, + logLevel, + instanceId: uuidv4(), + }); + + const httpServer1 = createServer(); + await new Promise((resolve, reject) => { + httpServer1.once("error", reject); + httpServer1.once("listening", resolve); + httpServer1.listen(0, "127.0.0.1"); + }); + const addressInfo1 = httpServer1.address() as AddressInfo; + t.comment(`HttpServer1 AddressInfo: ${JSON.stringify(addressInfo1)}`); + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + t.comment(`Cactus Node 1 Host: ${node1Host}`); + + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + const tearDown = async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }; + + test.onFinish(tearDown); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [keychain] }); + + // 3. Instantiate the web service consortium plugin + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + const pluginValidatorBesu = new PluginLedgerConnectorBesu(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginValidatorBesu); + + const apiServer = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 7. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + t.comment(`AddressInfo: ${JSON.stringify(addressInfo1)}`); + + const web3Provider = new Web3.providers.HttpProvider(rpcApiHttpHost); + const web3 = new Web3(web3Provider); + const web3Eea: IWeb3InstanceExtended = EEAClient(web3, 1337); + + const orionKeyPair = await besuTestLedger.getOrionKeyPair(); + const besuKeyPair = await besuTestLedger.getBesuKeyPair(); + + const besuPrivateKey = besuKeyPair.privateKey.toLowerCase().startsWith("0x") + ? besuKeyPair.privateKey.substring(2) + : besuKeyPair.privateKey; // besu node's private key + + const contractOptions = { + data: `0x123`, + // privateFrom : Orion public key of the sender. + privateFrom: orionKeyPair.publicKey, + // privateFor : Orion public keys of recipients or privacyGroupId: Privacy group to receive the transaction + privateFor: [orionKeyPair.publicKey], + // privateKey: Ethereum private key with which to sign the transaction. + privateKey: besuPrivateKey, + }; + + const transactionHash = await web3Eea.eea.sendRawTransaction(contractOptions); + + await web3.eth.getTransaction(transactionHash); + + const request: GetTransactionV1Request = { + transactionHash: transactionHash, + }; + + const configuration = new BesuApiClientOptions({ basePath: node1Host }); + const api = new BesuApiClient(configuration); + + // Test for 200 valid response test case + const res = await api.getTransactionV1(request); + + // const { } = res; + t.ok(res, "API response object is truthy"); + t.ok(res.data.transaction, "Transaction is truthy ok"); + t.true( + typeof res.data.transaction === "object", + "Response.transaction is OK", + ); +}); + +test("AFTER " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-sign-transaction-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-sign-transaction-endpoint.test.ts new file mode 100644 index 00000000000..f2e93b9ad79 --- /dev/null +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-sign-transaction-endpoint.test.ts @@ -0,0 +1,198 @@ +import test, { Test } from "tape-promise/tape"; + +import { v4 as uuidv4 } from "uuid"; +import { createServer } from "http"; +import KeyEncoder from "key-encoder"; +import { AddressInfo } from "net"; +import Web3 from "web3"; +import EEAClient, { IWeb3InstanceExtended } from "web3-eea"; + +import { + ApiServer, + AuthorizationProtocol, + ConfigService, +} from "@hyperledger/cactus-cmd-api-server"; +import { + JsObjectSigner, + IJsObjectSignerOptions, + Secp256k1Keys, + KeyFormat, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +import { + BesuApiClientOptions, + BesuApiClient, + IPluginLedgerConnectorBesuOptions, + PluginLedgerConnectorBesu, + SignTransactionRequest, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; + +import { PluginRegistry } from "@hyperledger/cactus-core"; + +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; + +const testCase = "Test sign transaction endpoint"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const keyEncoder: KeyEncoder = new KeyEncoder("secp256k1"); + const keychainId = uuidv4(); + const keychainRef = uuidv4(); + + const { privateKey } = Secp256k1Keys.generateKeyPairsBuffer(); + const keyHex = privateKey.toString("hex"); + const pem = keyEncoder.encodePrivate(keyHex, KeyFormat.Raw, KeyFormat.PEM); + + const keychain = new PluginKeychainMemory({ + backend: new Map([[keychainRef, pem]]), + keychainId, + logLevel, + instanceId: uuidv4(), + }); + + const httpServer1 = createServer(); + await new Promise((resolve, reject) => { + httpServer1.once("error", reject); + httpServer1.once("listening", resolve); + httpServer1.listen(0, "127.0.0.1"); + }); + const addressInfo1 = httpServer1.address() as AddressInfo; + t.comment(`HttpServer1 AddressInfo: ${JSON.stringify(addressInfo1)}`); + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + t.comment(`Cactus Node 1 Host: ${node1Host}`); + + const containerImageVersion = "latest"; + const containerImageName = "ghcr.io/azaharac/cactus-besu-21-1-6-all-in-one"; + const besuOptions = { containerImageName, containerImageVersion }; + const besuTestLedger = new BesuTestLedger(besuOptions); + await besuTestLedger.start(); + + const tearDown = async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }; + + test.onFinish(tearDown); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + const jsObjectSignerOptions: IJsObjectSignerOptions = { + privateKey: keyHex, + logLevel, + }; + const jsObjectSigner = new JsObjectSigner(jsObjectSignerOptions); + + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [keychain] }); + + // 3. Instantiate the web service consortium plugin + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + const pluginValidatorBesu = new PluginLedgerConnectorBesu(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.authorizationProtocol = AuthorizationProtocol.NONE; + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginValidatorBesu); + + const apiServer = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 7. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + t.comment(`AddressInfo: ${JSON.stringify(addressInfo1)}`); + + const web3Provider = new Web3.providers.HttpProvider(rpcApiHttpHost); + const web3 = new Web3(web3Provider); + const web3Eea: IWeb3InstanceExtended = EEAClient(web3, 1337); + + const orionKeyPair = await besuTestLedger.getOrionKeyPair(); + const besuKeyPair = await besuTestLedger.getBesuKeyPair(); + + const besuPrivateKey = besuKeyPair.privateKey.toLowerCase().startsWith("0x") + ? besuKeyPair.privateKey.substring(2) + : besuKeyPair.privateKey; // besu node's private key + + const contractOptions = { + data: `0x123`, + // privateFrom : Orion public key of the sender. + privateFrom: orionKeyPair.publicKey, + // privateFor : Orion public keys of recipients or privacyGroupId: Privacy group to receive the transaction + privateFor: [orionKeyPair.publicKey], + // privateKey: Ethereum private key with which to sign the transaction. + privateKey: besuPrivateKey, + }; + + const transactionHash = await web3Eea.eea.sendRawTransaction(contractOptions); + + const transaction = await web3.eth.getTransaction(transactionHash); + const singData = jsObjectSigner.sign(transaction.input); + const signDataHex = Buffer.from(singData).toString("hex"); + + const request: SignTransactionRequest = { + keychainId, + keychainRef, + transactionHash: transactionHash, + }; + + const configuration = new BesuApiClientOptions({ basePath: node1Host }); + const api = new BesuApiClient(configuration); + + // Test for 200 valid response test case + const res = await api.signTransactionV1(request); + t.ok(res, "API response object is truthy"); + t.deepEquals(signDataHex, res.data.signature, "Signature data are equal"); + + // Test for 404 Transaction not found test case + try { + const notFoundRequest: SignTransactionRequest = { + keychainId: "fake", + keychainRef: "fake", + transactionHash: + "0x46eac4d1d1ff81837698cbab38862a428ddf042f92855a72010de2771a7b704d", + }; + await api.signTransactionV1(notFoundRequest); + } catch (error) { + t.equal(error.response.status, 404, "HTTP response status are equal"); + t.equal( + error.response.statusText, + "Transaction not found", + "Response text are equal", + ); + } +}); diff --git a/tools/docker/besu-all-in-one/README.md b/tools/docker/besu-all-in-one/README.md index ff12265454e..6ff35e610c3 100644 --- a/tools/docker/besu-all-in-one/README.md +++ b/tools/docker/besu-all-in-one/README.md @@ -7,10 +7,16 @@ Comes equipped with supervisord which provides access to tailing logs of both or ## Build an image locally -```sh -docker build . -t hyperledger/cactus-besu-all-in-one:latest -``` - +* To build the besu v1.5.1 image locally use: + ```sh + docker build . -t hyperledger/cactus-besu-all-in-one:latest + ``` + +* To build the besu v21.1.x image locally use: + + ```sh + docker build ./tools/docker/besu-all-in-one/ -f ./tools/docker/besu-all-in-one/v21_1_x/Dockerfile -t baio21 + ``` ## Shell into a running container: diff --git a/tools/docker/besu-all-in-one/v21_1_x/Dockerfile b/tools/docker/besu-all-in-one/v21_1_x/Dockerfile new file mode 100644 index 00000000000..1a0181ecf57 --- /dev/null +++ b/tools/docker/besu-all-in-one/v21_1_x/Dockerfile @@ -0,0 +1,30 @@ +ARG BESU_VERSION=21.1.6 + +FROM hyperledger/besu:$BESU_VERSION AS besu +FROM pegasyseng/orion:21.1.1 AS orion + +COPY --from=besu /opt/besu/ /opt/besu/ + +USER root + +RUN mkdir /config/ +RUN mkdir /config/orion/ +RUN mkdir /config/besu/ +RUN mkdir /opt/besu/public-keys/ + +ADD ../log-config.xml /config/log-config.xml +ADD ../orion.conf /config/orion/orion.conf +ADD ../nodeKey.pub /config/orion/nodeKey.pub +ADD ../nodeKey.key /config/orion/nodeKey.key +ADD ../bootnode_start.sh /opt/besu/bootnode_start.sh +ADD ../key /opt/besu/keys/key +ADD ../key.pub /opt/besu/keys/key.pub + +RUN apt-get update && apt-get install -y supervisor +RUN mkdir -p /var/log/supervisor +COPY ../supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +EXPOSE 9001 + +ENTRYPOINT ["/usr/bin/supervisord"] +CMD ["--nodaemon"]