From d89a2dab8aec367fc7318e6d56a5b763404e219f Mon Sep 17 00:00:00 2001 From: "ruiwei.guo" <335209779@qq.com> Date: Fri, 16 Jan 2026 10:13:04 +0800 Subject: [PATCH 1/4] support full trace --- Cargo.lock | 8 + Cargo.toml | 1 + crates/monitor/Cargo.toml | 17 ++ crates/monitor/src/lib.rs | 460 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 486 insertions(+) create mode 100644 crates/monitor/Cargo.toml create mode 100644 crates/monitor/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 46b6b6ca97d..bcf7b2142af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8811,6 +8811,14 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "reth-monitor" +version = "1.9.3" +dependencies = [ + "alloy-primitives", + "tracing", +] + [[package]] name = "reth-net-banlist" version = "1.9.3" diff --git a/Cargo.toml b/Cargo.toml index 77be8e4a178..e4c4916c261 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ "crates/exex/test-utils/", "crates/exex/types/", "crates/metrics/", + "crates/monitor/", "crates/net/banlist/", "crates/net/discv4/", "crates/net/discv5/", diff --git a/crates/monitor/Cargo.toml b/crates/monitor/Cargo.toml new file mode 100644 index 00000000000..a9a927eb528 --- /dev/null +++ b/crates/monitor/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "reth-monitor" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Transaction and block monitoring for X Layer" + +[lints] +workspace = true + +[dependencies] +alloy-primitives.workspace = true +tracing.workspace = true + diff --git a/crates/monitor/src/lib.rs b/crates/monitor/src/lib.rs new file mode 100644 index 00000000000..4efcf54faff --- /dev/null +++ b/crates/monitor/src/lib.rs @@ -0,0 +1,460 @@ +//! Transaction tracing module for monitoring transaction lifecycle +//! +//! This module provides functionality to trace and log transaction lifecycle events +//! for monitoring and debugging purposes. + +use alloy_primitives::B256; +use std::{ + fs::{self, File, OpenOptions}, + io::Write, + path::PathBuf, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, Mutex, OnceLock, + }, + time::Instant, +}; + +/// Number of log entries to write before forcing a flush +const FLUSH_INTERVAL_WRITES: u64 = 100; + +/// Time interval between flushes (in seconds) +const FLUSH_INTERVAL_SECONDS: u64 = 1; + +/// Fixed chain name +const CHAIN_NAME: &str = "X Layer"; + +/// Fixed business name +const BUSINESS_NAME: &str = "X Layer"; + +/// Fixed chain ID +const CHAIN_ID: &str = "196"; + +/// RPC service name +const RPC_SERVICE_NAME: &str = "okx-defi-xlayer-rpcpay-pro"; + +/// Sequencer service name +const SEQ_SERVICE_NAME: &str = "okx-defi-xlayer-egseqz-pro"; + +/// Node type for identifying sequencer vs RPC node +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NodeType { + /// Sequencer node (builds blocks) + Sequencer, + /// RPC node (forwards transactions to sequencer) + Rpc, + /// Unknown node type (default) + Unknown, +} + +impl NodeType { + /// Returns the string representation of the node type + pub const fn as_str(&self) -> &'static str { + match self { + Self::Sequencer => "sequencer", + Self::Rpc => "rpc", + Self::Unknown => "unknown", + } + } +} + +/// Transaction process ID for tracking different stages in the transaction lifecycle +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TransactionProcessId { + /// RPC node: Transaction received and ready to forward + RpcReceiveTxEnd = 15010, + + /// Sequencer node: Transaction received and added to pool + SeqReceiveTxEnd = 15030, + + /// Sequencer node: Block building started + SeqBlockBuildStart = 15032, + + /// Sequencer node: Transaction execution completed + SeqTxExecutionEnd = 15034, + + /// Sequencer node: Block building completed + SeqBlockBuildEnd = 15036, + + /// Sequencer node: Block sending started + SeqBlockSendStart = 15042, + + /// RPC node: Block received from sequencer + RpcBlockReceiveEnd = 15060, + + /// RPC node: Block insertion completed + RpcBlockInsertEnd = 15062, +} + +impl TransactionProcessId { + /// Returns the string representation of the process ID + pub const fn as_str(&self) -> &'static str { + match self { + Self::RpcReceiveTxEnd => "xlayer_rpc_receive_tx", + Self::SeqReceiveTxEnd => "xlayer_seq_receive_tx", + Self::SeqBlockBuildStart => "xlayer_seq_begin_block", + Self::SeqTxExecutionEnd => "xlayer_seq_package_tx", + Self::SeqBlockBuildEnd => "xlayer_seq_end_block", + Self::SeqBlockSendStart => "xlayer_seq_ds_sent", + Self::RpcBlockReceiveEnd => "xlayer_rpc_receive_block", + Self::RpcBlockInsertEnd => "xlayer_rpc_finish_block", + } + } + + /// Returns the numeric ID of the process + pub const fn as_u64(&self) -> u64 { + *self as u64 + } + + /// Returns the service name based on the process ID + pub const fn service_name(&self) -> &'static str { + match self { + // RPC-related process IDs + Self::RpcReceiveTxEnd | Self::RpcBlockReceiveEnd | Self::RpcBlockInsertEnd => { + RPC_SERVICE_NAME + } + + // Sequencer-related process IDs + Self::SeqReceiveTxEnd | + Self::SeqBlockBuildStart | + Self::SeqTxExecutionEnd | + Self::SeqBlockBuildEnd | + Self::SeqBlockSendStart => SEQ_SERVICE_NAME, + } + } +} + +/// Internal state for the transaction tracer +#[derive(Debug)] +struct TransactionTracerInner { + /// Whether tracing is enabled + enabled: bool, + /// Output file path (if None, logs to console only) + #[allow(dead_code)] + output_path: Option, + /// File handle for writing logs + output_file: Mutex>, + /// Node type (Sequencer, Rpc, or Unknown) + #[allow(dead_code)] + node_type: NodeType, + /// Counter for number of writes since last flush + write_count: AtomicU64, + /// Last flush time + last_flush_time: Mutex, +} + +/// Transaction tracer for logging transaction and block events +#[derive(Debug, Clone)] +pub struct TransactionTracer { + inner: Arc, +} + +impl TransactionTracer { + /// Create a new transaction tracer + pub fn new(enabled: bool, output_path: Option, node_type: NodeType) -> Self { + let output_file = if let Some(ref path) = output_path { + let file_path = if path.to_string_lossy().ends_with('/') || + path.to_string_lossy().ends_with('\\') || + (path.extension().is_none() && !path.exists()) + { + path.join("trace.log") + } else { + path.clone() + }; + + // Create parent directories if they don't exist + if let Some(parent) = file_path.parent() { + if let Err(e) = fs::create_dir_all(parent) { + tracing::warn!( + target: "tx_trace", + ?parent, + error = %e, + "Failed to create transaction trace output directory" + ); + } + } + + match OpenOptions::new().create(true).append(true).open(&file_path) { + Ok(file) => { + tracing::info!( + target: "tx_trace", + ?file_path, + "Transaction trace file opened for appending" + ); + Some(file) + } + Err(e) => { + tracing::warn!( + target: "tx_trace", + ?file_path, + error = %e, + "Failed to open transaction trace file" + ); + None + } + } + } else { + None + }; + + Self { + inner: Arc::new(TransactionTracerInner { + enabled, + output_path: output_path.clone(), + output_file: Mutex::new(output_file), + node_type, + write_count: AtomicU64::new(0), + last_flush_time: Mutex::new(Instant::now()), + }), + } + } + + /// Check if tracing is enabled + pub fn is_enabled(&self) -> bool { + self.inner.enabled + } + + /// Write CSV line to trace file with periodic flush + fn write_to_file(&self, csv_line: &str) { + match self.inner.output_file.lock() { + Ok(mut file_guard) => { + if let Some(ref mut file) = *file_guard { + if let Err(e) = writeln!(file, "{csv_line}") { + tracing::warn!( + target: "tx_trace", + error = %e, + "Failed to write to transaction trace file" + ); + } else { + let count = self.inner.write_count.fetch_add(1, Ordering::Relaxed) + 1; + + let should_flush = { + let mut last_flush = self.inner.last_flush_time.lock().unwrap(); + let now = Instant::now(); + let time_since_flush = now.duration_since(*last_flush); + + if count.is_multiple_of(FLUSH_INTERVAL_WRITES) || + time_since_flush.as_secs() >= FLUSH_INTERVAL_SECONDS + { + *last_flush = now; + true + } else { + false + } + }; + + if should_flush && let Err(e) = file.flush() { + tracing::warn!( + target: "tx_trace", + error = %e, + "Failed to flush transaction trace file" + ); + } + } + } + } + Err(e) => { + tracing::warn!( + target: "tx_trace", + error = %e, + "Failed to acquire lock for transaction trace file" + ); + } + } + } + + /// Force flush the trace file + pub fn flush(&self) { + match self.inner.output_file.lock() { + Ok(mut file_guard) => { + if let Some(ref mut file) = *file_guard { + if let Err(e) = file.flush() { + tracing::warn!( + target: "tx_trace", + error = %e, + "Failed to flush transaction trace file on shutdown" + ); + } + } + } + Err(e) => { + tracing::warn!( + target: "tx_trace", + error = %e, + "Failed to acquire lock for flushing transaction trace file" + ); + } + } + } + + /// Format CSV line with 23 fields + fn format_csv_line( + &self, + trace: &str, + process_id: TransactionProcessId, + current_time: u128, + block_hash: Option, + block_number: Option, + ) -> String { + let escape_csv = |s: &str| -> String { + if s.contains(',') || s.contains('"') || s.contains('\n') { + format!("\"{}\"", s.replace('"', "\"\"")) + } else { + s.to_string() + } + }; + + let chain = CHAIN_NAME; + let trace_hash = trace.to_lowercase(); + let status_str = ""; + let service_name = process_id.service_name(); + let business = BUSINESS_NAME; + let client = ""; + let chainld = CHAIN_ID; + let process_str = (process_id as u32).to_string(); + let process_word_str = process_id.as_str(); + let index = ""; + let inner_index = ""; + let current_time_str = current_time.to_string(); + let referld = ""; + let contract_address = ""; + let block_height = block_number.map(|n| n.to_string()).unwrap_or_default(); + let block_hash_str = + block_hash.map(|h| format!("{h:#x}").to_lowercase()).unwrap_or_default(); + let block_time = ""; + let deposit_confirm_height = ""; + let token_id = ""; + let mev_supplier = ""; + let business_hash = ""; + let transaction_type = ""; + let ext_json = ""; + + format!( + "{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}", + escape_csv(chain), + escape_csv(&trace_hash), + escape_csv(status_str), + escape_csv(service_name), + escape_csv(business), + escape_csv(client), + escape_csv(chainld), + escape_csv(&process_str), + escape_csv(process_word_str), + escape_csv(index), + escape_csv(inner_index), + escape_csv(¤t_time_str), + escape_csv(referld), + escape_csv(contract_address), + escape_csv(&block_height), + escape_csv(&block_hash_str), + escape_csv(block_time), + escape_csv(deposit_confirm_height), + escape_csv(token_id), + escape_csv(mev_supplier), + escape_csv(business_hash), + escape_csv(transaction_type), + escape_csv(ext_json) + ) + } + + /// Log transaction event at current time point + pub fn log_transaction( + &self, + tx_hash: B256, + process_id: TransactionProcessId, + block_number: Option, + ) { + if !self.inner.enabled { + return; + } + + let timestamp_duration = + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default(); + let timestamp_ms = timestamp_duration.as_millis(); + let trace_hash = format!("{tx_hash:#x}"); + + let csv_line = + self.format_csv_line(&trace_hash, process_id, timestamp_ms, None, block_number); + + self.write_to_file(&csv_line); + } + + /// Log block event at current time point + pub fn log_block(&self, block_hash: B256, block_number: u64, process_id: TransactionProcessId) { + if !self.inner.enabled { + return; + } + + let timestamp_duration = + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default(); + let timestamp_ms = timestamp_duration.as_millis(); + let trace_hash = format!("{block_hash:#x}"); + + let csv_line = self.format_csv_line( + &trace_hash, + process_id, + timestamp_ms, + Some(block_hash), + Some(block_number), + ); + + self.write_to_file(&csv_line); + } + + /// Log block event with a specific timestamp + /// + /// This method is used when we need to log a block event with a timestamp + /// that was saved earlier (e.g., when block building started but block hash + /// was not yet available). + pub fn log_block_with_timestamp( + &self, + block_hash: B256, + block_number: u64, + process_id: TransactionProcessId, + timestamp_ms: u128, + ) { + if !self.inner.enabled { + return; + } + + let trace_hash = format!("{block_hash:#x}"); + + let csv_line = self.format_csv_line( + &trace_hash, + process_id, + timestamp_ms, + Some(block_hash), + Some(block_number), + ); + + self.write_to_file(&csv_line); + } +} + +/// Global transaction tracer instance (singleton) +static GLOBAL_TRACER: OnceLock> = OnceLock::new(); + +/// Initialize the global transaction tracer +/// +/// This function should be called once at application startup to initialize +/// the singleton tracer instance. Subsequent calls will be ignored. +pub fn init_global_tracer(enabled: bool, output_path: Option, node_type: NodeType) { + let tracer = TransactionTracer::new(enabled, output_path, node_type); + GLOBAL_TRACER.set(Arc::new(tracer)).ok(); +} + +/// Get the global transaction tracer +/// +/// Returns `None` if the tracer has not been initialized yet. +pub fn get_global_tracer() -> Option> { + GLOBAL_TRACER.get().cloned() +} + +/// Flush the global transaction tracer +/// +/// Forces a flush of the trace file to ensure all buffered data is written. +pub fn flush_global_tracer() { + if let Some(tracer) = get_global_tracer() { + tracer.flush(); + } +} From 61703dd5b672797a7f34f648efedec44472f1ec9 Mon Sep 17 00:00:00 2001 From: "ruiwei.guo" <335209779@qq.com> Date: Fri, 16 Jan 2026 14:53:13 +0800 Subject: [PATCH 2/4] add dependency --- Cargo.lock | 4 ++++ Cargo.toml | 1 + crates/engine/tree/Cargo.toml | 1 + crates/engine/tree/src/tree/mod.rs | 23 +++++++++++++++++++-- crates/monitor/src/lib.rs | 20 ++++++++++-------- crates/optimism/payload/Cargo.toml | 1 + crates/optimism/payload/src/builder.rs | 23 ++++++++++++++++++++- crates/optimism/rpc/Cargo.toml | 1 + crates/optimism/rpc/src/eth/transaction.rs | 16 +++++++++++++- crates/rpc/rpc-engine-api/Cargo.toml | 1 + crates/rpc/rpc-engine-api/src/engine_api.rs | 8 +++++++ 11 files changed, 86 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcf7b2142af..e90ee453151 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8167,6 +8167,7 @@ dependencies = [ "reth-execution-types", "reth-exex-types", "reth-metrics", + "reth-monitor", "reth-network-p2p", "reth-node-ethereum", "reth-payload-builder", @@ -9579,6 +9580,7 @@ dependencies = [ "reth-chainspec", "reth-evm", "reth-execution-types", + "reth-monitor", "reth-optimism-evm", "reth-optimism-forks", "reth-optimism-primitives", @@ -9660,6 +9662,7 @@ dependencies = [ "reth-chainspec", "reth-evm", "reth-metrics", + "reth-monitor", "reth-node-api", "reth-node-builder", "reth-optimism-chainspec", @@ -10283,6 +10286,7 @@ dependencies = [ "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-metrics", + "reth-monitor", "reth-node-ethereum", "reth-payload-builder", "reth-payload-builder-primitives", diff --git a/Cargo.toml b/Cargo.toml index e4c4916c261..af3b54e3ad6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -392,6 +392,7 @@ reth-ipc = { path = "crates/rpc/ipc" } reth-libmdbx = { path = "crates/storage/libmdbx-rs" } reth-mdbx-sys = { path = "crates/storage/libmdbx-rs/mdbx-sys" } reth-metrics = { path = "crates/metrics" } +reth-monitor = { path = "crates/monitor" } reth-net-banlist = { path = "crates/net/banlist" } reth-net-nat = { path = "crates/net/nat" } reth-network = { path = "crates/net/network" } diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index ba99898a842..54fdee24863 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -56,6 +56,7 @@ smallvec.workspace = true # metrics metrics.workspace = true reth-metrics = { workspace = true, features = ["common"] } +reth-monitor.workspace = true # misc dashmap.workspace = true diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 344ba13d99c..91af8d6f244 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -23,6 +23,7 @@ use reth_engine_primitives::{ }; use reth_errors::{ConsensusError, ProviderResult}; use reth_evm::{ConfigureEvm, OnStateHook}; +use reth_monitor::{get_global_tracer, TransactionProcessId}; use reth_payload_builder::PayloadBuilderHandle; use reth_payload_primitives::{ BuiltPayload, EngineApiMessageVersion, NewPayloadError, PayloadBuilderAttributes, PayloadTypes, @@ -542,6 +543,13 @@ where self.emit_event(EngineApiEvent::BeaconConsensus(engine_event)); let block_hash = num_hash.hash; + let block_number = payload.block_number(); + + // X Layer: Log block receive end (record immediately after receiving the block, + // before processing/insertion starts, to ensure correct timestamp ordering) + if let Some(tracer) = get_global_tracer() { + tracer.log_block(block_hash, block_number, TransactionProcessId::RpcBlockReceiveEnd); + } // Check for invalid ancestors if let Some(invalid) = self.find_invalid_ancestor(&payload) { @@ -2504,12 +2512,23 @@ where // emit insert event let elapsed = start.elapsed(); let engine_event = if is_fork { - ConsensusEngineEvent::ForkBlockAdded(executed, elapsed) + ConsensusEngineEvent::ForkBlockAdded(executed.clone(), elapsed) } else { - ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed) + ConsensusEngineEvent::CanonicalBlockAdded(executed.clone(), elapsed) }; self.emit_event(EngineApiEvent::BeaconConsensus(engine_event)); + // X Layer: Log block insertion end + if let Some(tracer) = get_global_tracer() { + let is_canonical = !is_fork; + + if is_canonical { + let block_hash = executed.recovered_block().hash(); + let block_number = executed.recovered_block().number(); + tracer.log_block(block_hash, block_number, TransactionProcessId::RpcBlockInsertEnd); + } + } + self.metrics .engine .block_insert_total_duration diff --git a/crates/monitor/src/lib.rs b/crates/monitor/src/lib.rs index 4efcf54faff..606f5538762 100644 --- a/crates/monitor/src/lib.rs +++ b/crates/monitor/src/lib.rs @@ -152,14 +152,18 @@ pub struct TransactionTracer { impl TransactionTracer { /// Create a new transaction tracer pub fn new(enabled: bool, output_path: Option, node_type: NodeType) -> Self { - let output_file = if let Some(ref path) = output_path { - let file_path = if path.to_string_lossy().ends_with('/') || - path.to_string_lossy().ends_with('\\') || - (path.extension().is_none() && !path.exists()) + // Default path if not specified: /data/logs/trace.log + let default_path = PathBuf::from("/data/logs/trace.log"); + let final_path = output_path.unwrap_or(default_path); + + let output_file = { + let file_path = if final_path.to_string_lossy().ends_with('/') || + final_path.to_string_lossy().ends_with('\\') || + (final_path.extension().is_none() && !final_path.exists()) { - path.join("trace.log") + final_path.join("trace.log") } else { - path.clone() + final_path.clone() }; // Create parent directories if they don't exist @@ -193,14 +197,12 @@ impl TransactionTracer { None } } - } else { - None }; Self { inner: Arc::new(TransactionTracerInner { enabled, - output_path: output_path.clone(), + output_path: Some(final_path), output_file: Mutex::new(output_file), node_type, write_count: AtomicU64::new(0), diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index e75075a12cf..d0b27876ebe 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -52,3 +52,4 @@ tracing.workspace = true thiserror.workspace = true sha2.workspace = true serde.workspace = true +reth-monitor.workspace = true diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 3f45c4429de..9f801f8470c 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -20,6 +20,7 @@ use reth_evm::{ ConfigureEvm, Database, }; use reth_execution_types::ExecutionOutcome; +use reth_monitor::{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::{ @@ -40,7 +41,7 @@ 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::SystemTime}; use tracing::{debug, trace, warn}; /// Optimism's payload builder @@ -338,6 +339,10 @@ impl OpBuilder<'_, Txs> { let Self { best } = self; debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number(), "building new payload"); + // X Layer: Save block build start timestamp + let build_start_timestamp = + SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis(); + let mut db = State::builder().with_database(db).with_bundle_update().build(); // Load the L1 block contract into the database cache. If the L1 block contract is not @@ -396,6 +401,22 @@ impl OpBuilder<'_, Txs> { let payload = OpBuiltPayload::new(ctx.payload_id(), sealed_block, info.total_fees, Some(executed)); + // X Layer: Log block build start and end + let block_hash = payload.block().hash(); + let block_number = payload.block().number(); + if let Some(tracer) = get_global_tracer() { + // Log block build start using saved timestamp (now we have the block hash) + tracer.log_block_with_timestamp( + block_hash, + block_number, + TransactionProcessId::SeqBlockBuildStart, + build_start_timestamp, + ); + + // Log block build end + tracer.log_block(block_hash, block_number, TransactionProcessId::SeqBlockBuildEnd); + } + if no_tx_pool { // if `no_tx_pool` is set only transactions from the payload attributes will be included // in the payload. In other words, the payload is deterministic and we can diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 5d926caf159..9f46eea67d5 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -80,6 +80,7 @@ derive_more = { workspace = true, features = ["constructor"] } # metrics reth-metrics.workspace = true metrics.workspace = true +reth-monitor.workspace = true [dev-dependencies] reth-optimism-chainspec.workspace = true diff --git a/crates/optimism/rpc/src/eth/transaction.rs b/crates/optimism/rpc/src/eth/transaction.rs index 661fd11da0c..231cfe7cf01 100644 --- a/crates/optimism/rpc/src/eth/transaction.rs +++ b/crates/optimism/rpc/src/eth/transaction.rs @@ -6,6 +6,7 @@ use alloy_rpc_types_eth::TransactionInfo; use futures::StreamExt; use op_alloy_consensus::{transaction::OpTransactionInfo, OpTransaction}; use reth_chain_state::CanonStateSubscriptions; +use reth_monitor::{get_global_tracer, TransactionProcessId}; use reth_optimism_primitives::DepositReceipt; use reth_primitives_traits::{BlockBody, SignedTransaction, SignerRecoverable}; use reth_rpc_eth_api::{ @@ -54,8 +55,15 @@ where // blocks that it builds. if let Some(client) = self.raw_tx_forwarder().as_ref() { tracing::debug!(target: "rpc::eth", hash = %pool_transaction.hash(), "forwarding raw transaction to sequencer"); + let tx_hash = *pool_transaction.hash(); + + // X Layer: Log RPC receive end + if let Some(tracer) = get_global_tracer() { + tracer.log_transaction(tx_hash, TransactionProcessId::RpcReceiveTxEnd, None); + } + let hash = client.forward_raw_transaction(&tx).await.inspect_err(|err| { - tracing::debug!(target: "rpc::eth", %err, hash=% *pool_transaction.hash(), "failed to forward raw transaction"); + tracing::debug!(target: "rpc::eth", %err, hash=%tx_hash, "failed to forward raw transaction"); })?; // Retain tx in local tx pool after forwarding, for local RPC usage. @@ -67,12 +75,18 @@ where } // submit the transaction to the pool with a `Local` origin + let tx_hash = *pool_transaction.hash(); let AddedTransactionOutcome { hash, .. } = self .pool() .add_transaction(TransactionOrigin::Local, pool_transaction) .await .map_err(Self::Error::from_eth_err)?; + // X Layer: Log sequencer receive end + if let Some(tracer) = get_global_tracer() { + tracer.log_transaction(tx_hash, TransactionProcessId::SeqReceiveTxEnd, None); + } + Ok(hash) } diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 825eb485fc2..eeec35ccff8 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -35,6 +35,7 @@ tokio = { workspace = true, features = ["sync"] } # metrics reth-metrics.workspace = true metrics.workspace = true +reth-monitor.workspace = true # misc async-trait.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 6aeadeecba5..f229966a040 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -19,6 +19,7 @@ use jsonrpsee_core::{server::RpcModule, RpcResult}; use parking_lot::Mutex; use reth_chainspec::EthereumHardforks; use reth_engine_primitives::{ConsensusEngineHandle, EngineApiValidator, EngineTypes}; +use reth_monitor::{get_global_tracer, TransactionProcessId}; use reth_payload_builder::PayloadStore; use reth_payload_primitives::{ validate_payload_timestamp, EngineApiMessageVersion, ExecutionPayload, PayloadOrAttributes, @@ -196,6 +197,13 @@ where &self, payload: PayloadT::ExecutionData, ) -> EngineApiResult { + // X Layer: Log block send start + let block_hash = payload.block_hash(); + let block_number = payload.block_number(); + if let Some(tracer) = get_global_tracer() { + tracer.log_block(block_hash, block_number, TransactionProcessId::SeqBlockSendStart); + } + let start = Instant::now(); let gas_used = payload.gas_used(); From 052c73eab895bc0b61c68e60a9cf2fdfd48cf866 Mon Sep 17 00:00:00 2001 From: "ruiwei.guo" <335209779@qq.com> Date: Fri, 16 Jan 2026 15:10:18 +0800 Subject: [PATCH 3/4] add log --- crates/optimism/payload/src/builder.rs | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 9f801f8470c..99f46ce81e6 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -712,10 +712,13 @@ where let block_da_limit = self.builder_config.da_config.max_da_block_size(); let tx_da_limit = self.builder_config.da_config.max_da_tx_size(); let base_fee = builder.evm_mut().block().basefee(); + let block_number: u64 = builder.evm_mut().block().number().saturating_to(); while let Some(tx) = best_txs.next(()) { let interop = tx.interop_deadline(); let tx_da_size = tx.estimated_da_size(); + // X Layer: Get transaction hash before converting to consensus + let tx_hash = *tx.hash(); let tx = tx.into_consensus(); let da_footprint_gas_scalar = self @@ -762,11 +765,29 @@ where } let gas_used = match builder.execute_transaction(tx.clone()) { - Ok(gas_used) => gas_used, + Ok(gas_used) => { + // X Layer: Log transaction execution end (success) + if let Some(tracer) = get_global_tracer() { + tracer.log_transaction( + tx_hash, + TransactionProcessId::SeqTxExecutionEnd, + Some(block_number), + ); + } + gas_used + } Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { error, .. })) => { + // X Layer: Log transaction execution end (failed) + if let Some(tracer) = get_global_tracer() { + tracer.log_transaction( + tx_hash, + TransactionProcessId::SeqTxExecutionEnd, + Some(block_number), + ); + } if error.is_nonce_too_low() { // if the nonce is too low, we can skip this transaction trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction"); @@ -779,6 +800,14 @@ where continue } Err(err) => { + // X Layer: Log transaction execution end (fatal error) + if let Some(tracer) = get_global_tracer() { + tracer.log_transaction( + tx_hash, + TransactionProcessId::SeqTxExecutionEnd, + Some(block_number), + ); + } // this is an error that we should treat as fatal for this attempt return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) } From 1fe239d6e83c5e2dff8fe9617bec418ceeb1559e Mon Sep 17 00:00:00 2001 From: "ruiwei.guo" <335209779@qq.com> Date: Fri, 16 Jan 2026 15:24:53 +0800 Subject: [PATCH 4/4] add log --- crates/rpc/rpc-engine-api/src/engine_api.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index f229966a040..0a8a47c69e9 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -241,6 +241,13 @@ where &self, payload: PayloadT::ExecutionData, ) -> RpcResult { + // X Layer: Log block send start + let block_hash = payload.block_hash(); + let block_number = payload.block_number(); + if let Some(tracer) = get_global_tracer() { + tracer.log_block(block_hash, block_number, TransactionProcessId::SeqBlockSendStart); + } + let start = Instant::now(); let gas_used = payload.gas_used();