diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 51f2b1d165..2496290e6d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1044,7 +1044,7 @@ jobs: JEST_TEST_PATTERN: packages/cactus-plugin-ledger-connector-besu/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts JEST_TEST_RUNNER_DISABLED: false TAPE_TEST_PATTERN: >- - --files={./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts,./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-cactus.test.ts,./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-web3-eea.test.ts,./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-deploy-contract-from-json.test.ts,./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-record-locator.test.ts} + --files={./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-web3-eea.test.ts,./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-record-locator.test.ts} TAPE_TEST_RUNNER_DISABLED: false runs-on: ubuntu-22.04 steps: diff --git a/.taprc b/.taprc index e914ecfb0b..9cdb617842 100644 --- a/.taprc +++ b/.taprc @@ -37,9 +37,6 @@ files: - ./packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/plugin-factory-keychain.test.ts - ./packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/plugin-keychain-google-sm.test.ts - ./packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/openapi/openapi-validation.test.ts - - ./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-cactus.test.ts - - ./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts - - ./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-deploy-contract-from-json.test.ts - ./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-record-locator.test.ts - ./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-web3-eea.test.ts - ./packages/cactus-plugin-ledger-connector-xdai/src/test/typescript/integration/deploy-contract-from-json-xdai-json-object.test.ts diff --git a/jest.config.js b/jest.config.js index fd30b8abf5..795859173f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -50,9 +50,6 @@ module.exports = { `./packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/plugin-factory-keychain.test.ts`, `./packages/cactus-plugin-keychain-google-sm/src/test/typescript/integration/plugin-keychain-google-sm.test.ts`, `./packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/openapi/openapi-validation.test.ts`, - `./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-cactus.test.ts`, - `./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts`, - `./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-deploy-contract-from-json.test.ts`, `./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/v21-get-record-locator.test.ts`, `./packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-web3-eea.test.ts`, `./packages/cactus-plugin-ledger-connector-xdai/src/test/typescript/integration/deploy-contract-from-json-xdai-json-object.test.ts`, diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts index 0505bfdc6d..328d40afc4 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts @@ -1,4 +1,4 @@ -import test, { Test } from "tape-promise/tape"; +import "jest-extended"; import { v4 as uuidv4 } from "uuid"; import { Server as SocketIoServer } from "socket.io"; import { PluginRegistry } from "@hyperledger/cactus-core"; @@ -32,75 +32,42 @@ 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"; +import { Account } from "web3-core"; -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) => { +describe("PluginLedgerConnectorBesu", () => { + const testCase = "deploys contract via .json file"; + const logLevel: LogLevelDesc = "INFO"; const besuTestLedger = new BesuTestLedger(); - 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/1.5.1/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]]), + backend: new Map(), 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] }), - }); - await connector.onPluginInit(); - const expressApp = express(); expressApp.use(bodyParser.json({ limit: "250mb" })); const server = http.createServer(expressApp); + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const wsApi = new SocketIoServer(server, { path: Constants.SocketIoConnectionPathV1, }); @@ -110,66 +77,116 @@ test(testCase, async (t: Test) => { 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, - }, + let rpcApiHttpHost: string; + let rpcApiWsHost: string; + let firstHighNetWorthAccount: string; + let web3: Web3; + let testEthAccount: Account; + let keychainEntryValue: string; + let connector: PluginLedgerConnectorBesu; + let addressInfo: AddressInfo; + let apiClient: BesuApiClient; + let apiHost: string; + let contractAddress: string; + + beforeAll(async () => { + await pruneDockerAllIfGithubAction({ logLevel }); }); - 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); + beforeAll(async () => { + await besuTestLedger.start(); + rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(uuidv4()); + keychainEntryValue = testEthAccount.privateKey; + keychainPlugin.set(keychainEntryKey, keychainEntryValue); + + addressInfo = await Servers.listen(listenOptions); + + connector = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: uuidv4(), + pluginRegistry, + }); + + const { address, port } = addressInfo; + apiHost = `http://${address}:${port}`; + const besuApiClientOptions = new BesuApiClientOptions({ + basePath: apiHost, }); + apiClient = new BesuApiClient(besuApiClientOptions); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); }); - 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"); + afterAll(async () => { + await Servers.shutdown(server); + }); - let contractAddress: string; + afterAll(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); - test("deploys contract via .json file", async (t2: Test) => { + afterAll(async () => { + await pruneDockerAllIfGithubAction({ logLevel }); + }); + + test(testCase, async () => { + 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); + }); + }, + ); + expect(aBlockHeader).toBeTruthy(); + expect(aBlockHeader).toBeObject(); + expect(aBlockHeader).not.toBeEmptyObject(); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toEqual(10e9); + }); + + test("deploys contract via .json file", async () => { const deployOut = await connector.deployContract({ keychainId: keychainPlugin.getKeychainId(), contractName: HelloWorldContractJson.contractName, @@ -183,21 +200,16 @@ test(testCase, async (t: Test) => { 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", - ); + expect(deployOut).toBeTruthy(); + expect(deployOut).toBeObject(); + + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeObject(); + + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeString(); contractAddress = deployOut.transactionReceipt.contractAddress as string; - t2.ok( - typeof contractAddress === "string", - "contractAddress typeof string OK", - ); const { callOutput: helloMsg } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -212,14 +224,12 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.ok(helloMsg, "sayHello() output is truthy"); - t2.true( - typeof helloMsg === "string", - "sayHello() output is type of string", - ); + expect(helloMsg).toBeTruthy(); + expect(helloMsg).toBeString(); + expect(helloMsg).not.toBeEmpty(); }); - test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + test("invoke Web3SigningCredentialType.NONE", async () => { const testEthAccount2 = web3.eth.accounts.create(uuidv4()); const { rawTransaction } = await web3.eth.accounts.signTransaction( @@ -247,12 +257,10 @@ test(testCase, async (t: Test) => { }); 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(); + expect(parseInt(balance2, 10)).toEqual(10e6); }); - test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + test("invoke Web3SigningCredentialType.PrivateKeyHex", async () => { const newName = `DrCactus${uuidv4()}`; const setNameOut = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -268,32 +276,28 @@ test(testCase, async (t: Test) => { }, 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", - ); - } + expect(setNameOut).toBeTruthy(); + + const setNameOutPromise1 = 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, + }); + + await expect(setNameOutPromise1).rejects.toMatchObject>({ + message: expect.stringContaining("Nonce too low"), + }); + const { callOutput: getNameOut } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, contractAbi: HelloWorldContractJson.abi, @@ -308,7 +312,7 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + expect(getNameOut).toEqual(newName); const getNameOut2 = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -324,7 +328,7 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + expect(getNameOut2).toBeTruthy(); const response = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -341,7 +345,7 @@ test(testCase, async (t: Test) => { }, value: 10, }); - t2.ok(response, "deposit() payable invocation output is truthy OK"); + expect(response).toBeTruthy(); const { callOutput } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -357,16 +361,11 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.equal( - callOutput, - newName, - `getNameByIndex() output reflects the update OK`, - ); - t2.end(); + expect(callOutput).toEqual(newName); }); - test("invoke Web3SigningCredentialType.CactusKeychainRef", async (t2: Test) => { + test("invoke Web3SigningCredentialType.CactusKeychainRef", async () => { const newName = `DrCactus${uuidv4()}`; const signingCredential: Web3SigningCredentialCactusKeychainRef = { @@ -387,28 +386,23 @@ test(testCase, async (t: Test) => { 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", - ); - } + expect(setNameOut).toBeTruthy(); + + const setNameOutPromise2 = connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + + await expect(setNameOutPromise2).rejects.toMatchObject>({ + message: expect.stringContaining("Nonce too low"), + }); const { callOutput: getNameOut } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -420,7 +414,7 @@ test(testCase, async (t: Test) => { gas: 1000000, signingCredential, }); - t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + expect(getNameOut).toEqual(newName); const getNameOut2 = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -432,7 +426,7 @@ test(testCase, async (t: Test) => { gas: 1000000, signingCredential, }); - t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + expect(getNameOut2).toBeTruthy(); const response = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -445,7 +439,7 @@ test(testCase, async (t: Test) => { signingCredential, value: 10, }); - t2.ok(response, "deposit() payable invocation output is truthy OK"); + expect(response).toBeTruthy(); const { callOutput } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -457,16 +451,11 @@ test(testCase, async (t: Test) => { gas: 1000000, signingCredential, }); - t2.equal( - callOutput, - newName, - `getNameByIndex() output reflects the update OK`, - ); - t2.end(); + expect(callOutput).toEqual(newName); }); - test("get prometheus exporter metrics", async (t2: Test) => { + test("get prometheus exporter metrics", async () => { const res = await apiClient.getPrometheusMetricsV1(); const promMetricsOutput = "# HELP " + @@ -479,15 +468,9 @@ test(testCase, async (t: Test) => { '{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(); + expect(res).toBeTruthy(); + expect(res.data).toBeTruthy(); + expect(res.status).toEqual(200); + expect(res.data.includes(promMetricsOutput)).toBeTrue(); }); - - t.end(); }); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-cactus.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-cactus.test.ts index e28d6efb84..75156dcfb0 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-cactus.test.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/private-deploy-contract-from-json-cactus.test.ts @@ -1,4 +1,4 @@ -import test, { Test } from "tape-promise/tape"; +import "jest-extended"; import { v4 as uuidv4 } from "uuid"; import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; import Web3 from "web3"; @@ -14,522 +14,497 @@ import { PluginImportType } from "@hyperledger/cactus-core-api"; import { PluginRegistry } from "@hyperledger/cactus-core"; import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; -const containerImageName = - "ghcr.io/hyperledger/cactus-besu-all-in-one-multi-party"; -const containerImageTag = "2023-08-08-pr-2596"; -const testCase = "Executes private transactions on Hyperledger Besu"; -const logLevel: LogLevelDesc = "TRACE"; - -const doctorCactusHex = - "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d446f63746f722043616374757300000000000000000000000000000000000000"; - -// WARNING: the keys here are demo purposes ONLY. Please use a tool like Orchestrate or EthSigner for production, rather than hard coding private keys -const keysStatic = { - tessera: { - member1: { - publicKey: "BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=", - }, - member2: { - publicKey: "QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=", - }, - member3: { - publicKey: "1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg=", - }, - }, - besu: { - member1: { - url: "http://127.0.0.1:20000", - wsUrl: "ws://127.0.0.1:20001", - privateKey: - "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", - }, - member2: { - url: "http://127.0.0.1:20002", - wsUrl: "ws://127.0.0.1:20003", - privateKey: - "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", - }, - member3: { - url: "http://127.0.0.1:20004", - wsUrl: "ws://127.0.0.1:20005", - privateKey: - "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", +describe("PluginLedgerConnectorBesu", () => { + const containerImageName = + "ghcr.io/hyperledger/cactus-besu-all-in-one-multi-party"; + const containerImageTag = "2023-08-08-pr-2596"; + const testCase = "Executes private transactions on Hyperledger Besu"; + const logLevel: LogLevelDesc = "INFO"; + + const doctorCactusHex = + "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d446f63746f722043616374757300000000000000000000000000000000000000"; + + // WARNING: the keys here are demo purposes ONLY. Please use a tool like Orchestrate or EthSigner for production, rather than hard coding private keys + const keysStatic = { + tessera: { + member1: { + publicKey: "BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=", + }, + member2: { + publicKey: "QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=", + }, + member3: { + publicKey: "1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg=", + }, }, - ethsignerProxy: { - url: "http://127.0.0.1:18545", - accountAddress: "9b790656b9ec0db1936ed84b3bea605873558198", + besu: { + member1: { + url: "http://127.0.0.1:20000", + wsUrl: "ws://127.0.0.1:20001", + privateKey: + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", + }, + member2: { + url: "http://127.0.0.1:20002", + wsUrl: "ws://127.0.0.1:20003", + privateKey: + "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + }, + member3: { + url: "http://127.0.0.1:20004", + wsUrl: "ws://127.0.0.1:20005", + privateKey: + "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", + }, + ethsignerProxy: { + url: "http://127.0.0.1:18545", + accountAddress: "9b790656b9ec0db1936ed84b3bea605873558198", + }, }, - }, -}; - -test(testCase, async (t: Test) => { - // At development time one can specify this environment variable if there is - // a multi-party network already running, which is doable with something like - // this on the terminal: - // docker run --rm --privileged --publish 2222:22 --publish 3000:3000 --publish 8545:8545 --publish 8546:8546 --publish 9001:9001 --publish 9081:9081 --publish 9082:9082 --publish 9083:9083 --publish 9090:9090 --publish 18545:18545 --publish 20000:20000 --publish 20001:20001 --publish 20002:20002 --publish 20003:20003 --publish 20004:20004 --publish 20005:20005 --publish 25000:25000 petermetz/cactus-besu-multi-party-all-in-one:0.1.2 - // - // The upside of this approach is that a new container is not launched from - // scratch for every test execution which enables faster iteration. - const preWarmedLedger = process.env.CACTUS_TEST_PRE_WARMED_LEDGER === "true"; - - let keys: any; - if (preWarmedLedger) { - keys = keysStatic; - } else { - const ledger = new BesuMpTestLedger({ - logLevel, - imageName: containerImageName, - imageTag: containerImageTag, - emitContainerLogs: false, - }); - test.onFinish(() => ledger.stop()); - await ledger.start(); - keys = await ledger.getKeys(); - } - - const rpcApiHttpHostMember1 = keys.besu.member1.url; - const rpcApiHttpHostMember2 = keys.besu.member2.url; - const rpcApiHttpHostMember3 = keys.besu.member3.url; - - const rpcApiWsHostMember1 = keys.besu.member1.wsUrl; - const rpcApiWsHostMember2 = keys.besu.member2.wsUrl; - const rpcApiWsHostMember3 = keys.besu.member3.wsUrl; - - const web3Member1 = new Web3(rpcApiHttpHostMember1); - const web3Member2 = new Web3(rpcApiHttpHostMember2); - const web3Member3 = new Web3(rpcApiHttpHostMember3); - - const pluginRegistry1 = new PluginRegistry(); - const pluginRegistry2 = new PluginRegistry(); - const pluginRegistry3 = new PluginRegistry(); - - const pluginFactoryLedgerConnector = new PluginFactoryLedgerConnector({ - pluginImportType: PluginImportType.Local, + }; + + const infrastructureElements: Array<{ stop: () => Promise }> = []; + afterAll(async () => { + for (let index = 0; index < infrastructureElements.length; index++) { + const aStoppable = infrastructureElements[index]; + await aStoppable.stop(); + } }); - const connectorInstanceId1 = "besu1_" + uuidv4(); - const connectorInstanceId2 = "besu2_" + uuidv4(); - const connectorInstanceId3 = "besu3_" + uuidv4(); + test(testCase, async () => { + // At development time one can specify this environment variable if there is + // a multi-party network already running, which is doable with something like + // this on the terminal: + // docker run --rm --privileged --publish 2222:22 --publish 3000:3000 --publish 8545:8545 --publish 8546:8546 --publish 9001:9001 --publish 9081:9081 --publish 9082:9082 --publish 9083:9083 --publish 9090:9090 --publish 18545:18545 --publish 20000:20000 --publish 20001:20001 --publish 20002:20002 --publish 20003:20003 --publish 20004:20004 --publish 20005:20005 --publish 25000:25000 petermetz/cactus-besu-multi-party-all-in-one:0.1.2 + // + // The upside of this approach is that a new container is not launched from + // scratch for every test execution which enables faster iteration. + const preWarmedLedger = + process.env.CACTUS_TEST_PRE_WARMED_LEDGER === "true"; + + let keys: any; + if (preWarmedLedger) { + keys = keysStatic; + } else { + const ledger = new BesuMpTestLedger({ + logLevel, + imageName: containerImageName, + imageTag: containerImageTag, + emitContainerLogs: false, + }); + infrastructureElements.push(ledger); + await ledger.start(true); + keys = await ledger.getKeys(); + } + + const rpcApiHttpHostMember1 = keys.besu.member1.url; + const rpcApiHttpHostMember2 = keys.besu.member2.url; + const rpcApiHttpHostMember3 = keys.besu.member3.url; + + const rpcApiWsHostMember1 = keys.besu.member1.wsUrl; + const rpcApiWsHostMember2 = keys.besu.member2.wsUrl; + const rpcApiWsHostMember3 = keys.besu.member3.wsUrl; + + const web3Member1 = new Web3(rpcApiHttpHostMember1); + const web3Member2 = new Web3(rpcApiHttpHostMember2); + const web3Member3 = new Web3(rpcApiHttpHostMember3); + + const pluginRegistry1 = new PluginRegistry(); + const pluginRegistry2 = new PluginRegistry(); + const pluginRegistry3 = new PluginRegistry(); + + const pluginFactoryLedgerConnector = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); - const keychainInstanceId1 = "keychain_instance1_" + uuidv4(); - const keychainId1 = "keychain1_" + uuidv4(); - const keychain1 = new PluginKeychainMemory({ - instanceId: keychainInstanceId1, - keychainId: keychainId1, - logLevel, - }); - t.ok(keychain1, "keychain1 truthy OK"); - keychain1.set( - HelloWorldContractJson.contractName, - JSON.stringify(HelloWorldContractJson), - ); - pluginRegistry1.add(keychain1); - - const keychainInstanceId2 = "keychain_instance2_" + uuidv4(); - const keychainId2 = "keychain2_" + uuidv4(); - const keychain2 = new PluginKeychainMemory({ - instanceId: keychainInstanceId2, - keychainId: keychainId2, - logLevel, - }); - t.ok(keychain2, "keychain2 truthy OK"); - keychain2.set( - HelloWorldContractJson.contractName, - JSON.stringify(HelloWorldContractJson), - ); - pluginRegistry2.add(keychain2); - - const keychainInstanceId3 = "keychain_instance3_" + uuidv4(); - const keychainId3 = "keychain3_" + uuidv4(); - const keychain3 = new PluginKeychainMemory({ - instanceId: keychainInstanceId3, - keychainId: keychainId3, - logLevel, - }); - t.ok(keychain3, "keychain3 truthy OK"); - keychain3.set( - HelloWorldContractJson.contractName, - JSON.stringify(HelloWorldContractJson), - ); - pluginRegistry3.add(keychain3); - - const connector1 = await pluginFactoryLedgerConnector.create({ - instanceId: connectorInstanceId1, - pluginRegistry: pluginRegistry1, - rpcApiHttpHost: rpcApiHttpHostMember1, - rpcApiWsHost: rpcApiWsHostMember1, - logLevel, - }); - t.ok(connector1, "connector1 truthy OK"); - test.onFinish(() => connector1.shutdown()); - pluginRegistry1.add(connector1); - - const connector2 = await pluginFactoryLedgerConnector.create({ - instanceId: connectorInstanceId2, - pluginRegistry: pluginRegistry2, - rpcApiHttpHost: rpcApiHttpHostMember2, - rpcApiWsHost: rpcApiWsHostMember2, - logLevel, - }); - t.ok(connector2, "connector2 truthy OK"); - test.onFinish(() => connector2.shutdown()); - pluginRegistry2.add(connector2); - - const connector3 = await pluginFactoryLedgerConnector.create({ - instanceId: connectorInstanceId3, - pluginRegistry: pluginRegistry3, - rpcApiHttpHost: rpcApiHttpHostMember3, - rpcApiWsHost: rpcApiWsHostMember3, - logLevel, - }); - t.ok(connector3, "connector3 truthy OK"); - test.onFinish(() => connector3.shutdown()); - pluginRegistry3.add(connector3); - - await connector1.onPluginInit(); - await connector2.onPluginInit(); - await connector3.onPluginInit(); - - const chainIdMember1 = await web3Member1.eth.getChainId(); - t.comment(`chainIdMember1=${chainIdMember1}`); - const chainIdMember2 = await web3Member2.eth.getChainId(); - t.comment(`chainIdMember2=${chainIdMember2}`); - const chainIdMember3 = await web3Member3.eth.getChainId(); - t.comment(`chainIdMember3=${chainIdMember3}`); - - const web3QuorumMember1 = Web3JsQuorum(web3Member1); - t.ok(web3QuorumMember1, "web3QuorumMember1 truthy OK"); - - const web3QuorumMember2 = Web3JsQuorum(web3Member2); - t.ok(web3QuorumMember2, "web3QuorumMember2 truthy OK"); - - const web3QuorumMember3 = Web3JsQuorum(web3Member3); - t.ok(web3QuorumMember3, "web3QuorumMember3 truthy OK"); - - const deployRes = await connector1.deployContract({ - bytecode: HelloWorldContractJson.bytecode, - contractAbi: HelloWorldContractJson.abi, - contractName: HelloWorldContractJson.contractName, - constructorArgs: [], - privateTransactionConfig: { - privateFrom: keys.tessera.member1.publicKey, - privateFor: [ - keys.tessera.member1.publicKey, - keys.tessera.member2.publicKey, - ], - }, - web3SigningCredential: { - secret: keys.besu.member1.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - keychainId: keychain1.getKeychainId(), - gas: 3000000, - }); + const connectorInstanceId1 = "besu1_" + uuidv4(); + const connectorInstanceId2 = "besu2_" + uuidv4(); + const connectorInstanceId3 = "besu3_" + uuidv4(); - t.ok(deployRes, "deployRes truthy OK"); - t.ok(deployRes.transactionReceipt, "deployRes.transactionReceipt truthy OK"); - t.ok( - deployRes.transactionReceipt.contractAddress, - "deployRes.transactionReceipt.contractAddress truthy OK", - ); - t.ok( - deployRes.transactionReceipt.commitmentHash, - "deployRes.transactionReceipt.commitmentHash truthy OK", - ); - - // t.ok(deployRes.status, "deployRes.status truthy OK"); - // t.equal(deployRes.status, 200, "deployRes.status === 200 OK"); - // t.ok(deployRes.data, "deployRes.data truthy OK"); - // t.ok( - // deployRes.data.transactionReceipt, - // "deployRes.data.transactionReceipt truthy OK", - // ); - - // t.ok(privacyMarkerTxHash, "privacyMarkerTxHash truthy OK"); - - const contractDeployReceipt = - (await web3QuorumMember1.priv.waitForTransactionReceipt( - deployRes.transactionReceipt.commitmentHash, - )) as IPrivateTransactionReceipt; - - t.ok(contractDeployReceipt, "contractDeployReceipt truthy OK"); - const receipt = contractDeployReceipt as IPrivateTransactionReceipt; - - const { contractAddress } = receipt; - t.comment(`Private contract address: ${contractAddress}`); - t.ok(contractAddress, "contractAddress truthy OK"); - - // Check that the third node does not see the transaction of the contract - // deployment that was sent to node 1 and 2 only, not 3. - const txReceiptNever = await web3QuorumMember3.priv.waitForTransactionReceipt( - deployRes.transactionReceipt.commitmentHash, - ); - t.notok(txReceiptNever, "txReceiptNever falsy OK"); - - // Check that node 1 and 2 can indeed see the transaction for the contract - // deployment that was sent to them and them only (node 3 was left out) - - // Note that changing this to use web3QuorumMember3 breaks it and I'm suspecting - // that this is what's plaguing the tests that are based on the connector - // which is instantiated with a single web3+web3 Quorum client. - // What I will try next is to have 3 connectors each with a web3 Quorum client - // that points to one of the 3 nodes and see if that makes it work. - const txReceiptAlways1 = - await web3QuorumMember1.priv.waitForTransactionReceipt( - deployRes.transactionReceipt.commitmentHash, + const keychainInstanceId1 = "keychain_instance1_" + uuidv4(); + const keychainId1 = "keychain1_" + uuidv4(); + const keychain1 = new PluginKeychainMemory({ + instanceId: keychainInstanceId1, + keychainId: keychainId1, + logLevel, + }); + expect(keychain1).toBeTruthy(); + keychain1.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), ); - t.ok(txReceiptAlways1, "txReceiptAlways1 truthy OK"); + pluginRegistry1.add(keychain1); - const txReceiptAlways2 = - await web3QuorumMember2.priv.waitForTransactionReceipt( - deployRes.transactionReceipt.commitmentHash, - ); - t.ok(txReceiptAlways2, "txReceiptAlways2 truthy OK"); - - const contract = new web3Member1.eth.Contract( - HelloWorldContractJson.abi as never, - ); - - { - t.comment("Checking if member1 can call setName()"); - const data = contract.methods.setName("ProfessorCactus - #1").encodeABI(); - const functionParams = { - to: contractDeployReceipt.contractAddress, - data, - privateFrom: keys.tessera.member1.publicKey, - privateFor: [keys.tessera.member2.publicKey], - privateKey: keys.besu.member1.privateKey, - }; - const transactionHash = - await web3QuorumMember1.priv.generateAndSendRawTransaction( - functionParams, - ); - t.comment(`Transaction hash: ${transactionHash}`); - t.ok(transactionHash, "transactionHash truthy OK"); - - const result = - await web3QuorumMember1.priv.waitForTransactionReceipt(transactionHash); - t.comment(`Transaction receipt for set() call: ${JSON.stringify(result)}`); - t.ok(result, "set() result member 1 truthy OK"); - } - - { - t.comment("Checking if member1 can see new name via getName()"); - const data = contract.methods.getName().encodeABI(); - const fnParams = { - to: contractDeployReceipt.contractAddress, - data, - privateFrom: keys.tessera.member1.publicKey, - privateFor: [keys.tessera.member2.publicKey], - privateKey: keys.besu.member1.privateKey, - }; - - const privacyGroupId = - web3QuorumMember1.utils.generatePrivacyGroup(fnParams); - const callOutput = await web3QuorumMember1.priv.call(privacyGroupId, { - to: contractDeployReceipt.contractAddress, - data: contract.methods.getName().encodeABI(), - }); - t.comment(`getName Call output: ${JSON.stringify(callOutput)}`); - t.ok(callOutput, "callOutput truthy OK"); - const name = web3Member1.eth.abi.decodeParameter("string", callOutput); - t.equal(name, "ProfessorCactus - #1", "getName() member 1 equals #1"); - } - - { - // Member 3 cannot see into the privacy group of 1 and 2 so the getName - // will not return the value that was set earlier in that privacy group. - t.comment("Checking if member3 can see new name via getName()"); - const data = contract.methods.getName().encodeABI(); - const fnParams = { - to: contractDeployReceipt.contractAddress, - data, - privateFrom: keys.tessera.member1.publicKey, - privateFor: [keys.tessera.member2.publicKey], - privateKey: keys.besu.member3.privateKey, - }; - - const privacyGroupId = - web3QuorumMember3.utils.generatePrivacyGroup(fnParams); - const callOutput = await web3QuorumMember3.priv.call(privacyGroupId, { - to: contractDeployReceipt.contractAddress, - data, + const keychainInstanceId2 = "keychain_instance2_" + uuidv4(); + const keychainId2 = "keychain2_" + uuidv4(); + const keychain2 = new PluginKeychainMemory({ + instanceId: keychainInstanceId2, + keychainId: keychainId2, + logLevel, }); - t.comment(`getName member3 output: ${JSON.stringify(callOutput)}`); - t.equal(callOutput, "0x", "member3 getName callOutput === 0x OK"); - } - - { - const data = contract.methods.setName("ProfessorCactus - #2").encodeABI(); - t.comment("Checking if member2 can call setName()"); - const functionParams = { - to: contractDeployReceipt.contractAddress, - data, - privateFrom: keys.tessera.member2.publicKey, - privateFor: [keys.tessera.member2.publicKey], - privateKey: keys.besu.member2.privateKey, - }; - const transactionHash = - await web3QuorumMember2.priv.generateAndSendRawTransaction( - functionParams, - ); - t.comment(`Transaction hash: ${transactionHash}`); - t.ok(transactionHash, "transactionHash truthy OK"); - - const result = - await web3QuorumMember2.priv.waitForTransactionReceipt(transactionHash); - t.comment(`Transaction receipt for set() call: ${JSON.stringify(result)}`); - t.ok(result, "set() result member 2 truthy OK"); - } - - { - const data = contract.methods.setName("ProfessorCactus - #3").encodeABI(); - t.comment("Checking if member3 can call setName()"); - const functionParams = { - to: contractDeployReceipt.contractAddress, - data, - privateFrom: keys.tessera.member3.publicKey, - privateKey: keys.besu.member3.privateKey, - privateFor: [keys.tessera.member2.publicKey], - }; - const transactionHash = - await web3QuorumMember3.priv.generateAndSendRawTransaction( - functionParams, - ); - t.comment(`setName tx hash for member 3: ${transactionHash}`); - t.ok(transactionHash, "setName tx hash for member 3 truthy OK"); - - const result = - await web3QuorumMember3.priv.waitForTransactionReceipt(transactionHash); - t.comment(`Transaction receipt for set() call: ${JSON.stringify(result)}`); - t.ok(result, "set() result for member 3 truthy OK"); - } - - { - t.comment("Checking that private contract cannot be called anonymously"); - const contractInvocationNoPrivTxConfig = connector1.invokeContract({ - contractName: HelloWorldContractJson.contractName, - contractAbi: HelloWorldContractJson.abi, - contractAddress: contractDeployReceipt.contractAddress, - invocationType: EthContractInvocationType.Call, - gas: 3000000, - methodName: "getName", - params: [], - signingCredential: { - secret: "incorrect-secret", - type: Web3SigningCredentialType.PrivateKeyHex, - }, + expect(keychain2).toBeTruthy(); + keychain2.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + pluginRegistry2.add(keychain2); + + const keychainInstanceId3 = "keychain_instance3_" + uuidv4(); + const keychainId3 = "keychain3_" + uuidv4(); + const keychain3 = new PluginKeychainMemory({ + instanceId: keychainInstanceId3, + keychainId: keychainId3, + logLevel, }); - await t.rejects( - contractInvocationNoPrivTxConfig, - /Returned values aren't valid, did it run Out of Gas\? You might also see this error if you are not using the correct ABI for the contract you are retrieving data from, requesting data from a block number that does not exist, or querying a node which is not fully synced\./, - "private contract call fails without Besu member credentials OK", + expect(keychain3).toBeTruthy(); + keychain3.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), ); - } + pluginRegistry3.add(keychain3); - { - t.comment("Ensuring member1 can call setName() via connector"); - const res = await connector1.invokeContract({ - contractName: HelloWorldContractJson.contractName, - contractAbi: HelloWorldContractJson.abi, - contractAddress: contractDeployReceipt.contractAddress, - invocationType: EthContractInvocationType.Send, - gas: 3000000, - methodName: "setName", - params: ["Doctor Cactus"], - privateTransactionConfig: { - privateFrom: keys.tessera.member1.publicKey, - privateFor: [keys.tessera.member2.publicKey], - }, - signingCredential: { - secret: keys.besu.member1.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, + const connector1 = await pluginFactoryLedgerConnector.create({ + instanceId: connectorInstanceId1, + pluginRegistry: pluginRegistry1, + rpcApiHttpHost: rpcApiHttpHostMember1, + rpcApiWsHost: rpcApiWsHostMember1, + logLevel, + }); + expect(connector1).toBeTruthy(); + infrastructureElements.push({ stop: () => connector1.shutdown() }); + pluginRegistry1.add(connector1); + + const connector2 = await pluginFactoryLedgerConnector.create({ + instanceId: connectorInstanceId2, + pluginRegistry: pluginRegistry2, + rpcApiHttpHost: rpcApiHttpHostMember2, + rpcApiWsHost: rpcApiWsHostMember2, + logLevel, + }); + expect(connector2).toBeTruthy(); + infrastructureElements.push({ stop: () => connector2.shutdown() }); + pluginRegistry2.add(connector2); + + const connector3 = await pluginFactoryLedgerConnector.create({ + instanceId: connectorInstanceId3, + pluginRegistry: pluginRegistry3, + rpcApiHttpHost: rpcApiHttpHostMember3, + rpcApiWsHost: rpcApiWsHostMember3, + logLevel, }); + expect(connector3).toBeTruthy(); + infrastructureElements.push({ stop: () => connector3.shutdown() }); + pluginRegistry3.add(connector3); - t.equal(res.success, "0x1", "member1 setName callOutput === 0x1 OK"); - } + await connector1.onPluginInit(); + await connector2.onPluginInit(); + await connector3.onPluginInit(); - { - t.comment( - "Ensuring member1 can call getName() and receive correct value after setName call", - ); - const res = await connector1.invokeContract({ - contractName: HelloWorldContractJson.contractName, - contractAbi: HelloWorldContractJson.abi, - contractAddress: contractDeployReceipt.contractAddress, - invocationType: EthContractInvocationType.Call, - gas: 3000000, - methodName: "getName", - params: [], - privateTransactionConfig: { - privateFrom: keys.tessera.member1.publicKey, - privateFor: [keys.tessera.member2.publicKey], - }, - signingCredential: { - secret: keys.besu.member1.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - }); + const web3QuorumMember1 = Web3JsQuorum(web3Member1); + expect(web3QuorumMember1).toBeTruthy(); - t.equals( - res.callOutput, - doctorCactusHex, - "member1 getName callOutput === DoctorCactus", - ); - } + const web3QuorumMember2 = Web3JsQuorum(web3Member2); + expect(web3QuorumMember2).toBeTruthy(); - { - t.comment( - "Ensuring member2 can call getName() and receive correct value after setName call", - ); - const res = await connector2.invokeContract({ - contractName: HelloWorldContractJson.contractName, + const web3QuorumMember3 = Web3JsQuorum(web3Member3); + expect(web3QuorumMember3).toBeTruthy(); + + const deployOut = await connector1.deployContract({ + bytecode: HelloWorldContractJson.bytecode, contractAbi: HelloWorldContractJson.abi, - contractAddress: contractDeployReceipt.contractAddress, - invocationType: EthContractInvocationType.Call, - gas: 3000000, - methodName: "getName", - params: [], + contractName: HelloWorldContractJson.contractName, + constructorArgs: [], privateTransactionConfig: { privateFrom: keys.tessera.member1.publicKey, - privateFor: [keys.tessera.member2.publicKey], + privateFor: [ + keys.tessera.member1.publicKey, + keys.tessera.member2.publicKey, + ], }, - signingCredential: { + web3SigningCredential: { secret: keys.besu.member1.privateKey, type: Web3SigningCredentialType.PrivateKeyHex, }, + keychainId: keychain1.getKeychainId(), + gas: 3000000, }); - t.equals( - res.callOutput, - doctorCactusHex, - "member2 getName callOutput === DoctorCactus", + expect(deployOut).toBeTruthy(); + expect(deployOut).toBeObject(); + + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeObject(); + + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeString(); + + expect(deployOut.transactionReceipt.commitmentHash).toBeTruthy(); + expect(deployOut.transactionReceipt.commitmentHash).toBeString(); + + // t.ok(deployRes.status, "deployRes.status truthy OK"); + // t.equal(deployRes.status, 200, "deployRes.status === 200 OK"); + // t.ok(deployRes.data, "deployRes.data truthy OK"); + // t.ok( + // deployRes.data.transactionReceipt, + // "deployRes.data.transactionReceipt truthy OK", + // ); + + // t.ok(privacyMarkerTxHash, "privacyMarkerTxHash truthy OK"); + + const contractDeployReceipt = + (await web3QuorumMember1.priv.waitForTransactionReceipt( + deployOut.transactionReceipt.commitmentHash, + )) as IPrivateTransactionReceipt; + + expect(contractDeployReceipt).toBeTruthy(); + const receipt = contractDeployReceipt as IPrivateTransactionReceipt; + + const { contractAddress } = receipt; + expect(contractAddress).toBeTruthy(); + expect(contractAddress).toBeString(); + expect(contractAddress).not.toBeEmpty(); + + // Check that the third node does not see the transaction of the contract + // deployment that was sent to node 1 and 2 only, not 3. + const txReceiptNever = + await web3QuorumMember3.priv.waitForTransactionReceipt( + deployOut.transactionReceipt.commitmentHash, + ); + expect(txReceiptNever).toBeFalsy(); + + // Check that node 1 and 2 can indeed see the transaction for the contract + // deployment that was sent to them and them only (node 3 was left out) + + // Note that changing this to use web3QuorumMember3 breaks it and I'm suspecting + // that this is what's plaguing the tests that are based on the connector + // which is instantiated with a single web3+web3 Quorum client. + // What I will try next is to have 3 connectors each with a web3 Quorum client + // that points to one of the 3 nodes and see if that makes it work. + const txReceiptAlways1 = + await web3QuorumMember1.priv.waitForTransactionReceipt( + deployOut.transactionReceipt.commitmentHash, + ); + expect(txReceiptAlways1).toBeTruthy(); + + const txReceiptAlways2 = + await web3QuorumMember2.priv.waitForTransactionReceipt( + deployOut.transactionReceipt.commitmentHash, + ); + expect(txReceiptAlways2).toBeTruthy(); + + const contract = new web3Member1.eth.Contract( + HelloWorldContractJson.abi as never, ); - } - { - t.comment("Checking if member3 can call getName() via connector"); - const res = await connector3.invokeContract({ - contractName: HelloWorldContractJson.contractName, - contractAbi: HelloWorldContractJson.abi, - contractAddress: contractDeployReceipt.contractAddress, - invocationType: EthContractInvocationType.Call, - gas: 3000000, - methodName: "getName", - params: [], - privateTransactionConfig: { + { + const data = contract.methods.setName("ProfessorCactus - #1").encodeABI(); + const functionParams = { + to: contractDeployReceipt.contractAddress, + data, privateFrom: keys.tessera.member1.publicKey, privateFor: [keys.tessera.member2.publicKey], - }, - signingCredential: { - secret: keys.besu.member3.privateKey, - type: Web3SigningCredentialType.PrivateKeyHex, - }, - }); - - t.equal(res.callOutput, "0x", "member3 getName callOutput === 0x OK"); - } - - t.end(); + privateKey: keys.besu.member1.privateKey, + }; + const transactionHash = + await web3QuorumMember1.priv.generateAndSendRawTransaction( + functionParams, + ); + expect(transactionHash).toBeTruthy(); + expect(transactionHash).not.toBeEmpty(); + expect(transactionHash).toBeString(); + + const result = + await web3QuorumMember1.priv.waitForTransactionReceipt(transactionHash); + expect(result).toBeTruthy(); + } + + { + const data = contract.methods.getName().encodeABI(); + const fnParams = { + to: contractDeployReceipt.contractAddress, + data, + privateFrom: keys.tessera.member1.publicKey, + privateFor: [keys.tessera.member2.publicKey], + privateKey: keys.besu.member1.privateKey, + }; + + const privacyGroupId = + web3QuorumMember1.utils.generatePrivacyGroup(fnParams); + const callOutput = await web3QuorumMember1.priv.call(privacyGroupId, { + to: contractDeployReceipt.contractAddress, + data: contract.methods.getName().encodeABI(), + }); + expect(callOutput).toBeTruthy(); + const name = web3Member1.eth.abi.decodeParameter("string", callOutput); + expect(name).toEqual("ProfessorCactus - #1"); + } + + { + // Member 3 cannot see into the privacy group of 1 and 2 so the getName + // will not return the value that was set earlier in that privacy group. + const data = contract.methods.getName().encodeABI(); + const fnParams = { + to: contractDeployReceipt.contractAddress, + data, + privateFrom: keys.tessera.member1.publicKey, + privateFor: [keys.tessera.member2.publicKey], + privateKey: keys.besu.member3.privateKey, + }; + + const privacyGroupId = + web3QuorumMember3.utils.generatePrivacyGroup(fnParams); + const callOutput = await web3QuorumMember3.priv.call(privacyGroupId, { + to: contractDeployReceipt.contractAddress, + data, + }); + expect(callOutput).toEqual("0x"); + } + + { + const data = contract.methods.setName("ProfessorCactus - #2").encodeABI(); + const functionParams = { + to: contractDeployReceipt.contractAddress, + data, + privateFrom: keys.tessera.member2.publicKey, + privateFor: [keys.tessera.member2.publicKey], + privateKey: keys.besu.member2.privateKey, + }; + const transactionHash = + await web3QuorumMember2.priv.generateAndSendRawTransaction( + functionParams, + ); + expect(transactionHash).toBeTruthy(); + + const result = + await web3QuorumMember2.priv.waitForTransactionReceipt(transactionHash); + expect(result).toBeTruthy(); + } + + { + const data = contract.methods.setName("ProfessorCactus - #3").encodeABI(); + const functionParams = { + to: contractDeployReceipt.contractAddress, + data, + privateFrom: keys.tessera.member3.publicKey, + privateKey: keys.besu.member3.privateKey, + privateFor: [keys.tessera.member2.publicKey], + }; + const transactionHash = + await web3QuorumMember3.priv.generateAndSendRawTransaction( + functionParams, + ); + expect(transactionHash).toBeTruthy(); + + const result = + await web3QuorumMember3.priv.waitForTransactionReceipt(transactionHash); + expect(result).toBeTruthy(); + } + + { + const contractInvocationNoPrivTxConfig = connector1.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress: contractDeployReceipt.contractAddress, + invocationType: EthContractInvocationType.Call, + gas: 3000000, + methodName: "getName", + params: [], + signingCredential: { + secret: "incorrect-secret", + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + await expect(contractInvocationNoPrivTxConfig).rejects.toMatch( + /Returned values aren't valid, did it run Out of Gas\? You might also see this error if you are not using the correct ABI for the contract you are retrieving data from, requesting data from a block number that does not exist, or querying a node which is not fully synced\./, + ); + } + + { + const res = await connector1.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress: contractDeployReceipt.contractAddress, + invocationType: EthContractInvocationType.Send, + gas: 3000000, + methodName: "setName", + params: ["Doctor Cactus"], + privateTransactionConfig: { + privateFrom: keys.tessera.member1.publicKey, + privateFor: [keys.tessera.member2.publicKey], + }, + signingCredential: { + secret: keys.besu.member1.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + + expect(res.success).toEqual("0x1"); + } + + { + const res = await connector1.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress: contractDeployReceipt.contractAddress, + invocationType: EthContractInvocationType.Call, + gas: 3000000, + methodName: "getName", + params: [], + privateTransactionConfig: { + privateFrom: keys.tessera.member1.publicKey, + privateFor: [keys.tessera.member2.publicKey], + }, + signingCredential: { + secret: keys.besu.member1.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + + expect(res.callOutput).toEqual(doctorCactusHex); + } + + { + const res = await connector2.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress: contractDeployReceipt.contractAddress, + invocationType: EthContractInvocationType.Call, + gas: 3000000, + methodName: "getName", + params: [], + privateTransactionConfig: { + privateFrom: keys.tessera.member1.publicKey, + privateFor: [keys.tessera.member2.publicKey], + }, + signingCredential: { + secret: keys.besu.member1.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + + expect(res.callOutput).toEqual(doctorCactusHex); + } + + { + const res = await connector3.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress: contractDeployReceipt.contractAddress, + invocationType: EthContractInvocationType.Call, + gas: 3000000, + methodName: "getName", + params: [], + privateTransactionConfig: { + privateFrom: keys.tessera.member1.publicKey, + privateFor: [keys.tessera.member2.publicKey], + }, + signingCredential: { + secret: keys.besu.member3.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + }); + + expect(res.callOutput).toEqual("0x"); + } + }); }); 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 index 2e5bbbfb95..553c963016 100644 --- 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 @@ -1,4 +1,4 @@ -import test, { Test } from "tape-promise/tape"; +import "jest-extended"; import { v4 as uuidv4 } from "uuid"; import { Server as SocketIoServer } from "socket.io"; import { PluginRegistry } from "@hyperledger/cactus-core"; @@ -32,78 +32,46 @@ 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"; +import { Account } from "web3-core"; -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) => { +describe("PluginLedgerConnectorBesu", () => { + const testCase = "deploys contract via .json file"; + const logLevel: LogLevelDesc = "INFO"; const containerImageVersion = "2021-08-24--feat-1244"; const containerImageName = "ghcr.io/hyperledger/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]]), + backend: new Map(), 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 pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + keychainPlugin.set( + HelloWorldContractJson.contractName, + JSON.stringify(HelloWorldContractJson), + ); + const wsApi = new SocketIoServer(server, { path: Constants.SocketIoConnectionPathV1, }); @@ -113,66 +81,116 @@ test(testCase, async (t: Test) => { 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, - }, + let rpcApiHttpHost: string; + let rpcApiWsHost: string; + let firstHighNetWorthAccount: string; + let web3: Web3; + let testEthAccount: Account; + let keychainEntryValue: string; + let connector: PluginLedgerConnectorBesu; + let addressInfo: AddressInfo; + let apiClient: BesuApiClient; + let apiHost: string; + let contractAddress: string; + + beforeAll(async () => { + await pruneDockerAllIfGithubAction({ logLevel }); }); - 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); + beforeAll(async () => { + await besuTestLedger.start(); + rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(uuidv4()); + keychainEntryValue = testEthAccount.privateKey; + keychainPlugin.set(keychainEntryKey, keychainEntryValue); + + addressInfo = await Servers.listen(listenOptions); + + connector = await factory.create({ + rpcApiHttpHost, + rpcApiWsHost, + logLevel, + instanceId: uuidv4(), + pluginRegistry, + }); + + const { address, port } = addressInfo; + apiHost = `http://${address}:${port}`; + const besuApiClientOptions = new BesuApiClientOptions({ + basePath: apiHost, }); + apiClient = new BesuApiClient(besuApiClientOptions); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); }); - 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"); + afterAll(async () => { + await Servers.shutdown(server); + }); - let contractAddress: string; + afterAll(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + afterAll(async () => { + await pruneDockerAllIfGithubAction({ logLevel }); + }); + + test(testCase, async () => { + 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); + }); + }, + ); + expect(aBlockHeader).toBeTruthy(); + expect(aBlockHeader).toBeObject(); + expect(aBlockHeader).not.toBeEmptyObject(); + + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toEqual(10e9); + }); - test("deploys contract via .json file", async (t2: Test) => { + test("deploys contract via .json file", async () => { const deployOut = await connector.deployContract({ keychainId: keychainPlugin.getKeychainId(), contractName: HelloWorldContractJson.contractName, @@ -186,21 +204,16 @@ test(testCase, async (t: Test) => { 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", - ); + expect(deployOut).toBeTruthy(); + expect(deployOut).toBeObject(); + + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeObject(); + + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeString(); contractAddress = deployOut.transactionReceipt.contractAddress as string; - t2.ok( - typeof contractAddress === "string", - "contractAddress typeof string OK", - ); const { callOutput: helloMsg } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -215,14 +228,12 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.ok(helloMsg, "sayHello() output is truthy"); - t2.true( - typeof helloMsg === "string", - "sayHello() output is type of string", - ); + expect(helloMsg).toBeTruthy(); + expect(helloMsg).toBeString(); + expect(helloMsg).not.toBeEmpty(); }); - test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + test("invoke Web3SigningCredentialType.NONE", async () => { const testEthAccount2 = web3.eth.accounts.create(uuidv4()); const { rawTransaction } = await web3.eth.accounts.signTransaction( @@ -250,12 +261,10 @@ test(testCase, async (t: Test) => { }); 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(); + expect(parseInt(balance2, 10)).toEqual(10e6); }); - test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + test("invoke Web3SigningCredentialType.PrivateKeyHex", async () => { const newName = `DrCactus${uuidv4()}`; const setNameOut = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -271,32 +280,28 @@ test(testCase, async (t: Test) => { }, 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", - ); - } + expect(setNameOut).toBeTruthy(); + + const setNameOutPromise1 = 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, + }); + + await expect(setNameOutPromise1).rejects.toMatchObject>({ + message: expect.stringContaining("Nonce too low"), + }); + const { callOutput: getNameOut } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, contractAbi: HelloWorldContractJson.abi, @@ -311,7 +316,7 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + expect(getNameOut).toEqual(newName); const getNameOut2 = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -327,7 +332,7 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + expect(getNameOut2).toBeTruthy(); const response = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -344,7 +349,7 @@ test(testCase, async (t: Test) => { }, value: 10, }); - t2.ok(response, "deposit() payable invocation output is truthy OK"); + expect(response).toBeTruthy(); const { callOutput } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -360,16 +365,11 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.PrivateKeyHex, }, }); - t2.equal( - callOutput, - newName, - `getNameByIndex() output reflects the update OK`, - ); - t2.end(); + expect(callOutput).toEqual(newName); }); - test("invoke Web3SigningCredentialType.CactusKeychainRef", async (t2: Test) => { + test("invoke Web3SigningCredentialType.CactusKeychainRef", async () => { const newName = `DrCactus${uuidv4()}`; const signingCredential: Web3SigningCredentialCactusKeychainRef = { @@ -390,28 +390,23 @@ test(testCase, async (t: Test) => { 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", - ); - } + expect(setNameOut).toBeTruthy(); + + const setNameOutPromise2 = connector.invokeContract({ + contractName: HelloWorldContractJson.contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + + await expect(setNameOutPromise2).rejects.toMatchObject>({ + message: expect.stringContaining("Nonce too low"), + }); const { callOutput: getNameOut } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -423,7 +418,7 @@ test(testCase, async (t: Test) => { gas: 1000000, signingCredential, }); - t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + expect(getNameOut).toEqual(newName); const getNameOut2 = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -435,7 +430,8 @@ test(testCase, async (t: Test) => { gas: 1000000, signingCredential, }); - t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + expect(getNameOut2).toBeTruthy(); const response = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -448,7 +444,8 @@ test(testCase, async (t: Test) => { signingCredential, value: 10, }); - t2.ok(response, "deposit() payable invocation output is truthy OK"); + + expect(response).toBeTruthy(); const { callOutput } = await connector.invokeContract({ contractName: HelloWorldContractJson.contractName, @@ -460,16 +457,11 @@ test(testCase, async (t: Test) => { gas: 1000000, signingCredential, }); - t2.equal( - callOutput, - newName, - `getNameByIndex() output reflects the update OK`, - ); - t2.end(); + expect(callOutput).toEqual(newName); }); - test("get prometheus exporter metrics", async (t2: Test) => { + test("get prometheus exporter metrics", async () => { const res = await apiClient.getPrometheusMetricsV1(); const promMetricsOutput = "# HELP " + @@ -482,15 +474,10 @@ test(testCase, async (t: Test) => { '{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(); + expect(res).toBeTruthy(); + expect(res.data).toBeTruthy(); + expect(res.status).toEqual(200); + expect(res.data.includes(promMetricsOutput)).toBeTrue(); + }); });