diff --git a/crates/engine/tree/src/tree/metrics.rs b/crates/engine/tree/src/tree/metrics.rs index b056f1848e4..387418d9103 100644 --- a/crates/engine/tree/src/tree/metrics.rs +++ b/crates/engine/tree/src/tree/metrics.rs @@ -90,8 +90,9 @@ impl EngineApiMetrics { pub struct TreeMetrics { /// The highest block number in the canonical chain pub canonical_chain_height: Gauge, - /// The number of reorgs - pub reorgs: Counter, + /// Metrics for reorgs. + #[metric(skip)] + pub reorgs: ReorgMetrics, /// The latest reorg depth pub latest_reorg_depth: Gauge, /// The current safe block height (this is required by optimism) @@ -100,6 +101,27 @@ pub struct TreeMetrics { pub finalized_block_height: Gauge, } +/// Metrics for reorgs. +#[derive(Debug)] +pub struct ReorgMetrics { + /// The number of head block reorgs + pub head: Counter, + /// The number of safe block reorgs + pub safe: Counter, + /// The number of finalized block reorgs + pub finalized: Counter, +} + +impl Default for ReorgMetrics { + fn default() -> Self { + Self { + head: metrics::counter!("blockchain_tree_reorgs", "commitment" => "head"), + safe: metrics::counter!("blockchain_tree_reorgs", "commitment" => "safe"), + finalized: metrics::counter!("blockchain_tree_reorgs", "commitment" => "finalized"), + } + } +} + /// Metrics for the `EngineApi`. #[derive(Metrics)] #[metrics(scope = "consensus.engine.beacon")] diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index f45022c7ede..f74bd448944 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -38,6 +38,7 @@ use reth_revm::database::StateProviderDatabase; use reth_stages_api::ControlFlow; use reth_tasks::spawn_os_thread; use reth_trie_db::ChangesetCache; +use revm::interpreter::debug_unreachable; use state::TreeState; use std::{fmt::Debug, ops, sync::Arc, time::Instant}; @@ -2376,7 +2377,7 @@ where let old_first = old.first().map(|first| first.recovered_block().num_hash()); trace!(target: "engine::tree", ?new_first, ?old_first, "Reorg detected, new and old first blocks"); - self.update_reorg_metrics(old.len()); + self.update_reorg_metrics(old.len(), old_first); self.reinsert_reorged_blocks(new.clone()); self.reinsert_reorged_blocks(old.clone()); } @@ -2398,9 +2399,23 @@ where )); } - /// This updates metrics based on the given reorg length. - fn update_reorg_metrics(&self, old_chain_length: usize) { - self.metrics.tree.reorgs.increment(1); + /// This updates metrics based on the given reorg length and first reorged block number. + fn update_reorg_metrics(&self, old_chain_length: usize, first_reorged_block: Option) { + if let Some(first_reorged_block) = first_reorged_block.map(|block| block.number) { + if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() && + first_reorged_block <= finalized.number + { + self.metrics.tree.reorgs.finalized.increment(1); + } else if let Some(safe) = self.canonical_in_memory_state.get_safe_num_hash() && + first_reorged_block <= safe.number + { + self.metrics.tree.reorgs.safe.increment(1); + } else { + self.metrics.tree.reorgs.head.increment(1); + } + } else { + debug_unreachable!("Reorged chain doesn't have any blocks"); + } self.metrics.tree.latest_reorg_depth.set(old_chain_length as f64); } diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 302f35491fe..ec4efd42646 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -1969,6 +1969,29 @@ mod forkchoice_updated_tests { assert!(result.is_some(), "OpStack should handle canonical head"); } + #[test] + fn test_update_reorg_metrics() { + let chain_spec = MAINNET.clone(); + let test_harness = TestHarness::new(chain_spec); + + let seal_header = |number: u64| { + SealedHeader::seal_slow(alloy_consensus::Header { number, ..Default::default() }) + }; + + // Set finalized=30, safe=50 to test all three commitment levels + test_harness.tree.canonical_in_memory_state.set_finalized(seal_header(30)); + test_harness.tree.canonical_in_memory_state.set_safe(seal_header(50)); + + // Reorg at block 20 (below finalized=30) -> finalized reorg + test_harness.tree.update_reorg_metrics(5, Some(NumHash::new(20, B256::random()))); + + // Reorg at block 40 (below safe=50, above finalized=30) -> safe reorg + test_harness.tree.update_reorg_metrics(3, Some(NumHash::new(40, B256::random()))); + + // Reorg at block 60 (above safe=50) -> head reorg + test_harness.tree.update_reorg_metrics(2, Some(NumHash::new(60, B256::random()))); + } + /// Test that engine termination persists all blocks and signals completion. #[test] fn test_engine_termination_with_everything_persisted() {