From 5d4773baf514566c370f553f69ca4a97599ad6f7 Mon Sep 17 00:00:00 2001 From: krabat-l Date: Mon, 9 Feb 2026 23:18:40 +0800 Subject: [PATCH] feat: support timed prefix --- bin/debug-trace-server/src/metrics.rs | 130 +++++++++++++++++++++- bin/debug-trace-server/src/rpc_service.rs | 66 +++++++++++ 2 files changed, 192 insertions(+), 4 deletions(-) diff --git a/bin/debug-trace-server/src/metrics.rs b/bin/debug-trace-server/src/metrics.rs index 15fdba9..90d1f55 100644 --- a/bin/debug-trace-server/src/metrics.rs +++ b/bin/debug-trace-server/src/metrics.rs @@ -19,17 +19,45 @@ pub const DEFAULT_METRICS_PORT: u16 = 9090; // RPC Method Name Constants // --------------------------------------------------------------------------- +/// Prefix for timed RPC method aliases. +pub const TIMED_PREFIX: &str = "timed_"; + /// RPC method name for debug_traceBlockByNumber. pub const METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER: &str = "debug_traceBlockByNumber"; /// RPC method name for debug_traceBlockByHash. pub const METHOD_DEBUG_TRACE_BLOCK_BY_HASH: &str = "debug_traceBlockByHash"; /// RPC method name for debug_traceTransaction. pub const METHOD_DEBUG_TRACE_TRANSACTION: &str = "debug_traceTransaction"; +/// RPC method name for debug_getCacheStatus. +pub const METHOD_DEBUG_GET_CACHE_STATUS: &str = "debug_getCacheStatus"; /// RPC method name for trace_block. pub const METHOD_TRACE_BLOCK: &str = "trace_block"; /// RPC method name for trace_transaction. pub const METHOD_TRACE_TRANSACTION: &str = "trace_transaction"; +/// Timed alias for debug_traceBlockByNumber. +pub const TIMED_METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER: &str = "timed_debug_traceBlockByNumber"; +/// Timed alias for debug_traceBlockByHash. +pub const TIMED_METHOD_DEBUG_TRACE_BLOCK_BY_HASH: &str = "timed_debug_traceBlockByHash"; +/// Timed alias for debug_traceTransaction. +pub const TIMED_METHOD_DEBUG_TRACE_TRANSACTION: &str = "timed_debug_traceTransaction"; +/// Timed alias for debug_getCacheStatus. +pub const TIMED_METHOD_DEBUG_GET_CACHE_STATUS: &str = "timed_debug_getCacheStatus"; +/// Timed alias for trace_block. +pub const TIMED_METHOD_TRACE_BLOCK: &str = "timed_trace_block"; +/// Timed alias for trace_transaction. +pub const TIMED_METHOD_TRACE_TRANSACTION: &str = "timed_trace_transaction"; + +/// All (timed_alias, original_method) pairs for registering aliases. +pub const TIMED_METHOD_ALIASES: &[(&str, &str)] = &[ + (TIMED_METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER, METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER), + (TIMED_METHOD_DEBUG_TRACE_BLOCK_BY_HASH, METHOD_DEBUG_TRACE_BLOCK_BY_HASH), + (TIMED_METHOD_DEBUG_TRACE_TRANSACTION, METHOD_DEBUG_TRACE_TRANSACTION), + (TIMED_METHOD_DEBUG_GET_CACHE_STATUS, METHOD_DEBUG_GET_CACHE_STATUS), + (TIMED_METHOD_TRACE_BLOCK, METHOD_TRACE_BLOCK), + (TIMED_METHOD_TRACE_TRANSACTION, METHOD_TRACE_TRANSACTION), +]; + // --------------------------------------------------------------------------- // Cache Type Constants // --------------------------------------------------------------------------- @@ -320,11 +348,18 @@ pub fn init_metrics(addr: SocketAddr) -> Result<()> { // Backward-compatible helper functions // --------------------------------------------------------------------------- +/// Strips the `timed_` prefix from a method name if present. +/// Returns the original method name (without prefix) for metrics recording. +pub fn strip_timed_prefix(method: &str) -> &str { + method.strip_prefix(TIMED_PREFIX).unwrap_or(method) +} + /// Records a successful RPC request (backward-compatible helper). /// -/// Uses pre-defined method metrics to avoid hardcoded strings at call sites. +/// Handles both original and `timed_`-prefixed method names by stripping the +/// prefix before recording metrics, so timed variants share the same counters. pub fn record_rpc_request(method: &str, duration_secs: f64) { - // Match known methods to use pre-instantiated metrics (same instance = same metric) + let method = strip_timed_prefix(method); match method { METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER => { RpcMethodMetrics::new_for_method(METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER) @@ -345,15 +380,16 @@ pub fn record_rpc_request(method: &str, duration_secs: f64) { RpcMethodMetrics::new_for_method(METHOD_TRACE_TRANSACTION).record_request(duration_secs) } _ => { - // Unknown method - this shouldn't happen with current code - // Log warning but don't panic tracing::warn!(method = method, "Unknown RPC method in metrics"); } } } /// Records an RPC error for a specific method (backward-compatible helper). +/// +/// Handles both original and `timed_`-prefixed method names. pub fn record_rpc_error(method: &str) { + let method = strip_timed_prefix(method); match method { METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER => { RpcMethodMetrics::new_for_method(METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER).record_error() @@ -371,3 +407,89 @@ pub fn record_rpc_error(method: &str) { _ => {} } } + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_strip_timed_prefix_with_prefix() { + assert_eq!( + strip_timed_prefix("timed_debug_traceBlockByNumber"), + "debug_traceBlockByNumber" + ); + assert_eq!(strip_timed_prefix("timed_trace_block"), "trace_block"); + assert_eq!(strip_timed_prefix("timed_trace_transaction"), "trace_transaction"); + } + + #[test] + fn test_strip_timed_prefix_without_prefix() { + assert_eq!(strip_timed_prefix("debug_traceBlockByNumber"), "debug_traceBlockByNumber"); + assert_eq!(strip_timed_prefix("trace_block"), "trace_block"); + assert_eq!(strip_timed_prefix("unknown_method"), "unknown_method"); + } + + #[test] + fn test_strip_timed_prefix_edge_cases() { + assert_eq!(strip_timed_prefix("timed_"), ""); + assert_eq!(strip_timed_prefix(""), ""); + assert_eq!( + strip_timed_prefix("TIMED_debug_traceBlockByNumber"), + "TIMED_debug_traceBlockByNumber" + ); + // Only strips once + assert_eq!(strip_timed_prefix("timed_timed_trace_block"), "timed_trace_block"); + } + + #[test] + fn test_timed_aliases_consistency() { + // Every alias must start with the TIMED_PREFIX + for &(alias, _original) in TIMED_METHOD_ALIASES { + assert!( + alias.starts_with(TIMED_PREFIX), + "Alias '{}' does not start with '{}'", + alias, + TIMED_PREFIX + ); + } + } + + #[test] + fn test_timed_aliases_match_originals() { + // Stripping the prefix from each alias must yield the original method name + for &(alias, original) in TIMED_METHOD_ALIASES { + assert_eq!( + strip_timed_prefix(alias), + original, + "Alias '{}' does not map back to '{}'", + alias, + original + ); + } + } + + #[test] + fn test_timed_aliases_cover_all_methods() { + let all_methods = [ + METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER, + METHOD_DEBUG_TRACE_BLOCK_BY_HASH, + METHOD_DEBUG_TRACE_TRANSACTION, + METHOD_DEBUG_GET_CACHE_STATUS, + METHOD_TRACE_BLOCK, + METHOD_TRACE_TRANSACTION, + ]; + let aliased_originals: Vec<&str> = + TIMED_METHOD_ALIASES.iter().map(|&(_, orig)| orig).collect(); + for method in all_methods { + assert!( + aliased_originals.contains(&method), + "Method '{}' has no timed_ alias in TIMED_METHOD_ALIASES", + method + ); + } + } +} diff --git a/bin/debug-trace-server/src/rpc_service.rs b/bin/debug-trace-server/src/rpc_service.rs index 959c296..29ea3f2 100644 --- a/bin/debug-trace-server/src/rpc_service.rs +++ b/bin/debug-trace-server/src/rpc_service.rs @@ -192,10 +192,20 @@ impl RpcContext { } /// Creates the merged RPC module containing all methods. + /// + /// Also registers `timed_` prefixed aliases for every method, so that + /// e.g. `timed_debug_traceBlockByNumber` routes to the same handler as + /// `debug_traceBlockByNumber`. pub fn into_rpc_module(self) -> eyre::Result> { let mut module = jsonrpsee::server::RpcModule::new(()); module.merge(DebugTraceRpcServer::into_rpc(self.clone()))?; module.merge(TraceRpcServer::into_rpc(self))?; + + // Register timed_ aliases for all methods + for &(alias, existing) in metrics::TIMED_METHOD_ALIASES { + module.register_alias(alias, existing)?; + } + Ok(module) } } @@ -745,4 +755,60 @@ mod tests { fn test_slow_request_threshold() { assert_eq!(SLOW_REQUEST_THRESHOLD.as_secs(), 5); } + + #[test] + fn test_timed_alias_registration() { + // Create a module and register dummy methods matching the real method names, + // then register timed_ aliases the same way into_rpc_module does. + let mut module = jsonrpsee::server::RpcModule::new(()); + + // Register dummy methods for all known RPC methods + let methods = [ + metrics::METHOD_DEBUG_TRACE_BLOCK_BY_NUMBER, + metrics::METHOD_DEBUG_TRACE_BLOCK_BY_HASH, + metrics::METHOD_DEBUG_TRACE_TRANSACTION, + metrics::METHOD_DEBUG_GET_CACHE_STATUS, + metrics::METHOD_TRACE_BLOCK, + metrics::METHOD_TRACE_TRANSACTION, + ]; + for method in methods { + module + .register_method(method, |_, _, _| serde_json::Value::Null) + .expect("failed to register method"); + } + + // Register timed_ aliases + for &(alias, existing) in metrics::TIMED_METHOD_ALIASES { + module.register_alias(alias, existing).expect("failed to register alias"); + } + + let names: Vec<&str> = module.method_names().collect(); + + // Verify all original methods are present + for method in methods { + assert!(names.contains(&method), "Missing original method: {}", method); + } + + // Verify all timed_ aliases are present + for &(alias, _) in metrics::TIMED_METHOD_ALIASES { + assert!(names.contains(&alias), "Missing timed alias: {}", alias); + } + } + + #[test] + fn test_timed_alias_duplicate_rejected() { + // Registering the same alias twice should fail + let mut module = jsonrpsee::server::RpcModule::new(()); + module + .register_method(metrics::METHOD_TRACE_BLOCK, |_, _, _| serde_json::Value::Null) + .unwrap(); + module + .register_alias(metrics::TIMED_METHOD_TRACE_BLOCK, metrics::METHOD_TRACE_BLOCK) + .unwrap(); + + // Second registration of the same alias should error + let result = + module.register_alias(metrics::TIMED_METHOD_TRACE_BLOCK, metrics::METHOD_TRACE_BLOCK); + assert!(result.is_err(), "Duplicate alias registration should fail"); + } }