diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index 63e74dd83ec..60a6e73e545 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -104,6 +104,18 @@ pub trait Consensus: HeaderValidator { /// Note: validating blocks does not include other validations of the Consensus fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), ConsensusError>; + /// Returns `true` if the given consensus error is transient and may resolve on its own. + /// + /// On fast chains, clock skew between nodes can cause a valid block's timestamp to + /// appear briefly in the future. Caching such blocks as permanently invalid would + /// prevent them from being re-validated once the local clock catches up. + /// + /// Transient errors will not cause the block hash to be cached as permanently invalid, + /// allowing the block to be re-validated later. + fn is_transient_error(&self, _error: &ConsensusError) -> bool { + false + } + /// Validate a block disregarding world state using an optional pre-computed transaction root. /// /// If `transaction_root` is provided, the implementation should use the pre-computed diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index d0bd2141986..7440dbe2180 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -11,7 +11,7 @@ use alloy_primitives::B256; use alloy_rpc_types_engine::{ ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError, }; -use error::{InsertBlockError, InsertBlockFatalError}; +use error::{InsertBlockError, InsertBlockFatalError, InsertBlockValidationError}; use reth_chain_state::{ CanonicalInMemoryState, ComputedTrieData, ExecutedBlock, ExecutionTimingStats, MemoryOverlayStateProvider, NewCanonicalChain, @@ -3019,8 +3019,14 @@ where ); let latest_valid_hash = self.latest_valid_hash_for_invalid_payload(block.parent_hash())?; - // keep track of the invalid header - self.state.invalid_headers.insert(block.block_with_parent()); + // keep track of the invalid header unless the consensus impl considers it transient + let is_transient = match &validation_err { + InsertBlockValidationError::Consensus(err) => self.consensus.is_transient_error(err), + _ => false, + }; + if !is_transient { + self.state.invalid_headers.insert(block.block_with_parent()); + } self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock( Box::new(block), )));