Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8af3e69
feat: add Persistence trait for ShareAccounting with async channel su…
average-gary Sep 22, 2025
d24dc4e
feat: integrate Persistence trait into ShareAccounting for async even…
average-gary Sep 22, 2025
da08f08
feat: add feature gating for non-default Persistence constructors
average-gary Sep 23, 2025
7d8a115
feat: add file writing for share persistence
average-gary Sep 24, 2025
069e244
feat: add Persistence to Pool
average-gary Sep 24, 2025
20aa89b
refactor: don't pretty print
average-gary Sep 25, 2025
f2be5f3
feat(share-accounting): Add block_found flag to ShareAccountingEvent
average-gary Oct 9, 2025
0afb89c
feat: add user_identity to ShareAccounting
average-gary Sep 24, 2025
96f3dcd
refactor(share-accounting): Remove channel_id and user_identity from …
average-gary Oct 9, 2025
a5b9e42
feat(share-persistence): Add robust error handling for share file per…
average-gary Oct 20, 2025
e3b478c
refactor: remove '_with_persistence' suffix from constructors
average-gary Oct 20, 2025
9da1e55
style: run cargo fmt
average-gary Oct 20, 2025
8f82a11
feat(channels-sv2): Add NoPersistence to test constructors
average-gary Oct 20, 2025
e998903
feat(pool-persistence): Add configurable share persistence mechanism
average-gary Oct 20, 2025
9b21706
refactor(pool): Update dependencies and persistence handling
average-gary Oct 20, 2025
e49c5d4
refactor(channels-sv2): Add NoPersistence to channel types
average-gary Oct 21, 2025
0ddd34c
fix(fmt): use correct rust version to satify GH actions
average-gary Oct 21, 2025
20e07a1
chore(dependencies): Update Cargo.lock files and dependencies
average-gary Oct 21, 2025
3e34b8f
fix(ci/cd): revert lock to main
average-gary Oct 21, 2025
554403e
fix: no unecessary cloning and extra code block
average-gary Oct 22, 2025
ff65f3e
refactor(channels-sv2): Revise persistence trait and implementation
average-gary Oct 22, 2025
6a2827a
refactor(channels-sv2): Update persistence trait and implementation
average-gary Oct 22, 2025
1f175e6
docs(pool-config): Add share persistence configuration example
average-gary Oct 22, 2025
3b97a85
refactor(channels-sv2): Remove redundant user_identity assignment
average-gary Oct 22, 2025
d90846e
refactor(channels-sv2): Update persistence handling in test modules
average-gary Oct 22, 2025
1af1f83
fmt: fix formatting
average-gary Oct 22, 2025
6e93d7d
revert: bad fmt
average-gary Oct 22, 2025
d3ab6fe
refactor(share-persistence): Genericize SharePersistence
average-gary Oct 23, 2025
4a9e0f2
refactor(share-persistence): Move share persistence to stratum-apps
average-gary Oct 23, 2025
510c7d7
refactor(channels-sv2): Update share accounting and persistence handling
average-gary Oct 23, 2025
27706fa
fix: CI clippy and formatting issues
average-gary Oct 23, 2025
268430c
refactor(channels-sv2): Simplify target calculation in extended channel
average-gary Oct 23, 2025
fb7e4aa
refactor(share-persistence): Remove share sequence number from persis…
average-gary Oct 23, 2025
61915a6
refactor: use type alias
average-gary Oct 23, 2025
9cf7fc5
fix: remove erroneous docstring
average-gary Oct 23, 2025
8987110
fix: debug logging
average-gary Oct 23, 2025
ecfe656
fix: docstring
average-gary Oct 23, 2025
71d9b55
fix: whitespace
average-gary Oct 24, 2025
9e1f624
Revert "refactor(share-persistence): Remove share sequence number fro…
average-gary Oct 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 47 additions & 16 deletions protocols/v2/channels-sv2/src/client/extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
share_accounting::{ShareAccounting, ShareValidationError, ShareValidationResult},
},
merkle_root::merkle_root_from_path,
persistence::PersistenceHandler,
target::{bytes_to_hex, u256_to_block_hash},
MAX_EXTRANONCE_PREFIX_LEN,
};
Expand Down Expand Up @@ -59,7 +60,7 @@ pub type ExtendedJob<'a> = (NewExtendedMiningJob<'a>, Vec<u8>);
/// - Share accounting for the channel (as tracked by the client).
/// - The channel's current chain tip.
#[derive(Clone, Debug)]
pub struct ExtendedChannel<'a> {
pub struct ExtendedChannel<'a, P> {
channel_id: u32,
user_identity: String,
extranonce_prefix: Vec<u8>,
Expand All @@ -74,12 +75,16 @@ pub struct ExtendedChannel<'a> {
past_jobs: HashMap<u32, ExtendedJob<'a>>,
// stale jobs are indexed with job_id (u32)
stale_jobs: HashMap<u32, ExtendedJob<'a>>,
share_accounting: ShareAccounting,
share_accounting: ShareAccounting<P>,
chain_tip: Option<ChainTip>,
}

