Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion sv2/channels-sv2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "channels_sv2"
version = "7.0.0"
version = "7.1.0"
authors = ["The Stratum V2 Developers"]
edition = "2021"
readme = "README.md"
Expand Down
101 changes: 58 additions & 43 deletions sv2/channels-sv2/src/server/extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ impl<'a> ExtendedChannel<'a> {
/// Returns an error if target/difficulty parameters are invalid or extranonce prefix
/// requirements are not met.
///
/// For non-JD jobs, `pool_tag_string` is added to the coinbase scriptSig in between `/`
/// and `//` delimiters: `/pool_tag_string//`
/// For non-JD jobs, `pool_tag_string` is added to the coinbase scriptSig as
/// `Sv2/pool_tag_string//`.
#[allow(clippy::too_many_arguments)]
pub fn new_for_pool(
channel_id: u32,
Expand Down Expand Up @@ -153,8 +153,8 @@ impl<'a> ExtendedChannel<'a> {
/// Returns an error if target/difficulty parameters are invalid or extranonce prefix
/// requirements are not met.
///
/// The `pool_tag_string` and `miner_tag_string` are added to the coinbase scriptSig in between
/// `/` delimiters: `/pool_tag_string/miner_tag_string/`
/// The `pool_tag_string` and `miner_tag_string` are added to the coinbase scriptSig as
/// `Sv2/pool_tag_string/miner_tag_string/`.
#[allow(clippy::too_many_arguments)]
pub fn new_for_job_declaration_client(
channel_id: u32,
Expand Down Expand Up @@ -220,6 +220,7 @@ impl<'a> ExtendedChannel<'a> {

let script_sig_size = 5 + // BIP34
1 + // OP_PUSHBYTES
3 + // "Sv2"
3 + // `/` delimiters
pool_tag.as_ref().map_or(0, |s| s.len()) +
miner_tag.as_ref().map_or(0, |s| s.len()) +
Expand Down Expand Up @@ -988,7 +989,8 @@ mod tests {
version_rolling_allowed: true,
coinbase_tx_prefix: vec![
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 38, 82, 0, 3, 47, 47, 47, 31,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 41, 82, 0, 6, 83, 118, 50, 47, 47,
47, 31,
]
.try_into()
.unwrap(),
Expand Down Expand Up @@ -1139,7 +1141,8 @@ mod tests {
version_rolling_allowed: true,
coinbase_tx_prefix: vec![
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 38, 82, 0, 3, 47, 47, 47, 31,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 41, 82, 0, 6, 83, 118, 50, 47, 47,
47, 31,
]
.try_into()
.unwrap(),
Expand Down Expand Up @@ -1323,25 +1326,30 @@ mod tests {
.on_new_template(template.clone(), coinbase_reward_outputs)
.unwrap();

// this share has hash 4c68f79a585c8b609e9b43113f73311eada20ec88a70a999406267db3499f1d9
// which satisfies network target
// 7fffff0000000000000000000000000000000000000000000000000000000000
let share_valid_block = SubmitSharesExtended {
channel_id,
sequence_number: 0,
job_id: 1,
nonce: 8,
ntime: 1745596971,
version: 536870912,
extranonce: vec![1, 0, 0, 0, 0, 0, 0, 0].try_into().unwrap(),
};
// Find a share that satisfies the network target.
let mut share_valid_block = None;
for nonce in 0..1_000_000u32 {
let candidate = SubmitSharesExtended {
channel_id,
sequence_number: 0,
job_id: 1,
nonce,
ntime: 1745596971,
version: 536870912,
extranonce: vec![1, 0, 0, 0, 0, 0, 0, 0].try_into().unwrap(),
};

let res = channel.validate_share(share_valid_block.clone());
if matches!(
channel.validate_share(candidate.clone()),
Ok(ShareValidationResult::BlockFound(_, _, _))
) {
share_valid_block = Some(candidate);
break;
}
}

assert!(matches!(
res,
Ok(ShareValidationResult::BlockFound(_, _, _))
));
let share_valid_block = share_valid_block
.expect("must find a block-finding share for this easy network target");
assert_eq!(channel.get_share_accounting().get_blocks_found(), 1);

// re-submitting the same valid block must be rejected as duplicate
Expand Down Expand Up @@ -1559,34 +1567,41 @@ mod tests {
.on_new_template(template.clone(), coinbase_reward_outputs)
.unwrap();

// this share has hash 000004f9d35777e4d56eedc20b1d05d251a7c0ed0b4e3013b5a809852844e218
// which does meet the channel target
// 0001179d9861a761ffdadd11c307c4fc04eea3a418f7d687584e4434af158205
// but does not meet network target
// 000000000000d7c0000000000000000000000000000000000000000000000000
let valid_share = SubmitSharesExtended {
channel_id,
sequence_number: 1,
job_id: 1,
nonce: 51208,
ntime: 1745611105,
version: 536870912,
extranonce: vec![1, 0, 0, 0, 0, 0, 0, 0].try_into().unwrap(),
};
// Find a share that meets the channel target but not the network target.
let mut valid_share = None;
for nonce in 0..2_000_000u32 {
let candidate = SubmitSharesExtended {
channel_id,
sequence_number: 1,
job_id: 1,
nonce,
ntime: 1745611105,
version: 536870912,
extranonce: vec![1, 0, 0, 0, 0, 0, 0, 0].try_into().unwrap(),
};

let res = channel.validate_share(valid_share);
assert!(matches!(res, Ok(ShareValidationResult::Valid(_))));
if matches!(
channel.validate_share(candidate.clone()),
Ok(ShareValidationResult::Valid(_))
) {
valid_share = Some(candidate);
break;
}
}

let valid_share =
valid_share.expect("must find a valid (non-block) share under the channel target");

// try to cheat by re-submitting the same share
// with a different sequence number
let repeated_share = SubmitSharesExtended {
channel_id,
sequence_number: 2,
job_id: 1,
nonce: 51208,
ntime: 1745611105,
version: 536870912,
extranonce: vec![1, 0, 0, 0, 0, 0, 0, 0].try_into().unwrap(),
nonce: valid_share.nonce,
ntime: valid_share.ntime,
version: valid_share.version,
extranonce: valid_share.extranonce,
};

let res = channel.validate_share(repeated_share);
Expand Down
15 changes: 9 additions & 6 deletions sv2/channels-sv2/src/server/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ impl<'a> GroupChannel<'a> {
/// Initializes the group channel state with the provided group channel ID.
/// The job factory is initialized with version rolling enabled.
///
/// For non-JD jobs, `pool_tag_string` is added to the coinbase scriptSig in between `/`
/// and `//` delimiters: `/pool_tag_string//`
/// For non-JD jobs, `pool_tag_string` is added to the coinbase scriptSig as
/// `Sv2/pool_tag_string//`.
pub fn new_for_pool(
group_channel_id: u32,
full_extranonce_size: usize,
Expand All @@ -99,8 +99,8 @@ impl<'a> GroupChannel<'a> {
/// Returns an error if target/difficulty parameters are invalid or extranonce prefix
/// requirements are not met.
///
/// The `pool_tag_string` and `miner_tag_string` are added to the coinbase scriptSig in between
/// `/` delimiters: `/pool_tag_string/miner_tag_string/`
/// The `pool_tag_string` and `miner_tag_string` are added to the coinbase scriptSig as
/// `Sv2/pool_tag_string/miner_tag_string/`.
pub fn new_for_job_declaration_client(
group_channel_id: u32,
full_extranonce_size: usize,
Expand All @@ -125,6 +125,7 @@ impl<'a> GroupChannel<'a> {
) -> Result<Self, GroupChannelError> {
let script_sig_size = 5 + // BIP34
1 + // OP_PUSHBYTES
3 + // "Sv2"
3 + // `/` delimiters
pool_tag.as_ref().map_or(0, |s| s.len()) +
miner_tag.as_ref().map_or(0, |s| s.len()) +
Expand Down Expand Up @@ -394,7 +395,8 @@ mod tests {
version_rolling_allowed: true,
coinbase_tx_prefix: vec![
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 39, 82, 0, 3, 47, 47, 47, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 42, 82, 0, 6, 83, 118, 50, 47, 47,
47, 32,
]
.try_into()
.unwrap(),
Expand Down Expand Up @@ -520,7 +522,8 @@ mod tests {
version_rolling_allowed: true,
coinbase_tx_prefix: vec![
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 39, 82, 0, 3, 47, 47, 47, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 42, 82, 0, 6, 83, 118, 50, 47, 47,
47, 32,
]
.try_into()
.unwrap(),
Expand Down
29 changes: 17 additions & 12 deletions sv2/channels-sv2/src/server/jobs/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,15 @@ impl JobFactory {
}
}

/// Returns a byte vector with the OP_PUSHBYTES opcode and the pool+miner tag.
/// Returns a byte vector with the OP_PUSHBYTES opcode and `Sv2/<pool>/<miner>/` tag.
///
/// The character `/` is used as a delimiter.
///
/// If no pool or miner tag is provided, the delimiters are still added.
/// If pool and/or miner tags are not provided, delimiters are still kept
/// (e.g. `Sv2///` when both are missing).
pub fn op_pushbytes_pool_miner_tag(&self) -> Result<Vec<u8>, JobFactoryError> {
let mut pool_miner_tag = vec![];
pool_miner_tag.extend_from_slice(b"/");
pool_miner_tag.extend_from_slice(b"Sv2/");
if let Some(pool_tag_string) = &self.pool_tag_string {
pool_miner_tag.extend_from_slice(pool_tag_string.as_bytes());
}
Expand Down Expand Up @@ -627,8 +628,9 @@ impl JobFactory {
)?;
let serialized_coinbase = serialize(&coinbase);

// Calculate the full pool/miner tag length including delimiters and OP_PUSHBYTES opcode
// Calculate the full `Sv2/<pool>/<miner>/` tag length including OP_PUSHBYTES opcode.
let pool_miner_tag_len = 1 // OP_PUSHBYTES opcode
+ 3 // "Sv2"
+ 3 // three "/" delimiters
+ self.pool_tag_string.as_ref().map_or(0, |s| s.len())
+ self.miner_tag_string.as_ref().map_or(0, |s| s.len());
Expand Down Expand Up @@ -661,8 +663,9 @@ impl JobFactory {
)?;
let serialized_coinbase = serialize(&coinbase);

// Calculate the full pool/miner tag length including delimiters and OP_PUSHBYTES opcode
// Calculate the full `Sv2/<pool>/<miner>/` tag length including OP_PUSHBYTES opcode.
let pool_miner_tag_len = 1 // OP_PUSHBYTES opcode
+ 3 // "Sv2"
+ 3 // three "/" delimiters
+ self.pool_tag_string.as_ref().map_or(0, |s| s.len())
+ self.miner_tag_string.as_ref().map_or(0, |s| s.len());
Expand Down Expand Up @@ -757,11 +760,12 @@ mod tests {
min_ntime: Sv2Option::new(None),
version: 536870912,
version_rolling_allowed: true,
// contains scriptSig with /Stratum V2 SRI Pool//
// contains scriptSig with Sv2/Stratum V2 SRI Pool//
coinbase_tx_prefix: vec![
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 58, 82, 0, 22, 47, 83, 116, 114, 97,
116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 80, 111, 111, 108, 47, 47, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 61, 82, 0, 25, 83, 118, 50, 47, 83,
116, 114, 97, 116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 80, 111, 111, 108, 47,
47, 32,
]
.try_into()
.unwrap(),
Expand Down Expand Up @@ -864,12 +868,13 @@ mod tests {
min_ntime: Sv2Option::new(Some(1746839905)),
version: 536870912,
version_rolling_allowed: true,
// contains scriptSig with /Stratum V2 SRI Pool/Stratum V2 SRI Miner/
// contains scriptSig with Sv2/Stratum V2 SRI Pool/Stratum V2 SRI Miner/
coinbase_tx_prefix: vec![
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 78, 82, 0, 42, 47, 83, 116, 114, 97,
116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 80, 111, 111, 108, 47, 83, 116, 114,
97, 116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 77, 105, 110, 101, 114, 47, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 81, 82, 0, 45, 83, 118, 50, 47, 83,
116, 114, 97, 116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 80, 111, 111, 108, 47,
83, 116, 114, 97, 116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 77, 105, 110, 101,
114, 47, 32,
]
.try_into()
.unwrap(),
Expand Down
Loading
Loading