Skip to content
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions stratum-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stratum-core"
version = "0.3.0"
version = "0.4.0"
authors = ["The Stratum V2 Developers"]
edition = "2021"
readme = "README.md"
Expand All @@ -18,15 +18,15 @@ codec_sv2 = { path = "../sv2/codec-sv2", version = "^5.0.0", features = ["noise_
extensions_sv2 = { path = "../sv2/extensions-sv2", version = "^0.1.0" }
framing_sv2 = { path = "../sv2/framing-sv2", version = "^6.0.0" }
noise_sv2 = { path = "../sv2/noise-sv2", version = "^1.0.0" }
parsers_sv2 = { path = "../sv2/parsers-sv2", version = "^0.3.0" }
handlers_sv2 = { path = "../sv2/handlers-sv2", version = "^0.3.0" }
channels_sv2 = { path = "../sv2/channels-sv2", version = "^5.0.0" }
common_messages_sv2 = { path = "../sv2/subprotocols/common-messages", version = "^7.0.0" }
mining_sv2 = { path = "../sv2/subprotocols/mining", version = "^9.0.0" }
template_distribution_sv2 = { path = "../sv2/subprotocols/template-distribution", version = "^5.0.0" }
job_declaration_sv2 = { path = "../sv2/subprotocols/job-declaration", version = "^7.0.0" }
parsers_sv2 = { path = "../sv2/parsers-sv2", version = "^0.4.0" }
handlers_sv2 = { path = "../sv2/handlers-sv2", version = "^0.4.0" }
channels_sv2 = { path = "../sv2/channels-sv2", version = "^6.0.0" }
common_messages_sv2 = { path = "../sv2/subprotocols/common-messages", version = "^7.2.0" }
mining_sv2 = { path = "../sv2/subprotocols/mining", version = "^10.0.0" }
template_distribution_sv2 = { path = "../sv2/subprotocols/template-distribution", version = "^5.1.0" }
job_declaration_sv2 = { path = "../sv2/subprotocols/job-declaration", version = "^7.1.0" }
sv1_api = { path = "../sv1", version = "^4.0.0", optional = true }
stratum_translation = { path = "stratum-translation", version = "^0.2.0", optional = true }
stratum_translation = { path = "stratum-translation", version = "^0.3.0", optional = true }
bitcoin = { workspace = true }

[features]
Expand Down
6 changes: 3 additions & 3 deletions stratum-core/stratum-translation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stratum_translation"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Stratum V1 ↔ Stratum V2 translation utilities for reuse across proxies, apps, and firmware"
Expand All @@ -11,8 +11,8 @@ path = "src/lib.rs"

[dependencies]
binary_sv2 = { path = "../../sv2/binary-sv2", version = "^5.0.0" }
mining_sv2 = { path = "../../sv2/subprotocols/mining", version = "^9.0.0" }
channels_sv2 = { path = "../../sv2/channels-sv2", version = "^5.0.0" }
mining_sv2 = { path = "../../sv2/subprotocols/mining", version = "^10.0.0" }
channels_sv2 = { path = "../../sv2/channels-sv2", version = "^6.0.0" }
v1 = { path = "../../sv1", package = "sv1_api", version = "^4.0.0" }
tracing = { workspace = true }
bitcoin = { workspace = true }
6 changes: 3 additions & 3 deletions sv2/channels-sv2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "channels_sv2"
version = "5.0.0"
version = "6.0.0"
authors = ["The Stratum V2 Developers"]
edition = "2021"
readme = "README.md"
Expand All @@ -15,8 +15,8 @@ keywords = ["stratum", "mining", "bitcoin", "protocol"]

[dependencies]
binary_sv2 = { path = "../binary-sv2", version = "^5.0.0" }
mining_sv2 = { path = "../subprotocols/mining", version = "^9.0.0" }
template_distribution_sv2 = { path = "../subprotocols/template-distribution", version = "^5.0.0" }
mining_sv2 = { path = "../subprotocols/mining", version = "^10.0.0" }
template_distribution_sv2 = { path = "../subprotocols/template-distribution", version = "^5.1.0" }
tracing = { workspace = true }
bitcoin = { workspace = true }
primitive-types = { workspace = true }
Expand Down
42 changes: 31 additions & 11 deletions sv2/channels-sv2/src/client/extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ use bitcoin::{
use mining_sv2::{
NewExtendedMiningJob, SetCustomMiningJob, SetCustomMiningJobSuccess,
SetNewPrevHash as SetNewPrevHashMp, SubmitSharesExtended,
ERROR_CODE_SUBMIT_SHARES_BAD_EXTRANONCE_SIZE, ERROR_CODE_SUBMIT_SHARES_DIFFICULTY_TOO_LOW,
ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE, ERROR_CODE_SUBMIT_SHARES_INVALID_JOB_ID,
ERROR_CODE_SUBMIT_SHARES_INVALID_SHARE, ERROR_CODE_SUBMIT_SHARES_STALE_SHARE,
ERROR_CODE_VERSION_ROLLING_NOT_ALLOWED,
};
use tracing::debug;

Expand Down Expand Up @@ -502,20 +506,26 @@ impl<'a> ExtendedChannel<'a> {
let is_stale_job = self.stale_jobs.contains_key(&job_id);

if is_stale_job {
return Err(ShareValidationError::Stale);
return Err(ShareValidationError::Stale(
ERROR_CODE_SUBMIT_SHARES_STALE_SHARE,
));
}

let job = if is_active_job {
self.active_job.as_ref().expect("active job must exist")
} else if is_past_job {
self.past_jobs.get(&job_id).expect("past job must exist")
} else {
return Err(ShareValidationError::InvalidJobId);
return Err(ShareValidationError::InvalidJobId(
ERROR_CODE_SUBMIT_SHARES_INVALID_JOB_ID,
));
};

let extranonce_size = share.extranonce.inner_as_ref().len();
if extranonce_size != self.rollable_extranonce_size as usize {
return Err(ShareValidationError::BadExtranonceSize);
return Err(ShareValidationError::BadExtranonceSize(
ERROR_CODE_SUBMIT_SHARES_BAD_EXTRANONCE_SIZE,
));
}

let mut full_extranonce = vec![];
Expand All @@ -533,7 +543,9 @@ impl<'a> ExtendedChannel<'a> {
full_extranonce.as_ref(),
&job.0.merkle_path.inner_as_ref(),
)
.ok_or(ShareValidationError::Invalid)?
.ok_or(ShareValidationError::Invalid(
ERROR_CODE_SUBMIT_SHARES_INVALID_SHARE,
))?
.try_into()
.expect("merkle root must be 32 bytes");

Expand All @@ -551,7 +563,9 @@ impl<'a> ExtendedChannel<'a> {
// This is done by checking if the version & 0x1fffe000 == 0
// ref: https://github.com/bitcoin/bips/blob/master/bip-0320.mediawiki
if (share.version & 0x1fffe000) != 0 {
return Err(ShareValidationError::VersionRollingNotAllowed);
return Err(ShareValidationError::VersionRollingNotAllowed(
ERROR_CODE_VERSION_ROLLING_NOT_ALLOWED,
));
}
}

Expand Down Expand Up @@ -591,7 +605,9 @@ impl<'a> ExtendedChannel<'a> {
.share_accounting
.is_share_seen(share_hash.to_raw_hash())
{
return Err(ShareValidationError::DuplicateShare);
return Err(ShareValidationError::DuplicateShare(
ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE,
));
}
self.share_accounting
.track_validated_share(share.sequence_number, share_hash.to_raw_hash());
Expand All @@ -605,7 +621,9 @@ impl<'a> ExtendedChannel<'a> {
.share_accounting
.is_share_seen(share_hash.to_raw_hash())
{
return Err(ShareValidationError::DuplicateShare);
return Err(ShareValidationError::DuplicateShare(
ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE,
));
}

self.share_accounting
Expand All @@ -617,7 +635,9 @@ impl<'a> ExtendedChannel<'a> {
return Ok(ShareValidationResult::Valid(share_hash.to_raw_hash()));
}

Err(ShareValidationError::DoesNotMeetTarget)
Err(ShareValidationError::DoesNotMeetTarget(
ERROR_CODE_SUBMIT_SHARES_DIFFICULTY_TOO_LOW,
))
}
}

Expand Down Expand Up @@ -896,7 +916,7 @@ mod tests {
let res = channel.validate_share(share_valid_block);
assert!(matches!(
res.unwrap_err(),
ShareValidationError::DuplicateShare
ShareValidationError::DuplicateShare(_)
));
assert_eq!(channel.get_share_accounting().get_blocks_found(), 1);
}
Expand Down Expand Up @@ -992,7 +1012,7 @@ mod tests {

assert!(matches!(
res.unwrap_err(),
ShareValidationError::DoesNotMeetTarget
ShareValidationError::DoesNotMeetTarget(_)
));
}

Expand Down Expand Up @@ -1103,7 +1123,7 @@ mod tests {

assert!(matches!(
res.unwrap_err(),
ShareValidationError::DuplicateShare
ShareValidationError::DuplicateShare(_)
));
}
}
22 changes: 14 additions & 8 deletions sv2/channels-sv2/src/client/share_accounting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,31 @@ pub enum ShareValidationResult {

/// Possible errors encountered during share validation.
///
/// Variants carrying `&'static str` are intended to be used as `error_code` values in
/// [`SubmitSharesError`](mining_sv2::SubmitSharesError).
///
/// Variants without `&'static str` SHOULD lead to a client disconnection or application
/// shutdown.
///
/// - `Invalid`: The share is malformed or not valid.
/// - `Stale`: The share refers to an outdated job or block tip.
/// - `InvalidJobId`: The job ID referenced by the share is not recognized.
/// - `DoesNotMeetTarget`: The share does not meet the required target difficulty.
/// - `VersionRollingNotAllowed`: Version rolling is not permitted for this channel/job.
/// - `DuplicateShare`: The share has already been submitted (detected by hash).
/// - `NoChainTip`: The chain tip is unknown or unavailable.
/// - `BadExtranonceSize`: The share extranonce size is different from the channel's rollable
/// extranonce size.
/// - `NoChainTip`: The chain tip is unknown or unavailable.
#[derive(Debug)]
pub enum ShareValidationError {
Invalid,
Stale,
InvalidJobId,
DoesNotMeetTarget,
VersionRollingNotAllowed,
DuplicateShare,
Invalid(&'static str),
Stale(&'static str),
InvalidJobId(&'static str),
DoesNotMeetTarget(&'static str),
VersionRollingNotAllowed(&'static str),
DuplicateShare(&'static str),
BadExtranonceSize(&'static str),
NoChainTip,
BadExtranonceSize,
}

/// Tracks share validation and acceptance state for a specific channel (Extended or Standard).
Expand Down
26 changes: 19 additions & 7 deletions sv2/channels-sv2/src/client/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ use bitcoin::{
};
use mining_sv2::{
NewExtendedMiningJob, NewMiningJob, SetNewPrevHash as SetNewPrevHashMp, SubmitSharesStandard,
ERROR_CODE_SUBMIT_SHARES_DIFFICULTY_TOO_LOW, ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE,
ERROR_CODE_SUBMIT_SHARES_INVALID_JOB_ID, ERROR_CODE_SUBMIT_SHARES_STALE_SHARE,
};
use tracing::debug;

Expand Down Expand Up @@ -310,15 +312,19 @@ impl<'a> StandardChannel<'a> {
let is_stale_job = self.stale_jobs.contains_key(&job_id);

if is_stale_job {
return Err(ShareValidationError::Stale);
return Err(ShareValidationError::Stale(
ERROR_CODE_SUBMIT_SHARES_STALE_SHARE,
));
}

let job = if is_active_job {
self.active_job.as_ref().expect("active job must exist")
} else if is_past_job {
self.past_jobs.get(&job_id).expect("past job must exist")
} else {
return Err(ShareValidationError::InvalidJobId);
return Err(ShareValidationError::InvalidJobId(
ERROR_CODE_SUBMIT_SHARES_INVALID_JOB_ID,
));
};

let merkle_root: [u8; 32] = job
Expand Down Expand Up @@ -372,7 +378,9 @@ impl<'a> StandardChannel<'a> {
.share_accounting
.is_share_seen(share_hash.to_raw_hash())
{
return Err(ShareValidationError::DuplicateShare);
return Err(ShareValidationError::DuplicateShare(
ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE,
));
}
self.share_accounting
.track_validated_share(share.sequence_number, share_hash.to_raw_hash());
Expand All @@ -386,7 +394,9 @@ impl<'a> StandardChannel<'a> {
.share_accounting
.is_share_seen(share_hash.to_raw_hash())
{
return Err(ShareValidationError::DuplicateShare);
return Err(ShareValidationError::DuplicateShare(
ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE,
));
}

self.share_accounting
Expand All @@ -398,7 +408,9 @@ impl<'a> StandardChannel<'a> {
return Ok(ShareValidationResult::Valid(share_hash.to_raw_hash()));
}

Err(ShareValidationError::DoesNotMeetTarget)
Err(ShareValidationError::DoesNotMeetTarget(
ERROR_CODE_SUBMIT_SHARES_DIFFICULTY_TOO_LOW,
))
}
}

Expand Down Expand Up @@ -604,7 +616,7 @@ mod tests {
let res = channel.validate_share(share_valid_block);
assert!(matches!(
res.unwrap_err(),
ShareValidationError::DuplicateShare
ShareValidationError::DuplicateShare(_)
));
assert_eq!(channel.get_share_accounting().get_blocks_found(), 1);
}
Expand Down Expand Up @@ -681,7 +693,7 @@ mod tests {

assert!(matches!(
res.unwrap_err(),
ShareValidationError::DoesNotMeetTarget
ShareValidationError::DoesNotMeetTarget(_)
));
}

Expand Down
Loading
Loading