diff --git a/.env.example b/.env.example index 046efaf..7876c62 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,8 @@ MONGO_REPLICASET= # Ethereum RPC ETH_RPC_URL=http://10.1.200.113:8545 ETH_WS_URL=ws://10.1.200.113:8546 +# Max time (ms) for a single JSON-RPC request before timing out (slow nodes / large blocks) +ETH_RPC_TIMEOUT_MS=120000 # Chain CHAIN_ID=1 diff --git a/package.json b/package.json index 83962c7..053948f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@missionsquad/agentindex-api", - "version": "1.5.1", + "version": "1.5.2", "description": "AgentIndex blockchain scanner API", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/env.ts b/src/env.ts index 4d9b9f6..daf049f 100644 --- a/src/env.ts +++ b/src/env.ts @@ -24,6 +24,10 @@ export const env = { // Ethereum RPC ETH_RPC_URL: process.env.ETH_RPC_URL || '', ETH_WS_URL: process.env.ETH_WS_URL || '', + // Max time (ms) for a single JSON-RPC request to the Ethereum node before timing + // out. Applied to evmdecoder's HTTP transport (node-fetch request timeout AND the + // keep-alive agent socket timeout). Raise for slow nodes / large blocks. + ETH_RPC_TIMEOUT_MS: parseInt(process.env.ETH_RPC_TIMEOUT_MS || '120000', 10), // Chain CHAIN_ID: parseInt(process.env.CHAIN_ID || '1', 10), diff --git a/src/index.ts b/src/index.ts index f0d9b91..422aa07 100644 --- a/src/index.ts +++ b/src/index.ts @@ -337,6 +337,7 @@ async function startServer(): Promise { rpcUrl: chainConfig.rpcUrl, abiDirectory: env.ABI_DIRECTORY, txConcurrency: env.SCANNER_TX_CONCURRENCY, + rpcTimeoutMs: env.ETH_RPC_TIMEOUT_MS, onEventFactsPersisted: (eventFacts) => { void publishDashboardActivityFromPersistedEventFacts(eventFacts).catch((error) => { log({ level: 'warn', msg: 'Failed to publish dashboard activity websocket events', error }) diff --git a/src/services/scanner.service.ts b/src/services/scanner.service.ts index 8a1ba3e..8030712 100644 --- a/src/services/scanner.service.ts +++ b/src/services/scanner.service.ts @@ -29,6 +29,7 @@ interface ScannerOptions { rpcUrl: string abiDirectory?: string txConcurrency?: number + rpcTimeoutMs?: number onEventFactsPersisted?: (eventFacts: EventFact[]) => void } @@ -49,6 +50,7 @@ export class ScannerService { private readonly rpcUrl: string private readonly abiDirectory: string | undefined private readonly txConcurrency: number + private readonly rpcTimeoutMs: number private readonly onEventFactsPersisted?: (eventFacts: EventFact[]) => void private readonly inFlightTx = new Map>() @@ -58,6 +60,7 @@ export class ScannerService { this.rpcUrl = opts.rpcUrl this.abiDirectory = opts.abiDirectory this.txConcurrency = Math.max(1, opts.txConcurrency ?? 8) + this.rpcTimeoutMs = Math.max(1000, opts.rpcTimeoutMs ?? 120_000) this.onEventFactsPersisted = opts.onEventFactsPersisted } @@ -65,6 +68,11 @@ export class ScannerService { const config: DeepPartial = { eth: { url: this.rpcUrl, + http: { + // Configurable RPC request timeout. evmdecoder >= 0.0.70 propagates this to + // both the node-fetch request timeout and the keep-alive agent socket timeout. + timeout: this.rpcTimeoutMs, + }, }, abi: { decodeAnonymous: true,