Skip to content

Commit dbc349b

Browse files
authored
Merge pull request #1325 from GitGab19/fix-timestamp-bug
Fix timestamp bug - `min_ntime` becomes always the one sent by TP
2 parents 5fec3f1 + fb70488 commit dbc349b

File tree

6 files changed

+135
-31
lines changed

6 files changed

+135
-31
lines changed

protocols/v2/roles-logic-sv2/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "roles_logic_sv2"
3-
version = "1.2.2"
3+
version = "1.2.3"
44
authors = ["The Stratum V2 Developers"]
55
edition = "2018"
66
description = "Common handlers for use within SV2 roles"

protocols/v2/roles-logic-sv2/src/job_creator.rs

+16-15
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct JobsCreators {
2727
templte_to_job_id: HashMap<u64, u32, BuildNoHashHasher<u64>>,
2828
ids: Id,
2929
last_target: mining_sv2::Target,
30+
last_ntime: Option<u32>,
3031
extranonce_len: u8,
3132
}
3233

@@ -56,6 +57,7 @@ impl JobsCreators {
5657
templte_to_job_id: HashMap::with_hasher(BuildNoHashHasher::default()),
5758
ids: Id::new(),
5859
last_target: mining_sv2::Target::new(0, 0),
60+
last_ntime: None,
5961
extranonce_len,
6062
}
6163
}
@@ -91,6 +93,7 @@ impl JobsCreators {
9193
next_job_id,
9294
version_rolling_allowed,
9395
self.extranonce_len,
96+
self.last_ntime,
9497
)
9598
}
9699