impl<'a> ExtendedChannel<'a> {
impl<'a, P> ExtendedChannel<'a, P>
where
P: PersistenceHandler,
{
/// Constructs a new [`ExtendedChannel`].
#[allow(clippy::too_many_arguments)]
pub fn new(
channel_id: u32,
user_identity: String,
Expand All @@ -88,6 +93,7 @@ impl<'a> ExtendedChannel<'a> {
nominal_hashrate: f32,
version_rolling: bool,
rollable_extranonce_size: u16,
persistence: P,
) -> Self {
Self {
channel_id,
Expand All @@ -101,7 +107,7 @@ impl<'a> ExtendedChannel<'a> {
active_job: None,
past_jobs: HashMap::new(),
stale_jobs: HashMap::new(),
share_accounting: ShareAccounting::new(),
share_accounting: ShareAccounting::new(persistence),
chain_tip: None,
}
}
Expand Down Expand Up @@ -202,7 +208,7 @@ impl<'a> ExtendedChannel<'a> {
}

/// Returns a reference to the [`ShareAccounting`] for this channel.
pub fn get_share_accounting(&self) -> &ShareAccounting {
pub fn get_share_accounting(&self) -> &ShareAccounting<P> {
&self.share_accounting
}

Expand Down Expand Up @@ -533,9 +539,12 @@ impl<'a> ExtendedChannel<'a> {
// check if a block was found
if network_target.is_met_by(hash) {
self.share_accounting.update_share_accounting(
self.target.difficulty_float() as u64,
self.channel_id,
&self.user_identity,
self.target.difficulty_float(),
share.sequence_number,
hash.to_raw_hash(),
true,
);
return Ok(ShareValidationResult::BlockFound);
}
Expand All @@ -547,13 +556,17 @@ impl<'a> ExtendedChannel<'a> {
}

self.share_accounting.update_share_accounting(
self.target.difficulty_float() as u64,
self.channel_id,
&self.user_identity,
self.target.difficulty_float(),
share.sequence_number,
hash.to_raw_hash(),
false,
);

// update the best diff
self.share_accounting.update_best_diff(hash_as_diff);
self.share_accounting
.update_best_diff(self.channel_id, hash_as_diff);

return Ok(ShareValidationResult::Valid);
}
Expand All @@ -564,9 +577,12 @@ impl<'a> ExtendedChannel<'a> {

#[cfg(test)]
mod tests {
use crate::client::{
extended::ExtendedChannel,
share_accounting::{ShareValidationError, ShareValidationResult},
use crate::{
client::{
extended::ExtendedChannel,
share_accounting::{ShareValidationError, ShareValidationResult},
},
persistence::{Persistence, PersistenceHandler, ShareAccountingEvent},
};
use binary_sv2::Sv2Option;
use bitcoin::Target;
Expand All @@ -575,6 +591,16 @@ mod tests {
};
use std::convert::TryInto;

/// Unit-like type for persistence in tests
#[derive(Debug, Clone)]
struct TestPersistence;

impl PersistenceHandler for TestPersistence {
fn persist_event(&self, _event: ShareAccountingEvent) {
// No-op for tests
}
}

#[test]
fn test_future_job_activation_flow() {
let channel_id = 1;
Expand All @@ -589,14 +615,15 @@ mod tests {
let version_rolling = true;
let rollable_extranonce_size = 4u16;

let mut channel = ExtendedChannel::new(
let mut channel = ExtendedChannel::<Persistence<TestPersistence>>::new(
channel_id,
user_identity,
extranonce_prefix.clone(),
target,
nominal_hashrate,
version_rolling,
rollable_extranonce_size,
Persistence::default(),
);

let future_job = NewExtendedMiningJob {
Expand Down Expand Up @@ -671,14 +698,15 @@ mod tests {
let version_rolling = true;
let rollable_extranonce_size = 4u16;

let mut channel = ExtendedChannel::new(
let mut channel = ExtendedChannel::<Persistence<TestPersistence>>::new(
channel_id,
user_identity,
extranonce_prefix.clone(),
target,
nominal_hashrate,
version_rolling,
rollable_extranonce_size,
Persistence::default(),
);

let ntime: u32 = 1746839905;
Expand Down Expand Up @@ -744,14 +772,15 @@ mod tests {
let version_rolling = true;
let rollable_extranonce_size = 8u16;

let mut channel = ExtendedChannel::new(
let mut channel = ExtendedChannel::<Persistence<TestPersistence>>::new(
channel_id,
user_identity,
extranonce_prefix.clone(),
target,
nominal_hashrate,
version_rolling,
rollable_extranonce_size,
Persistence::default(),
);

let future_job = NewExtendedMiningJob {
Expand Down Expand Up @@ -836,14 +865,15 @@ mod tests {
let version_rolling = true;
let rollable_extranonce_size = 8u16;

let mut channel = ExtendedChannel::new(
let mut channel = ExtendedChannel::<Persistence<TestPersistence>>::new(
channel_id,
user_identity,
extranonce_prefix.clone(),
target,
nominal_hashrate,
version_rolling,
rollable_extranonce_size,
Persistence::default(),
);

let future_job = NewExtendedMiningJob {
Expand Down Expand Up @@ -931,14 +961,15 @@ mod tests {
let version_rolling = true;
let rollable_extranonce_size = 8u16;

let mut channel = ExtendedChannel::new(
let mut channel = ExtendedChannel::<Persistence<TestPersistence>>::new(
channel_id,
user_identity,
extranonce_prefix.clone(),
target,
nominal_hashrate,
version_rolling,
rollable_extranonce_size,
Persistence::default(),
);

let future_job = NewExtendedMiningJob {
Expand Down
62 changes: 47 additions & 15 deletions protocols/v2/channels-sv2/src/client/share_accounting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! are intended for use in Mining Clients.

use super::HashSet;
use crate::persistence::{PersistenceHandler, ShareAccountingEvent};
use bitcoin::hashes::sha256d::Hash;

/// The outcome of share validation, as seen by a Mining Client.
Expand Down Expand Up @@ -46,33 +47,34 @@ pub enum ShareValidationError {
/// Keeps statistics and state for shares submitted through the channel:
/// - last received share's sequence number
/// - total accepted shares
/// - cumulative work from accepted shares
/// - cumulative work from accepted shares (sum of share difficulties as floating point)
/// - hashes of seen shares (for duplicate detection)
/// - highest difficulty seen in accepted shares
#[derive(Clone, Debug)]
pub struct ShareAccounting {
pub struct ShareAccounting<P> {
last_share_sequence_number: u32,
shares_accepted: u32,
share_work_sum: u64,
share_work_sum: f64,
seen_shares: HashSet<Hash>,
best_diff: f64,
persistence: P,
}

impl Default for ShareAccounting {
fn default() -> Self {
Self::new()
}
}

impl ShareAccounting {
impl<P> ShareAccounting<P>
where
P: PersistenceHandler,
{
/// Creates a new [`ShareAccounting`] instance, initializing all statistics to zero.
pub fn new() -> Self {
///
/// `persistence` handles background persistence of share accounting events.
pub fn new(persistence: P) -> Self {
Self {
last_share_sequence_number: 0,
shares_accepted: 0,
share_work_sum: 0,
share_work_sum: 0.0,
seen_shares: HashSet::new(),
best_diff: 0.0,
persistence,
}
}

Expand All @@ -83,14 +85,31 @@ impl ShareAccounting {
/// - Records share hash to detect duplicates.
pub fn update_share_accounting(
&mut self,
share_work: u64,
channel_id: u32,
user_identity: &str,
share_work: f64,
share_sequence_number: u32,
share_hash: Hash,
block_found: bool,
) {
self.last_share_sequence_number = share_sequence_number;
self.shares_accepted += 1;
self.share_work_sum += share_work;
self.seen_shares.insert(share_hash);

// Persist the share accepted event
let event = ShareAccountingEvent::ShareAccepted {
channel_id,
user_identity: user_identity.to_string(),
share_work,
share_sequence_number,
share_hash,
total_shares_accepted: self.shares_accepted,
total_share_work_sum: self.share_work_sum,
timestamp: std::time::SystemTime::now(),
block_found,
};
self.persistence.persist_event(event);
}

/// Clears the set of seen share hashes.
Expand All @@ -99,6 +118,9 @@ impl ShareAccounting {
/// to prevent unbounded memory growth.
pub fn flush_seen_shares(&mut self) {
self.seen_shares.clear();

// Ensure any pending persistence events are flushed
self.persistence.flush();
}

/// Returns the sequence number of the last share received.
Expand All @@ -112,7 +134,7 @@ impl ShareAccounting {
}

/// Returns the cumulative work of all accepted shares.
pub fn get_share_work_sum(&self) -> u64 {
pub fn get_share_work_sum(&self) -> f64 {
self.share_work_sum
}

Expand All @@ -127,9 +149,19 @@ impl ShareAccounting {
}

/// Updates the best difficulty if the new difficulty is higher than the current best.
pub fn update_best_diff(&mut self, diff: f64) {
pub fn update_best_diff(&mut self, channel_id: u32, diff: f64) {
let previous_best_diff = self.best_diff;
if diff > self.best_diff {
self.best_diff = diff;

// Persist the best difficulty updated event
let event = ShareAccountingEvent::BestDifficultyUpdated {
channel_id,
new_best_diff: diff,
previous_best_diff,
timestamp: std::time::SystemTime::now(),
};
self.persistence.persist_event(event);
}
}
}
Loading
Loading