From f54c12a79f88017a5d6cf8a51e8fbd24dc92c920 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 17 Jun 2026 16:07:45 +0000 Subject: [PATCH] feat(scanner): make RPC request timeout configurable (default 120s) Adds ETH_RPC_TIMEOUT_MS (default 120000) wired through ScannerService into evmdecoder's eth.http.timeout. With evmdecoder >= 0.0.70 this controls both the node-fetch request timeout and the keep-alive agent socket timeout, so slow nodes / large blocks no longer abort catch-up at the previous 60s default. Bumps version to 1.5.2 (1.5.1 is already tagged/deployed). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BEG3HfBuwNhijzL168Rkvv --- .env.example | 2 ++ package.json | 2 +- src/env.ts | 4 ++++ src/index.ts | 1 + src/services/scanner.service.ts | 8 ++++++++ 5 files changed, 16 insertions(+), 1 deletion(-) 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,