Skip to content

Commit 319b66c

Browse files
authored
Merge pull request stratum-mining#432 from Shourya742/2026-04-15-remove-statics-from-tproxy-and-jdc
Remove statics from tproxy and jdc
2 parents 527237a + 7b4a82a commit 319b66c

20 files changed

Lines changed: 343 additions & 209 deletions

integration-tests/tests/jd_integration.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,88 @@ async fn jds_should_not_panic_if_jdc_shutsdown() {
5959
shutdown_all!(jdc_1, pool);
6060
}
6161

62+
// This test verifies that mode state is isolated per JDC instance.
63+
//
64+
// We start one JDC in solo mining mode (no upstreams) and then start another
65+
// JDC in full template mode (with upstream). The solo instance must not start
66+
// behaving like full-template mode after the second instance activates.
67+
#[tokio::test]
68+
async fn multiple_jdc_sessions() {
69+
start_tracing();
70+
let (tp, tp_addr) = start_template_provider(Some(1), DifficultyLevel::Low);
71+
let (pool, pool_addr, jds_addr, _) =
72+
start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await;
73+
74+
let (solo_tp_sniffer, solo_tp_sniffer_addr) =
75+
start_sniffer("solo-jdc-tp", tp_addr, false, vec![], None);
76+
let (solo_jdc, solo_jdc_addr, _) = start_jdc(
77+
&[],
78+
sv2_tp_config(solo_tp_sniffer_addr),
79+
vec![],
80+
vec![],
81+
false,
82+
Some(jd_client_sv2::config::ConfigJDCMode::SoloMining),
83+
);
84+
let _solo_downstream = MockDownstream::new(
85+
solo_jdc_addr,
86+
WithSetup::yes_with_defaults(Protocol::MiningProtocol, 0),
87+
)
88+
.start()
89+
.await;
90+
91+
solo_tp_sniffer
92+
.wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION)
93+
.await;
94+
solo_tp_sniffer
95+
.wait_for_message_type(
96+
MessageDirection::ToDownstream,
97+
MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS,
98+
)
99+
.await;
100+
solo_tp_sniffer.clean_queue(MessageDirection::ToUpstream);
101+
solo_tp_sniffer.clean_queue(MessageDirection::ToDownstream);
102+
103+
let (full_jds_sniffer, full_jds_sniffer_addr) =
104+
start_sniffer("full-jdc-jds", jds_addr, false, vec![], None);
105+
let (full_jdc, _full_jdc_addr, _) = start_jdc(
106+
&[(pool_addr, full_jds_sniffer_addr)],
107+
sv2_tp_config(tp_addr),
108+
vec![],
109+
vec![],
110+
false,
111+
Some(jd_client_sv2::config::ConfigJDCMode::FullTemplate),
112+
);
113+
114+
full_jds_sniffer
115+
.wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION)
116+
.await;
117+
full_jds_sniffer
118+
.wait_for_message_type(
119+
MessageDirection::ToDownstream,
120+
MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS,
121+
)
122+
.await;
123+
124+
// Trigger post-start template updates; using two blocks reduces timing flakiness.
125+
tp.generate_blocks(1);
126+
tp.generate_blocks(1);
127+
128+
// RequestTransactionData is FullTemplate-only. If mode leaked process-wide,
129+
// the solo JDC would emit this after the full-template JDC activates.
130+
assert!(
131+
solo_tp_sniffer
132+
.assert_message_not_present(
133+
MessageDirection::ToUpstream,
134+
MESSAGE_TYPE_REQUEST_TRANSACTION_DATA,
135+
std::time::Duration::from_secs(2),
136+
)
137+
.await,
138+
"Solo-mode JDC should not request transaction data after another JDC activates full-template mode"
139+
);
140+
141+
shutdown_all!(solo_jdc, full_jdc, pool);
142+
}
143+
62144
// This test verifies that jd-client exchange SetupConnection messages with a Template Provider.
63145
//
64146
// Note that jd-client starts to exchange messages with the Template Provider after it has accepted

