diff --git a/integration-tests/lib/mod.rs b/integration-tests/lib/mod.rs index b667eaf8d..c7907a010 100644 --- a/integration-tests/lib/mod.rs +++ b/integration-tests/lib/mod.rs @@ -197,6 +197,7 @@ pub fn start_jdc( supported_extensions: Vec, required_extensions: Vec, enable_monitoring: bool, + jdc_mode: Option<&str>, ) -> (JobDeclaratorClient, SocketAddr, Option) { use jd_client_sv2::config::{JobDeclaratorClientConfig, PoolConfig, ProtocolConfig, Upstream}; let jdc_address = get_available_address(); @@ -255,7 +256,7 @@ pub fn start_jdc( template_provider_config, upstreams, jdc_signature, - None, + jdc_mode.map(|s| s.to_string()), supported_extensions, required_extensions, monitoring_address, @@ -272,6 +273,7 @@ pub async fn start_pool_with_jds( supported_extensions: Vec, required_extensions: Vec, enable_monitoring: bool, + full_template_mode_required: bool, ) -> (PoolSv2, SocketAddr, SocketAddr, Option) { use pool_sv2::config::{AuthorityConfig, ConnectionConfig, JDSPartialConfig, PoolConfig}; @@ -318,7 +320,11 @@ pub async fn start_pool_with_jds( required_extensions.clone(), monitoring_address, monitoring_cache_refresh_secs, - Some(JDSPartialConfig::new(jds_address)), + Some({ + let mut jds_partial = JDSPartialConfig::new(jds_address); + jds_partial.set_full_template_mode_required(full_template_mode_required); + jds_partial + }), ); let pool = PoolSv2::new(config); diff --git a/integration-tests/tests/bitcoin_core_ipc_integration.rs b/integration-tests/tests/bitcoin_core_ipc_integration.rs index a4eb0d38e..32c5383f1 100644 --- a/integration-tests/tests/bitcoin_core_ipc_integration.rs +++ b/integration-tests/tests/bitcoin_core_ipc_integration.rs @@ -51,7 +51,7 @@ async fn jdc_propagates_block_with_bitcoin_core_ipc() { let (tp, _tp_addr) = start_template_provider(None, DifficultyLevel::Low); let current_block_hash = tp.get_best_block_hash().unwrap(); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let ignore_push_solution = IgnoreMessage::new(MessageDirection::ToUpstream, MESSAGE_TYPE_PUSH_SOLUTION); let (sniffer, sniffer_addr) = start_sniffer( @@ -71,6 +71,7 @@ async fn jdc_propagates_block_with_bitcoin_core_ipc() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; diff --git a/integration-tests/tests/jd_full_template_mode.rs b/integration-tests/tests/jd_full_template_mode.rs new file mode 100644 index 000000000..a119ac40f --- /dev/null +++ b/integration-tests/tests/jd_full_template_mode.rs @@ -0,0 +1,67 @@ +use integration_tests_sv2::{interceptor::MessageDirection, template_provider::DifficultyLevel, *}; +use stratum_apps::stratum_core::{ + common_messages_sv2::*, job_declaration_sv2::*, template_distribution_sv2::*, +}; + +// JDC in FullTemplate mode (default) exchanges DeclareMiningJob with JDS +// and propagates blocks via both SubmitSolution (to TP) and PushSolution (to JDS) +#[tokio::test] +async fn jd_full_template_mode_declare_mining_job_exchanged() { + start_tracing(); + let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); + let current_block_hash = tp.get_best_block_hash().unwrap(); + let (_pool, pool_addr, jds_addr, _) = + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; + let (jdc_jds_sniffer, jdc_jds_sniffer_addr) = + start_sniffer("jdc-jds", jds_addr, false, vec![], None); + let (jdc_tp_sniffer, jdc_tp_sniffer_addr) = + start_sniffer("jdc-tp", tp_addr, false, vec![], None); + let (_jdc, jdc_addr, _) = start_jdc( + &[(pool_addr, jdc_jds_sniffer_addr)], + sv2_tp_config(jdc_tp_sniffer_addr), + vec![], + vec![], + false, + None, + ); + jdc_jds_sniffer + .wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION) + .await; + jdc_jds_sniffer + .wait_for_message_type( + MessageDirection::ToDownstream, + MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS, + ) + .await; + + let (_translator, tproxy_addr, _) = + start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; + let (_minerd, _) = start_minerd(tproxy_addr, None, None, false).await; + + // DeclareMiningJob exchanged in both directions + jdc_jds_sniffer + .wait_for_message_type( + MessageDirection::ToUpstream, + MESSAGE_TYPE_DECLARE_MINING_JOB, + ) + .await; + jdc_jds_sniffer + .wait_for_message_type( + MessageDirection::ToDownstream, + MESSAGE_TYPE_DECLARE_MINING_JOB_SUCCESS, + ) + .await; + + // block propagation from JDC to TP + jdc_tp_sniffer + .wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SUBMIT_SOLUTION) + .await; + let new_block_hash = tp.get_best_block_hash().unwrap(); + assert_ne!(current_block_hash, new_block_hash); + + // PushSolution sent to JDS in FullTemplate mode + assert!( + jdc_jds_sniffer.has_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_PUSH_SOLUTION), + "PushSolution should be sent to JDS in FullTemplate mode" + ); +} diff --git a/integration-tests/tests/jd_integration.rs b/integration-tests/tests/jd_integration.rs index 4dfdcc9a5..c1d0858a7 100644 --- a/integration-tests/tests/jd_integration.rs +++ b/integration-tests/tests/jd_integration.rs @@ -23,7 +23,7 @@ async fn jds_should_not_panic_if_jdc_shutsdown() { start_tracing(); let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (sniffer_a, sniffer_addr_a) = start_sniffer("0", jds_addr, false, vec![], None); let (jdc, jdc_addr, _) = start_jdc( &[(pool_addr, sniffer_addr_a)], @@ -31,6 +31,7 @@ async fn jds_should_not_panic_if_jdc_shutsdown() { vec![], vec![], false, + None, ); sniffer_a .wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION) @@ -50,6 +51,7 @@ async fn jds_should_not_panic_if_jdc_shutsdown() { vec![], vec![], false, + None, ); sniffer .wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION) @@ -66,7 +68,7 @@ async fn jdc_tp_success_setup() { start_tracing(); let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (tp_jdc_sniffer, tp_jdc_sniffer_addr) = start_sniffer("0", tp_addr, false, vec![], None); let (jdc, jdc_addr, _) = start_jdc( &[(pool_addr, jds_addr)], @@ -74,6 +76,7 @@ async fn jdc_tp_success_setup() { vec![], vec![], false, + None, ); // This is needed because jd-client waits for a downstream connection before it starts // exchanging messages with the Template Provider. @@ -97,7 +100,7 @@ async fn jds_reject_setup_connection_with_non_job_declaration_protocol() { start_tracing(); let (tp, _tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, _pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (sniffer, sniffer_addr) = start_sniffer("mock-jds", jds_addr, false, vec![], None); let _mock_downstream = MockDownstream::new( sniffer_addr, @@ -138,7 +141,7 @@ async fn jds_reject_setup_connection_without_declare_tx_data_flag() { start_tracing(); let (tp, _tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, _pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (sniffer, sniffer_addr) = start_sniffer("mock-jds", jds_addr, false, vec![], None); let _mock_downstream = MockDownstream::new( sniffer_addr, @@ -179,7 +182,7 @@ async fn jds_reject_declare_mining_job_with_invalid_mining_job_token() { start_tracing(); let (tp, _tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, _pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (sniffer, sniffer_addr) = start_sniffer("mock-jds", jds_addr, false, vec![], None); let send_to_jds = MockDownstream::new( sniffer_addr, @@ -303,7 +306,7 @@ async fn pool_rejects_reused_set_custom_mining_job_token() { start_tracing(); let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; // First, run the regular JDC flow and capture one valid SetCustomMiningJob. let (jdc_pool_sniffer, jdc_pool_sniffer_addr) = @@ -314,6 +317,7 @@ async fn pool_rejects_reused_set_custom_mining_job_token() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; @@ -409,7 +413,7 @@ async fn jds_receive_solution_while_processing_declared_job_test() { let (tp_1, _tp_addr_1) = start_template_provider(None, DifficultyLevel::Low); let (tp_2, tp_addr_2) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp_1.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp_1.bitcoin_core(), vec![], vec![], false, true).await; let prev_hash = U256::Owned(vec![ 184, 103, 138, 88, 153, 105, 236, 29, 123, 246, 107, 203, 1, 33, 10, 122, 188, 139, 218, @@ -447,6 +451,7 @@ async fn jds_receive_solution_while_processing_declared_job_test() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; @@ -509,7 +514,7 @@ async fn jds_wont_exit_upon_receiving_unexpected_txids_in_provide_missing_transa assert!(tp_2.create_mempool_transaction().is_ok()); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp_1.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp_1.bitcoin_core(), vec![], vec![], false, true).await; let provide_missing_transaction_success_replace = ReplaceMessage::new( MessageDirection::ToUpstream, @@ -540,6 +545,7 @@ async fn jds_wont_exit_upon_receiving_unexpected_txids_in_provide_missing_transa vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr_1], false, vec![], vec![], None, false).await; @@ -598,7 +604,7 @@ async fn jdc_group_extended_channels() { let (tp, tp_addr) = start_template_provider(sv2_interval, DifficultyLevel::Low); tp.fund_wallet().unwrap(); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (jdc, jdc_addr, _) = start_jdc( &[(pool_addr, jds_addr)], @@ -606,6 +612,7 @@ async fn jdc_group_extended_channels() { vec![], vec![], false, + None, ); let (sniffer, sniffer_addr) = start_sniffer("sniffer", jdc_addr, false, vec![], None); @@ -782,7 +789,7 @@ async fn jdc_group_standard_channels() { let (tp, tp_addr) = start_template_provider(sv2_interval, DifficultyLevel::Low); tp.fund_wallet().unwrap(); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (jdc, jdc_addr, _) = start_jdc( &[(pool_addr, jds_addr)], @@ -790,6 +797,7 @@ async fn jdc_group_standard_channels() { vec![], vec![], false, + None, ); let (sniffer, sniffer_addr) = start_sniffer("sniffer", jdc_addr, false, vec![], None); @@ -975,7 +983,7 @@ async fn jdc_require_standard_jobs_set_does_not_group_standard_channels() { let (tp, tp_addr) = start_template_provider(sv2_interval, DifficultyLevel::Low); tp.fund_wallet().unwrap(); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], true).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], true, true).await; let (jdc, jdc_addr, _) = start_jdc( &[(pool_addr, jds_addr)], @@ -983,6 +991,7 @@ async fn jdc_require_standard_jobs_set_does_not_group_standard_channels() { vec![], vec![], false, + None, ); let (sniffer, sniffer_addr) = start_sniffer("sniffer", jdc_addr, false, vec![], None); diff --git a/integration-tests/tests/jd_mining_modes.rs b/integration-tests/tests/jd_mining_modes.rs new file mode 100644 index 000000000..3fdf9f501 --- /dev/null +++ b/integration-tests/tests/jd_mining_modes.rs @@ -0,0 +1,33 @@ +use integration_tests_sv2::{interceptor::MessageDirection, template_provider::DifficultyLevel, *}; +use stratum_apps::stratum_core::common_messages_sv2::*; + +// JDS requires FullTemplate but JDC asks for CoinbaseOnly — SetupConnection rejected +#[tokio::test] +async fn jd_mode_mismatch_setup_connection_fails() { + start_tracing(); + let (tp, _tp_addr) = start_template_provider(None, DifficultyLevel::Low); + let (_pool, pool_addr, jds_addr, _) = + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; + let (sniffer, sniffer_addr) = start_sniffer("jdc-jds", jds_addr, false, vec![], None); + let (_jdc, _jdc_addr, _) = start_jdc( + &[(pool_addr, sniffer_addr)], + ipc_config( + tp.bitcoin_core().data_dir().clone(), + tp.bitcoin_core().is_signet(), + None, + ), + vec![], + vec![], + false, + Some("COINBASEONLY"), + ); + sniffer + .wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION) + .await; + sniffer + .wait_for_message_type( + MessageDirection::ToDownstream, + MESSAGE_TYPE_SETUP_CONNECTION_ERROR, + ) + .await; +} diff --git a/integration-tests/tests/jd_provide_missing_transaction.rs b/integration-tests/tests/jd_provide_missing_transaction.rs index dd121fe17..8cd045d02 100644 --- a/integration-tests/tests/jd_provide_missing_transaction.rs +++ b/integration-tests/tests/jd_provide_missing_transaction.rs @@ -7,7 +7,7 @@ async fn jds_ask_for_missing_transactions() { let (tp_1, _tp_addr_1) = start_template_provider(None, DifficultyLevel::Low); let (tp_2, tp_addr_2) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp_1.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp_1.bitcoin_core(), vec![], vec![], false, true).await; let (sniffer, sniffer_addr) = start_sniffer("A", jds_addr, false, vec![], None); let (jdc, jdc_addr, _) = start_jdc( &[(pool_addr, sniffer_addr)], @@ -15,6 +15,7 @@ async fn jds_ask_for_missing_transactions() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; diff --git a/integration-tests/tests/jd_tproxy_integration.rs b/integration-tests/tests/jd_tproxy_integration.rs index cb957989b..f2556f54b 100644 --- a/integration-tests/tests/jd_tproxy_integration.rs +++ b/integration-tests/tests/jd_tproxy_integration.rs @@ -6,7 +6,7 @@ async fn jd_non_aggregated_tproxy_integration() { start_tracing(); let (tp, _tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (jdc_pool_sniffer, jdc_pool_sniffer_addr) = start_sniffer("0", pool_addr, false, vec![], None); let (jdc, jdc_addr, _) = start_jdc( @@ -19,6 +19,7 @@ async fn jd_non_aggregated_tproxy_integration() { vec![], vec![], false, + None, ); let (tproxy_jdc_sniffer, tproxy_jdc_sniffer_addr) = start_sniffer("1", jdc_addr, false, vec![], None); @@ -94,7 +95,7 @@ async fn jd_aggregated_tproxy_integration() { start_tracing(); let (tp, _tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (jdc_pool_sniffer, jdc_pool_sniffer_addr) = start_sniffer("0", pool_addr, false, vec![], None); let (jdc, jdc_addr, _) = start_jdc( @@ -107,6 +108,7 @@ async fn jd_aggregated_tproxy_integration() { vec![], vec![], false, + None, ); let (tproxy_jdc_sniffer, tproxy_jdc_sniffer_addr) = start_sniffer("1", jdc_addr, false, vec![], None); diff --git a/integration-tests/tests/jdc_block_propagation.rs b/integration-tests/tests/jdc_block_propagation.rs index 28d84d6f4..7cdd00beb 100644 --- a/integration-tests/tests/jdc_block_propagation.rs +++ b/integration-tests/tests/jdc_block_propagation.rs @@ -12,7 +12,7 @@ async fn propagated_from_jdc_to_tp() { let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let current_block_hash = tp.get_best_block_hash().unwrap(); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let ignore_push_solution = IgnoreMessage::new(MessageDirection::ToUpstream, MESSAGE_TYPE_PUSH_SOLUTION); let (jdc_jds_sniffer, jdc_jds_sniffer_addr) = start_sniffer( @@ -29,6 +29,7 @@ async fn propagated_from_jdc_to_tp() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; diff --git a/integration-tests/tests/jdc_cached_shares.rs b/integration-tests/tests/jdc_cached_shares.rs index 57b7ed183..14d9d4a00 100644 --- a/integration-tests/tests/jdc_cached_shares.rs +++ b/integration-tests/tests/jdc_cached_shares.rs @@ -27,7 +27,7 @@ async fn jdc_cached_shares_relayed_on_set_custom_job_success() { // causing `SetCustomMiningJob.Success` to fail. let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::High); let (_pool, _pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let mock_pool_addr = get_available_address(); let mock_pool = MockUpstream::new( @@ -45,6 +45,7 @@ async fn jdc_cached_shares_relayed_on_set_custom_job_success() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; diff --git a/integration-tests/tests/jdc_receives_submit_shares_success.rs b/integration-tests/tests/jdc_receives_submit_shares_success.rs index ff17d610c..13ae9149c 100644 --- a/integration-tests/tests/jdc_receives_submit_shares_success.rs +++ b/integration-tests/tests/jdc_receives_submit_shares_success.rs @@ -6,7 +6,7 @@ async fn jdc_submit_shares_success() { start_tracing(); let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (sniffer, sniffer_addr) = start_sniffer("0", pool_addr, false, vec![], None); let (jdc, jdc_addr, _) = start_jdc( &[(sniffer_addr, jds_addr)], @@ -14,6 +14,7 @@ async fn jdc_submit_shares_success() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; diff --git a/integration-tests/tests/jds_block_propagation.rs b/integration-tests/tests/jds_block_propagation.rs index ab47c8364..4a8e3fbe8 100644 --- a/integration-tests/tests/jds_block_propagation.rs +++ b/integration-tests/tests/jds_block_propagation.rs @@ -14,7 +14,7 @@ async fn propagated_from_jds_to_tp() { let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let current_block_hash = tp.get_best_block_hash().unwrap(); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (jdc_jds_sniffer, jdc_jds_sniffer_addr) = start_sniffer("0", jds_addr, false, vec![], None); let ignore_submit_solution = IgnoreMessage::new(MessageDirection::ToUpstream, MESSAGE_TYPE_SUBMIT_SOLUTION); @@ -31,6 +31,7 @@ async fn propagated_from_jds_to_tp() { vec![], vec![], false, + None, ); let (translator, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; diff --git a/integration-tests/tests/monitoring_integration.rs b/integration-tests/tests/monitoring_integration.rs index d362168ae..41efdf9f4 100644 --- a/integration-tests/tests/monitoring_integration.rs +++ b/integration-tests/tests/monitoring_integration.rs @@ -130,7 +130,7 @@ async fn jd_aggregated_topology_monitoring() { start_tracing(); let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (_pool, pool_addr, jds_addr, pool_monitoring) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], true).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], true, true).await; let (jdc_pool_sniffer, jdc_pool_sniffer_addr) = start_sniffer("0", pool_addr, false, vec![], None); let (_jdc, jdc_addr, _jdc_monitoring) = start_jdc( @@ -139,6 +139,7 @@ async fn jd_aggregated_topology_monitoring() { vec![], vec![], true, + None, ); let (_tproxy_jdc_sniffer, tproxy_jdc_sniffer_addr) = start_sniffer("1", jdc_addr, false, vec![], None); @@ -202,7 +203,7 @@ async fn block_found_detected_in_pool_metrics() { start_tracing(); let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); let (_pool, pool_addr, jds_addr, pool_monitoring) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], true).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], true, true).await; let (_jdc_jds_sniffer, jdc_jds_sniffer_addr) = start_sniffer("0", jds_addr, false, vec![], None); @@ -213,6 +214,7 @@ async fn block_found_detected_in_pool_metrics() { vec![], vec![], false, + None, ); let (_tproxy, tproxy_addr, _) = start_sv2_translator(&[jdc_addr], false, vec![], vec![], None, false).await; diff --git a/integration-tests/tests/pool_integration.rs b/integration-tests/tests/pool_integration.rs index da1215109..5d90e98cb 100644 --- a/integration-tests/tests/pool_integration.rs +++ b/integration-tests/tests/pool_integration.rs @@ -251,7 +251,7 @@ async fn pool_does_not_send_jobs_to_jdc() { let (tp, tp_addr) = start_template_provider(sv2_interval, DifficultyLevel::Low); tp.fund_wallet().unwrap(); let (pool, pool_addr, jds_addr, _) = - start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false, true).await; let (pool_jdc_sniffer, pool_jdc_sniffer_addr) = start_sniffer("pool_jdc", pool_addr, false, vec![], None); let (jdc, jdc_addr, _) = start_jdc( @@ -260,6 +260,7 @@ async fn pool_does_not_send_jobs_to_jdc() { vec![], vec![], false, + None, ); // Block NewExtendedMiningJob and SetNewPrevHash messages between JDC and translator proxy let (_tproxy_jdc_sniffer, tproxy_jdc_sniffer_addr) = start_sniffer( diff --git a/pool-apps/jd-server/src/lib/config.rs b/pool-apps/jd-server/src/lib/config.rs index 557e881f6..a0caf22ea 100644 --- a/pool-apps/jd-server/src/lib/config.rs +++ b/pool-apps/jd-server/src/lib/config.rs @@ -25,6 +25,8 @@ pub struct JDSPartialConfig { supported_extensions: Vec, #[serde(default)] required_extensions: Vec, + #[serde(default = "default_true")] + full_template_mode_required: bool, } /// Complete JDS configuration with all required fields populated. @@ -40,6 +42,7 @@ pub struct JDSConfig { coinbase_reward_script: CoinbaseRewardScript, supported_extensions: Vec, required_extensions: Vec, + full_template_mode_required: bool, } impl JDSPartialConfig { @@ -52,8 +55,13 @@ impl JDSPartialConfig { listen_address, supported_extensions: Vec::new(), required_extensions: Vec::new(), + full_template_mode_required: true, } } + + pub fn set_full_template_mode_required(&mut self, required: bool) { + self.full_template_mode_required = required; + } } #[cfg_attr(not(test), hotpath::measure_all)] @@ -77,6 +85,7 @@ impl JDSConfig { coinbase_reward_script, supported_extensions, required_extensions, + full_template_mode_required: true, } } @@ -100,9 +109,14 @@ impl JDSConfig { coinbase_reward_script, supported_extensions: partial.supported_extensions, required_extensions: partial.required_extensions, + full_template_mode_required: partial.full_template_mode_required, } } + pub fn full_template_mode_required(&self) -> bool { + self.full_template_mode_required + } + /// Address the JDS downstream server listens on (Noise-encrypted JDP). pub fn listen_address(&self) -> &SocketAddr { &self.listen_address @@ -138,3 +152,7 @@ impl JDSConfig { &self.required_extensions } } + +fn default_true() -> bool { + true +} diff --git a/pool-apps/jd-server/src/lib/job_declarator/downstream/common_message_handler.rs b/pool-apps/jd-server/src/lib/job_declarator/downstream/common_message_handler.rs index 05c566916..be474ae20 100644 --- a/pool-apps/jd-server/src/lib/job_declarator/downstream/common_message_handler.rs +++ b/pool-apps/jd-server/src/lib/job_declarator/downstream/common_message_handler.rs @@ -97,6 +97,35 @@ impl HandleCommonMessagesFromClientAsync for Downstream { )); } + if self.full_template_mode_required { + let has_full_template_mode = msg.flags & 0b0000_0000_0000_0000_0000_0000_0000_0001 != 0; + if !has_full_template_mode { + info!( + "Rejecting connection from {downstream_id}: JDS requires full template mode." + ); + let response = SetupConnectionError { + flags: 0, + error_code: "requires-full-template-mode" + .to_string() + .try_into() + .expect("error code must be valid string"), + }; + let frame: Sv2Frame = AnyMessage::Common(response.into_static().into()) + .try_into() + .map_err(JDSError::shutdown)?; + self.downstream_io + .to_downstream_sender + .send(frame) + .await + .map_err(|e| JDSError::disconnect(e, self.downstream_id))?; + + return Err(JDSError::disconnect( + JDSErrorKind::UnsupportedConnectionFlags, + downstream_id, + )); + } + } + let response = SetupConnectionSuccess { used_version: 2, flags: 0, diff --git a/pool-apps/jd-server/src/lib/job_declarator/downstream/mod.rs b/pool-apps/jd-server/src/lib/job_declarator/downstream/mod.rs index 0c1101e94..007cb99c9 100644 --- a/pool-apps/jd-server/src/lib/job_declarator/downstream/mod.rs +++ b/pool-apps/jd-server/src/lib/job_declarator/downstream/mod.rs @@ -65,6 +65,8 @@ pub struct Downstream { /// Extensions that JDS requires #[allow(unused)] pub required_extensions: Vec, + /// Whether the JDS requires full template mode from this downstream. + pub full_template_mode_required: bool, /// Per-downstream cancellation token (child of the global token). /// Cancelling this stops IO tasks, the pending jobs janitor, and the downstream loop /// without affecting other downstreams or the server. @@ -82,6 +84,7 @@ impl Downstream { from_job_declarator_receiver: Receiver, supported_extensions: Vec, required_extensions: Vec, + full_template_mode_required: bool, task_manager: Arc, global_cancellation_token: CancellationToken, ) -> Self { @@ -117,6 +120,7 @@ impl Downstream { downstream_id, supported_extensions, required_extensions, + full_template_mode_required, downstream_cancellation_token, } } diff --git a/pool-apps/jd-server/src/lib/job_declarator/mod.rs b/pool-apps/jd-server/src/lib/job_declarator/mod.rs index c53f72177..3cfcbf13d 100644 --- a/pool-apps/jd-server/src/lib/job_declarator/mod.rs +++ b/pool-apps/jd-server/src/lib/job_declarator/mod.rs @@ -160,6 +160,7 @@ impl JobDeclarator { cancellation_token: CancellationToken, supported_extensions: Vec, required_extensions: Vec, + full_template_mode_required: bool, ) -> JDSResult<(), error::JobDeclarator> { info!("Starting downstream server at {listening_address}"); let server = TcpListener::bind(listening_address) @@ -189,6 +190,7 @@ impl JobDeclarator { let task_manager_inner = task_manager_clone.clone(); let supported_extensions_inner = supported_extensions.clone(); let required_extensions_inner = required_extensions.clone(); + let full_template_mode_required_inner = full_template_mode_required; task_manager_clone.spawn(async move { let noise_stream = tokio::select! { @@ -228,6 +230,7 @@ impl JobDeclarator { to_downstream_receiver, supported_extensions_inner, required_extensions_inner, + full_template_mode_required_inner, task_manager_inner.clone(), cancellation_token_inner.clone(), ); diff --git a/pool-apps/pool/src/lib/mod.rs b/pool-apps/pool/src/lib/mod.rs index ac9688b79..421ad9506 100644 --- a/pool-apps/pool/src/lib/mod.rs +++ b/pool-apps/pool/src/lib/mod.rs @@ -139,6 +139,7 @@ impl PoolSv2 { cancellation_token.clone(), jds_config.supported_extensions().to_vec(), jds_config.required_extensions().to_vec(), + jds_config.full_template_mode_required(), ) .await .map_err(|e| PoolErrorKind::Jds(e.into()))?;