diff --git a/Cargo.lock b/Cargo.lock index 4a97d9142a1..f4d7d04783c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7640,7 +7640,7 @@ dependencies = [ [[package]] name = "reth-apollo" -version = "1.9.2" +version = "1.9.3" dependencies = [ "apollo-sdk", "async-once-cell", @@ -9788,6 +9788,7 @@ dependencies = [ "alloy-primitives", "once_cell", "reth-ethereum-forks", + "tracing", ] [[package]] @@ -14588,7 +14589,7 @@ dependencies = [ [[package]] name = "xlayer-db" -version = "1.9.2" +version = "1.9.3" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -14603,7 +14604,7 @@ dependencies = [ [[package]] name = "xlayer-exex" -version = "1.9.2" +version = "1.9.3" dependencies = [ "alloy-consensus", "alloy-evm", @@ -14621,7 +14622,7 @@ dependencies = [ [[package]] name = "xlayer-rpc" -version = "1.9.2" +version = "1.9.3" dependencies = [ "alloy-eips", "alloy-network-primitives", diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 5db698107c5..fba2eab20b5 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -241,8 +241,10 @@ fn update_insert_timing_metrics( validate_and_execute: std::time::Duration, insert_to_tree: std::time::Duration, ) { - use reth_node_metrics::block_timing::{get_block_timing, store_block_timing, BlockTimingMetrics}; - + use reth_node_metrics::block_timing::{ + get_block_timing, store_block_timing, BlockTimingMetrics, + }; + let mut timing_metrics = if let Some(existing) = get_block_timing(&block_hash) { // Block was built locally, update insert timing existing @@ -250,12 +252,13 @@ fn update_insert_timing_metrics( // Block was received from network, create timing metrics with insert timing only BlockTimingMetrics::default() }; - + timing_metrics.insert.validate_and_execute = validate_and_execute; timing_metrics.insert.insert_to_tree = insert_to_tree; // Total should be the sum of validate_and_execute + insert_to_tree - timing_metrics.insert.total = timing_metrics.insert.validate_and_execute + timing_metrics.insert.insert_to_tree; - + timing_metrics.insert.total = + timing_metrics.insert.validate_and_execute + timing_metrics.insert.insert_to_tree; + store_block_timing(block_hash, timing_metrics); } @@ -1423,13 +1426,18 @@ where let insert_tree_elapsed = insert_tree_start.elapsed(); self.metrics.engine.inserted_already_executed_blocks.increment(1); let elapsed = now.elapsed(); - + // X Layer: Update timing metrics for InsertExecutedBlock path - // Note: validate_exec time is 0 because block was already executed during build + // Note: validate_exec time is 0 because block was already executed during + // build use std::time::Duration; let block_hash = block.recovered_block().hash(); - update_insert_timing_metrics(block_hash, Duration::from_nanos(0), insert_tree_elapsed); - + update_insert_timing_metrics( + block_hash, + Duration::from_nanos(0), + insert_tree_elapsed, + ); + self.emit_event(EngineApiEvent::BeaconConsensus( ConsensusEngineEvent::CanonicalBlockAdded(block, elapsed), )); @@ -2491,17 +2499,21 @@ where // We now assume that we already have this block in the tree. However, we need to // run the conversion to ensure that the block hash is valid. convert_to_block(self, input)?; - - // X Layer: Even if block is already seen, update timing metrics if it was built locally - // Block was built locally but already exists in tree + + // X Layer: Even if block is already seen, update timing metrics if it was built + // locally Block was built locally but already exists in tree // Set insert timing to 0 for now, will be updated in event handler if elapsed > 0 use reth_node_metrics::block_timing::get_block_timing; use std::time::Duration; let block_hash = block_num_hash.hash; if get_block_timing(&block_hash).is_some() { - update_insert_timing_metrics(block_hash, Duration::from_nanos(0), Duration::from_nanos(0)); + update_insert_timing_metrics( + block_hash, + Duration::from_nanos(0), + Duration::from_nanos(0), + ); } - + return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid)) } _ => {} @@ -2567,7 +2579,7 @@ where // emit insert event let elapsed = start.elapsed(); - + // X Layer: Update timing metrics with insert timing let block_hash = executed.recovered_block().hash(); update_insert_timing_metrics(block_hash, validate_exec_elapsed, insert_tree_elapsed); diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index e3af753559f..008912e6852 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -253,15 +253,15 @@ impl NodeState { if full.is_nan() { full = 0.0; } - + // X Layer: Get timing metrics for this block use reth_node_metrics::block_timing::{get_block_timing, remove_block_timing}; let block_hash = block.hash(); - + let timing_str = get_block_timing(&block_hash) .map(|metrics| metrics.format_for_log()) .unwrap_or_default(); - + info!( number=block.number(), hash=?block.hash(), @@ -278,7 +278,7 @@ impl NodeState { timing=%timing_str, "Block added to canonical chain" ); - + // Clean up timing metrics after logging remove_block_timing(&block.hash()); } diff --git a/crates/node/metrics/src/block_timing.rs b/crates/node/metrics/src/block_timing.rs index cfeed8215b3..4098743e10b 100644 --- a/crates/node/metrics/src/block_timing.rs +++ b/crates/node/metrics/src/block_timing.rs @@ -34,13 +34,14 @@ pub struct InsertTiming { } /// Timing metrics for transaction execution -/// +/// /// Note: Individual transaction execution times (sequencer_txs, mempool_txs) are stored /// in `BuildTiming` to avoid duplication. This struct only stores the total time. #[derive(Debug, Clone, Default)] pub struct DeliverTxsTiming { /// Total transaction execution time - /// Note: Individual times are stored in BuildTiming (execute_sequencer_transactions, execute_mempool_transactions) + /// Note: Individual times are stored in BuildTiming (execute_sequencer_transactions, + /// execute_mempool_transactions) pub total: Duration, } @@ -72,11 +73,12 @@ impl BlockTimingMetrics { // Check if block was built locally (has build timing) or received from network let is_locally_built = self.build.total.as_nanos() > 0; - + if is_locally_built { // Block was built locally, show full timing including Build and DeliverTxs // Note: DeliverTxs only shows total to avoid duplication with Build's seqTxs/mempoolTxs - let deliver_txs_total_time = self.build.execute_sequencer_transactions + self.build.execute_mempool_transactions; + let deliver_txs_total_time = + self.build.execute_sequencer_transactions + self.build.execute_mempool_transactions; format!( "Produce[Build[applyPreExec<{}>, seqTxs<{}>, mempoolTxs<{}>, finish<{}>, total<{}>], Insert[validateExec<{}>, insertTree<{}>, total<{}>]], DeliverTxs[total<{}>]", @@ -103,7 +105,7 @@ impl BlockTimingMetrics { } /// Global storage for block timing metrics -/// +/// /// Uses IndexMap to maintain insertion order, allowing us to remove the oldest entries /// when the cache exceeds the limit. static BLOCK_TIMING_STORE: std::sync::OnceLock>>> = @@ -111,28 +113,26 @@ static BLOCK_TIMING_STORE: std::sync::OnceLock Arc>> { - BLOCK_TIMING_STORE - .get_or_init(|| Arc::new(Mutex::new(IndexMap::new()))) - .clone() + BLOCK_TIMING_STORE.get_or_init(|| Arc::new(Mutex::new(IndexMap::new()))).clone() } /// Store timing metrics for a block -/// +/// /// If the block already exists, it will be updated and moved to the end (most recent). /// When the cache exceeds 1000 entries, the oldest entries are removed. pub fn store_block_timing(block_hash: B256, metrics: BlockTimingMetrics) { let store = get_timing_store(); let mut map = store.lock().unwrap(); - + // If the block already exists, remove it first so it can be re-inserted at the end // This ensures that updated blocks are treated as the most recent if map.contains_key(&block_hash) { map.shift_remove(&block_hash); } - + // Insert at the end (most recent position) map.insert(block_hash, metrics); - + // Clean up old entries to prevent memory leak (keep last 1000 blocks) // IndexMap maintains insertion order, so we can safely remove from the front const MAX_ENTRIES: usize = 1000; @@ -155,4 +155,3 @@ pub fn remove_block_timing(block_hash: &B256) { let mut map = store.lock().unwrap(); map.remove(block_hash); } - diff --git a/crates/node/metrics/src/lib.rs b/crates/node/metrics/src/lib.rs index b375dff8df8..8de5f07e94b 100644 --- a/crates/node/metrics/src/lib.rs +++ b/crates/node/metrics/src/lib.rs @@ -7,6 +7,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +/// Block timing metrics for tracking block production and execution times (X Layer) +pub mod block_timing; pub mod chain; /// The metrics hooks for prometheus. pub mod hooks; @@ -16,8 +18,6 @@ pub mod server; /// Transaction tracing for monitoring transaction lifecycle (X Layer) pub mod transaction_trace_xlayer; pub mod version; -/// Block timing metrics for tracking block production and execution times (X Layer) -pub mod block_timing; pub use metrics_exporter_prometheus::*; pub use metrics_process::*; @@ -27,4 +27,7 @@ pub use transaction_trace_xlayer::{ flush_global_tracer, get_global_tracer, init_global_tracer, TransactionProcessId, }; // Re-export block timing module items for convenience -pub use block_timing::{BlockTimingMetrics, BuildTiming, InsertTiming, DeliverTxsTiming, store_block_timing, get_block_timing, remove_block_timing}; +pub use block_timing::{ + get_block_timing, remove_block_timing, store_block_timing, BlockTimingMetrics, BuildTiming, + DeliverTxsTiming, InsertTiming, +}; diff --git a/crates/optimism/chainspec/src/xlayer_mainnet.rs b/crates/optimism/chainspec/src/xlayer_mainnet.rs index d0e0a5bfeec..8f7b11a5f5e 100644 --- a/crates/optimism/chainspec/src/xlayer_mainnet.rs +++ b/crates/optimism/chainspec/src/xlayer_mainnet.rs @@ -182,9 +182,12 @@ mod tests { assert!(spec.fork(OpHardfork::Granite).active_at_timestamp(ts)); assert!(spec.fork(OpHardfork::Holocene).active_at_timestamp(ts)); assert!(spec.fork(OpHardfork::Isthmus).active_at_timestamp(ts)); - // Jovian is configured but not active at genesis timestamp, it activates at a future timestamp - // Verify Jovian is configured (not ForkCondition::Never) - assert!(!matches!(spec.fork(OpHardfork::Jovian), reth_ethereum_forks::ForkCondition::Never)); + // Jovian is configured but not active at genesis timestamp, it activates at a future + // timestamp Verify Jovian is configured (not ForkCondition::Never) + assert!(!matches!( + spec.fork(OpHardfork::Jovian), + reth_ethereum_forks::ForkCondition::Never + )); // Verify it's not active at genesis timestamp assert!(!spec.fork(OpHardfork::Jovian).active_at_timestamp(ts)); } @@ -198,9 +201,8 @@ mod tests { let hardforks = &*XLAYER_MAINNET_HARDFORKS; // Verify Jovian is configured with XLAYER_MAINNET_JOVIAN_TIMESTAMP - let jovian_fork = hardforks - .get(OpHardfork::Jovian) - .expect("Jovian fork should be configured"); + let jovian_fork = + hardforks.get(OpHardfork::Jovian).expect("Jovian fork should be configured"); assert!(matches!( jovian_fork, reth_ethereum_forks::ForkCondition::Timestamp(ts) if ts == XLAYER_MAINNET_JOVIAN_TIMESTAMP @@ -210,13 +212,17 @@ mod tests { assert_eq!(XLAYER_MAINNET_JOVIAN_TIMESTAMP, OP_MAINNET_JOVIAN_TIMESTAMP); // Test activation before Jovian timestamp - assert!(!spec.fork(OpHardfork::Jovian).active_at_timestamp(XLAYER_MAINNET_JOVIAN_TIMESTAMP - 1)); + assert!(!spec + .fork(OpHardfork::Jovian) + .active_at_timestamp(XLAYER_MAINNET_JOVIAN_TIMESTAMP - 1)); // Test activation at Jovian timestamp assert!(spec.fork(OpHardfork::Jovian).active_at_timestamp(XLAYER_MAINNET_JOVIAN_TIMESTAMP)); // Test activation after Jovian timestamp - assert!(spec.fork(OpHardfork::Jovian).active_at_timestamp(XLAYER_MAINNET_JOVIAN_TIMESTAMP + 1)); + assert!(spec + .fork(OpHardfork::Jovian) + .active_at_timestamp(XLAYER_MAINNET_JOVIAN_TIMESTAMP + 1)); } #[test] diff --git a/crates/optimism/chainspec/src/xlayer_testnet.rs b/crates/optimism/chainspec/src/xlayer_testnet.rs index 8b575790c78..ad6db95f39f 100644 --- a/crates/optimism/chainspec/src/xlayer_testnet.rs +++ b/crates/optimism/chainspec/src/xlayer_testnet.rs @@ -168,9 +168,12 @@ mod tests { assert!(spec.fork(OpHardfork::Granite).active_at_timestamp(ts)); assert!(spec.fork(OpHardfork::Holocene).active_at_timestamp(ts)); assert!(spec.fork(OpHardfork::Isthmus).active_at_timestamp(ts)); - // Jovian is configured but not active at genesis timestamp, it activates at a future timestamp - // Verify Jovian is configured (not ForkCondition::Never) - assert!(!matches!(spec.fork(OpHardfork::Jovian), reth_ethereum_forks::ForkCondition::Never)); + // Jovian is configured but not active at genesis timestamp, it activates at a future + // timestamp Verify Jovian is configured (not ForkCondition::Never) + assert!(!matches!( + spec.fork(OpHardfork::Jovian), + reth_ethereum_forks::ForkCondition::Never + )); // Verify it's not active at genesis timestamp assert!(!spec.fork(OpHardfork::Jovian).active_at_timestamp(ts)); } @@ -183,22 +186,25 @@ mod tests { let hardforks = &*XLAYER_TESTNET_HARDFORKS; // Verify Jovian is configured with XLAYER_TESTNET_JOVIAN_TIMESTAMP - let jovian_fork = hardforks - .get(OpHardfork::Jovian) - .expect("Jovian fork should be configured"); + let jovian_fork = + hardforks.get(OpHardfork::Jovian).expect("Jovian fork should be configured"); assert!(matches!( jovian_fork, reth_ethereum_forks::ForkCondition::Timestamp(ts) if ts == XLAYER_TESTNET_JOVIAN_TIMESTAMP )); // Test activation before Jovian timestamp - assert!(!spec.fork(OpHardfork::Jovian).active_at_timestamp(XLAYER_TESTNET_JOVIAN_TIMESTAMP - 1)); + assert!(!spec + .fork(OpHardfork::Jovian) + .active_at_timestamp(XLAYER_TESTNET_JOVIAN_TIMESTAMP - 1)); // Test activation at Jovian timestamp assert!(spec.fork(OpHardfork::Jovian).active_at_timestamp(XLAYER_TESTNET_JOVIAN_TIMESTAMP)); // Test activation after Jovian timestamp - assert!(spec.fork(OpHardfork::Jovian).active_at_timestamp(XLAYER_TESTNET_JOVIAN_TIMESTAMP + 1)); + assert!(spec + .fork(OpHardfork::Jovian) + .active_at_timestamp(XLAYER_TESTNET_JOVIAN_TIMESTAMP + 1)); // Verify timestamp matches expected value (2025-11-28 11:00:00 UTC) assert_eq!(XLAYER_TESTNET_JOVIAN_TIMESTAMP, 1764327600); diff --git a/crates/optimism/hardforks/src/lib.rs b/crates/optimism/hardforks/src/lib.rs index aa19739e0b2..6c3404bb975 100644 --- a/crates/optimism/hardforks/src/lib.rs +++ b/crates/optimism/hardforks/src/lib.rs @@ -336,18 +336,13 @@ mod tests { let xlayer_mainnet = &*XLAYER_MAINNET_HARDFORKS; let op_mainnet = &*OP_MAINNET_HARDFORKS; - let xlayer_jovian = xlayer_mainnet - .get(OpHardfork::Jovian) - .expect("XLayer mainnet should have Jovian fork"); - let op_jovian = op_mainnet - .get(OpHardfork::Jovian) - .expect("OP mainnet should have Jovian fork"); + let xlayer_jovian = + xlayer_mainnet.get(OpHardfork::Jovian).expect("XLayer mainnet should have Jovian fork"); + let op_jovian = + op_mainnet.get(OpHardfork::Jovian).expect("OP mainnet should have Jovian fork"); match (xlayer_jovian, op_jovian) { - ( - ForkCondition::Timestamp(xlayer_ts), - ForkCondition::Timestamp(op_ts), - ) => { + (ForkCondition::Timestamp(xlayer_ts), ForkCondition::Timestamp(op_ts)) => { assert_eq!( xlayer_ts, op_ts, "XLayer mainnet Jovian timestamp should match OP mainnet" @@ -369,9 +364,8 @@ mod tests { fn test_xlayer_testnet_jovian_timestamp_condition() { let xlayer_testnet = &*XLAYER_TESTNET_HARDFORKS; - let jovian_fork = xlayer_testnet - .get(OpHardfork::Jovian) - .expect("XLayer testnet should have Jovian fork"); + let jovian_fork = + xlayer_testnet.get(OpHardfork::Jovian).expect("XLayer testnet should have Jovian fork"); match jovian_fork { ForkCondition::Timestamp(ts) => { diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 0b695463006..11d03a8fea8 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -20,7 +20,10 @@ use reth_evm::{ ConfigureEvm, Database, }; use reth_execution_types::ExecutionOutcome; -use reth_node_metrics::transaction_trace_xlayer::{get_global_tracer, TransactionProcessId}; +use reth_node_metrics::{ + block_timing::{store_block_timing, BlockTimingMetrics, BuildTiming, DeliverTxsTiming}, + transaction_trace_xlayer::{get_global_tracer, TransactionProcessId}, +}; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{transaction::OpTransaction, ADDRESS_L2_TO_L1_MESSAGE_PASSER}; use reth_optimism_txpool::{ @@ -41,10 +44,8 @@ use reth_revm::{ use reth_storage_api::{errors::ProviderError, StateProvider, StateProviderFactory}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::context::{Block, BlockEnv}; -use std::{marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, sync::Arc, time::Instant}; use tracing::{debug, trace, warn}; -use reth_node_metrics::block_timing::{BlockTimingMetrics, BuildTiming, DeliverTxsTiming, store_block_timing}; -use std::time::Instant; /// Optimism's payload builder #[derive(Debug)] @@ -413,7 +414,8 @@ impl OpBuilder<'_, Txs> { timing_metrics.build.finish = finish_start.elapsed(); timing_metrics.build.total = build_start.elapsed(); // Calculate DeliverTxs total from BuildTiming to avoid duplication - timing_metrics.deliver_txs.total = timing_metrics.build.execute_sequencer_transactions + timing_metrics.build.execute_mempool_transactions; + timing_metrics.deliver_txs.total = timing_metrics.build.execute_sequencer_transactions + + timing_metrics.build.execute_mempool_transactions; let sealed_block = Arc::new(block.sealed_block().clone()); debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header(), "sealed built block");