From b495038fde970c7a2489468d36ae9a715a9dd019 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 10 Feb 2025 15:35:40 +0100 Subject: [PATCH] feat(engine): invalid block event (#14365) --- crates/engine/primitives/src/event.rs | 7 ++++++- crates/engine/tree/src/tree/mod.rs | 27 +++++++++++++++++++++++---- crates/node/events/src/node.rs | 3 +++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/crates/engine/primitives/src/event.rs b/crates/engine/primitives/src/event.rs index f4471dd8c..99accf693 100644 --- a/crates/engine/primitives/src/event.rs +++ b/crates/engine/primitives/src/event.rs @@ -10,7 +10,7 @@ use core::{ time::Duration, }; use reth_chain_state::ExecutedBlockWithTrieUpdates; -use reth_primitives::EthPrimitives; +use reth_primitives::{EthPrimitives, SealedBlock}; use reth_primitives_traits::{NodePrimitives, SealedHeader}; /// Events emitted by the consensus engine. @@ -24,6 +24,8 @@ pub enum BeaconConsensusEngineEvent { CanonicalBlockAdded(ExecutedBlockWithTrieUpdates, Duration), /// A canonical chain was committed, and the elapsed time committing the data CanonicalChainCommitted(Box>, Duration), + /// The consensus engine processed an invalid block. + InvalidBlock(Box>), /// The consensus engine is involved in live sync, and has specific progress LiveSyncProgress(ConsensusEngineLiveSyncProgress), } @@ -61,6 +63,9 @@ where Self::CanonicalChainCommitted(block, duration) => { write!(f, "CanonicalChainCommitted({:?}, {duration:?})", block.num_hash()) } + Self::InvalidBlock(block) => { + write!(f, "InvalidBlock({:?})", block.num_hash()) + } Self::LiveSyncProgress(progress) => { write!(f, "LiveSyncProgress({progress:?})") } diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 8da382cd0..52db04927 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -912,7 +912,7 @@ where // now check the block itself if let Some(status) = - self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, block_hash)? + self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, &block)? { return Ok(TreeOutcome::new(status)) } @@ -1887,7 +1887,7 @@ where fn check_invalid_ancestor_with_head( &mut self, check: B256, - head: B256, + head: &SealedBlock, ) -> ProviderResult> { // check if the check hash was previously marked as invalid let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) }; @@ -1896,7 +1896,8 @@ where let status = self.prepare_invalid_response(header.parent)?; // insert the head block into the invalid header cache - self.state.invalid_headers.insert_with_invalid_ancestor(head, header); + self.state.invalid_headers.insert_with_invalid_ancestor(head.hash(), header); + self.emit_event(BeaconConsensusEngineEvent::InvalidBlock(Box::new(head.clone()))); Ok(Some(status)) } @@ -2276,7 +2277,7 @@ where let block_num_hash = block.num_hash(); let lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_num_hash.hash); if self - .check_invalid_ancestor_with_head(lowest_buffered_ancestor, block_num_hash.hash)? + .check_invalid_ancestor_with_head(lowest_buffered_ancestor, block.sealed_block())? .is_some() { return Ok(None) @@ -2841,6 +2842,9 @@ where // keep track of the invalid header self.state.invalid_headers.insert(block.block_with_parent()); + self.emit_event(EngineApiEvent::BeaconConsensus(BeaconConsensusEngineEvent::InvalidBlock( + Box::new(block), + ))); Ok(PayloadStatus::new( PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() }, latest_valid_hash, @@ -3554,6 +3558,18 @@ mod tests { } } + async fn check_invalid_block(&mut self, expected_hash: B256) { + let event = self.from_tree_rx.recv().await.unwrap(); + match event { + EngineApiEvent::BeaconConsensus(BeaconConsensusEngineEvent::InvalidBlock( + block, + )) => { + assert_eq!(block.hash(), expected_hash); + } + _ => panic!("Unexpected event: {:#?}", event), + } + } + fn persist_blocks(&self, blocks: Vec>) { let mut block_data: Vec<(B256, Block)> = Vec::with_capacity(blocks.len()); let mut headers_data: Vec<(B256, Header)> = Vec::with_capacity(blocks.len()); @@ -4765,6 +4781,9 @@ mod tests { chain_a[..chain_a.len() - invalid_index - 1].iter().cloned(), ) .await; + for block in &chain_a[chain_a.len() - invalid_index - 1..] { + test_harness.check_invalid_block(block.hash()).await; + } // send FCU to make the tip of chain A, expect invalid let chain_a_tip_hash = chain_a.last().unwrap().hash(); diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index 9c3ba2fc4..d999f11fd 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -277,6 +277,9 @@ impl NodeState { let block = executed.sealed_block(); info!(number=block.number(), hash=?block.hash(), ?elapsed, "Block added to fork chain"); } + BeaconConsensusEngineEvent::InvalidBlock(block) => { + warn!(number=block.number(), hash=?block.hash(), "Encountered invalid block"); + } } }