@@ -106,6 +109,7 @@ impl JobsCreators {
106109
/// we clear all the saved templates.
107110
pub fn on_new_prev_hash(&mut self, prev_hash: &SetNewPrevHash<'static>) -> Option<u32> {
108111
self.last_target = prev_hash.target.clone().into();
112+
self.last_ntime = prev_hash.header_timestamp.into(); // set correct ntime
109113
let template: Vec<NewTemplate<'static>> = self
110114
.lasts_new_template
111115
.clone()
@@ -162,6 +166,7 @@ pub fn extended_job_from_custom_job(
162166
0,
163167
true,
164168
extranonce_len,
169+
Some(referenced_job.min_ntime),
165170
)
166171
}
167172

@@ -181,6 +186,7 @@ fn new_extended_job(
181186
job_id: u32,
182187
version_rolling_allowed: bool,
183188
extranonce_len: u8,
189+
ntime: Option<u32>,
184190
) -> Result<NewExtendedMiningJob<'static>, Error> {
185191
coinbase_outputs[0].value = match new_template.coinbase_tx_value_remaining.checked_mul(1) {
186192
//check that value_remaining is updated by TP
@@ -205,16 +211,11 @@ fn new_extended_job(
205211
extranonce_len,
206212
);
207213

208-
let min_ntime = match new_template.future_template {
209-
true => binary_sv2::Sv2Option::new(None),
210-
false => {
211-
let now = std::time::SystemTime::now()
212-
.duration_since(std::time::UNIX_EPOCH)
213-
.unwrap()
214-
.as_secs() as u32;
215-
binary_sv2::Sv2Option::new(Some(now))
216-
}
217-
};
214+
let min_ntime = binary_sv2::Sv2Option::new(if new_template.future_template {
215+
None
216+
} else {
217+
ntime
218+
});
218219

219220
let new_extended_mining_job: NewExtendedMiningJob<'static> = NewExtendedMiningJob {
220221
channel_id: 0,
@@ -432,11 +433,11 @@ impl StrippedCoinbaseTx {
432433
let mut inputs = self.inputs.clone();
433434
let last_input = inputs.last_mut().ok_or(Error::BadPayloadSize)?;
434435
let new_last_input_len =
435-
32 // outpoint
436-
+ 4 // vout
437-
+ 1 // script length byte -> TODO can be also 3 (based on TODO in `coinbase_tx_prefix()`)
438-
+ self.bip141_bytes_len // space for bip34 bytes
439-
;
436+
32 // outpoint
437+
+ 4 // vout
438+
+ 1 // script length byte -> TODO can be also 3 (based on TODO in `coinbase_tx_prefix()`)
439+
+ self.bip141_bytes_len // space for bip34 bytes
440+
;
440441
last_input.truncate(new_last_input_len);
441442
let mut prefix: Vec<u8> = vec![];
442443
prefix.extend_from_slice(&self.version.to_le_bytes());

roles/Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

roles/tests-integration/tests/common/mod.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,21 @@ pub struct TemplateProvider {
8181
}
8282

8383
impl TemplateProvider {
84-
pub fn start(port: u16) -> Self {
84+
pub fn start(port: u16, sv2_interval: u32) -> Self {
8585
let temp_dir = PathBuf::from("/tmp/.template-provider");
8686
let mut conf = Conf::default();
8787
let staticdir = format!(".bitcoin-{}", port);
8888
conf.staticdir = Some(temp_dir.join(staticdir));
89-
let port = format!("-sv2port={}", port);
89+
let port_arg = format!("-sv2port={}", port);
90+
let sv2_interval_arg = format!("-sv2interval={}", sv2_interval);
9091
conf.args.extend(vec![
9192
"-txindex=1",
9293
"-sv2",
93-
&port,
94+
&port_arg,
9495
"-debug=rpc",
9596
"-debug=sv2",
96-
"-sv2interval=20",
97-
"-sv2feedelta=1000",
97+
&sv2_interval_arg,
98+
"-sv2feedelta=0",
9899
"-loglevel=sv2:trace",
99100
"-logtimemicros=1",
100101
]);
@@ -284,8 +285,9 @@ pub async fn start_pool(
284285
pool
285286
}
286287

287-
pub async fn start_template_provider(tp_port: u16) -> TemplateProvider {
288-
let template_provider = TemplateProvider::start(tp_port);
288+
pub async fn start_template_provider(tp_port: u16, sv2_interval: Option<u32>) -> TemplateProvider {
289+
let sv2_interval = sv2_interval.unwrap_or(20);
290+
let template_provider = TemplateProvider::start(tp_port, sv2_interval);
289291
template_provider.generate_blocks(16);
290292
template_provider
291293
}

roles/tests-integration/tests/pool_integration.rs

+107-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
mod common;
22

3-
use std::convert::TryInto;
4-
53
use common::{InterceptMessage, MessageDirection};
64
use const_sv2::{
7-
MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SETUP_CONNECTION_ERROR, MESSAGE_TYPE_SET_NEW_PREV_HASH,
5+
MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, MESSAGE_TYPE_NEW_TEMPLATE,
6+
MESSAGE_TYPE_SETUP_CONNECTION_ERROR,
87
};
98
use roles_logic_sv2::{
109
common_messages_sv2::{Protocol, SetupConnection, SetupConnectionError},
11-
parsers::{CommonMessages, PoolMessages, TemplateDistribution},
10+
parsers::{AnyMessage, CommonMessages, Mining, PoolMessages, TemplateDistribution},
1211
};
12+
use std::convert::TryInto;
1313

1414
// This test starts a Template Provider and a Pool, and checks if they exchange the correct
1515
// messages upon connection.
@@ -20,7 +20,7 @@ async fn success_pool_template_provider_connection() {
2020
let sniffer_addr = common::get_available_address();
2121
let tp_addr = common::get_available_address();
2222
let pool_addr = common::get_available_address();
23-
let _tp = common::start_template_provider(tp_addr.port()).await;
23+
let _tp = common::start_template_provider(tp_addr.port(), None).await;
2424
let sniffer_identifier =
2525
"success_pool_template_provider_connection tp_pool sniffer".to_string();
2626
let sniffer_check_on_drop = true;
@@ -64,12 +64,113 @@ async fn success_pool_template_provider_connection() {
6464
assert_tp_message!(sniffer.next_message_from_upstream(), SetNewPrevHash);
6565
}
6666

67+
// This test starts a Template Provider, a Pool, and a Translator Proxy, and verifies the
68+
// correctness of the exchanged messages during connection and operation.
69+
//
70+
// Two Sniffers are used:
71+
// - Between the Template Provider and the Pool.
72+
// - Between the Pool and the Translator Proxy.
73+
//
74+
// The test ensures that:
75+
// - The Template Provider sends valid `SetNewPrevHash` and `NewTemplate` messages.
76+
// - The `minntime` field in the second `NewExtendedMiningJob` message sent to the Translator Proxy
77+
// matches the `header_timestamp` from the `SetNewPrevHash` message, addressing a bug that
78+
// occurred with non-future jobs.
79+
//
80+
// Related issue: https://github.com/stratum-mining/stratum/issues/1324
81+
#[tokio::test]
82+
async fn header_timestamp_value_assertion_in_new_extended_mining_job() {
83+
let tp_pool_sniffer_addr = common::get_available_address();
84+
let pool_translator_sniffer_addr = common::get_available_address();
85+
let tp_addr = common::get_available_address();
86+
let pool_addr = common::get_available_address();
87+
let sv2_interval = Some(5);
88+
let _tp = common::start_template_provider(tp_addr.port(), sv2_interval).await;
89+
let tp_pool_sniffer_identifier =
90+
"header_timestamp_value_assertion_in_new_extended_mining_job tp_pool sniffer".to_string();
91+
let tp_pool_sniffer = common::start_sniffer(
92+
tp_pool_sniffer_identifier,
93+
tp_pool_sniffer_addr,
94+
tp_addr,
95+
false,
96+
None,
97+
)
98+
.await;
99+
let _ = common::start_pool(Some(pool_addr), Some(tp_pool_sniffer_addr)).await;
100+
let pool_translator_sniffer_identifier =
101+
"header_timestamp_value_assertion_in_new_extended_mining_job pool_translator sniffer"
102+
.to_string();
103+
let pool_translator_sniffer = common::start_sniffer(
104+
pool_translator_sniffer_identifier,
105+
pool_translator_sniffer_addr,
106+
pool_addr,
107+
false,
108+
None,
109+
)
110+
.await;
111+
let _tproxy_addr = common::start_sv2_translator(pool_translator_sniffer_addr).await;
112+
assert_common_message!(
113+
&tp_pool_sniffer.next_message_from_upstream(),
114+
SetupConnectionSuccess
115+
);
116+
// Wait for a NewTemplate message from the Template Provider
117+
tp_pool_sniffer
118+
.wait_for_message_type(MessageDirection::ToDownstream, MESSAGE_TYPE_NEW_TEMPLATE)
119+
.await;
120+
assert_tp_message!(&tp_pool_sniffer.next_message_from_upstream(), NewTemplate);
121+
// Extract header timestamp from SetNewPrevHash message
122+
let header_timestamp_to_check = match tp_pool_sniffer.next_message_from_upstream() {
123+
Some((_, AnyMessage::TemplateDistribution(TemplateDistribution::SetNewPrevHash(msg)))) => {
124+
msg.header_timestamp
125+
}
126+
_ => panic!("SetNewPrevHash not found!"),
127+
};
128+
// Assertions of messages between Pool and Translator Proxy (these are not necessary for the
129+
// test itself, but they are used to pop from the sniffer's message queue)
130+
assert_common_message!(
131+
&pool_translator_sniffer.next_message_from_upstream(),
132+
SetupConnectionSuccess
133+
);
134+
assert_mining_message!(
135+
&pool_translator_sniffer.next_message_from_upstream(),
136+
OpenExtendedMiningChannelSuccess
137+
);
138+
assert_mining_message!(
139+
&pool_translator_sniffer.next_message_from_upstream(),
140+
NewExtendedMiningJob
141+
);
142+
assert_mining_message!(
143+
&pool_translator_sniffer.next_message_from_upstream(),
144+
SetNewPrevHash
145+
);
146+
// Wait for a second NewExtendedMiningJob message
147+
pool_translator_sniffer
148+
.wait_for_message_type(
149+
MessageDirection::ToDownstream,
150+
MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB,
151+
)
152+
.await;
153+
// Extract min_ntime from the second NewExtendedMiningJob message
154+
let second_job_ntime = match pool_translator_sniffer.next_message_from_upstream() {
155+
Some((_, AnyMessage::Mining(Mining::NewExtendedMiningJob(job)))) => {
156+
job.min_ntime.into_inner()
157+
}
158+
_ => panic!("Second NewExtendedMiningJob not found!"),
159+
};
160+
// Assert that min_ntime matches header_timestamp
161+
assert_eq!(
162+
second_job_ntime,
163+
Some(header_timestamp_to_check),
164+
"The `minntime` field of the second NewExtendedMiningJob does not match the `header_timestamp`!"
165+
);
166+
}
167+
67168
#[tokio::test]
68169
async fn test_sniffer_interrupter() {
69170
let sniffer_addr = common::get_available_address();
70171
let tp_addr = common::get_available_address();
71172
let pool_addr = common::get_available_address();
72-
let _tp = common::start_template_provider(tp_addr.port()).await;
173+
let _tp = common::start_template_provider(tp_addr.port(), None).await;
73174
use const_sv2::MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS;
74175
let message =
75176
PoolMessages::Common(CommonMessages::SetupConnectionError(SetupConnectionError {

roles/tests-integration/tests/translator_integration.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async fn translation_proxy() {
2121
None,
2222
)
2323
.await;
24-
let _tp = common::start_template_provider(tp_addr.port()).await;
24+
let _tp = common::start_template_provider(tp_addr.port(), None).await;
2525
let _pool = common::start_pool(Some(pool_addr), Some(tp_addr)).await;
2626
let tproxy_addr = common::start_sv2_translator(pool_translator_sniffer_addr).await;
2727
let _mining_device = common::start_mining_device_sv1(tproxy_addr).await;

0 commit comments

Comments
 (0)