diff --git a/miner-apps/jd-client/src/args.rs b/miner-apps/jd-client/src/args.rs index 1836b2d1c..1e518a410 100644 --- a/miner-apps/jd-client/src/args.rs +++ b/miner-apps/jd-client/src/args.rs @@ -1,6 +1,6 @@ use clap::Parser; use ext_config::{Config, File, FileFormat}; -use jd_client_sv2::{config::JobDeclaratorClientConfig, error::JDCError}; +use jd_client_sv2::{config::JobDeclaratorClientConfig, error::JDCErrorKind}; use std::path::PathBuf; use tracing::error; @@ -23,12 +23,12 @@ pub struct Args { } #[allow(clippy::result_large_err)] -pub fn process_cli_args() -> Result { +pub fn process_cli_args() -> Result { let args = Args::parse(); let config_path = args.config_path.to_str().ok_or_else(|| { error!("Invalid configuration path."); - JDCError::BadCliArgs + JDCErrorKind::BadCliArgs })?; let settings = Config::builder() diff --git a/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs b/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs index 6a40ca87d..170d6d003 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs @@ -32,8 +32,9 @@ use tracing::{debug, error, info, warn}; use crate::{ channel_manager::{ChannelManager, ChannelManagerChannel, FULL_EXTRANONCE_SIZE}, - error::{ChannelSv2Error, JDCError}, + error::{self, JDCError, JDCErrorKind}, jd_mode::{get_jd_mode, JdMode}, + utils::create_close_channel_msg, }; /// `RouteMessageTo` is an abstraction used to route protocol messages @@ -100,7 +101,7 @@ impl RouteMessageTo<'_> { pub async fn forward( self, channel_manager_channel: &ChannelManagerChannel, - ) -> Result<(), JDCError> { + ) -> Result<(), JDCErrorKind> { match self { RouteMessageTo::Downstream((downstream_id, message)) => { _ = channel_manager_channel.downstream_sender.send(( @@ -138,7 +139,7 @@ impl RouteMessageTo<'_> { #[cfg_attr(not(test), hotpath::measure_all)] impl HandleMiningMessagesFromClientAsync for ChannelManager { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_client( &self, @@ -193,7 +194,10 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { "No downstream with channel_id: {:?} and downstream_id: {:?}, found", msg.channel_id, downstream_id ); - return Err(JDCError::DownstreamNotFound(downstream_id)); + return Err(JDCError::disconnect( + JDCErrorKind::DownstreamNotFound(downstream_id), + downstream_id, + )); }; downstream.downstream_data.super_safe_lock(|data| { data.extended_channels.remove(&msg.channel_id); @@ -240,7 +244,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { .super_safe_lock(|data| data.coinbase_outputs.clone()); let mut coinbase_outputs = deserialize_outputs(coinbase_outputs) - .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?; + .map_err(|_| JDCError::shutdown(JDCErrorKind::ChannelManagerHasBadCoinbaseOutputs))?; info!(downstream_id, "Received: {}", msg); @@ -258,19 +262,28 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { channel_manager_data.last_future_template.clone() else { error!("Missing last_future_template, cannot open channel"); - return Err(JDCError::FutureTemplateNotPresent); + return Err(JDCError::disconnect( + JDCErrorKind::FutureTemplateNotPresent, + downstream_id, + )); }; let Some(last_new_prev_hash) = channel_manager_data.last_new_prev_hash.clone() else { error!("Missing last_new_prev_hash, cannot open channel"); - return Err(JDCError::LastNewPrevhashNotFound); + return Err(JDCError::disconnect( + JDCErrorKind::LastNewPrevhashNotFound, + downstream_id, + )); }; let Some(downstream) = channel_manager_data.downstream.get(&downstream_id) else { error!(downstream_id, "Downstream not registered"); - return Err(JDCError::DownstreamNotFound(downstream_id)); + return Err(JDCError::disconnect( + JDCErrorKind::DownstreamNotFound(downstream_id), + downstream_id, + )); }; coinbase_outputs[0].value = @@ -301,7 +314,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { Ok(channel) => channel, Err(e) => { error!(?e, "Failed to create group channel"); - return Err(JDCError::FailedToCreateGroupChannel(e)); + return Err(JDCError::shutdown(e)); } }; @@ -310,18 +323,14 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { coinbase_outputs.clone(), ) { error!(?e, "Failed to apply template to group channel"); - return Err(JDCError::ChannelSv2( - ChannelSv2Error::GroupChannelServerSide(e), - )); + return Err(JDCError::shutdown(e)); } if let Err(e) = group_channel.on_set_new_prev_hash(last_new_prev_hash.clone()) { error!(?e, "Failed to apply prevhash to group channel"); - return Err(JDCError::ChannelSv2( - ChannelSv2Error::GroupChannelServerSide(e), - )); + return Err(JDCError::shutdown(e)); }; data.group_channels = Some(group_channel); @@ -347,7 +356,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { Ok(p) => p, Err(e) => { error!(?e, "Failed to get extranonce prefix"); - return Err(JDCError::ExtranoncePrefixFactoryError(e)); + return Err(JDCError::shutdown(e)); } }; @@ -381,9 +390,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { ) .into()]) } - other => Err(JDCError::ChannelSv2( - ChannelSv2Error::StandardChannelServerSide(other), - )), + other => Err(JDCError::disconnect(other, downstream_id)), }; } }; @@ -397,7 +404,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { .get_extranonce_prefix() .clone() .try_into() - .expect("extranonce_prefix must be valid"), + .map_err(JDCError::shutdown)?, group_channel_id, } .into_static(); @@ -416,9 +423,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { .on_new_template(last_future_template.clone(), coinbase_outputs.clone()) { error!(?e, "Failed to apply template to standard channel"); - return Err(JDCError::ChannelSv2( - ChannelSv2Error::StandardChannelServerSide(e), - )); + return Err(JDCError::shutdown(e)); } let future_standard_job_id = standard_channel @@ -455,9 +460,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { standard_channel.on_set_new_prev_hash(last_new_prev_hash.clone()) { error!(?e, "Failed to apply prevhash to standard channel"); - return Err(JDCError::ChannelSv2( - ChannelSv2Error::StandardChannelServerSide(e), - )); + return Err(JDCError::shutdown(e)); } messages.push( ( @@ -543,17 +546,17 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let Some(last_future_template) = channel_manager_data.last_future_template.clone() else { error!("No template to share"); - return Err(JDCError::FutureTemplateNotPresent); + return Err(JDCError::disconnect(JDCErrorKind::FutureTemplateNotPresent, downstream_id)); }; let Some(last_new_prev_hash) = channel_manager_data.last_new_prev_hash.clone() else { error!("No prevhash in system"); - return Err(JDCError::LastNewPrevhashNotFound); + return Err(JDCError::disconnect(JDCErrorKind::LastNewPrevhashNotFound, downstream_id)); }; let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { error!(downstream_id, "Downstream not found"); - return Err(JDCError::DownstreamNotFound(downstream_id)); + return Err(JDCError::disconnect(JDCErrorKind::DownstreamNotFound(downstream_id), downstream_id)); }; downstream.downstream_data.super_safe_lock(|data| { @@ -567,7 +570,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { Ok(p) => p, Err(e) => { error!(?e, "Extranonce prefix error"); - return Err(JDCError::ExtranoncePrefixFactoryError(e)); + return Err(JDCError::shutdown(e)); } }; @@ -602,9 +605,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { Ok(vec![(downstream_id, build_error("min-extranonce-size-too-large")).into()]) } other => Err( - JDCError::ChannelSv2( - ChannelSv2Error::ExtendedChannelServerSide(other) - ) + JDCError::disconnect(other, downstream_id) ), } } @@ -633,7 +634,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let mut coinbase_outputs = match deserialize_outputs(channel_manager_data.coinbase_outputs.clone()) { Ok(outputs) => outputs, - Err(_) => return Err(JDCError::ChannelManagerHasBadCoinbaseOutputs), + Err(_) => return Err(JDCError::shutdown(JDCErrorKind::ChannelManagerHasBadCoinbaseOutputs)), }; coinbase_outputs[0].value = Amount::from_sat(last_future_template.coinbase_tx_value_remaining); @@ -644,7 +645,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { extended_channel.on_new_template(last_future_template.clone(), coinbase_outputs) { error!(?e, "Failed to apply template to extended channel"); - return Err(JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(e))); + return Err(JDCError::shutdown(e)); } let future_extended_job_id = extended_channel @@ -679,7 +680,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { }; if let Err(e) = extended_channel.on_set_new_prev_hash(last_new_prev_hash) { error!(?e, "Failed to set prevhash on extended channel"); - return Err(JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(e))); + return Err(JDCError::shutdown(e)); } messages.push(( downstream_id, @@ -928,11 +929,11 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { warn!("No downstream found for downstream_id={downstream_id}"); - return Err(JDCError::DownstreamNotFound(downstream_id)); + return Err(JDCError::disconnect(JDCErrorKind::DownstreamNotFound(downstream_id), downstream_id)); }; let Some(prev_hash) = channel_manager_data.last_new_prev_hash.as_ref() else { warn!("No prev_hash available yet, ignoring share"); - return Err(JDCError::LastNewPrevhashNotFound); + return Err(JDCError::disconnect(JDCErrorKind::LastNewPrevhashNotFound, downstream_id)); }; downstream.downstream_data.super_safe_lock(|data| { @@ -944,7 +945,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { }; let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(downstream_id, channel_id).into()) else { - return Err(JDCError::VardiffNotFound((downstream_id, channel_id).into())); + return Ok(vec![(downstream_id, Mining::CloseChannel(create_close_channel_msg(channel_id, "invalid-channel-id"))).into()]); }; vardiff.increment_shares_since_last_update(); let res = standard_channel.validate_share(msg.clone()); @@ -979,7 +980,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { version: msg.version, header_timestamp: msg.ntime, header_nonce: msg.nonce, - coinbase_tx: coinbase.try_into()?, + coinbase_tx: coinbase.try_into().map_err(JDCError::shutdown)?, }; messages.push(TemplateDistribution::SubmitSolution(solution.clone()).into()); @@ -1053,7 +1054,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { upstream_message.sequence_number = channel_manager_data.sequence_number_factory.fetch_add(1, Ordering::Relaxed); info!("SubmitSharesStandard forwarding it to upstream: 💰 Block Found!!! 💰{share_hash}"); let push_solution = PushSolution { - extranonce: standard_channel.get_extranonce_prefix().to_vec().try_into()?, + extranonce: standard_channel.get_extranonce_prefix().to_vec().try_into().map_err(JDCError::shutdown)?, ntime: upstream_message.ntime, nonce: upstream_message.nonce, version: upstream_message.version, @@ -1124,11 +1125,11 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { warn!("No downstream found for downstream_id={downstream_id}"); - return Err(JDCError::DownstreamNotFound(downstream_id)); + return Err(JDCError::disconnect(JDCErrorKind::DownstreamNotFound(downstream_id), downstream_id)); }; let Some(prev_hash) = channel_manager_data.last_new_prev_hash.as_ref() else { warn!("No prev_hash available yet, ignoring share"); - return Err(JDCError::LastNewPrevhashNotFound); + return Err(JDCError::disconnect(JDCErrorKind::LastNewPrevhashNotFound, downstream_id)); }; downstream.downstream_data.super_safe_lock(|data| { let mut messages: Vec = vec![]; @@ -1155,7 +1156,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { } let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(downstream_id, channel_id).into()) else { - return Err(JDCError::VardiffNotFound((downstream_id, channel_id).into())); + return Ok(vec![(downstream_id, Mining::CloseChannel(create_close_channel_msg(channel_id, "invalid-channel-id"))).into()]); }; vardiff.increment_shares_since_last_update(); let res = extended_channel.validate_share(msg.clone()); @@ -1189,7 +1190,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { version: msg.version, header_timestamp: msg.ntime, header_nonce: msg.nonce, - coinbase_tx: coinbase.try_into()?, + coinbase_tx: coinbase.try_into().map_err(JDCError::shutdown)?, }; messages.push(TemplateDistribution::SubmitSolution(solution.clone()).into()); } @@ -1266,7 +1267,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let mut channel_extranonce = upstream_channel.get_extranonce_prefix().to_vec(); channel_extranonce.extend_from_slice(&upstream_message.extranonce.to_vec()); let push_solution = PushSolution { - extranonce: channel_extranonce.try_into()?, + extranonce: channel_extranonce.try_into().map_err(JDCError::shutdown)?, ntime: upstream_message.ntime, nonce: upstream_message.nonce, version: upstream_message.version, @@ -1299,7 +1300,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { })?; for messages in messages { - messages.forward(&self.channel_manager_channel).await?; + _ = messages.forward(&self.channel_manager_channel).await; } Ok(()) @@ -1313,9 +1314,9 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { _tlv_fields: Option<&[Tlv]>, ) -> Result<(), Self::Error> { warn!("Received: {}", msg); - Err(Self::Error::UnexpectedMessage( + Err(JDCError::log(JDCErrorKind::UnexpectedMessage( 0, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB, - )) + ))) } } diff --git a/miner-apps/jd-client/src/lib/channel_manager/extensions_message_handler.rs b/miner-apps/jd-client/src/lib/channel_manager/extensions_message_handler.rs index 5f20b9c4c..b8ebabca7 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/extensions_message_handler.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/extensions_message_handler.rs @@ -1,4 +1,7 @@ -use crate::{channel_manager::ChannelManager, error::JDCError}; +use crate::{ + channel_manager::ChannelManager, + error::{self, JDCError, JDCErrorKind}, +}; use stratum_apps::{ stratum_core::{ binary_sv2::Seq064K, @@ -12,7 +15,7 @@ use tracing::{error, info}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleExtensionsFromServerAsync for ChannelManager { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_server( &self, @@ -47,7 +50,9 @@ impl HandleExtensionsFromServerAsync for ChannelManager { "Server does not support our required extensions {:?}. Connection should fail over to another upstream.", missing_required ); - return Err(JDCError::RequiredExtensionsNotSupported(missing_required)); + return Err(JDCError::fallback( + JDCErrorKind::RequiredExtensionsNotSupported(missing_required), + )); } // Store the negotiated extensions in the shared channel manager data @@ -88,7 +93,9 @@ impl HandleExtensionsFromServerAsync for ChannelManager { "Server does not support our required extensions {:?}. Connection should fail over to another upstream.", missing_required ); - return Err(JDCError::RequiredExtensionsNotSupported(missing_required)); + return Err(JDCError::fallback( + JDCErrorKind::RequiredExtensionsNotSupported(missing_required), + )); } // Check if server requires extensions - if we support them, we should retry with them @@ -117,8 +124,8 @@ impl HandleExtensionsFromServerAsync for ChannelManager { "Server requires extensions {:?} that we don't support. Connection should fail over to another upstream.", cannot_support ); - return Err(JDCError::ServerRequiresUnsupportedExtensions( - cannot_support, + return Err(JDCError::fallback( + JDCErrorKind::ServerRequiresUnsupportedExtensions(cannot_support), )); } @@ -134,7 +141,9 @@ impl HandleExtensionsFromServerAsync for ChannelManager { }; let sv2_frame: Sv2Frame = - AnyMessage::Extensions(new_require_extensions.into_static().into()).try_into()?; + AnyMessage::Extensions(new_require_extensions.into_static().into()) + .try_into() + .map_err(JDCError::shutdown)?; self.channel_manager_channel .upstream_sender @@ -142,7 +151,7 @@ impl HandleExtensionsFromServerAsync for ChannelManager { .await .map_err(|e| { error!("Failed to send message to upstream: {:?}", e); - JDCError::ChannelErrorSender + JDCError::fallback(JDCErrorKind::ChannelErrorSender) })?; } diff --git a/miner-apps/jd-client/src/lib/channel_manager/jd_message_handler.rs b/miner-apps/jd-client/src/lib/channel_manager/jd_message_handler.rs index 3df42fda2..064f6377e 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/jd_message_handler.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/jd_message_handler.rs @@ -20,13 +20,12 @@ use tracing::{debug, error, info, warn}; use crate::{ channel_manager::ChannelManager, - error::JDCError, - status::{State, Status}, + error::{self, JDCError, JDCErrorKind}, }; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_server( &self, @@ -65,7 +64,7 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { info!("Coinbase outputs from JDS changed, recalculating constraints"); let deserialized_jds_coinbase_outputs: Vec = bitcoin::consensus::deserialize(&msg.coinbase_outputs.to_vec()) - .map_err(JDCError::BitcoinEncodeError)?; + .map_err(JDCError::shutdown)?; let max_additional_size: usize = deserialized_jds_coinbase_outputs .iter() @@ -103,7 +102,7 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { .tp_sender .send(coinbase_output_constraints_message) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::shutdown(JDCErrorKind::ChannelErrorSender))?; info!("Sent updated CoinbaseOutputConstraints to TP channel"); } else { @@ -132,15 +131,7 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { ) -> Result<(), Self::Error> { warn!("Received: {}", msg); warn!("⚠️ JDS refused the declared job with a DeclareMiningJobError ❌. Starting fallback mechanism."); - self.channel_manager_channel - .status_sender - .send(Status { - state: State::JobDeclaratorShutdownFallback(JDCError::Shutdown), - }) - .await - .map_err(|_e| JDCError::ChannelErrorSender)?; - - Ok(()) + Err(JDCError::fallback(JDCErrorKind::DeclareMiningJobError)) } // Handles a `DeclareMiningJobSuccess` message from the JDS. @@ -175,17 +166,23 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { "No last_declare_job found for request_id={}", msg.request_id ); - return Err(JDCError::LastDeclareJobNotFound(msg.request_id)); + return Err(JDCError::log(JDCErrorKind::LastDeclareJobNotFound( + msg.request_id, + ))); }; let Some(prevhash) = last_declare_job.prev_hash else { error!("Prevhash not found for request_id = {}", msg.request_id); - return Err(JDCError::LastNewPrevhashNotFound); + return Err(JDCError::log(JDCErrorKind::LastNewPrevhashNotFound)); }; let outputs = match deserialize_outputs(last_declare_job.coinbase_output.clone()) { Ok(outputs) => outputs, - Err(_) => return Err(JDCError::ChannelManagerHasBadCoinbaseOutputs), + Err(_) => { + return Err(JDCError::shutdown( + JDCErrorKind::ChannelManagerHasBadCoinbaseOutputs, + )) + } }; let Some(custom_job) = self @@ -206,10 +203,11 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { Some(custom_job) }) else { - return Err(JDCError::FailedToCreateCustomJob); + return Err(JDCError::log(JDCErrorKind::FailedToCreateCustomJob)); }; - let custom_job = custom_job.map_err(|_e| JDCError::FailedToCreateCustomJob)?; + let custom_job = + custom_job.map_err(|_e| JDCError::log(JDCErrorKind::FailedToCreateCustomJob))?; self.channel_manager_data.super_safe_lock(|data| { if let Some(value) = data.last_declare_job_store.get_mut(&msg.request_id) { @@ -221,12 +219,14 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { debug!("Sending SetCustomMiningJob to the upstream with channel_id: {channel_id}"); let message = Mining::SetCustomMiningJob(custom_job).into_static(); - let sv2_frame: Sv2Frame = AnyMessage::Mining(message).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(message) + .try_into() + .map_err(JDCError::shutdown)?; self.channel_manager_channel .upstream_sender .send(sv2_frame) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::fallback(JDCErrorKind::ChannelErrorSender))?; info!("Successfully sent SetCustomMiningJob to the upstream with channel_id: {channel_id}"); Ok(()) @@ -259,7 +259,9 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { "No transaction list found for request_id={}", msg.request_id ); - return Err(JDCError::LastDeclareJobNotFound(msg.request_id)); + return Err(JDCError::log(JDCErrorKind::LastDeclareJobNotFound( + msg.request_id, + ))); }; let full_tx_list: Vec = entry @@ -286,8 +288,7 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { let response = ProvideMissingTransactionsSuccess { request_id: msg.request_id, - transaction_list: binary_sv2::Seq064K::new(missing_txns) - .map_err(JDCError::BinarySv2)?, + transaction_list: binary_sv2::Seq064K::new(missing_txns).map_err(JDCError::shutdown)?, }; let message = JobDeclaration::ProvideMissingTransactionsSuccess(response); @@ -295,7 +296,7 @@ impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager { .jd_sender .send(message) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::fallback(JDCErrorKind::ChannelErrorSender))?; info!("Successfully sent ProvideMissingTransactionsSuccess to the JDS with request_id: {request_id}"); diff --git a/miner-apps/jd-client/src/lib/channel_manager/mod.rs b/miner-apps/jd-client/src/lib/channel_manager/mod.rs index 81d433db7..4f402c787 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/mod.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/mod.rs @@ -59,7 +59,7 @@ use crate::{ channel_manager::downstream_message_handler::RouteMessageTo, config::JobDeclaratorClientConfig, downstream::Downstream, - error::JDCError, + error::{self, JDCError, JDCErrorKind, JDCResult}, status::{handle_error, Status, StatusSender}, utils::{ AtomicUpstreamState, DownstreamChannelJobId, PendingChannelRequest, ShutdownMessage, @@ -234,7 +234,6 @@ pub struct ChannelManagerChannel { tp_receiver: Receiver>, downstream_sender: broadcast::Sender<(DownstreamId, Mining<'static>, Option>)>, downstream_receiver: Receiver<(DownstreamId, Mining<'static>, Option>)>, - status_sender: Sender, } /// Contains all the state of mutable and immutable data required @@ -270,11 +269,10 @@ impl ChannelManager { tp_receiver: Receiver>, downstream_sender: broadcast::Sender<(DownstreamId, Mining<'static>, Option>)>, downstream_receiver: Receiver<(DownstreamId, Mining<'static>, Option>)>, - status_sender: Sender, coinbase_outputs: Vec, supported_extensions: Vec, required_extensions: Vec, - ) -> Result { + ) -> JDCResult { let (range_0, range_1, range_2) = { let range_1 = 0..JDC_SEARCH_SPACE_BYTES; ( @@ -326,7 +324,6 @@ impl ChannelManager { tp_receiver, downstream_sender, downstream_receiver, - status_sender, }; let channel_manager = ChannelManager { @@ -361,11 +358,11 @@ impl ChannelManager { )>, supported_extensions: Vec, required_extensions: Vec, - ) -> Result<(), JDCError> { + ) -> JDCResult<(), error::ChannelManager> { info!("Starting downstream server at {listening_address}"); let server = TcpListener::bind(listening_address).await.map_err(|e| { error!(error = ?e, "Failed to bind downstream server at {listening_address}"); - e + JDCError::shutdown(e) })?; let mut shutdown_rx = notify_shutdown.subscribe(); @@ -533,42 +530,34 @@ impl ChannelManager { } res = cm_jds.handle_jds_message() => { if let Err(e) = res { - if !e.is_critical() { - continue; - } error!(error = ?e, "Error handling JDS message"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = cm_pool.handle_pool_message_frame() => { if let Err(e) = res { - if !e.is_critical() { - continue; - } error!(error = ?e, "Error handling Pool message"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = cm_template.handle_template_provider_message() => { if let Err(e) = res { - if !e.is_critical() { - continue; - } error!(error = ?e, "Error handling Template Receiver message"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = cm_downstreams.handle_downstream_message() => { if let Err(e) = res { - if !e.is_critical() { - continue; - } error!(error = ?e, "Error handling Downstreams message"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } } @@ -580,7 +569,11 @@ impl ChannelManager { // // Given a `downstream_id`, this method: // 1. Removes the corresponding downstream from the `downstream` map. - fn remove_downstream(&mut self, downstream_id: DownstreamId) -> Result<(), JDCError> { + #[allow(clippy::result_large_err)] + fn remove_downstream( + &mut self, + downstream_id: DownstreamId, + ) -> JDCResult<(), error::ChannelManager> { self.channel_manager_data.super_safe_lock(|cm_data| { cm_data.downstream.remove(&downstream_id); cm_data @@ -599,7 +592,7 @@ impl ChannelManager { /// - If the frame contains a JobDeclaration message, it forwards it to the job declaration /// message handler. /// - If the frame contains any unsupported message type, an error is returned. - async fn handle_jds_message(&mut self) -> Result<(), JDCError> { + async fn handle_jds_message(&mut self) -> JDCResult<(), error::ChannelManager> { if let Ok(message) = self.channel_manager_channel.jd_receiver.recv().await { self.handle_job_declaration_message_from_server(None, message, None) .await?; @@ -613,11 +606,11 @@ impl ChannelManager { /// - If the frame contains a **Mining** message, it forwards it to the mining message /// handler. /// - If the frame contains any unsupported message type, an error is returned. - async fn handle_pool_message_frame(&mut self) -> Result<(), JDCError> { + async fn handle_pool_message_frame(&mut self) -> JDCResult<(), error::ChannelManager> { if let Ok(mut sv2_frame) = self.channel_manager_channel.upstream_receiver.recv().await { let header = sv2_frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - JDCError::FramingSv2(framing_sv2::Error::MissingHeader) + JDCError::fallback(framing_sv2::Error::MissingHeader) })?; let message_type = header.msg_type(); let extension_type = header.ext_type(); @@ -633,7 +626,10 @@ impl ChannelManager { } _ => { warn!("Received unsupported message type from upstream: {message_type}"); - return Err(JDCError::UnexpectedMessage(extension_type, message_type)); + return Err(JDCError::log(JDCErrorKind::UnexpectedMessage( + extension_type, + message_type, + ))); } } } @@ -646,7 +642,7 @@ impl ChannelManager { // - If the frame contains a TemplateDistribution message, it forwards it to the template // distribution message handler. // - If the frame contains any unsupported message type, an error is returned. - async fn handle_template_provider_message(&mut self) -> Result<(), JDCError> { + async fn handle_template_provider_message(&mut self) -> JDCResult<(), error::ChannelManager> { if let Ok(message) = self.channel_manager_channel.tp_receiver.recv().await { self.handle_template_distribution_message_from_server(None, message, None) .await?; @@ -687,7 +683,7 @@ impl ChannelManager { // - Only one upstream channel is created per JDC instance. // - After the upstream channel is established, all new downstream requests bypass the pending // mechanism and are sent directly to the mining handler. - async fn handle_downstream_message(&mut self) -> Result<(), JDCError> { + async fn handle_downstream_message(&mut self) -> JDCResult<(), error::ChannelManager> { if let Ok((downstream_id, message, tlvs)) = self .channel_manager_channel .downstream_receiver @@ -711,21 +707,27 @@ impl ChannelManager { .is_ok() { let mut upstream_message = downstream_channel_request; - upstream_message.user_identity = - self.user_identity.clone().try_into()?; + upstream_message.user_identity = self + .user_identity + .clone() + .try_into() + .map_err(JDCError::shutdown)?; upstream_message.request_id = 1; upstream_message.min_extranonce_size += JDC_SEARCH_SPACE_BYTES as u16; let upstream_message = Mining::OpenExtendedMiningChannel(upstream_message) .into_static(); - let sv2_frame: Sv2Frame = - AnyMessage::Mining(upstream_message).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(upstream_message) + .try_into() + .map_err(JDCError::shutdown)?; self.channel_manager_channel .upstream_sender .send(sv2_frame) .await - .map_err(|_| JDCError::ChannelErrorSender)?; + .map_err(|_| { + JDCError::fallback(JDCErrorKind::ChannelErrorSender) + })?; } } UpstreamState::Pending => { @@ -777,12 +779,16 @@ impl ChannelManager { let message = Mining::OpenExtendedMiningChannel(upstream_open).into_static(); - let sv2_frame: Sv2Frame = AnyMessage::Mining(message).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(message) + .try_into() + .map_err(JDCError::shutdown)?; self.channel_manager_channel .upstream_sender .send(sv2_frame) .await - .map_err(|_| JDCError::ChannelErrorSender)?; + .map_err(|_| { + JDCError::fallback(JDCErrorKind::ChannelErrorSender) + })?; } } UpstreamState::Pending => { @@ -830,14 +836,17 @@ impl ChannelManager { downstream_id: DownstreamId, message: Mining<'_>, tlvs: Option<&[Tlv]>, - ) -> Result<(), JDCError> { + ) -> JDCResult<(), error::ChannelManager> { self.handle_mining_message_from_client(Some(downstream_id), message, tlvs) .await?; Ok(()) } /// Utility method to request for more token to JDS. - pub async fn allocate_tokens(&self, token_to_allocate: u32) -> Result<(), JDCError> { + pub async fn allocate_tokens( + &self, + token_to_allocate: u32, + ) -> JDCResult<(), error::ChannelManager> { debug!("Allocating {} job tokens", token_to_allocate); for i in 0..token_to_allocate { @@ -867,7 +876,7 @@ impl ChannelManager { .await .map_err(|e| { info!(error = ?e, "Failed to send AllocateMiningJobToken frame"); - JDCError::ChannelErrorSender + JDCError::fallback(JDCErrorKind::ChannelErrorSender) })?; } @@ -969,7 +978,7 @@ impl ChannelManager { // # Purpose // - Executes the vardiff cycle every 60 seconds for all downstreams. // - Delegates to [`Self::run_vardiff`] on each tick. - async fn run_vardiff_loop(&self) -> Result<(), JDCError> { + async fn run_vardiff_loop(&self) -> JDCResult<(), error::ChannelManager> { let mut ticker = tokio::time::interval(std::time::Duration::from_secs(60)); loop { ticker.tick().await; @@ -988,7 +997,7 @@ impl ChannelManager { // - Runs vardiff for each channel and collects the resulting updates. // - Propagates difficulty changes to downstreams and also sends an `UpdateChannel` message // upstream if applicable. - async fn run_vardiff(&self) -> Result<(), JDCError> { + async fn run_vardiff(&self) -> JDCResult<(), error::ChannelManager> { let mut messages: Vec = vec![]; self.channel_manager_data .super_safe_lock(|channel_manager_data| { @@ -1090,7 +1099,7 @@ impl ChannelManager { pub async fn coinbase_output_constraints( &self, coinbase_outputs: Vec, - ) -> Result<(), JDCError> { + ) -> JDCResult<(), error::ChannelManager> { let msg = coinbase_output_constraints_message(coinbase_outputs); self.channel_manager_channel @@ -1099,7 +1108,7 @@ impl ChannelManager { .await .map_err(|e| { error!(error = ?e, "Failed to send CoinbaseOutputConstraints message to TP"); - JDCError::ChannelErrorSender + JDCError::shutdown(JDCErrorKind::ChannelErrorSender) })?; Ok(()) diff --git a/miner-apps/jd-client/src/lib/channel_manager/template_message_handler.rs b/miner-apps/jd-client/src/lib/channel_manager/template_message_handler.rs index ee350369b..f2edb74a8 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/template_message_handler.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/template_message_handler.rs @@ -14,13 +14,13 @@ use tracing::{error, info, warn}; use crate::{ channel_manager::{downstream_message_handler::RouteMessageTo, ChannelManager, DeclaredJob}, - error::JDCError, + error::{self, JDCError, JDCErrorKind}, jd_mode::{get_jd_mode, JdMode}, }; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_server( &self, @@ -61,7 +61,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { }); let mut coinbase_outputs = deserialize_outputs(coinbase_outputs) - .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?; + .map_err(|_| JDCError::shutdown(JDCErrorKind::ChannelManagerHasBadCoinbaseOutputs))?; if get_jd_mode() == JdMode::FullTemplate { let tx_data_request = @@ -73,7 +73,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { .tp_sender .send(tx_data_request) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::shutdown(JDCErrorKind::ChannelErrorSender))?; } let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { @@ -266,7 +266,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { ) { return Ok(()); } - Err(JDCError::TxDataError) + Err(JDCError::log(JDCErrorKind::TxDataError)) } // Handles a `RequestTransactionDataSuccess` message from the Template Provider. @@ -293,7 +293,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { .super_safe_lock(|data| data.coinbase_outputs.clone()); let mut deserialized_outputs = deserialize_outputs(coinbase_outputs) - .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?; + .map_err(|_| JDCError::shutdown(JDCErrorKind::ChannelManagerHasBadCoinbaseOutputs))?; let (token, template_message, request_id, prevhash) = self.channel_manager_data.super_safe_lock(|data| { @@ -308,12 +308,14 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { _ = self.allocate_tokens(1).await; let Some(token) = token else { error!("Token not found, template id: {}", msg.template_id); - return Err(JDCError::TokenNotFound); + return Err(JDCError::log(JDCErrorKind::TokenNotFound)); }; let Some(template_message) = template_message else { error!("Template not found, template id: {}", msg.template_id); - return Err(JDCError::TemplateNotFound(msg.template_id)); + return Err(JDCError::log(JDCErrorKind::TemplateNotFound( + msg.template_id, + ))); }; let mining_token = token.mining_job_token.clone(); @@ -336,7 +338,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { }) .collect(); - let wtx_ids = Seq064K::new(wtxids_as_u256).map_err(JDCError::BinarySv2)?; + let wtx_ids = Seq064K::new(wtxids_as_u256).map_err(JDCError::shutdown)?; let is_activated_future_template = template_message.future_template && prevhash .map(|prev_hash| prev_hash.template_id != template_message.template_id) @@ -413,7 +415,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { .super_safe_lock(|data| data.coinbase_outputs.clone()); let outputs = deserialize_outputs(coinbase_outputs) - .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?; + .map_err(|_| JDCError::shutdown(JDCErrorKind::ChannelManagerHasBadCoinbaseOutputs))?; let (future_template, declare_job) = self.channel_manager_data.super_safe_lock(|data| { if let Some(upstream_channel) = data.upstream_channel.as_mut() { @@ -444,7 +446,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { .jd_sender .send(message) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::fallback(JDCErrorKind::ChannelErrorSender))?; } } diff --git a/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs b/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs index 9bb53032f..3a33624b7 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs @@ -21,15 +21,14 @@ use crate::{ downstream_message_handler::RouteMessageTo, ChannelManager, DeclaredJob, JDC_SEARCH_SPACE_BYTES, }, - error::{ChannelSv2Error, JDCError}, + error::{self, JDCError, JDCErrorKind}, jd_mode::{get_jd_mode, JdMode}, - status::{State, Status}, utils::{create_close_channel_msg, UpstreamState}, }; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleMiningMessagesFromServerAsync for ChannelManager { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_server( &self, @@ -65,14 +64,9 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { info!( "⚠️ JDC can only open extended channels with the upstream server, preparing fallback." ); - _ = self - .channel_manager_channel - .status_sender - .send(Status { - state: State::UpstreamShutdownFallback(JDCError::Shutdown), - }) - .await; - Ok(()) + Err(JDCError::fallback( + JDCErrorKind::OpenStandardMiningChannelError, + )) } // Handles `OpenExtendedMiningChannelSuccess` messages from upstream. @@ -99,7 +93,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .super_safe_lock(|data| data.coinbase_outputs.clone()); let outputs = deserialize_outputs(coinbase_outputs) - .map_err(|_| JDCError::DeclaredJobHasBadCoinbaseOutputs)?; + .map_err(|_| JDCError::shutdown(JDCErrorKind::DeclaredJobHasBadCoinbaseOutputs))?; let (channel_state, template, custom_job, close_channel) = self.channel_manager_data.super_safe_lock(|data| { @@ -230,19 +224,21 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .tp_sender .send(tx_data_request) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::shutdown(JDCErrorKind::ChannelErrorSender))?; } } if get_jd_mode() == JdMode::CoinbaseOnly { if let Some(custom_job) = custom_job { let set_custom_job = Mining::SetCustomMiningJob(custom_job); - let sv2_frame: Sv2Frame = AnyMessage::Mining(set_custom_job).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(set_custom_job) + .try_into() + .map_err(JDCError::shutdown)?; self.channel_manager_channel .upstream_sender .send(sv2_frame) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::fallback(JDCErrorKind::ChannelErrorSender))?; _ = self.allocate_tokens(1).await; } } @@ -264,12 +260,14 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { // In case of failure, close the channel with upstream. if let Some(close_channel) = close_channel { let close_channel = Mining::CloseChannel(close_channel); - let sv2_frame: Sv2Frame = AnyMessage::Mining(close_channel).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(close_channel) + .try_into() + .map_err(JDCError::shutdown)?; self.channel_manager_channel .upstream_sender .send(sv2_frame) .await - .map_err(|_e| JDCError::ChannelErrorSender)?; + .map_err(|_e| JDCError::fallback(JDCErrorKind::ChannelErrorSender))?; _ = self.allocate_tokens(1).await; } @@ -290,14 +288,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { warn!("Received: {}", msg); warn!("⚠️ Cannot open extended channel with the upstream server, preparing fallback."); - _ = self - .channel_manager_channel - .status_sender - .send(Status { - state: State::UpstreamShutdownFallback(JDCError::Shutdown), - }) - .await; - Ok(()) + Err(JDCError::fallback(JDCErrorKind::OpenMiningChannelError)) } // Handles `UpdateChannelError` messages from upstream. @@ -326,14 +317,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { self.channel_manager_data.super_safe_lock(|data| { data.upstream_channel = None; }); - _ = self - .channel_manager_channel - .status_sender - .send(Status { - state: State::UpstreamShutdownFallback(JDCError::Shutdown), - }) - .await; - Ok(()) + Err(JDCError::fallback(JDCErrorKind::CloseChannel)) } // Handles `SetExtranoncePrefix` messages from upstream. @@ -357,9 +341,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { if let Err(e) = upstream_channel.set_extranonce_prefix(msg.extranonce_prefix.to_vec()) { - return Err(JDCError::ChannelSv2( - ChannelSv2Error::ExtendedChannelClientSide(e), - )); + return Err(JDCError::fallback(e)); } let new_prefix_len = msg.extranonce_prefix.len(); @@ -368,7 +350,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { let full_extranonce_size = new_prefix_len + rollable_extranonce_size as usize; if full_extranonce_size > MAX_EXTRANONCE_LEN { - return Err(JDCError::ExtranonceSizeTooLarge); + return Err(JDCError::fallback(JDCErrorKind::ExtranonceSizeTooLarge)); } let range_0 = 0..new_prefix_len; @@ -390,7 +372,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { Ok(e) => e, Err(e) => { warn!("Failed to build extranonce factory: {e:?}"); - return Err(JDCError::ExtranoncePrefixFactoryError(e)); + return Err(JDCError::fallback(e)); } }; @@ -409,31 +391,22 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .extranonce_prefix_factory_standard .next_prefix_standard() { - Ok(prefix) => match standard_channel - .set_extranonce_prefix(prefix.clone().to_vec()) - { - Ok(_) => { - messages_results.push(Ok(( - *downstream_id, - Mining::SetExtranoncePrefix( - SetExtranoncePrefix { - channel_id: *channel_id, - extranonce_prefix: prefix.into(), - }, - ), - ) - .into())); - } - Err(e) => { - messages_results.push(Err(JDCError::ChannelSv2( - ChannelSv2Error::StandardChannelServerSide(e), - ))); - } - }, + Ok(prefix) => { + standard_channel + .set_extranonce_prefix(prefix.clone().to_vec()) + .expect("Prefix will always be less than 32"); + messages_results.push(Ok(( + *downstream_id, + Mining::SetExtranoncePrefix(SetExtranoncePrefix { + channel_id: *channel_id, + extranonce_prefix: prefix.into(), + }), + ) + .into())); + } Err(e) => { - messages_results.push(Err( - JDCError::ExtranoncePrefixFactoryError(e), - )); + messages_results + .push(Err(JDCError::disconnect(e, *downstream_id))); } } } @@ -446,31 +419,23 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { extended_channel.get_rollable_extranonce_size() as usize, ) { - Ok(prefix) => match extended_channel - .set_extranonce_prefix(prefix.clone().to_vec()) - { - Ok(_) => { - messages_results.push(Ok(( - *downstream_id, - Mining::SetExtranoncePrefix( - SetExtranoncePrefix { - channel_id: *channel_id, - extranonce_prefix: prefix.into(), - }, - ), - ) - .into())); - } - Err(e) => { - messages_results.push(Err(JDCError::ChannelSv2( - ChannelSv2Error::ExtendedChannelServerSide(e), - ))); - } - }, + Ok(prefix) => { + extended_channel + .set_extranonce_prefix(prefix.clone().to_vec()) + .expect("Prefix will always be less than 32"); + + messages_results.push(Ok(( + *downstream_id, + Mining::SetExtranoncePrefix(SetExtranoncePrefix { + channel_id: *channel_id, + extranonce_prefix: prefix.into(), + }), + ) + .into())); + } Err(e) => { - messages_results.push(Err( - JDCError::ExtranoncePrefixFactoryError(e), - )); + messages_results + .push(Err(JDCError::disconnect(e, *downstream_id))); } } } @@ -598,14 +563,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { ) -> Result<(), Self::Error> { warn!("⚠️ Received: {} ❌", msg); warn!("⚠️ Starting fallback mechanism."); - _ = self - .channel_manager_channel - .status_sender - .send(Status { - state: State::UpstreamShutdownFallback(JDCError::Shutdown), - }) - .await; - Ok(()) + Err(JDCError::fallback(JDCErrorKind::CustomJobError)) } // Handles a `SetTarget` message from upstream. diff --git a/miner-apps/jd-client/src/lib/downstream/common_message_handler.rs b/miner-apps/jd-client/src/lib/downstream/common_message_handler.rs index 29724d97d..e55f8f4f2 100644 --- a/miner-apps/jd-client/src/lib/downstream/common_message_handler.rs +++ b/miner-apps/jd-client/src/lib/downstream/common_message_handler.rs @@ -1,4 +1,7 @@ -use crate::{downstream::Downstream, error::JDCError}; +use crate::{ + downstream::Downstream, + error::{self, JDCError, JDCErrorKind}, +}; use std::convert::TryInto; use stratum_apps::{ stratum_core::{ @@ -15,7 +18,7 @@ use tracing::info; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleCommonMessagesFromClientAsync for Downstream { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_client( &self, @@ -61,12 +64,17 @@ impl HandleCommonMessagesFromClientAsync for Downstream { error_code: "unsupported-protocol" .to_string() .try_into() - .expect("error code must be valid string"), + .map_err(JDCError::shutdown)?, }; - let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()).try_into()?; + let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()) + .try_into() + .map_err(JDCError::shutdown)?; _ = self.downstream_channel.downstream_sender.send(frame).await; - return Err(JDCError::Shutdown); + return Err(JDCError::disconnect( + JDCErrorKind::SetupConnectionError, + self.downstream_id, + )); } if has_work_selection(msg.flags) { @@ -76,14 +84,17 @@ impl HandleCommonMessagesFromClientAsync for Downstream { error_code: "unsupported-feature-flags" .to_string() .try_into() - .expect("error code must be valid string"), + .map_err(JDCError::shutdown)?, }; let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()) .try_into() - .unwrap(); + .map_err(JDCError::shutdown)?; _ = self.downstream_channel.downstream_sender.send(frame).await; - return Err(JDCError::Shutdown); + return Err(JDCError::disconnect( + JDCErrorKind::SetupConnectionError, + self.downstream_id, + )); } if has_requires_std_job(msg.flags) { @@ -94,7 +105,9 @@ impl HandleCommonMessagesFromClientAsync for Downstream { used_version: 2, flags: msg.flags, }; - let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()).try_into()?; + let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()) + .try_into() + .map_err(JDCError::shutdown)?; _ = self.downstream_channel.downstream_sender.send(frame).await; diff --git a/miner-apps/jd-client/src/lib/downstream/extensions_message_handler.rs b/miner-apps/jd-client/src/lib/downstream/extensions_message_handler.rs index 002300f8b..40940acc5 100644 --- a/miner-apps/jd-client/src/lib/downstream/extensions_message_handler.rs +++ b/miner-apps/jd-client/src/lib/downstream/extensions_message_handler.rs @@ -1,4 +1,7 @@ -use crate::{downstream::Downstream, error::JDCError}; +use crate::{ + downstream::Downstream, + error::{self, JDCError, JDCErrorKind}, +}; use std::convert::TryInto; use stratum_apps::{ stratum_core::{ @@ -13,7 +16,7 @@ use tracing::{error, info}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleExtensionsFromClientAsync for Downstream { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_client( &self, @@ -75,13 +78,17 @@ impl HandleExtensionsFromClientAsync for Downstream { let error = RequestExtensionsError { request_id: msg.request_id, - unsupported_extensions: Seq064K::new(unsupported) - .map_err(|_| JDCError::InvalidUnsupportedExtensionsSequence)?, - required_extensions: Seq064K::new(missing_required.clone()) - .map_err(|_| JDCError::InvalidRequiredExtensionsSequence)?, + unsupported_extensions: Seq064K::new(unsupported).map_err(|_| { + JDCError::shutdown(JDCErrorKind::InvalidUnsupportedExtensionsSequence) + })?, + required_extensions: Seq064K::new(missing_required.clone()).map_err(|_| { + JDCError::shutdown(JDCErrorKind::InvalidRequiredExtensionsSequence) + })?, }; - let frame: Sv2Frame = AnyMessage::Extensions(error.into()).try_into()?; + let frame: Sv2Frame = AnyMessage::Extensions(error.into()) + .try_into() + .map_err(JDCError::shutdown)?; _ = self.downstream_channel.downstream_sender.send(frame).await; // If required extensions are missing, the server SHOULD disconnect the client @@ -106,11 +113,14 @@ impl HandleExtensionsFromClientAsync for Downstream { let success = RequestExtensionsSuccess { request_id: msg.request_id, - supported_extensions: Seq064K::new(supported.clone()) - .map_err(|_| JDCError::InvalidSupportedExtensionsSequence)?, + supported_extensions: Seq064K::new(supported.clone()).map_err(|_| { + JDCError::shutdown(JDCErrorKind::InvalidSupportedExtensionsSequence) + })?, }; - let frame: Sv2Frame = AnyMessage::Extensions(success.into()).try_into()?; + let frame: Sv2Frame = AnyMessage::Extensions(success.into()) + .try_into() + .map_err(JDCError::shutdown)?; _ = self.downstream_channel.downstream_sender.send(frame).await; info!( diff --git a/miner-apps/jd-client/src/lib/downstream/mod.rs b/miner-apps/jd-client/src/lib/downstream/mod.rs index a1a317fdc..c5e4f99ec 100644 --- a/miner-apps/jd-client/src/lib/downstream/mod.rs +++ b/miner-apps/jd-client/src/lib/downstream/mod.rs @@ -26,7 +26,7 @@ use tokio::sync::broadcast; use tracing::{debug, error, warn}; use crate::{ - error::JDCError, + error::{self, JDCError, JDCErrorKind, JDCResult}, io_task::spawn_io_tasks, status::{handle_error, Status, StatusSender}, utils::ShutdownMessage, @@ -200,15 +200,17 @@ impl Downstream { res = self_clone_1.handle_downstream_message() => { if let Err(e) = res { error!(?e, "Error handling downstream message for {downstream_id}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = self_clone_2.handle_channel_manager_message(&mut receiver) => { if let Err(e) = res { error!(?e, "Error handling channel manager message for {downstream_id}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } @@ -219,17 +221,22 @@ impl Downstream { } // Performs the initial handshake with a downstream peer. - async fn setup_connection_with_downstream(&mut self) -> Result<(), JDCError> { - let mut frame = self.downstream_channel.downstream_receiver.recv().await?; + async fn setup_connection_with_downstream(&mut self) -> JDCResult<(), error::Downstream> { + let mut frame = self + .downstream_channel + .downstream_receiver + .recv() + .await + .map_err(|error| JDCError::disconnect(error, self.downstream_id))?; let header = frame.get_header().expect("frame header must be present"); if header.msg_type() == MESSAGE_TYPE_SETUP_CONNECTION { self.handle_common_message_frame_from_client(None, header, frame.payload()) .await?; return Ok(()); } - Err(JDCError::UnexpectedMessage( - header.ext_type(), - header.msg_type(), + Err(JDCError::disconnect( + JDCErrorKind::UnexpectedMessage(header.ext_type(), header.msg_type()), + self.downstream_id, )) } @@ -237,12 +244,14 @@ impl Downstream { async fn handle_channel_manager_message( self, receiver: &mut broadcast::Receiver<(DownstreamId, Mining<'static>, Option>)>, - ) -> Result<(), JDCError> { + ) -> JDCResult<(), error::Downstream> { let (downstream_id, message, _tlv_fields) = match receiver.recv().await { Ok(msg) => msg, Err(e) => { warn!(?e, "Broadcast receive failed"); - return Ok(()); + return Err(JDCError::shutdown( + JDCErrorKind::BroadcastChannelErrorReceiver(e), + )); } }; @@ -255,7 +264,7 @@ impl Downstream { } let message = AnyMessage::Mining(message); - let sv2_frame: Sv2Frame = message.try_into()?; + let sv2_frame: Sv2Frame = message.try_into().map_err(JDCError::shutdown)?; self.downstream_channel .downstream_sender @@ -263,17 +272,20 @@ impl Downstream { .await .map_err(|e| { error!(?e, "Downstream send failed"); - JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - ) + JDCError::disconnect(JDCErrorKind::ChannelErrorSender, self.downstream_id) })?; Ok(()) } // Handles incoming messages from the downstream peer. - async fn handle_downstream_message(mut self) -> Result<(), JDCError> { - let mut sv2_frame = self.downstream_channel.downstream_receiver.recv().await?; + async fn handle_downstream_message(mut self) -> JDCResult<(), error::Downstream> { + let mut sv2_frame = self + .downstream_channel + .downstream_receiver + .recv() + .await + .map_err(|error| JDCError::disconnect(error, self.downstream_id))?; let header = sv2_frame .get_header() .expect("frame header must be present"); @@ -282,7 +294,8 @@ impl Downstream { .downstream_data .super_safe_lock(|data| data.negotiated_extensions.clone()); let (any_message, tlv_fields) = - parse_message_frame_with_tlvs(header, payload, &negotiated_extensions)?; + parse_message_frame_with_tlvs(header, payload, &negotiated_extensions) + .map_err(|error| JDCError::disconnect(error, self.downstream_id))?; match any_message { AnyMessage::Mining(message) => { self.downstream_channel @@ -291,7 +304,7 @@ impl Downstream { .await .map_err(|e| { error!(?e, "Failed to send mining message to channel manager."); - JDCError::ChannelErrorSender + JDCError::shutdown(JDCErrorKind::ChannelErrorSender) })?; } AnyMessage::Extensions(message) => { diff --git a/miner-apps/jd-client/src/lib/error.rs b/miner-apps/jd-client/src/lib/error.rs index abc8f6c29..a89d517e6 100644 --- a/miner-apps/jd-client/src/lib/error.rs +++ b/miner-apps/jd-client/src/lib/error.rs @@ -12,7 +12,10 @@ //! This module ensures that all errors can be passed around consistently, including across async //! boundaries. use ext_config::ConfigError; -use std::fmt; +use std::{ + fmt::{self, Formatter}, + marker::PhantomData, +}; use stratum_apps::{ network_helpers, stratum_core::{ @@ -31,21 +34,117 @@ use stratum_apps::{ parsers_sv2::ParserError, }, utils::types::{ - DownstreamId, ExtensionType, JobId, MessageType, RequestId, TemplateId, VardiffKey, + CanDisconnect, CanFallback, CanShutdown, DownstreamId, ExtensionType, JobId, MessageType, + RequestId, TemplateId, VardiffKey, }, }; use tokio::{sync::broadcast, time::error::Elapsed}; +pub type JDCResult = Result>; + +#[derive(Debug)] +pub struct ChannelManager; + +#[derive(Debug)] +pub struct TemplateProvider; + +#[derive(Debug)] +pub struct JobDeclarator; + +#[derive(Debug)] +pub struct Upstream; + +#[derive(Debug)] +pub struct Downstream; + +#[derive(Debug)] +pub struct JDCError { + pub kind: JDCErrorKind, + pub action: Action, + _owner: PhantomData, +} + +#[derive(Debug, Clone, Copy)] +pub enum Action { + Log, + Disconnect(DownstreamId), + Fallback, + Shutdown, +} + +impl CanDisconnect for Downstream {} +impl CanDisconnect for ChannelManager {} + +impl CanFallback for Upstream {} +impl CanFallback for JobDeclarator {} +impl CanFallback for ChannelManager {} + +impl CanShutdown for ChannelManager {} +impl CanShutdown for TemplateProvider {} +impl CanShutdown for Downstream {} +impl CanShutdown for Upstream {} +impl CanShutdown for JobDeclarator {} + +impl JDCError { + pub fn log>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Log, + _owner: PhantomData, + } + } +} + +impl JDCError +where + O: CanDisconnect, +{ + pub fn disconnect>(kind: E, downstream_id: DownstreamId) -> Self { + Self { + kind: kind.into(), + action: Action::Disconnect(downstream_id), + _owner: PhantomData, + } + } +} + +impl JDCError +where + O: CanFallback, +{ + pub fn fallback>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Fallback, + _owner: PhantomData, + } + } +} + +impl JDCError +where + O: CanShutdown, +{ + pub fn shutdown>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Shutdown, + _owner: PhantomData, + } + } +} + #[derive(Debug)] pub enum ChannelSv2Error { ExtendedChannelClientSide(ExtendedChannelClientError), ExtendedChannelServerSide(ExtendedChannelServerError), + ExtranonceError(ExtendedExtranonceError), StandardChannelServerSide(StandardChannelError), GroupChannelServerSide(GroupChannelError), } #[derive(Debug)] -pub enum JDCError { +pub enum JDCErrorKind { /// Errors on bad CLI argument input. BadCliArgs, /// Errors on bad `config` TOML deserialize. @@ -67,8 +166,6 @@ pub enum JDCError { ChannelErrorSender, /// Broadcast channel receiver error BroadcastChannelErrorReceiver(broadcast::error::RecvError), - /// Shutdown - Shutdown, /// Network helpers error NetworkHelpersError(network_helpers::Error), /// Unexpected message @@ -133,13 +230,31 @@ pub enum JDCError { FailedToCreateBitcoinCoreTokioRuntime, /// Failed to send CoinbaseOutputConstraints message FailedToSendCoinbaseOutputConstraints, + /// Setup Connection Error + SetupConnectionError, + /// Endpoint changed + ChangeEndpoint, + /// Received upstream message during solo mining + UpstreamMessageDuringSoloMining, + /// Declare mining job error + DeclareMiningJobError, + /// Channel opening error + OpenMiningChannelError, + /// Standard channel opening error + OpenStandardMiningChannelError, + /// Close channel + CloseChannel, + /// Custom job error + CustomJobError, + /// Could not initiate subsystem + CouldNotInitiateSystem, } -impl std::error::Error for JDCError {} +impl std::error::Error for JDCErrorKind {} -impl fmt::Display for JDCError { +impl fmt::Display for JDCErrorKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use JDCError::*; + use JDCErrorKind::*; match self { BadCliArgs => write!(f, "Bad CLI arg input"), BadConfigDeserialize(ref e) => write!(f, "Bad `config` TOML deserialize: `{e:?}`"), @@ -154,7 +269,6 @@ impl fmt::Display for JDCError { write!(f, "Broadcast channel receive error: {e:?}") } ChannelErrorSender => write!(f, "Sender error"), - Shutdown => write!(f, "Shutdown"), NetworkHelpersError(ref e) => write!(f, "Network error: {e:?}"), UnexpectedMessage(extension_type, message_type) => { write!(f, "Unexpected Message: {extension_type} {message_type}") @@ -259,132 +373,151 @@ impl fmt::Display for JDCError { FailedToSendCoinbaseOutputConstraints => { write!(f, "Failed to send CoinbaseOutputConstraints message") } + SetupConnectionError => { + write!(f, "Failed to Setup connection") + } + ChangeEndpoint => { + write!(f, "Change endpoint") + } + OpenMiningChannelError => write!(f, "failed to open mining channel"), + OpenStandardMiningChannelError => write!(f, "failed to open standard mining channel"), + DeclareMiningJobError => write!(f, "job declaration rejected by server"), + UpstreamMessageDuringSoloMining => { + write!(f, "received upstream message during solo mining mode") + } + CloseChannel => write!(f, "channel closed by upstream"), + CustomJobError => write!(f, "Custom job not acknowledged"), + CouldNotInitiateSystem => write!(f, "Could not initiate subsystem"), } } } -impl JDCError { - fn is_non_critical_variant(&self) -> bool { - matches!( - self, - JDCError::LastNewPrevhashNotFound - | JDCError::FutureTemplateNotPresent - | JDCError::LastDeclareJobNotFound(_) - | JDCError::ActiveJobNotFound(_) - | JDCError::TokenNotFound - | JDCError::TemplateNotFound(_) - | JDCError::DownstreamNotFound(_) - | JDCError::VardiffNotFound(_) - | JDCError::TxDataError - | JDCError::FrameConversionError - | JDCError::FailedToCreateCustomJob - | JDCError::RequiredExtensionsNotSupported(_) - | JDCError::ServerRequiresUnsupportedExtensions(_) - ) - } - - /// Adds basic priority to error types: - /// todo: design a better error priority system. - pub fn is_critical(&self) -> bool { - if self.is_non_critical_variant() { - tracing::error!("Non-critical error: {self}"); - return false; - } - - true - } -} - -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: ParserError) -> Self { - JDCError::Parser(e) + JDCErrorKind::Parser(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: binary_sv2::Error) -> Self { - JDCError::BinarySv2(e) + JDCErrorKind::BinarySv2(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: noise_sv2::Error) -> Self { - JDCError::CodecNoise(e) + JDCErrorKind::CodecNoise(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: framing_sv2::Error) -> Self { - JDCError::FramingSv2(e) + JDCErrorKind::FramingSv2(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: std::io::Error) -> Self { - JDCError::Io(e) + JDCErrorKind::Io(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: std::num::ParseIntError) -> Self { - JDCError::ParseInt(e) + JDCErrorKind::ParseInt(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: ConfigError) -> Self { - JDCError::BadConfigDeserialize(e) + JDCErrorKind::BadConfigDeserialize(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(e: async_channel::RecvError) -> Self { - JDCError::ChannelErrorReceiver(e) + JDCErrorKind::ChannelErrorReceiver(e) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(value: network_helpers::Error) -> Self { - JDCError::NetworkHelpersError(value) + JDCErrorKind::NetworkHelpersError(value) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(value: stratum_apps::stratum_core::bitcoin::consensus::encode::Error) -> Self { - JDCError::BitcoinEncodeError(value) + JDCErrorKind::BitcoinEncodeError(value) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(_value: Elapsed) -> Self { Self::Timeout } } -impl HandlerErrorType for JDCError { +impl HandlerErrorType for JDCErrorKind { fn parse_error(error: ParserError) -> Self { - JDCError::Parser(error) + JDCErrorKind::Parser(error) } fn unexpected_message(extension_type: ExtensionType, message_type: MessageType) -> Self { - JDCError::UnexpectedMessage(extension_type, message_type) + JDCErrorKind::UnexpectedMessage(extension_type, message_type) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(value: ExtendedChannelClientError) -> Self { - JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelClientSide(value)) + JDCErrorKind::ChannelSv2(ChannelSv2Error::ExtendedChannelClientSide(value)) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(value: ExtendedChannelServerError) -> Self { - JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(value)) + JDCErrorKind::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(value)) } } -impl From for JDCError { +impl From for JDCErrorKind { fn from(value: StandardChannelError) -> Self { - JDCError::ChannelSv2(ChannelSv2Error::StandardChannelServerSide(value)) + JDCErrorKind::ChannelSv2(ChannelSv2Error::StandardChannelServerSide(value)) + } +} + +impl From for JDCErrorKind { + fn from(value: ExtendedExtranonceError) -> Self { + JDCErrorKind::ChannelSv2(ChannelSv2Error::ExtranonceError(value)) + } +} + +impl From for JDCErrorKind { + fn from(value: GroupChannelError) -> Self { + JDCErrorKind::ChannelSv2(ChannelSv2Error::GroupChannelServerSide(value)) + } +} + +impl HandlerErrorType for JDCError { + fn parse_error(error: ParserError) -> Self { + Self { + kind: JDCErrorKind::Parser(error), + action: Action::Log, + _owner: PhantomData, + } + } + + fn unexpected_message(extension_type: ExtensionType, message_type: MessageType) -> Self { + Self { + kind: JDCErrorKind::UnexpectedMessage(extension_type, message_type), + action: Action::Log, + _owner: PhantomData, + } + } +} + +impl std::fmt::Display for JDCError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "[{:?}/{:?}]", self.kind, self.action) } } diff --git a/miner-apps/jd-client/src/lib/job_declarator/message_handler.rs b/miner-apps/jd-client/src/lib/job_declarator/message_handler.rs index 6d12248fc..b62a0decf 100644 --- a/miner-apps/jd-client/src/lib/job_declarator/message_handler.rs +++ b/miner-apps/jd-client/src/lib/job_declarator/message_handler.rs @@ -8,14 +8,14 @@ use stratum_apps::stratum_core::{ use tracing::{info, warn}; use crate::{ - error::JDCError, + error::{self, JDCError, JDCErrorKind}, jd_mode::{set_jd_mode, JdMode}, job_declarator::JobDeclarator, }; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleCommonMessagesFromServerAsync for JobDeclarator { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_server( &self, @@ -39,10 +39,6 @@ impl HandleCommonMessagesFromServerAsync for JobDeclarator { }; set_jd_mode(jd_mode); - if jd_mode == JdMode::SoloMining { - return Err(JDCError::Shutdown); - } - Ok(()) } @@ -73,6 +69,6 @@ impl HandleCommonMessagesFromServerAsync for JobDeclarator { _tlv_fields: Option<&[Tlv]>, ) -> Result<(), Self::Error> { warn!("Received: {}", msg); - Err(JDCError::Shutdown) + Err(JDCError::fallback(JDCErrorKind::SetupConnectionError)) } } diff --git a/miner-apps/jd-client/src/lib/job_declarator/mod.rs b/miner-apps/jd-client/src/lib/job_declarator/mod.rs index bc2975ad1..5d4e4859c 100644 --- a/miner-apps/jd-client/src/lib/job_declarator/mod.rs +++ b/miner-apps/jd-client/src/lib/job_declarator/mod.rs @@ -26,7 +26,7 @@ use tracing::{debug, error, info, warn}; use crate::{ config::ConfigJDCMode, - error::JDCError, + error::{self, JDCError, JDCErrorKind, JDCResult}, io_task::spawn_io_tasks, status::{handle_error, Status, StatusSender}, utils::{get_setup_connection_message_jds, ShutdownMessage}, @@ -75,19 +75,22 @@ impl JobDeclarator { mode: ConfigJDCMode, task_manager: Arc, status_sender: Sender, - ) -> Result { + ) -> JDCResult { let (_, addr, pubkey, _) = upstreams; info!("Connecting to JD Server at {addr}"); let stream = tokio::time::timeout( tokio::time::Duration::from_secs(5), TcpStream::connect(addr), ) - .await??; + .await + .map_err(JDCError::fallback)? + .map_err(JDCError::fallback)?; info!("Connection established with JD Server at {addr} in mode: {mode:?}"); - let initiator = Initiator::from_raw_k(pubkey.into_bytes())?; + let initiator = Initiator::from_raw_k(pubkey.into_bytes()).map_err(JDCError::fallback)?; let (noise_stream_reader, noise_stream_writer) = NoiseTcpStream::::new(stream, HandshakeRole::Initiator(initiator)) - .await? + .await + .map_err(JDCError::fallback)? .into_split(); let status_sender = StatusSender::JobDeclarator(status_sender); @@ -178,15 +181,17 @@ impl JobDeclarator { res = self_clone_1.handle_job_declarator_message() => { if let Err(e) = res { error!(error = ?e, "Job Declarator message handling failed"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = self_clone_2.handle_channel_manager_message() => { if let Err(e) = res { error!(error = ?e, "Channel Manager message handling failed"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } }, } @@ -202,7 +207,7 @@ impl JobDeclarator { /// - Sends `SetupConnection` message. /// - Waits for and validates server response. /// - Completes SV2 protocol handshake. - pub async fn setup_connection(&mut self) -> Result<(), JDCError> { + pub async fn setup_connection(&mut self) -> JDCResult<(), error::JobDeclarator> { info!("Sending SetupConnection to JDS at {}", self.socket_address); let setup_connection = get_setup_connection_message_jds(&self.socket_address, &self.mode); @@ -210,14 +215,12 @@ impl JobDeclarator { .try_into() .map_err(|e| { error!(error=?e, "Failed to serialize SetupConnection message."); - JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - ) + JDCError::shutdown(e) })?; if let Err(e) = self.job_declarator_channel.jds_sender.send(sv2_frame).await { error!(error=?e, "Failed to send SetupConnection frame."); - return Err(JDCError::ChannelErrorSender); + return Err(JDCError::fallback(JDCErrorKind::ChannelErrorSender)); } debug!("SetupConnection frame sent successfully."); @@ -228,14 +231,12 @@ impl JobDeclarator { .await .map_err(|e| { error!(error=?e, "No handshake response received from Job declarator."); - JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - ) + JDCError::fallback(JDCErrorKind::ChannelErrorSender) })?; let header = incoming.get_header().ok_or_else(|| { error!("Handshake frame missing header."); - JDCError::FramingSv2(framing_sv2::Error::MissingHeader) + JDCError::fallback(framing_sv2::Error::MissingHeader) })?; debug!(ext_type = ?header.ext_type(), @@ -250,7 +251,7 @@ impl JobDeclarator { } // Handles messages coming from the Channel Manager and forwards them to the Job Declarator. - async fn handle_channel_manager_message(&self) -> Result<(), JDCError> { + async fn handle_channel_manager_message(&self) -> JDCResult<(), error::JobDeclarator> { match self .job_declarator_channel .channel_manager_receiver @@ -260,14 +261,14 @@ impl JobDeclarator { Ok(msg) => { debug!("Forwarding message from channel manager to JDS."); let message = AnyMessage::JobDeclaration(msg); - let sv2_frame: Sv2Frame = message.try_into()?; + let sv2_frame: Sv2Frame = message.try_into().map_err(JDCError::shutdown)?; self.job_declarator_channel .jds_sender .send(sv2_frame) .await .map_err(|e| { error!("Failed to send message to outbound channel: {:?}", e); - JDCError::ChannelErrorSender + JDCError::fallback(JDCErrorKind::ChannelErrorSender) })?; } Err(e) => { @@ -282,13 +283,18 @@ impl JobDeclarator { // - Forwards `JobDeclaration` messages to Channel Manager. // - Processes `Common` messages via handler. // - Rejects unsupported message types. - async fn handle_job_declarator_message(&mut self) -> Result<(), JDCError> { - let mut sv2_frame = self.job_declarator_channel.jds_receiver.recv().await?; + async fn handle_job_declarator_message(&mut self) -> JDCResult<(), error::JobDeclarator> { + let mut sv2_frame = self + .job_declarator_channel + .jds_receiver + .recv() + .await + .map_err(JDCError::fallback)?; debug!("Received SV2 frame from JDS."); let header = sv2_frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - JDCError::FramingSv2(framing_sv2::Error::MissingHeader) + JDCError::fallback(framing_sv2::Error::MissingHeader) })?; let message_type = header.msg_type(); let extension_type = header.ext_type(); @@ -300,15 +306,16 @@ impl JobDeclarator { .await?; } MessageType::JobDeclaration => { - let message = - JobDeclaration::try_from((message_type, sv2_frame.payload()))?.into_static(); + let message = JobDeclaration::try_from((message_type, sv2_frame.payload())) + .map_err(JDCError::fallback)? + .into_static(); self.job_declarator_channel .channel_manager_sender .send(message) .await .map_err(|e| { error!(error=?e, "Failed to send Job declaration message to channel manager."); - JDCError::ChannelErrorSender + JDCError::shutdown(JDCErrorKind::ChannelErrorSender) })?; } _ => { diff --git a/miner-apps/jd-client/src/lib/mod.rs b/miner-apps/jd-client/src/lib/mod.rs index 649048e48..30497d75c 100644 --- a/miner-apps/jd-client/src/lib/mod.rs +++ b/miner-apps/jd-client/src/lib/mod.rs @@ -15,7 +15,7 @@ use tracing::{debug, error, info, warn}; use crate::{ channel_manager::ChannelManager, config::{ConfigJDCMode, JobDeclaratorClientConfig}, - error::JDCError, + error::JDCErrorKind, jd_mode::{set_jd_mode, JdMode}, job_declarator::JobDeclarator, status::{State, Status}, @@ -105,7 +105,6 @@ impl JobDeclaratorClient { tp_to_channel_manager_receiver.clone(), channel_manager_to_downstream_sender.clone(), downstream_to_channel_manager_receiver, - status_sender.clone(), encoded_outputs.clone(), self.config.supported_extensions().to_vec(), self.config.required_extensions().to_vec(), @@ -410,7 +409,7 @@ impl JobDeclaratorClient { status_sender: Sender, mode: ConfigJDCMode, task_manager: Arc, - ) -> Result<(Upstream, JobDeclarator), JDCError> { + ) -> Result<(Upstream, JobDeclarator), JDCErrorKind> { const MAX_RETRIES: usize = 3; let upstream_len = upstreams.len(); for (i, upstream_addr) in upstreams.iter_mut().enumerate() { @@ -474,7 +473,7 @@ impl JobDeclaratorClient { } tracing::error!("All upstreams failed after {} retries each", MAX_RETRIES); - Err(JDCError::Shutdown) + Err(JDCErrorKind::CouldNotInitiateSystem) } } @@ -492,7 +491,7 @@ async fn try_initialize_single( mode: ConfigJDCMode, task_manager: Arc, config: &JobDeclaratorClientConfig, -) -> Result<(Upstream, JobDeclarator), JDCError> { +) -> Result<(Upstream, JobDeclarator), JDCErrorKind> { info!("Upstream connection in-progress at initialize single"); let upstream = Upstream::new( upstream_addr, @@ -503,7 +502,8 @@ async fn try_initialize_single( status_sender.clone(), config.required_extensions().to_vec(), ) - .await?; + .await + .map_err(|error| error.kind)?; info!("Upstream connection done at initialize single"); @@ -516,7 +516,8 @@ async fn try_initialize_single( task_manager.clone(), status_sender.clone(), ) - .await?; + .await + .map_err(|error| error.kind)?; Ok((upstream, job_declarator)) } diff --git a/miner-apps/jd-client/src/lib/status.rs b/miner-apps/jd-client/src/lib/status.rs index 8c65afa96..28ef49433 100644 --- a/miner-apps/jd-client/src/lib/status.rs +++ b/miner-apps/jd-client/src/lib/status.rs @@ -7,9 +7,9 @@ //! converted into shutdown signals, allowing coordinated teardown of tasks. use stratum_apps::utils::types::DownstreamId; -use tracing::{debug, error, warn}; +use tracing::{debug, warn}; -use crate::error::JDCError; +use crate::error::{Action, JDCError, JDCErrorKind}; /// Sender type for propagating status updates from different system components. #[derive(Debug, Clone)] @@ -97,16 +97,16 @@ pub enum State { /// A downstream connection has shut down with a reason. DownstreamShutdown { downstream_id: DownstreamId, - reason: JDCError, + reason: JDCErrorKind, }, /// Template receiver has shut down with a reason. - TemplateReceiverShutdown(JDCError), + TemplateReceiverShutdown(JDCErrorKind), /// Job declarator has shut down during fallback with a reason. - JobDeclaratorShutdownFallback(JDCError), + JobDeclaratorShutdownFallback(JDCErrorKind), /// Channel manager has shut down with a reason. - ChannelManagerShutdown(JDCError), + ChannelManagerShutdown(JDCErrorKind), /// Upstream has shut down during fallback with a reason. - UpstreamShutdownFallback(JDCError), + UpstreamShutdownFallback(JDCErrorKind), } /// Wrapper around a component’s state, sent as status updates across the system. @@ -116,43 +116,75 @@ pub struct Status { pub state: State, } -/// Sends a shutdown status for the given component, logging the error cause. #[cfg_attr(not(test), hotpath::measure)] -async fn send_status(sender: &StatusSender, error: JDCError) { - let state = match sender { - StatusSender::Downstream { downstream_id, .. } => { - warn!("Downstream [{downstream_id}] shutting down due to error: {error:?}"); - State::DownstreamShutdown { - downstream_id: *downstream_id, - reason: error, - } - } - StatusSender::TemplateReceiver(_) => { - warn!("Template Receiver shutting down due to error: {error:?}"); - State::TemplateReceiverShutdown(error) - } - StatusSender::ChannelManager(_) => { - warn!("ChannelManager shutting down due to error: {error:?}"); - State::ChannelManagerShutdown(error) +async fn send_status(sender: &StatusSender, error: JDCError) -> bool { + use Action::*; + + match error.action { + Log => { + warn!("Log-only error from {:?}: {:?}", sender, error.kind); + false } - StatusSender::Upstream(_) => { - warn!("Upstream shutting down due to error: {error:?}"); - State::UpstreamShutdownFallback(error) + + Disconnect(downstream_id) => { + let state = State::DownstreamShutdown { + downstream_id, + reason: error.kind, + }; + + if let Err(e) = sender.send(Status { state }).await { + tracing::error!( + "Failed to send downstream shutdown status from {:?}: {:?}", + sender, + e + ); + std::process::abort(); + } + matches!(sender, StatusSender::Downstream { .. }) } - StatusSender::JobDeclarator(_) => { - warn!("Job declarator shutting down due to error: {error:?}"); - State::JobDeclaratorShutdownFallback(error) + + Fallback => { + let state = State::UpstreamShutdownFallback(error.kind); + + if let Err(e) = sender.send(Status { state }).await { + tracing::error!("Failed to send fallback status from {:?}: {:?}", sender, e); + std::process::abort(); + } + matches!(sender, StatusSender::Upstream { .. }) } - }; - if let Err(e) = sender.send(Status { state }).await { - tracing::error!("Failed to send status update from {sender:?}: {e:?}"); + Shutdown => { + let state = match sender { + StatusSender::ChannelManager(_) => { + warn!( + "Channel Manager shutdown requested due to error: {:?}", + error.kind + ); + State::ChannelManagerShutdown(error.kind) + } + StatusSender::TemplateReceiver(_) => { + warn!( + "Template Receiver shutdown requested due to error: {:?}", + error.kind + ); + State::TemplateReceiverShutdown(error.kind) + } + _ => { + tracing::error!("Shutdown action received from invalid sender: {:?}", sender); + State::ChannelManagerShutdown(error.kind) + } + }; + + if let Err(e) = sender.send(Status { state }).await { + tracing::error!("Failed to send shutdown status from {:?}: {:?}", sender, e); + std::process::abort(); + } + true + } } } -/// Logs an error and propagates a corresponding shutdown status for the component. #[cfg_attr(not(test), hotpath::measure)] -pub async fn handle_error(sender: &StatusSender, e: JDCError) { - error!("Error in {:?}: {:?}", sender, e); - send_status(sender, e).await; +pub async fn handle_error(sender: &StatusSender, e: JDCError) -> bool { + send_status(sender, e).await } diff --git a/miner-apps/jd-client/src/lib/template_receiver/bitcoin_core.rs b/miner-apps/jd-client/src/lib/template_receiver/bitcoin_core.rs index 4db57eaaf..6942b5c56 100644 --- a/miner-apps/jd-client/src/lib/template_receiver/bitcoin_core.rs +++ b/miner-apps/jd-client/src/lib/template_receiver/bitcoin_core.rs @@ -1,5 +1,5 @@ use crate::{ - error::JDCError, + error::{self, JDCError, JDCErrorKind}, status::{handle_error, State, Status, StatusSender}, utils::ShutdownMessage, }; @@ -44,10 +44,9 @@ pub async fn connect_to_bitcoin_core( _ = cancellation_token_clone.cancelled() => { // turn status_sender into a StatusSender::TemplateReceiver let status_sender = StatusSender::TemplateReceiver(status_sender_clone); - handle_error( &status_sender, - JDCError::BitcoinCoreSv2CancellationTokenActivated, + JDCError::::shutdown(JDCErrorKind::BitcoinCoreSv2CancellationTokenActivated), ) .await; break; @@ -71,7 +70,7 @@ pub async fn connect_to_bitcoin_core( // we can't use handle_error here because we're not in a async context yet let _ = status_sender_clone.send_blocking(Status { state: State::TemplateReceiverShutdown( - JDCError::FailedToCreateBitcoinCoreTokioRuntime, + JDCErrorKind::FailedToCreateBitcoinCoreTokioRuntime, ), }); return; diff --git a/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/message_handler.rs b/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/message_handler.rs index a7d5d9e38..e24878d95 100644 --- a/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/message_handler.rs +++ b/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/message_handler.rs @@ -7,11 +7,14 @@ use stratum_apps::stratum_core::{ }; use tracing::{info, warn}; -use crate::{error::JDCError, template_receiver::sv2_tp::Sv2Tp}; +use crate::{ + error::{self, JDCError, JDCErrorKind}, + template_receiver::sv2_tp::Sv2Tp, +}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleCommonMessagesFromServerAsync for Sv2Tp { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_server( &self, @@ -58,6 +61,6 @@ impl HandleCommonMessagesFromServerAsync for Sv2Tp { _tlv_fields: Option<&[Tlv]>, ) -> Result<(), Self::Error> { warn!("Received: {}", msg); - Err(JDCError::Shutdown) + Err(JDCError::shutdown(JDCErrorKind::SetupConnectionError)) } } diff --git a/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/mod.rs b/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/mod.rs index 500294600..c160fe5c2 100644 --- a/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/mod.rs +++ b/miner-apps/jd-client/src/lib/template_receiver/sv2_tp/mod.rs @@ -21,7 +21,7 @@ use stratum_apps::{ codec_sv2::HandshakeRole, framing_sv2, handlers_sv2::HandleCommonMessagesFromServerAsync, - noise_sv2::Initiator, + noise_sv2::{self, Initiator}, parsers_sv2::{AnyMessage, TemplateDistribution}, }, task_manager::TaskManager, @@ -34,7 +34,7 @@ use tokio::{net::TcpStream, sync::broadcast}; use tracing::{debug, error, info, warn}; use crate::{ - error::JDCError, + error::{self, JDCError, JDCErrorKind, JDCResult}, io_task::spawn_io_tasks, status::{handle_error, Status, StatusSender}, utils::{get_setup_connection_message_tp, ShutdownMessage}, @@ -96,7 +96,7 @@ impl Sv2Tp { notify_shutdown: broadcast::Sender, task_manager: Arc, status_sender: Sender, - ) -> Result { + ) -> JDCResult { const MAX_RETRIES: usize = 3; for attempt in 1..=MAX_RETRIES { @@ -111,7 +111,8 @@ impl Sv2Tp { debug!(attempt, "Using anonymous initiator (no public key)"); Initiator::without_pk() } - }?; + } + .map_err(JDCError::shutdown)?; match TcpStream::connect(tp_address.as_str()).await { Ok(stream) => { @@ -179,7 +180,7 @@ impl Sv2Tp { } error!("Exhausted all connection attempts, shutting down TemplateReceiver"); - Err(JDCError::Shutdown) + Err(JDCError::shutdown(JDCErrorKind::CouldNotInitiateSystem)) } /// Start unified message loop for template receiver. @@ -227,15 +228,17 @@ impl Sv2Tp { res = self_clone_1.handle_template_provider_message() => { if let Err(e) = res { error!("TemplateReceiver template provider handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = self_clone_2.handle_channel_manager_message() => { if let Err(e) = res { error!("TemplateReceiver channel manager handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } }, } @@ -251,13 +254,20 @@ impl Sv2Tp { /// - `Common` messages → handled locally /// - `TemplateDistribution` messages → forwarded to channel manager /// - Unsupported messages → logged and ignored - pub async fn handle_template_provider_message(&mut self) -> Result<(), JDCError> { - let mut sv2_frame = self.sv2_tp_channel.tp_receiver.recv().await?; + pub async fn handle_template_provider_message( + &mut self, + ) -> JDCResult<(), error::TemplateProvider> { + let mut sv2_frame = self + .sv2_tp_channel + .tp_receiver + .recv() + .await + .map_err(JDCError::shutdown)?; debug!("Received SV2 frame from Template provider."); let header = sv2_frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - JDCError::FramingSv2(framing_sv2::Error::MissingHeader) + JDCError::shutdown(framing_sv2::Error::MissingHeader) })?; let message_type = header.msg_type(); let extension_type = header.ext_type(); @@ -273,7 +283,8 @@ impl Sv2Tp { .await?; } MessageType::TemplateDistribution => { - let message = TemplateDistribution::try_from((message_type, sv2_frame.payload()))? + let message = TemplateDistribution::try_from((message_type, sv2_frame.payload())) + .map_err(JDCError::shutdown)? .into_static(); self.sv2_tp_channel .channel_manager_sender @@ -281,7 +292,7 @@ impl Sv2Tp { .await .map_err(|e| { error!(error=?e, "Failed to send template distribution message to channel manager."); - JDCError::ChannelErrorSender + JDCError::shutdown(JDCErrorKind::ChannelErrorSender) })?; } _ => { @@ -294,31 +305,40 @@ impl Sv2Tp { /// Handle messages from channel manager → template provider. /// /// Forwards outbound frames upstream - pub async fn handle_channel_manager_message(&self) -> Result<(), JDCError> { + pub async fn handle_channel_manager_message(&self) -> JDCResult<(), error::TemplateProvider> { let msg = AnyMessage::TemplateDistribution( - self.sv2_tp_channel.channel_manager_receiver.recv().await?, + self.sv2_tp_channel + .channel_manager_receiver + .recv() + .await + .map_err(JDCError::shutdown)?, ); debug!("Forwarding message from channel manager to outbound_tx"); - let sv2_frame: Sv2Frame = msg.try_into()?; + let sv2_frame: Sv2Frame = msg.try_into().map_err(JDCError::shutdown)?; self.sv2_tp_channel .tp_sender .send(sv2_frame) .await - .map_err(|_| JDCError::ChannelErrorSender)?; + .map_err(|_| JDCError::shutdown(JDCErrorKind::ChannelErrorSender))?; Ok(()) } // Performs the initial handshake with template provider. - pub async fn setup_connection(&mut self, addr: String) -> Result<(), JDCError> { + pub async fn setup_connection( + &mut self, + addr: String, + ) -> JDCResult<(), error::TemplateProvider> { let socket: SocketAddr = addr.parse().map_err(|_| { error!(%addr, "Invalid socket address"); - JDCError::InvalidSocketAddress(addr.clone()) + JDCError::shutdown(JDCErrorKind::InvalidSocketAddress(addr.clone())) })?; info!(%socket, "Building setup connection message for upstream"); let setup_msg = get_setup_connection_message_tp(socket); - let frame: Sv2Frame = Message::Common(setup_msg.into()).try_into()?; + let frame: Sv2Frame = Message::Common(setup_msg.into()) + .try_into() + .map_err(JDCError::shutdown)?; info!("Sending setup connection message to upstream"); self.sv2_tp_channel @@ -327,20 +347,18 @@ impl Sv2Tp { .await .map_err(|_| { error!("Failed to send setup connection message upstream"); - JDCError::ChannelErrorSender + JDCError::shutdown(JDCErrorKind::ChannelErrorSender) })?; info!("Waiting for upstream handshake response"); let mut incoming: Sv2Frame = self.sv2_tp_channel.tp_receiver.recv().await.map_err(|e| { error!(?e, "Upstream connection closed during handshake"); - JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - ) + JDCError::shutdown(noise_sv2::Error::ExpectedIncomingHandshakeMessage) })?; let header = incoming.get_header().ok_or_else(|| { error!("Handshake frame missing header"); - JDCError::FramingSv2(framing_sv2::Error::MissingHeader) + JDCError::shutdown(framing_sv2::Error::MissingHeader) })?; debug!(ext_type = ?header.ext_type(), msg_type = ?header.msg_type(), diff --git a/miner-apps/jd-client/src/lib/upstream/message_handler.rs b/miner-apps/jd-client/src/lib/upstream/message_handler.rs index fcf426617..2181bfa1d 100644 --- a/miner-apps/jd-client/src/lib/upstream/message_handler.rs +++ b/miner-apps/jd-client/src/lib/upstream/message_handler.rs @@ -7,11 +7,14 @@ use stratum_apps::stratum_core::{ }; use tracing::{info, warn}; -use crate::{error::JDCError, upstream::Upstream}; +use crate::{ + error::{self, JDCError, JDCErrorKind}, + upstream::Upstream, +}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleCommonMessagesFromServerAsync for Upstream { - type Error = JDCError; + type Error = JDCError; fn get_negotiated_extensions_with_server( &self, @@ -58,6 +61,6 @@ impl HandleCommonMessagesFromServerAsync for Upstream { _tlv_fields: Option<&[Tlv]>, ) -> Result<(), Self::Error> { warn!("Received: {}", msg); - Err(JDCError::Shutdown) + Err(JDCError::fallback(JDCErrorKind::SetupConnectionError)) } } diff --git a/miner-apps/jd-client/src/lib/upstream/mod.rs b/miner-apps/jd-client/src/lib/upstream/mod.rs index 402718456..1116745ca 100644 --- a/miner-apps/jd-client/src/lib/upstream/mod.rs +++ b/miner-apps/jd-client/src/lib/upstream/mod.rs @@ -34,7 +34,7 @@ use tokio::{ use tracing::{debug, error, info, warn}; use crate::{ - error::JDCError, + error::{self, JDCError, JDCErrorKind, JDCResult}, io_task::spawn_io_tasks, status::{handle_error, Status, StatusSender}, utils::{get_setup_connection_message, ShutdownMessage}, @@ -85,19 +85,22 @@ impl Upstream { task_manager: Arc, status_sender: Sender, required_extensions: Vec, - ) -> Result { + ) -> JDCResult { let (addr, _, pubkey, _) = upstreams; let stream = tokio::time::timeout( tokio::time::Duration::from_secs(5), TcpStream::connect(addr), ) - .await??; + .await + .map_err(JDCError::fallback)? + .map_err(JDCError::fallback)?; info!("Connected to upstream at {}", addr); - let initiator = Initiator::from_raw_k(pubkey.into_bytes())?; + let initiator = Initiator::from_raw_k(pubkey.into_bytes()).map_err(JDCError::fallback)?; debug!("Begin with noise setup in upstream connection"); let (noise_stream_reader, noise_stream_writer) = NoiseTcpStream::::new(stream, HandshakeRole::Initiator(initiator)) - .await? + .await + .map_err(JDCError::fallback)? .into_split(); let status_sender = StatusSender::Upstream(status_sender); @@ -136,19 +139,20 @@ impl Upstream { &mut self, min_version: u16, max_version: u16, - ) -> Result<(), JDCError> { + ) -> JDCResult<(), error::Upstream> { info!("Upstream: initiating SV2 handshake..."); - let setup_connection = get_setup_connection_message(min_version, max_version)?; + let setup_connection = + get_setup_connection_message(min_version, max_version).map_err(JDCError::shutdown)?; debug!(?setup_connection, "Prepared `SetupConnection` message"); - let sv2_frame: Sv2Frame = Message::Common(setup_connection.into()).try_into()?; + let sv2_frame: Sv2Frame = Message::Common(setup_connection.into()) + .try_into() + .map_err(JDCError::shutdown)?; debug!(?sv2_frame, "Encoded `SetupConnection` frame"); // Send SetupConnection if let Err(e) = self.upstream_channel.upstream_sender.send(sv2_frame).await { error!(?e, "Failed to send `SetupConnection` frame to upstream"); - return Err(JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - )); + return Err(JDCError::fallback(JDCErrorKind::ChannelErrorSender)); } info!("Sent `SetupConnection` to upstream, awaiting response..."); @@ -159,9 +163,7 @@ impl Upstream { } Err(e) => { error!(?e, "Upstream closed connection during handshake"); - return Err(JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - )); + return Err(JDCError::fallback(e)); } }; @@ -170,7 +172,7 @@ impl Upstream { let header = incoming.get_header().ok_or_else(|| { error!("Handshake frame missing header"); - JDCError::FramingSv2(framing_sv2::Error::MissingHeader) + JDCError::fallback(framing_sv2::Error::MissingHeader) })?; info!(ext_type = ?header.ext_type(), msg_type = ?header.msg_type(), "Dispatching inbound handshake message"); @@ -188,7 +190,7 @@ impl Upstream { /// Send `RequestExtensions` message to upstream. /// The supported extensions are stored for potential retry if the server requires additional /// extensions. - async fn send_request_extensions(&mut self) -> Result<(), JDCError> { + async fn send_request_extensions(&mut self) -> JDCResult<(), error::Upstream> { info!( "Sending RequestExtensions to upstream with required extensions: {:?}", self.required_extensions @@ -198,7 +200,7 @@ impl Upstream { } let requested_extensions = - Seq064K::new(self.required_extensions.clone()).map_err(JDCError::BinarySv2)?; + Seq064K::new(self.required_extensions.clone()).map_err(JDCError::shutdown)?; let request_extensions = RequestExtensions { request_id: 0, @@ -210,8 +212,9 @@ impl Upstream { self.required_extensions ); - let sv2_frame: Sv2Frame = - AnyMessage::Extensions(request_extensions.into_static().into()).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Extensions(request_extensions.into_static().into()) + .try_into() + .map_err(JDCError::shutdown)?; self.upstream_channel .upstream_sender @@ -219,7 +222,7 @@ impl Upstream { .await .map_err(|e| { error!(?e, "Failed to send RequestExtensions to upstream"); - JDCError::ChannelErrorSender + JDCError::fallback(JDCErrorKind::ChannelErrorSender) })?; info!("Sent RequestExtensions to upstream"); @@ -290,15 +293,17 @@ impl Upstream { res = self_clone_1.handle_pool_message_frame() => { if let Err(e) = res { error!(error = ?e, "Upstream: error handling pool message."); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = self_clone_2.handle_channel_manager_message_frame() => { if let Err(e) = res { error!(error = ?e, "Upstream: error handling channel manager message."); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } @@ -315,12 +320,17 @@ impl Upstream { // - `Common` messages → handled locally // - `Mining` messages → forwarded to channel manager // - Unsupported → error - async fn handle_pool_message_frame(&mut self) -> Result<(), JDCError> { + async fn handle_pool_message_frame(&mut self) -> JDCResult<(), error::Upstream> { debug!("Received SV2 frame from upstream."); - let mut sv2_frame = self.upstream_channel.upstream_receiver.recv().await?; + let mut sv2_frame = self + .upstream_channel + .upstream_receiver + .recv() + .await + .map_err(JDCError::fallback)?; let header = sv2_frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - JDCError::FramingSv2(framing_sv2::Error::MissingHeader) + JDCError::fallback(framing_sv2::Error::MissingHeader) })?; let message_type = header.msg_type(); let extension_type = header.ext_type(); @@ -338,7 +348,7 @@ impl Upstream { .await .map_err(|e| { error!(error=?e, "Failed to send mining message to channel manager."); - JDCError::ChannelErrorSender + JDCError::shutdown(JDCErrorKind::ChannelErrorSender) })?; } _ => { @@ -351,7 +361,7 @@ impl Upstream { // Handle outbound frames from channel manager → upstream. // // Forwards messages upstream. - async fn handle_channel_manager_message_frame(&mut self) -> Result<(), JDCError> { + async fn handle_channel_manager_message_frame(&mut self) -> JDCResult<(), error::Upstream> { match self.upstream_channel.channel_manager_receiver.recv().await { Ok(sv2_frame) => { debug!("Received sv2 frame from channel manager, forwarding upstream."); @@ -361,9 +371,7 @@ impl Upstream { .await .map_err(|e| { error!(error=?e, "Failed to send sv2 frame to upstream."); - JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - ) + JDCError::fallback(JDCErrorKind::ChannelErrorSender) })?; } Err(e) => { diff --git a/miner-apps/jd-client/src/lib/utils.rs b/miner-apps/jd-client/src/lib/utils.rs index de7d1b861..58b61e22f 100644 --- a/miner-apps/jd-client/src/lib/utils.rs +++ b/miner-apps/jd-client/src/lib/utils.rs @@ -29,7 +29,7 @@ use stratum_apps::{ utils::types::{ChannelId, DownstreamId, Hashrate, JobId}, }; -use crate::{config::ConfigJDCMode, error::JDCError}; +use crate::{config::ConfigJDCMode, error::JDCErrorKind}; /// Represents a message that can trigger shutdown of various system components. #[derive(Debug, Clone)] @@ -54,7 +54,7 @@ pub enum ShutdownMessage { pub fn get_setup_connection_message( min_version: u16, max_version: u16, -) -> Result, JDCError> { +) -> Result, JDCErrorKind> { let endpoint_host = "0.0.0.0".to_string().into_bytes().try_into()?; let vendor = String::new().try_into()?; let hardware_version = String::new().try_into()?; diff --git a/miner-apps/translator/src/args.rs b/miner-apps/translator/src/args.rs index 4cbb0886b..22a3947a7 100644 --- a/miner-apps/translator/src/args.rs +++ b/miner-apps/translator/src/args.rs @@ -6,7 +6,7 @@ use clap::Parser; use ext_config::{Config, File, FileFormat}; use std::path::PathBuf; use tracing::error; -use translator_sv2::{config::TranslatorConfig, error::TproxyError}; +use translator_sv2::{config::TranslatorConfig, error::TproxyErrorKind}; /// Holds the parsed CLI arguments. #[derive(Parser, Debug)] @@ -29,14 +29,14 @@ pub struct Args { /// Process CLI args, if any. #[allow(clippy::result_large_err)] -pub fn process_cli_args() -> Result { +pub fn process_cli_args() -> Result { // Parse CLI arguments let args = Args::parse(); // Build configuration from the provided file path let config_path = args.config_path.to_str().ok_or_else(|| { error!("Invalid configuration path."); - TproxyError::BadCliArgs + TproxyErrorKind::BadCliArgs })?; let settings = Config::builder() diff --git a/miner-apps/translator/src/lib/error.rs b/miner-apps/translator/src/lib/error.rs index ba6496d53..b86a044f8 100644 --- a/miner-apps/translator/src/lib/error.rs +++ b/miner-apps/translator/src/lib/error.rs @@ -9,21 +9,123 @@ //! asynchronous channels. use ext_config::ConfigError; -use std::{fmt, sync::PoisonError}; +use std::{ + fmt::{self, Formatter}, + marker::PhantomData, + sync::PoisonError, +}; use stratum_apps::{ stratum_core::{ binary_sv2, framing_sv2, handlers_sv2::HandlerErrorType, noise_sv2, - parsers_sv2::{self, ParserError}, + parsers_sv2::{self, ParserError, TlvError}, sv1_api::server_to_client::SetDifficulty, }, - utils::types::{ExtensionType, MessageType}, + utils::types::{ + CanDisconnect, CanFallback, CanShutdown, DownstreamId, ExtensionType, MessageType, + }, }; use tokio::sync::broadcast; +pub type TproxyResult = Result>; + +#[derive(Debug)] +pub struct ChannelManager; + +#[derive(Debug)] +pub struct Sv1Server; + +#[derive(Debug)] +pub struct Upstream; + +#[derive(Debug)] +pub struct Downstream; + +#[derive(Debug)] +pub struct TproxyError { + pub kind: TproxyErrorKind, + pub action: Action, + _owner: PhantomData, +} + +#[derive(Debug, Clone, Copy)] +pub enum Action { + Log, + Disconnect(DownstreamId), + Fallback, + Shutdown, +} + +impl CanDisconnect for Downstream {} +impl CanDisconnect for Sv1Server {} +impl CanDisconnect for ChannelManager {} + +impl CanFallback for Upstream {} +impl CanFallback for ChannelManager {} + +impl CanShutdown for ChannelManager {} +impl CanShutdown for Sv1Server {} +impl CanShutdown for Downstream {} +impl CanShutdown for Upstream {} + +impl TproxyError { + pub fn log>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Log, + _owner: PhantomData, + } + } +} + +impl TproxyError +where + O: CanDisconnect, +{ + pub fn disconnect>(kind: E, downstream_id: DownstreamId) -> Self { + Self { + kind: kind.into(), + action: Action::Disconnect(downstream_id), + _owner: PhantomData, + } + } +} + +impl TproxyError +where + O: CanFallback, +{ + pub fn fallback>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Fallback, + _owner: PhantomData, + } + } +} + +impl TproxyError +where + O: CanShutdown, +{ + pub fn shutdown>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Shutdown, + _owner: PhantomData, + } + } +} + +impl From> for TproxyErrorKind { + fn from(value: TproxyError) -> Self { + value.kind + } +} + #[derive(Debug)] -pub enum TproxyError { +pub enum TproxyErrorKind { /// Generic SV1 protocol error SV1Error, /// Error from the network helpers library @@ -64,10 +166,6 @@ pub enum TproxyError { JobNotFound, /// Invalid merkle root during share validation InvalidMerkleRoot, - /// Shutdown signal received - Shutdown, - /// Fallback - Fallback, /// Pending channel not found for the given request ID PendingChannelNotFound(u32), /// Server does not support required extensions @@ -82,13 +180,19 @@ pub enum TproxyError { DownstreamNotFound(u32), /// Error about TLV encoding/decoding TlvError(parsers_sv2::TlvError), + /// Setup connection error + SetupConnectionError, + /// Open mining channel error + OpenMiningChannelError, + /// Could not initiate subsystem + CouldNotInitiateSystem, } -impl std::error::Error for TproxyError {} +impl std::error::Error for TproxyErrorKind {} -impl fmt::Display for TproxyError { +impl fmt::Display for TproxyErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use TproxyError::*; + use TproxyErrorKind::*; match self { General(e) => write!(f, "{e}"), BadCliArgs => write!(f, "Bad CLI arg input"), @@ -117,8 +221,6 @@ impl fmt::Display for TproxyError { } JobNotFound => write!(f, "Job not found during share validation"), InvalidMerkleRoot => write!(f, "Invalid merkle root during share validation"), - Shutdown => write!(f, "Shutdown signal"), - Fallback => write!(f, "Fallback signal"), PendingChannelNotFound(request_id) => { write!(f, "No pending channel found for request_id: {}", request_id) } @@ -145,111 +247,144 @@ impl fmt::Display for TproxyError { "Downstream id associated to request id: {request_id} not found" ), TlvError(ref e) => write!(f, "TLV error: {e:?}"), + OpenMiningChannelError => write!(f, "failed to open mining channel"), + SetupConnectionError => write!(f, "failed to setup connection with upstream"), + CouldNotInitiateSystem => write!(f, "Could not initiate subsystem"), } } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: binary_sv2::Error) -> Self { - TproxyError::BinarySv2(e) + TproxyErrorKind::BinarySv2(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: noise_sv2::Error) -> Self { - TproxyError::CodecNoise(e) + TproxyErrorKind::CodecNoise(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: framing_sv2::Error) -> Self { - TproxyError::FramingSv2(e) + TproxyErrorKind::FramingSv2(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: std::io::Error) -> Self { - TproxyError::Io(e) + TproxyErrorKind::Io(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: std::num::ParseIntError) -> Self { - TproxyError::ParseInt(e) + TproxyErrorKind::ParseInt(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: serde_json::Error) -> Self { - TproxyError::BadSerdeJson(e) + TproxyErrorKind::BadSerdeJson(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: ConfigError) -> Self { - TproxyError::BadConfigDeserialize(e) + TproxyErrorKind::BadConfigDeserialize(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: async_channel::RecvError) -> Self { - TproxyError::ChannelErrorReceiver(e) + TproxyErrorKind::ChannelErrorReceiver(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: tokio::sync::broadcast::error::RecvError) -> Self { - TproxyError::TokioChannelErrorRecv(e) + TproxyErrorKind::TokioChannelErrorRecv(e) } } //*** LOCK ERRORS *** -impl From> for TproxyError { +impl From> for TproxyErrorKind { fn from(_e: PoisonError) -> Self { - TproxyError::PoisonLock + TproxyErrorKind::PoisonLock } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(e: SetDifficulty) -> Self { - TproxyError::SetDifficultyToMessage(e) + TproxyErrorKind::SetDifficultyToMessage(e) } } -impl<'a> From> for TproxyError { +impl<'a> From> for TproxyErrorKind { fn from(_: stratum_apps::stratum_core::sv1_api::error::Error<'a>) -> Self { - TproxyError::SV1Error + TproxyErrorKind::SV1Error } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(value: stratum_apps::network_helpers::Error) -> Self { - TproxyError::NetworkHelpersError(value) + TproxyErrorKind::NetworkHelpersError(value) } } impl From - for TproxyError + for TproxyErrorKind { fn from( e: stratum_apps::stratum_core::stratum_translation::error::StratumTranslationError, ) -> Self { - TproxyError::TranslatorCore(e) + TproxyErrorKind::TranslatorCore(e) } } -impl From for TproxyError { +impl From for TproxyErrorKind { fn from(value: ParserError) -> Self { - TproxyError::ParserError(value) + TproxyErrorKind::ParserError(value) } } -impl HandlerErrorType for TproxyError { +impl From for TproxyErrorKind { + fn from(value: TlvError) -> Self { + TproxyErrorKind::TlvError(value) + } +} + +impl HandlerErrorType for TproxyErrorKind { fn parse_error(error: ParserError) -> Self { - TproxyError::ParserError(error) + TproxyErrorKind::ParserError(error) } fn unexpected_message(extension_type: ExtensionType, message_type: MessageType) -> Self { - TproxyError::UnexpectedMessage(extension_type, message_type) + TproxyErrorKind::UnexpectedMessage(extension_type, message_type) + } +} + +impl HandlerErrorType for TproxyError { + fn parse_error(error: ParserError) -> Self { + Self { + kind: TproxyErrorKind::ParserError(error), + action: Action::Log, + _owner: PhantomData, + } + } + + fn unexpected_message(extension_type: ExtensionType, message_type: MessageType) -> Self { + Self { + kind: TproxyErrorKind::UnexpectedMessage(extension_type, message_type), + action: Action::Log, + _owner: PhantomData, + } + } +} + +impl std::fmt::Display for TproxyError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "[{:?}/{:?}]", self.kind, self.action) } } diff --git a/miner-apps/translator/src/lib/mod.rs b/miner-apps/translator/src/lib/mod.rs index 8362339e5..9f710c6d6 100644 --- a/miner-apps/translator/src/lib/mod.rs +++ b/miner-apps/translator/src/lib/mod.rs @@ -22,7 +22,7 @@ pub use stratum_apps::stratum_core::sv1_api::server_to_client; use config::TranslatorConfig; use crate::{ - error::TproxyError, + error::TproxyErrorKind, status::{State, Status}, sv1::sv1_server::sv1_server::Sv1Server, sv2::{channel_manager::ChannelMode, ChannelManager, Upstream}, @@ -239,7 +239,7 @@ impl TranslatorSv2 { task_manager: Arc, sv1_server_instance: Arc, required_extensions: Vec, - ) -> Result<(), TproxyError> { + ) -> Result<(), TproxyErrorKind> { const MAX_RETRIES: usize = 3; let upstream_len = upstreams.len(); for (i, upstream_entry) in upstreams.iter_mut().enumerate() { @@ -286,7 +286,7 @@ impl TranslatorSv2 { .await { error!("SV1 server startup failed: {e:?}"); - return Err(e); + return Err(e.kind); } upstream_entry.tried_or_flagged = true; @@ -310,7 +310,7 @@ impl TranslatorSv2 { } tracing::error!("All upstreams failed after {} retries each", MAX_RETRIES); - Err(TproxyError::Shutdown) + Err(TproxyErrorKind::CouldNotInitiateSystem) } } @@ -326,7 +326,7 @@ async fn try_initialize_upstream( shutdown_complete_tx: mpsc::Sender<()>, task_manager: Arc, required_extensions: Vec, -) -> Result<(), TproxyError> { +) -> Result<(), TproxyErrorKind> { let upstream = Upstream::new( upstream_addr, upstream_to_channel_manager_sender, diff --git a/miner-apps/translator/src/lib/status.rs b/miner-apps/translator/src/lib/status.rs index 11252b8ae..784ef3939 100644 --- a/miner-apps/translator/src/lib/status.rs +++ b/miner-apps/translator/src/lib/status.rs @@ -7,9 +7,9 @@ //! tagged with a [`Sender`] variant that identifies the source subsystem. use stratum_apps::utils::types::DownstreamId; -use tracing::{debug, error, warn}; +use tracing::{debug, warn}; -use crate::error::TproxyError; +use crate::error::{Action, TproxyError, TproxyErrorKind}; /// Identifies the component that originated a [`Status`] update. /// @@ -64,14 +64,14 @@ pub enum State { /// Downstream task exited or encountered an unrecoverable error. DownstreamShutdown { downstream_id: DownstreamId, - reason: TproxyError, + reason: TproxyErrorKind, }, /// SV1 server listener exited unexpectedly. - Sv1ServerShutdown(TproxyError), + Sv1ServerShutdown(TproxyErrorKind), /// Channel manager shut down (SV2 bridge manager). - ChannelManagerShutdown(TproxyError), + ChannelManagerShutdown(TproxyErrorKind), /// Upstream SV2 connection closed or failed. - UpstreamShutdown(TproxyError), + UpstreamShutdown(TproxyErrorKind), } /// A message reporting the current [`State`] of a component. @@ -80,42 +80,72 @@ pub struct Status { pub state: State, } -/// Constructs and sends a [`Status`] update based on the [`Sender`] and error context. #[cfg_attr(not(test), hotpath::measure)] -async fn send_status(sender: &StatusSender, error: TproxyError) { - let state = match sender { - StatusSender::Downstream { downstream_id, .. } => { - warn!("Downstream [{downstream_id}] shutting down due to error: {error:?}"); - State::DownstreamShutdown { - downstream_id: *downstream_id, - reason: error, - } - } - StatusSender::Sv1Server(_) => { - warn!("Sv1Server shutting down due to error: {error:?}"); - State::Sv1ServerShutdown(error) +async fn send_status(sender: &StatusSender, error: TproxyError) -> bool { + use Action::*; + + match error.action { + Log => { + warn!("Log-only error from {:?}: {:?}", sender, error.kind); + false } - StatusSender::ChannelManager(_) => { - warn!("ChannelManager shutting down due to error: {error:?}"); - State::ChannelManagerShutdown(error) + + Disconnect(downstream_id) => { + let state = State::DownstreamShutdown { + downstream_id, + reason: error.kind, + }; + + if let Err(e) = sender.send(Status { state }).await { + tracing::error!( + "Failed to send downstream shutdown status from {:?}: {:?}", + sender, + e + ); + std::process::abort(); + } + matches!(sender, StatusSender::Downstream { .. }) } - StatusSender::Upstream(_) => { - warn!("Upstream shutting down due to error: {error:?}"); - State::UpstreamShutdown(error) + + Fallback => { + let state = State::UpstreamShutdown(error.kind); + + if let Err(e) = sender.send(Status { state }).await { + tracing::error!("Failed to send fallback status from {:?}: {:?}", sender, e); + std::process::abort(); + } + matches!(sender, StatusSender::Upstream { .. }) } - }; - if let Err(e) = sender.send(Status { state }).await { - error!("Failed to send status update from {sender:?}: {e:?}"); + Shutdown => { + let state = match sender { + StatusSender::ChannelManager(_) => { + warn!( + "Channel Manager shutdown requested due to error: {:?}", + error.kind + ); + State::ChannelManagerShutdown(error.kind) + } + StatusSender::Sv1Server(_) => { + warn!( + "Sv1Server shutdown requested due to error: {:?}", + error.kind + ); + State::Sv1ServerShutdown(error.kind) + } + _ => State::ChannelManagerShutdown(error.kind), + }; + + if let Err(e) = sender.send(Status { state }).await { + tracing::error!("Failed to send shutdown status from {:?}: {:?}", sender, e); + std::process::abort(); + } + true + } } } -/// Centralized error dispatcher for the Translator. -/// -/// Used by the `handle_result!` macro across the codebase. -/// Decides whether the task should `Continue` or `Break` based on the error type and source. #[cfg_attr(not(test), hotpath::measure)] -pub async fn handle_error(sender: &StatusSender, e: TproxyError) { - error!("Error in {:?}: {:?}", sender, e); - send_status(sender, e).await; +pub async fn handle_error(sender: &StatusSender, e: TproxyError) -> bool { + send_status(sender, e).await } diff --git a/miner-apps/translator/src/lib/sv1/downstream/downstream.rs b/miner-apps/translator/src/lib/sv1/downstream/downstream.rs index 5ca226fda..149feec49 100644 --- a/miner-apps/translator/src/lib/sv1/downstream/downstream.rs +++ b/miner-apps/translator/src/lib/sv1/downstream/downstream.rs @@ -1,6 +1,6 @@ use super::DownstreamMessages; use crate::{ - error::TproxyError, + error::{self, TproxyError, TproxyErrorKind, TproxyResult}, status::{handle_error, StatusSender}, sv1::{ downstream::{channel::DownstreamChannelState, data::DownstreamData}, @@ -136,8 +136,9 @@ impl Downstream { res = Self::handle_downstream_message(self.clone()) => { if let Err(e) = res { error!("Downstream {downstream_id}: error in downstream message handler: {e:?}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } @@ -145,8 +146,9 @@ impl Downstream { res = Self::handle_sv1_server_message(self.clone(),&mut sv1_server_receiver) => { if let Err(e) = res { error!("Downstream {downstream_id}: error in server message handler: {e:?}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } @@ -186,7 +188,7 @@ impl Downstream { Option, json_rpc::Message, )>, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Downstream> { match sv1_server_receiver.recv().await { Ok((channel_id, downstream_id, message)) => { let (my_channel_id, my_downstream_id, handshake_complete) = @@ -271,7 +273,7 @@ impl Downstream { "Down: Failed to send mining.set_difficulty to downstream: {:?}", e ); - TproxyError::ChannelErrorSender + TproxyError::disconnect(TproxyErrorKind::ChannelErrorSender, downstream_id.unwrap_or(0)) })?; } @@ -283,7 +285,7 @@ impl Downstream { .await .map_err(|e| { error!("Down: Failed to send mining.notify to downstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::disconnect(TproxyErrorKind::ChannelErrorSender, downstream_id.unwrap_or(0)) })?; } return Ok(()); @@ -299,7 +301,10 @@ impl Downstream { "Down: Failed to send notification to downstream: {:?}", e ); - TproxyError::ChannelErrorSender + TproxyError::disconnect( + TproxyErrorKind::ChannelErrorSender, + downstream_id.unwrap_or(0), + ) })?; } } @@ -337,7 +342,10 @@ impl Downstream { .await .map_err(|e| { error!("Down: Failed to send queued message to downstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::disconnect( + TproxyErrorKind::ChannelErrorSender, + downstream_id.unwrap_or(0), + ) })?; } else { // Neither handshake complete nor queued response - skip non-notification @@ -351,7 +359,7 @@ impl Downstream { "Sv1 message handler error for downstream {}: {:?}", downstream_id, e ); - return Err(TproxyError::BroadcastChannelErrorReceiver(e)); + return Err(TproxyError::disconnect(e, downstream_id)); } } @@ -370,7 +378,10 @@ impl Downstream { /// which implements the SV1 protocol logic and generates appropriate responses. /// Responses are sent back to the miner, while share submissions are forwarded /// to the SV1 server for upstream processing. - pub async fn handle_downstream_message(self: Arc) -> Result<(), TproxyError> { + pub async fn handle_downstream_message(self: Arc) -> TproxyResult<(), error::Downstream> { + let downstream_id = self + .downstream_data + .super_safe_lock(|data| data.downstream_id); let message = match self .downstream_channel_state .downstream_sv1_receiver @@ -380,7 +391,7 @@ impl Downstream { Ok(msg) => msg, Err(e) => { error!("Error receiving downstream message: {:?}", e); - return Err(TproxyError::ChannelErrorReceiver(e)); + return Err(TproxyError::disconnect(e, downstream_id)); } }; @@ -403,7 +414,7 @@ impl Downstream { .await .map_err(|e| { error!("Down: Failed to send OpenChannel request: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; debug!( "Down: Sent OpenChannel request for downstream {}", @@ -413,9 +424,11 @@ impl Downstream { // Queue all messages until channel is established debug!("Down: Queuing Sv1 message until channel is established"); - self.downstream_data.safe_lock(|d| { - d.queued_sv1_handshake_messages.push(message.clone()); - })?; + self.downstream_data + .safe_lock(|d| { + d.queued_sv1_handshake_messages.push(message.clone()); + }) + .map_err(|error| TproxyError::disconnect(error, downstream_id))?; return Ok(()); } @@ -436,7 +449,7 @@ impl Downstream { .await .map_err(|e| { error!("Down: Failed to send message to downstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::disconnect(TproxyErrorKind::ChannelErrorSender, downstream_id) })?; // Check if this was an authorize message and handle sv1 handshake completion @@ -458,7 +471,7 @@ impl Downstream { } Err(e) => { error!("Down: Error handling downstream message: {:?}", e); - return Err(e.into()); + return Err(TproxyError::disconnect(e, downstream_id)); } } @@ -473,7 +486,7 @@ impl Downstream { .await .map_err(|e| { error!("Down: Failed to send share to SV1 server: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; } @@ -485,12 +498,19 @@ impl Downstream { /// This method is called when the downstream completes the SV1 handshake /// (subscribe + authorize). It sends any cached messages in the correct order: /// set_difficulty first, then notify. - async fn handle_sv1_handshake_completion(self: &Arc) -> Result<(), TproxyError> { - let (cached_set_difficulty, cached_notify) = self.downstream_data.super_safe_lock(|d| { - d.sv1_handshake_complete - .store(true, std::sync::atomic::Ordering::SeqCst); - (d.cached_set_difficulty.take(), d.cached_notify.take()) - }); + async fn handle_sv1_handshake_completion( + self: &Arc, + ) -> TproxyResult<(), error::Downstream> { + let (cached_set_difficulty, cached_notify, downstream_id) = + self.downstream_data.super_safe_lock(|d| { + d.sv1_handshake_complete + .store(true, std::sync::atomic::Ordering::SeqCst); + ( + d.cached_set_difficulty.take(), + d.cached_notify.take(), + d.downstream_id, + ) + }); debug!("Down: SV1 handshake completed for downstream"); // Send cached messages in correct order: set_difficulty first, then notify @@ -505,7 +525,7 @@ impl Downstream { "Down: Failed to send cached mining.set_difficulty to downstream: {:?}", e ); - TproxyError::ChannelErrorSender + TproxyError::disconnect(TproxyErrorKind::ChannelErrorSender, downstream_id) })?; // Update target and hashrate after sending set_difficulty @@ -530,7 +550,7 @@ impl Downstream { "Down: Failed to send cached mining.notify to downstream: {:?}", e ); - TproxyError::ChannelErrorSender + TproxyError::disconnect(TproxyErrorKind::ChannelErrorSender, downstream_id) })?; // Update last job received time for keepalive tracking self.downstream_data.super_safe_lock(|d| { diff --git a/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs b/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs index 0981ef915..fd005308f 100644 --- a/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs +++ b/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs @@ -1,6 +1,6 @@ use crate::{ config::TranslatorConfig, - error::TproxyError, + error::{self, TproxyError, TproxyErrorKind, TproxyResult}, status::{handle_error, Status, StatusSender}, sv1::{ downstream::{downstream::Downstream, DownstreamMessages}, @@ -137,7 +137,7 @@ impl Sv1Server { shutdown_complete_tx: mpsc::Sender<()>, status_sender: Sender, task_manager: Arc, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Sv1Server> { info!("Starting SV1 server on {}", self.listener_addr); let mut shutdown_rx_main = notify_shutdown.subscribe(); @@ -165,7 +165,7 @@ impl Sv1Server { let listener = TcpListener::bind(self.listener_addr).await.map_err(|e| { error!("Failed to bind to {}: {}", self.listener_addr, e); - e + TproxyError::shutdown(e) })?; info!("Translator Proxy: listening on {}", self.listener_addr); @@ -288,9 +288,10 @@ impl Sv1Server { Arc::clone(&self) ) => { if let Err(e) = res { - handle_error(&sv1_status_sender, e).await; - self.sv1_server_channel_state.drop(); - break; + if handle_error(&sv1_status_sender, e).await { + self.sv1_server_channel_state.drop(); + break; + } } } res = Self::handle_upstream_message( @@ -298,9 +299,10 @@ impl Sv1Server { first_target, ) => { if let Err(e) = res { - handle_error(&sv1_status_sender, e).await; - self.sv1_server_channel_state.drop(); - break; + if handle_error(&sv1_status_sender, e).await { + self.sv1_server_channel_state.drop(); + break; + } } } _ = &mut vardiff_future, if vardiff_enabled => {} @@ -325,13 +327,13 @@ impl Sv1Server { /// # Returns /// * `Ok(())` - Message processed successfully /// * `Err(TproxyError)` - Error processing the message - pub async fn handle_downstream_message(self: Arc) -> Result<(), TproxyError> { + pub async fn handle_downstream_message(self: Arc) -> TproxyResult<(), error::Sv1Server> { let downstream_message = self .sv1_server_channel_state .downstream_to_sv1_server_receiver .recv() .await - .map_err(TproxyError::ChannelErrorReceiver)?; + .map_err(TproxyError::shutdown)?; match downstream_message { DownstreamMessages::SubmitShares(message) => { @@ -347,17 +349,19 @@ impl Sv1Server { async fn handle_submit_shares( self: &Arc, message: crate::sv1::downstream::SubmitShareWithChannelId, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Sv1Server> { // Increment vardiff counter for this downstream (only if vardiff is enabled) if self.config.downstream_difficulty_config.enable_vardiff { - self.sv1_server_data.safe_lock(|v| { - if let Some(vardiff_state) = v.vardiff.get(&message.downstream_id) { - vardiff_state - .write() - .unwrap() - .increment_shares_since_last_update(); - } - })?; + self.sv1_server_data + .safe_lock(|v| { + if let Some(vardiff_state) = v.vardiff.get(&message.downstream_id) { + vardiff_state + .write() + .unwrap() + .increment_shares_since_last_update(); + } + }) + .map_err(TproxyError::shutdown)?; } let job_version = match message.job_version { @@ -394,7 +398,7 @@ impl Sv1Server { job_version, message.version_rolling_mask, ) - .map_err(|_| TproxyError::SV1Error)?; + .map_err(|_| TproxyError::shutdown(TproxyErrorKind::SV1Error))?; // Only add TLV fields with user identity in non-aggregated mode let tlv_fields = if !self.config.aggregate_channels { @@ -421,7 +425,7 @@ impl Sv1Server { tlv_fields, )) .await - .map_err(|_| TproxyError::ChannelErrorSender)?; + .map_err(|_| TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender))?; self.sequence_counter.fetch_add(1, Ordering::SeqCst); @@ -432,7 +436,7 @@ impl Sv1Server { async fn handle_open_channel_request( self: &Arc, downstream_id: DownstreamId, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Sv1Server> { info!("SV1 Server: Opening extended mining channel for downstream {} after receiving first message", downstream_id); let (request_id, downstreams) = self.sv1_server_data.super_safe_lock(|v| { @@ -475,13 +479,13 @@ impl Sv1Server { pub async fn handle_upstream_message( self: Arc, first_target: Target, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Sv1Server> { let (message, _tlv_fields) = self .sv1_server_channel_state .channel_manager_receiver .recv() .await - .map_err(TproxyError::ChannelErrorReceiver)?; + .map_err(TproxyError::shutdown)?; match message { Mining::OpenExtendedMiningChannelSuccess(m) => { @@ -494,18 +498,23 @@ impl Sv1Server { (downstream_id, v.downstreams.clone()) }); let Some(downstream_id) = downstream_id else { - return Err(TproxyError::DownstreamNotFound(m.request_id)); + return Err(TproxyError::log(TproxyErrorKind::DownstreamNotFound( + m.request_id, + ))); }; if let Some(downstream) = Self::get_downstream(downstream_id, downstreams) { let initial_target = Target::from_le_bytes(m.target.inner_as_ref().try_into().unwrap()); - downstream.downstream_data.safe_lock(|d| { - d.extranonce1 = m.extranonce_prefix.to_vec(); - d.extranonce2_len = m.extranonce_size.into(); - d.channel_id = Some(m.channel_id); - // Set the initial upstream target from OpenExtendedMiningChannelSuccess - d.set_upstream_target(initial_target); - })?; + downstream + .downstream_data + .safe_lock(|d| { + d.extranonce1 = m.extranonce_prefix.to_vec(); + d.extranonce2_len = m.extranonce_size.into(); + d.channel_id = Some(m.channel_id); + // Set the initial upstream target from OpenExtendedMiningChannelSuccess + d.set_upstream_target(initial_target); + }) + .map_err(TproxyError::shutdown)?; // Process all queued messages now that channel is established if let Ok(queued_messages) = downstream.downstream_data.safe_lock(|d| { @@ -538,7 +547,11 @@ impl Sv1Server { Some(downstream_id), response_msg.into(), )) - .map_err(|_| TproxyError::ChannelErrorSender)?; + .map_err(|_| { + TproxyError::shutdown( + TproxyErrorKind::ChannelErrorSender, + ) + })?; } } } @@ -546,13 +559,15 @@ impl Sv1Server { let set_difficulty = build_sv1_set_difficulty_from_sv2_target(first_target) .map_err(|_| { - TproxyError::General("Failed to generate set_difficulty".into()) + TproxyError::shutdown(TproxyErrorKind::General( + "Failed to generate set_difficulty".into(), + )) })?; // send the set_difficulty message to the downstream self.sv1_server_channel_state .sv1_server_to_downstream_sender .send((m.channel_id, None, set_difficulty)) - .map_err(|_| TproxyError::ChannelErrorSender)?; + .map_err(|_| TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender))?; } else { error!("Downstream not found for downstream_id: {}", downstream_id); } @@ -569,7 +584,8 @@ impl Sv1Server { prevhash, m.clone().into_static(), self.clean_job.load(Ordering::SeqCst), - )?; + ) + .map_err(TproxyError::shutdown)?; let clean_jobs = self.clean_job.load(Ordering::SeqCst); self.clean_job.store(false, Ordering::SeqCst); @@ -656,7 +672,7 @@ impl Sv1Server { &self, request_id: u32, downstream: Arc, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Sv1Server> { let config = &self.config.downstream_difficulty_config; let hashrate = config.min_individual_miner_hashrate as f64; @@ -684,7 +700,8 @@ impl Sv1Server { downstream .downstream_data - .safe_lock(|d| d.user_identity = user_identity.clone())?; + .safe_lock(|d| d.user_identity = user_identity.clone()) + .map_err(TproxyError::shutdown)?; if let Ok(open_channel_msg) = build_sv2_open_extended_mining_channel( request_id, @@ -697,7 +714,7 @@ impl Sv1Server { .channel_manager_sender .send((Mining::OpenExtendedMiningChannel(open_channel_msg), None)) .await - .map_err(|_| TproxyError::ChannelErrorSender)?; + .map_err(|_| TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender))?; } else { error!("Failed to build OpenExtendedMiningChannel message"); } diff --git a/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs b/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs index 8b2084d12..b8b522442 100644 --- a/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs +++ b/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs @@ -1,5 +1,5 @@ use crate::{ - error::TproxyError, + error::{self, TproxyError, TproxyErrorKind, TproxyResult}, status::{handle_error, Status, StatusSender}, sv2::channel_manager::{ channel::ChannelState, @@ -161,14 +161,16 @@ impl ChannelManager { } res = Self::handle_upstream_frame(self.clone()) => { if let Err(e) = res { - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } }, res = Self::handle_downstream_message(self.clone()) => { if let Err(e) = res { - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } }, else => { @@ -198,17 +200,17 @@ impl ChannelManager { /// # Returns /// * `Ok(())` - Message processed successfully /// * `Err(TproxyError)` - Error processing the message - pub async fn handle_upstream_frame(self: Arc) -> Result<(), TproxyError> { + pub async fn handle_upstream_frame(self: Arc) -> TproxyResult<(), error::ChannelManager> { let mut channel_manager = self.get_channel_manager(); let mut sv2_frame = self .channel_state .upstream_receiver .recv() .await - .map_err(TproxyError::ChannelErrorReceiver)?; + .map_err(TproxyError::fallback)?; let header = sv2_frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - TproxyError::FramingSv2(framing_sv2::Error::MissingHeader) + TproxyError::fallback(framing_sv2::Error::MissingHeader) })?; match protocol_message_type(header.ext_type(), header.msg_type()) { MessageType::Mining => { @@ -227,10 +229,10 @@ impl ChannelManager { message_type = header.msg_type(), "Received unexpected message type from upstream" ); - return Err(TproxyError::UnexpectedMessage( + return Err(TproxyError::fallback(TproxyErrorKind::UnexpectedMessage( header.ext_type(), header.msg_type(), - )); + ))); } } @@ -253,13 +255,15 @@ impl ChannelManager { /// # Returns /// * `Ok(())` - Message processed successfully /// * `Err(TproxyError)` - Error processing the message - pub async fn handle_downstream_message(self: Arc) -> Result<(), TproxyError> { + pub async fn handle_downstream_message( + self: Arc, + ) -> TproxyResult<(), error::ChannelManager> { let (message, tlv_fields) = self .channel_state .sv1_server_receiver .recv() .await - .map_err(TproxyError::ChannelErrorReceiver)?; + .map_err(TproxyError::shutdown)?; match message { Mining::OpenExtendedMiningChannel(m) => { let mut open_channel_msg = m.clone(); @@ -349,10 +353,10 @@ impl ChannelManager { .await .map_err(|e| { error!( - "Failed to send open channel message to upstream: {:?}", + "Failed to send open channel message to SV1Server: {:?}", e ); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; // get the last active job from the upstream extended channel let last_active_job = @@ -403,8 +407,8 @@ impl ChannelManager { .send((Mining::NewExtendedMiningJob(job.clone()), None)) .await .map_err(|e| { - error!("Failed to send last new extended mining job to upstream: {:?}", e); - TproxyError::ChannelErrorSender + error!("Failed to send last new extended mining job to SV1Server: {:?}", e); + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; } } @@ -452,14 +456,16 @@ impl ChannelManager { ); let message = Mining::OpenExtendedMiningChannel(open_channel_msg); - let sv2_frame: Sv2Frame = AnyMessage::Mining(message).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(message) + .try_into() + .map_err(TproxyError::shutdown)?; self.channel_state .upstream_sender .send(sv2_frame) .await .map_err(|e| { error!("Failed to send open channel message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; } Mining::SubmitSharesExtended(mut m) => { @@ -531,7 +537,8 @@ impl ChannelManager { new_extranonce.extend_from_slice(m.extranonce.as_ref()); // Replace the original extranonce with the modified one for // upstream submission - m.extranonce = new_extranonce.try_into()?; + m.extranonce = + new_extranonce.try_into().map_err(TproxyError::shutdown)?; } // We need to set the channel id to the upstream extended // channel id @@ -572,7 +579,8 @@ impl ChannelManager { new_extranonce.extend_from_slice(m.extranonce.as_ref()); // Replace the original extranonce with the modified one for // upstream submission - m.extranonce = new_extranonce.try_into()?; + m.extranonce = + new_extranonce.try_into().map_err(TproxyError::shutdown)?; } } } @@ -607,7 +615,7 @@ impl ChannelManager { if let Some(tlv) = user_identity_tlv { let tlv_list = TlvList::from_slice(&[tlv]).map_err(|e| { error!("Failed to create TLV list: {:?}", e); - TproxyError::TlvError(e) + TproxyError::shutdown(e) })?; let frame_bytes = tlv_list .build_frame_bytes_with_tlvs(Mining::SubmitSharesExtended( @@ -615,7 +623,7 @@ impl ChannelManager { )) .map_err(|e| { error!("Failed to build frame bytes with TLVs: {:?}", e); - TproxyError::TlvError(e) + TproxyError::shutdown(e) })?; // Convert to StandardSv2Frame with proper buffer type let sv2_frame = StandardSv2Frame::from_bytes(frame_bytes.into()) @@ -624,11 +632,11 @@ impl ChannelManager { "Failed to convert frame bytes to StandardSv2Frame: {:?}", missing ); - TproxyError::FramingSv2(framing_sv2::Error::ExpectedSv2Frame) + TproxyError::shutdown(framing_sv2::Error::ExpectedSv2Frame) })?; self.channel_state.upstream_sender.send(sv2_frame).await.map_err(|e| { error!("Failed to send submit shares extended message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; sent = true; } @@ -636,10 +644,12 @@ impl ChannelManager { if !sent { let message = Mining::SubmitSharesExtended(m); - let sv2_frame: Sv2Frame = AnyMessage::Mining(message).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(message) + .try_into() + .map_err(TproxyError::shutdown)?; self.channel_state.upstream_sender.send(sv2_frame).await.map_err(|e| { error!("Failed to send submit shares extended message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; } } @@ -670,7 +680,9 @@ impl ChannelManager { ); // Forward UpdateChannel message to upstream let message = Mining::UpdateChannel(m); - let sv2_frame: Sv2Frame = AnyMessage::Mining(message).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(message) + .try_into() + .map_err(TproxyError::shutdown)?; self.channel_state .upstream_sender @@ -678,13 +690,15 @@ impl ChannelManager { .await .map_err(|e| { error!("Failed to send UpdateChannel message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; } Mining::CloseChannel(m) => { debug!("Received CloseChannel from SV1Server: {m}"); let message = Mining::CloseChannel(m); - let sv2_frame: Sv2Frame = AnyMessage::Mining(message).try_into()?; + let sv2_frame: Sv2Frame = AnyMessage::Mining(message) + .try_into() + .map_err(TproxyError::shutdown)?; self.channel_state .upstream_sender @@ -692,7 +706,7 @@ impl ChannelManager { .await .map_err(|e| { error!("Failed to send UpdateChannel message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; } _ => { diff --git a/miner-apps/translator/src/lib/sv2/channel_manager/extensions_message_handler.rs b/miner-apps/translator/src/lib/sv2/channel_manager/extensions_message_handler.rs index 6039f5e55..3f1082744 100644 --- a/miner-apps/translator/src/lib/sv2/channel_manager/extensions_message_handler.rs +++ b/miner-apps/translator/src/lib/sv2/channel_manager/extensions_message_handler.rs @@ -1,4 +1,7 @@ -use crate::{error::TproxyError, sv2::channel_manager::ChannelManager}; +use crate::{ + error::{self, TproxyError, TproxyErrorKind}, + sv2::channel_manager::ChannelManager, +}; use stratum_apps::{ stratum_core::{ binary_sv2::Seq064K, @@ -12,7 +15,7 @@ use tracing::{error, info}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleExtensionsFromServerAsync for ChannelManager { - type Error = TproxyError; + type Error = TproxyError; fn get_negotiated_extensions_with_server( &self, @@ -46,10 +49,10 @@ impl HandleExtensionsFromServerAsync for ChannelManager { "Server does not support our required extensions {:?}. Connection should fail over to another upstream.", missing_required ); - return Err(TproxyError::General(format!( + return Err(TproxyError::fallback(TproxyErrorKind::General(format!( "Server does not support required extensions: {:?}", missing_required - ))); + )))); } // Store the negotiated extensions in the shared channel manager data @@ -89,8 +92,8 @@ impl HandleExtensionsFromServerAsync for ChannelManager { "Server does not support our required extensions {:?}. Connection should fail over to another upstream.", missing_required ); - return Err(TproxyError::RequiredExtensionsNotSupported( - missing_required, + return Err(TproxyError::fallback( + TproxyErrorKind::RequiredExtensionsNotSupported(missing_required), )); } @@ -115,8 +118,8 @@ impl HandleExtensionsFromServerAsync for ChannelManager { "Server requires extensions {:?} that we don't support. Connection should fail over to another upstream.", cannot_support ); - return Err(TproxyError::ServerRequiresUnsupportedExtensions( - cannot_support, + return Err(TproxyError::fallback( + TproxyErrorKind::ServerRequiresUnsupportedExtensions(cannot_support), )); } @@ -132,7 +135,9 @@ impl HandleExtensionsFromServerAsync for ChannelManager { }; let sv2_frame: Sv2Frame = - AnyMessage::Extensions(new_require_extensions.into_static().into()).try_into()?; + AnyMessage::Extensions(new_require_extensions.into_static().into()) + .try_into() + .map_err(TproxyError::shutdown)?; self.channel_state .upstream_sender @@ -140,7 +145,7 @@ impl HandleExtensionsFromServerAsync for ChannelManager { .await .map_err(|e| { error!("Failed to send message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; } diff --git a/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs b/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs index d9181bf50..38d7c7159 100644 --- a/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs +++ b/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs @@ -1,8 +1,7 @@ use std::sync::{Arc, RwLock}; use crate::{ - error::TproxyError, - status::{State, Status}, + error::{self, TproxyError, TproxyErrorKind}, sv2::{channel_manager::ChannelMode, ChannelManager}, utils::proxy_extranonce_prefix_len, }; @@ -30,7 +29,7 @@ use tracing::{debug, error, info, warn}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleMiningMessagesFromServerAsync for ChannelManager { - type Error = TproxyError; + type Error = TproxyError; fn get_channel_type_for_server(&self, _server_id: Option) -> SupportedChannelTypes { SupportedChannelTypes::Extended @@ -56,10 +55,10 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { _tlv_fields: Option<&[Tlv]>, ) -> Result<(), Self::Error> { warn!("Received: {}", m); - Err(Self::Error::UnexpectedMessage( + Err(TproxyError::log(TproxyErrorKind::UnexpectedMessage( 0, MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS, - )) + ))) } async fn handle_open_extended_mining_channel_success( @@ -78,11 +77,11 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { }) .map_err(|e| { error!("Failed to lock channel manager data: {:?}", e); - TproxyError::PoisonLock + TproxyError::shutdown(TproxyErrorKind::PoisonLock) })? .ok_or_else(|| { error!("No pending channel found for request_id: {}", m.request_id); - TproxyError::PendingChannelNotFound(m.request_id) + TproxyError::log(TproxyErrorKind::PendingChannelNotFound(m.request_id)) })?; let success = self @@ -242,7 +241,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { }) .map_err(|e| { error!("Failed to lock channel manager data: {:?}", e); - TproxyError::PoisonLock + TproxyError::shutdown(TproxyErrorKind::PoisonLock) })?; self.channel_state @@ -254,7 +253,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .await .map_err(|e| { error!("Failed to send OpenExtendedMiningChannelSuccess: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; Ok(()) @@ -267,14 +266,9 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { _tlv_fields: Option<&[Tlv]>, ) -> Result<(), Self::Error> { warn!("Received: {}", m); - _ = self - .channel_state - .status_sender - .send(Status { - state: State::UpstreamShutdown(TproxyError::Shutdown), - }) - .await; - Ok(()) + Err(TproxyError::fallback( + TproxyErrorKind::OpenMiningChannelError, + )) } async fn handle_update_channel_error( @@ -392,7 +386,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .await .map_err(|e| { error!("Failed to send immediate NewExtendedMiningJob: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; } Ok(()) @@ -437,7 +431,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .await .map_err(|e| { error!("Failed to send SetNewPrevHash: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; let mode = self @@ -470,7 +464,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .await .map_err(|e| { error!("Failed to send NewExtendedMiningJob: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; } Ok(()) @@ -484,10 +478,10 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { ) -> Result<(), Self::Error> { warn!("Received: {}", m); warn!("⚠️ Cannot process SetCustomMiningJobSuccess since Translator Proxy does not support custom mining jobs. Ignoring."); - Err(Self::Error::UnexpectedMessage( + Err(TproxyError::log(TproxyErrorKind::UnexpectedMessage( 0, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS, - )) + ))) } async fn handle_set_custom_mining_job_error( @@ -498,10 +492,10 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { ) -> Result<(), Self::Error> { warn!("Received: {}", m); warn!("⚠️ Cannot process SetCustomMiningJobError since Translator Proxy does not support custom mining jobs. Ignoring."); - Err(Self::Error::UnexpectedMessage( + Err(TproxyError::log(TproxyErrorKind::UnexpectedMessage( 0, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR, - )) + ))) } async fn handle_set_target( @@ -549,7 +543,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { .await .map_err(|e| { error!("Failed to forward SetTarget message to SV1Server: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; Ok(()) @@ -563,9 +557,9 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { ) -> Result<(), Self::Error> { warn!("Received: {}", m); warn!("⚠️ Cannot process SetGroupChannel since Translator Proxy does not support group channels. Ignoring."); - Err(Self::Error::UnexpectedMessage( + Err(TproxyError::log(TproxyErrorKind::UnexpectedMessage( 0, MESSAGE_TYPE_SET_GROUP_CHANNEL, - )) + ))) } } diff --git a/miner-apps/translator/src/lib/sv2/upstream/common_message_handler.rs b/miner-apps/translator/src/lib/sv2/upstream/common_message_handler.rs index 58c4be8de..7a89c211c 100644 --- a/miner-apps/translator/src/lib/sv2/upstream/common_message_handler.rs +++ b/miner-apps/translator/src/lib/sv2/upstream/common_message_handler.rs @@ -1,4 +1,7 @@ -use crate::{error::TproxyError, sv2::Upstream}; +use crate::{ + error::{self, TproxyError, TproxyErrorKind}, + sv2::Upstream, +}; use stratum_apps::stratum_core::{ common_messages_sv2::{ ChannelEndpointChanged, Reconnect, SetupConnectionError, SetupConnectionSuccess, @@ -10,7 +13,7 @@ use tracing::{error, info}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleCommonMessagesFromServerAsync for Upstream { - type Error = TproxyError; + type Error = TproxyError; fn get_negotiated_extensions_with_server( &self, @@ -26,7 +29,7 @@ impl HandleCommonMessagesFromServerAsync for Upstream { _tlv_fields: Option<&[Tlv]>, ) -> Result<(), Self::Error> { error!("Received: {}", msg); - Err(TproxyError::Fallback) + Err(TproxyError::fallback(TproxyErrorKind::SetupConnectionError)) } async fn handle_setup_connection_success( diff --git a/miner-apps/translator/src/lib/sv2/upstream/upstream.rs b/miner-apps/translator/src/lib/sv2/upstream/upstream.rs index 037573a80..5676f23d0 100644 --- a/miner-apps/translator/src/lib/sv2/upstream/upstream.rs +++ b/miner-apps/translator/src/lib/sv2/upstream/upstream.rs @@ -1,5 +1,5 @@ use crate::{ - error::TproxyError, + error::{self, TproxyError, TproxyErrorKind, TproxyResult}, io_task::spawn_io_tasks, status::{handle_error, Status, StatusSender}, sv2::upstream::channel::UpstreamChannelState, @@ -81,7 +81,7 @@ impl Upstream { shutdown_complete_tx: mpsc::Sender<()>, task_manager: Arc, required_extensions: Vec, - ) -> Result { + ) -> TproxyResult { let mut shutdown_rx = notify_shutdown.subscribe(); info!("Trying to connect to upstream at {}", upstream.addr); @@ -89,14 +89,17 @@ impl Upstream { if shutdown_rx.try_recv().is_ok() { info!("Shutdown signal received during upstream connection attempt. Aborting."); drop(shutdown_complete_tx); - return Err(TproxyError::Shutdown); + return Err(TproxyError::shutdown( + TproxyErrorKind::CouldNotInitiateSystem, + )); } match TcpStream::connect(upstream.addr).await { Ok(socket) => { info!("Connected to upstream at {}", upstream.addr); - let initiator = Initiator::from_raw_k(upstream.authority_pubkey.into_bytes())?; + let initiator = Initiator::from_raw_k(upstream.authority_pubkey.into_bytes()) + .map_err(TproxyError::fallback)?; match NoiseTcpStream::new(socket, HandshakeRole::Initiator(initiator)).await { Ok(stream) => { let (reader, writer) = stream.into_split(); @@ -144,7 +147,9 @@ impl Upstream { error!("Failed to connect to any configured upstream."); drop(shutdown_complete_tx); - Err(TproxyError::Shutdown) + Err(TproxyError::shutdown( + TproxyErrorKind::CouldNotInitiateSystem, + )) } /// Starts the upstream connection and begins message processing. @@ -163,7 +168,7 @@ impl Upstream { shutdown_complete_tx: mpsc::Sender<()>, status_sender: Sender, task_manager: Arc, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Upstream> { let mut shutdown_rx = notify_shutdown.subscribe(); // Wait for connection setup or shutdown signal tokio::select! { @@ -220,16 +225,17 @@ impl Upstream { /// /// The handshake establishes the protocol version, capabilities, and /// other connection parameters needed for SV2 communication. - pub async fn setup_connection(&mut self) -> Result<(), TproxyError> { + pub async fn setup_connection(&mut self) -> TproxyResult<(), error::Upstream> { debug!("Upstream: initiating SV2 handshake..."); // Build SetupConnection message - let setup_conn_msg = Self::get_setup_connection_message(2, 2, false)?; + let setup_conn_msg = + Self::get_setup_connection_message(2, 2, false).map_err(TproxyError::shutdown)?; let sv2_frame: Sv2Frame = Message::Common(setup_conn_msg.into()) .try_into() - .map_err(|e| { - error!("Failed to serialize SetupConnection message: {:?}", e); - TproxyError::ParserError(e) + .map_err(|error| { + error!("Failed to serialize SetupConnection message: {error:?}"); + TproxyError::shutdown(error) })?; // Send SetupConnection message to upstream @@ -239,7 +245,7 @@ impl Upstream { .await .map_err(|e| { error!("Failed to send SetupConnection to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; let mut incoming: Sv2Frame = @@ -250,15 +256,13 @@ impl Upstream { } Err(e) => { error!("Failed to receive handshake response from upstream: {}", e); - return Err(TproxyError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - )); + return Err(TproxyError::fallback(e)); } }; let header = incoming.get_header().ok_or_else(|| { error!("Expected handshake frame but no header found."); - TproxyError::UnexpectedMessage(0, 0) + TproxyError::fallback(TproxyErrorKind::UnexpectedMessage(0, 0)) })?; let payload = incoming.payload(); @@ -275,7 +279,9 @@ impl Upstream { }; let sv2_frame: Sv2Frame = - AnyMessage::Extensions(require_extensions.into_static().into()).try_into()?; + AnyMessage::Extensions(require_extensions.into_static().into()) + .try_into() + .map_err(TproxyError::shutdown)?; info!( "Sending RequestExtensions message to upstream: {:?}", @@ -288,7 +294,7 @@ impl Upstream { .await .map_err(|e| { error!("Failed to send message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; } Ok(()) @@ -305,10 +311,12 @@ impl Upstream { pub async fn on_upstream_message( &mut self, mut sv2_frame: Sv2Frame, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Upstream> { debug!("Received SV2 frame from upstream."); let Some(header) = sv2_frame.get_header() else { - return Err(TproxyError::UnexpectedMessage(0, 0)); + return Err(TproxyError::fallback(TproxyErrorKind::UnexpectedMessage( + 0, 0, + ))); }; match protocol_message_type(header.ext_type(), header.msg_type()) { @@ -328,7 +336,7 @@ impl Upstream { .await .map_err(|e| { error!("Failed to send mining message to channel manager: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::shutdown(TproxyErrorKind::ChannelErrorSender) })?; } _ => { @@ -337,23 +345,24 @@ impl Upstream { message_type = header.msg_type(), "Received unsupported message type from upstream." ); - return Err(TproxyError::UnexpectedMessage( + return Err(TproxyError::fallback(TproxyErrorKind::UnexpectedMessage( header.ext_type(), header.msg_type(), - )); + ))); } } Ok(()) } /// Spawns a unified task to handle upstream message I/O and shutdown logic. + #[allow(clippy::result_large_err)] fn run_upstream_task( mut self, notify_shutdown: broadcast::Sender, shutdown_complete_tx: mpsc::Sender<()>, status_sender: StatusSender, task_manager: Arc, - ) -> Result<(), TproxyError> { + ) -> TproxyResult<(), error::Upstream> { let mut shutdown_rx = notify_shutdown.subscribe(); let shutdown_complete_tx = shutdown_complete_tx.clone(); @@ -394,7 +403,7 @@ impl Upstream { } Err(e) => { error!("Upstream: receiver channel closed unexpectedly: {e}"); - handle_error(&status_sender, TproxyError::ChannelErrorReceiver(e)).await; + handle_error(&status_sender, TproxyError::::fallback(e)).await; break; } } @@ -412,7 +421,7 @@ impl Upstream { .await .map_err(|e| { error!("Upstream: failed to send sv2 frame: {e:?}"); - TproxyError::ChannelErrorSender + TproxyError::::fallback(TproxyErrorKind::ChannelErrorSender) }) { handle_error(&status_sender, e).await; @@ -420,7 +429,7 @@ impl Upstream { } Err(e) => { error!("Upstream: channel manager receiver closed: {e}"); - handle_error(&status_sender, TproxyError::ChannelErrorReceiver(e)).await; + handle_error(&status_sender, TproxyError::::shutdown(e)).await; break; } } @@ -448,10 +457,13 @@ impl Upstream { /// # Returns /// * `Ok(())` - Message sent successfully /// * `Err(TproxyError)` - Error sending the message - pub async fn send_upstream(&self, message: Mining<'static>) -> Result<(), TproxyError> { + pub async fn send_upstream( + &self, + message: Mining<'static>, + ) -> TproxyResult<(), error::Upstream> { debug!("Sending message to upstream."); let message = AnyMessage::Mining(message); - let sv2_frame: Sv2Frame = message.try_into()?; + let sv2_frame: Sv2Frame = message.try_into().map_err(TproxyError::shutdown)?; self.upstream_channel_state .upstream_sender @@ -459,7 +471,7 @@ impl Upstream { .await .map_err(|e| { error!("Failed to send message to upstream: {:?}", e); - TproxyError::ChannelErrorSender + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) })?; Ok(()) @@ -471,7 +483,7 @@ impl Upstream { min_version: u16, max_version: u16, is_work_selection_enabled: bool, - ) -> Result, TproxyError> { + ) -> Result, TproxyErrorKind> { let endpoint_host = "0.0.0.0".to_string().into_bytes().try_into()?; let vendor = "SRI".to_string().try_into()?; let hardware_version = "Translator Proxy".to_string().try_into()?; diff --git a/miner-apps/translator/src/lib/utils.rs b/miner-apps/translator/src/lib/utils.rs index d4119d9dd..cc9531ac6 100644 --- a/miner-apps/translator/src/lib/utils.rs +++ b/miner-apps/translator/src/lib/utils.rs @@ -22,7 +22,7 @@ use stratum_apps::{ use tokio::sync::mpsc; use tracing::debug; -use crate::error::TproxyError; +use crate::error::TproxyErrorKind; /// Validates an SV1 share against the target difficulty and job parameters. /// @@ -52,7 +52,7 @@ pub fn validate_sv1_share( version_rolling_mask: Option, sv1_server_data: std::sync::Arc>, channel_id: ChannelId, -) -> Result { +) -> Result { let job_id = share.job_id.clone(); // Access valid jobs based on the configured mode @@ -74,7 +74,7 @@ pub fn validate_sv1_share( None } }) - .ok_or(TproxyError::JobNotFound)?; + .ok_or(TproxyErrorKind::JobNotFound)?; let mut full_extranonce = vec![]; full_extranonce.extend_from_slice(extranonce1.as_slice()); @@ -89,7 +89,7 @@ pub fn validate_sv1_share( let version = (job.version.0 & !mask) | (share_version & mask); let prev_hash_vec: Vec = job.prev_hash.clone().into(); - let prev_hash = U256::from_vec_(prev_hash_vec).map_err(TproxyError::BinarySv2)?; + let prev_hash = U256::from_vec_(prev_hash_vec).map_err(TproxyErrorKind::BinarySv2)?; // calculate the merkle root from: // - job coinbase_tx_prefix @@ -102,9 +102,9 @@ pub fn validate_sv1_share( full_extranonce.as_ref(), job.merkle_branch.as_ref(), ) - .ok_or(TproxyError::InvalidMerkleRoot)? + .ok_or(TproxyErrorKind::InvalidMerkleRoot)? .try_into() - .map_err(|_| TproxyError::InvalidMerkleRoot)?; + .map_err(|_| TproxyErrorKind::InvalidMerkleRoot)?; // create the header for validation let header = Header { diff --git a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs index 3c7a8124d..0637f1ecc 100644 --- a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs +++ b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs @@ -26,12 +26,13 @@ use tracing::{error, info}; use crate::{ channel_manager::{ChannelManager, RouteMessageTo, FULL_EXTRANONCE_SIZE}, - error::PoolError, + error::{self, PoolError, PoolErrorKind}, + utils::create_close_channel_msg, }; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleMiningMessagesFromClientAsync for ChannelManager { - type Error = PoolError; + type Error = PoolError; fn get_channel_type_for_client(&self, _client_id: Option) -> SupportedChannelTypes { SupportedChannelTypes::GroupAndExtended @@ -84,7 +85,10 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { .super_safe_lock(|channel_manager_data| { let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); + return Err(PoolError::disconnect( + PoolErrorKind::DownstreamNotFound(downstream_id), + downstream_id, + )); }; downstream @@ -115,7 +119,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { - return Err(PoolError::DownstreamIdNotFound); + return Err(PoolError::disconnect(PoolErrorKind::DownstreamIdNotFound, downstream_id)); }; if downstream.requires_custom_work.load(Ordering::SeqCst) { @@ -131,11 +135,11 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { } let Some(last_future_template) = channel_manager_data.last_future_template.clone() else { - return Err(PoolError::FutureTemplateNotPresent); + return Err(PoolError::disconnect(PoolErrorKind::FutureTemplateNotPresent, downstream_id)); }; let Some(last_set_new_prev_hash_tdp) = channel_manager_data.last_new_prev_hash.clone() else { - return Err(PoolError::LastNewPrevhashNotFound); + return Err(PoolError::disconnect(PoolErrorKind::LastNewPrevhashNotFound, downstream_id)); }; @@ -153,17 +157,17 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { Ok(channel) => channel, Err(e) => { error!(?e, "Failed to create group channel"); - return Err(PoolError::FailedToCreateGroupChannel(e)); + return Err(PoolError::shutdown(e)); } }; - group_channel.on_new_template(last_future_template.clone(), vec![pool_coinbase_output.clone()])?; + group_channel.on_new_template(last_future_template.clone(), vec![pool_coinbase_output.clone()]).map_err(PoolError::shutdown)?; - group_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp.clone())?; + group_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp.clone()).map_err(PoolError::shutdown)?; downstream_data.group_channels = Some(group_channel); } let nominal_hash_rate = msg.nominal_hash_rate; let requested_max_target = Target::from_le_bytes(msg.max_target.inner_as_ref().try_into().unwrap()); - let extranonce_prefix = channel_manager_data.extranonce_prefix_factory_standard.next_prefix_standard()?; + let extranonce_prefix = channel_manager_data.extranonce_prefix_factory_standard.next_prefix_standard().map_err(PoolError::shutdown)?; let channel_id = downstream_data.channel_id_factory.fetch_add(1, Ordering::SeqCst); let job_store = DefaultJobStore::new(); @@ -195,7 +199,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { } _ => { error!("error in handle_open_standard_mining_channel: {:?}", e); - return Err(PoolError::ChannelErrorSender); + return Err(PoolError::disconnect(PoolErrorKind::ChannelErrorSender, downstream_id) ); } }, }; @@ -217,7 +221,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let template_id = last_future_template.template_id; // create a future standard job based on the last future template - standard_channel.on_new_template(last_future_template, vec![pool_coinbase_output.clone()])?; + standard_channel.on_new_template(last_future_template, vec![pool_coinbase_output.clone()]).map_err(PoolError::shutdown)?; let future_standard_job_id = standard_channel .get_future_job_id_from_template_id(template_id) .expect("future job id must exist"); @@ -240,7 +244,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { }; standard_channel - .on_set_new_prev_hash(last_set_new_prev_hash_tdp.clone())?; + .on_set_new_prev_hash(last_set_new_prev_hash_tdp.clone()).map_err(PoolError::shutdown)?; messages.push((downstream_id, Mining::SetNewPrevHash(set_new_prev_hash_mining)).into()); @@ -248,7 +252,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { if let Some(group_channel) = downstream_data.group_channels.as_mut() { group_channel.add_standard_channel_id(channel_id); } - let vardiff = VardiffState::new()?; + let vardiff = VardiffState::new().map_err(PoolError::shutdown)?; channel_manager_data.vardiff.insert((downstream_id, channel_id).into(), vardiff); Ok(messages) @@ -284,7 +288,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { .super_safe_lock(|channel_manager_data| { let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { - return Err(PoolError::DownstreamIdNotFound); + return Err(PoolError::disconnect(PoolErrorKind::DownstreamIdNotFound, downstream_id)); }; downstream .downstream_data @@ -391,7 +395,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { } e => { error!("error in handle_open_extended_mining_channel: {:?}", e); - return Err(e)?; + return Err(PoolError::disconnect(e, downstream_id))?; } }, }; @@ -404,7 +408,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { extranonce_prefix: extended_channel .get_extranonce_prefix() .clone() - .try_into()?, + .try_into().map_err(PoolError::shutdown)?, extranonce_size: extended_channel.get_rollable_extranonce_size(), } .into_static(); @@ -423,20 +427,20 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let Some(last_set_new_prev_hash_tdp) = channel_manager_data.last_new_prev_hash.clone() else { - return Err(PoolError::LastNewPrevhashNotFound); + return Err(PoolError::disconnect(PoolErrorKind::LastNewPrevhashNotFound, downstream_id)); }; let Some(last_future_template) = channel_manager_data.last_future_template.clone() else { - return Err(PoolError::FutureTemplateNotPresent); + return Err(PoolError::disconnect(PoolErrorKind::FutureTemplateNotPresent,downstream_id)); }; // if the client requires custom work, we don't need to send any extended // jobs so we just process the SetNewPrevHash // message if downstream.requires_custom_work.load(Ordering::SeqCst) { - extended_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp)?; + extended_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp).map_err(PoolError::shutdown)?; // if the client does not require custom work, we need to send the // future extended job // and the SetNewPrevHash message @@ -451,7 +455,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { extended_channel.on_new_template( last_future_template.clone(), vec![pool_coinbase_output], - )?; + ).map_err(PoolError::shutdown)?; let future_extended_job_id = extended_channel .get_future_job_id_from_template_id(last_future_template.template_id) @@ -486,7 +490,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { nbits: n_bits, }; - extended_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp)?; + extended_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp).map_err(PoolError::shutdown)?; messages.push( ( @@ -500,7 +504,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { downstream_data .extended_channels .insert(channel_id, extended_channel); - let vardiff = VardiffState::new()?; + let vardiff = VardiffState::new().map_err(PoolError::shutdown)?; channel_manager_data .vardiff .insert((downstream_id, channel_id).into(), vardiff); @@ -530,7 +534,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let channel_id = msg.channel_id; let Some(downstream) = channel_manager_data.downstream.get(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); + return Err(PoolError::disconnect(PoolErrorKind::DownstreamNotFound(downstream_id), downstream_id)); }; downstream.downstream_data.super_safe_lock(|downstream_data| { @@ -549,7 +553,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { }; let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(downstream_id, channel_id).into()) else { - return Err(PoolError::VardiffNotFound(channel_id)); + return Ok(vec![(downstream_id, Mining::CloseChannel(create_close_channel_msg(channel_id, "invalid-channel-id"))).into()]); }; let res = standard_channel.validate_share(msg.clone()); @@ -588,7 +592,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { version: msg.version, header_timestamp: msg.ntime, header_nonce: msg.nonce, - coinbase_tx: coinbase.try_into()?, + coinbase_tx: coinbase.try_into().map_err(PoolError::shutdown)?, }; messages.push(TemplateDistribution::SubmitSolution(solution).into()); } @@ -663,7 +667,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); } Err(e) => { - return Err(e)?; + return Err(PoolError::disconnect(e, downstream_id))?; } } @@ -709,7 +713,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { let channel_id = msg.channel_id; let Some(downstream) = channel_manager_data.downstream.get(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); + return Err(PoolError::disconnect(PoolErrorKind::DownstreamNotFound(downstream_id), downstream_id)); }; downstream.downstream_data.super_safe_lock(|downstream_data| { @@ -732,7 +736,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { } let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(downstream_id, channel_id).into()) else { - return Err(PoolError::VardiffNotFound(channel_id)); + return Ok(vec![(downstream_id, Mining::CloseChannel(create_close_channel_msg(channel_id, "invalid-channel-id"))).into()]); }; let res = extended_channel.validate_share(msg.clone()); @@ -769,7 +773,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { version: msg.version, header_timestamp: msg.ntime, header_nonce: msg.nonce, - coinbase_tx: coinbase.try_into()?, + coinbase_tx: coinbase.try_into().map_err(PoolError::shutdown)?, }; messages.push(TemplateDistribution::SubmitSolution(solution).into()); } @@ -855,7 +859,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); } Err(e) => { - return Err(e)?; + return Err(PoolError::disconnect(e, downstream_id))?; } } @@ -883,7 +887,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let messages: Vec = self.channel_manager_data.super_safe_lock(|channel_manager_data| { let Some(downstream) = channel_manager_data.downstream.get(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); + return Err(PoolError::disconnect(PoolErrorKind::DownstreamNotFound(downstream_id), downstream_id)); }; downstream.downstream_data.super_safe_lock(|downstream_data| { @@ -922,9 +926,10 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { }; messages.push((downstream_id, Mining::UpdateChannelError(update_channel_error)).into()); } - standard_channel_error => { - return Err(standard_channel_error)?; - } + // We don't care about other variants as they are not + // associated to Update channel, and we will never + // encounter it. + _ => unreachable!() } } } @@ -964,9 +969,10 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { }; messages.push((downstream_id, Mining::UpdateChannelError(update_channel_error)).into()); } - extended_channel_error => { - return Err(extended_channel_error)?; - } + // We don't care about other variants as they are not + // associated to Update channel, and we will never + // encounter it. + _ => unreachable!() } } } @@ -1015,7 +1021,8 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { // - the amount of the pool payout output let custom_job_coinbase_outputs = Vec::::consensus_decode( &mut msg.coinbase_tx_outputs.inner_as_ref().to_vec().as_slice(), - )?; + ) + .map_err(PoolError::shutdown)?; let message: RouteMessageTo = self.channel_manager_data @@ -1043,7 +1050,10 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); + return Err(PoolError::disconnect( + PoolErrorKind::DownstreamNotFound(downstream_id), + downstream_id, + )); }; downstream @@ -1066,8 +1076,10 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { ); }; + // TOOD: Send a CustomMiningJobError and not disconnect. let job_id = extended_channel - .on_set_custom_mining_job(msg.clone().into_static())?; + .on_set_custom_mining_job(msg.clone().into_static()) + .map_err(|error| PoolError::disconnect(error, downstream_id))?; let success = SetCustomMiningJobSuccess { channel_id: msg.channel_id, diff --git a/pool-apps/pool/src/lib/channel_manager/mod.rs b/pool-apps/pool/src/lib/channel_manager/mod.rs index 7fb78bee7..cdc0109f9 100644 --- a/pool-apps/pool/src/lib/channel_manager/mod.rs +++ b/pool-apps/pool/src/lib/channel_manager/mod.rs @@ -40,7 +40,7 @@ use tracing::{debug, error, info, warn}; use crate::{ config::PoolConfig, downstream::Downstream, - error::PoolResult, + error::{self, PoolError, PoolErrorKind, PoolResult}, status::{handle_error, Status, StatusSender}, utils::ShutdownMessage, }; @@ -111,7 +111,7 @@ impl ChannelManager { downstream_sender: broadcast::Sender<(DownstreamId, Mining<'static>, Option>)>, downstream_receiver: Receiver<(DownstreamId, Mining<'static>, Option>)>, coinbase_outputs: Vec, - ) -> PoolResult { + ) -> PoolResult { let range_0 = 0..0; let range_1 = 0..POOL_ALLOCATION_BYTES; let range_2 = POOL_ALLOCATION_BYTES..POOL_ALLOCATION_BYTES + CLIENT_SEARCH_SPACE_BYTES; @@ -183,12 +183,15 @@ impl ChannelManager { Mining<'static>, Option>, )>, - ) -> PoolResult<()> { + ) -> PoolResult<(), error::ChannelManager> { info!("Starting downstream server at {listening_address}"); - let server = TcpListener::bind(listening_address).await.map_err(|e| { - error!(error = ?e, "Failed to bind downstream server at {listening_address}"); - e - })?; + let server = TcpListener::bind(listening_address) + .await + .map_err(|e| { + error!(error = ?e, "Failed to bind downstream server at {listening_address}"); + e + }) + .map_err(PoolError::shutdown)?; let mut shutdown_rx = notify_shutdown.subscribe(); @@ -292,7 +295,7 @@ impl ChannelManager { status_sender: Sender, task_manager: Arc, coinbase_outputs: Vec, - ) -> PoolResult<()> { + ) -> PoolResult<(), error::ChannelManager> { let status_sender = StatusSender::ChannelManager(status_sender); let mut shutdown_rx = notify_shutdown.subscribe(); @@ -331,15 +334,17 @@ impl ChannelManager { res = cm_template.handle_template_provider_message() => { if let Err(e) = res { error!(error = ?e, "Error handling Template Receiver message"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = cm_downstreams.handle_downstream_mining_message() => { if let Err(e) = res { error!(error = ?e, "Error handling Downstreams message"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } } @@ -354,7 +359,10 @@ impl ChannelManager { // 1. Removes the corresponding Downstream from the `downstream` map. // 2. Removes the channels of the corresponding Downstream from `vardiff` map. #[allow(clippy::result_large_err)] - fn remove_downstream(&self, downstream_id: DownstreamId) -> PoolResult<()> { + fn remove_downstream( + &self, + downstream_id: DownstreamId, + ) -> PoolResult<(), error::ChannelManager> { self.channel_manager_data.super_safe_lock(|cm_data| { cm_data.downstream.remove(&downstream_id); cm_data @@ -370,7 +378,7 @@ impl ChannelManager { // - If the frame contains a TemplateDistribution message, it forwards it to the template // distribution message handler. // - If the frame contains any unsupported message type, an error is returned. - async fn handle_template_provider_message(&mut self) -> PoolResult<()> { + async fn handle_template_provider_message(&mut self) -> PoolResult<(), error::ChannelManager> { if let Ok(message) = self.channel_manager_channel.tp_receiver.recv().await { self.handle_template_distribution_message_from_server(None, message, None) .await?; @@ -378,7 +386,7 @@ impl ChannelManager { Ok(()) } - async fn handle_downstream_mining_message(&mut self) -> PoolResult<()> { + async fn handle_downstream_mining_message(&mut self) -> PoolResult<(), error::ChannelManager> { if let Ok((downstream_id, message, tlv_fields)) = self .channel_manager_channel .downstream_receiver @@ -486,7 +494,7 @@ impl ChannelManager { // # Purpose // - Executes the vardiff cycle every 60 seconds for all downstreams. // - Delegates to [`Self::run_vardiff`] on each tick. - async fn run_vardiff_loop(&self) -> PoolResult<()> { + async fn run_vardiff_loop(&self) -> PoolResult<(), error::ChannelManager> { let mut ticker = tokio::time::interval(std::time::Duration::from_secs(60)); loop { ticker.tick().await; @@ -505,7 +513,7 @@ impl ChannelManager { // - Runs vardiff for each channel and collects the resulting updates. // - Propagates difficulty changes to downstreams and also sends an `UpdateChannel` message // upstream if applicable. - async fn run_vardiff(&self) -> PoolResult<()> { + async fn run_vardiff(&self) -> PoolResult<(), error::ChannelManager> { let mut messages: Vec = vec![]; self.channel_manager_data .super_safe_lock(|channel_manager_data| { @@ -560,7 +568,7 @@ impl ChannelManager { pub async fn coinbase_output_constraints( &self, coinbase_outputs: Vec, - ) -> PoolResult<()> { + ) -> PoolResult<(), error::ChannelManager> { let msg = coinbase_output_constraints_message(coinbase_outputs); self.channel_manager_channel @@ -569,7 +577,7 @@ impl ChannelManager { .await .map_err(|e| { error!(error = ?e, "Failed to send CoinbaseOutputConstraints message to TP"); - crate::error::PoolError::ChannelErrorSender + PoolError::shutdown(PoolErrorKind::ChannelErrorSender) })?; Ok(()) diff --git a/pool-apps/pool/src/lib/channel_manager/template_distribution_message_handler.rs b/pool-apps/pool/src/lib/channel_manager/template_distribution_message_handler.rs index ea5ae8c83..62d2fb85b 100644 --- a/pool-apps/pool/src/lib/channel_manager/template_distribution_message_handler.rs +++ b/pool-apps/pool/src/lib/channel_manager/template_distribution_message_handler.rs @@ -12,12 +12,12 @@ use tracing::{info, warn}; use crate::{ channel_manager::{ChannelManager, RouteMessageTo}, - error::PoolError, + error::{self, PoolError}, }; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { - type Error = PoolError; + type Error = PoolError; fn get_negotiated_extensions_with_server( &self, diff --git a/pool-apps/pool/src/lib/downstream/common_message_handler.rs b/pool-apps/pool/src/lib/downstream/common_message_handler.rs index 03f63b256..3759f4ee9 100644 --- a/pool-apps/pool/src/lib/downstream/common_message_handler.rs +++ b/pool-apps/pool/src/lib/downstream/common_message_handler.rs @@ -1,4 +1,7 @@ -use crate::{downstream::Downstream, error::PoolError}; +use crate::{ + downstream::Downstream, + error::{self, PoolError, PoolErrorKind}, +}; use std::{convert::TryInto, sync::atomic::Ordering}; use stratum_apps::{ stratum_core::{ @@ -14,7 +17,7 @@ use tracing::info; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleCommonMessagesFromClientAsync for Downstream { - type Error = PoolError; + type Error = PoolError; fn get_negotiated_extensions_with_client( &self, @@ -45,11 +48,16 @@ impl HandleCommonMessagesFromClientAsync for Downstream { used_version: 2, flags: msg.flags, }; - let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()).try_into()?; + let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()) + .try_into() + .map_err(PoolError::shutdown)?; self.downstream_channel .downstream_sender .send(frame) - .await?; + .await + .map_err(|_| { + PoolError::disconnect(PoolErrorKind::ChannelErrorSender, self.downstream_id) + })?; Ok(()) } diff --git a/pool-apps/pool/src/lib/downstream/extensions_message_handler.rs b/pool-apps/pool/src/lib/downstream/extensions_message_handler.rs index acf6b0738..3451946a0 100644 --- a/pool-apps/pool/src/lib/downstream/extensions_message_handler.rs +++ b/pool-apps/pool/src/lib/downstream/extensions_message_handler.rs @@ -1,4 +1,7 @@ -use crate::{downstream::Downstream, error::PoolError}; +use crate::{ + downstream::Downstream, + error::{self, PoolError, PoolErrorKind}, +}; use std::convert::TryInto; use stratum_apps::{ stratum_core::{ @@ -13,7 +16,7 @@ use tracing::{error, info}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleExtensionsFromClientAsync for Downstream { - type Error = PoolError; + type Error = PoolError; fn get_negotiated_extensions_with_client( &self, @@ -76,17 +79,21 @@ impl HandleExtensionsFromClientAsync for Downstream { let error = RequestExtensionsError { request_id: msg.request_id, - unsupported_extensions: Seq064K::new(unsupported) - .map_err(PoolError::InvalidUnsupportedExtensionsSequence)?, + unsupported_extensions: Seq064K::new(unsupported).map_err(PoolError::shutdown)?, required_extensions: Seq064K::new(missing_required.clone()) - .map_err(PoolError::InvalidRequiredExtensionsSequence)?, + .map_err(PoolError::shutdown)?, }; - let frame: Sv2Frame = AnyMessage::Extensions(error.into_static().into()).try_into()?; + let frame: Sv2Frame = AnyMessage::Extensions(error.into_static().into()) + .try_into() + .map_err(PoolError::shutdown)?; self.downstream_channel .downstream_sender .send(frame) - .await?; + .await + .map_err(|_| { + PoolError::disconnect(PoolErrorKind::ChannelErrorSender, self.downstream_id) + })?; // If required extensions are missing, the server SHOULD disconnect the client if !missing_required.is_empty() { @@ -94,8 +101,9 @@ impl HandleExtensionsFromClientAsync for Downstream { "Downstream {}: Client does not support required extensions {:?}. Server MUST disconnect.", self.downstream_id, missing_required ); - Err(PoolError::ClientDoesNotSupportRequiredExtensions( - missing_required, + Err(PoolError::disconnect( + PoolErrorKind::ClientDoesNotSupportRequiredExtensions(missing_required), + self.downstream_id, ))?; } } else { @@ -113,15 +121,19 @@ impl HandleExtensionsFromClientAsync for Downstream { let success = RequestExtensionsSuccess { request_id: msg.request_id, supported_extensions: Seq064K::new(supported.clone()) - .map_err(PoolError::InvalidSupportedExtensionsSequence)?, + .map_err(PoolError::shutdown)?, }; - let frame: Sv2Frame = - AnyMessage::Extensions(success.into_static().into()).try_into()?; + let frame: Sv2Frame = AnyMessage::Extensions(success.into_static().into()) + .try_into() + .map_err(PoolError::shutdown)?; self.downstream_channel .downstream_sender .send(frame) - .await?; + .await + .map_err(|_| { + PoolError::disconnect(PoolErrorKind::ChannelErrorSender, self.downstream_id) + })?; info!( "Downstream {}: Stored negotiated extensions: {:?}", diff --git a/pool-apps/pool/src/lib/downstream/mod.rs b/pool-apps/pool/src/lib/downstream/mod.rs index fc6c5e367..a876482bf 100644 --- a/pool-apps/pool/src/lib/downstream/mod.rs +++ b/pool-apps/pool/src/lib/downstream/mod.rs @@ -20,7 +20,6 @@ use stratum_apps::{ common_messages_sv2::MESSAGE_TYPE_SETUP_CONNECTION, framing_sv2, handlers_sv2::{HandleCommonMessagesFromClientAsync, HandleExtensionsFromClientAsync}, - noise_sv2::Error, parsers_sv2::{parse_message_frame_with_tlvs, AnyMessage, Mining, Tlv}, }, task_manager::TaskManager, @@ -33,7 +32,7 @@ use tokio::sync::broadcast; use tracing::{debug, error, warn}; use crate::{ - error::{PoolError, PoolResult}, + error::{self, PoolError, PoolErrorKind, PoolResult}, io_task::spawn_io_tasks, status::{handle_error, Status, StatusSender}, utils::ShutdownMessage, @@ -200,15 +199,17 @@ impl Downstream { res = self_clone_1.handle_downstream_message() => { if let Err(e) = res { error!(?e, "Error handling downstream message for {downstream_id}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = self_clone_2.handle_channel_manager_message(&mut receiver) => { if let Err(e) = res { error!(?e, "Error handling channel manager message for {downstream_id}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } @@ -219,11 +220,16 @@ impl Downstream { } // Performs the initial handshake with a downstream peer. - async fn setup_connection_with_downstream(&mut self) -> PoolResult<()> { - let mut frame = self.downstream_channel.downstream_receiver.recv().await?; + async fn setup_connection_with_downstream(&mut self) -> PoolResult<(), error::Downstream> { + let mut frame = self + .downstream_channel + .downstream_receiver + .recv() + .await + .map_err(|error| PoolError::disconnect(error, self.downstream_id))?; let header = frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - PoolError::Framing(framing_sv2::Error::MissingHeader) + PoolError::disconnect(framing_sv2::Error::MissingHeader, self.downstream_id) })?; // The first ever message received on a new downstream connection // should always be a setup connection message. @@ -232,9 +238,12 @@ impl Downstream { .await?; return Ok(()); } - Err(PoolError::UnexpectedMessage( - header.ext_type_without_channel_msg(), - header.msg_type(), + Err(PoolError::disconnect( + PoolErrorKind::UnexpectedMessage( + header.ext_type_without_channel_msg(), + header.msg_type(), + ), + self.downstream_id, )) } @@ -242,7 +251,7 @@ impl Downstream { async fn handle_channel_manager_message( self, receiver: &mut broadcast::Receiver<(DownstreamId, Mining<'static>, Option>)>, - ) -> PoolResult<()> { + ) -> PoolResult<(), error::Downstream> { let (downstream_id, msg, _tlv_fields) = match receiver.recv().await { Ok(msg) => msg, Err(e) => { @@ -260,7 +269,7 @@ impl Downstream { } let message = AnyMessage::Mining(msg); - let std_frame: Sv2Frame = message.try_into()?; + let std_frame: Sv2Frame = message.try_into().map_err(PoolError::shutdown)?; self.downstream_channel .downstream_sender @@ -268,18 +277,23 @@ impl Downstream { .await .map_err(|e| { error!(?e, "Downstream send failed"); - PoolError::Noise(Error::ExpectedIncomingHandshakeMessage) + PoolError::disconnect(PoolErrorKind::ChannelErrorSender, self.downstream_id) })?; Ok(()) } // Handles incoming messages from the downstream peer. - async fn handle_downstream_message(&mut self) -> PoolResult<()> { - let mut sv2_frame = self.downstream_channel.downstream_receiver.recv().await?; + async fn handle_downstream_message(&mut self) -> PoolResult<(), error::Downstream> { + let mut sv2_frame = self + .downstream_channel + .downstream_receiver + .recv() + .await + .map_err(|error| PoolError::disconnect(error, self.downstream_id))?; let header = sv2_frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - PoolError::Framing(framing_sv2::Error::MissingHeader) + PoolError::disconnect(framing_sv2::Error::MissingHeader, self.downstream_id) })?; match protocol_message_type(header.ext_type(), header.msg_type()) { @@ -292,14 +306,18 @@ impl Downstream { header, sv2_frame.payload(), &negotiated_extensions, - )?; + ) + .map_err(|error| PoolError::disconnect(error, self.downstream_id))?; let mining_message = match any_message { AnyMessage::Mining(msg) => msg, _ => { error!("Expected Mining message but got different type"); - return Err(PoolError::UnexpectedMessage( - header.ext_type_without_channel_msg(), - header.msg_type(), + return Err(PoolError::disconnect( + PoolErrorKind::UnexpectedMessage( + header.ext_type_without_channel_msg(), + header.msg_type(), + ), + self.downstream_id, )); } }; @@ -309,7 +327,7 @@ impl Downstream { .await .map_err(|e| { error!(?e, "Failed to send mining message to channel manager."); - PoolError::ChannelErrorSender + PoolError::shutdown(e) })?; } MessageType::Extensions => { diff --git a/pool-apps/pool/src/lib/error.rs b/pool-apps/pool/src/lib/error.rs index 6ef63c441..fbdc61bdb 100644 --- a/pool-apps/pool/src/lib/error.rs +++ b/pool-apps/pool/src/lib/error.rs @@ -1,6 +1,7 @@ use std::{ convert::From, - fmt::Debug, + fmt::{self, Debug, Formatter}, + marker::PhantomData, sync::{MutexGuard, PoisonError}, }; @@ -20,10 +21,84 @@ use stratum_apps::{ noise_sv2, parsers_sv2::{Mining, ParserError}, }, - utils::types::{ChannelId, ExtensionType, MessageType}, + utils::types::{ + CanDisconnect, CanShutdown, ChannelId, DownstreamId, ExtensionType, MessageType, + }, }; -pub type PoolResult = Result; +pub type PoolResult = Result>; + +#[derive(Debug)] +pub struct ChannelManager; + +#[derive(Debug)] +pub struct TemplateProvider; + +#[derive(Debug)] +pub struct Downstream; + +#[derive(Debug)] +pub struct PoolError { + pub kind: PoolErrorKind, + pub action: Action, + _owner: PhantomData, +} + +#[derive(Debug, Clone, Copy)] +pub enum Action { + Log, + Disconnect(DownstreamId), + Shutdown, +} + +impl CanDisconnect for Downstream {} +impl CanDisconnect for ChannelManager {} + +impl CanShutdown for ChannelManager {} +impl CanShutdown for TemplateProvider {} +impl CanShutdown for Downstream {} + +impl PoolError { + pub fn log>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Log, + _owner: PhantomData, + } + } +} + +impl PoolError +where + O: CanDisconnect, +{ + pub fn disconnect>(kind: E, downstream_id: DownstreamId) -> Self { + Self { + kind: kind.into(), + action: Action::Disconnect(downstream_id), + _owner: PhantomData, + } + } +} + +impl PoolError +where + O: CanShutdown, +{ + pub fn shutdown>(kind: E) -> Self { + Self { + kind: kind.into(), + action: Action::Shutdown, + _owner: PhantomData, + } + } +} + +impl From> for PoolErrorKind { + fn from(value: PoolError) -> Self { + value.kind + } +} #[derive(Debug)] pub enum ChannelSv2Error { @@ -36,7 +111,7 @@ pub enum ChannelSv2Error { /// Represents various errors that can occur in the pool implementation. #[derive(std::fmt::Debug)] -pub enum PoolError { +pub enum PoolErrorKind { /// I/O-related error. Io(std::io::Error), ChannelSv2(ChannelSv2Error), @@ -66,8 +141,6 @@ pub enum PoolError { Vardiff(VardiffError), /// Parser Error Parser(ParserError), - /// Shutdown - Shutdown, /// Unexpected message UnexpectedMessage(ExtensionType, MessageType), /// Channel error sender @@ -106,11 +179,17 @@ pub enum PoolError { FailedToSendCoinbaseOutputConstraints, /// BitcoinCoreSv2 cancellation token activated BitcoinCoreSv2CancellationTokenActivated, + /// Setup connection error + SetupConnectionError, + /// endpoint change error + ChangeEndpoint, + /// Could not initiate subsystem + CouldNotInitiateSystem, } -impl std::fmt::Display for PoolError { +impl std::fmt::Display for PoolErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use PoolError::*; + use PoolErrorKind::*; match self { Io(e) => write!(f, "I/O error: `{e:?}"), ChannelSend(e) => write!(f, "Channel send failed: `{e:?}`"), @@ -126,11 +205,10 @@ impl std::fmt::Display for PoolError { Sv2ProtocolError(e) => { write!(f, "Received Sv2 Protocol Error from upstream: `{e:?}`") } - PoolError::Vardiff(e) => { + PoolErrorKind::Vardiff(e) => { write!(f, "Received Vardiff Error : {e:?}") } Parser(e) => write!(f, "Parser error: `{e:?}`"), - Shutdown => write!(f, "Shutdown"), UnexpectedMessage(extension_type, message_type) => write!(f, "Unexpected message: extension type: {extension_type:?}, message type: {message_type:?}"), ChannelErrorSender => write!(f, "Channel sender error"), InvalidSocketAddress(address) => write!(f, "Invalid socket address: {address:?}"), @@ -189,129 +267,150 @@ impl std::fmt::Display for PoolError { BitcoinCoreSv2CancellationTokenActivated => { write!(f, "BitcoinCoreSv2 cancellation token activated") } + SetupConnectionError => { + write!(f, "Failed to Setup connection") + }, + ChangeEndpoint => { + write!(f, "Change endpoint") + }, + CouldNotInitiateSystem => write!(f, "Could not initiate subsystem"), } } } -impl From for PoolError { - fn from(e: std::io::Error) -> PoolError { - PoolError::Io(e) +impl From for PoolErrorKind { + fn from(e: std::io::Error) -> PoolErrorKind { + PoolErrorKind::Io(e) } } -impl From for PoolError { - fn from(e: async_channel::RecvError) -> PoolError { - PoolError::ChannelRecv(e) +impl From for PoolErrorKind { + fn from(e: async_channel::RecvError) -> PoolErrorKind { + PoolErrorKind::ChannelRecv(e) } } -impl From for PoolError { - fn from(e: binary_sv2::Error) -> PoolError { - PoolError::BinarySv2(e) +impl From for PoolErrorKind { + fn from(e: binary_sv2::Error) -> PoolErrorKind { + PoolErrorKind::BinarySv2(e) } } -impl From for PoolError { - fn from(e: codec_sv2::Error) -> PoolError { - PoolError::Codec(e) +impl From for PoolErrorKind { + fn from(e: codec_sv2::Error) -> PoolErrorKind { + PoolErrorKind::Codec(e) } } -impl From for PoolError { - fn from(e: stratum_apps::config_helpers::CoinbaseOutputError) -> PoolError { - PoolError::CoinbaseOutput(e) +impl From for PoolErrorKind { + fn from(e: stratum_apps::config_helpers::CoinbaseOutputError) -> PoolErrorKind { + PoolErrorKind::CoinbaseOutput(e) } } -impl From for PoolError { - fn from(e: noise_sv2::Error) -> PoolError { - PoolError::Noise(e) +impl From for PoolErrorKind { + fn from(e: noise_sv2::Error) -> PoolErrorKind { + PoolErrorKind::Noise(e) } } -impl From> for PoolError { - fn from(e: async_channel::SendError) -> PoolError { - PoolError::ChannelSend(Box::new(e)) +impl From> for PoolErrorKind { + fn from(e: async_channel::SendError) -> PoolErrorKind { + PoolErrorKind::ChannelSend(Box::new(e)) } } -impl From for PoolError { - fn from(e: String) -> PoolError { - PoolError::Custom(e) +impl From for PoolErrorKind { + fn from(e: String) -> PoolErrorKind { + PoolErrorKind::Custom(e) } } -impl From for PoolError { - fn from(e: framing_sv2::Error) -> PoolError { - PoolError::Framing(e) +impl From for PoolErrorKind { + fn from(e: framing_sv2::Error) -> PoolErrorKind { + PoolErrorKind::Framing(e) } } -impl From>> for PoolError { - fn from(e: PoisonError>) -> PoolError { - PoolError::PoisonLock(e.to_string()) +impl From>> for PoolErrorKind { + fn from(e: PoisonError>) -> PoolErrorKind { + PoolErrorKind::PoisonLock(e.to_string()) } } -impl From<(u32, Mining<'static>)> for PoolError { +impl From<(u32, Mining<'static>)> for PoolErrorKind { fn from(e: (u32, Mining<'static>)) -> Self { - PoolError::Sv2ProtocolError(e) - } -} - -impl HandlerErrorType for PoolError { - fn parse_error(error: ParserError) -> Self { - PoolError::Parser(error) - } - - fn unexpected_message(extension_type: ExtensionType, message_type: MessageType) -> Self { - PoolError::UnexpectedMessage(extension_type, message_type) + PoolErrorKind::Sv2ProtocolError(e) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: stratum_apps::stratum_core::bitcoin::consensus::encode::Error) -> Self { - PoolError::BitcoinEncodeError(value) + PoolErrorKind::BitcoinEncodeError(value) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: ExtendedChannelError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(value)) + PoolErrorKind::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(value)) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: StandardChannelError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::StandardChannelServerSide(value)) + PoolErrorKind::ChannelSv2(ChannelSv2Error::StandardChannelServerSide(value)) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: GroupChannelError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::GroupChannelServerSide(value)) + PoolErrorKind::ChannelSv2(ChannelSv2Error::GroupChannelServerSide(value)) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: ExtendedExtranonceError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::ExtranonceError(value)) + PoolErrorKind::ChannelSv2(ChannelSv2Error::ExtranonceError(value)) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: VardiffError) -> Self { - PoolError::Vardiff(value) + PoolErrorKind::Vardiff(value) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: ParserError) -> Self { - PoolError::Parser(value) + PoolErrorKind::Parser(value) } } -impl From for PoolError { +impl From for PoolErrorKind { fn from(value: ShareValidationError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::ShareValidationError(value)) + PoolErrorKind::ChannelSv2(ChannelSv2Error::ShareValidationError(value)) + } +} + +impl HandlerErrorType for PoolError { + fn parse_error(error: ParserError) -> Self { + Self { + kind: PoolErrorKind::Parser(error), + action: Action::Log, + _owner: PhantomData, + } + } + + fn unexpected_message(extension_type: ExtensionType, message_type: MessageType) -> Self { + Self { + kind: PoolErrorKind::UnexpectedMessage(extension_type, message_type), + action: Action::Log, + _owner: PhantomData, + } + } +} + +impl std::fmt::Display for PoolError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "[{:?}/{:?}]", self.kind, self.action) } } diff --git a/pool-apps/pool/src/lib/mod.rs b/pool-apps/pool/src/lib/mod.rs index d6f5795a9..2351817a9 100644 --- a/pool-apps/pool/src/lib/mod.rs +++ b/pool-apps/pool/src/lib/mod.rs @@ -13,7 +13,7 @@ use tracing::{debug, error, info, warn}; use crate::{ channel_manager::ChannelManager, config::PoolConfig, - error::PoolResult, + error::PoolErrorKind, status::State, template_receiver::{ bitcoin_core::{connect_to_bitcoin_core, BitcoinCoreSv2Config}, @@ -48,7 +48,7 @@ impl PoolSv2 { } /// Starts the Pool main loop. - pub async fn start(&self) -> PoolResult<()> { + pub async fn start(&self) -> Result<(), PoolErrorKind> { let coinbase_outputs = vec![self.config.get_txout()]; let mut encoded_outputs = vec![]; diff --git a/pool-apps/pool/src/lib/status.rs b/pool-apps/pool/src/lib/status.rs index 1fad7da99..b2fffd17f 100644 --- a/pool-apps/pool/src/lib/status.rs +++ b/pool-apps/pool/src/lib/status.rs @@ -7,9 +7,9 @@ //! converted into shutdown signals, allowing coordinated teardown of tasks. use stratum_apps::utils::types::DownstreamId; -use tracing::{debug, error, warn}; +use tracing::{debug, warn}; -use crate::error::PoolError; +use crate::error::{Action, PoolError, PoolErrorKind}; /// Sender type for propagating status updates from different system components. #[derive(Debug, Clone)] @@ -79,12 +79,12 @@ pub enum State { /// A downstream connection has shut down with a reason. DownstreamShutdown { downstream_id: DownstreamId, - reason: PoolError, + reason: PoolErrorKind, }, /// Template receiver has shut down with a reason. - TemplateReceiverShutdown(PoolError), + TemplateReceiverShutdown(PoolErrorKind), /// Channel manager has shut down with a reason. - ChannelManagerShutdown(PoolError), + ChannelManagerShutdown(PoolErrorKind), } /// Wrapper around a component’s state, sent as status updates across the system. @@ -95,33 +95,59 @@ pub struct Status { } #[cfg_attr(not(test), hotpath::measure)] -/// Sends a shutdown status for the given component, logging the error cause. -async fn send_status(sender: &StatusSender, error: PoolError) { - let state = match sender { - StatusSender::Downstream { downstream_id, .. } => { - warn!("Downstream [{downstream_id}] shutting down due to error: {error:?}"); - State::DownstreamShutdown { - downstream_id: *downstream_id, - reason: error, - } - } - StatusSender::TemplateReceiver(_) => { - warn!("Template Receiver shutting down due to error: {error:?}"); - State::TemplateReceiverShutdown(error) +async fn send_status(sender: &StatusSender, error: PoolError) -> bool { + use Action::*; + + match error.action { + Log => { + warn!("Log-only error from {:?}: {:?}", sender, error.kind); + false } - StatusSender::ChannelManager(_) => { - warn!("ChannelManager shutting down due to error: {error:?}"); - State::ChannelManagerShutdown(error) + + Disconnect(downstream_id) => { + let state = State::DownstreamShutdown { + downstream_id, + reason: error.kind, + }; + + if let Err(e) = sender.send(Status { state }).await { + tracing::error!( + "Failed to send downstream shutdown status from {:?}: {:?}", + sender, + e + ); + std::process::abort(); + } + matches!(sender, StatusSender::Downstream { .. }) } - }; + Shutdown => { + let state = match sender { + StatusSender::ChannelManager(_) => { + warn!( + "Channel Manager shutdown requested due to error: {:?}", + error.kind + ); + State::ChannelManagerShutdown(error.kind) + } + StatusSender::TemplateReceiver(_) => { + warn!( + "Template Receiver shutdown requested due to error: {:?}", + error.kind + ); + State::TemplateReceiverShutdown(error.kind) + } + _ => State::ChannelManagerShutdown(error.kind), + }; - if let Err(e) = sender.send(Status { state }).await { - tracing::error!("Failed to send status update from {sender:?}: {e:?}"); + if let Err(e) = sender.send(Status { state }).await { + tracing::error!("Failed to send shutdown status from {:?}: {:?}", sender, e); + std::process::abort(); + } + true + } } } -/// Logs an error and propagates a corresponding shutdown status for the component. -pub async fn handle_error(sender: &StatusSender, e: PoolError) { - error!("Error in {:?}: {:?}", sender, e); - send_status(sender, e).await; +pub async fn handle_error(sender: &StatusSender, e: PoolError) -> bool { + send_status(sender, e).await } diff --git a/pool-apps/pool/src/lib/template_receiver/bitcoin_core.rs b/pool-apps/pool/src/lib/template_receiver/bitcoin_core.rs index af9fcf893..84f86b676 100644 --- a/pool-apps/pool/src/lib/template_receiver/bitcoin_core.rs +++ b/pool-apps/pool/src/lib/template_receiver/bitcoin_core.rs @@ -1,5 +1,5 @@ use crate::{ - error::PoolError, + error::{self, PoolError, PoolErrorKind}, status::{handle_error, State, Status, StatusSender}, utils::ShutdownMessage, }; @@ -44,9 +44,9 @@ pub async fn connect_to_bitcoin_core( // turn status_sender into a StatusSender::TemplateReceiver let status_sender = StatusSender::TemplateReceiver(status_sender_clone); - handle_error( + handle_error::( &status_sender, - PoolError::BitcoinCoreSv2CancellationTokenActivated, + PoolError::shutdown(PoolErrorKind::BitcoinCoreSv2CancellationTokenActivated), ) .await; break; @@ -70,7 +70,7 @@ pub async fn connect_to_bitcoin_core( // we can't use handle_error here because we're not in a async context yet let _ = status_sender_clone.send_blocking(Status { state: State::TemplateReceiverShutdown( - PoolError::FailedToCreateBitcoinCoreTokioRuntime, + PoolErrorKind::FailedToCreateBitcoinCoreTokioRuntime, ), }); return; diff --git a/pool-apps/pool/src/lib/template_receiver/sv2_tp/common_message_handler.rs b/pool-apps/pool/src/lib/template_receiver/sv2_tp/common_message_handler.rs index 87a03a3d3..bfa1fedf7 100644 --- a/pool-apps/pool/src/lib/template_receiver/sv2_tp/common_message_handler.rs +++ b/pool-apps/pool/src/lib/template_receiver/sv2_tp/common_message_handler.rs @@ -7,11 +7,14 @@ use stratum_apps::stratum_core::{ }; use tracing::{error, info}; -use crate::{error::PoolError, template_receiver::sv2_tp::Sv2Tp}; +use crate::{ + error::{self, PoolError, PoolErrorKind}, + template_receiver::sv2_tp::Sv2Tp, +}; #[cfg_attr(not(test), hotpath::measure_all)] impl HandleCommonMessagesFromServerAsync for Sv2Tp { - type Error = PoolError; + type Error = PoolError; fn get_negotiated_extensions_with_server( &self, @@ -43,7 +46,7 @@ impl HandleCommonMessagesFromServerAsync for Sv2Tp { "Received ChannelEndpointChanged with channel id: {}", msg.channel_id ); - Err(PoolError::Shutdown) + Err(PoolError::shutdown(PoolErrorKind::ChangeEndpoint)) } async fn handle_reconnect( @@ -66,6 +69,6 @@ impl HandleCommonMessagesFromServerAsync for Sv2Tp { "Received `SetupConnectionError` from TP with error code {}", std::str::from_utf8(msg.error_code.as_ref()).unwrap_or("unknown error code") ); - Err(PoolError::Shutdown) + Err(PoolError::shutdown(PoolErrorKind::SetupConnectionError)) } } diff --git a/pool-apps/pool/src/lib/template_receiver/sv2_tp/mod.rs b/pool-apps/pool/src/lib/template_receiver/sv2_tp/mod.rs index 6b67ecd5e..45f813007 100644 --- a/pool-apps/pool/src/lib/template_receiver/sv2_tp/mod.rs +++ b/pool-apps/pool/src/lib/template_receiver/sv2_tp/mod.rs @@ -8,7 +8,7 @@ use stratum_apps::{ codec_sv2::HandshakeRole, framing_sv2, handlers_sv2::HandleCommonMessagesFromServerAsync, - noise_sv2::{Error, Initiator}, + noise_sv2::Initiator, parsers_sv2::{AnyMessage, TemplateDistribution}, }, task_manager::TaskManager, @@ -21,7 +21,7 @@ use tokio::{net::TcpStream, sync::broadcast}; use tracing::{debug, error, info, warn}; use crate::{ - error::{PoolError, PoolResult}, + error::{self, PoolError, PoolErrorKind, PoolResult}, io_task::spawn_io_tasks, status::{handle_error, Status, StatusSender}, utils::{get_setup_connection_message_tp, ShutdownMessage}, @@ -57,7 +57,7 @@ impl Sv2Tp { notify_shutdown: broadcast::Sender, task_manager: Arc, status_sender: Sender, - ) -> PoolResult { + ) -> PoolResult { const MAX_RETRIES: usize = 3; for attempt in 1..=MAX_RETRIES { @@ -72,7 +72,8 @@ impl Sv2Tp { debug!(attempt, "Using anonymous initiator (no public key)"); Initiator::without_pk() } - }?; + } + .map_err(PoolError::shutdown)?; match TcpStream::connect(tp_address.as_str()).await { Ok(stream) => { @@ -137,7 +138,7 @@ impl Sv2Tp { } error!("Exhausted all connection attempts, shutting down TemplateReceiver"); - Err(PoolError::Shutdown) + Err(PoolError::shutdown(PoolErrorKind::CouldNotInitiateSystem)) } /// Start unified message loop for Sv2Tp. @@ -154,7 +155,7 @@ impl Sv2Tp { notify_shutdown: broadcast::Sender, status_sender: Sender, task_manager: Arc, - ) -> PoolResult<()> { + ) -> PoolResult<(), error::TemplateProvider> { let status_sender = StatusSender::TemplateReceiver(status_sender); let mut shutdown_rx = notify_shutdown.subscribe(); @@ -184,15 +185,17 @@ impl Sv2Tp { res = self_clone_1.handle_template_provider_message() => { if let Err(e) = res { error!("TemplateReceiver template provider handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } } res = self_clone_2.handle_channel_manager_message() => { if let Err(e) = res { error!("TemplateReceiver channel manager handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; + if handle_error(&status_sender, e).await { + break; + } } }, } @@ -209,12 +212,19 @@ impl Sv2Tp { /// - `Common` messages → handled locally /// - `TemplateDistribution` messages → forwarded to ChannelManager /// - Unsupported messages → logged and ignored - pub async fn handle_template_provider_message(&mut self) -> PoolResult<()> { - let mut sv2_frame = self.sv2_tp_channel.tp_receiver.recv().await?; + pub async fn handle_template_provider_message( + &mut self, + ) -> PoolResult<(), error::TemplateProvider> { + let mut sv2_frame = self + .sv2_tp_channel + .tp_receiver + .recv() + .await + .map_err(PoolError::shutdown)?; debug!("Received SV2 frame from Template provider."); let header = sv2_frame.get_header().ok_or_else(|| { error!("SV2 frame missing header"); - PoolError::Framing(framing_sv2::Error::MissingHeader) + PoolError::shutdown(framing_sv2::Error::MissingHeader) })?; match protocol_message_type(header.ext_type(), header.msg_type()) { @@ -230,7 +240,8 @@ impl Sv2Tp { } MessageType::TemplateDistribution => { let message = - TemplateDistribution::try_from((header.msg_type(), sv2_frame.payload()))? + TemplateDistribution::try_from((header.msg_type(), sv2_frame.payload())) + .map_err(PoolError::shutdown)? .into_static(); self.sv2_tp_channel @@ -239,7 +250,7 @@ impl Sv2Tp { .await .map_err(|e| { error!(error=?e, "Failed to send template distribution message to channel manager."); - PoolError::ChannelErrorSender + PoolError::shutdown(PoolErrorKind::ChannelErrorSender) })?; } _ => { @@ -256,31 +267,41 @@ impl Sv2Tp { /// Handle messages from channel manager → template provider. /// /// Forwards outbound frames upstream - pub async fn handle_channel_manager_message(&self) -> PoolResult<()> { - let msg = self.sv2_tp_channel.channel_manager_receiver.recv().await?; + pub async fn handle_channel_manager_message(&self) -> PoolResult<(), error::TemplateProvider> { + let msg = self + .sv2_tp_channel + .channel_manager_receiver + .recv() + .await + .map_err(PoolError::shutdown)?; let message = AnyMessage::TemplateDistribution(msg).into_static(); - let frame: Sv2Frame = message.try_into()?; + let frame: Sv2Frame = message.try_into().map_err(PoolError::shutdown)?; debug!("Forwarding message from channel manager to outbound_tx"); self.sv2_tp_channel .tp_sender .send(frame) .await - .map_err(|_| PoolError::ChannelErrorSender)?; + .map_err(|_| PoolError::shutdown(PoolErrorKind::ChannelErrorSender))?; Ok(()) } // Performs the initial handshake with Template Provider. - pub async fn setup_connection(&mut self, addr: String) -> PoolResult<()> { + pub async fn setup_connection( + &mut self, + addr: String, + ) -> PoolResult<(), error::TemplateProvider> { let socket: SocketAddr = addr.parse().map_err(|_| { error!(%addr, "Invalid socket address"); - PoolError::InvalidSocketAddress(addr.clone()) + PoolError::shutdown(PoolErrorKind::InvalidSocketAddress(addr.clone())) })?; debug!(%socket, "Building SetupConnection message to the Template Provider"); - let setup_msg = get_setup_connection_message_tp(socket); - let frame: Sv2Frame = Message::Common(setup_msg.into()).try_into()?; + let setup_msg = get_setup_connection_message_tp(socket).map_err(PoolError::shutdown)?; + let frame: Sv2Frame = Message::Common(setup_msg.into()) + .try_into() + .map_err(PoolError::shutdown)?; info!("Sending SetupConnection message to the Template Provider"); self.sv2_tp_channel @@ -289,18 +310,18 @@ impl Sv2Tp { .await .map_err(|_| { error!("Failed to send setup connection message upstream"); - PoolError::ChannelErrorSender + PoolError::shutdown(PoolErrorKind::ChannelErrorSender) })?; info!("Waiting for upstream handshake response"); let mut incoming: Sv2Frame = self.sv2_tp_channel.tp_receiver.recv().await.map_err(|e| { error!(?e, "Upstream connection closed during handshake"); - PoolError::Noise(Error::ExpectedIncomingHandshakeMessage) + PoolError::shutdown(e) })?; let header = incoming.get_header().ok_or_else(|| { error!("Handshake frame missing header"); - PoolError::Framing(framing_sv2::Error::MissingHeader) + PoolError::shutdown(framing_sv2::Error::MissingHeader) })?; debug!( ext_type = ?header.ext_type(), diff --git a/pool-apps/pool/src/lib/utils.rs b/pool-apps/pool/src/lib/utils.rs index 84b28aaca..ffe98495f 100644 --- a/pool-apps/pool/src/lib/utils.rs +++ b/pool-apps/pool/src/lib/utils.rs @@ -1,10 +1,14 @@ use std::net::SocketAddr; use stratum_apps::{ - stratum_core::common_messages_sv2::{Protocol, SetupConnection}, - utils::types::DownstreamId, + stratum_core::{ + binary_sv2::Str0255, + common_messages_sv2::{Protocol, SetupConnection}, + mining_sv2::CloseChannel, + }, + utils::types::{ChannelId, DownstreamId}, }; -use crate::error::PoolResult; +use crate::error::PoolErrorKind; /// Represents a message that can trigger shutdown of various system components. #[derive(Debug, Clone)] @@ -22,7 +26,7 @@ pub enum ShutdownMessage { pub fn get_setup_connection_message( min_version: u16, max_version: u16, -) -> PoolResult> { +) -> Result, PoolErrorKind> { let endpoint_host = "0.0.0.0".to_string().into_bytes().try_into()?; let vendor = String::new().try_into()?; let hardware_version = String::new().try_into()?; @@ -44,13 +48,16 @@ pub fn get_setup_connection_message( } /// Constructs a `SetupConnection` message for the Template Provider (TP). -pub fn get_setup_connection_message_tp(address: SocketAddr) -> SetupConnection<'static> { - let endpoint_host = address.ip().to_string().into_bytes().try_into().unwrap(); - let vendor = String::new().try_into().unwrap(); - let hardware_version = String::new().try_into().unwrap(); - let firmware = String::new().try_into().unwrap(); - let device_id = String::new().try_into().unwrap(); - SetupConnection { +#[allow(clippy::result_large_err)] +pub fn get_setup_connection_message_tp( + address: SocketAddr, +) -> Result, PoolErrorKind> { + let endpoint_host = address.ip().to_string().into_bytes().try_into()?; + let vendor = String::new().try_into()?; + let hardware_version = String::new().try_into()?; + let firmware = String::new().try_into()?; + let device_id = String::new().try_into()?; + Ok(SetupConnection { protocol: Protocol::TemplateDistributionProtocol, min_version: 2, max_version: 2, @@ -61,5 +68,16 @@ pub fn get_setup_connection_message_tp(address: SocketAddr) -> SetupConnection<' hardware_version, firmware, device_id, + }) +} + +/// Creates a [`CloseChannel`] message for the given channel ID and reason. +/// +/// The `msg` is converted into a [`Str0255`] reason code. +/// If conversion fails, this function will panic. +pub(crate) fn create_close_channel_msg(channel_id: ChannelId, msg: &str) -> CloseChannel<'_> { + CloseChannel { + channel_id, + reason_code: Str0255::try_from(msg.to_string()).expect("Could not convert message."), } } diff --git a/stratum-apps/src/utils/types.rs b/stratum-apps/src/utils/types.rs index c490b50b7..90cbd4187 100644 --- a/stratum-apps/src/utils/types.rs +++ b/stratum-apps/src/utils/types.rs @@ -29,3 +29,9 @@ impl From<(DownstreamId, ChannelId)> for VardiffKey { } } } + +/// Marker traits used to define set of action +/// the implementor can take +pub trait CanDisconnect {} +pub trait CanFallback {} +pub trait CanShutdown {}