diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index 5ca03f770..ccb822bf6 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -106,7 +106,10 @@ impl RethRpcServerConfig for RpcServerArgs { } fn flashbots_config(&self) -> ValidationApiConfig { - ValidationApiConfig { disallow: self.builder_disallow.clone().unwrap_or_default() } + ValidationApiConfig { + disallow: self.builder_disallow.clone().unwrap_or_default(), + validation_window: self.rpc_eth_proof_window, + } } fn state_cache_config(&self) -> EthStateCacheConfig { diff --git a/crates/rpc/rpc/src/validation.rs b/crates/rpc/rpc/src/validation.rs index c621c8b97..cbf240dd7 100644 --- a/crates/rpc/rpc/src/validation.rs +++ b/crates/rpc/rpc/src/validation.rs @@ -51,7 +51,7 @@ where dyn PayloadValidator::Block>, >, ) -> Self { - let ValidationApiConfig { disallow } = config; + let ValidationApiConfig { disallow, validation_window } = config; let inner = Arc::new(ValidationApiInner { provider, @@ -59,6 +59,7 @@ where payload_validator, executor_provider, disallow, + validation_window, cached_state: Default::default(), task_spawner, }); @@ -130,11 +131,13 @@ where let latest_header = self.provider.latest_header()?.ok_or_else(|| ValidationApiError::MissingLatestBlock)?; - if latest_header.hash() != block.parent_hash() { - return Err(ConsensusError::ParentHashMismatch( - GotExpected { got: block.parent_hash(), expected: latest_header.hash() }.into(), - ) - .into()) + let parent_header = self + .provider + .header(&block.parent_hash())? + .ok_or_else(|| ValidationApiError::MissingParentBlock)?; + + if latest_header.number().saturating_sub(parent_header.number()) > self.validation_window { + return Err(ValidationApiError::BlockTooOld) } self.consensus.validate_header_against_parent(block.sealed_header(), &latest_header)?; self.validate_gas_limit(registered_gas_limit, &latest_header, block.sealed_header())?; @@ -466,6 +469,8 @@ pub struct ValidationApiInner { executor_provider: E, /// Set of disallowed addresses disallow: HashSet
, + /// The maximum block distance - parent to latest - allowed for validation + validation_window: u64, /// Cached state reads to avoid redundant disk I/O across multiple validation attempts /// targeting the same state. Stores a tuple of (`block_hash`, `cached_reads`) for the /// latest head block state. Uses async `RwLock` to safely handle concurrent validation @@ -476,10 +481,23 @@ pub struct ValidationApiInner { } /// Configuration for validation API. -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct ValidationApiConfig { /// Disallowed addresses. pub disallow: HashSet
, + /// The maximum block distance - parent to latest - allowed for validation + pub validation_window: u64, +} + +impl ValidationApiConfig { + /// Default validation blocks window of 3 blocks + pub const DEFAULT_VALIDATION_WINDOW: u64 = 3; +} + +impl Default for ValidationApiConfig { + fn default() -> Self { + Self { disallow: Default::default(), validation_window: Self::DEFAULT_VALIDATION_WINDOW } + } } /// Errors thrown by the validation API. @@ -495,6 +513,10 @@ pub enum ValidationApiError { BlockHashMismatch(GotExpected), #[error("missing latest block in database")] MissingLatestBlock, + #[error("parent block not found")] + MissingParentBlock, + #[error("block is too old, outside validation window")] + BlockTooOld, #[error("could not verify proposer payment")] ProposerPayment, #[error("invalid blobs bundle")]