diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index ba90a1fbd8..a6ba9e2998 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -2173,18 +2173,26 @@ where /// Finds any invalid ancestor for the given payload. /// - /// This function walks up the chain of buffered ancestors from the payload's block - /// hash and checks if any ancestor is marked as invalid in the tree state. + /// This function first checks if the block itself is in the invalid headers cache (to + /// avoid re-executing a known-invalid block). Then it walks up the chain of buffered + /// ancestors and checks if any ancestor is marked as invalid. /// /// The check works by: - /// 1. Finding the lowest buffered ancestor for the given block hash - /// 2. If the ancestor is the same as the block hash itself, using the parent hash instead - /// 3. Checking if this ancestor is in the `invalid_headers` map + /// 1. Checking if the block hash itself is in the `invalid_headers` map + /// 2. Finding the lowest buffered ancestor for the given block hash + /// 3. If the ancestor is the same as the block hash itself, using the parent hash instead + /// 4. Checking if this ancestor is in the `invalid_headers` map /// /// Returns the invalid ancestor block info if found, or None if no invalid ancestor exists. fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option { let parent_hash = payload.parent_hash(); let block_hash = payload.block_hash(); + + // Check if the block itself is already known to be invalid, avoiding re-execution + if let Some(entry) = self.state.invalid_headers.get(&block_hash) { + return Some(entry); + } + let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash); if lowest_buffered_ancestor == block_hash { lowest_buffered_ancestor = parent_hash; diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 0c06e10ce6..d7b16332ab 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -1582,6 +1582,39 @@ mod check_invalid_ancestors_tests { } } + /// Test that `find_invalid_ancestor` detects the block itself in the invalid cache + #[test] + fn test_find_invalid_ancestor_detects_block_itself() { + reth_tracing::init_test_tracing(); + + let mut test_harness = TestHarness::new(HOLESKY.clone()); + + // Read block 1 + let s1 = include_str!("../../test-data/holesky/1.rlp"); + let data1 = Bytes::from_str(s1).unwrap(); + let block1 = Block::decode(&mut data1.as_ref()).unwrap(); + let sealed1 = block1.seal_slow(); + let hash1 = sealed1.hash(); + let parent1 = sealed1.parent_hash(); + + // Mark block 1 itself as invalid (simulates a block that failed execution) + test_harness + .tree + .state + .invalid_headers + .insert(BlockWithParent { block: sealed1.num_hash(), parent: parent1 }); + + // Create payload for block 1 (same block, sent again by CL) + let payload1 = ExecutionData { + payload: ExecutionPayloadV1::from_block_unchecked(hash1, &sealed1.into_block()).into(), + sidecar: ExecutionPayloadSidecar::none(), + }; + + // find_invalid_ancestor should detect the block itself without re-execution + let result = test_harness.tree.find_invalid_ancestor(&payload1); + assert!(result.is_some(), "Should detect block itself in invalid headers cache"); + } + /// Helper function to create a malformed payload that descends from a given parent fn create_malformed_payload_descending_from(parent_hash: B256) -> ExecutionData { // Create a block with invalid hash (mismatch between computed and provided hash)