Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 27 additions & 15 deletions crates/engine/tree/src/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,21 +241,24 @@ 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
} else {
// 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);
}

Expand Down Expand Up @@ -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),
));
Expand Down Expand Up @@ -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))
}
_ => {}
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions crates/node/events/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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());
}
Expand Down
25 changes: 12 additions & 13 deletions crates/node/metrics/src/block_timing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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<{}>]",
Expand All @@ -103,36 +105,34 @@ 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<Arc<Mutex<IndexMap<B256, BlockTimingMetrics>>>> =
std::sync::OnceLock::new();

/// Initialize the global block timing store
fn get_timing_store() -> Arc<Mutex<IndexMap<B256, BlockTimingMetrics>>> {
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;
Expand All @@ -155,4 +155,3 @@ pub fn remove_block_timing(block_hash: &B256) {
let mut map = store.lock().unwrap();
map.remove(block_hash);
}

9 changes: 6 additions & 3 deletions crates/node/metrics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;
Expand All @@ -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,
};
22 changes: 14 additions & 8 deletions crates/optimism/chainspec/src/xlayer_mainnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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
Expand All @@ -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]
Expand Down
22 changes: 14 additions & 8 deletions crates/optimism/chainspec/src/xlayer_testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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);
Expand Down
20 changes: 7 additions & 13 deletions crates/optimism/hardforks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) => {
Expand Down
Loading