From 5333efb08ff10de89d4bdeb086cb34e80da3827e Mon Sep 17 00:00:00 2001 From: amirylm <83904651+amirylm@users.noreply.github.com> Date: Mon, 1 Sep 2025 20:01:10 +0300 Subject: [PATCH 1/2] init_retries flag --- src/adapters/hotshot.rs | 6 ++++++ src/bin/testrpc.rs | 20 +++++++++++++++++--- src/common.rs | 27 ++++++++++++++++++++++++++- src/jrpc.rs | 2 ++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/adapters/hotshot.rs b/src/adapters/hotshot.rs index e05b013..f2bd1dd 100644 --- a/src/adapters/hotshot.rs +++ b/src/adapters/hotshot.rs @@ -79,6 +79,11 @@ impl Adapter for HotshotAdapter { .map(|ip| format!("http://{ip}:{rpc_port}")) .collect::>(); + if rpc_urls.is_empty() { + return Err(TestrpcError::LoadEndpointsError( + "No RPC endpoints found".to_string(), + )); + } Ok(rpc_urls) } @@ -134,6 +139,7 @@ fn parse_endpoints(endpoints: &str) -> Result, TestrpcError> { // "/ip4/*.*.*.*" -> "*.*.*.*" ip_addr[5..].to_string() }) + .filter(|s| !s.is_empty()) .collect::>(); Ok(addrs) diff --git a/src/bin/testrpc.rs b/src/bin/testrpc.rs index f8ca795..fe0edc7 100644 --- a/src/bin/testrpc.rs +++ b/src/bin/testrpc.rs @@ -15,6 +15,8 @@ struct Opts { log_file: Option, #[clap(long, default_value = "debug")] log_level: String, + #[clap(long, default_value = "10")] + init_retries: u32, } #[tokio::main] @@ -33,7 +35,7 @@ async fn main() -> Result<(), common::TestrpcError> { let ctx = Arc::new(ctx::Context::new()); let start = std::time::Instant::now(); - tracing::info!("Starting testrpc with config file: {}", &opts.file); + tracing::info!("XXX1 Starting testrpc with config file: {}", &opts.file); if opts.dry_run { tracing::info!("Dry run, we will not send any RPCs"); @@ -41,7 +43,11 @@ async fn main() -> Result<(), common::TestrpcError> { } let cfg = config::load_config(opts.file.as_str()).unwrap(); - let rpc_urls = if opts.dry_run && opts.gen_mock_rpcs { + let retries = opts.init_retries; + let cfg_rpcs = cfg.clone().rpcs.unwrap_or(Vec::new()); + let rpc_urls = if cfg_rpcs.len() > 0 { + cfg_rpcs + } else if opts.dry_run && opts.gen_mock_rpcs { let num_of_nodes = cfg.num_of_nodes.unwrap_or(4); let mut urls = Vec::new(); for i in 0..num_of_nodes { @@ -49,7 +55,15 @@ async fn main() -> Result<(), common::TestrpcError> { } urls } else { - runner::load_endpoints(cfg.clone()).await.unwrap() + let cfg_clone = cfg.clone(); + let urls = common::retry(retries as usize, std::time::Duration::from_secs(1), + || Box::pin(runner::load_endpoints(cfg_clone.clone())), true).await.unwrap_or(Vec::new()); + if urls.is_empty() { + return Err(common::TestrpcError::LoadEndpointsError( + format!("Failed to load RPC endpoints after {retries} retries"), + )); + } + urls }; if let Some(num_of_nodes) = cfg.num_of_nodes { diff --git a/src/common.rs b/src/common.rs index 2523c91..34f1427 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::time::Duration; +use std::{future::Future, time::Duration, pin::Pin}; use thiserror::Error; #[derive(Error, Debug)] @@ -59,3 +59,28 @@ impl FlowResults { } } } + +/// Retry a given operation a specified number of times with a delay between attempts +/// and optional exponential backoff. +/// Returns Ok(()) if successful, or an error if all retries fail. +pub async fn retry( + retries: usize, + delay: Duration, + mut operation: impl FnMut() -> Pin> + Send>>, + exponential_backoff: bool, +) -> Result { + let mut delay = delay; + for _ in 0..retries { + match operation().await { + Ok(result) => return Ok(result), + Err(e) => { + tracing::warn!("Operation failed: {}. Retrying in {:?}...", e, delay); + tokio::time::sleep(delay).await; + if exponential_backoff { + delay *= 2; + } + } + } + } + Err(TestrpcError::ExecutionError("Max retries reached".to_string())) +} \ No newline at end of file diff --git a/src/jrpc.rs b/src/jrpc.rs index ca8284e..063dec8 100644 --- a/src/jrpc.rs +++ b/src/jrpc.rs @@ -80,6 +80,8 @@ pub async fn send( start_time.elapsed().as_millis() ); + tracing::debug!("Raw RPC response: {:?}", response); + let response: RpcResponse = response .json() .await From f614072357985b7514454b967dd97331266ef403 Mon Sep 17 00:00:00 2001 From: amirylm <83904651+amirylm@users.noreply.github.com> Date: Mon, 1 Sep 2025 20:03:23 +0300 Subject: [PATCH 2/2] fmt + lint --- src/bin/testrpc.rs | 20 +++++++++++++------- src/common.rs | 10 ++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/bin/testrpc.rs b/src/bin/testrpc.rs index fe0edc7..9d3c648 100644 --- a/src/bin/testrpc.rs +++ b/src/bin/testrpc.rs @@ -44,8 +44,8 @@ async fn main() -> Result<(), common::TestrpcError> { let cfg = config::load_config(opts.file.as_str()).unwrap(); let retries = opts.init_retries; - let cfg_rpcs = cfg.clone().rpcs.unwrap_or(Vec::new()); - let rpc_urls = if cfg_rpcs.len() > 0 { + let cfg_rpcs = cfg.clone().rpcs.unwrap_or_default(); + let rpc_urls = if cfg_rpcs.is_empty() { cfg_rpcs } else if opts.dry_run && opts.gen_mock_rpcs { let num_of_nodes = cfg.num_of_nodes.unwrap_or(4); @@ -56,12 +56,18 @@ async fn main() -> Result<(), common::TestrpcError> { urls } else { let cfg_clone = cfg.clone(); - let urls = common::retry(retries as usize, std::time::Duration::from_secs(1), - || Box::pin(runner::load_endpoints(cfg_clone.clone())), true).await.unwrap_or(Vec::new()); + let urls = common::retry( + retries as usize, + std::time::Duration::from_secs(1), + || Box::pin(runner::load_endpoints(cfg_clone.clone())), + true, + ) + .await + .unwrap_or(Vec::new()); if urls.is_empty() { - return Err(common::TestrpcError::LoadEndpointsError( - format!("Failed to load RPC endpoints after {retries} retries"), - )); + return Err(common::TestrpcError::LoadEndpointsError(format!( + "Failed to load RPC endpoints after {retries} retries" + ))); } urls }; diff --git a/src/common.rs b/src/common.rs index 34f1427..be8de6d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{future::Future, time::Duration, pin::Pin}; +use std::{future::Future, pin::Pin, time::Duration}; use thiserror::Error; #[derive(Error, Debug)] @@ -60,7 +60,7 @@ impl FlowResults { } } -/// Retry a given operation a specified number of times with a delay between attempts +/// Retry a given operation a specified number of times with a delay between attempts /// and optional exponential backoff. /// Returns Ok(()) if successful, or an error if all retries fail. pub async fn retry( @@ -82,5 +82,7 @@ pub async fn retry( } } } - Err(TestrpcError::ExecutionError("Max retries reached".to_string())) -} \ No newline at end of file + Err(TestrpcError::ExecutionError( + "Max retries reached".to_string(), + )) +}