diff --git a/Cargo.lock b/Cargo.lock index ce59fd8f98..b52a1fd376 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,7 @@ dependencies = [ [[package]] name = "channels_sv2" -version = "5.0.0" +version = "5.1.0" dependencies = [ "binary_sv2", "bitcoin", diff --git a/sv2/channels-sv2/Cargo.toml b/sv2/channels-sv2/Cargo.toml index 1c22cd1e15..e4dae3cf3e 100644 --- a/sv2/channels-sv2/Cargo.toml +++ b/sv2/channels-sv2/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "channels_sv2" -version = "5.0.0" +version = "5.1.0" authors = ["The Stratum V2 Developers"] edition = "2021" readme = "README.md" diff --git a/sv2/channels-sv2/src/server/extended.rs b/sv2/channels-sv2/src/server/extended.rs index 899278935b..21e91f5c41 100644 --- a/sv2/channels-sv2/src/server/extended.rs +++ b/sv2/channels-sv2/src/server/extended.rs @@ -435,6 +435,11 @@ where &self.share_accounting } + /// Updates rejected-share accounting for an emitted `SubmitShares.Error`. + pub fn increment_rejected_shares(&mut self, error_code: &str) { + self.share_accounting.increment_rejected_shares(error_code); + } + /// Updates the channel state with a new template. /// /// If the template is a future template, the chain tip is not used. diff --git a/sv2/channels-sv2/src/server/share_accounting.rs b/sv2/channels-sv2/src/server/share_accounting.rs index a7dd53e49d..b1d7136cdd 100644 --- a/sv2/channels-sv2/src/server/share_accounting.rs +++ b/sv2/channels-sv2/src/server/share_accounting.rs @@ -11,15 +11,16 @@ //! success, batch acknowledgment, and block discovery. //! - **Share Validation Error**: Enumerates possible failure reasons when validating a share. //! - **Share Accounting**: Tracks per-channel share statistics, acknowledges batches, detects -//! duplicate shares, and maintains best difficulty found. +//! duplicate shares, tracks rejected shares, and maintains best difficulty found. //! //! ## Usage //! //! Intended for use within mining server implementations that process SV2 share submissions and -//! issue `SubmitShares.Success` messages. Not intended for use by mining clients. +//! issue `SubmitShares.Success` or `SubmitShares.Error` messages. Not intended for use by mining +//! clients. use bitcoin::hashes::sha256d::Hash; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; /// The outcome of share validation, from the perspective of a Mining Server. /// @@ -68,11 +69,12 @@ pub enum ShareValidationError { /// Standard). /// /// This struct manages per-channel share statistics, batch acknowledgment, duplicate detection, -/// and difficulty tracking. Only meant for usage on Mining Servers. +/// rejected-share accounting, and difficulty tracking. Only meant for usage on Mining Servers. #[derive(Clone, Debug)] pub struct ShareAccounting { last_share_sequence_number: u32, shares_accepted: u32, + rejected_shares: HashMap, share_work_sum: f64, last_batch_accepted: u32, last_batch_work_sum: f64, @@ -91,6 +93,7 @@ impl ShareAccounting { Self { last_share_sequence_number: 0, shares_accepted: 0, + rejected_shares: HashMap::new(), share_work_sum: 0.0, last_batch_accepted: 0, last_batch_work_sum: 0.0, @@ -102,6 +105,19 @@ impl ShareAccounting { } } + /// Increments rejected-share accounting for an emitted `SubmitShares.Error`. + /// + /// This should only be called by the application layer after it decides to send a + /// `SubmitShares.Error` downstream. Validation errors that are handled internally and do not + /// produce `SubmitShares.Error` messages should not be counted here. + pub fn increment_rejected_shares(&mut self, error_code: &str) { + if let Some(count) = self.rejected_shares.get_mut(error_code) { + *count += 1; + } else { + self.rejected_shares.insert(error_code.to_string(), 1); + } + } + /// Updates internal accounting for a newly accepted share. /// /// - Increments total shares accepted and work sum. @@ -160,6 +176,16 @@ impl ShareAccounting { self.shares_accepted } + /// Returns a reference to the map of rejected shares by error code. + pub fn get_rejected_shares(&self) -> &HashMap { + &self.rejected_shares + } + + /// Returns the total number of rejected shares on this channel. + pub fn get_rejected_shares_total(&self) -> u32 { + self.rejected_shares.values().copied().sum() + } + /// Returns the sum of work contributed by all accepted shares. /// /// Note: this is not what we use for `SubmitShares.Success` messages. @@ -214,3 +240,33 @@ impl ShareAccounting { self.blocks_found } } + +#[cfg(test)] +mod tests { + use super::ShareAccounting; + + #[test] + fn rejected_shares_are_tracked_by_error_code() { + let mut accounting = ShareAccounting::new(10); + + accounting.increment_rejected_shares("difficulty-too-low"); + accounting.increment_rejected_shares("duplicate-share"); + accounting.increment_rejected_shares("difficulty-too-low"); + + assert_eq!(accounting.get_rejected_shares_total(), 3); + assert_eq!( + accounting + .get_rejected_shares() + .get("difficulty-too-low") + .copied(), + Some(2) + ); + assert_eq!( + accounting + .get_rejected_shares() + .get("duplicate-share") + .copied(), + Some(1) + ); + } +} diff --git a/sv2/channels-sv2/src/server/standard.rs b/sv2/channels-sv2/src/server/standard.rs index 0e07f7e426..73db06d081 100644 --- a/sv2/channels-sv2/src/server/standard.rs +++ b/sv2/channels-sv2/src/server/standard.rs @@ -410,6 +410,11 @@ where &self.share_accounting } + /// Updates rejected-share accounting for an emitted `SubmitShares.Error`. + pub fn increment_rejected_shares(&mut self, error_code: &str) { + self.share_accounting.increment_rejected_shares(error_code); + } + /// Updates the channel state with a new job. /// /// If the template is a future template, the chain tip is not used.