From 6a4df5c7c5ff2cd0feb91bf497ad910dfb255e60 Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Thu, 13 Nov 2025 14:24:31 +0000 Subject: [PATCH 1/2] feat: Increased logging, added sentry and test script --- Cargo.lock | 441 ++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/config.rs | 43 ++++ src/main.rs | 36 ++- src/rpc.rs | 324 +++++++++++++++++++++---- tests/test_relayx_service.sh | 366 +++++++++++++++++++++++++++++ 6 files changed, 1161 insertions(+), 50 deletions(-) create mode 100755 tests/test_relayx_service.sh diff --git a/Cargo.lock b/Cargo.lock index 03d45a1..41bbef2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -931,6 +931,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "blst" version = "0.3.16" @@ -1226,6 +1235,16 @@ dependencies = [ "parking_lot_core 0.9.12", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "der" version = "0.7.10" @@ -1236,6 +1255,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1310,6 +1338,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.4", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1420,7 +1458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1467,6 +1505,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -1766,6 +1816,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hostname" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +dependencies = [ + "cfg-if 1.0.3", + "libc", + "windows-link 0.1.3", +] + [[package]] name = "http" version = "0.2.12" @@ -2468,6 +2529,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.4", + "cfg-if 1.0.3", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -2497,6 +2570,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -2547,6 +2626,165 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.9.4", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.9.4", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.9.4", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.9.4", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.9.4", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.9.4", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "object" version = "0.37.3" @@ -2612,6 +2850,22 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_info" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c39b5918402d564846d5aba164c09a66cc88d232179dfd3e3c619a25a268392" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -2773,6 +3027,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -3076,6 +3336,7 @@ dependencies = [ "jsonrpc-http-server", "reqwest", "rocksdb", + "sentry", "serde", "serde_json", "tempfile", @@ -3095,7 +3356,9 @@ dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", + "futures-util", "h2", "http 1.3.1", "http-body 1.0.1", @@ -3260,7 +3523,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3409,6 +3672,125 @@ dependencies = [ "pest", ] +[[package]] +name = "sentry" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00421ed8fa0c995f07cde48ba6c89e80f2b312f74ff637326f392fbfd23abe02" +dependencies = [ + "httpdate", + "native-tls", + "reqwest", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-log", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", +] + +[[package]] +name = "sentry-backtrace" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79194074f34b0cbe5dd33896e5928bbc6ab63a889bd9df2264af5acb186921e" +dependencies = [ + "backtrace", + "once_cell", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba8870c5dba2bfd9db25c75574a11429f6b95957b0a78ac02e2970dd7a5249a" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version 0.4.1", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a75011ea1c0d5c46e9e57df03ce81f5c7f0a9e199086334a1f9c0a541e0826" +dependencies = [ + "once_cell", + "rand 0.8.5", + "sentry-types", + "serde", + "serde_json", +] + +[[package]] +name = "sentry-debug-images" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec2a486336559414ab66548da610da5e9626863c3c4ffca07d88f7dc71c8de8" +dependencies = [ + "findshlibs", + "once_cell", + "sentry-core", +] + +[[package]] +name = "sentry-log" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74b7261245ff17a8c48e8f3e1e96fb6b84146870121af880d53aef6a5b4f784" +dependencies = [ + "log", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eaa3ecfa3c8750c78dcfd4637cfa2598b95b52897ed184b4dc77fcf7d95060d" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f715932bf369a61b7256687c6f0554141b7ce097287e30e3f7ed6e9de82498fe" +dependencies = [ + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4519c900ce734f7a0eb7aba0869dfb225a7af8820634a7dd51449e3b093cfb7c" +dependencies = [ + "debugid", + "hex", + "rand 0.8.5", + "serde", + "serde_json", + "thiserror 1.0.69", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.228" @@ -3698,7 +4080,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3759,6 +4141,37 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -4047,6 +4460,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unarray" version = "0.1.4" @@ -4077,6 +4499,19 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "log", + "native-tls", + "once_cell", + "url", +] + [[package]] name = "url" version = "2.5.7" diff --git a/Cargo.toml b/Cargo.toml index 2e4870b..fd36f05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } url = "2.5" uuid = { version = "1.0", features = ["v4", "serde"] } +sentry = { version = "0.32", features = ["panic", "log"] } [dev-dependencies] reqwest = { version = "0.12", features = ["json", "rustls-tls"] } diff --git a/src/config.rs b/src/config.rs index 61703b0..d1afea5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,6 +59,14 @@ pub struct Config { /// Relayer private key used for signing transactions #[arg(long = "relayer-private-key", env = "RELAYX_PRIVATE_KEY")] pub relayer_private_key: Option, + + /// Disable transaction simulation (use default gas limit instead) + #[arg(long = "disable-simulation", env = "RELAYX_DISABLE_SIMULATION")] + pub disable_simulation: bool, + + /// Sentry DSN for error tracking (optional) + #[arg(long = "sentry-dsn", env = "SENTRY_DSN")] + pub sentry_dsn: Option, } impl Config { @@ -275,6 +283,41 @@ impl Config { .unwrap_or_else(|| self.log_level.clone()) } + /// Check if simulation is disabled (from config.json or CLI/env) + pub fn is_simulation_disabled(&self) -> bool { + if self.disable_simulation { + return true; + } + self.get_json_config() + .and_then(|v| { + v.get("disableSimulation") + .and_then(|s| s.as_bool()) + }) + .unwrap_or(false) + } + + /// Get Sentry DSN from config.json or CLI/env + pub fn get_sentry_dsn(&self) -> Option { + if let Some(cli_dsn) = self.sentry_dsn.as_ref().filter(|s| !s.is_empty()) { + return Some(cli_dsn.clone()); + } + + if let Ok(env_dsn) = std::env::var("SENTRY_DSN") { + if !env_dsn.is_empty() { + return Some(env_dsn); + } + } + + self.get_json_config() + .and_then(|v| { + v.get("sentryDsn") + .or_else(|| v.get("sentry_dsn")) + .and_then(|s| s.as_str()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + }) + } + /// Returns the configured Etherscan API key if present in the JSON file. /// Supports either top-level `etherscanApiKey` in config.json or `ETHERSCAN_API_KEY` env var. pub fn etherscan_api_key(&self) -> Option { diff --git a/src/main.rs b/src/main.rs index e1730db..e5345a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ async fn main() -> Result<()> { let log_level = config.get_log_level(); // Parse the log level string - let filter = match log_level.to_lowercase().as_str() { + let filter_str = match log_level.to_lowercase().as_str() { "trace" => "trace", "debug" => "debug", "info" => "info", @@ -24,18 +24,46 @@ async fn main() -> Result<()> { } }; - // Initialize logging with the configured level + // Initialize logging - respect RUST_LOG env var first, then use config + // Format: "relayx=level" to include all logs from the relayx crate + let env_filter = if std::env::var("RUST_LOG").is_ok() { + // RUST_LOG is set, use it + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug")) + } else { + // Use config value, format to include all modules from relayx crate and dependencies + EnvFilter::new(&format!("relayx={},{}", filter_str, filter_str)) + }; + tracing_subscriber::fmt() - .with_env_filter(EnvFilter::new(filter)) + .with_env_filter(env_filter) .with_target(true) .with_thread_ids(false) .with_file(false) .with_line_number(false) .init(); + // Initialize Sentry if DSN is provided (after tracing is set up) + // Note: With the "panic" feature enabled, panics are automatically captured + let _sentry_guard = if let Some(dsn) = config.get_sentry_dsn() { + tracing::info!("Initializing Sentry error tracking"); + let guard = sentry::init(( + dsn.as_str(), + sentry::ClientOptions { + release: sentry::release_name!(), + ..Default::default() + }, + )); + + tracing::info!("✓ Sentry initialized successfully (panics will be automatically captured)"); + Some(guard) + } else { + tracing::debug!("Sentry DSN not provided, skipping error tracking initialization"); + None + }; + tracing::info!("Starting RelayX service"); tracing::debug!("Configuration: {:?}", config); - tracing::info!("Log level set to: {}", filter); + tracing::info!("Log level set to: {}", filter_str); // Initialize storage tracing::info!("Initializing storage at: {:?}", config.db_path); diff --git a/src/rpc.rs b/src/rpc.rs index 51ecfeb..3497459 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -74,6 +74,16 @@ fn simulation_failed_error() -> jsonrpc_core::Error { err } +/// Capture an error in Sentry with context +fn capture_sentry_error(endpoint: &str, error: &jsonrpc_core::Error) { + sentry::configure_scope(|scope| { + scope.set_tag("endpoint", endpoint); + scope.set_tag("error_code", format!("{:?}", error.code)); + scope.set_extra("error_message", error.message.clone().into()); + }); + sentry::capture_message(&format!("{} error: {}", endpoint, error.message), sentry::Level::Error); +} + fn validate_authorization_list( authorization_list: &str, chain_id: u64, @@ -341,6 +351,8 @@ async fn send_relay_transaction( Err(e) => { let error_msg = format!("Failed to send transaction: {}", e); tracing::error!("{}", error_msg); + // Capture critical transaction sending errors in Sentry + sentry::capture_message(&error_msg, sentry::Level::Error); Err(error_msg) } } @@ -348,12 +360,22 @@ async fn send_relay_transaction( /// Simulate a transaction and estimate gas consumption /// Returns the estimated gas on success +/// If simulation is disabled, returns a default gas limit async fn simulate_transaction( wallet_address: &str, calldata: &str, chain_id: u64, cfg: &Config, ) -> Result { + if cfg.is_simulation_disabled() { + tracing::debug!( + "Simulation disabled: using default gas limit for wallet {} on chain {}", + wallet_address, + chain_id + ); + return Ok(150_000); + } + if stub_mode_enabled() { tracing::debug!( "Stub mode enabled: skipping on-chain simulation for wallet {} on chain {}", @@ -534,13 +556,23 @@ async fn process_send_transaction( gas } Err(e) => { - tracing::warn!( - "Pre-relay simulation failed for wallet {} on chain {}: {}", - input.to, - chain_id, - e - ); - return Err(simulation_failed_error()); + // If simulation is disabled, use default gas limit instead of failing + if cfg.is_simulation_disabled() { + tracing::debug!( + "Simulation disabled: using default gas limit for wallet {} on chain {}", + input.to, + chain_id + ); + 150_000 // Default gas limit + } else { + tracing::warn!( + "Pre-relay simulation failed for wallet {} on chain {}: {}", + input.to, + chain_id, + e + ); + return Err(simulation_failed_error()); + } } }; @@ -792,6 +824,16 @@ async fn process_send_transaction( e ); + // Capture critical transaction relay failure in Sentry + sentry::configure_scope(|scope| { + scope.set_tag("transaction_id", &transaction_id); + scope.set_extra("error", e.clone().into()); + }); + sentry::capture_message( + &format!("Failed to send relay transaction: {}", e), + sentry::Level::Error, + ); + // Update storage with error status if let Err(update_err) = storage .update_request_status(relayer_request.id, RequestStatus::Failed, Some(e.clone())) @@ -976,12 +1018,21 @@ async fn process_send_transaction_multichain( gas } Err(e) => { - tracing::warn!( - "Transaction {}: Simulation failed, using default gas limit: {}", - idx, - e - ); - 21000 // Use default if simulation fails + // If simulation is disabled, use default gas limit instead of failing + if cfg.is_simulation_disabled() { + tracing::debug!( + "Transaction {}: Simulation disabled, using default gas limit", + idx + ); + 150_000 // Default gas limit + } else { + tracing::warn!( + "Transaction {}: Simulation failed, using default gas limit: {}", + idx, + e + ); + 150_000 // Use default if simulation fails + } } }; @@ -1628,18 +1679,45 @@ impl RpcServer { let cfg = cfg1.clone(); async move { + tracing::info!("[relayer_sendTransaction] Request received"); + if let Ok(params_json) = serde_json::to_string(¶ms) { + tracing::debug!("[relayer_sendTransaction] Request params: {}", params_json); + } + let inputs: Vec = params.parse::>().map_err(|e| { - tracing::warn!("Failed to parse relayer_sendTransaction params: {}", e); - invalid_params_error() + tracing::warn!("[relayer_sendTransaction] Failed to parse params: {}", e); + let err = invalid_params_error(); + tracing::error!("[relayer_sendTransaction] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_sendTransaction", &err); + err })?; let input = inputs.first().ok_or_else(|| { - tracing::warn!("relayer_sendTransaction missing params: expected one object"); - invalid_params_error() + tracing::warn!("[relayer_sendTransaction] Missing params: expected one object"); + let err = invalid_params_error(); + tracing::error!("[relayer_sendTransaction] Error response: code={:?}, message={}", err.code, err.message); + err })?; - let response = process_send_transaction(storage, input, &cfg).await?; - serde_json::to_value(response).map_err(|_| jsonrpc_core::Error::internal_error()) + match process_send_transaction(storage, input, &cfg).await { + Ok(response) => { + if let Ok(response_json) = serde_json::to_string(&response) { + tracing::info!("[relayer_sendTransaction] Success response: {}", response_json); + } else { + tracing::info!("[relayer_sendTransaction] Success response (serialization failed)"); + } + serde_json::to_value(response).map_err(|e| { + tracing::error!("[relayer_sendTransaction] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) + } + Err(e) => { + tracing::error!("[relayer_sendTransaction] Error response: code={:?}, message={}", e.code, e.message); + // Capture error in Sentry + capture_sentry_error("relayer_sendTransaction", &e); + Err(e) + } + } } }); @@ -1654,17 +1732,45 @@ impl RpcServer { let cfg = cfg1b.clone(); async move { + tracing::info!("[relayer_sendTransactionMultichain] Request received"); + if let Ok(params_json) = serde_json::to_string(¶ms) { + tracing::debug!("[relayer_sendTransactionMultichain] Request params: {}", params_json); + } + let inputs: Vec = params .parse::>() - .map_err(|e| jsonrpc_core::Error::invalid_params(e.to_string()))?; + .map_err(|e| { + tracing::warn!("[relayer_sendTransactionMultichain] Failed to parse params: {}", e); + let err = jsonrpc_core::Error::invalid_params(e.to_string()); + tracing::error!("[relayer_sendTransactionMultichain] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_sendTransactionMultichain", &err); + err + })?; let input = inputs.first().ok_or_else(|| { - jsonrpc_core::Error::invalid_params("missing params: expected one object") + tracing::warn!("[relayer_sendTransactionMultichain] Missing params: expected one object"); + let err = jsonrpc_core::Error::invalid_params("missing params: expected one object"); + tracing::error!("[relayer_sendTransactionMultichain] Error response: code={:?}, message={}", err.code, err.message); + err })?; - let response = - process_send_transaction_multichain(storage, input, &cfg).await?; - serde_json::to_value(response) - .map_err(|_| jsonrpc_core::Error::internal_error()) + match process_send_transaction_multichain(storage, input, &cfg).await { + Ok(response) => { + if let Ok(response_json) = serde_json::to_string(&response) { + tracing::info!("[relayer_sendTransactionMultichain] Success response: {}", response_json); + } else { + tracing::info!("[relayer_sendTransactionMultichain] Success response (serialization failed)"); + } + serde_json::to_value(response).map_err(|e| { + tracing::error!("[relayer_sendTransactionMultichain] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) + } + Err(e) => { + tracing::error!("[relayer_sendTransactionMultichain] Error response: code={:?}, message={}", e.code, e.message); + capture_sentry_error("relayer_sendTransactionMultichain", &e); + Err(e) + } + } } }, ); @@ -1678,12 +1784,39 @@ impl RpcServer { let cfg = cfg2.clone(); async move { + tracing::info!("[relayer_getStatus] Request received"); + if let Ok(params_json) = serde_json::to_string(¶ms) { + tracing::debug!("[relayer_getStatus] Request params: {}", params_json); + } + let request: GetStatusRequest = params .parse::() - .map_err(|e| jsonrpc_core::Error::invalid_params(e.to_string()))?; + .map_err(|e| { + tracing::warn!("[relayer_getStatus] Failed to parse params: {}", e); + let err = jsonrpc_core::Error::invalid_params(e.to_string()); + tracing::error!("[relayer_getStatus] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_getStatus", &err); + err + })?; - let response = process_get_status(storage, &request, &cfg).await?; - serde_json::to_value(response).map_err(|_| jsonrpc_core::Error::internal_error()) + match process_get_status(storage, &request, &cfg).await { + Ok(response) => { + if let Ok(response_json) = serde_json::to_string(&response) { + tracing::info!("[relayer_getStatus] Success response: {}", response_json); + } else { + tracing::info!("[relayer_getStatus] Success response (serialization failed)"); + } + serde_json::to_value(response).map_err(|e| { + tracing::error!("[relayer_getStatus] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) + } + Err(e) => { + tracing::error!("[relayer_getStatus] Error response: code={:?}, message={}", e.code, e.message); + capture_sentry_error("relayer_getStatus", &e); + Err(e) + } + } } }); @@ -1696,9 +1829,26 @@ impl RpcServer { let cfg = cfg3.clone(); async move { - let health = process_health_check(storage, &cfg).await?; - - serde_json::to_value(health).map_err(|_| jsonrpc_core::Error::internal_error()) + tracing::info!("[health_check] Request received"); + + match process_health_check(storage, &cfg).await { + Ok(health) => { + if let Ok(health_json) = serde_json::to_string(&health) { + tracing::info!("[health_check] Success response: {}", health_json); + } else { + tracing::info!("[health_check] Success response (serialization failed)"); + } + serde_json::to_value(health).map_err(|e| { + tracing::error!("[health_check] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) + } + Err(e) => { + tracing::error!("[health_check] Error response: code={:?}, message={}", e.code, e.message); + capture_sentry_error("health_check", &e); + Err(e) + } + } } }); @@ -1708,16 +1858,39 @@ impl RpcServer { io.add_method("relayer_getExchangeRate", move |params: Params| { let cfg = cfg4.clone(); async move { + tracing::info!("[relayer_getExchangeRate] Request received"); + if let Ok(params_json) = serde_json::to_string(¶ms) { + tracing::debug!("[relayer_getExchangeRate] Request params: {}", params_json); + } + let inputs: Vec = params .parse::>() - .map_err(|e| jsonrpc_core::Error::invalid_params(e.to_string()))?; + .map_err(|e| { + tracing::warn!("[relayer_getExchangeRate] Failed to parse params: {}", e); + let err = jsonrpc_core::Error::invalid_params(e.to_string()); + tracing::error!("[relayer_getExchangeRate] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_getExchangeRate", &err); + err + })?; let input = inputs.first().ok_or_else(|| { - jsonrpc_core::Error::invalid_params("missing params: expected one object") + tracing::warn!("[relayer_getExchangeRate] Missing params: expected one object"); + let err = jsonrpc_core::Error::invalid_params("missing params: expected one object"); + tracing::error!("[relayer_getExchangeRate] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_getExchangeRate", &err); + err })?; let payload = build_exchange_rate_response(&cfg, input).await; - serde_json::to_value(payload).map_err(|_| jsonrpc_core::Error::internal_error()) + if let Ok(payload_json) = serde_json::to_string(&payload) { + tracing::info!("[relayer_getExchangeRate] Success response: {}", payload_json); + } else { + tracing::info!("[relayer_getExchangeRate] Success response (serialization failed)"); + } + serde_json::to_value(payload).map_err(|e| { + tracing::error!("[relayer_getExchangeRate] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) } }); @@ -1727,11 +1900,26 @@ impl RpcServer { io.add_method("relayer_getQuote", move |params: Params| { let cfg = cfg6.clone(); async move { + tracing::info!("[relayer_getQuote] Request received"); + if let Ok(params_json) = serde_json::to_string(¶ms) { + tracing::debug!("[relayer_getQuote] Request params: {}", params_json); + } + let inputs: Vec = params .parse::>() - .map_err(|e| jsonrpc_core::Error::invalid_params(e.to_string()))?; + .map_err(|e| { + tracing::warn!("[relayer_getQuote] Failed to parse params: {}", e); + let err = jsonrpc_core::Error::invalid_params(e.to_string()); + tracing::error!("[relayer_getQuote] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_getQuote", &err); + err + })?; let input = inputs.first().ok_or_else(|| { - jsonrpc_core::Error::invalid_params("missing params: expected one object") + tracing::warn!("[relayer_getQuote] Missing params: expected one object"); + let err = jsonrpc_core::Error::invalid_params("missing params: expected one object"); + tracing::error!("[relayer_getQuote] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_getQuote", &err); + err })?; // Minimal realistic quote: estimate gas and use current gas price @@ -1774,7 +1962,15 @@ impl RpcServer { revert_reason: "".to_string(), }; - serde_json::to_value(payload).map_err(|_| jsonrpc_core::Error::internal_error()) + if let Ok(payload_json) = serde_json::to_string(&payload) { + tracing::info!("[relayer_getQuote] Success response: {}", payload_json); + } else { + tracing::info!("[relayer_getQuote] Success response (serialization failed)"); + } + serde_json::to_value(payload).map_err(|e| { + tracing::error!("[relayer_getQuote] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) } }); @@ -1787,9 +1983,26 @@ impl RpcServer { let cfg = cfg5.clone(); async move { - let capabilities = process_get_capabilities(storage, &cfg).await?; - serde_json::to_value(capabilities) - .map_err(|_| jsonrpc_core::Error::internal_error()) + tracing::info!("[relayer_getCapabilities] Request received"); + + match process_get_capabilities(storage, &cfg).await { + Ok(capabilities) => { + if let Ok(capabilities_json) = serde_json::to_string(&capabilities) { + tracing::info!("[relayer_getCapabilities] Success response: {}", capabilities_json); + } else { + tracing::info!("[relayer_getCapabilities] Success response (serialization failed)"); + } + serde_json::to_value(capabilities).map_err(|e| { + tracing::error!("[relayer_getCapabilities] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) + } + Err(e) => { + tracing::error!("[relayer_getCapabilities] Error response: code={:?}, message={}", e.code, e.message); + capture_sentry_error("relayer_getCapabilities", &e); + Err(e) + } + } } }); @@ -1799,15 +2012,38 @@ impl RpcServer { io.add_method("relayer_getFeeData", move |params: Params| { let cfg = cfg_fee.clone(); async move { + tracing::info!("[relayer_getFeeData] Request received"); + if let Ok(params_json) = serde_json::to_string(¶ms) { + tracing::debug!("[relayer_getFeeData] Request params: {}", params_json); + } + let inputs: Vec = params .parse::>() - .map_err(|e| jsonrpc_core::Error::invalid_params(e.to_string()))?; + .map_err(|e| { + tracing::warn!("[relayer_getFeeData] Failed to parse params: {}", e); + let err = jsonrpc_core::Error::invalid_params(e.to_string()); + tracing::error!("[relayer_getFeeData] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_getFeeData", &err); + err + })?; let input = inputs.first().ok_or_else(|| { - jsonrpc_core::Error::invalid_params("missing params: expected one object") + tracing::warn!("[relayer_getFeeData] Missing params: expected one object"); + let err = jsonrpc_core::Error::invalid_params("missing params: expected one object"); + tracing::error!("[relayer_getFeeData] Error response: code={:?}, message={}", err.code, err.message); + capture_sentry_error("relayer_getFeeData", &err); + err })?; let payload = build_exchange_rate_response(&cfg, input).await; - serde_json::to_value(payload).map_err(|_| jsonrpc_core::Error::internal_error()) + if let Ok(payload_json) = serde_json::to_string(&payload) { + tracing::info!("[relayer_getFeeData] Success response: {}", payload_json); + } else { + tracing::info!("[relayer_getFeeData] Success response (serialization failed)"); + } + serde_json::to_value(payload).map_err(|e| { + tracing::error!("[relayer_getFeeData] Failed to serialize response: {}", e); + jsonrpc_core::Error::internal_error() + }) } }); @@ -2049,6 +2285,8 @@ mod tests { http_cors: "*".to_string(), log_level: "debug".to_string(), relayer_private_key: None, + disable_simulation: false, + sentry_dsn: None, } } diff --git a/tests/test_relayx_service.sh b/tests/test_relayx_service.sh new file mode 100755 index 0000000..55a5fa6 --- /dev/null +++ b/tests/test_relayx_service.sh @@ -0,0 +1,366 @@ +#!/bin/bash + +# RelayX Service Test Script +# Tests all JSON-RPC endpoints using curl commands + +set -e + +# Configuration +RELAYX_URL="${RELAYX_URL:-http://127.0.0.1:4937}" +COLOR_GREEN='\033[0;32m' +COLOR_RED='\033[0;31m' +COLOR_YELLOW='\033[1;33m' +COLOR_BLUE='\033[0;34m' +COLOR_RESET='\033[0m' + +# Helper function to print colored output +print_test() { + echo -e "${COLOR_BLUE}▶ Testing: $1${COLOR_RESET}" +} + +print_success() { + echo -e "${COLOR_GREEN}✓ $1${COLOR_RESET}" +} + +print_error() { + echo -e "${COLOR_RED}✗ $1${COLOR_RESET}" +} + +print_info() { + echo -e "${COLOR_YELLOW}ℹ $1${COLOR_RESET}" +} + +# Helper function to make JSON-RPC requests +jsonrpc_request() { + local method=$1 + local params=$2 + local id=${3:-1} + + curl -s -X POST "$RELAYX_URL" \ + -H "Content-Type: application/json" \ + -d "{ + \"jsonrpc\": \"2.0\", + \"id\": $id, + \"method\": \"$method\", + \"params\": $params + }" +} + +echo "==========================================" +echo "RelayX Service Test Suite" +echo "==========================================" +echo "Testing against: $RELAYX_URL" +echo "" + +# Test 1: Health Check +print_test "health_check" +HEALTH_RESPONSE=$(jsonrpc_request "health_check" "null" 1) +echo "$HEALTH_RESPONSE" | jq '.' 2>/dev/null || echo "$HEALTH_RESPONSE" +if echo "$HEALTH_RESPONSE" | grep -q '"status"'; then + print_success "Health check passed" +else + print_error "Health check failed" +fi +echo "" + +# Test 2: Get Capabilities +print_test "relayer_getCapabilities" +CAPABILITIES_RESPONSE=$(jsonrpc_request "relayer_getCapabilities" "null" 2) +echo "$CAPABILITIES_RESPONSE" | jq '.' 2>/dev/null || echo "$CAPABILITIES_RESPONSE" +if echo "$CAPABILITIES_RESPONSE" | grep -q '"result"'; then + print_success "Get capabilities passed" +else + print_error "Get capabilities failed" +fi +echo "" + +# Test 3: Get Fee Data (with native payment) +print_test "relayer_getFeeData (native payment)" +FEE_DATA_RESPONSE=$(jsonrpc_request "relayer_getFeeData" "[{ + \"token\": \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\", + \"chainId\": \"84532\" +}]" 3) +echo "$FEE_DATA_RESPONSE" | jq '.' 2>/dev/null || echo "$FEE_DATA_RESPONSE" +if echo "$FEE_DATA_RESPONSE" | grep -q '"result"'; then + print_success "Get fee data passed" +else + print_error "Get fee data failed" +fi +echo "" + +# Test 4: Get Exchange Rate (legacy endpoint) +print_test "relayer_getExchangeRate" +EXCHANGE_RATE_RESPONSE=$(jsonrpc_request "relayer_getExchangeRate" "[{ + \"token\": \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\", + \"chainId\": \"84532\" +}]" 4) +echo "$EXCHANGE_RATE_RESPONSE" | jq '.' 2>/dev/null || echo "$EXCHANGE_RATE_RESPONSE" +if echo "$EXCHANGE_RATE_RESPONSE" | grep -q '"result"'; then + print_success "Get exchange rate passed" +else + print_error "Get exchange rate failed" +fi +echo "" + +# Test 5: Get Quote +print_test "relayer_getQuote" +QUOTE_RESPONSE=$(jsonrpc_request "relayer_getQuote" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c0000000000000000000000000000000000000000000000000000000000000040\", + \"chainId\": \"84532\" +}]" 5) +echo "$QUOTE_RESPONSE" | jq '.' 2>/dev/null || echo "$QUOTE_RESPONSE" +if echo "$QUOTE_RESPONSE" | grep -q '"result"'; then + print_success "Get quote passed" +else + print_error "Get quote failed" +fi +echo "" + +# Test 6: Send Transaction (native payment) +print_test "relayer_sendTransaction (native payment)" +SEND_TX_RESPONSE=$(jsonrpc_request "relayer_sendTransaction" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c0000000000000000000000000000000000000000000000000000000000000040\", + \"chainId\": \"84532\", + \"capabilities\": { + \"payment\": { + \"type\": \"native\", + \"token\": \"\", + \"data\": \"\" + } + }, + \"authorizationList\": \"\" +}]" 6) +echo "$SEND_TX_RESPONSE" | jq '.' 2>/dev/null || echo "$SEND_TX_RESPONSE" +if echo "$SEND_TX_RESPONSE" | grep -q '"result"'; then + print_success "Send transaction (native) passed" + # Extract transaction ID for status check + TX_ID=$(echo "$SEND_TX_RESPONSE" | jq -r '.result[0].id' 2>/dev/null || echo "") + if [ -n "$TX_ID" ] && [ "$TX_ID" != "null" ]; then + print_info "Transaction ID: $TX_ID" + TX_IDS="$TX_ID" + fi +else + print_error "Send transaction (native) failed" +fi +echo "" + +# Test 7: Send Transaction (ERC20 payment) +print_test "relayer_sendTransaction (ERC20 payment)" +SEND_TX_ERC20_RESPONSE=$(jsonrpc_request "relayer_sendTransaction" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c0000000000000000000000000000000000000000000000000000000000000040\", + \"chainId\": \"84532\", + \"capabilities\": { + \"payment\": { + \"type\": \"erc20\", + \"token\": \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\", + \"data\": \"\" + } + }, + \"authorizationList\": \"\" +}]" 7) +echo "$SEND_TX_ERC20_RESPONSE" | jq '.' 2>/dev/null || echo "$SEND_TX_ERC20_RESPONSE" +if echo "$SEND_TX_ERC20_RESPONSE" | grep -q '"result"'; then + print_success "Send transaction (ERC20) passed" + # Extract transaction ID + TX_ID_ERC20=$(echo "$SEND_TX_ERC20_RESPONSE" | jq -r '.result[0].id' 2>/dev/null || echo "") + if [ -n "$TX_ID_ERC20" ] && [ "$TX_ID_ERC20" != "null" ]; then + print_info "Transaction ID: $TX_ID_ERC20" + TX_IDS="$TX_IDS $TX_ID_ERC20" + fi +else + print_error "Send transaction (ERC20) failed" +fi +echo "" + +# Test 8: Send Transaction (sponsored payment) +print_test "relayer_sendTransaction (sponsored payment)" +SEND_TX_SPONSORED_RESPONSE=$(jsonrpc_request "relayer_sendTransaction" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c0000000000000000000000000000000000000000000000000000000000000040\", + \"chainId\": \"84532\", + \"capabilities\": { + \"payment\": { + \"type\": \"sponsored\", + \"token\": \"\", + \"data\": \"\" + } + }, + \"authorizationList\": \"\" +}]" 8) +echo "$SEND_TX_SPONSORED_RESPONSE" | jq '.' 2>/dev/null || echo "$SEND_TX_SPONSORED_RESPONSE" +if echo "$SEND_TX_SPONSORED_RESPONSE" | grep -q '"result"'; then + print_success "Send transaction (sponsored) passed" + # Extract transaction ID + TX_ID_SPONSORED=$(echo "$SEND_TX_SPONSORED_RESPONSE" | jq -r '.result[0].id' 2>/dev/null || echo "") + if [ -n "$TX_ID_SPONSORED" ] && [ "$TX_ID_SPONSORED" != "null" ]; then + print_info "Transaction ID: $TX_ID_SPONSORED" + TX_IDS="$TX_IDS $TX_ID_SPONSORED" + fi +else + print_error "Send transaction (sponsored) failed" +fi +echo "" + +# Test 9: Send Transaction Multichain (DISABLED) +# print_test "relayer_sendTransactionMultichain" +# SEND_TX_MULTI_RESPONSE=$(jsonrpc_request "relayer_sendTransactionMultichain" "[{ +# \"transactions\": [ +# { +# \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", +# \"data\": \"0xa85a325c0000000000000000000000000000000000000000000000000000000000000040\", +# \"chainId\": \"84532\", +# \"authorizationList\": \"\" +# }, +# { +# \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", +# \"data\": \"0xa85a325c0000000000000000000000000000000000000000000000000000000000000040\", +# \"chainId\": \"1\", +# \"authorizationList\": \"\" +# } +# ], +# \"capabilities\": { +# \"payment\": { +# \"type\": \"sponsored\", +# \"token\": \"\", +# \"data\": \"\" +# } +# }, +# \"paymentChainId\": \"84532\" +# }]" 9) +# echo "$SEND_TX_MULTI_RESPONSE" | jq '.' 2>/dev/null || echo "$SEND_TX_MULTI_RESPONSE" +# if echo "$SEND_TX_MULTI_RESPONSE" | grep -q '"result"'; then +# print_success "Send transaction multichain passed" +# # Extract transaction IDs +# MULTI_IDS=$(echo "$SEND_TX_MULTI_RESPONSE" | jq -r '.result[].id' 2>/dev/null | tr '\n' ' ' || echo "") +# if [ -n "$MULTI_IDS" ]; then +# print_info "Transaction IDs: $MULTI_IDS" +# TX_IDS="$TX_IDS $MULTI_IDS" +# fi +# else +# print_error "Send transaction multichain failed" +# fi +# echo "" + +# Test 9: Send Transaction with EIP-7702 Delegation (delegates private key wallet to contract) +print_test "relayer_sendTransaction (EIP-7702 delegation to 0xb7a972aee9fB89aaA39F9B42C11235A45E34C95F)" +# Note: This test attempts to delegate a private key wallet to the specified contract address +# For a valid authorization, you would need a properly signed EIP-7702 authorization +# This test uses an empty authorizationList - in production, this would contain RLP-encoded signed authorizations +SEND_TX_DELEGATION_RESPONSE=$(jsonrpc_request "relayer_sendTransaction" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c0000000000000000000000000000000000000000000000000000000000000040\", + \"chainId\": \"84532\", + \"capabilities\": { + \"payment\": { + \"type\": \"sponsored\", + \"token\": \"\", + \"data\": \"\" + } + }, + \"authorizationList\": \"\" +}]" 9) +echo "$SEND_TX_DELEGATION_RESPONSE" | jq '.' 2>/dev/null || echo "$SEND_TX_DELEGATION_RESPONSE" +if echo "$SEND_TX_DELEGATION_RESPONSE" | grep -q '"result"'; then + print_success "Send transaction (delegation) passed" + # Extract transaction ID + TX_ID_DELEGATION=$(echo "$SEND_TX_DELEGATION_RESPONSE" | jq -r '.result[0].id' 2>/dev/null || echo "") + if [ -n "$TX_ID_DELEGATION" ] && [ "$TX_ID_DELEGATION" != "null" ]; then + print_info "Transaction ID: $TX_ID_DELEGATION" + print_info "Delegation target: 0xb7a972aee9fB89aaA39F9B42C11235A45E34C95F" + TX_IDS="$TX_IDS $TX_ID_DELEGATION" + fi +else + print_error "Send transaction (delegation) failed" + print_info "Note: For valid EIP-7702 delegation, authorizationList must contain RLP-encoded signed authorizations delegating to 0xb7a972aee9fB89aaA39F9B42C11235A45E34C95F" +fi +echo "" + +# Test 10: Get Status (if we have transaction IDs) +if [ -n "$TX_IDS" ]; then + print_test "relayer_getStatus" + # Clean up TX_IDS and create array + TX_ID_ARRAY=$(echo "$TX_IDS" | tr ' ' '\n' | grep -v '^$' | jq -R . | jq -s .) + STATUS_RESPONSE=$(jsonrpc_request "relayer_getStatus" "{\"ids\": $TX_ID_ARRAY}" 10) + echo "$STATUS_RESPONSE" | jq '.' 2>/dev/null || echo "$STATUS_RESPONSE" + if echo "$STATUS_RESPONSE" | grep -q '"result"'; then + print_success "Get status passed" + else + print_error "Get status failed" + fi + echo "" +else + print_info "Skipping getStatus test (no transaction IDs available)" + echo "" +fi + +# Test 11: Error Cases - Invalid params +print_test "relayer_sendTransaction (invalid params - missing capabilities)" +INVALID_RESPONSE=$(jsonrpc_request "relayer_sendTransaction" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c\", + \"chainId\": \"84532\" +}]" 11) +echo "$INVALID_RESPONSE" | jq '.' 2>/dev/null || echo "$INVALID_RESPONSE" +if echo "$INVALID_RESPONSE" | grep -q '"error"'; then + print_success "Error handling test passed (correctly rejected invalid params)" +else + print_error "Error handling test failed (should have returned error)" +fi +echo "" + +# Test 12: Error Cases - Invalid chain ID +print_test "relayer_sendTransaction (invalid chain ID)" +INVALID_CHAIN_RESPONSE=$(jsonrpc_request "relayer_sendTransaction" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c\", + \"chainId\": \"99999\", + \"capabilities\": { + \"payment\": { + \"type\": \"native\", + \"token\": \"\", + \"data\": \"\" + } + }, + \"authorizationList\": \"\" +}]" 12) +echo "$INVALID_CHAIN_RESPONSE" | jq '.' 2>/dev/null || echo "$INVALID_CHAIN_RESPONSE" +if echo "$INVALID_CHAIN_RESPONSE" | grep -q '"error"'; then + print_success "Error handling test passed (correctly rejected invalid chain ID)" +else + print_error "Error handling test failed (should have returned error)" +fi +echo "" + +# Test 13: Error Cases - Unsupported payment token +print_test "relayer_sendTransaction (unsupported payment token)" +UNSUPPORTED_TOKEN_RESPONSE=$(jsonrpc_request "relayer_sendTransaction" "[{ + \"to\": \"0x52d41be4e18012eb565d5406a41627361dddc41a\", + \"data\": \"0xa85a325c\", + \"chainId\": \"84532\", + \"capabilities\": { + \"payment\": { + \"type\": \"erc20\", + \"token\": \"0x0000000000000000000000000000000000000000\", + \"data\": \"\" + } + }, + \"authorizationList\": \"\" +}]" 13) +echo "$UNSUPPORTED_TOKEN_RESPONSE" | jq '.' 2>/dev/null || echo "$UNSUPPORTED_TOKEN_RESPONSE" +if echo "$UNSUPPORTED_TOKEN_RESPONSE" | grep -q '"error"'; then + print_success "Error handling test passed (correctly rejected unsupported token)" +else + print_error "Error handling test failed (should have returned error)" +fi +echo "" + +echo "==========================================" +echo "Test Suite Complete" +echo "==========================================" +print_info "All tests executed. Review the output above for results." +print_info "To test against a different URL, set RELAYX_URL environment variable:" +print_info " RELAYX_URL=http://localhost:4937 ./tests/test_relayx_service.sh" + From 3e3ebfdbfddaede679fceb5cb07314f54606230c Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Thu, 13 Nov 2025 14:40:11 +0000 Subject: [PATCH 2/2] fix: fixed lint errors --- Cargo.toml | 2 +- src/config.rs | 20 ++--- src/main.rs | 4 +- src/rpc.rs | 193 ++++++++++++++++++++++++++++++++------------- tests/rpc_tests.rs | 2 + 5 files changed, 153 insertions(+), 68 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fd36f05..f554477 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ hex = "0.4" jsonrpc-core = "18.0" jsonrpc-http-server = "18.0" rocksdb = "0.21" +sentry = { version = "0.32", features = ["panic", "log"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "time"] } @@ -21,7 +22,6 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } url = "2.5" uuid = { version = "1.0", features = ["v4", "serde"] } -sentry = { version = "0.32", features = ["panic", "log"] } [dev-dependencies] reqwest = { version = "0.12", features = ["json", "rustls-tls"] } diff --git a/src/config.rs b/src/config.rs index d1afea5..74873d8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -289,10 +289,7 @@ impl Config { return true; } self.get_json_config() - .and_then(|v| { - v.get("disableSimulation") - .and_then(|s| s.as_bool()) - }) + .and_then(|v| v.get("disableSimulation").and_then(|s| s.as_bool())) .unwrap_or(false) } @@ -308,14 +305,13 @@ impl Config { } } - self.get_json_config() - .and_then(|v| { - v.get("sentryDsn") - .or_else(|| v.get("sentry_dsn")) - .and_then(|s| s.as_str()) - .filter(|s| !s.is_empty()) - .map(|s| s.to_string()) - }) + self.get_json_config().and_then(|v| { + v.get("sentryDsn") + .or_else(|| v.get("sentry_dsn")) + .and_then(|s| s.as_str()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + }) } /// Returns the configured Etherscan API key if present in the JSON file. diff --git a/src/main.rs b/src/main.rs index e5345a6..4405641 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ async fn main() -> Result<()> { EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug")) } else { // Use config value, format to include all modules from relayx crate and dependencies - EnvFilter::new(&format!("relayx={},{}", filter_str, filter_str)) + EnvFilter::new(format!("relayx={},{}", filter_str, filter_str)) }; tracing_subscriber::fmt() @@ -53,7 +53,7 @@ async fn main() -> Result<()> { ..Default::default() }, )); - + tracing::info!("✓ Sentry initialized successfully (panics will be automatically captured)"); Some(guard) } else { diff --git a/src/rpc.rs b/src/rpc.rs index 3497459..2bc0ed2 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -81,7 +81,10 @@ fn capture_sentry_error(endpoint: &str, error: &jsonrpc_core::Error) { scope.set_tag("error_code", format!("{:?}", error.code)); scope.set_extra("error_message", error.message.clone().into()); }); - sentry::capture_message(&format!("{} error: {}", endpoint, error.message), sentry::Level::Error); + sentry::capture_message( + &format!("{} error: {}", endpoint, error.message), + sentry::Level::Error, + ); } fn validate_authorization_list( @@ -1688,31 +1691,51 @@ impl RpcServer { params.parse::>().map_err(|e| { tracing::warn!("[relayer_sendTransaction] Failed to parse params: {}", e); let err = invalid_params_error(); - tracing::error!("[relayer_sendTransaction] Error response: code={:?}, message={}", err.code, err.message); + tracing::error!( + "[relayer_sendTransaction] Error response: code={:?}, message={}", + err.code, + err.message + ); capture_sentry_error("relayer_sendTransaction", &err); err })?; let input = inputs.first().ok_or_else(|| { tracing::warn!("[relayer_sendTransaction] Missing params: expected one object"); let err = invalid_params_error(); - tracing::error!("[relayer_sendTransaction] Error response: code={:?}, message={}", err.code, err.message); + tracing::error!( + "[relayer_sendTransaction] Error response: code={:?}, message={}", + err.code, + err.message + ); err })?; match process_send_transaction(storage, input, &cfg).await { Ok(response) => { if let Ok(response_json) = serde_json::to_string(&response) { - tracing::info!("[relayer_sendTransaction] Success response: {}", response_json); + tracing::info!( + "[relayer_sendTransaction] Success response: {}", + response_json + ); } else { - tracing::info!("[relayer_sendTransaction] Success response (serialization failed)"); + tracing::info!( + "[relayer_sendTransaction] Success response (serialization failed)" + ); } serde_json::to_value(response).map_err(|e| { - tracing::error!("[relayer_sendTransaction] Failed to serialize response: {}", e); + tracing::error!( + "[relayer_sendTransaction] Failed to serialize response: {}", + e + ); jsonrpc_core::Error::internal_error() }) } Err(e) => { - tracing::error!("[relayer_sendTransaction] Error response: code={:?}, message={}", e.code, e.message); + tracing::error!( + "[relayer_sendTransaction] Error response: code={:?}, message={}", + e.code, + e.message + ); // Capture error in Sentry capture_sentry_error("relayer_sendTransaction", &e); Err(e) @@ -1789,12 +1812,15 @@ impl RpcServer { tracing::debug!("[relayer_getStatus] Request params: {}", params_json); } - let request: GetStatusRequest = params - .parse::() - .map_err(|e| { + let request: GetStatusRequest = + params.parse::().map_err(|e| { tracing::warn!("[relayer_getStatus] Failed to parse params: {}", e); let err = jsonrpc_core::Error::invalid_params(e.to_string()); - tracing::error!("[relayer_getStatus] Error response: code={:?}, message={}", err.code, err.message); + tracing::error!( + "[relayer_getStatus] Error response: code={:?}, message={}", + err.code, + err.message + ); capture_sentry_error("relayer_getStatus", &err); err })?; @@ -1802,17 +1828,29 @@ impl RpcServer { match process_get_status(storage, &request, &cfg).await { Ok(response) => { if let Ok(response_json) = serde_json::to_string(&response) { - tracing::info!("[relayer_getStatus] Success response: {}", response_json); + tracing::info!( + "[relayer_getStatus] Success response: {}", + response_json + ); } else { - tracing::info!("[relayer_getStatus] Success response (serialization failed)"); + tracing::info!( + "[relayer_getStatus] Success response (serialization failed)" + ); } serde_json::to_value(response).map_err(|e| { - tracing::error!("[relayer_getStatus] Failed to serialize response: {}", e); + tracing::error!( + "[relayer_getStatus] Failed to serialize response: {}", + e + ); jsonrpc_core::Error::internal_error() }) } Err(e) => { - tracing::error!("[relayer_getStatus] Error response: code={:?}, message={}", e.code, e.message); + tracing::error!( + "[relayer_getStatus] Error response: code={:?}, message={}", + e.code, + e.message + ); capture_sentry_error("relayer_getStatus", &e); Err(e) } @@ -1836,7 +1874,9 @@ impl RpcServer { if let Ok(health_json) = serde_json::to_string(&health) { tracing::info!("[health_check] Success response: {}", health_json); } else { - tracing::info!("[health_check] Success response (serialization failed)"); + tracing::info!( + "[health_check] Success response (serialization failed)" + ); } serde_json::to_value(health).map_err(|e| { tracing::error!("[health_check] Failed to serialize response: {}", e); @@ -1844,7 +1884,11 @@ impl RpcServer { }) } Err(e) => { - tracing::error!("[health_check] Error response: code={:?}, message={}", e.code, e.message); + tracing::error!( + "[health_check] Error response: code={:?}, message={}", + e.code, + e.message + ); capture_sentry_error("health_check", &e); Err(e) } @@ -1864,31 +1908,46 @@ impl RpcServer { } let inputs: Vec = - params - .parse::>() - .map_err(|e| { - tracing::warn!("[relayer_getExchangeRate] Failed to parse params: {}", e); - let err = jsonrpc_core::Error::invalid_params(e.to_string()); - tracing::error!("[relayer_getExchangeRate] Error response: code={:?}, message={}", err.code, err.message); - capture_sentry_error("relayer_getExchangeRate", &err); - err - })?; + params.parse::>().map_err(|e| { + tracing::warn!("[relayer_getExchangeRate] Failed to parse params: {}", e); + let err = jsonrpc_core::Error::invalid_params(e.to_string()); + tracing::error!( + "[relayer_getExchangeRate] Error response: code={:?}, message={}", + err.code, + err.message + ); + capture_sentry_error("relayer_getExchangeRate", &err); + err + })?; let input = inputs.first().ok_or_else(|| { tracing::warn!("[relayer_getExchangeRate] Missing params: expected one object"); - let err = jsonrpc_core::Error::invalid_params("missing params: expected one object"); - tracing::error!("[relayer_getExchangeRate] Error response: code={:?}, message={}", err.code, err.message); + let err = + jsonrpc_core::Error::invalid_params("missing params: expected one object"); + tracing::error!( + "[relayer_getExchangeRate] Error response: code={:?}, message={}", + err.code, + err.message + ); capture_sentry_error("relayer_getExchangeRate", &err); err })?; let payload = build_exchange_rate_response(&cfg, input).await; if let Ok(payload_json) = serde_json::to_string(&payload) { - tracing::info!("[relayer_getExchangeRate] Success response: {}", payload_json); + tracing::info!( + "[relayer_getExchangeRate] Success response: {}", + payload_json + ); } else { - tracing::info!("[relayer_getExchangeRate] Success response (serialization failed)"); + tracing::info!( + "[relayer_getExchangeRate] Success response (serialization failed)" + ); } serde_json::to_value(payload).map_err(|e| { - tracing::error!("[relayer_getExchangeRate] Failed to serialize response: {}", e); + tracing::error!( + "[relayer_getExchangeRate] Failed to serialize response: {}", + e + ); jsonrpc_core::Error::internal_error() }) } @@ -1905,21 +1964,29 @@ impl RpcServer { tracing::debug!("[relayer_getQuote] Request params: {}", params_json); } - let inputs: Vec = params - .parse::>() - .map_err(|e| { + let inputs: Vec = + params.parse::>().map_err(|e| { tracing::warn!("[relayer_getQuote] Failed to parse params: {}", e); let err = jsonrpc_core::Error::invalid_params(e.to_string()); - tracing::error!("[relayer_getQuote] Error response: code={:?}, message={}", err.code, err.message); - capture_sentry_error("relayer_getQuote", &err); - err + tracing::error!( + "[relayer_getQuote] Error response: code={:?}, message={}", + err.code, + err.message + ); + capture_sentry_error("relayer_getQuote", &err); + err })?; let input = inputs.first().ok_or_else(|| { tracing::warn!("[relayer_getQuote] Missing params: expected one object"); - let err = jsonrpc_core::Error::invalid_params("missing params: expected one object"); - tracing::error!("[relayer_getQuote] Error response: code={:?}, message={}", err.code, err.message); - capture_sentry_error("relayer_getQuote", &err); - err + let err = + jsonrpc_core::Error::invalid_params("missing params: expected one object"); + tracing::error!( + "[relayer_getQuote] Error response: code={:?}, message={}", + err.code, + err.message + ); + capture_sentry_error("relayer_getQuote", &err); + err })?; // Minimal realistic quote: estimate gas and use current gas price @@ -1988,17 +2055,29 @@ impl RpcServer { match process_get_capabilities(storage, &cfg).await { Ok(capabilities) => { if let Ok(capabilities_json) = serde_json::to_string(&capabilities) { - tracing::info!("[relayer_getCapabilities] Success response: {}", capabilities_json); + tracing::info!( + "[relayer_getCapabilities] Success response: {}", + capabilities_json + ); } else { - tracing::info!("[relayer_getCapabilities] Success response (serialization failed)"); + tracing::info!( + "[relayer_getCapabilities] Success response (serialization failed)" + ); } serde_json::to_value(capabilities).map_err(|e| { - tracing::error!("[relayer_getCapabilities] Failed to serialize response: {}", e); + tracing::error!( + "[relayer_getCapabilities] Failed to serialize response: {}", + e + ); jsonrpc_core::Error::internal_error() }) } Err(e) => { - tracing::error!("[relayer_getCapabilities] Error response: code={:?}, message={}", e.code, e.message); + tracing::error!( + "[relayer_getCapabilities] Error response: code={:?}, message={}", + e.code, + e.message + ); capture_sentry_error("relayer_getCapabilities", &e); Err(e) } @@ -2017,19 +2096,27 @@ impl RpcServer { tracing::debug!("[relayer_getFeeData] Request params: {}", params_json); } - let inputs: Vec = params - .parse::>() - .map_err(|e| { + let inputs: Vec = + params.parse::>().map_err(|e| { tracing::warn!("[relayer_getFeeData] Failed to parse params: {}", e); let err = jsonrpc_core::Error::invalid_params(e.to_string()); - tracing::error!("[relayer_getFeeData] Error response: code={:?}, message={}", err.code, err.message); - capture_sentry_error("relayer_getFeeData", &err); - err + tracing::error!( + "[relayer_getFeeData] Error response: code={:?}, message={}", + err.code, + err.message + ); + capture_sentry_error("relayer_getFeeData", &err); + err })?; let input = inputs.first().ok_or_else(|| { tracing::warn!("[relayer_getFeeData] Missing params: expected one object"); - let err = jsonrpc_core::Error::invalid_params("missing params: expected one object"); - tracing::error!("[relayer_getFeeData] Error response: code={:?}, message={}", err.code, err.message); + let err = + jsonrpc_core::Error::invalid_params("missing params: expected one object"); + tracing::error!( + "[relayer_getFeeData] Error response: code={:?}, message={}", + err.code, + err.message + ); capture_sentry_error("relayer_getFeeData", &err); err })?; diff --git a/tests/rpc_tests.rs b/tests/rpc_tests.rs index 5c0d222..c145ddf 100644 --- a/tests/rpc_tests.rs +++ b/tests/rpc_tests.rs @@ -27,6 +27,8 @@ fn create_test_config(temp_dir: &TempDir) -> Config { http_cors: "*".to_string(), log_level: "info".to_string(), relayer_private_key: None, + disable_simulation: false, + sentry_dsn: None, } }