@@ -18,9 +18,9 @@ pub struct Metrics {
1818 /// Number of successful RPC calls
1919 pub successful_calls : AtomicU64 ,
2020 /// Number of failed RPC calls by error type
21- pub failed_calls_by_type : DashMap < String , u64 > ,
21+ pub failed_calls_by_type : DashMap < String , AtomicU64 > ,
2222 /// Number of failed RPC calls by method
23- pub failed_calls_by_method : DashMap < String , u64 > ,
23+ pub failed_calls_by_method : DashMap < String , AtomicU64 > ,
2424 /// Duration histogram buckets (in milliseconds)
2525 /// Buckets: <10ms, 10-50ms, 50-100ms, 100-500ms, 500-1000ms, >1000ms
2626 pub duration_buckets : [ AtomicU64 ; 6 ] ,
@@ -58,18 +58,18 @@ impl Metrics {
5858
5959 /// Increment failed calls counter by error type and record duration
6060 pub fn increment_failed_calls ( & self , error_type : & str , method : Option < & str > , duration_ms : u64 ) {
61- // Increment by error type using dashmap for concurrent access
61+ // Increment by error type using dashmap for concurrent access with AtomicU64
6262 self . failed_calls_by_type
6363 . entry ( error_type. to_string ( ) )
64- . and_modify ( |e| * e += 1 )
65- . or_insert ( 1 ) ;
64+ . or_insert_with ( || AtomicU64 :: new ( 0 ) )
65+ . fetch_add ( 1 , Ordering :: Relaxed ) ;
6666
6767 // Increment by method if available
6868 if let Some ( method) = method {
6969 self . failed_calls_by_method
7070 . entry ( method. to_string ( ) )
71- . and_modify ( |e| * e += 1 )
72- . or_insert ( 1 ) ;
71+ . or_insert_with ( || AtomicU64 :: new ( 0 ) )
72+ . fetch_add ( 1 , Ordering :: Relaxed ) ;
7373 }
7474
7575 // Record duration for failed requests too
@@ -81,12 +81,12 @@ impl Metrics {
8181 // Convert DashMap to HashMap for JSON serialization
8282 let failed_by_type: std:: collections:: HashMap < String , u64 > = self . failed_calls_by_type
8383 . iter ( )
84- . map ( |entry| ( entry. key ( ) . clone ( ) , * entry. value ( ) ) )
84+ . map ( |entry| ( entry. key ( ) . clone ( ) , entry. value ( ) . load ( Ordering :: Relaxed ) ) )
8585 . collect ( ) ;
8686
8787 let failed_by_method: std:: collections:: HashMap < String , u64 > = self . failed_calls_by_method
8888 . iter ( )
89- . map ( |entry| ( entry. key ( ) . clone ( ) , * entry. value ( ) ) )
89+ . map ( |entry| ( entry. key ( ) . clone ( ) , entry. value ( ) . load ( Ordering :: Relaxed ) ) )
9090 . collect ( ) ;
9191
9292 let total_calls = self . total_calls . load ( Ordering :: Relaxed ) ;
@@ -117,6 +117,7 @@ impl Metrics {
117117 }
118118
119119 /// Reset all metrics (useful for testing)
120+ #[ cfg( test) ]
120121 pub fn reset ( & self ) {
121122 self . total_calls . store ( 0 , Ordering :: Relaxed ) ;
122123 self . successful_calls . store ( 0 , Ordering :: Relaxed ) ;
@@ -419,6 +420,65 @@ pub fn create_result_summary(result: &Value) -> String {
419420}
420421
421422/// Macro to reduce repetitive boilerplate around timing and logs for RPC calls
423+ ///
424+ /// This macro provides standardized logging and timing for RPC calls across the codebase.
425+ /// It automatically handles request ID generation, timing, success/failure logging,
426+ /// and error wrapping with proper context.
427+ ///
428+ /// # Input Expectations
429+ ///
430+ /// ## Required Parameters
431+ /// * `method` - A string literal or expression evaluating to a method name (e.g., "getBalance")
432+ /// * `client` - A reference to an RpcClient instance that implements `.url()` method
433+ /// * `async_block` - An async block/expression that returns a Result<T, E>
434+ ///
435+ /// ## Optional Parameters
436+ /// * `params` - A string describing the parameters for logging (4th parameter)
437+ ///
438+ /// # Return Value
439+ /// Returns `McpResult<T>` where T is the success type from the async block.
440+ /// On error, returns an `McpError` with full context including request ID, method, and RPC URL.
441+ ///
442+ /// # Logging Behavior
443+ /// * **Start**: Logs request initiation with method, RPC URL, and optional params
444+ /// * **Success**: Logs completion with duration and success message
445+ /// * **Failure**: Logs error with duration, error type, and error details
446+ ///
447+ /// # Examples
448+ ///
449+ /// ```rust
450+ /// use crate::log_rpc_call;
451+ ///
452+ /// // Simple usage without parameters
453+ /// let result = log_rpc_call!(
454+ /// "getHealth",
455+ /// client,
456+ /// async { client.get_health().await }
457+ /// );
458+ ///
459+ /// // With parameter description for logging
460+ /// let pubkey = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
461+ /// let result = log_rpc_call!(
462+ /// "getBalance",
463+ /// client,
464+ /// async {
465+ /// let balance = client.get_balance(&pubkey_parsed).await?;
466+ /// Ok(serde_json::json!({ "balance": balance }))
467+ /// },
468+ /// &format!("pubkey: {}", pubkey)
469+ /// );
470+ /// ```
471+ ///
472+ /// # Error Handling
473+ /// The macro automatically:
474+ /// * Converts any error type implementing Into<McpError>
475+ /// * Adds contextual information (request_id, method, rpc_url)
476+ /// * Logs the failure with proper error categorization
477+ /// * Returns a fully contextualized McpError
478+ ///
479+ /// # Thread Safety
480+ /// This macro is thread-safe and can be used in concurrent async contexts.
481+ /// All logging operations use atomic counters and thread-safe data structures.
422482#[ macro_export]
423483macro_rules! log_rpc_call {
424484 ( $method: expr, $client: expr, $async_block: expr) => { {
@@ -538,9 +598,9 @@ mod tests {
538598 assert_eq ! ( metrics. total_calls. load( Ordering :: Relaxed ) , 1 ) ;
539599 assert_eq ! ( metrics. successful_calls. load( Ordering :: Relaxed ) , 1 ) ;
540600
541- // Test dashmap access
542- assert_eq ! ( metrics. failed_calls_by_type. get( "validation" ) . map( |v| * v ) , Some ( 1 ) ) ;
543- assert_eq ! ( metrics. failed_calls_by_method. get( "getBalance" ) . map( |v| * v ) , Some ( 1 ) ) ;
601+ // Test dashmap access with AtomicU64
602+ assert_eq ! ( metrics. failed_calls_by_type. get( "validation" ) . map( |v| v . load ( Ordering :: Relaxed ) ) , Some ( 1 ) ) ;
603+ assert_eq ! ( metrics. failed_calls_by_method. get( "getBalance" ) . map( |v| v . load ( Ordering :: Relaxed ) ) , Some ( 1 ) ) ;
544604 }
545605
546606 #[ test]
@@ -642,4 +702,53 @@ mod tests {
642702 let avg_duration = performance. get ( "avg_duration_ms" ) . unwrap ( ) . as_u64 ( ) . unwrap ( ) ;
643703 assert_eq ! ( avg_duration, 425 ) ;
644704 }
705+
706+ #[ test]
707+ fn test_spl_token_compatibility ( ) {
708+ // Test that we can import and use basic spl-token types from version 7.0
709+ use spl_token:: state:: { Account , Mint } ;
710+ use spl_token:: instruction:: { TokenInstruction } ;
711+ use solana_sdk:: program_pack:: Pack ;
712+
713+ // Test that we can create instruction types (this would fail if there were breaking changes)
714+ let _instruction = TokenInstruction :: InitializeMint {
715+ decimals : 9 ,
716+ mint_authority : solana_sdk:: pubkey:: Pubkey :: default ( ) ,
717+ freeze_authority : solana_sdk:: program_option:: COption :: None ,
718+ } ;
719+
720+ // Test account size calculation still works
721+ let account_size = std:: mem:: size_of :: < Account > ( ) ;
722+ assert ! ( account_size > 0 ) ;
723+
724+ let mint_size = std:: mem:: size_of :: < Mint > ( ) ;
725+ assert ! ( mint_size > 0 ) ;
726+
727+ // Test basic constants are accessible using Pack trait
728+ assert ! ( Account :: LEN > 0 ) ;
729+ assert ! ( Mint :: LEN > 0 ) ;
730+ }
731+
732+ #[ test]
733+ fn test_solana_dependency_compatibility ( ) {
734+ // Test that basic Solana SDK functionality works with version 2.2
735+ use solana_sdk:: { pubkey:: Pubkey , signature:: Signature , transaction:: Transaction } ;
736+ use solana_client:: rpc_client:: RpcClient ;
737+
738+ // Test pubkey creation and validation
739+ let pubkey = Pubkey :: default ( ) ;
740+ assert_eq ! ( pubkey. to_string( ) , "11111111111111111111111111111111" ) ;
741+
742+ // Test signature creation
743+ let signature = Signature :: default ( ) ;
744+ assert_eq ! ( signature. to_string( ) . len( ) , 64 ) ; // Base58 encoded signature length
745+
746+ // Test RPC client can be instantiated (constructor compatibility)
747+ let _client = RpcClient :: new ( "https://api.mainnet-beta.solana.com" . to_string ( ) ) ;
748+
749+ // Test that transaction types are compatible
750+ let transaction = Transaction :: default ( ) ;
751+ assert ! ( transaction. signatures. is_empty( ) ) ;
752+ assert ! ( transaction. message. instructions. is_empty( ) ) ;
753+ }
645754}
0 commit comments