From f117ccfdf410fec5d657ed18c25453766d97979e Mon Sep 17 00:00:00 2001 From: GitGab19 Date: Fri, 3 Jan 2025 17:16:12 +0100 Subject: [PATCH] header_timestamp_value_assertion_in_new_extended_mining_job test addition --- .../v2/roles-logic-sv2/src/job_creator.rs | 9 +- roles/tests-integration/tests/common/mod.rs | 16 +-- .../tests/pool_integration.rs | 113 +++++++++++++++++- .../tests/translator_integration.rs | 3 +- 4 files changed, 123 insertions(+), 18 deletions(-) diff --git a/protocols/v2/roles-logic-sv2/src/job_creator.rs b/protocols/v2/roles-logic-sv2/src/job_creator.rs index 8dfc625575..999b8a2f1c 100644 --- a/protocols/v2/roles-logic-sv2/src/job_creator.rs +++ b/protocols/v2/roles-logic-sv2/src/job_creator.rs @@ -211,10 +211,11 @@ fn new_extended_job( extranonce_len, ); - let min_ntime = match new_template.future_template { - true => binary_sv2::Sv2Option::new(None), - false => binary_sv2::Sv2Option::new(ntime), - }; + let min_ntime = binary_sv2::Sv2Option::new(if new_template.future_template { + None + } else { + ntime + }); let new_extended_mining_job: NewExtendedMiningJob<'static> = NewExtendedMiningJob { channel_id: 0, diff --git a/roles/tests-integration/tests/common/mod.rs b/roles/tests-integration/tests/common/mod.rs index 7992e395ee..065ce989f7 100644 --- a/roles/tests-integration/tests/common/mod.rs +++ b/roles/tests-integration/tests/common/mod.rs @@ -81,20 +81,21 @@ pub struct TemplateProvider { } impl TemplateProvider { - pub fn start(port: u16) -> Self { + pub fn start(port: u16, sv2_interval: u32) -> Self { let temp_dir = PathBuf::from("/tmp/.template-provider"); let mut conf = Conf::default(); let staticdir = format!(".bitcoin-{}", port); conf.staticdir = Some(temp_dir.join(staticdir)); - let port = format!("-sv2port={}", port); + let port_arg = format!("-sv2port={}", port); + let sv2_interval_arg = format!("-sv2interval={}", sv2_interval); conf.args.extend(vec![ "-txindex=1", "-sv2", - &port, + &port_arg, "-debug=rpc", "-debug=sv2", - "-sv2interval=20", - "-sv2feedelta=1000", + &sv2_interval_arg, + "-sv2feedelta=0", "-loglevel=sv2:trace", "-logtimemicros=1", ]); @@ -284,8 +285,9 @@ pub async fn start_pool( pool } -pub async fn start_template_provider(tp_port: u16) -> TemplateProvider { - let template_provider = TemplateProvider::start(tp_port); +pub async fn start_template_provider(tp_port: u16, sv2_interval: Option) -> TemplateProvider { + let sv2_interval = sv2_interval.unwrap_or(20); + let template_provider = TemplateProvider::start(tp_port, sv2_interval); template_provider.generate_blocks(16); template_provider } diff --git a/roles/tests-integration/tests/pool_integration.rs b/roles/tests-integration/tests/pool_integration.rs index 039a0d599d..eb5417db31 100644 --- a/roles/tests-integration/tests/pool_integration.rs +++ b/roles/tests-integration/tests/pool_integration.rs @@ -1,15 +1,15 @@ mod common; -use std::convert::TryInto; - use common::{InterceptMessage, MessageDirection}; use const_sv2::{ - MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SETUP_CONNECTION_ERROR, MESSAGE_TYPE_SET_NEW_PREV_HASH, + MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, MESSAGE_TYPE_NEW_TEMPLATE, + MESSAGE_TYPE_SETUP_CONNECTION_ERROR, }; use roles_logic_sv2::{ common_messages_sv2::{Protocol, SetupConnection, SetupConnectionError}, - parsers::{CommonMessages, PoolMessages, TemplateDistribution}, + parsers::{AnyMessage, CommonMessages, Mining, PoolMessages, TemplateDistribution}, }; +use std::convert::TryInto; // This test starts a Template Provider and a Pool, and checks if they exchange the correct // messages upon connection. @@ -20,7 +20,7 @@ async fn success_pool_template_provider_connection() { let sniffer_addr = common::get_available_address(); let tp_addr = common::get_available_address(); let pool_addr = common::get_available_address(); - let _tp = common::start_template_provider(tp_addr.port()).await; + let _tp = common::start_template_provider(tp_addr.port(), None).await; let sniffer_identifier = "success_pool_template_provider_connection tp_pool sniffer".to_string(); let sniffer_check_on_drop = true; @@ -64,12 +64,113 @@ async fn success_pool_template_provider_connection() { assert_tp_message!(sniffer.next_message_from_upstream(), SetNewPrevHash); } +// This test starts a Template Provider, a Pool, and a Translator Proxy, and verifies the +// correctness of the exchanged messages during connection and operation. +// +// Two Sniffers are used: +// - Between the Template Provider and the Pool. +// - Between the Pool and the Translator Proxy. +// +// The test ensures that: +// - The Template Provider sends valid `SetNewPrevHash` and `NewTemplate` messages. +// - The `minntime` field in the second `NewExtendedMiningJob` message sent to the Translator Proxy +// matches the `header_timestamp` from the `SetNewPrevHash` message, addressing a bug that +// occurred with non-future jobs. +// +// Related issue: https://github.com/stratum-mining/stratum/issues/1324 +#[tokio::test] +async fn header_timestamp_value_assertion_in_new_extended_mining_job() { + let tp_pool_sniffer_addr = common::get_available_address(); + let pool_translator_sniffer_addr = common::get_available_address(); + let tp_addr = common::get_available_address(); + let pool_addr = common::get_available_address(); + let sv2_interval = Some(5); + let _tp = common::start_template_provider(tp_addr.port(), sv2_interval).await; + let tp_pool_sniffer_identifier = + "header_timestamp_value_assertion_in_new_extended_mining_job tp_pool sniffer".to_string(); + let tp_pool_sniffer = common::start_sniffer( + tp_pool_sniffer_identifier, + tp_pool_sniffer_addr, + tp_addr, + false, + None, + ) + .await; + let _ = common::start_pool(Some(pool_addr), Some(tp_pool_sniffer_addr)).await; + let pool_translator_sniffer_identifier = + "header_timestamp_value_assertion_in_new_extended_mining_job pool_translator sniffer" + .to_string(); + let pool_translator_sniffer = common::start_sniffer( + pool_translator_sniffer_identifier, + pool_translator_sniffer_addr, + pool_addr, + false, + None, + ) + .await; + let _tproxy_addr = common::start_sv2_translator(pool_translator_sniffer_addr).await; + assert_common_message!( + &tp_pool_sniffer.next_message_from_upstream(), + SetupConnectionSuccess + ); + // Wait for a NewTemplate message from the Template Provider + tp_pool_sniffer + .wait_for_message_type(MessageDirection::ToDownstream, MESSAGE_TYPE_NEW_TEMPLATE) + .await; + assert_tp_message!(&tp_pool_sniffer.next_message_from_upstream(), NewTemplate); + // Extract header timestamp from SetNewPrevHash message + let header_timestamp_to_check = match tp_pool_sniffer.next_message_from_upstream() { + Some((_, AnyMessage::TemplateDistribution(TemplateDistribution::SetNewPrevHash(msg)))) => { + msg.header_timestamp + } + _ => panic!("SetNewPrevHash not found!"), + }; + // Assertions of messages between Pool and Translator Proxy (these are not necessary for the + // test itself, but they are used to pop from the sniffer's message queue) + assert_common_message!( + &pool_translator_sniffer.next_message_from_upstream(), + SetupConnectionSuccess + ); + assert_mining_message!( + &pool_translator_sniffer.next_message_from_upstream(), + OpenExtendedMiningChannelSuccess + ); + assert_mining_message!( + &pool_translator_sniffer.next_message_from_upstream(), + NewExtendedMiningJob + ); + assert_mining_message!( + &pool_translator_sniffer.next_message_from_upstream(), + SetNewPrevHash + ); + // Wait for a second NewExtendedMiningJob message + pool_translator_sniffer + .wait_for_message_type( + MessageDirection::ToDownstream, + MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, + ) + .await; + // Extract min_ntime from the second NewExtendedMiningJob message + let second_job_ntime = match pool_translator_sniffer.next_message_from_upstream() { + Some((_, AnyMessage::Mining(Mining::NewExtendedMiningJob(job)))) => { + job.min_ntime.into_inner() + } + _ => panic!("Second NewExtendedMiningJob not found!"), + }; + // Assert that min_ntime matches header_timestamp + assert_eq!( + second_job_ntime, + Some(header_timestamp_to_check), + "The `minntime` field of the second NewExtendedMiningJob does not match the `header_timestamp`!" + ); +} + #[tokio::test] async fn test_sniffer_interrupter() { let sniffer_addr = common::get_available_address(); let tp_addr = common::get_available_address(); let pool_addr = common::get_available_address(); - let _tp = common::start_template_provider(tp_addr.port()).await; + let _tp = common::start_template_provider(tp_addr.port(), None).await; use const_sv2::MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS; let message = PoolMessages::Common(CommonMessages::SetupConnectionError(SetupConnectionError { diff --git a/roles/tests-integration/tests/translator_integration.rs b/roles/tests-integration/tests/translator_integration.rs index fa4aac086b..9d568e71c2 100644 --- a/roles/tests-integration/tests/translator_integration.rs +++ b/roles/tests-integration/tests/translator_integration.rs @@ -21,7 +21,8 @@ async fn translation_proxy() { None, ) .await; - let _tp = common::start_template_provider(tp_addr.port()).await; + let sv2_interval = 20; + let _tp = common::start_template_provider(tp_addr.port(), Some(sv2_interval)).await; let _pool = common::start_pool(Some(pool_addr), Some(tp_addr)).await; let tproxy_addr = common::start_sv2_translator(pool_translator_sniffer_addr).await; let _mining_device = common::start_mining_device_sv1(tproxy_addr).await;