Skip to content
This repository was archived by the owner on Aug 3, 2026. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 97 additions & 5 deletions core/src/provider/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use alloy_transport_http::Http;
use raiko_lib::clear_line;
use reqwest_alloy::Client;
use reth_primitives::revm_primitives::{AccountInfo, Bytecode};
use std::collections::HashMap;
use std::{collections::HashMap, env, time::Duration};
use tracing::debug;

use crate::{
Expand All @@ -15,6 +15,36 @@ use crate::{
MerkleProof,
};

/// Env: total per-request timeout for L1/L2 JSON-RPC (connect + send + response body).
/// Default 300s so large `eth_getProof` / batch calls can finish on slow nodes.
const ENV_RPC_HTTP_TIMEOUT_SECS: &str = "RAIKO_RPC_HTTP_TIMEOUT_SECS";
/// Env: TCP connect timeout only. Default 30s.
const ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS: &str = "RAIKO_RPC_HTTP_CONNECT_TIMEOUT_SECS";

fn build_rpc_reqwest_client() -> RaikoResult<Client> {
let timeout_secs: u64 = env::var(ENV_RPC_HTTP_TIMEOUT_SECS)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(300);
let connect_secs: u64 = env::var(ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(30);

Client::builder()
.timeout(Duration::from_secs(timeout_secs))
.connect_timeout(Duration::from_secs(connect_secs))
.build()
Comment on lines +29 to +37

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior adds a separate TCP connect_timeout (driven by RAIKO_RPC_HTTP_CONNECT_TIMEOUT_SECS), but the added test only exercises the overall request timeout. Consider adding a focused test that verifies the connect timeout path (e.g., dial an unroutable IP/port) so regressions in connect-timeout handling are caught.

Copilot uses AI. Check for mistakes.
.map_err(|e| RaikoError::RPC(format!("Failed to build RPC HTTP client: {e}")))
}

fn rpc_http_transport(url: reqwest::Url) -> RaikoResult<(Http<Client>, bool)> {
let client = build_rpc_reqwest_client()?;
let http = Http::with_client(client, url);
let is_local = http.guess_local();
Ok((http, is_local))
}

#[derive(Clone)]
pub struct RpcBlockDataProvider {
pub provider: ReqwestProvider,
Expand All @@ -31,9 +61,11 @@ impl RpcBlockDataProvider {
"provider rpc url: {:?} for block_number {}",
url, block_number
);
let (http, is_local) = rpc_http_transport(url)?;
let rpc_client = RpcClient::new(http.clone(), is_local);
Ok(Self {
provider: ProviderBuilder::new().on_provider(RootProvider::new_http(url.clone())),
client: ClientBuilder::default().http(url),
provider: ProviderBuilder::new().on_provider(RootProvider::new(rpc_client)),
client: ClientBuilder::default().transport(http, is_local),
block_numbers: vec![block_number, block_number + 1],
})
}
Expand All @@ -49,9 +81,11 @@ impl RpcBlockDataProvider {
"Batch provider rpc: {:?} for block_number {}",
url, block_numbers[0]
);
let (http, is_local) = rpc_http_transport(url)?;
let rpc_client = RpcClient::new(http.clone(), is_local);
Ok(Self {
provider: ProviderBuilder::new().on_provider(RootProvider::new_http(url.clone())),
client: ClientBuilder::default().http(url),
provider: ProviderBuilder::new().on_provider(RootProvider::new(rpc_client)),
client: ClientBuilder::default().transport(http, is_local),
block_numbers,
})
}
Expand Down Expand Up @@ -368,3 +402,61 @@ impl BlockDataProvider for RpcBlockDataProvider {
Ok(storage_proofs)
}
}

#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use tokio::io::AsyncReadExt;
use tokio::net::TcpListener;

fn set_short_rpc_timeouts_for_test() {
std::env::set_var(ENV_RPC_HTTP_TIMEOUT_SECS, "1");
std::env::set_var(ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS, "1");
}

fn clear_rpc_timeout_env() {
std::env::remove_var(ENV_RPC_HTTP_TIMEOUT_SECS);
std::env::remove_var(ENV_RPC_HTTP_CONNECT_TIMEOUT_SECS);
}
Comment on lines +413 to +421

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test mutates global process env vars but clear_rpc_timeout_env() unconditionally removes them (and won’t run if the test panics before line 446). Capture previous values and restore them via a guard (Drop) so existing RAIKO_RPC_HTTP_* settings aren’t lost and cleanup is guaranteed.

Copilot uses AI. Check for mistakes.

/// Local TCP server: read the JSON-RPC POST then stall so the client hits `reqwest` timeout.
async fn spawn_stall_after_accept_json_rpc() -> String {
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind test listener");
let addr = listener.local_addr().expect("listener addr");
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
let mut buf = [0u8; 8192];
let _ = stream.read(&mut buf).await;
tokio::time::sleep(Duration::from_secs(600)).await;
}
});
format!("http://{addr}")
}

#[tokio::test]
#[serial]
async fn get_blocks_returns_rpc_error_when_http_times_out() {
set_short_rpc_timeouts_for_test();
let url = spawn_stall_after_accept_json_rpc().await;
let provider = RpcBlockDataProvider::new(&url, 1)
.await
.expect("provider new with short timeout");
let result = provider.get_blocks(&[(1, false)]).await;
clear_rpc_timeout_env();

let err = result.expect_err("expected RPC failure when server does not respond in time");
let RaikoError::RPC(payload) = &err else {
panic!("expected RaikoError::RPC, got {err:?}");
};
let lower = payload.to_lowercase();
// Reqwest may report `operation timed out` or a generic `error sending request for url (...)`
// when the overall request timeout fires.
assert!(
lower.contains("timeout")
|| lower.contains("timed out")
|| payload.contains("error sending request for url"),

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the assertion, you lowercase payload into lower but the third branch still checks payload.contains("error sending request for url") case-sensitively. This can make the test flaky across reqwest error message variants/capitalization; use the lowercased string for that check too (or match on error kind if available).

Suggested change
|| payload.contains("error sending request for url"),
|| lower.contains("error sending request for url"),

Copilot uses AI. Check for mistakes.
"expected timeout or stalled-request error, got: {err}"
);
}
}
Loading