From 13d994ca9473afcca99709ec164cf1207efd7726 Mon Sep 17 00:00:00 2001 From: bansalayush247 Date: Wed, 23 Jul 2025 18:04:02 +0530 Subject: [PATCH 1/7] Add multi-upstream support with dynamic load balancing and enhanced logging - Enhance configuration handling for hashrate distribution and improve router's multi-upstream support - Refactor logging and improve share validation - Clean up formatting and remove redundant code in main and downstream modules - Fix formatting in test module by removing unnecessary parameter - Update downstream struct passing value - Remove unused Mutex import and simplify weighted distribution initialization in Router - Fix formatting and improve logging messages in main and router modules; update TODO comments in notify module - Refactor imports in router module for improved organization and readability - Enhance proxy initialization and downstream handling for multi-upstream mode; improve logging format in router - Enhance logging by including pool address in various components --- .gitignore | 2 + src/config.rs | 93 ++++ src/jd_client/job_declarator/mod.rs | 2 +- src/jd_client/mod.rs | 41 +- src/main.rs | 418 ++++++++++++++---- src/minin_pool_connection/mod.rs | 20 +- src/router/mod.rs | 122 +++++ src/share_accounter/mod.rs | 14 +- .../downstream/accept_connection.rs | 3 + src/translator/downstream/diff_management.rs | 33 +- src/translator/downstream/downstream.rs | 30 +- src/translator/downstream/notify.rs | 3 +- .../downstream/receive_from_downstream.rs | 7 + src/translator/mod.rs | 4 + src/translator/upstream/upstream.rs | 13 +- 15 files changed, 667 insertions(+), 138 deletions(-) diff --git a/.gitignore b/.gitignore index 54466f5b..68b3e7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target +.env +config.toml \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 47fc2256..02043ad9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -45,6 +45,14 @@ struct Args { monitor: bool, #[clap(long, short = 'u')] auto_update: bool, + #[clap(long = "hashrate-dist", value_delimiter = ',')] + hashrate_distribution: Option>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct PoolConfig { + pub address: SocketAddr, + pub weight: f32, } #[derive(Serialize, Deserialize)] @@ -53,6 +61,7 @@ struct ConfigFile { tp_address: Option, pool_addresses: Option>, test_pool_addresses: Option>, + hashrate_distribution: Option>, interval: Option, delay: Option, downstream_hashrate: Option, @@ -71,6 +80,7 @@ pub struct Configuration { tp_address: Option, pool_addresses: Option>, test_pool_addresses: Option>, + hashrate_distribution: Option>, interval: u64, delay: u64, downstream_hashrate: f32, @@ -161,6 +171,76 @@ impl Configuration { CONFIG.auto_update } + pub fn hashrate_distribution() -> Option> { + CONFIG.hashrate_distribution.clone() + } + + pub fn pool_configs() -> Option> { + let hashrate_dist = Self::hashrate_distribution(); + if let Some(distribution) = hashrate_dist { + // Get pool addresses (considering test addresses) + let addresses = Self::pool_address().unwrap_or_default(); + + if addresses.is_empty() { + warn!("No pool addresses provided for hashrate distribution"); + return None; + } + + let mut pools = Vec::new(); + let total_dist = distribution.iter().sum::(); + + if addresses.len() != distribution.len() { + warn!( + "Hashrate distribution length ({}) doesn't match pools ({}). Normalizing.", + distribution.len(), + addresses.len() + ); + } + + let min_len = addresses.len().min(distribution.len()); + for i in 0..min_len { + let weight = if total_dist > 0.0 { + distribution[i] / total_dist + } else { + 1.0 / addresses.len() as f32 + }; + pools.push(PoolConfig { + address: addresses[i], + weight, + }); + } + + // Assign 0.0 weight to extra addresses (if any) + for addr in addresses.into_iter().skip(min_len) { + pools.push(PoolConfig { + address: addr, + weight: 0.0, + }); + } + + Some(Self::normalize_pool_weights(pools)) + } else { + None + } + } + + /// Normalize weights to sum to 1.0 + fn normalize_pool_weights(mut pools: Vec) -> Vec { + let total_weight: f32 = pools.iter().map(|p| p.weight).sum(); + if total_weight <= 0.0 { + warn!("Total weight is zero or negative. Assigning equal weights."); + let equal_weight = 1.0 / pools.len() as f32; + for pool in &mut pools { + pool.weight = equal_weight; + } + } else { + for pool in &mut pools { + pool.weight /= total_weight; + } + } + pools + } + // Loads config from CLI, file, or env vars with precedence: CLI > file > env. fn load_config() -> Self { let args = Args::parse(); @@ -173,6 +253,7 @@ impl Configuration { tp_address: None, pool_addresses: None, test_pool_addresses: None, + hashrate_distribution: None, interval: None, delay: None, downstream_hashrate: None, @@ -325,6 +406,17 @@ impl Configuration { || config.auto_update.unwrap_or(true) || std::env::var("AUTO_UPDATE").is_ok(); + let hashrate_distribution = args + .hashrate_distribution + .or(config.hashrate_distribution) + .or_else(|| { + std::env::var("HASHRATE_DISTRIBUTION").ok().and_then(|s| { + s.split(',') + .map(|x| x.trim().parse::().ok()) + .collect::>>() + }) + }); + Configuration { token, tp_address, @@ -341,6 +433,7 @@ impl Configuration { api_server_port, monitor, auto_update, + hashrate_distribution, } } } diff --git a/src/jd_client/job_declarator/mod.rs b/src/jd_client/job_declarator/mod.rs index fad28dad..de7fbf44 100644 --- a/src/jd_client/job_declarator/mod.rs +++ b/src/jd_client/job_declarator/mod.rs @@ -94,7 +94,7 @@ impl JobDeclarator { SetupConnectionHandler::setup(&mut receiver, &mut sender, address).await?; if should_log_when_connected { - info!("JD CONNECTED"); + info!("JD CONNECTED to {}", address); } let min_extranonce_size = crate::MIN_EXTRANONCE_SIZE; diff --git a/src/jd_client/mod.rs b/src/jd_client/mod.rs index 79fd87fa..0e693dbd 100644 --- a/src/jd_client/mod.rs +++ b/src/jd_client/mod.rs @@ -55,11 +55,12 @@ pub async fn start( sender: tokio::sync::mpsc::Sender>, up_receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, + pool_address: SocketAddr, ) -> Option { - // This will not work when we implement support for multiple upstream + info!("JD client starting for pool address: {}", pool_address); IS_CUSTOM_JOB_SET.store(true, std::sync::atomic::Ordering::Release); IS_NEW_TEMPLATE_HANDLED.store(true, std::sync::atomic::Ordering::Release); - initialize_jd(receiver, sender, up_receiver, up_sender).await + initialize_jd(receiver, sender, up_receiver, up_sender, pool_address).await } async fn initialize_jd( @@ -67,12 +68,15 @@ async fn initialize_jd( sender: tokio::sync::mpsc::Sender>, up_receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, + pool_address: SocketAddr, ) -> Option { + info!("Initializing JD client for pool address: {}", pool_address); + let task_manager = TaskManager::initialize(); let abortable = match task_manager.safe_lock(|t| t.get_aborter()) { Ok(abortable) => abortable?, Err(e) => { - error!("Jdc task manager mutex corrupt: {e}"); + error!("Jdc task manager mutex corrupt: {e} (pool: {})", pool_address); return None; } }; @@ -87,7 +91,7 @@ async fn initialize_jd( { Ok(upstream) => upstream, Err(e) => { - error!("Failed to instantiate new Upstream: {e}"); + error!("Failed to instantiate new Upstream for pool {}: {e}", pool_address); drop(abortable); return None; } @@ -98,7 +102,7 @@ async fn initialize_jd( Ok(tp_address) => tp_address .expect("Unreachable code, jdc is not instantiated when TP_ADDRESS not present"), Err(e) => { - error!("TP_ADDRESS mutex corrupted: {e}"); + error!("TP_ADDRESS mutex corrupted for pool {}: {e}", pool_address); drop(abortable); return None; } @@ -127,7 +131,7 @@ async fn initialize_jd( match JobDeclarator::new(address, auth_pub_k.into_bytes(), upstream.clone(), true).await { Ok(c) => c, Err(e) => { - error!("Failed to intialize Jd: {e}"); + error!("Failed to initialize JobDeclarator for pool {}: {e}", pool_address); drop(abortable); return None; } @@ -138,7 +142,8 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add job declarator task{}", + "Task manager failed while trying to add job declarator task for pool {}: {}", + pool_address, error::Error::TaskManagerFailed ); drop(abortable); @@ -157,7 +162,7 @@ async fn initialize_jd( { Ok(abortable) => abortable, Err(e) => { - error!("Can not start downstream mining node: {e}"); + error!("Cannot start downstream mining node for pool {}: {e}", pool_address); ProxyState::update_downstream_state(DownstreamType::JdClientMiningDownstream); return None; } @@ -167,7 +172,8 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add mining downstream task{}", + "Task manager failed while trying to add mining downstream task for pool {}: {}", + pool_address, error::Error::TaskManagerFailed ); drop(abortable); @@ -177,7 +183,7 @@ async fn initialize_jd( .safe_lock(|u| u.downstream = Some(donwstream.clone())) .is_err() { - error!("Upstream mutex failed"); + error!("Upstream mutex failed for pool {}", pool_address); drop(abortable); // drop all tasks initailzed upto this point return None; }; @@ -187,7 +193,7 @@ async fn initialize_jd( match mining_upstream::Upstream::parse_incoming(upstream.clone(), up_receiver).await { Ok(abortable) => abortable, Err(e) => { - error!("Failed to get jdc upstream abortable: {e}"); + error!("Failed to get jdc upstream abortable for pool {}: {e}", pool_address); drop(abortable); // drop all tasks initailzed upto this point return None; } @@ -197,7 +203,8 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add mining upstream task{}", + "Task manager failed while trying to add mining upstream task for pool {}: {}", + pool_address, error::Error::TaskManagerFailed ); drop(abortable); // drop all tasks initailzed upto this point @@ -218,13 +225,13 @@ async fn initialize_jd( { Ok(abortable) => abortable, Err(_) => { - info!("Dropping jd abortable"); - eprintln!("TP is unreachable, the proxy is in not in JD mode"); + info!("Dropping jd abortable for pool {}", pool_address); + eprintln!("TP is unreachable, the proxy is not in JD mode"); drop(abortable); // Temporaily set TP_ADDRESS to None so that proxy can restart without it. // that means we will start mining without jd if crate::TP_ADDRESS.safe_lock(|tp| *tp = None).is_err() { - error!("TP_ADDRESS mutex corrupt"); + error!("TP_ADDRESS mutex corrupt for pool {}", pool_address); return None; }; tokio::spawn(retry_connection(tp_address)); @@ -237,12 +244,14 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add template receiver task{}", + "Task manager failed while trying to add template receiver task for pool {}: {}", + pool_address, error::Error::TaskManagerFailed ); drop(abortable); return None; }; + info!("JD client successfully started for pool address: {}", pool_address); Some(abortable) } diff --git a/src/main.rs b/src/main.rs index 0fcb4bbf..9d893775 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use key_utils::Secp256k1PublicKey; use lazy_static::lazy_static; use proxy_state::{PoolState, ProxyState, TpState, TranslatorState}; use self_update::{backends, cargo_crate_version, update::UpdateStatus, TempDir}; +use std::sync::Arc; use std::{net::SocketAddr, time::Duration}; use tokio::sync::mpsc::channel; use tracing::{debug, error, info, warn}; @@ -105,7 +106,12 @@ async fn main() { let mut router = router::Router::new(pool_addresses, auth_pub_k, None, None); let epsilon = Duration::from_millis(30_000); - let best_upstream = router.select_pool_connect().await; + let best_upstream = if router.is_multi_upstream() { + None + } else { + router.select_pool_connect().await + }; + initialize_proxy(&mut router, best_upstream, epsilon).await; info!("exiting"); tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; @@ -118,28 +124,244 @@ async fn initialize_proxy( ) { loop { let stats_sender = api::stats::StatsSender::new(); - let (send_to_pool, recv_from_pool, pool_connection_abortable) = - match router.connect_pool(pool_addr).await { - Ok(connection) => connection, - Err(_) => { - error!("No upstream available. Retrying in 5 seconds..."); - warn!( - "Please make sure the your token {} is correct", - Configuration::token().expect("Token is not set") - ); - let secs = 5; - tokio::time::sleep(Duration::from_secs(secs)).await; - // Restart loop, esentially restarting proxy - continue; + let is_multi_upstream = router.is_multi_upstream(); + + if is_multi_upstream { + // Multi-upstream: connect to all pools and start components for each + let pool_connections = router.connect_all_pools().await; + + // Handle connection results + let mut any_success = false; + let mut abort_handles = Vec::new(); + + // Create per-pool translator stacks + let mut pool_translators = Vec::new(); + + // For each pool, create a complete translator stack + for (pool_id, pool_addr, result) in pool_connections { + match result { + Ok((send_to_pool, recv_from_pool, pool_connection_abortable)) => { + info!( + "Successfully connected to pool {} (ID: {})", + pool_addr, pool_id + ); + any_success = true; + + // Create per-pool downstream channels + let (pool_downs_tx, pool_downs_rx) = channel(10); + + // Create per-pool translator channels + let (translator_up_tx, mut translator_up_rx) = channel(10); + + // Start translator for this specific pool + let translator_abortable = match translator::start( + pool_downs_rx, + translator_up_tx, + stats_sender.clone(), + Arc::new(router.clone()), + pool_addr, + ) + .await + { + Ok(abortable) => abortable, + Err(e) => { + error!("Impossible to initialize translator for pool {}: {e}", pool_addr); + continue; + } + }; + + // Get translator channels for this pool + let (jdc_to_translator_sender, jdc_from_translator_receiver, _) = + match translator_up_rx.recv().await { + Some(val) => val, + None => { + error!( + "Translator failed before initialization for pool {}", + pool_addr + ); + continue; + } + }; + + // Set up JDC and share accounter for this pool + let (from_jdc_to_share_accounter_send, from_jdc_to_share_accounter_recv) = + channel(10); + let (from_share_accounter_to_jdc_send, from_share_accounter_to_jdc_recv) = + channel(10); + + let tp = match TP_ADDRESS.safe_lock(|tp| tp.clone()) { + Ok(tp) => tp, + Err(e) => { + error!("TP_ADDRESS Mutex Corrupted: {e}"); + continue; + } + }; + + let jdc_abortable: Option; + let share_accounter_abortable; + if let Some(_tp_addr) = tp { + jdc_abortable = jd_client::start( + jdc_from_translator_receiver, + jdc_to_translator_sender, + from_share_accounter_to_jdc_recv, + from_jdc_to_share_accounter_send, + pool_addr, + ) + .await; + if jdc_abortable.is_none() { + ProxyState::update_tp_state(TpState::Down); + }; + share_accounter_abortable = match share_accounter::start( + from_jdc_to_share_accounter_recv, + from_share_accounter_to_jdc_send, + recv_from_pool, + send_to_pool.clone(), + pool_addr, + ) + .await + { + Ok(abortable) => abortable, + Err(_) => { + error!( + "Failed to start share_accounter for pool {}", + pool_addr + ); + continue; + } + } + } else { + jdc_abortable = None; + share_accounter_abortable = match share_accounter::start( + jdc_from_translator_receiver, + jdc_to_translator_sender, + recv_from_pool, + send_to_pool.clone(), + pool_addr, + ) + .await + { + Ok(abortable) => abortable, + Err(_) => { + error!( + "Failed to start share_accounter for pool {}", + pool_addr + ); + continue; + } + }; + } + + // Store pool translator info for downstream distribution + pool_translators.push((pool_id, pool_addr, pool_downs_tx)); + + // Collect abort handles for this pool + abort_handles.push(( + pool_connection_abortable, + format!("pool_connection_{}", pool_id), + )); + abort_handles.push(( + translator_abortable, + format!("translator_{}", pool_id), + )); + abort_handles.push(( + share_accounter_abortable, + format!("share_accounter_{}", pool_id), + )); + if let Some(jdc_handle) = jdc_abortable { + abort_handles.push((jdc_handle, format!("jdc_{}", pool_id))); + } + } + Err(e) => { + error!( + "Failed to connect to pool {} (ID: {}): {:?}", + pool_addr, pool_id, e + ); + } } - }; + } + + // Start SV1 ingress with custom downstream distribution + let (downs_sv1_tx, downs_sv1_rx) = channel(10); + let sv1_ingress_abortable = + ingress::sv1_ingress::start_listen_for_downstream(downs_sv1_tx); + abort_handles.push((sv1_ingress_abortable, "sv1_ingress".to_string())); + + // Start downstream distribution task that routes miners to appropriate pools + let router_for_distribution = router.clone(); + let pool_translators_for_distribution = pool_translators.clone(); + let distribution_task = tokio::spawn(async move { + let mut recv = downs_sv1_rx; + + while let Some((send_to_downstream, recv_from_downstream, ip_addr)) = recv.recv().await { + info!("New downstream connection from {}", ip_addr); + + // Calling router to assign pool + if let Some(assigned_pool) = router_for_distribution.assign_miner_to_pool().await { + // Send to the appropriate translator + if let Some((_, _, pool_downs_tx)) = pool_translators_for_distribution.iter().find(|(_, addr, _)| *addr == assigned_pool) { + if let Err(e) = pool_downs_tx.send((send_to_downstream, recv_from_downstream, ip_addr)).await { + error!("Failed to send downstream connection to pool {}: {}", assigned_pool, e); + } + } + } else { + error!("Could not assign miner from {} to any pool", ip_addr); + } + } + }); + abort_handles.push((distribution_task.into(), "downstream_distribution".to_string())); + + if !any_success { + error!("No upstream available. Retrying in 5 seconds..."); + warn!( + "Please make sure the your token {} is correct", + Configuration::token().expect("Token is not set") + ); + let secs = 5; + tokio::time::sleep(Duration::from_secs(secs)).await; + // Restart loop, esentially restarting proxy - let (downs_sv1_tx, downs_sv1_rx) = channel(10); - let sv1_ingress_abortable = ingress::sv1_ingress::start_listen_for_downstream(downs_sv1_tx); + continue; + } - let (translator_up_tx, mut translator_up_rx) = channel(10); - let translator_abortable = - match translator::start(downs_sv1_rx, translator_up_tx, stats_sender.clone()).await { + let server_handle = tokio::spawn(api::start(router.clone(), stats_sender)); + match monitor(router, abort_handles, epsilon, server_handle).await { + Reconnect::NewUpstream(_) | Reconnect::NoUpstream => { + ProxyState::update_proxy_state_up(); + continue; + } + }; + } else { + // Single-upstream: use the best pool passed in (or re-select if needed) + let (send_to_pool, recv_from_pool, pool_connection_abortable) = + match router.connect_pool(pool_addr).await { + Ok(connection) => connection, + Err(_) => { + error!("No upstream available. Retrying in 5 seconds..."); + warn!( + "Please make sure the your token {} is correct", + Configuration::token().expect("Token is not set") + ); + let secs = 5; + tokio::time::sleep(Duration::from_secs(secs)).await; + // Restart loop, esentially restarting proxy + continue; + } + }; + + let (downs_sv1_tx, downs_sv1_rx) = channel(10); + let sv1_ingress_abortable = + ingress::sv1_ingress::start_listen_for_downstream(downs_sv1_tx); + + let (translator_up_tx, mut translator_up_rx) = channel(10); + let translator_abortable = match translator::start( + downs_sv1_rx, + translator_up_tx, + stats_sender.clone(), + Arc::new(router.clone()), + pool_addr.expect("Best latency pool address should be available"), + ) + .await + { Ok(abortable) => abortable, Err(e) => { error!("Impossible to initialize translator: {e}"); @@ -150,90 +372,94 @@ async fn initialize_proxy( } }; - let (from_jdc_to_share_accounter_send, from_jdc_to_share_accounter_recv) = channel(10); - let (from_share_accounter_to_jdc_send, from_share_accounter_to_jdc_recv) = channel(10); - let (jdc_to_translator_sender, jdc_from_translator_receiver, _) = translator_up_rx - .recv() - .await - .expect("Translator failed before initialization"); - - let jdc_abortable: Option; - let share_accounter_abortable; - let tp = match TP_ADDRESS.safe_lock(|tp| tp.clone()) { - Ok(tp) => tp, - Err(e) => { - error!("TP_ADDRESS Mutex Corrupted: {e}"); - return; - } - }; - - if let Some(_tp_addr) = tp { - jdc_abortable = jd_client::start( - jdc_from_translator_receiver, - jdc_to_translator_sender, - from_share_accounter_to_jdc_recv, - from_jdc_to_share_accounter_send, - ) - .await; - if jdc_abortable.is_none() { - ProxyState::update_tp_state(TpState::Down); - }; - share_accounter_abortable = match share_accounter::start( - from_jdc_to_share_accounter_recv, - from_share_accounter_to_jdc_send, - recv_from_pool, - send_to_pool, - ) - .await - { - Ok(abortable) => abortable, - Err(_) => { - error!("Failed to start share_accounter"); + let (from_jdc_to_share_accounter_send, from_jdc_to_share_accounter_recv) = channel(10); + let (from_share_accounter_to_jdc_send, from_share_accounter_to_jdc_recv) = channel(10); + let (jdc_to_translator_sender, jdc_from_translator_receiver, _) = translator_up_rx + .recv() + .await + .expect("Translator failed before initialization"); + + let jdc_abortable: Option; + let share_accounter_abortable; + let tp = match TP_ADDRESS.safe_lock(|tp| tp.clone()) { + Ok(tp) => tp, + Err(e) => { + error!("TP_ADDRESS Mutex Corrupted: {e}"); return; } - } - } else { - jdc_abortable = None; + }; - share_accounter_abortable = match share_accounter::start( - jdc_from_translator_receiver, - jdc_to_translator_sender, - recv_from_pool, - send_to_pool, - ) - .await - { - Ok(abortable) => abortable, - Err(_) => { - error!("Failed to start share_accounter"); - return; + if let Some(_tp_addr) = tp { + jdc_abortable = jd_client::start( + jdc_from_translator_receiver, + jdc_to_translator_sender, + from_share_accounter_to_jdc_recv, + from_jdc_to_share_accounter_send, + pool_addr.expect("Best latency pool address should be available"), + ) + .await; + if jdc_abortable.is_none() { + ProxyState::update_tp_state(TpState::Down); + }; + share_accounter_abortable = match share_accounter::start( + from_jdc_to_share_accounter_recv, + from_share_accounter_to_jdc_send, + recv_from_pool, + send_to_pool, + pool_addr.expect("Best latency pool address should be available"), + ) + .await + { + Ok(abortable) => abortable, + Err(_) => { + error!("Failed to start share_accounter for pool {:?}", pool_addr); + return; + } } + } else { + jdc_abortable = None; + + share_accounter_abortable = match share_accounter::start( + jdc_from_translator_receiver, + jdc_to_translator_sender, + recv_from_pool, + send_to_pool, + pool_addr.expect("Best latency pool address should be available"), + ) + .await + { + Ok(abortable) => abortable, + Err(_) => { + error!("Failed to start share_accounter for pool {:?}", pool_addr); + return; + } + }; }; - }; - // Collecting all abort handles - let mut abort_handles = vec![ - (pool_connection_abortable, "pool_connection".to_string()), - (sv1_ingress_abortable, "sv1_ingress".to_string()), - (translator_abortable, "translator".to_string()), - (share_accounter_abortable, "share_accounter".to_string()), - ]; - if let Some(jdc_handle) = jdc_abortable { - abort_handles.push((jdc_handle, "jdc".to_string())); - } - let server_handle = tokio::spawn(api::start(router.clone(), stats_sender)); - match monitor(router, abort_handles, epsilon, server_handle).await { - Reconnect::NewUpstream(new_pool_addr) => { - ProxyState::update_proxy_state_up(); - pool_addr = Some(new_pool_addr); - continue; + // Collecting all abort handles + let mut abort_handles = vec![ + (pool_connection_abortable, "pool_connection".to_string()), + (sv1_ingress_abortable, "sv1_ingress".to_string()), + (translator_abortable, "translator".to_string()), + (share_accounter_abortable, "share_accounter".to_string()), + ]; + if let Some(jdc_handle) = jdc_abortable { + abort_handles.push((jdc_handle, "jdc".to_string())); } - Reconnect::NoUpstream => { - ProxyState::update_proxy_state_up(); - pool_addr = None; - continue; + let server_handle = tokio::spawn(api::start(router.clone(), stats_sender)); + match monitor(router, abort_handles, epsilon, server_handle).await { + Reconnect::NewUpstream(new_pool_addr) => { + ProxyState::update_proxy_state_up(); + pool_addr = Some(new_pool_addr); + continue; + } + Reconnect::NoUpstream => { + ProxyState::update_proxy_state_up(); + pool_addr = None; + continue; + } } - }; + } } } diff --git a/src/minin_pool_connection/mod.rs b/src/minin_pool_connection/mod.rs index 3565a94d..8c8aa94b 100644 --- a/src/minin_pool_connection/mod.rs +++ b/src/minin_pool_connection/mod.rs @@ -90,12 +90,12 @@ pub async fn connect_pool( let (send_to_down, recv_from_down) = tokio::sync::mpsc::channel(10); let (send_from_down, recv_to_up) = tokio::sync::mpsc::channel(10); - let relay_up_task = relay_up(recv_to_up, sender); + let relay_up_task = relay_up(recv_to_up, sender, address); TaskManager::add_sv2_relay_up(task_manager.clone(), relay_up_task) .await .map_err(|_| Error::MiningPoolTaskManagerFailed)?; - let relay_down_task = relay_down(receiver, send_to_down); + let relay_down_task = relay_down(receiver, send_to_down, address); TaskManager::add_sv2_relay_down(task_manager.clone(), relay_down_task) .await .map_err(|_| Error::MiningPoolTaskManagerFailed)?; @@ -108,6 +108,7 @@ pub async fn connect_pool( pub fn relay_up( mut recv: Receiver>, send: Sender, + pool_address: SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = recv.recv().await { @@ -115,12 +116,12 @@ pub fn relay_up( if let Ok(std_frame) = std_frame { let either_frame: EitherFrame = std_frame.into(); if send.send(either_frame).await.is_err() { - error!("Mining upstream failed"); + error!("Failed to send message to pool {}", pool_address); ProxyState::update_pool_state(PoolState::Down); break; }; } else { - panic!("Internal Mining downstream try to send invalid message"); + panic!("Internal Mining downstream tried to send invalid message to pool {}", pool_address); } } }); @@ -130,6 +131,7 @@ pub fn relay_up( pub fn relay_down( mut recv: Receiver, send: Sender>, + pool_address: SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = recv.recv().await { @@ -144,25 +146,25 @@ pub fn relay_down( if let Ok(msg) = msg { let msg = msg.into_static(); if send.send(msg).await.is_err() { - error!("Internal Mining downstream not available"); + error!("Internal Mining downstream not available for pool {}", pool_address); // Update Proxy state to reflect Internal inconsistency ProxyState::update_inconsistency(Some(1)); } } else { - error!("Mining Upstream send non Mining message. Disconnecting"); + error!("Pool {} sent non-Mining message. Disconnecting", pool_address); break; } } else { - error!("Mining Upstream send invalid message no header. Disconnecting"); + error!("Pool {} sent invalid message with no header. Disconnecting", pool_address); break; } } else { - error!("Mining Upstream down."); + error!("Pool {} connection down", pool_address); break; } } - error!("Failed to receive msg from Pool"); + error!("Failed to receive msg from Pool {}", pool_address); ProxyState::update_pool_state(PoolState::Down); }); task.into() diff --git a/src/router/mod.rs b/src/router/mod.rs index 95ca34bb..e1d5147c 100644 --- a/src/router/mod.rs +++ b/src/router/mod.rs @@ -10,6 +10,8 @@ use demand_sv2_connection::noise_connection_tokio::Connection; use key_utils::Secp256k1PublicKey; use noise_sv2::Initiator; use roles_logic_sv2::{common_messages_sv2::SetupConnection, parsers::Mining}; +use std::sync::atomic::{AtomicU32, Ordering}; + use tokio::{ net::TcpStream, sync::{ @@ -20,11 +22,14 @@ use tokio::{ use tracing::{error, info}; use crate::{ + config::{Configuration, PoolConfig}, minin_pool_connection::{self, get_mining_setup_connection_msg, mining_setup_connection}, shared::utils::AbortOnDrop, }; /// Router handles connection to Multiple upstreams. +use std::sync::Arc; + #[derive(Clone)] pub struct Router { pool_addresses: Vec, @@ -34,6 +39,8 @@ pub struct Router { timer: Option, latency_tx: watch::Sender>, pub latency_rx: watch::Receiver>, + multi_pool_configs: Option>, + pub weighted_dist: Option>>, } impl Router { @@ -49,6 +56,12 @@ impl Router { timer: Option, ) -> Self { let (latency_tx, latency_rx) = watch::channel(None); + + let multi_pool_configs = Configuration::pool_configs(); + let weighted_dist = multi_pool_configs.as_ref().map(|configs| Arc::new( + (0..configs.len()).map(|_| AtomicU32::new(0)).collect(), + )); + Self { pool_addresses, current_pool: None, @@ -57,6 +70,62 @@ impl Router { timer, latency_tx, latency_rx, + multi_pool_configs, + weighted_dist, + } + } + + pub fn is_multi_upstream(&self) -> bool { + self.multi_pool_configs + .as_ref() + .is_some_and(|configs| configs.len() > 1) + } + + pub async fn assign_miner_to_pool(&self) -> Option { + if self.is_multi_upstream() { + if let Some(configs) = &self.multi_pool_configs { + let assignments = self.weighted_dist.as_ref().unwrap(); + let total_miners: u32 = assignments.iter().map(|a| a.load(Ordering::Relaxed)).sum(); + + let mut best_pool_id = 0; + let mut best_difference = f32::NEG_INFINITY; + + for (pool_id, config) in configs.iter().enumerate() { + let current_count = assignments[pool_id].load(Ordering::Relaxed); + let current_ratio = if total_miners == 0 { + 0.0 + } else { + current_count as f32 / total_miners as f32 + }; + let target_ratio = config.weight; + let difference = target_ratio - current_ratio; + + if difference > best_difference { + best_difference = difference; + best_pool_id = pool_id; + } + } + + // Assign to most under-represented pool + let new_count = assignments[best_pool_id].fetch_add(1, Ordering::Relaxed) + 1; + let total_after = total_miners + 1; + let selected_pool = configs[best_pool_id].address; + + info!( + "✅ Miner {} → Pool {} ({}) [Pool:{}, Total:{}]", + total_after, best_pool_id, selected_pool, new_count, total_after + ); + + Some(selected_pool) + } else { + // Fallback to existing logic + self.current_pool + .or_else(|| self.pool_addresses.first().copied()) + } + } else { + // Single pool fallback + self.current_pool + .or_else(|| self.pool_addresses.first().copied()) } } @@ -247,6 +316,10 @@ impl Router { /// Checks for faster upstream switch to it if found pub async fn monitor_upstream(&mut self, epsilon: Duration) -> Option { + if self.is_multi_upstream() { + info!("Multi-upstream mode: No monitoring required"); + return None; + } if let Some(best_pool) = self.select_pool_monitor(epsilon).await { if Some(best_pool) != self.current_pool { info!("Switching to faster upstreamn {:?}", best_pool); @@ -257,6 +330,55 @@ impl Router { } None } + + /// Connect to all configured pools + pub async fn connect_all_pools( + &self, + ) -> Vec<( + usize, + SocketAddr, + Result< + ( + tokio::sync::mpsc::Sender>, + tokio::sync::mpsc::Receiver>, + crate::shared::utils::AbortOnDrop, + ), + crate::minin_pool_connection::errors::Error, + >, + )> { + if let Some(configs) = &self.multi_pool_configs { + let mut connections = Vec::new(); + for (id, config) in configs.iter().enumerate() { + info!( + "Connecting to pool {} with weight {}", + config.address, config.weight + ); + let result = self.clone().connect_pool(Some(config.address)).await; + connections.push((id, config.address, result)); + } + connections + } else { + Vec::new() + } + } + + /// Removes a miner from the assigned pool, allowing for rebalancing + pub async fn remove_miner_from_pool(&self, pool_address: SocketAddr) { + if let Some(configs) = &self.multi_pool_configs { + if let Some(pool_id) = configs.iter().position(|c| c.address == pool_address) { + let assignments = self.weighted_dist.as_ref().unwrap(); + let old = assignments[pool_id].load(Ordering::Relaxed); + if old > 0 { + let new = assignments[pool_id].fetch_sub(1, Ordering::Relaxed) - 1; + let total: u32 = assignments.iter().map(|a| a.load(Ordering::Relaxed)).sum(); + info!( + "Miner disconnected from Pool {}. New: {}/{} total", + pool_id, new, total + ); + } + } + } + } } /// Track latencies for various stages of pool connection setup. diff --git a/src/share_accounter/mod.rs b/src/share_accounter/mod.rs index 08004da6..3d738682 100644 --- a/src/share_accounter/mod.rs +++ b/src/share_accounter/mod.rs @@ -22,6 +22,7 @@ pub async fn start( sender: tokio::sync::mpsc::Sender>, up_receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, + pool_address: std::net::SocketAddr, ) -> Result { let task_manager = TaskManager::initialize(); let shares_sent_up = Arc::new(DashMap::with_capacity(100)); @@ -30,12 +31,12 @@ pub async fn start( .map_err(|_| Error::ShareAccounterTaskManagerMutexCorrupted)? .ok_or(Error::ShareAccounterTaskManagerError)?; - let relay_up_task = relay_up(receiver, up_sender, shares_sent_up.clone()); + let relay_up_task = relay_up(receiver, up_sender, shares_sent_up.clone(), pool_address); TaskManager::add_relay_up(task_manager.clone(), relay_up_task) .await .map_err(|_| Error::ShareAccounterTaskManagerError)?; - let relay_down_task = relay_down(up_receiver, sender, shares_sent_up.clone()); + let relay_down_task = relay_down(up_receiver, sender, shares_sent_up.clone(), pool_address); TaskManager::add_relay_down(task_manager.clone(), relay_down_task) .await .map_err(|_| Error::ShareAccounterTaskManagerError)?; @@ -51,6 +52,7 @@ fn relay_up( mut receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, shares_sent_up: Arc>, + pool_address: std::net::SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = receiver.recv().await { @@ -65,6 +67,7 @@ fn relay_up( }; let msg = PoolExtMessages::Mining(msg); if up_sender.send(msg).await.is_err() { + error!("Share accounter: Failed to send message to pool {}", pool_address); break; } } @@ -76,6 +79,7 @@ fn relay_down( mut up_receiver: tokio::sync::mpsc::Receiver>, sender: tokio::sync::mpsc::Sender>, shares_sent_up: Arc>, + pool_address: std::net::SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = up_receiver.recv().await { @@ -88,7 +92,7 @@ fn relay_down( Some(shares) => shares.1, // job_id doesn't exist None => { - error!("Pool sent invalid share success"); + error!("Pool {} sent invalid share success for job {}", pool_address, job_id); // Set global pool state to Down ProxyState::update_pool_state(PoolState::Down); return; @@ -110,13 +114,13 @@ fn relay_down( } PoolExtMessages::Mining(msg) => { if let Err(e) = sender.send(msg).await { - error!("{e}"); + error!("Share accounter: Failed to send message from pool {} to downstream: {}", pool_address, e); ProxyState::update_share_accounter_state(ShareAccounterState::Down); break; } } _ => { - error!("Pool send unexpected message on mining connection"); + error!("Pool {} sent unexpected message on mining connection", pool_address); ProxyState::update_pool_state(PoolState::Down); break; } diff --git a/src/translator/downstream/accept_connection.rs b/src/translator/downstream/accept_connection.rs index 7aa179c3..9e8e88f7 100644 --- a/src/translator/downstream/accept_connection.rs +++ b/src/translator/downstream/accept_connection.rs @@ -24,6 +24,7 @@ pub async fn start_accept_connection( upstream_difficulty_config: Arc>, mut downstreams: Receiver<(Sender, Receiver, IpAddr)>, stats_sender: crate::api::stats::StatsSender, + router: Arc, ) -> Result<(), Error<'static>> { let handle = { let task_manager = task_manager.clone(); @@ -70,6 +71,7 @@ pub async fn start_accept_connection( break; } }; + let open_sv1_downstream = match bridge.safe_lock(|s| s.on_new_sv1_connection(expected_hash_rate)) { Ok(sv1_downstream) => sv1_downstream, @@ -99,6 +101,7 @@ pub async fn start_accept_connection( task_manager.clone(), initial_difficulty, stats_sender.clone(), + router.clone(), ) .await } diff --git a/src/translator/downstream/diff_management.rs b/src/translator/downstream/diff_management.rs index 552f84bc..b96be961 100644 --- a/src/translator/downstream/diff_management.rs +++ b/src/translator/downstream/diff_management.rs @@ -61,20 +61,37 @@ impl Downstream { /// aggregated channel hashrate. pub fn remove_downstream_hashrate_from_channel( self_: &Arc>, + router: Option>, ) -> ProxyResult<'static, ()> { - let (upstream_diff, estimated_downstream_hash_rate) = self_.safe_lock(|d| { - ( - d.upstream_difficulty_config.clone(), - d.difficulty_mgmt.estimated_downstream_hash_rate, - ) - })?; + let (upstream_diff, estimated_downstream_hash_rate, assigned_pool, connection_id) = self_.safe_lock(|d| { + ( + d.upstream_difficulty_config.clone(), + d.difficulty_mgmt.estimated_downstream_hash_rate, + d.assigned_pool, + d.connection_id, + ) + })?; info!( "Removing downstream hashrate from channel upstream_diff: {:?}, downstream_diff: {:?}", upstream_diff, estimated_downstream_hash_rate ); + + // Remove miner from pool assignment when they disconnect + if let Some(router) = router { + if let Some(pool_addr) = assigned_pool { + tokio::spawn(async move { + router.remove_miner_from_pool(pool_addr).await; + }); + info!( + "REMOVED: Miner {} disconnected from pool {}", + connection_id, pool_addr + ); + } + } + upstream_diff.safe_lock(|u| { u.channel_nominal_hashrate -= - // Make sure that upstream channel hasrate never goes below 0 + // Make sure that upstream channel hashrate never goes below 0 f32::min(estimated_downstream_hash_rate, u.channel_nominal_hashrate); })?; Ok(()) @@ -448,11 +465,11 @@ mod test { None, tx_sv1_submit, tx_outgoing, - false, 0, downstream_conf.clone(), Arc::new(Mutex::new(upstream_config)), crate::api::stats::StatsSender::new(), + None, ); downstream.difficulty_mgmt.estimated_downstream_hash_rate = start_hashrate as f32; diff --git a/src/translator/downstream/downstream.rs b/src/translator/downstream/downstream.rs index a192e4f5..2a1edd6c 100644 --- a/src/translator/downstream/downstream.rs +++ b/src/translator/downstream/downstream.rs @@ -32,7 +32,7 @@ use rand::Rng; use server_to_client::Notify; use std::{ collections::{hash_map::Entry, HashMap, VecDeque}, - net::IpAddr, + net::{IpAddr, SocketAddr}, sync::Arc, }; use sv1_api::{ @@ -118,6 +118,7 @@ pub struct Downstream { pub(super) stats_sender: StatsSender, pub recent_jobs: RecentJobs, pub first_job: Notify<'static>, + pub assigned_pool: Option, } impl Downstream { @@ -137,6 +138,7 @@ impl Downstream { task_manager: Arc>, initial_difficulty: f32, stats_sender: StatsSender, + router: Arc, ) { assert!(last_notify.is_some()); @@ -182,6 +184,25 @@ impl Downstream { initial_difficulty, }; + let mut recent_notifies = VecDeque::with_capacity(2); + if let Some(notify) = last_notify.clone() { + recent_notifies.push_back(notify); + } + // Add this check to prevent router calls in multi-upstream mode + let assigned_pool = if !router.is_multi_upstream() { + // Only assign pool in single-upstream mode + router.assign_miner_to_pool().await + } else { + // In multi-upstream mode, pool is already determined by distribution + // Skip router calls entirely + None + }; + if let Some(pool_addr) = assigned_pool { + info!( + "New miner (ID: {}) assigned to pool {}", + connection_id, pool_addr + ); + } let downstream = Arc::new(Mutex::new(Downstream { connection_id, authorized_names: vec![], @@ -197,6 +218,7 @@ impl Downstream { stats_sender, recent_jobs: RecentJobs::new(), first_job: last_notify.expect("we have an assertion at the beginning of this function"), + assigned_pool, })); if let Err(e) = start_receive_downstream( @@ -204,6 +226,7 @@ impl Downstream { downstream.clone(), recv_from_down, connection_id, + router.clone(), ) .await { @@ -230,6 +253,7 @@ impl Downstream { rx_sv1_notify, host.clone(), connection_id, + router.clone(), ) .await { @@ -247,6 +271,7 @@ impl Downstream { upstream_difficulty_config: Arc>, downstreams: Receiver<(Sender, Receiver, IpAddr)>, stats_sender: StatsSender, + router: Arc, ) -> Result> { let task_manager = TaskManager::initialize(); let abortable = task_manager @@ -261,6 +286,7 @@ impl Downstream { upstream_difficulty_config, downstreams, stats_sender, + router, ) .await { @@ -354,6 +380,7 @@ impl Downstream { difficulty_mgmt: DownstreamDifficultyConfig, upstream_difficulty_config: Arc>, stats_sender: StatsSender, + assigned_pool: Option, ) -> Self { Downstream { connection_id, @@ -370,6 +397,7 @@ impl Downstream { first_job: Notify, stats_sender, recent_jobs: RecentJobs::new(), + assigned_pool, } } } diff --git a/src/translator/downstream/notify.rs b/src/translator/downstream/notify.rs index 88e66952..08a378f2 100644 --- a/src/translator/downstream/notify.rs +++ b/src/translator/downstream/notify.rs @@ -17,6 +17,7 @@ pub async fn start_notify( mut rx_sv1_notify: broadcast::Receiver>, host: String, connection_id: u32, + router: Arc, ) -> Result<(), Error<'static>> { let handle = { let task_manager = task_manager.clone(); @@ -126,7 +127,7 @@ pub async fn start_notify( } } // TODO here we want to be sure that on drop this is called - let _ = Downstream::remove_downstream_hashrate_from_channel(&downstream); + let _ = Downstream::remove_downstream_hashrate_from_channel(&downstream, Some(router)); // TODO here we want to kill the tasks warn!( "Downstream: Shutting down sv1 downstream job notifier for {}", diff --git a/src/translator/downstream/receive_from_downstream.rs b/src/translator/downstream/receive_from_downstream.rs index 214ab9be..32b31d21 100644 --- a/src/translator/downstream/receive_from_downstream.rs +++ b/src/translator/downstream/receive_from_downstream.rs @@ -15,6 +15,7 @@ pub async fn start_receive_downstream( downstream: Arc>, mut recv_from_down: mpsc::Receiver, connection_id: u32, + router: Arc, ) -> Result<(), Error<'static>> { let handle = task::spawn(async move { while let Some(incoming) = recv_from_down.recv().await { @@ -53,6 +54,12 @@ pub async fn start_receive_downstream( "Downstream: Shutting down sv1 downstream reader {}", connection_id ); + + // Call disconnect handler with router + let _ = super::downstream::Downstream::remove_downstream_hashrate_from_channel( + &downstream, + Some(router), + ); }); TaskManager::add_receive_downstream(task_manager, handle.into()) .await diff --git a/src/translator/mod.rs b/src/translator/mod.rs index b861b46f..499ecee5 100644 --- a/src/translator/mod.rs +++ b/src/translator/mod.rs @@ -35,6 +35,8 @@ pub async fn start( Option
, )>, stats_sender: crate::api::stats::StatsSender, + router: Arc, + pool_address: std::net::SocketAddr, ) -> Result> { let task_manager = TaskManager::initialize(pool_connection.clone()); let abortable = task_manager @@ -102,6 +104,7 @@ pub async fn start( target.clone(), diff_config.clone(), send_to_up, + pool_address, ) .await?; @@ -176,6 +179,7 @@ pub async fn start( diff_config, downstreams, stats_sender, + router, ) .await { diff --git a/src/translator/upstream/upstream.rs b/src/translator/upstream/upstream.rs index f5b62d5a..3554ba3b 100644 --- a/src/translator/upstream/upstream.rs +++ b/src/translator/upstream/upstream.rs @@ -86,6 +86,8 @@ pub struct Upstream { // than the configured percentage pub(super) difficulty_config: Arc>, pub sender: TSender>, + /// The address of the pool, used for logging and debugging purposes. + pub pool_address: std::net::SocketAddr, } impl PartialEq for Upstream { @@ -109,6 +111,7 @@ impl Upstream { target: Arc>>, difficulty_config: Arc>, sender: TSender>, + pool_address: std::net::SocketAddr, ) -> ProxyResult<'static, Arc>> { Ok(Arc::new(Mutex::new(Self { extranonce_prefix: None, @@ -123,6 +126,7 @@ impl Upstream { target, difficulty_config, sender, + pool_address, }))) } @@ -300,7 +304,14 @@ impl Upstream { }; } Mining::NewExtendedMiningJob(m) => { - info!("Parsing incoming NewExtendedMiningJob message from Pool for Channel Id: {}", m.channel_id); + let pool_address = match self_.safe_lock(|s| s.pool_address) { + Ok(addr) => addr, + Err(e) => { + error!("Translator upstream mutex poisoned: {e}"); + return; + } + }; + info!("Parsing incoming NewExtendedMiningJob message from Pool {} for Channel Id: {}", pool_address, m.channel_id); let job_id = m.job_id; if let Err(e) = self_.safe_lock(|s| { From 6414198bacc635563db6444bd420f895d784cff2 Mon Sep 17 00:00:00 2001 From: bansalayush247 Date: Wed, 23 Jul 2025 18:58:19 +0530 Subject: [PATCH 2/7] passed pool address parameter in relay functions to improve the logs --- src/router/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/router/mod.rs b/src/router/mod.rs index e1d5147c..3459f294 100644 --- a/src/router/mod.rs +++ b/src/router/mod.rs @@ -453,9 +453,9 @@ impl PoolLatency { return Err(()); } - let relay_up_task = minin_pool_connection::relay_up(recv_to_up, sender); + let relay_up_task = minin_pool_connection::relay_up(recv_to_up, sender, self.pool); let relay_down_task = - minin_pool_connection::relay_down(receiver, send_to_down); + minin_pool_connection::relay_down(receiver, send_to_down, self.pool); let timer = Instant::now(); let mut received_new_job = false; From d1036c4862918d5a01db0237cdcc7e1f730e97c5 Mon Sep 17 00:00:00 2001 From: bansalayush247 Date: Tue, 5 Aug 2025 12:47:14 +0530 Subject: [PATCH 3/7] Update from upstream: version bump and non-conflicting files --- .github/workflows/checks.yml | 3 +- .gitignore | 2 - Cargo.lock | 190 ++++++++++++++++------------ Cargo.toml | 2 +- src/ingress/sv1_ingress.rs | 21 +-- src/jd_client/job_declarator/mod.rs | 2 +- src/jd_client/mod.rs | 41 +++--- src/share_accounter/mod.rs | 14 +- src/shared/error.rs | 20 +++ 9 files changed, 164 insertions(+), 131 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 66cc2a5b..0419e8f4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -18,8 +18,9 @@ jobs: uses: actions/checkout@v3 - name: Set up Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master with: + toolchain: "1.87.0" components: rustfmt, clippy - name: Run cargo fmt diff --git a/.gitignore b/.gitignore index 68b3e7ef..54466f5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ /target -.env -config.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index af31881d..f951d330 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,15 +240,16 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "binary_codec_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "buffer_sv2", + "hex", ] [[package]] name = "binary_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_codec_sv2", "derive_codec_sv2", @@ -256,9 +257,9 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.6" +version = "0.32.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b" +checksum = "0fda569d741b895131a88ee5589a467e73e9c4718e958ac9308e4f7dc44b6945" dependencies = [ "base58ck", "bech32", @@ -381,16 +382,16 @@ dependencies = [ [[package]] name = "buffer_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "aes-gcm", ] [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-slice-cast" @@ -412,9 +413,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "shlex", ] @@ -468,9 +469,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", "clap_derive", @@ -478,9 +479,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstream", "anstyle", @@ -490,9 +491,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -509,7 +510,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "codec_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "buffer_sv2", @@ -529,7 +530,7 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "common_messages_sv2" version = "4.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "const_sv2", @@ -577,7 +578,7 @@ dependencies = [ [[package]] name = "const_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" [[package]] name = "core-foundation" @@ -631,9 +632,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -698,7 +699,7 @@ dependencies = [ [[package]] name = "demand-cli" -version = "0.1.14" +version = "0.1.18" dependencies = [ "async-recursion", "axum", @@ -770,7 +771,7 @@ dependencies = [ [[package]] name = "derive_codec_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_codec_sv2", ] @@ -817,9 +818,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -936,7 +937,7 @@ dependencies = [ [[package]] name = "framing_sv2" version = "4.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "buffer_sv2", @@ -1093,9 +1094,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -1266,9 +1267,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", @@ -1282,7 +1283,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -1419,9 +1420,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -1449,6 +1450,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1500,7 +1512,7 @@ dependencies = [ [[package]] name = "job_declaration_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "const_sv2", @@ -1543,9 +1555,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags", "libc", @@ -1616,10 +1628,11 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mining_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "const_sv2", + "hex", ] [[package]] @@ -1668,7 +1681,7 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "noise_sv2" version = "1.2.1" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "aes-gcm", "chacha20poly1305", @@ -1981,7 +1994,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.5.10", "thiserror", "tokio", "tracing", @@ -1997,7 +2010,7 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -2018,7 +2031,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -2057,9 +2070,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -2125,9 +2138,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -2178,9 +2191,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.20" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64", "bytes", @@ -2239,7 +2252,7 @@ dependencies = [ [[package]] name = "roles_logic_sv2" version = "3.2.1" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "chacha20poly1305", @@ -2259,9 +2272,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -2286,22 +2299,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "ring", @@ -2323,9 +2336,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -2483,9 +2496,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -2565,9 +2578,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -2610,6 +2623,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spki" version = "0.7.3" @@ -2635,7 +2658,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stratum-common" version = "1.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "bitcoin", "secp256k1 0.28.2", @@ -2656,7 +2679,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sv1_api" version = "1.0.1" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "bitcoin_hashes 0.3.2", @@ -2766,7 +2789,7 @@ dependencies = [ [[package]] name = "template_distribution_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" +source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" dependencies = [ "binary_sv2", "const_sv2", @@ -2828,21 +2851,23 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "slab", + "socket2 0.6.0", "tokio-macros", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2878,9 +2903,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -3260,9 +3285,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -3341,9 +3366,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result 0.3.4", @@ -3401,7 +3426,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -3422,10 +3447,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -3534,9 +3560,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -3567,9 +3593,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", "rustix", @@ -3659,9 +3685,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index eb8cd502..a3ddd20c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "demand-cli" -version = "0.1.14" +version = "0.1.18" edition = "2021" [dependencies] diff --git a/src/ingress/sv1_ingress.rs b/src/ingress/sv1_ingress.rs index d8465b01..568470aa 100644 --- a/src/ingress/sv1_ingress.rs +++ b/src/ingress/sv1_ingress.rs @@ -1,4 +1,7 @@ -use std::{net::{IpAddr, SocketAddr}, sync::Arc}; +use std::{ + net::{IpAddr, SocketAddr}, + sync::Arc, +}; use crate::{ config::Configuration, @@ -96,14 +99,12 @@ impl Downstream { if Configuration::sv1_ingress_log() { info!("Sending msg to upstream: {}", message); } - if ! is_subscribed { - if message.contains("mining.subscribe") { - is_subscribed = true; - if message.contains("LUXminer") { - firmware.safe_lock(|f| *f = Firmware::Luxor).unwrap(); - } else { - firmware.safe_lock(|f| *f = Firmware::Other).unwrap(); - } + if !is_subscribed && message.contains("mining.subscribe") { + is_subscribed = true; + if message.contains("LUXminer") { + firmware.safe_lock(|f| *f = Firmware::Luxor).unwrap(); + } else { + firmware.safe_lock(|f| *f = Firmware::Other).unwrap(); } } if send.send(message).await.is_err() { @@ -158,7 +159,7 @@ impl Downstream { } } -#[derive(Debug,Clone,Copy)] +#[derive(Debug, Clone, Copy)] enum Firmware { Luxor, Other, diff --git a/src/jd_client/job_declarator/mod.rs b/src/jd_client/job_declarator/mod.rs index de7fbf44..fad28dad 100644 --- a/src/jd_client/job_declarator/mod.rs +++ b/src/jd_client/job_declarator/mod.rs @@ -94,7 +94,7 @@ impl JobDeclarator { SetupConnectionHandler::setup(&mut receiver, &mut sender, address).await?; if should_log_when_connected { - info!("JD CONNECTED to {}", address); + info!("JD CONNECTED"); } let min_extranonce_size = crate::MIN_EXTRANONCE_SIZE; diff --git a/src/jd_client/mod.rs b/src/jd_client/mod.rs index 0e693dbd..79fd87fa 100644 --- a/src/jd_client/mod.rs +++ b/src/jd_client/mod.rs @@ -55,12 +55,11 @@ pub async fn start( sender: tokio::sync::mpsc::Sender>, up_receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, - pool_address: SocketAddr, ) -> Option { - info!("JD client starting for pool address: {}", pool_address); + // This will not work when we implement support for multiple upstream IS_CUSTOM_JOB_SET.store(true, std::sync::atomic::Ordering::Release); IS_NEW_TEMPLATE_HANDLED.store(true, std::sync::atomic::Ordering::Release); - initialize_jd(receiver, sender, up_receiver, up_sender, pool_address).await + initialize_jd(receiver, sender, up_receiver, up_sender).await } async fn initialize_jd( @@ -68,15 +67,12 @@ async fn initialize_jd( sender: tokio::sync::mpsc::Sender>, up_receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, - pool_address: SocketAddr, ) -> Option { - info!("Initializing JD client for pool address: {}", pool_address); - let task_manager = TaskManager::initialize(); let abortable = match task_manager.safe_lock(|t| t.get_aborter()) { Ok(abortable) => abortable?, Err(e) => { - error!("Jdc task manager mutex corrupt: {e} (pool: {})", pool_address); + error!("Jdc task manager mutex corrupt: {e}"); return None; } }; @@ -91,7 +87,7 @@ async fn initialize_jd( { Ok(upstream) => upstream, Err(e) => { - error!("Failed to instantiate new Upstream for pool {}: {e}", pool_address); + error!("Failed to instantiate new Upstream: {e}"); drop(abortable); return None; } @@ -102,7 +98,7 @@ async fn initialize_jd( Ok(tp_address) => tp_address .expect("Unreachable code, jdc is not instantiated when TP_ADDRESS not present"), Err(e) => { - error!("TP_ADDRESS mutex corrupted for pool {}: {e}", pool_address); + error!("TP_ADDRESS mutex corrupted: {e}"); drop(abortable); return None; } @@ -131,7 +127,7 @@ async fn initialize_jd( match JobDeclarator::new(address, auth_pub_k.into_bytes(), upstream.clone(), true).await { Ok(c) => c, Err(e) => { - error!("Failed to initialize JobDeclarator for pool {}: {e}", pool_address); + error!("Failed to intialize Jd: {e}"); drop(abortable); return None; } @@ -142,8 +138,7 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add job declarator task for pool {}: {}", - pool_address, + "Task manager failed while trying to add job declarator task{}", error::Error::TaskManagerFailed ); drop(abortable); @@ -162,7 +157,7 @@ async fn initialize_jd( { Ok(abortable) => abortable, Err(e) => { - error!("Cannot start downstream mining node for pool {}: {e}", pool_address); + error!("Can not start downstream mining node: {e}"); ProxyState::update_downstream_state(DownstreamType::JdClientMiningDownstream); return None; } @@ -172,8 +167,7 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add mining downstream task for pool {}: {}", - pool_address, + "Task manager failed while trying to add mining downstream task{}", error::Error::TaskManagerFailed ); drop(abortable); @@ -183,7 +177,7 @@ async fn initialize_jd( .safe_lock(|u| u.downstream = Some(donwstream.clone())) .is_err() { - error!("Upstream mutex failed for pool {}", pool_address); + error!("Upstream mutex failed"); drop(abortable); // drop all tasks initailzed upto this point return None; }; @@ -193,7 +187,7 @@ async fn initialize_jd( match mining_upstream::Upstream::parse_incoming(upstream.clone(), up_receiver).await { Ok(abortable) => abortable, Err(e) => { - error!("Failed to get jdc upstream abortable for pool {}: {e}", pool_address); + error!("Failed to get jdc upstream abortable: {e}"); drop(abortable); // drop all tasks initailzed upto this point return None; } @@ -203,8 +197,7 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add mining upstream task for pool {}: {}", - pool_address, + "Task manager failed while trying to add mining upstream task{}", error::Error::TaskManagerFailed ); drop(abortable); // drop all tasks initailzed upto this point @@ -225,13 +218,13 @@ async fn initialize_jd( { Ok(abortable) => abortable, Err(_) => { - info!("Dropping jd abortable for pool {}", pool_address); - eprintln!("TP is unreachable, the proxy is not in JD mode"); + info!("Dropping jd abortable"); + eprintln!("TP is unreachable, the proxy is in not in JD mode"); drop(abortable); // Temporaily set TP_ADDRESS to None so that proxy can restart without it. // that means we will start mining without jd if crate::TP_ADDRESS.safe_lock(|tp| *tp = None).is_err() { - error!("TP_ADDRESS mutex corrupt for pool {}", pool_address); + error!("TP_ADDRESS mutex corrupt"); return None; }; tokio::spawn(retry_connection(tp_address)); @@ -244,14 +237,12 @@ async fn initialize_jd( .is_err() { error!( - "Task manager failed while trying to add template receiver task for pool {}: {}", - pool_address, + "Task manager failed while trying to add template receiver task{}", error::Error::TaskManagerFailed ); drop(abortable); return None; }; - info!("JD client successfully started for pool address: {}", pool_address); Some(abortable) } diff --git a/src/share_accounter/mod.rs b/src/share_accounter/mod.rs index 3d738682..08004da6 100644 --- a/src/share_accounter/mod.rs +++ b/src/share_accounter/mod.rs @@ -22,7 +22,6 @@ pub async fn start( sender: tokio::sync::mpsc::Sender>, up_receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, - pool_address: std::net::SocketAddr, ) -> Result { let task_manager = TaskManager::initialize(); let shares_sent_up = Arc::new(DashMap::with_capacity(100)); @@ -31,12 +30,12 @@ pub async fn start( .map_err(|_| Error::ShareAccounterTaskManagerMutexCorrupted)? .ok_or(Error::ShareAccounterTaskManagerError)?; - let relay_up_task = relay_up(receiver, up_sender, shares_sent_up.clone(), pool_address); + let relay_up_task = relay_up(receiver, up_sender, shares_sent_up.clone()); TaskManager::add_relay_up(task_manager.clone(), relay_up_task) .await .map_err(|_| Error::ShareAccounterTaskManagerError)?; - let relay_down_task = relay_down(up_receiver, sender, shares_sent_up.clone(), pool_address); + let relay_down_task = relay_down(up_receiver, sender, shares_sent_up.clone()); TaskManager::add_relay_down(task_manager.clone(), relay_down_task) .await .map_err(|_| Error::ShareAccounterTaskManagerError)?; @@ -52,7 +51,6 @@ fn relay_up( mut receiver: tokio::sync::mpsc::Receiver>, up_sender: tokio::sync::mpsc::Sender>, shares_sent_up: Arc>, - pool_address: std::net::SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = receiver.recv().await { @@ -67,7 +65,6 @@ fn relay_up( }; let msg = PoolExtMessages::Mining(msg); if up_sender.send(msg).await.is_err() { - error!("Share accounter: Failed to send message to pool {}", pool_address); break; } } @@ -79,7 +76,6 @@ fn relay_down( mut up_receiver: tokio::sync::mpsc::Receiver>, sender: tokio::sync::mpsc::Sender>, shares_sent_up: Arc>, - pool_address: std::net::SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = up_receiver.recv().await { @@ -92,7 +88,7 @@ fn relay_down( Some(shares) => shares.1, // job_id doesn't exist None => { - error!("Pool {} sent invalid share success for job {}", pool_address, job_id); + error!("Pool sent invalid share success"); // Set global pool state to Down ProxyState::update_pool_state(PoolState::Down); return; @@ -114,13 +110,13 @@ fn relay_down( } PoolExtMessages::Mining(msg) => { if let Err(e) = sender.send(msg).await { - error!("Share accounter: Failed to send message from pool {} to downstream: {}", pool_address, e); + error!("{e}"); ProxyState::update_share_accounter_state(ShareAccounterState::Down); break; } } _ => { - error!("Pool {} sent unexpected message on mining connection", pool_address); + error!("Pool send unexpected message on mining connection"); ProxyState::update_pool_state(PoolState::Down); break; } diff --git a/src/shared/error.rs b/src/shared/error.rs index 79c9ca74..bc0d5565 100644 --- a/src/shared/error.rs +++ b/src/shared/error.rs @@ -1,5 +1,25 @@ +use std::fmt; + pub enum Sv1IngressError { TranslatorDropped, DownstreamDropped, TaskFailed, } + +#[derive(Debug)] +pub enum Error { + ReqwestError(reqwest::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::ReqwestError(e) => write!(f, "ReqwestError: {}", e), + } + } +} +impl From for Error { + fn from(err: reqwest::Error) -> Self { + Error::ReqwestError(err) + } +} From ab78ebf8cda3797f519faee3cb9152b6bd907012 Mon Sep 17 00:00:00 2001 From: bansalayush247 Date: Tue, 5 Aug 2025 14:24:14 +0530 Subject: [PATCH 4/7] Refactor logging to include pool address for better traceability - Updated various logging statements across the codebase to include the pool address, enhancing the context of log messages. - Cleaned up formatting and spacing in several functions for improved readability. - Removed unnecessary parameters and streamlined error handling in the proxy and translator modules. - Ensured consistent logging practices in the downstream and upstream components. solved conflict merge --- Cargo.lock | 188 ++++++++---------- src/main.rs | 51 +++-- src/minin_pool_connection/mod.rs | 24 ++- src/router/mod.rs | 9 +- .../downstream/accept_connection.rs | 23 ++- src/translator/downstream/diff_management.rs | 50 +++-- src/translator/downstream/downstream.rs | 16 +- src/translator/downstream/notify.rs | 4 +- src/translator/mod.rs | 4 +- src/translator/proxy/bridge.rs | 56 ++++-- src/translator/upstream/upstream.rs | 8 +- src/translator/utils.rs | 11 +- 12 files changed, 255 insertions(+), 189 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f951d330..e45e32f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,16 +240,15 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "binary_codec_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "buffer_sv2", - "hex", ] [[package]] name = "binary_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_codec_sv2", "derive_codec_sv2", @@ -257,9 +256,9 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.7" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda569d741b895131a88ee5589a467e73e9c4718e958ac9308e4f7dc44b6945" +checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b" dependencies = [ "base58ck", "bech32", @@ -382,16 +381,16 @@ dependencies = [ [[package]] name = "buffer_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "aes-gcm", ] [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byte-slice-cast" @@ -413,9 +412,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.31" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "shlex", ] @@ -469,9 +468,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -479,9 +478,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -491,9 +490,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", @@ -510,7 +509,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "codec_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "buffer_sv2", @@ -530,7 +529,7 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "common_messages_sv2" version = "4.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "const_sv2", @@ -578,7 +577,7 @@ dependencies = [ [[package]] name = "const_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" [[package]] name = "core-foundation" @@ -632,9 +631,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -771,7 +770,7 @@ dependencies = [ [[package]] name = "derive_codec_sv2" version = "2.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_codec_sv2", ] @@ -818,9 +817,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", @@ -937,7 +936,7 @@ dependencies = [ [[package]] name = "framing_sv2" version = "4.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "buffer_sv2", @@ -1094,9 +1093,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.11" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -1267,9 +1266,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64", "bytes", @@ -1283,7 +1282,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2", "system-configuration", "tokio", "tower-service", @@ -1420,9 +1419,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -1450,17 +1449,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -1512,7 +1500,7 @@ dependencies = [ [[package]] name = "job_declaration_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "const_sv2", @@ -1555,9 +1543,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags", "libc", @@ -1628,11 +1616,10 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mining_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "const_sv2", - "hex", ] [[package]] @@ -1681,7 +1668,7 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "noise_sv2" version = "1.2.1" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "aes-gcm", "chacha20poly1305", @@ -1994,7 +1981,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2", "thiserror", "tokio", "tracing", @@ -2010,7 +1997,7 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand 0.9.2", + "rand 0.9.1", "ring", "rustc-hash", "rustls", @@ -2031,7 +2018,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", "windows-sys 0.59.0", ] @@ -2070,9 +2057,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -2138,9 +2125,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] @@ -2191,9 +2178,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "base64", "bytes", @@ -2252,7 +2239,7 @@ dependencies = [ [[package]] name = "roles_logic_sv2" version = "3.2.1" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "chacha20poly1305", @@ -2272,9 +2259,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -2299,22 +2286,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "once_cell", "ring", @@ -2336,9 +2323,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", @@ -2496,9 +2483,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -2578,9 +2565,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -2623,16 +2610,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "spki" version = "0.7.3" @@ -2658,7 +2635,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stratum-common" version = "1.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "bitcoin", "secp256k1 0.28.2", @@ -2679,7 +2656,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sv1_api" version = "1.0.1" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "bitcoin_hashes 0.3.2", @@ -2789,7 +2766,7 @@ dependencies = [ [[package]] name = "template_distribution_sv2" version = "3.0.0" -source = "git+https://github.com/demand-open-source/stratum#b680d3f9417cbf558ca374534526fc2a9fc5a6f9" +source = "git+https://github.com/demand-open-source/stratum#e1f92c76ceed09d29764b68d8351e9859f52f605" dependencies = [ "binary_sv2", "const_sv2", @@ -2851,23 +2828,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2903,9 +2878,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -3285,9 +3260,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -3366,9 +3341,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ "windows-link", "windows-result 0.3.4", @@ -3426,7 +3401,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.2", ] [[package]] @@ -3447,11 +3422,10 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -3560,9 +3534,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -3593,9 +3567,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" dependencies = [ "libc", "rustix", @@ -3685,9 +3659,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", diff --git a/src/main.rs b/src/main.rs index 9d893775..7c567794 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,7 +149,7 @@ async fn initialize_proxy( // Create per-pool downstream channels let (pool_downs_tx, pool_downs_rx) = channel(10); - + // Create per-pool translator channels let (translator_up_tx, mut translator_up_rx) = channel(10); @@ -165,7 +165,10 @@ async fn initialize_proxy( { Ok(abortable) => abortable, Err(e) => { - error!("Impossible to initialize translator for pool {}: {e}", pool_addr); + error!( + "Impossible to initialize translator for pool {}: {e}", + pool_addr + ); continue; } }; @@ -205,7 +208,6 @@ async fn initialize_proxy( jdc_to_translator_sender, from_share_accounter_to_jdc_recv, from_jdc_to_share_accounter_send, - pool_addr, ) .await; if jdc_abortable.is_none() { @@ -216,7 +218,6 @@ async fn initialize_proxy( from_share_accounter_to_jdc_send, recv_from_pool, send_to_pool.clone(), - pool_addr, ) .await { @@ -236,7 +237,6 @@ async fn initialize_proxy( jdc_to_translator_sender, recv_from_pool, send_to_pool.clone(), - pool_addr, ) .await { @@ -259,10 +259,8 @@ async fn initialize_proxy( pool_connection_abortable, format!("pool_connection_{}", pool_id), )); - abort_handles.push(( - translator_abortable, - format!("translator_{}", pool_id), - )); + abort_handles + .push((translator_abortable, format!("translator_{}", pool_id))); abort_handles.push(( share_accounter_abortable, format!("share_accounter_{}", pool_id), @@ -291,16 +289,29 @@ async fn initialize_proxy( let pool_translators_for_distribution = pool_translators.clone(); let distribution_task = tokio::spawn(async move { let mut recv = downs_sv1_rx; - - while let Some((send_to_downstream, recv_from_downstream, ip_addr)) = recv.recv().await { + + while let Some((send_to_downstream, recv_from_downstream, ip_addr)) = + recv.recv().await + { info!("New downstream connection from {}", ip_addr); - + // Calling router to assign pool - if let Some(assigned_pool) = router_for_distribution.assign_miner_to_pool().await { + if let Some(assigned_pool) = + router_for_distribution.assign_miner_to_pool().await + { // Send to the appropriate translator - if let Some((_, _, pool_downs_tx)) = pool_translators_for_distribution.iter().find(|(_, addr, _)| *addr == assigned_pool) { - if let Err(e) = pool_downs_tx.send((send_to_downstream, recv_from_downstream, ip_addr)).await { - error!("Failed to send downstream connection to pool {}: {}", assigned_pool, e); + if let Some((_, _, pool_downs_tx)) = pool_translators_for_distribution + .iter() + .find(|(_, addr, _)| *addr == assigned_pool) + { + if let Err(e) = pool_downs_tx + .send((send_to_downstream, recv_from_downstream, ip_addr)) + .await + { + error!( + "Failed to send downstream connection to pool {}: {}", + assigned_pool, e + ); } } } else { @@ -308,7 +319,10 @@ async fn initialize_proxy( } } }); - abort_handles.push((distribution_task.into(), "downstream_distribution".to_string())); + abort_handles.push(( + distribution_task.into(), + "downstream_distribution".to_string(), + )); if !any_success { error!("No upstream available. Retrying in 5 seconds..."); @@ -395,7 +409,6 @@ async fn initialize_proxy( jdc_to_translator_sender, from_share_accounter_to_jdc_recv, from_jdc_to_share_accounter_send, - pool_addr.expect("Best latency pool address should be available"), ) .await; if jdc_abortable.is_none() { @@ -406,7 +419,6 @@ async fn initialize_proxy( from_share_accounter_to_jdc_send, recv_from_pool, send_to_pool, - pool_addr.expect("Best latency pool address should be available"), ) .await { @@ -424,7 +436,6 @@ async fn initialize_proxy( jdc_to_translator_sender, recv_from_pool, send_to_pool, - pool_addr.expect("Best latency pool address should be available"), ) .await { diff --git a/src/minin_pool_connection/mod.rs b/src/minin_pool_connection/mod.rs index 8c8aa94b..5bf01894 100644 --- a/src/minin_pool_connection/mod.rs +++ b/src/minin_pool_connection/mod.rs @@ -108,7 +108,7 @@ pub async fn connect_pool( pub fn relay_up( mut recv: Receiver>, send: Sender, - pool_address: SocketAddr, + pool_address: SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = recv.recv().await { @@ -121,7 +121,10 @@ pub fn relay_up( break; }; } else { - panic!("Internal Mining downstream tried to send invalid message to pool {}", pool_address); + panic!( + "Internal Mining downstream tried to send invalid message to pool {}", + pool_address + ); } } }); @@ -131,7 +134,7 @@ pub fn relay_up( pub fn relay_down( mut recv: Receiver, send: Sender>, - pool_address: SocketAddr, + pool_address: SocketAddr, ) -> AbortOnDrop { let task = tokio::spawn(async move { while let Some(msg) = recv.recv().await { @@ -146,17 +149,26 @@ pub fn relay_down( if let Ok(msg) = msg { let msg = msg.into_static(); if send.send(msg).await.is_err() { - error!("Internal Mining downstream not available for pool {}", pool_address); + error!( + "Internal Mining downstream not available for pool {}", + pool_address + ); // Update Proxy state to reflect Internal inconsistency ProxyState::update_inconsistency(Some(1)); } } else { - error!("Pool {} sent non-Mining message. Disconnecting", pool_address); + error!( + "Pool {} sent non-Mining message. Disconnecting", + pool_address + ); break; } } else { - error!("Pool {} sent invalid message with no header. Disconnecting", pool_address); + error!( + "Pool {} sent invalid message with no header. Disconnecting", + pool_address + ); break; } } else { diff --git a/src/router/mod.rs b/src/router/mod.rs index 3459f294..d7fbd31b 100644 --- a/src/router/mod.rs +++ b/src/router/mod.rs @@ -58,9 +58,9 @@ impl Router { let (latency_tx, latency_rx) = watch::channel(None); let multi_pool_configs = Configuration::pool_configs(); - let weighted_dist = multi_pool_configs.as_ref().map(|configs| Arc::new( - (0..configs.len()).map(|_| AtomicU32::new(0)).collect(), - )); + let weighted_dist = multi_pool_configs + .as_ref() + .map(|configs| Arc::new((0..configs.len()).map(|_| AtomicU32::new(0)).collect())); Self { pool_addresses, @@ -453,7 +453,8 @@ impl PoolLatency { return Err(()); } - let relay_up_task = minin_pool_connection::relay_up(recv_to_up, sender, self.pool); + let relay_up_task = + minin_pool_connection::relay_up(recv_to_up, sender, self.pool); let relay_down_task = minin_pool_connection::relay_down(receiver, send_to_down, self.pool); diff --git a/src/translator/downstream/accept_connection.rs b/src/translator/downstream/accept_connection.rs index 9e8e88f7..ca61f99b 100644 --- a/src/translator/downstream/accept_connection.rs +++ b/src/translator/downstream/accept_connection.rs @@ -25,6 +25,8 @@ pub async fn start_accept_connection( mut downstreams: Receiver<(Sender, Receiver, IpAddr)>, stats_sender: crate::api::stats::StatsSender, router: Arc, + //for logging purposes + pool_address: std::net::SocketAddr, ) -> Result<(), Error<'static>> { let handle = { let task_manager = task_manager.clone(); @@ -37,13 +39,13 @@ pub async fn start_accept_connection( // The initial difficulty is derived from the formula: difficulty = hash_rate / (shares_per_second * 2^32) let initial_hash_rate = *crate::EXPECTED_SV1_HASHPOWER; info!( - "Translator initial hash rate for ip {} is {} H/s", - addr, initial_hash_rate + "Pool {}: Translator initial hash rate for ip {} is {} H/s", + pool_address, addr, initial_hash_rate ); let share_per_second = crate::SHARE_PER_MIN / 60.0; info!( - "Translator share per second for ip {} is {} shares/s", - addr, share_per_second + "Pool {}: Translator share per second for ip {} is {} shares/s", + pool_address, addr, share_per_second ); let initial_difficulty = initial_hash_rate / (share_per_second * 2f32.powf(32.0)); let initial_difficulty = @@ -51,15 +53,15 @@ pub async fn start_accept_connection( initial_difficulty, ); info!( - "Translator initial difficulty for ip {} is {}", - addr, initial_difficulty + "Pool {}: Translator initial difficulty for ip {} is {}", + pool_address, addr, initial_difficulty ); // Formula: expected_hash_rate = (shares_per_second) * initial_difficulty * 2^32, where shares_per_second = SHARE_PER_MIN / 60 let expected_hash_rate = (crate::SHARE_PER_MIN / 60.0) * initial_difficulty * 2f32.powf(32.0); info!( - "Translator expected hash rate for ip {} is {} H/s", - addr, expected_hash_rate + "Pool {}: Translator expected hash rate for ip {} is {} H/s", + pool_address, addr, expected_hash_rate ); match Bridge::ready(&bridge).await { @@ -84,8 +86,8 @@ pub async fn start_accept_connection( match open_sv1_downstream { Ok(opened) => { info!( - "Translator opening connection for ip {} with id {}", - addr, opened.channel_id + "Pool {}: Translator opening connection for ip {} with id {}", + pool_address, addr, opened.channel_id ); Downstream::new_downstream( opened.channel_id, @@ -102,6 +104,7 @@ pub async fn start_accept_connection( initial_difficulty, stats_sender.clone(), router.clone(), + pool_address, ) .await } diff --git a/src/translator/downstream/diff_management.rs b/src/translator/downstream/diff_management.rs index b96be961..4ce1e12f 100644 --- a/src/translator/downstream/diff_management.rs +++ b/src/translator/downstream/diff_management.rs @@ -63,17 +63,24 @@ impl Downstream { self_: &Arc>, router: Option>, ) -> ProxyResult<'static, ()> { - let (upstream_diff, estimated_downstream_hash_rate, assigned_pool, connection_id) = self_.safe_lock(|d| { - ( - d.upstream_difficulty_config.clone(), - d.difficulty_mgmt.estimated_downstream_hash_rate, - d.assigned_pool, - d.connection_id, - ) - })?; + let ( + upstream_diff, + estimated_downstream_hash_rate, + assigned_pool, + connection_id, + pool_address, + ) = self_.safe_lock(|d| { + ( + d.upstream_difficulty_config.clone(), + d.difficulty_mgmt.estimated_downstream_hash_rate, + d.assigned_pool, + d.connection_id, + d.pool_address, + ) + })?; info!( - "Removing downstream hashrate from channel upstream_diff: {:?}, downstream_diff: {:?}", - upstream_diff, estimated_downstream_hash_rate + "Pool {}: Removing downstream hashrate from channel upstream_diff: {:?}, downstream_diff: {:?}", + pool_address, upstream_diff, estimated_downstream_hash_rate ); // Remove miner from pool assignment when they disconnect @@ -83,8 +90,8 @@ impl Downstream { router.remove_miner_from_pool(pool_addr).await; }); info!( - "REMOVED: Miner {} disconnected from pool {}", - connection_id, pool_addr + "Pool {}: REMOVED: Miner {} disconnected", + pool_address, connection_id ); } } @@ -338,6 +345,10 @@ mod test { sync::Arc, time::{Duration, Instant}, }; + use sv1_api::{ + server_to_client::Notify, + utils::{HexU32Be, MerkleNode, PrevHash}, + }; use tokio::sync::mpsc::channel; #[test] @@ -457,6 +468,19 @@ mod test { }; let (tx_sv1_submit, _rx_sv1_submit) = tokio::sync::mpsc::channel(10); let (tx_outgoing, _rx_outgoing) = channel(10); + let random_str = rand::thread_rng().gen::<[u8; 32]>().to_vec(); + let first_job = Notify { + job_id: "ciao".to_string(), + prev_hash: PrevHash::try_from("0".repeat(64).as_str()).unwrap(), + coin_base1: "ffff".try_into().unwrap(), + coin_base2: "ffff".try_into().unwrap(), + merkle_branch: vec![MerkleNode::try_from(random_str).unwrap()], + version: HexU32Be(5667), + bits: HexU32Be(5678), + time: HexU32Be(5609), + clean_jobs: true, + }; + let pool_address = "127.0.0.1:4444".parse().unwrap(); let mut downstream = Downstream::new( 1, vec![], @@ -469,7 +493,9 @@ mod test { downstream_conf.clone(), Arc::new(Mutex::new(upstream_config)), crate::api::stats::StatsSender::new(), + first_job, None, + pool_address, ); downstream.difficulty_mgmt.estimated_downstream_hash_rate = start_hashrate as f32; diff --git a/src/translator/downstream/downstream.rs b/src/translator/downstream/downstream.rs index 2a1edd6c..4de7407d 100644 --- a/src/translator/downstream/downstream.rs +++ b/src/translator/downstream/downstream.rs @@ -119,6 +119,8 @@ pub struct Downstream { pub recent_jobs: RecentJobs, pub first_job: Notify<'static>, pub assigned_pool: Option, + /// Pool address for logging purposes + pub pool_address: std::net::SocketAddr, } impl Downstream { @@ -139,6 +141,7 @@ impl Downstream { initial_difficulty: f32, stats_sender: StatsSender, router: Arc, + pool_address: std::net::SocketAddr, ) { assert!(last_notify.is_some()); @@ -219,6 +222,7 @@ impl Downstream { recent_jobs: RecentJobs::new(), first_job: last_notify.expect("we have an assertion at the beginning of this function"), assigned_pool, + pool_address, })); if let Err(e) = start_receive_downstream( @@ -272,6 +276,7 @@ impl Downstream { downstreams: Receiver<(Sender, Receiver, IpAddr)>, stats_sender: StatsSender, router: Arc, + pool_address: std::net::SocketAddr, ) -> Result> { let task_manager = TaskManager::initialize(); let abortable = task_manager @@ -287,6 +292,7 @@ impl Downstream { downstreams, stats_sender, router, + pool_address, ) .await { @@ -380,7 +386,9 @@ impl Downstream { difficulty_mgmt: DownstreamDifficultyConfig, upstream_difficulty_config: Arc>, stats_sender: StatsSender, + first_job: Notify<'static>, assigned_pool: Option, + pool_address: std::net::SocketAddr, ) -> Self { Downstream { connection_id, @@ -394,10 +402,11 @@ impl Downstream { difficulty_mgmt, upstream_difficulty_config, last_call_to_update_hr: 0, - first_job: Notify, + first_job, stats_sender, recent_jobs: RecentJobs::new(), assigned_pool, + pool_address, } } } @@ -464,8 +473,8 @@ impl IsServer<'static> for Downstream { /// Only [Submit](client_to_server::Submit) requests for authorized user names can be submitted. fn handle_submit(&self, request: &client_to_server::Submit<'static>) -> bool { info!( - "Handling mining.submit request {} from {} with job_id {}, nonce: {:?}", - request.id, request.user_name, request.job_id, request.nonce + "Pool {}: Handling mining.submit request {} from {} with job_id {}, nonce: {:?}", + self.pool_address, request.id, request.user_name, request.job_id, request.nonce ); let mut request = request.clone(); @@ -493,6 +502,7 @@ impl IsServer<'static> for Downstream { &self.difficulty_mgmt.current_difficulties, self.extranonce1.clone(), self.version_rolling_mask.clone(), + self.pool_address, ) { // Only forward upstream if the share meets the latest difficulty if let Some(latest_difficulty) = diff --git a/src/translator/downstream/notify.rs b/src/translator/downstream/notify.rs index 08a378f2..cce797bf 100644 --- a/src/translator/downstream/notify.rs +++ b/src/translator/downstream/notify.rs @@ -90,9 +90,7 @@ pub async fn start_notify( } tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - if let Err(e) = - start_update(task_manager, downstream.clone(), connection_id).await - { + if let Err(e) = start_update(task_manager, downstream.clone(), connection_id).await { warn!("Translator impossible to start update task: {e}"); } else if authorized_in_time { // Get the mask after initialization since is set by configure message diff --git a/src/translator/mod.rs b/src/translator/mod.rs index 499ecee5..84f088ea 100644 --- a/src/translator/mod.rs +++ b/src/translator/mod.rs @@ -104,7 +104,7 @@ pub async fn start( target.clone(), diff_config.clone(), send_to_up, - pool_address, + pool_address, ) .await?; @@ -149,6 +149,7 @@ pub async fn start( extended_extranonce, target, up_id, + pool_address, ) { Ok(b) => b, Err(e) => { @@ -180,6 +181,7 @@ pub async fn start( downstreams, stats_sender, router, + pool_address, ) .await { diff --git a/src/translator/proxy/bridge.rs b/src/translator/proxy/bridge.rs index 5faad361..0fc17dc5 100644 --- a/src/translator/proxy/bridge.rs +++ b/src/translator/proxy/bridge.rs @@ -26,6 +26,7 @@ use super::{ use crate::{ proxy_state::{ProxyState, TranslatorState, UpstreamType}, shared::utils::AbortOnDrop, + translator::utils::allow_submit_share, }; use lazy_static::lazy_static; use roles_logic_sv2::{channel_logic::channel_factory::OnNewShare, Error as RolesLogicError}; @@ -62,6 +63,8 @@ pub struct Bridge { future_jobs: Vec>, last_p_hash: Option>, target: Arc>>, + /// Pool address for logging purposes + pub pool_address: std::net::SocketAddr, } impl Bridge { @@ -83,6 +86,7 @@ impl Bridge { extranonces: ExtendedExtranonce, target: Arc>>, channel_id: u32, + pool_address: std::net::SocketAddr, ) -> Result>, Error<'static>> { info!("Creating new bridge for channel_id {}:", channel_id); let ids = Arc::new(Mutex::new(GroupId::new())); @@ -106,6 +110,7 @@ impl Bridge { future_jobs: vec![], last_p_hash: None, target, + pool_address, }))) } @@ -249,9 +254,14 @@ impl Bridge { let channel_id = share.channel_id; let job_id = share.share.job_id.clone(); let share_id = share.share.id; + + let pool_address = self_ + .safe_lock(|s| s.pool_address) + .map_err(|_| Error::BridgeMutexPoisoned)?; + info!( - "Bridge received share {:?} for channel {:?} and job {:?}", - &share_id, &channel_id, &job_id + "Pool {}: Bridge received share {:?} for channel {:?} and job {:?}", + pool_address, &share_id, &channel_id, &job_id ); let (tx_sv2_submit_shares_ext, target_mutex) = self_ .safe_lock(|s| (s.tx_sv2_submit_shares_ext.clone(), s.target.clone())) @@ -296,32 +306,42 @@ impl Bridge { .unwrap_or("unparsable error code") .to_string(); error!( - "Submit share {} from channel {} and job {} error {}", - &share_id, &channel_id, &job_id, error_code + "Pool {}: Submit share {} from channel {} and job {} error {}", + pool_address, &share_id, &channel_id, &job_id, error_code ); } Ok(OnNewShare::SendSubmitShareUpstream((s, _))) => { - info!( - "Share with id {} meets upstream target from channel {} and job {}", - &share_id, &channel_id, &job_id + if let Ok(is_rate_limited) = allow_submit_share() { + if !is_rate_limited { + warn!("Share will not be sent upstream: Exceeded 70 shares/min limit"); + return Ok(()); + } + info!( + "Pool {}: Share with id {} meets upstream target from channel {} and job {}", + pool_address, &share_id, &channel_id, &job_id ); - match s { - Share::Extended(share) => { - if tx_sv2_submit_shares_ext.send(share).await.is_err() { - error!("Failed to send SubmitShareExtended downstream"); - return Err(Error::AsyncChannelError); + match s { + Share::Extended(share) => { + if tx_sv2_submit_shares_ext.send(share).await.is_err() { + error!("Failed to send SubmitShareExtended downstream"); + return Err(Error::AsyncChannelError); + } } + // We are in an extended channel shares are extended + Share::Standard(_) => unreachable!(), } - // We are in an extended channel shares are extended - Share::Standard(_) => unreachable!(), + } else { + error!("Failed to record share: Bridge mutex poisoned"); + ProxyState::update_inconsistency(Some(1)); + return Err(Error::BridgeMutexPoisoned); } } // We are in an extended channel this variant is group channle only Ok(OnNewShare::RelaySubmitShareUpstream) => unreachable!(), Ok(OnNewShare::ShareMeetDownstreamTarget) => { info!( - "Share with id {} meets downstream target from channel {} and job {}", - &share_id, &channel_id, &job_id + "Pool {}: Share with id {} meets downstream target from channel {} and job {}", + pool_address, &share_id, &channel_id, &job_id ); } // Proxy do not have JD capabilities @@ -652,12 +672,16 @@ mod test { 0, 0, 0, 0, 0, 0, 0, ]; + // Define pool_address for tests + let pool_address = "127.0.0.1:4444".parse().unwrap(); + let b = Bridge::new( tx_sv2_submit_shares_ext.clone(), tx_sv1_notify, extranonces, Arc::new(Mutex::new(upstream_target)), 1, + pool_address, // Add pool_address parameter ) .map_err(|_| ())?; Ok(b) diff --git a/src/translator/upstream/upstream.rs b/src/translator/upstream/upstream.rs index 3554ba3b..cf60b5a6 100644 --- a/src/translator/upstream/upstream.rs +++ b/src/translator/upstream/upstream.rs @@ -520,8 +520,8 @@ impl ParseUpstreamMiningMessages Result, RolesLogicError> { info!( - "Handling OpenExtendedMiningChannelSuccess message from Pool for Channel Id: {}", - m.channel_id + "Pool {}: Handling OpenExtendedMiningChannelSuccess message for Channel Id: {}", + self.pool_address, m.channel_id ); let tproxy_e1_len = proxy_extranonce1_len(m.extranonce_size as usize, self.min_extranonce_size.into()) @@ -539,8 +539,8 @@ impl ParseUpstreamMiningMessages, extranonce1: Vec, version_rolling_mask: Option, + pool_address: std::net::SocketAddr, ) -> Option { info!( - "Validating share from request {} and job {}", - request.id, request.job_id + "Pool {}: Validating share from request {} and job {}", + pool_address, request.id, request.job_id ); let prev_hash_vec: Vec = job.prev_hash.clone().into(); @@ -149,7 +150,11 @@ pub fn validate_share( ); hash.reverse(); //convert to little-endian - info!("Share Hash: {:?}", hash.to_vec().as_hex()); + info!( + "Pool {}: Share Hash: {:?}", + pool_address, + hash.to_vec().as_hex() + ); // Check against difficulties from latest to earliest // TODO: This is not a sound check - We should check against the difficulty of the specific job for &difficulty in difficulties.iter().rev() { From f416eef7125d66a8576695ef64d5f2b7840984af Mon Sep 17 00:00:00 2001 From: bansalayush247 Date: Tue, 12 Aug 2025 21:21:15 +0530 Subject: [PATCH 5/7] Add unit tests for multi-upstream router functionality and weighted distribution --- src/main.rs | 282 +++++++++++++++++++++++++++++++++++++++++++ src/router/mod.rs | 301 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 583 insertions(+) diff --git a/src/main.rs b/src/main.rs index 7c567794..6e34668a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -704,3 +704,285 @@ impl HashUnit { } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::net::SocketAddr; + use std::time::Duration; + + // Mock configuration for testing + fn setup_test_config() { + std::env::set_var("TOKEN", "test_token"); + std::env::set_var("AUTO_UPDATE", "false"); + std::env::set_var("MONITOR", "false"); + } + + #[tokio::test] + async fn test_initialize_proxy_single_upstream_success() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(100); + let pool_addr = Some("127.0.0.1:12345".parse::().unwrap()); + + // Test that function starts without immediate panic/error + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(50)) => { + // Test passes if we reach here without panic + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(200), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_multi_upstream_mode() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + // Create multiple pool addresses to trigger multi-upstream mode + let pool_addresses = vec![ + "127.0.0.1:12345".parse::().unwrap(), + "127.0.0.1:12346".parse::().unwrap(), + ]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(100); + let pool_addr = None; // Multi-upstream uses None + + // Test multi-upstream initialization path + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(50)) => { + // Test passes if multi-upstream path executes + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(200), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_connection_failure_retry() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + // Use invalid port to simulate connection failure + let pool_addresses = vec!["127.0.0.1:1".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(50); + let pool_addr = Some("127.0.0.1:1".parse::().unwrap()); + + // Test that connection failure is handled gracefully with retry logic + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(100)) => { + // Should reach here as function retries on connection failure + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(300), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_multi_upstream_partial_failure() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + // Mix valid and invalid addresses to test partial connection failure + let pool_addresses = vec![ + "127.0.0.1:1".parse::().unwrap(), // Invalid + "127.0.0.1:12345".parse::().unwrap(), // Valid (but won't connect in test) + ]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(50); + let pool_addr = None; + + // Test multi-upstream with some connection failures + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(100)) => { + // Should handle partial failures gracefully + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(300), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_single_upstream_no_pool_addr() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(100); + let pool_addr = None; // No specific pool address provided + + // Test single-upstream with None pool_addr (should trigger connection logic) + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(50)) => { + // Test passes if function handles None pool_addr + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(200), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_with_zero_epsilon() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(0); // Edge case: zero epsilon + let pool_addr = Some("127.0.0.1:12345".parse::().unwrap()); + + // Test function behavior with zero epsilon + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(50)) => { + // Should handle zero epsilon gracefully + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(200), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_with_large_epsilon() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_secs(60); // Large epsilon value + let pool_addr = Some("127.0.0.1:12345".parse::().unwrap()); + + // Test function behavior with large epsilon + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(50)) => { + // Should handle large epsilon gracefully + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(200), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_stats_sender_creation() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(100); + let pool_addr = Some("127.0.0.1:12345".parse::().unwrap()); + + // Test that stats_sender is created properly (indirect test through function execution) + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(50)) => { + // If function executes, stats_sender creation was successful + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(200), task).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_router_is_multi_upstream_check() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + + // Test single upstream detection + let single_pool = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut single_router = Router::new(single_pool, auth_pub_k, None, None); + let epsilon = Duration::from_millis(50); + + let task1 = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut single_router, None, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(30)) => { + // Single upstream path should be taken + } + } + }); + + // Test multi upstream detection + let multi_pools = vec![ + "127.0.0.1:12345".parse::().unwrap(), + "127.0.0.1:12346".parse::().unwrap(), + ]; + let mut multi_router = Router::new(multi_pools, auth_pub_k, None, None); + + let task2 = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut multi_router, None, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(30)) => { + // Multi upstream path should be taken + } + } + }); + + let result1 = tokio::time::timeout(Duration::from_millis(100), task1).await; + let result2 = tokio::time::timeout(Duration::from_millis(100), task2).await; + + assert!(result1.is_ok()); + assert!(result2.is_ok()); + } + + #[tokio::test] + async fn test_initialize_proxy_loop_behavior() { + setup_test_config(); + + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:1".parse::().unwrap()]; // Will fail to connect + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + let epsilon = Duration::from_millis(10); + let pool_addr = Some("127.0.0.1:1".parse::().unwrap()); + + // Test that the function continues looping on connection failures + let task = tokio::spawn(async move { + tokio::select! { + _ = initialize_proxy(&mut router, pool_addr, epsilon) => {}, + _ = tokio::time::sleep(Duration::from_millis(150)) => { + // Should reach here as function keeps retrying in loop + } + } + }); + + let result = tokio::time::timeout(Duration::from_millis(300), task).await; + assert!(result.is_ok()); + } +} diff --git a/src/router/mod.rs b/src/router/mod.rs index d7fbd31b..30a7ae45 100644 --- a/src/router/mod.rs +++ b/src/router/mod.rs @@ -624,3 +624,304 @@ async fn initialize_mining_connections( setup_connection_msg.unwrap_or(get_mining_setup_connection_msg(true)); Ok((receiver, sender, setup_connection_msg)) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::PoolConfig; + use key_utils::Secp256k1PublicKey; + use std::net::SocketAddr; + use std::time::Duration; + + const TEST_AUTH_PUB_KEY: &str = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"; + + fn setup_multi_upstream_router() -> Router { + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec![ + "127.0.0.1:12345".parse::().unwrap(), + "127.0.0.1:12346".parse::().unwrap(), + "127.0.0.1:12347".parse::().unwrap(), + ]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + + // Mock multi-upstream configuration + router.multi_pool_configs = Some(vec![ + PoolConfig { + address: "127.0.0.1:12345".parse().unwrap(), + weight: 0.5, + }, + PoolConfig { + address: "127.0.0.1:12346".parse().unwrap(), + weight: 0.3, + }, + PoolConfig { + address: "127.0.0.1:12347".parse().unwrap(), + weight: 0.2, + }, + ]); + router.weighted_dist = Some(Arc::new(vec![ + AtomicU32::new(0), + AtomicU32::new(0), + AtomicU32::new(0), + ])); + + router + } + + #[test] + fn test_is_multi_upstream_true() { + let router = setup_multi_upstream_router(); + assert!(router.is_multi_upstream()); + } + + #[test] + fn test_is_multi_upstream_false_single_config() { + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + + router.multi_pool_configs = Some(vec![PoolConfig { + address: "127.0.0.1:12345".parse().unwrap(), + weight: 1.0, + }]); + + assert!(!router.is_multi_upstream()); + } + + #[tokio::test] + async fn test_assign_miner_to_pool_weighted_distribution() { + let router = setup_multi_upstream_router(); + + // First miner should go to pool with highest weight (0.5) + let pool1 = router.assign_miner_to_pool().await; + assert_eq!(pool1, Some("127.0.0.1:12345".parse().unwrap())); + + // Check that counter is updated + let assignments = router.weighted_dist.as_ref().unwrap(); + assert_eq!(assignments[0].load(Ordering::Relaxed), 1); + assert_eq!(assignments[1].load(Ordering::Relaxed), 0); + assert_eq!(assignments[2].load(Ordering::Relaxed), 0); + } + + #[tokio::test] + async fn test_assign_miner_to_pool_balancing() { + let router = setup_multi_upstream_router(); + let assignments = router.weighted_dist.as_ref().unwrap(); + + // Pre-assign miners to create imbalance + assignments[0].store(10, Ordering::Relaxed); // Pool 0: 10 miners (weight 0.5) + assignments[1].store(0, Ordering::Relaxed); // Pool 1: 0 miners (weight 0.3) + assignments[2].store(0, Ordering::Relaxed); // Pool 2: 0 miners (weight 0.2) + + // Next assignment should go to pool 1 (most under-represented) + let pool = router.assign_miner_to_pool().await; + assert_eq!(pool, Some("127.0.0.1:12346".parse().unwrap())); + assert_eq!(assignments[1].load(Ordering::Relaxed), 1); + } + + #[tokio::test] + async fn test_assign_miner_to_pool_equal_weights() { + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec![ + "127.0.0.1:12345".parse::().unwrap(), + "127.0.0.1:12346".parse::().unwrap(), + ]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + + router.multi_pool_configs = Some(vec![ + PoolConfig { + address: "127.0.0.1:12345".parse().unwrap(), + weight: 0.5, + }, + PoolConfig { + address: "127.0.0.1:12346".parse().unwrap(), + weight: 0.5, + }, + ]); + router.weighted_dist = Some(Arc::new(vec![AtomicU32::new(0), AtomicU32::new(0)])); + + // First assignment should go to pool 0 (both have equal weight and 0 miners) + let pool1 = router.assign_miner_to_pool().await; + assert_eq!(pool1, Some("127.0.0.1:12345".parse().unwrap())); + + // Second assignment should go to pool 1 to maintain balance + let pool2 = router.assign_miner_to_pool().await; + assert_eq!(pool2, Some("127.0.0.1:12346".parse().unwrap())); + } + + #[tokio::test] + async fn test_assign_miner_to_pool_empty_pools() { + let router = setup_multi_upstream_router(); + let assignments = router.weighted_dist.as_ref().unwrap(); + + // All pools empty - should assign to highest weight pool + assert_eq!(assignments[0].load(Ordering::Relaxed), 0); + assert_eq!(assignments[1].load(Ordering::Relaxed), 0); + assert_eq!(assignments[2].load(Ordering::Relaxed), 0); + + let pool = router.assign_miner_to_pool().await; + assert_eq!(pool, Some("127.0.0.1:12345".parse().unwrap())); + } + + #[tokio::test] + async fn test_monitor_upstream_multi_upstream_mode() { + let mut router = setup_multi_upstream_router(); + + let result = router.monitor_upstream(Duration::from_millis(100)).await; + + // Should return None for multi-upstream mode (no monitoring needed) + assert_eq!(result, None); + } + + #[tokio::test] + async fn test_connect_all_pools_no_configs() { + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + + // Explicitly set multi_pool_configs to None to test this path + router.multi_pool_configs = None; + + let connections = router.connect_all_pools().await; + + // Should return empty vector when no multi_pool_configs + assert!(connections.is_empty()); + } + + #[tokio::test] + async fn test_remove_miner_from_pool_valid_pool() { + let router = setup_multi_upstream_router(); + let assignments = router.weighted_dist.as_ref().unwrap(); + let pool_addr = "127.0.0.1:12345".parse().unwrap(); + + // Pre-assign miners + assignments[0].store(5, Ordering::Relaxed); + assignments[1].store(3, Ordering::Relaxed); + assignments[2].store(2, Ordering::Relaxed); + + // Remove miner from pool 0 + router.remove_miner_from_pool(pool_addr).await; + + // Pool 0 should have one less miner + assert_eq!(assignments[0].load(Ordering::Relaxed), 4); + assert_eq!(assignments[1].load(Ordering::Relaxed), 3); + assert_eq!(assignments[2].load(Ordering::Relaxed), 2); + } + + #[tokio::test] + async fn test_remove_miner_from_pool_invalid_pool() { + let router = setup_multi_upstream_router(); + let assignments = router.weighted_dist.as_ref().unwrap(); + // Use a valid port number but different address + let invalid_addr = "127.0.0.1:54321".parse().unwrap(); + + // Pre-assign miners + assignments[0].store(5, Ordering::Relaxed); + assignments[1].store(3, Ordering::Relaxed); + assignments[2].store(2, Ordering::Relaxed); + + // Remove miner from invalid pool - should not affect counters + router.remove_miner_from_pool(invalid_addr).await; + + // All counts should remain the same + assert_eq!(assignments[0].load(Ordering::Relaxed), 5); + assert_eq!(assignments[1].load(Ordering::Relaxed), 3); + assert_eq!(assignments[2].load(Ordering::Relaxed), 2); + } + + #[tokio::test] + async fn test_remove_miner_from_pool_zero_count() { + let router = setup_multi_upstream_router(); + let assignments = router.weighted_dist.as_ref().unwrap(); + let pool_addr = "127.0.0.1:12345".parse().unwrap(); + + // Pool already has 0 miners + assignments[0].store(0, Ordering::Relaxed); + + // Remove miner from pool with 0 count - should not underflow + router.remove_miner_from_pool(pool_addr).await; + + // Count should remain 0 + assert_eq!(assignments[0].load(Ordering::Relaxed), 0); + } + + #[tokio::test] + async fn test_remove_miner_from_pool_no_configs() { + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + let mut router = Router::new(pool_addresses, auth_pub_k, None, None); + router.multi_pool_configs = None; // Explicitly set to None + let pool_addr = "127.0.0.1:12345".parse().unwrap(); + + // Should not panic when no multi_pool_configs + router.remove_miner_from_pool(pool_addr).await; + } + + #[test] + fn test_weighted_dist_initialization() { + let router = setup_multi_upstream_router(); + + // Verify weighted_dist is properly initialized + assert!(router.weighted_dist.is_some()); + let assignments = router.weighted_dist.as_ref().unwrap(); + assert_eq!(assignments.len(), 3); + + // All should start at 0 + for assignment in assignments.iter() { + assert_eq!(assignment.load(Ordering::Relaxed), 0); + } + } + + #[test] + fn test_multi_pool_configs_initialization() { + let router = setup_multi_upstream_router(); + + // Verify multi_pool_configs is properly set + assert!(router.multi_pool_configs.is_some()); + let configs = router.multi_pool_configs.as_ref().unwrap(); + assert_eq!(configs.len(), 3); + + // Verify weights + assert_eq!(configs[0].weight, 0.5); + assert_eq!(configs[1].weight, 0.3); + assert_eq!(configs[2].weight, 0.2); + } + + #[tokio::test] + async fn test_assign_miner_large_scale() { + let router = setup_multi_upstream_router(); + let assignments = router.weighted_dist.as_ref().unwrap(); + + // Assign 100 miners and check distribution + for _ in 0..100 { + router.assign_miner_to_pool().await; + } + + let total: u32 = assignments.iter().map(|a| a.load(Ordering::Relaxed)).sum(); + assert_eq!(total, 100); + + // Check that distribution roughly matches weights + let pool0_ratio = assignments[0].load(Ordering::Relaxed) as f32 / 100.0; + let pool1_ratio = assignments[1].load(Ordering::Relaxed) as f32 / 100.0; + let pool2_ratio = assignments[2].load(Ordering::Relaxed) as f32 / 100.0; + + // Should be approximately correct (within reasonable tolerance) + assert!(pool0_ratio >= 0.4); // Target: 0.5 + assert!(pool1_ratio >= 0.2); // Target: 0.3 + assert!(pool2_ratio >= 0.1); // Target: 0.2 + } + + #[test] + fn test_router_new_calls_configuration() { + let auth_pub_k: Secp256k1PublicKey = TEST_AUTH_PUB_KEY.parse().unwrap(); + let pool_addresses = vec!["127.0.0.1:12345".parse::().unwrap()]; + + // This will call Configuration::pool_configs() internally + let router = Router::new(pool_addresses.clone(), auth_pub_k, None, None); + + assert_eq!(router.pool_addresses, pool_addresses); + assert_eq!(router.current_pool, None); + assert_eq!(router.auth_pub_k.into_bytes(), auth_pub_k.into_bytes()); + // multi_pool_configs will be set by Configuration::pool_configs() + } +} From 3412375c537e66eb3f7854ce477acdfc6aae8646 Mon Sep 17 00:00:00 2001 From: bansalayush247 Date: Sat, 23 Aug 2025 15:38:12 +0530 Subject: [PATCH 6/7] Add local files to .gitignore --- .gitignore | 14 +++++++++++++- cpuminer-opt | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) create mode 160000 cpuminer-opt diff --git a/.gitignore b/.gitignore index 54466f5b..399c9a20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,14 @@ -/target +/target/ +# Local environment and config files +.env +config.toml + +# Mining software +cpuminer-opt/ +cpuminer-opt + +# Other local files that shouldn't be tracked +*.local +.DS_Store + diff --git a/cpuminer-opt b/cpuminer-opt new file mode 160000 index 00000000..66191db9 --- /dev/null +++ b/cpuminer-opt @@ -0,0 +1 @@ +Subproject commit 66191db93c1b2dfa1c5fde97b457c56646d7a625 From b88bd6665386323c26c6af9a9b6afc4906a817f1 Mon Sep 17 00:00:00 2001 From: bansalayush247 Date: Sat, 23 Aug 2025 17:59:38 +0530 Subject: [PATCH 7/7] Remove cpuminer-opt directory --- cpuminer-opt | 1 - 1 file changed, 1 deletion(-) delete mode 160000 cpuminer-opt diff --git a/cpuminer-opt b/cpuminer-opt deleted file mode 160000 index 66191db9..00000000 --- a/cpuminer-opt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 66191db93c1b2dfa1c5fde97b457c56646d7a625