integration-tests/tests/translator_integration.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,3 +1950,60 @@ async fn tproxy_sends_single_open_extended_mining_channel_in_aggregated_mode() {
19501950

19511951
shutdown_all!(pool, tproxy);
19521952
}
1953+
1954+
// This test verifies whether we can spawn multiple tproxy in the
1955+
// same process.
1956+
//
1957+
// More info here: https://github.com/stratum-mining/sv2-apps/issues/430
1958+
#[tokio::test]
1959+
async fn multiple_tproxy_sessions() {
1960+
start_tracing();
1961+
let (_tp, tp_addr) = start_template_provider(None, DifficultyLevel::High);
1962+
let (pool, pool_addr, _) = start_pool(sv2_tp_config(tp_addr), vec![], vec![], false).await;
1963+
1964+
let (pool_translator_sniffer_1, pool_translator_sniffer_addr_1) =
1965+
start_sniffer("0", pool_addr, false, vec![], None);
1966+
let (tproxy_1, _, _) = start_sv2_translator(
1967+
&[pool_translator_sniffer_addr_1],
1968+
true,
1969+
vec![],
1970+
vec![],
1971+
None,
1972+
false,
1973+
)
1974+
.await;
1975+
1976+
let (pool_translator_sniffer_2, pool_translator_sniffer_addr_2) =
1977+
start_sniffer("0", pool_addr, false, vec![], None);
1978+
let (tproxy_2, _, _) = start_sv2_translator(
1979+
&[pool_translator_sniffer_addr_2],
1980+
true,
1981+
vec![],
1982+
vec![],
1983+
None,
1984+
false,
1985+
)
1986+
.await;
1987+
1988+
pool_translator_sniffer_1
1989+
.wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION)
1990+
.await;
1991+
pool_translator_sniffer_1
1992+
.wait_for_message_type(
1993+
MessageDirection::ToDownstream,
1994+
MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS,
1995+
)
1996+
.await;
1997+
1998+
pool_translator_sniffer_2
1999+
.wait_for_message_type(MessageDirection::ToUpstream, MESSAGE_TYPE_SETUP_CONNECTION)
2000+
.await;
2001+
pool_translator_sniffer_2
2002+
.wait_for_message_type(
2003+
MessageDirection::ToDownstream,
2004+
MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS,
2005+
)
2006+
.await;
2007+
2008+
shutdown_all!(pool, tproxy_1, tproxy_2);
2009+
}

miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ use crate::{
3434
ChannelManager, ChannelManagerChannel, SharesOrderedByDiff, SOLO_FULL_EXTRANONCE_SIZE,
3535
},
3636
error::{self, JDCError, JDCErrorKind},
37-
jd_mode::{get_jd_mode, JdMode},
3837
utils::{add_share_to_cache, create_close_channel_msg},
3938
};
4039

