diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index f7f2d46b44db..bbf2f8d56948 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -1,5 +1,6 @@ import { EpochCache } from '@aztec/epoch-cache'; import { createEthereumChain } from '@aztec/ethereum/chain'; +import { makeL1HttpTransport } from '@aztec/ethereum/client'; import { InboxContract, RollupContract } from '@aztec/ethereum/contracts'; import type { ViemPublicDebugClient } from '@aztec/ethereum/types'; import { BlockNumber } from '@aztec/foundation/branded-types'; @@ -18,7 +19,7 @@ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { EventEmitter } from 'events'; -import { createPublicClient, fallback, http } from 'viem'; +import { createPublicClient } from 'viem'; import { Archiver, type ArchiverDeps } from './archiver.js'; import { type ArchiverConfig, mapArchiverConfig } from './config.js'; @@ -59,9 +60,10 @@ export async function createArchiver( // Create Ethereum clients const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId); + const httpTimeout = config.l1HttpTimeoutMS; const publicClient = createPublicClient({ chain: chain.chainInfo, - transport: fallback(config.l1RpcUrls.map(url => http(url, { batch: false }))), + transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: httpTimeout }), pollingInterval: config.viemPollingIntervalMS, }); @@ -69,7 +71,7 @@ export async function createArchiver( const debugRpcUrls = config.l1DebugRpcUrls.length > 0 ? config.l1DebugRpcUrls : config.l1RpcUrls; const debugClient = createPublicClient({ chain: chain.chainInfo, - transport: fallback(debugRpcUrls.map(url => http(url, { batch: false }))), + transport: makeL1HttpTransport(debugRpcUrls, { timeout: httpTimeout }), pollingInterval: config.viemPollingIntervalMS, }) as ViemPublicDebugClient; diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index da94fd8fb1c5..dca7047a9efb 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -5,7 +5,7 @@ import { Blob } from '@aztec/blob-lib'; import { ARCHIVE_HEIGHT, type L1_TO_L2_MSG_TREE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants'; import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache'; import { createEthereumChain } from '@aztec/ethereum/chain'; -import { getPublicClient } from '@aztec/ethereum/client'; +import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client'; import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts'; import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; @@ -113,7 +113,7 @@ import { } from '@aztec/validator-client'; import { createWorldStateSynchronizer } from '@aztec/world-state'; -import { createPublicClient, fallback, http } from 'viem'; +import { createPublicClient } from 'viem'; import { createSentinel } from '../sentinel/factory.js'; import { Sentinel } from '../sentinel/sentinel.js'; @@ -257,7 +257,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { const publicClient = createPublicClient({ chain: ethereumChain.chainInfo, - transport: fallback(config.l1RpcUrls.map((url: string) => http(url, { batch: false }))), + transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }), pollingInterval: config.viemPollingIntervalMS, }); diff --git a/yarn-project/blob-client/src/client/config.ts b/yarn-project/blob-client/src/client/config.ts index afdf97e757f0..3f5ca648a011 100644 --- a/yarn-project/blob-client/src/client/config.ts +++ b/yarn-project/blob-client/src/client/config.ts @@ -3,6 +3,7 @@ import { SecretValue, booleanConfigHelper, getConfigFromMappings, + optionalNumberConfigHelper, } from '@aztec/foundation/config'; import { type BlobArchiveApiConfig, blobArchiveApiConfigMappings } from '../archive/config.js'; @@ -55,6 +56,9 @@ export interface BlobClientConfig extends BlobArchiveApiConfig { * Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour) */ blobHealthcheckUploadIntervalMinutes?: number; + + /** Timeout for HTTP requests to the L1 RPC node in ms. */ + l1HttpTimeoutMS?: number; } export const blobClientConfigMapping: ConfigMappingsType = { @@ -108,6 +112,11 @@ export const blobClientConfigMapping: ConfigMappingsType = { description: 'Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour)', parseEnv: (val: string | undefined) => (val ? +val : undefined), }, + l1HttpTimeoutMS: { + env: 'ETHEREUM_HTTP_TIMEOUT_MS', + description: 'Timeout for HTTP requests to the L1 RPC node in ms.', + ...optionalNumberConfigHelper(), + }, ...blobArchiveApiConfigMappings, }; diff --git a/yarn-project/blob-client/src/client/http.ts b/yarn-project/blob-client/src/client/http.ts index 5d626933261a..1a7396bd5f06 100644 --- a/yarn-project/blob-client/src/client/http.ts +++ b/yarn-project/blob-client/src/client/http.ts @@ -1,10 +1,11 @@ import { Blob, type BlobJson, computeEthVersionedBlobHash } from '@aztec/blob-lib'; +import { makeL1HttpTransport } from '@aztec/ethereum/client'; import { shuffle } from '@aztec/foundation/array'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { makeBackoff, retry } from '@aztec/foundation/retry'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; -import { type RpcBlock, createPublicClient, fallback, http } from 'viem'; +import { type RpcBlock, createPublicClient } from 'viem'; import { createBlobArchiveClient } from '../archive/factory.js'; import type { BlobArchiveClient } from '../archive/interface.js'; @@ -497,7 +498,7 @@ export class HttpBlobClient implements BlobClientInterface { // Ping execution node to get the parentBeaconBlockRoot for this block let parentBeaconBlockRoot: string | undefined; const client = createPublicClient({ - transport: fallback(l1RpcUrls.map(url => http(url, { batch: false }))), + transport: makeL1HttpTransport(l1RpcUrls, { timeout: this.config.l1HttpTimeoutMS }), }); try { const res: RpcBlock = await client.request({ diff --git a/yarn-project/epoch-cache/src/config.ts b/yarn-project/epoch-cache/src/config.ts index beae6d994158..bd9b76c1cd58 100644 --- a/yarn-project/epoch-cache/src/config.ts +++ b/yarn-project/epoch-cache/src/config.ts @@ -3,7 +3,7 @@ import { type L1ReaderConfig, getL1ReaderConfigFromEnv } from '@aztec/ethereum/l export type EpochCacheConfig = Pick< L1ReaderConfig & L1ContractsConfig, - 'l1RpcUrls' | 'l1ChainId' | 'viemPollingIntervalMS' | 'ethereumSlotDuration' + 'l1RpcUrls' | 'l1ChainId' | 'viemPollingIntervalMS' | 'l1HttpTimeoutMS' | 'ethereumSlotDuration' >; export function getEpochCacheConfigEnvVars(): EpochCacheConfig { diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 5c29e207a015..e961706d815c 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -1,4 +1,5 @@ import { createEthereumChain } from '@aztec/ethereum/chain'; +import { makeL1HttpTransport } from '@aztec/ethereum/client'; import { NoCommitteeError, RollupContract } from '@aztec/ethereum/contracts'; import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -14,7 +15,7 @@ import { getTimestampRangeForEpoch, } from '@aztec/stdlib/epoch-helpers'; -import { createPublicClient, encodeAbiParameters, fallback, http, keccak256 } from 'viem'; +import { createPublicClient, encodeAbiParameters, keccak256 } from 'viem'; import { type EpochCacheConfig, getEpochCacheConfigEnvVars } from './config.js'; @@ -93,7 +94,7 @@ export class EpochCache implements EpochCacheInterface { const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId); const publicClient = createPublicClient({ chain: chain.chainInfo, - transport: fallback(config.l1RpcUrls.map(url => http(url, { batch: false }))), + transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }), pollingInterval: config.viemPollingIntervalMS, }); rollup = new RollupContract(publicClient, rollupOrAddress.toString()); diff --git a/yarn-project/ethereum/src/client.ts b/yarn-project/ethereum/src/client.ts index 1cc4c8a9be81..3769b798719c 100644 --- a/yarn-project/ethereum/src/client.ts +++ b/yarn-project/ethereum/src/client.ts @@ -25,10 +25,17 @@ type Config = { l1ChainId: number; /** The polling interval viem uses in ms */ viemPollingIntervalMS?: number; + /** Timeout for HTTP requests to the L1 RPC node in ms. */ + l1HttpTimeoutMS?: number; }; export type { Config as EthereumClientConfig }; +/** Creates a viem fallback HTTP transport for the given L1 RPC URLs. */ +export function makeL1HttpTransport(rpcUrls: string[], opts?: { timeout?: number }) { + return fallback(rpcUrls.map(url => http(url, { batch: false, timeout: opts?.timeout }))); +} + // TODO: Use these methods to abstract the creation of viem clients. /** Returns a viem public client given the L1 config. */ @@ -36,7 +43,7 @@ export function getPublicClient(config: Config): ViemPublicClient { const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId); return createPublicClient({ chain: chain.chainInfo, - transport: fallback(config.l1RpcUrls.map(url => http(url, { batch: false }))), + transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }), pollingInterval: config.viemPollingIntervalMS, }); } @@ -77,6 +84,7 @@ export function createExtendedL1Client( chain: Chain = foundry, pollingIntervalMS?: number, addressIndex?: number, + opts?: { httpTimeoutMS?: number }, ): ExtendedViemWalletClient { const hdAccount = typeof mnemonicOrPrivateKeyOrHdAccount === 'string' @@ -88,7 +96,7 @@ export function createExtendedL1Client( const extendedClient = createWalletClient({ account: hdAccount, chain, - transport: fallback(rpcUrls.map(url => http(url, { batch: false }))), + transport: makeL1HttpTransport(rpcUrls, { timeout: opts?.httpTimeoutMS }), pollingInterval: pollingIntervalMS, }).extend(publicActions); diff --git a/yarn-project/ethereum/src/l1_reader.ts b/yarn-project/ethereum/src/l1_reader.ts index 753166cd341f..4d31bbd78ac4 100644 --- a/yarn-project/ethereum/src/l1_reader.ts +++ b/yarn-project/ethereum/src/l1_reader.ts @@ -1,4 +1,9 @@ -import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config'; +import { + type ConfigMappingsType, + getConfigFromMappings, + numberConfigHelper, + optionalNumberConfigHelper, +} from '@aztec/foundation/config'; import { type L1ContractAddresses, l1ContractAddressesMapping } from './l1_contract_addresses.js'; @@ -14,6 +19,8 @@ export interface L1ReaderConfig { l1Contracts: L1ContractAddresses; /** The polling interval viem uses in ms */ viemPollingIntervalMS: number; + /** Timeout for HTTP requests to the L1 RPC node in ms. */ + l1HttpTimeoutMS?: number; } export const l1ReaderConfigMappings: ConfigMappingsType = { @@ -43,6 +50,11 @@ export const l1ReaderConfigMappings: ConfigMappingsType = { description: 'The polling interval viem uses in ms', ...numberConfigHelper(1_000), }, + l1HttpTimeoutMS: { + env: 'ETHEREUM_HTTP_TIMEOUT_MS', + description: 'Timeout for HTTP requests to the L1 RPC node in ms.', + ...optionalNumberConfigHelper(), + }, }; export function getL1ReaderConfigFromEnv(): L1ReaderConfig { diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 44fc28ecc8ef..4e7c1eb89176 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -80,6 +80,7 @@ export type EnvVar = | 'KEY_STORE_DIRECTORY' | 'L1_CHAIN_ID' | 'L1_CONSENSUS_HOST_URLS' + | 'ETHEREUM_HTTP_TIMEOUT_MS' | 'L1_CONSENSUS_HOST_API_KEYS' | 'L1_CONSENSUS_HOST_API_KEY_HEADERS' | 'LOG_JSON' diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index ee312b2ce90d..48465c03d640 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -3,6 +3,7 @@ import type { BlobClientInterface } from '@aztec/blob-client/client'; import { Blob } from '@aztec/blob-lib'; import type { EpochCacheInterface } from '@aztec/epoch-cache'; import { createEthereumChain } from '@aztec/ethereum/chain'; +import { makeL1HttpTransport } from '@aztec/ethereum/client'; import { RollupContract } from '@aztec/ethereum/contracts'; import { L1TxUtils } from '@aztec/ethereum/l1-tx-utils'; import { PublisherManager } from '@aztec/ethereum/publisher-manager'; @@ -27,7 +28,7 @@ import type { } from '@aztec/stdlib/interfaces/server'; import { L1Metrics, type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; -import { createPublicClient, fallback, http } from 'viem'; +import { createPublicClient } from 'viem'; import type { SpecificProverNodeConfig } from './config.js'; import { EpochMonitor } from './monitors/epoch-monitor.js'; @@ -95,7 +96,7 @@ export async function createProverNode( const publicClient = createPublicClient({ chain: chain.chainInfo, - transport: fallback(config.l1RpcUrls.map((url: string) => http(url, { batch: false }))), + transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }), pollingInterval: config.viemPollingIntervalMS, }); diff --git a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts index 73f4bc47f1a1..20c0f236292d 100644 --- a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts +++ b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts @@ -1,4 +1,5 @@ import { createEthereumChain } from '@aztec/ethereum/chain'; +import { makeL1HttpTransport } from '@aztec/ethereum/client'; import type { L1ContractsConfig } from '@aztec/ethereum/config'; import { RollupContract } from '@aztec/ethereum/contracts'; import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader'; @@ -16,7 +17,7 @@ import type { } from '@aztec/stdlib/tx'; import { GlobalVariables } from '@aztec/stdlib/tx'; -import { createPublicClient, fallback, http } from 'viem'; +import { createPublicClient } from 'viem'; /** * Simple global variables builder. @@ -53,7 +54,7 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { this.publicClient = createPublicClient({ chain: chain.chainInfo, - transport: fallback(chain.rpcUrls.map(url => http(url, { batch: false }))), + transport: makeL1HttpTransport(chain.rpcUrls, { timeout: config.l1HttpTimeoutMS }), pollingInterval: config.viemPollingIntervalMS, });