From 8f4ed85806883c5175281849f0f11042274629ba Mon Sep 17 00:00:00 2001 From: Rafael Belchior Date: Fri, 15 Nov 2024 19:40:36 +0200 Subject: [PATCH] feat(cactus-connector-ethereum): add RunTransactionV1Exchange to share receipt data Also improves watch blocks Authored-by: Bruno Mateus Co-authored-by: Rafael Belchior Signed-off-by: Rafael Belchior --- .../plugin-ledger-connector-ethereum.ts | 24 +++++ .../src/main/typescript/public-api.ts | 1 + .../src/main/typescript/sign-utils.ts | 7 +- .../web-services/watch-blocks-v1-endpoint.ts | 90 ++++++++++--------- ...eploy-and-invoke-using-keychain-v1.test.ts | 5 +- 5 files changed, 80 insertions(+), 47 deletions(-) diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts index 551baf07fb..d455d23aa9 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/plugin-ledger-connector-ethereum.ts @@ -88,6 +88,13 @@ import { Web3StringReturnFormat, convertWeb3ReceiptStatusToBool, } from "./types/util-types"; +import { Observable, ReplaySubject } from "rxjs"; + +export interface RunTransactionV1Exchange { + request: InvokeContractV1Request; + response: RunTransactionResponse; + timestamp: Date; +} // Used when waiting for WS requests to be send correctly before disconnecting const waitForWsProviderRequestsTimeout = 5 * 1000; // 5s @@ -150,6 +157,9 @@ export class PluginLedgerConnectorEthereum private watchBlocksSubscriptions: Map = new Map(); + private txSubject: ReplaySubject = + new ReplaySubject(); + public get className(): string { return PluginLedgerConnectorEthereum.CLASS_NAME; } @@ -235,6 +245,10 @@ export class PluginLedgerConnectorEthereum return this.instanceId; } + public getTxSubjectObservable(): Observable { + return this.txSubject.asObservable(); + } + private async removeWatchBlocksSubscriptionForSocket(socketId: string) { try { const subscription = this.watchBlocksSubscriptions.get(socketId); @@ -655,6 +669,16 @@ export class PluginLedgerConnectorEthereum }); const success = out.transactionReceipt.status; const data = { success, out }; + + // create RunTransactionV1Exchange for transaction monitoring + const receiptData: RunTransactionV1Exchange = { + request: req, + response: out, + timestamp: new Date(), + }; + this.log.debug(`RunTransactionV1Exchange created ${receiptData}`); + this.txSubject.next(receiptData); + return data; } else { throw new Error( diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts index 56d5b0cfbb..cd2e9f7194 100755 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/public-api.ts @@ -3,6 +3,7 @@ export * from "./generated/openapi/typescript-axios"; export { PluginLedgerConnectorEthereum, IPluginLedgerConnectorEthereumOptions, + RunTransactionV1Exchange, } from "./plugin-ledger-connector-ethereum"; export * from "./sign-utils"; diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/sign-utils.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/sign-utils.ts index 0c9a163268..7156331980 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/sign-utils.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/sign-utils.ts @@ -32,13 +32,16 @@ export function signTransaction( | FeeMarketEIP1559Transaction | BlobEIP4844Transaction; } { - let chainConfiguration = new Common({ chain: Chain.Mainnet }); + let chainConfiguration = new Common({ + chain: Chain.Mainnet, + hardfork: "istanbul", + }); if (customChainInfo) { chainConfiguration = Common.custom(customChainInfo); } const transaction = TransactionFactory.fromTxData(txData, { - common: chainConfiguration, + common: chainConfiguration as any, }); if (privateKey.toLowerCase().startsWith("0x")) { privateKey = privateKey.slice(2); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts index cb94cce4c5..6125a6ece4 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/watch-blocks-v1-endpoint.ts @@ -5,7 +5,7 @@ */ import Web3, { BlockHeaderOutput, FMT_BYTES, FMT_NUMBER } from "web3"; -import { NewHeadsSubscription } from "web3-eth"; +import { NewHeadsSubscription, RegisteredSubscription } from "web3-eth"; import type { Socket as SocketIoSocket } from "socket.io"; import { @@ -21,10 +21,7 @@ import { WatchBlocksV1, Web3Transaction, } from "../generated/openapi/typescript-axios"; -import { - ConvertWeb3ReturnToString, - Web3StringReturnFormat, -} from "../types/util-types"; +import { ConvertWeb3ReturnToString } from "../types/util-types"; const DEFAULT_HTTP_POLL_INTERVAL = 1000 * 5; // 5 seconds const LAST_SEEN_LATEST_BLOCK = -1; // must be negative number, will be replaced with latest block in code @@ -337,7 +334,7 @@ export class WatchBlocksV1SubscriptionEndpoint extends WatchBlocksV1Endpoint { } public async subscribe() { - const { socket, log, web3, isGetBlockData } = this; + const { socket, log, isGetBlockData } = this; log.info(`${WatchBlocksV1.Subscribe} [WS Subscription] => ${socket.id}`); if (this.isSubscribed) { @@ -352,46 +349,53 @@ export class WatchBlocksV1SubscriptionEndpoint extends WatchBlocksV1Endpoint { await this.emitAllSinceLastSeenBlock(); log.debug("Subscribing to Web3 new block headers event..."); - this.newBlocksSubscription = await web3.eth.subscribe( - "newBlockHeaders", - undefined, - Web3StringReturnFormat, - ); - - this.newBlocksSubscription.on("data", async (blockHeader) => { - try { - log.debug("newBlockHeaders:", blockHeader); - - if (typeof blockHeader.number === undefined) { - throw new Error( - `Missing block number in received block header (number: ${blockHeader.number}, hash: ${blockHeader.hash})`, - ); - } - const blockNumber = Number(blockHeader.number); - if (blockNumber - this.lastSeenBlock > 2) { - log.info( - `Detected missing blocks since latest one (blockNumber: ${blockNumber}, lastSeenBlock: ${this.lastSeenBlock})`, - ); - await this.emitAllSinceLastSeenBlock(); - } + const options = { + subscription: "newBlockHeaders" as keyof RegisteredSubscription, + parameters: {}, + }; - let next: WatchBlocksV1Progress; - if (isGetBlockData) { - next = await this.getFullBlockProgress(blockNumber); - } else { - next = await this.headerDataToBlockProgress(blockHeader); + this.newBlocksSubscription = (await this.web3.eth.subscribe( + options.subscription, + options.parameters, + )) as unknown as NewHeadsSubscription; + + this.newBlocksSubscription?.on( + "data", + async (blockHeader: BlockHeaderOutput) => { + try { + log.debug("newBlockHeaders:", blockHeader); + + if (blockHeader.number === undefined || blockHeader.number === null) { + throw new Error( + `Missing block number in received block header (number: ${blockHeader.number}, hash: ${blockHeader.hash})`, + ); + } + const blockNumber = Number(blockHeader.number); + if (blockNumber - this.lastSeenBlock > 2) { + log.info( + `Detected missing blocks since latest one (blockNumber: ${blockNumber}, lastSeenBlock: ${this.lastSeenBlock})`, + ); + await this.emitAllSinceLastSeenBlock(); + } + + let next: WatchBlocksV1Progress; + if (isGetBlockData) { + next = await this.getFullBlockProgress(blockNumber); + } else { + next = await this.headerDataToBlockProgress(blockHeader); + } + + socket.emit(WatchBlocksV1.Next, next); + + this.lastSeenBlock = blockNumber; + } catch (error) { + log.warn("Error when parsing subscribed block data:", error); } + }, + ); - socket.emit(WatchBlocksV1.Next, next); - - this.lastSeenBlock = blockNumber; - } catch (error) { - log.warn("Error when parsing subscribed block data:", error); - } - }); - - this.newBlocksSubscription.on("error", async (error) => { - console.log("Error when subscribing to new block header: ", error); + this.newBlocksSubscription?.on("error", async (error: Error) => { + log.error("Error when subscribing to new block header: ", error); socket.emit(WatchBlocksV1.Error, safeStringifyException(error)); await this.unsubscribe(); }); diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts index 70f4f4dda7..998b8326d8 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-keychain-v1.test.ts @@ -19,6 +19,7 @@ import { v4 as uuidV4 } from "uuid"; import { AddressInfo } from "net"; import { Server as SocketIoServer } from "socket.io"; import Web3, { HexString } from "web3"; +import { Address } from "@ethereumjs/util"; import { LogLevelDesc, @@ -351,11 +352,11 @@ describe("Ethereum contract deploy and invoke using keychain tests", () => { test("invoke Web3SigningCredentialType.None", async () => { const testEthAccount2 = web3.eth.accounts.create(); - + const address = Address.fromString(testEthAccount2.address); const value = 10e6; const { serializedTransactionHex } = signTransaction( { - to: testEthAccount2.address, + to: address, value, maxPriorityFeePerGas: 0, maxFeePerGas: 0x40000000,