@@ -100,8 +99,8 @@ impl RouteMessageTo<'_> {
10099
/// The routing is handled as follows:
101100
/// - [`RouteMessageTo::Downstream`] → Sends the mining message to the specified downstream
102101
/// client.
103-
/// - [`RouteMessageTo::Upstream`] → Sends the mining message upstream, unless in
104-
/// [`JdMode::SoloMining`].
102+
/// - [`RouteMessageTo::Upstream`] → Forwards mining message upstream. In solo mode,
103+
/// upstream-directed messages should not be produced.
105104
/// - [`RouteMessageTo::JobDeclarator`] → Sends the job declaration message to the JDS.
106105
/// - [`RouteMessageTo::TemplateProvider`] → Sends the template distribution message to the
107106
/// template provider.
@@ -121,14 +120,12 @@ impl RouteMessageTo<'_> {
121120
}
122121
}
123122
RouteMessageTo::Upstream(message) => {
124-
if get_jd_mode() != JdMode::SoloMining {
125-
let message_static = message.into_static();
126-
let sv2_frame: Sv2Frame = AnyMessage::Mining(message_static).try_into()?;
127-
channel_manager_channel
128-
.upstream_sender
129-
.send(sv2_frame)
130-
.await?;
131-
}
123+
let message_static = message.into_static();
124+
let sv2_frame: Sv2Frame = AnyMessage::Mining(message_static).try_into()?;
125+
channel_manager_channel
126+
.upstream_sender
127+
.send(sv2_frame)
128+
.await?;
132129
}
133130
RouteMessageTo::JobDeclarator(message) => {
134131
channel_manager_channel

miner-apps/jd-client/src/lib/channel_manager/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ use crate::{
6161
config::JobDeclaratorClientConfig,
6262
downstream::Downstream,
6363
error::{self, Action, JDCError, JDCErrorKind, JDCResult, LoopControl},
64+
jd_mode::JDMode,
6465
utils::{
6566
AtomicUpstreamState, DownstreamChannelJobId, DownstreamMessage, PendingChannelRequest,
6667
SharesOrderedByDiff, UpstreamState,
@@ -284,6 +285,7 @@ pub struct ChannelManager {
284285
/// 3. Connected: An upstream channel is successfully established.
285286
/// 4. SoloMining: No upstream is available; the JDC operates in solo mining mode. case.
286287
pub upstream_state: AtomicUpstreamState,
288+
pub mode: JDMode,
287289
}
288290

289291
#[cfg_attr(not(test), hotpath::measure_all)]
@@ -345,6 +347,7 @@ impl ChannelManager {
345347
coinbase_outputs: Vec<u8>,
346348
supported_extensions: Vec<u16>,
347349
required_extensions: Vec<u16>,
350+
mode: JDMode,
348351
) -> JDCResult<Self, error::ChannelManager> {
349352
// Start with a solo-mining allocator (no upstream prefix). Once the
350353
// upstream channel is opened in `handle_open_extended_mining_channel_success`
@@ -399,6 +402,7 @@ impl ChannelManager {
399402
reserved_downstream_rollable_extranonce_size: config
400403
.reserved_downstream_rollable_extranonce_size(),
401404
upstream_state: AtomicUpstreamState::new(UpstreamState::SoloMining),
405+
mode,
402406
};
403407

404408
Ok(channel_manager)

miner-apps/jd-client/src/lib/channel_manager/template_message_handler.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use tracing::{error, info, warn};
1515
use crate::{
1616
channel_manager::{downstream_message_handler::RouteMessageTo, ChannelManager, DeclaredJob},
1717
error::{self, JDCError, JDCErrorKind},
18-
jd_mode::{get_jd_mode, JdMode},
1918
};
2019

2120
#[cfg_attr(not(test), hotpath::measure_all)]
@@ -63,7 +62,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager {
6362
let mut coinbase_outputs = deserialize_outputs(coinbase_outputs)
6463
.map_err(|_| JDCError::shutdown(JDCErrorKind::ChannelManagerHasBadCoinbaseOutputs))?;
6564

66-
if get_jd_mode() == JdMode::FullTemplate {
65+
if self.mode.is_full_template() {
6766
let tx_data_request =
6867
TemplateDistribution::RequestTransactionData(RequestTransactionData {
6968
template_id: msg.template_id,
@@ -81,7 +80,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager {
8180
coinbase_outputs[0].value = Amount::from_sat(msg.coinbase_tx_value_remaining);
8281

8382
let coinbase_only_token = if !msg.future_template
84-
&& get_jd_mode() == JdMode::CoinbaseOnly
83+
&& self.mode.is_coinbase_only()
8584
&& channel_manager_data.upstream_channel.is_some()
8685
&& channel_manager_data.last_new_prev_hash.is_some()
8786
&& channel_manager_data.job_factory.is_some()
@@ -440,7 +439,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager {
440439
(data.last_future_template.clone(), declare_job)
441440
});
442441

443-
if get_jd_mode() == JdMode::FullTemplate {
442+
if self.mode.is_full_template() {
444443
if let Some(Some(job)) = declare_job {
445444
let message = JobDeclaration::DeclareMiningJob(job);
446445

@@ -467,7 +466,7 @@ impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager {
467466
if let Some(ref mut upstream_channel) = channel_manager_data.upstream_channel {
468467
_ = upstream_channel.on_chain_tip_update(msg.clone().into());
469468

470-
if get_jd_mode() == JdMode::CoinbaseOnly
469+
if self.mode.is_coinbase_only()
471470
&& channel_manager_data.job_factory.is_some()
472471
&& future_template.is_some()
473472
{

miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use crate::{
2424
JDC_LOCAL_PREFIX_BYTES, JDC_MAX_CHANNELS,
2525
},
2626
error::{self, JDCError, JDCErrorKind},
27-
jd_mode::{get_jd_mode, JdMode},
2827
utils::{create_close_channel_msg, validate_cached_share, UpstreamState},
2928
};
3029

@@ -179,7 +178,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager {
179178
debug!("Applied last_new_prev_hash to new extended channel");
180179
}
181180

182-
let set_custom_job = if get_jd_mode() == JdMode::CoinbaseOnly
181+
let set_custom_job = if self.mode.is_coinbase_only()
183182
&& data.job_factory.is_some()
184183
&& data.last_future_template.is_some()
185184
&& data.last_new_prev_hash.is_some()
@@ -254,7 +253,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager {
254253
});
255254

256255
if channel_state == UpstreamState::Connected {
257-
if get_jd_mode() == JdMode::FullTemplate {
256+
if self.mode.is_full_template() {
258257
if let Some(template) = template {
259258
let tx_data_request =
260259
TemplateDistribution::RequestTransactionData(RequestTransactionData {
@@ -268,7 +267,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager {
268267
}
269268
}
270269

271-
if get_jd_mode() == JdMode::CoinbaseOnly {
270+
if self.mode.is_coinbase_only() {
272271
if let Some(custom_job) = custom_job {
273272
let set_custom_job = Mining::SetCustomMiningJob(custom_job);
274273
let sv2_frame: Sv2Frame = AnyMessage::Mining(set_custom_job)

miner-apps/jd-client/src/lib/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ impl JobDeclaratorClientConfig {
223223
}
224224
}
225225

226-
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
226+
#[derive(Debug, Deserialize, Clone, Copy, Default, PartialEq)]
227227
#[serde(rename_all = "UPPERCASE")]
228228
pub enum ConfigJDCMode {
229229
#[default]

0 commit comments

Comments
 (0)