diff --git a/crates/core/src/types/config.rs b/crates/core/src/types/config.rs index bb883603..1396396d 100644 --- a/crates/core/src/types/config.rs +++ b/crates/core/src/types/config.rs @@ -38,6 +38,21 @@ pub struct NetworkConfig { pub network_passphrase: String, /// History archive URL(s). pub archive_urls: Vec, + /// Per-request timeout in seconds for all RPC calls. + /// + /// Any request that does not receive a complete response within this + /// window is cancelled and returns [`crate::types::error::PrismError::NetworkTimeout`]. + /// Defaults to [`DEFAULT_REQUEST_TIMEOUT_SECS`] (30 s) when deserializing + /// configs that do not specify this field. + #[serde(default = "default_request_timeout_secs")] + pub request_timeout_secs: u64, +} + +/// Default per-request timeout: 30 seconds. +pub const DEFAULT_REQUEST_TIMEOUT_SECS: u64 = 30; + +fn default_request_timeout_secs() -> u64 { + DEFAULT_REQUEST_TIMEOUT_SECS } impl NetworkConfig { @@ -50,6 +65,7 @@ impl NetworkConfig { archive_urls: vec![ "https://history.stellar.org/prd/core-testnet/core_testnet_001".to_string(), ], + request_timeout_secs: DEFAULT_REQUEST_TIMEOUT_SECS, } } @@ -62,6 +78,7 @@ impl NetworkConfig { archive_urls: vec![ "https://history.stellar.org/prd/core-live/core_live_001".to_string() ], + request_timeout_secs: DEFAULT_REQUEST_TIMEOUT_SECS, } } @@ -72,6 +89,7 @@ impl NetworkConfig { rpc_url: "https://rpc-futurenet.stellar.org".to_string(), network_passphrase: "Test SDF Future Network ; October 2022".to_string(), archive_urls: vec!["https://history-futurenet.stellar.org".to_string()], + request_timeout_secs: DEFAULT_REQUEST_TIMEOUT_SECS, } } @@ -82,6 +100,7 @@ impl NetworkConfig { rpc_url: rpc_url.to_string(), network_passphrase: passphrase.to_string(), archive_urls: Vec::new(), + request_timeout_secs: DEFAULT_REQUEST_TIMEOUT_SECS, } } } diff --git a/crates/core/src/types/error.rs b/crates/core/src/types/error.rs index cbd3fc3b..acc9e93d 100644 --- a/crates/core/src/types/error.rs +++ b/crates/core/src/types/error.rs @@ -5,6 +5,8 @@ use std::fmt; /// Top-level error type for all Prism operations. #[derive(Debug)] pub enum PrismError { + /// A network request exceeded the configured timeout duration. + NetworkTimeout { method: String, timeout_secs: u64 }, /// Error communicating with the Soroban RPC endpoint. RpcError(String), /// Error fetching or parsing history archive data. @@ -34,6 +36,9 @@ pub enum PrismError { impl fmt::Display for PrismError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::NetworkTimeout { method, timeout_secs } => { + write!(f, "RPC request timed out after {timeout_secs}s (method: {method})") + } Self::RpcError(msg) => write!(f, "RPC error: {msg}"), Self::ArchiveError(msg) => write!(f, "Archive error: {msg}"), Self::XdrError(msg) => write!(f, "XDR error: {msg}"),