diff --git a/crates/optimism/txpool/src/maintain.rs b/crates/optimism/txpool/src/maintain.rs index c071bf708e4..637afc2c327 100644 --- a/crates/optimism/txpool/src/maintain.rs +++ b/crates/optimism/txpool/src/maintain.rs @@ -13,13 +13,14 @@ use crate::{ supervisor::SupervisorClient, }; use alloy_consensus::{conditional::BlockConditionalAttributes, BlockHeader}; +use alloy_primitives::TxHash; use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt}; use metrics::{Gauge, Histogram}; use reth_chain_state::CanonStateNotification; use reth_metrics::{metrics::Counter, Metrics}; use reth_primitives_traits::NodePrimitives; use reth_transaction_pool::{error::PoolTransactionError, PoolTransaction, TransactionPool}; -use std::time::Instant; +use std::{collections::HashMap, time::Instant}; use tracing::warn; /// Transaction pool maintenance metrics @@ -50,9 +51,16 @@ struct MaintainPoolInteropMetrics { /// Counter for interop transactions that became stale and need revalidation stale_interop_transactions: Counter, - // TODO: we also should add metric for (hash, counter) to check number of validation per tx /// Histogram for measuring supervisor revalidation duration (congestion metric) supervisor_revalidation_duration_seconds: Histogram, + + /// Total number of supervisor revalidation attempts across all interop transactions + revalidation_attempts_total: Counter, + /// Histogram recording the number of revalidation attempts a tx had at the moment it was + /// removed due to supervisor outcome or interop reclassification. + revalidation_attempts_on_removal: Histogram, + /// Number of interop transactions currently tracked for revalidation attempts + revalidation_tracked_entries: Gauge, } impl MaintainPoolInteropMetrics { @@ -75,6 +83,24 @@ impl MaintainPoolInteropMetrics { fn record_supervisor_duration(&self, duration: std::time::Duration) { self.supervisor_revalidation_duration_seconds.record(duration.as_secs_f64()); } + + /// Increment total number of supervisor revalidation attempts + #[inline] + fn inc_revalidation_attempts(&self, count: usize) { + self.revalidation_attempts_total.increment(count as u64); + } + + /// Record how many attempts a transaction had when it was removed + #[inline] + fn record_attempts_on_removal(&self, attempts: u32) { + self.revalidation_attempts_on_removal.record(attempts as f64); + } + + /// Set current number of tracked entries for revalidation attempts + #[inline] + fn set_tracked_entries(&self, count: usize) { + self.revalidation_tracked_entries.set(count as f64); + } } /// Returns a spawnable future for maintaining the state of the conditional txs in the transaction /// pool. @@ -161,6 +187,7 @@ pub async fn maintain_transaction_pool_interop( St: Stream> + Send + Unpin + 'static, { let metrics = MaintainPoolInteropMetrics::default(); + let mut revalidation_attempts: HashMap = HashMap::new(); loop { let Some(event) = events.next().await else { break }; @@ -200,6 +227,11 @@ pub async fn maintain_transaction_pool_interop( while let Some((tx_item_from_stream, validation_result)) = revalidation_stream.next().await { + let tx_hash = *tx_item_from_stream.hash(); + let attempts = revalidation_attempts.entry(tx_hash).or_insert(0); + *attempts += 1; + metrics.inc_revalidation_attempts(1); + match validation_result { Some(Ok(())) => { tx_item_from_stream @@ -207,26 +239,39 @@ pub async fn maintain_transaction_pool_interop( } Some(Err(err)) => { if err.is_bad_transaction() { - to_remove.push(*tx_item_from_stream.hash()); + if let Some(attempts) = revalidation_attempts.remove(&tx_hash) { + metrics.record_attempts_on_removal(attempts); + } + to_remove.push(tx_hash); } } None => { warn!( target: "txpool", - hash = %tx_item_from_stream.hash(), + hash = %tx_hash, "Interop transaction no longer considered cross-chain during revalidation; removing." ); - to_remove.push(*tx_item_from_stream.hash()); + if let Some(attempts) = revalidation_attempts.remove(&tx_hash) { + metrics.record_attempts_on_removal(attempts); + } + to_remove.push(tx_hash); } } } metrics.record_supervisor_duration(revalidation_start.elapsed()); + metrics.set_tracked_entries(revalidation_attempts.len()); } if !to_remove.is_empty() { + // Cleanup any attempt tracking for transactions that are being removed for reasons + // outside of supervisor revalidation outcome (e.g. deadline exceeded before stream) + for hash in &to_remove { + revalidation_attempts.remove(hash); + } let removed = pool.remove_transactions(to_remove); metrics.inc_removed_tx_interop(removed.len()); + metrics.set_tracked_entries(revalidation_attempts.len()); } } }