1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
use alloc::vec::Vec;
use binary_sv2::{self, Deserialize, Seq0255, Serialize, Sv2Option, B064K, U256};
use core::{convert::TryInto, fmt};
/// Message used by an upstream to provide an updated mining job to downstream.
///
/// This is used for Standard Channels only.
///
/// Note that Standard Jobs distrbuted through this message are restricted to a fixed Merkle Root,
/// and the only rollable bits are `version`, `nonce`, and `nTime` fields of the block header.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct NewMiningJob<'decoder> {
/// Channel identifier for the channel that this job is valid for.
///
/// This must be a Standard Channel.
pub channel_id: u32,
/// Upstream’s identification of the mining job.
///
/// This identifier must be provided to the upstream when shares are submitted.
pub job_id: u32,
/// Smallest `nTime` value available for hashing for the new mining job.
///
/// An empty value indicates this is a future job and will be ready to mine on once a
/// [`SetNewPrevHash`] message is received with a matching `job_id`.
/// [`SetNewPrevHash`] message will also provide `prev_hash` and `min_ntime`.
///
/// Otherwise, if [`NewMiningJob::min_ntime`] value is set, the downstream must start mining on
/// it immediately. In this case, the new mining job uses the `prev_hash` from the last
/// received [`SetNewPrevHash`] message.
///
/// [`SetNewPrevHash`]: crate::SetNewPrevHash
pub min_ntime: Sv2Option<'decoder, u32>,
/// Version field that reflects the current network consensus.
///
/// As specified in [BIP320](https://github.com/bitcoin/bips/blob/master/bip-0320.mediawiki),
/// the general purpose bits can be freely manipulated by the downstream node.
///
/// The downstream node must not rely on the upstream node to set the
/// [BIP320](https://github.com/bitcoin/bips/blob/master/bip-0320.mediawiki) bits to any
/// particular value.
pub version: u32,
/// Merkle root field as used in the bitcoin block header.
///
/// Note that this field is fixed and cannot be modified by the downstream node.
pub merkle_root: U256<'decoder>,
}
impl fmt::Display for NewMiningJob<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"NewMiningJob(channel_id: {}, job_id: {}, min_ntime: {}, version: 0x{:08x}, merkle_root: {})",
self.channel_id, self.job_id, self.min_ntime, self.version, self.merkle_root
)
}
}
impl NewMiningJob<'_> {
pub fn is_future(&self) -> bool {
self.min_ntime.clone().into_inner().is_none()
}
pub fn set_future(&mut self) {
self.min_ntime = Sv2Option::new(None);
}
pub fn set_no_future(&mut self, min_ntime: u32) {
self.min_ntime = Sv2Option::new(Some(min_ntime));
}
}
/// Message used by an upstream to provide an updated mining job to the downstream through
/// Extended or Group Channel only.
///
/// An Extended Job allows rolling Merkle Roots, giving extensive control over the search space so
/// that they can implement various advanced use cases such as: translation between Stratum V1 and
/// V2 protocols, difficulty aggregation and search space splitting.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct NewExtendedMiningJob<'decoder> {
/// Identifier of the Extended Mining Channel that this job is valid for.
///
/// For a Group Channel, the message is broadcasted to all standard channels belonging to the
/// group.
pub channel_id: u32,
/// Upstream’s identification of the mining job.
///
/// This identifier must be provided to the upstream when shares are submitted later in the
/// mining process.
pub job_id: u32,
/// Smallest `nTime` value available for hashing for the new mining job.
///
/// An empty value indicates this is a future job and will be ready to mine on once a
/// [`SetNewPrevHash`] message is received with a matching `job_id`.
/// [`SetNewPrevHash`] message will also provide `prev_hash` and `min_ntime`.
///
/// Otherwise, if [`NewMiningJob::min_ntime`] value is set, the downstream must start mining on
/// it immediately. In this case, the new mining job uses the `prev_hash` from the last
/// received [`SetNewPrevHash`] message.
///
/// [`SetNewPrevHash`]: crate::SetNewPrevHash
pub min_ntime: Sv2Option<'decoder, u32>,
/// Version field that reflects the current network consensus.
///
/// As specified in [BIP320](https://github.com/bitcoin/bips/blob/master/bip-0320.mediawiki),
/// the general purpose bits can be freely manipulated by the downstream node.
///
/// The downstream node must not rely on the upstream node to set the
/// [BIP320](https://github.com/bitcoin/bips/blob/master/bip-0320.mediawiki) bits to any
/// particular value.
pub version: u32,
/// If set to `true`, the general purpose bits of [`NewExtendedMiningJob::version`] (as
/// specified in BIP320) can be freely manipulated by the downstream node.
///
/// If set to `false`, the downstream node must use [`NewExtendedMiningJob::version`] as it is
/// defined by this message.
pub version_rolling_allowed: bool,
/// Merkle path hashes ordered from deepest.
pub merkle_path: Seq0255<'decoder, U256<'decoder>>,
/// Prefix part of the coinbase transaction.
pub coinbase_tx_prefix: B064K<'decoder>,
/// Suffix part of the coinbase transaction.
pub coinbase_tx_suffix: B064K<'decoder>,
}
impl fmt::Display for NewExtendedMiningJob<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"NewExtendedMiningJob(channel_id: {}, job_id: {}, min_ntime: {}, version: 0x{:08x}, version_rolling_allowed: {}, merkle_path: {}, coinbase_tx_prefix: {}, coinbase_tx_suffix: {})",
self.channel_id,
self.job_id,
self.min_ntime,
self.version,
self.version_rolling_allowed,
self.merkle_path,
self.coinbase_tx_prefix,
self.coinbase_tx_suffix
)
}
}
impl NewExtendedMiningJob<'_> {
pub fn is_future(&self) -> bool {
self.min_ntime.clone().into_inner().is_none()
}
pub fn set_future(&mut self) {
self.min_ntime = Sv2Option::new(None);
}
pub fn set_no_future(&mut self, min_ntime: u32) {
self.min_ntime = Sv2Option::new(Some(min_ntime));
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
use core::convert::TryFrom;
fn from_arbitrary_vec_to_array(vec: Vec<u8>) -> [u8; 32] {
let mut result = [0_u8; 32];
let start = 32_usize.saturating_sub(vec.len());
let copy_len = vec.len().min(32);
result[start..start + copy_len].copy_from_slice(&vec[..copy_len]);
result
}
#[quickcheck_macros::quickcheck]
#[allow(clippy::too_many_arguments)]
fn test_new_extended_mining_job(
channel_id: u32,
job_id: u32,
min_ntime: Option<u32>,
version: u32,
version_rolling_allowed: bool,
merkle_path: Vec<u8>,
coinbase_tx_prefix: Vec<u8>,
coinbase_tx_suffix: Vec<u8>,
) -> bool {
let merkle_path = helpers::scan_to_u256_sequence(&merkle_path);
let coinbase_tx_prefix = helpers::bytes_to_b064k(&coinbase_tx_prefix);
let coinbase_tx_suffix = helpers::bytes_to_b064k(&coinbase_tx_suffix);
let nemj = NewExtendedMiningJob {
channel_id,
job_id,
min_ntime: Sv2Option::new(min_ntime),
version,
version_rolling_allowed,
merkle_path: merkle_path.clone(),
coinbase_tx_prefix: coinbase_tx_prefix.clone(),
coinbase_tx_suffix: coinbase_tx_suffix.clone(),
};
let static_nmj = nemj.as_static();
static_nmj.channel_id == nemj.channel_id
&& static_nmj.job_id == nemj.job_id
&& static_nmj.min_ntime == nemj.min_ntime
&& static_nmj.version == nemj.version
&& static_nmj.version_rolling_allowed == nemj.version_rolling_allowed
&& static_nmj.merkle_path == merkle_path
&& static_nmj.coinbase_tx_prefix == coinbase_tx_prefix
&& static_nmj.coinbase_tx_suffix == coinbase_tx_suffix
}
#[quickcheck_macros::quickcheck]
fn test_new_mining_job(
channel_id: u32,
job_id: u32,
min_ntime: Option<u32>,
version: u32,
merkle_root: Vec<u8>,
) -> bool {
let merkle_root = from_arbitrary_vec_to_array(merkle_root);
let nmj = NewMiningJob {
channel_id,
job_id,
min_ntime: Sv2Option::new(min_ntime),
version,
merkle_root: U256::try_from(merkle_root.to_vec())
.expect("NewMiningJob: failed to convert merkle_root to B032"),
};
let static_nmj = nmj.clone().as_static();
static_nmj.channel_id == nmj.channel_id
&& static_nmj.job_id == nmj.job_id
&& static_nmj.min_ntime == nmj.min_ntime
&& static_nmj.version == nmj.version
&& static_nmj.merkle_root == nmj.merkle_root
}
pub mod helpers {
use super::*;
use alloc::borrow::ToOwned;
pub fn scan_to_u256_sequence(bytes: &[u8]) -> Seq0255<U256> {
let inner: Vec<U256> = bytes
.chunks(32)
.map(|chunk| {
let data = from_arbitrary_vec_to_array(chunk.to_vec());
U256::from(data)
})
.collect();
Seq0255::new(inner).expect("Could not convert bytes to SEQ0255<U256")
}
pub fn bytes_to_b064k(bytes: &[u8]) -> B064K {
B064K::try_from(bytes.to_owned()).expect("Failed to convert to B064K")
}
}
}