perf(engine): check block itself as invalid ancestor to eliminate duplicate exec (#22794)

Signed-off-by: Delweng <delweng@gmail.com>
Co-authored-by: YK <chiayongkang@hotmail.com>
This commit is contained in:
Delweng
2026-03-06 15:47:33 +08:00
committed by GitHub
parent 7402820d62
commit b3cfe87795
2 changed files with 46 additions and 5 deletions

View File

@@ -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<BlockWithParent> {
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;

View File

@@ -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)