diff --git a/packages/txm/lib/BlockMonitor.ts b/packages/txm/lib/BlockMonitor.ts index 804436b8c9..c79a64da8f 100644 --- a/packages/txm/lib/BlockMonitor.ts +++ b/packages/txm/lib/BlockMonitor.ts @@ -1,7 +1,7 @@ +import { LogTag, Logger } from "@happy.tech/common" import type { Block } from "viem" import { Topics, eventBus } from "./EventBus.js" import type { TransactionManager } from "./TransactionManager.js" - /** * A type alias for {@link Block} with the `blockTag` set to `"latest"`, ensuring type definitions correspond to the latest block. */ @@ -14,23 +14,42 @@ export type LatestBlock = Block */ export class BlockMonitor { private txmgr: TransactionManager + private unwatch: (() => void) | undefined + private blockTimeout: ReturnType | undefined constructor(_transactionManager: TransactionManager) { this.txmgr = _transactionManager } async start() { - this.txmgr.viemClient.watchBlocks({ + this.scheduleTimeout() + this.unwatch = this.txmgr.viemClient.watchBlocks({ onBlock: this.onNewBlock.bind(this), - ...(this.txmgr.transportProtocol === "http" - ? { - pollingInterval: this.txmgr.pollingInterval, - } - : {}), + ...(this.txmgr.transportProtocol === "http" ? { pollingInterval: this.txmgr.pollingInterval } : {}), + onError: (error) => { + Logger.instance.error(LogTag.TXM, "Error watching blocks", error) + this.resetBlockSubscription() + }, }) } private onNewBlock(block: LatestBlock) { + if (this.blockTimeout) clearTimeout(this.blockTimeout) eventBus.emit(Topics.NewBlock, block) + this.scheduleTimeout() + } + + private scheduleTimeout() { + this.blockTimeout = setTimeout(() => { + Logger.instance.warn(LogTag.TXM, "Timeout reached. Resetting block subscription.") + this.resetBlockSubscription() + }, this.txmgr.blockInactivityTimeout) + } + + private resetBlockSubscription() { + if (this.unwatch) { + this.unwatch() + } + this.start() } } diff --git a/packages/txm/lib/TransactionCollector.ts b/packages/txm/lib/TransactionCollector.ts index 703f7fa32d..917e502224 100644 --- a/packages/txm/lib/TransactionCollector.ts +++ b/packages/txm/lib/TransactionCollector.ts @@ -1,3 +1,4 @@ +import { LogTag, Logger } from "@happy.tech/common" import type { LatestBlock } from "./BlockMonitor.js" import { Topics, eventBus } from "./EventBus.js" import { AttemptType } from "./Transaction.js" @@ -36,7 +37,7 @@ export class TransactionCollector { for (const transaction of transactionsBatch) { eventBus.emit(Topics.TransactionSaveFailed, { transaction }) } - console.error("Error saving transactions", saveResult.error) + Logger.instance.error(LogTag.TXM, "Error saving transactions", saveResult.error) return } diff --git a/packages/txm/lib/TransactionManager.ts b/packages/txm/lib/TransactionManager.ts index 474bac1299..fa0a4c60fe 100644 --- a/packages/txm/lib/TransactionManager.ts +++ b/packages/txm/lib/TransactionManager.ts @@ -69,6 +69,12 @@ export type TransactionManagerConfig = { * Defaults to 1/2 of the block time. */ pollingInterval?: number + + /** + * The time without blocks before closing the connection to the RPC node and reconnecting. + * Defaults to 4000 milliseconds. + */ + blockInactivityTimeout?: number } /** The private key of the account used for signing transactions. */ privateKey: Hex @@ -167,6 +173,7 @@ export class TransactionManager { public readonly finalizedTransactionPurgeTime: number public readonly pollingInterval: number public readonly transportProtocol: "http" | "websocket" + public readonly blockInactivityTimeout: number constructor(_config: TransactionManagerConfig) { this.collectors = [] @@ -259,6 +266,7 @@ export class TransactionManager { this.finalizedTransactionPurgeTime = _config.finalizedTransactionPurgeTime || 2 * 60 * 1000 this.pollingInterval = _config.rpc.pollingInterval || (Number(this.blockTime) * 1000) / 2 + this.blockInactivityTimeout = _config.rpc.blockInactivityTimeout || 4000 } /** diff --git a/packages/txm/lib/TransactionSubmitter.ts b/packages/txm/lib/TransactionSubmitter.ts index 1c196d80a9..a8e27232b4 100644 --- a/packages/txm/lib/TransactionSubmitter.ts +++ b/packages/txm/lib/TransactionSubmitter.ts @@ -1,3 +1,5 @@ +import { LogTag } from "@happy.tech/common" +import { Logger } from "@happy.tech/common" import { type Result, err, ok } from "neverthrow" import type { Hash, Hex, TransactionRequestEIP1559 } from "viem" import { encodeFunctionData, keccak256 } from "viem" @@ -72,7 +74,7 @@ export class TransactionSubmitter { const abi = this.txmgr.abiManager.get(transaction.contractName) if (!abi) { - console.error(`ABI not found for contract ${transaction.contractName}`) + Logger.instance.error(LogTag.TXM, `ABI not found for contract ${transaction.contractName}`) return err({ cause: AttemptSubmissionErrorCause.ABINotFound, description: `ABI not found for contract ${transaction.contractName}`, diff --git a/packages/txm/lib/TxMonitor.ts b/packages/txm/lib/TxMonitor.ts index a0d48b65ab..7bd78ad8cd 100644 --- a/packages/txm/lib/TxMonitor.ts +++ b/packages/txm/lib/TxMonitor.ts @@ -1,4 +1,4 @@ -import { bigIntMax, promiseWithResolvers, unknownToError } from "@happy.tech/common" +import { LogTag, Logger, bigIntMax, promiseWithResolvers, unknownToError } from "@happy.tech/common" import { type Result, ResultAsync, err, ok } from "neverthrow" import { type GetTransactionReceiptErrorType, type TransactionReceipt, TransactionReceiptNotFoundError } from "viem" import type { LatestBlock } from "./BlockMonitor.js" @@ -54,7 +54,7 @@ export class TxMonitor { try { await this.handleNewBlock(block) } catch (error) { - console.error("Error in handleNewBlock: ", error) + Logger.instance.error(LogTag.TXM, "Error in handleNewBlock: ", error) } this.locked = false @@ -113,7 +113,10 @@ export class TxMonitor { that the transaction was executed and we don’t know */ if (attemptOrResults.some((v) => v.isErr())) { - console.error(`Failed to get transaction receipt for transaction ${transaction.intentId}`) + Logger.instance.error( + LogTag.TXM, + `Failed to get transaction receipt for transaction ${transaction.intentId}`, + ) return } @@ -126,7 +129,7 @@ export class TxMonitor { if (receipt.status === "success") { if (attempt.type === AttemptType.Cancellation) { - console.error(`Transaction ${transaction.intentId} was cancelled`) + Logger.instance.error(LogTag.TXM, `Transaction ${transaction.intentId} was cancelled`) return transaction.changeStatus(TransactionStatus.Cancelled) } return transaction.changeStatus(TransactionStatus.Success) @@ -140,7 +143,7 @@ export class TxMonitor { ) if (!shouldRetry) { - console.error(`Transaction ${transaction.intentId} failed`) + Logger.instance.error(LogTag.TXM, `Transaction ${transaction.intentId} failed`) return transaction.changeStatus(TransactionStatus.Failed) } @@ -155,7 +158,7 @@ export class TxMonitor { ) if (result.isErr()) { - console.error("Error flushing transactions in onNewBlock") + Logger.instance.error(LogTag.TXM, "Error flushing transactions in onNewBlock") } } @@ -182,7 +185,8 @@ export class TxMonitor { const attempt = transaction.lastAttempt if (!attempt) { - console.error( + Logger.instance.error( + LogTag.TXM, `Transaction ${transaction.intentId} inconsistent state: no attempt found in handleExpiredTransaction`, ) return @@ -207,7 +211,8 @@ export class TxMonitor { const attempt = transaction.lastAttempt if (!attempt) { - console.error( + Logger.instance.error( + LogTag.TXM, `Transaction ${transaction.intentId} inconsistent state: no attempt found in handleStuckTransaction`, ) return diff --git a/support/common/lib/index.ts b/support/common/lib/index.ts index 87bfd61ead..e3a1ec7a58 100644 --- a/support/common/lib/index.ts +++ b/support/common/lib/index.ts @@ -8,7 +8,7 @@ export { useIsHydrated } from "./hooks/isHydrated" // === UTILS ======================================================================================= -export { Logger } from "./utils/logger" +export { Logger, LogTag } from "./utils/logger" export { atomWithCompare, atomWithCompareAndStorage, accessorsFromAtom, createBigIntStorage } from "./utils/jotai" diff --git a/support/common/lib/utils/logger.ts b/support/common/lib/utils/logger.ts index d2304a2411..5507b6510e 100644 --- a/support/common/lib/utils/logger.ts +++ b/support/common/lib/utils/logger.ts @@ -20,6 +20,7 @@ export enum LogLevel { */ export enum LogTag { ALL = "All", + TXM = "Txm", } /**