Skip to content
This repository was archived by the owner on Aug 3, 2026. It is now read-only.

Commit 7570f92

Browse files
committed
feat(rpc): add configurable timeouts for JSON-RPC client
- Introduced environment variables for total request timeout and TCP connect timeout, allowing customization of the RPC client's behavior. - Implemented a new function to build the RPC client with the specified timeouts. - Updated `RpcBlockDataProvider` to utilize the new RPC client setup. - Added tests to verify timeout behavior in the RPC client.
1 parent 336d037 commit 7570f92

1 file changed

Lines changed: 97 additions & 5 deletions

File tree

core/src/provider/rpc.rs

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use alloy_transport_http::Http;
66
use raiko_lib::clear_line;
77
use reqwest_alloy::Client;
88
use reth_primitives::revm_primitives::{AccountInfo, Bytecode};
9-
use std::collections::HashMap;
9+
use std::{collections::HashMap, env, time::Duration};
1010
use tracing::debug;
1111

1212
use crate::{
@@ -15,6 +15,36 @@ use crate::{
1515
MerkleProof,
1616
};
1717

18+
/// Env: total per-request timeout for L1/L2 JSON-RPC (connect + send + response body).
19+
/// Default 300s so large `eth_getProof` / batch calls can finish on slow nodes.
20+
const ENV_RPC_HTTP_TIMEOUT_SECS: &str = "RAIKO_RPC_HTTP_TIMEOUT_SECS";
21+
/// Env: TCP connect timeout only. Default 30s.
22+
const ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS: &str = "RAIKO_RPC_HTTP_CONNECT_TIMEOUT_SECS";
23+
24+
fn build_rpc_reqwest_client() -> RaikoResult<Client> {
25+
let timeout_secs: u64 = env::var(ENV_RPC_HTTP_TIMEOUT_SECS)
26+
.ok()
27+
.and_then(|s| s.parse().ok())
28+
.unwrap_or(300);
29+
let connect_secs: u64 = env::var(ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS)
30+
.ok()
31+
.and_then(|s| s.parse().ok())
32+
.unwrap_or(30);
33+
34+
Client::builder()
35+
.timeout(Duration::from_secs(timeout_secs))
36+
.connect_timeout(Duration::from_secs(connect_secs))
37+
.build()
38+
.map_err(|e| RaikoError::RPC(format!("Failed to build RPC HTTP client: {e}")))
39+
}
40+
41+
fn rpc_http_transport(url: reqwest::Url) -> RaikoResult<(Http<Client>, bool)> {
42+
let client = build_rpc_reqwest_client()?;
43+
let http = Http::with_client(client, url);
44+
let is_local = http.guess_local();
45+
Ok((http, is_local))
46+
}
47+
1848
#[derive(Clone)]
1949
pub struct RpcBlockDataProvider {
2050
pub provider: ReqwestProvider,
@@ -31,9 +61,11 @@ impl RpcBlockDataProvider {
3161
"provider rpc url: {:?} for block_number {}",
3262
url, block_number
3363
);
64+
let (http, is_local) = rpc_http_transport(url)?;
65+
let rpc_client = RpcClient::new(http.clone(), is_local);
3466
Ok(Self {
35-
provider: ProviderBuilder::new().on_provider(RootProvider::new_http(url.clone())),
36-
client: ClientBuilder::default().http(url),
67+
provider: ProviderBuilder::new().on_provider(RootProvider::new(rpc_client)),
68+
client: ClientBuilder::default().transport(http, is_local),
3769
block_numbers: vec![block_number, block_number + 1],
3870
})
3971
}
@@ -49,9 +81,11 @@ impl RpcBlockDataProvider {
4981
"Batch provider rpc: {:?} for block_number {}",
5082
url, block_numbers[0]
5183
);
84+
let (http, is_local) = rpc_http_transport(url)?;
85+
let rpc_client = RpcClient::new(http.clone(), is_local);
5286
Ok(Self {
53-
provider: ProviderBuilder::new().on_provider(RootProvider::new_http(url.clone())),
54-
client: ClientBuilder::default().http(url),
87+
provider: ProviderBuilder::new().on_provider(RootProvider::new(rpc_client)),
88+
client: ClientBuilder::default().transport(http, is_local),
5589
block_numbers,
5690
})
5791
}
@@ -368,3 +402,61 @@ impl BlockDataProvider for RpcBlockDataProvider {
368402
Ok(storage_proofs)
369403
}
370404
}
405+
406+
#[cfg(test)]
407+
mod tests {
408+
use super::*;
409+
use serial_test::serial;
410+
use tokio::io::AsyncReadExt;
411+
use tokio::net::TcpListener;
412+
413+
fn set_short_rpc_timeouts_for_test() {
414+
std::env::set_var(ENV_RPC_HTTP_TIMEOUT_SECS, "1");
415+
std::env::set_var(ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS, "1");
416+
}
417+
418+
fn clear_rpc_timeout_env() {
419+
std::env::remove_var(ENV_RPC_HTTP_TIMEOUT_SECS);
420+
std::env::remove_var(ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS);
421+
}
422+
423+
/// Local TCP server: read the JSON-RPC POST then stall so the client hits `reqwest` timeout.
424+
async fn spawn_stall_after_accept_json_rpc() -> String {
425+
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind test listener");
426+
let addr = listener.local_addr().expect("listener addr");
427+
tokio::spawn(async move {
428+
if let Ok((mut stream, _)) = listener.accept().await {
429+
let mut buf = [0u8; 8192];
430+
let _ = stream.read(&mut buf).await;
431+
tokio::time::sleep(Duration::from_secs(600)).await;
432+
}
433+
});
434+
format!("http://{addr}")
435+
}
436+
437+
#[tokio::test]
438+
#[serial]
439+
async fn get_blocks_returns_rpc_error_when_http_times_out() {
440+
set_short_rpc_timeouts_for_test();
441+
let url = spawn_stall_after_accept_json_rpc().await;
442+
let provider = RpcBlockDataProvider::new(&url, 1)
443+
.await
444+
.expect("provider new with short timeout");
445+
let result = provider.get_blocks(&[(1, false)]).await;
446+
clear_rpc_timeout_env();
447+
448+
let err = result.expect_err("expected RPC failure when server does not respond in time");
449+
let RaikoError::RPC(payload) = &err else {
450+
panic!("expected RaikoError::RPC, got {err:?}");
451+
};
452+
let lower = payload.to_lowercase();
453+
// Reqwest may report `operation timed out` or a generic `error sending request for url (...)`
454+
// when the overall request timeout fires.
455+
assert!(
456+
lower.contains("timeout")
457+
|| lower.contains("timed out")
458+
|| payload.contains("error sending request for url"),
459+
"expected timeout or stalled-request error, got: {err}"
460+
);
461+
}
462+
}

0 commit comments

Comments
 (0)