Skip to content

Commit 5e3bb6f

Browse files
committed
feat: extend network inference to JDC; move tp_type utilities to stratum-apps
Move network_from_tp_port() and VALID_NETWORKS from pool-apps (private) into stratum-apps/src/tp_type.rs as public API. Add BitcoinNetwork::as_network_str() and TemplateProviderType::infer_network() so any application using TemplateProviderType can derive the Bitcoin network without duplicating the port-mapping logic. Pool config is updated to delegate to infer_network() instead of carrying its own copy of the helper function. JobDeclaratorClientConfig gains the same effective_network() / with_network() pattern as PoolConfig. For Sv2Tp the network is inferred from the sv2-tp port; for BitcoinCoreIpc it is taken directly from the BitcoinNetwork enum value. An explicit network override field (serde default) is provided for non-standard port setups. Both the initial startup and reconnect MonitoringServer paths are updated to call .with_network(config.effective_network()). Integration test lib adds start_jdc_with_network_override(); new test global_info_network_jdc_from_config_override verifies JDC GlobalInfo exposes the network field. Unit tests added to stratum-apps/tp_type, pool config, and JDC config.
1 parent a11db59 commit 5e3bb6f

File tree

6 files changed

+303
-61
lines changed

6 files changed

+303
-61
lines changed

integration-tests/lib/mod.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,26 @@ pub fn start_jdc(
216216
required_extensions: Vec<u16>,
217217
enable_monitoring: bool,
218218
jdc_mode: Option<ConfigJDCMode>,
219+
) -> (JobDeclaratorClient, SocketAddr, Option<SocketAddr>) {
220+
start_jdc_with_network_override(
221+
pool,
222+
template_provider_config,
223+
supported_extensions,
224+
required_extensions,
225+
enable_monitoring,
226+
jdc_mode,
227+
None,
228+
)
229+
}
230+
231+
pub fn start_jdc_with_network_override(
232+
pool: &[(SocketAddr, SocketAddr)], // (pool_address, jds_address)
233+
template_provider_config: TemplateProviderType,
234+
supported_extensions: Vec<u16>,
235+
required_extensions: Vec<u16>,
236+
enable_monitoring: bool,
237+
jdc_mode: Option<ConfigJDCMode>,
238+
network: Option<String>,
219239
) -> (JobDeclaratorClient, SocketAddr, Option<SocketAddr>) {
220240
use jd_client_sv2::config::{JobDeclaratorClientConfig, PoolConfig, ProtocolConfig, Upstream};
221241
let jdc_address = get_available_address();
@@ -279,7 +299,8 @@ pub fn start_jdc(
279299
required_extensions,
280300
monitoring_address,
281301
monitoring_cache_refresh_secs,
282-
);
302+
)
303+
.with_network(network);
283304
let ret = jd_client_sv2::JobDeclaratorClient::new(jd_client_proxy);
284305
let ret_clone = ret.clone();
285306
tokio::spawn(async move { ret_clone.start().await });

integration-tests/tests/monitoring_integration.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,48 @@ async fn global_info_network_from_config_override() {
305305
}
306306

307307
// ---------------------------------------------------------------------------
308-
// 6. Translator starts cleanly when upstream_monitoring_url is unreachable.
308+
// 6. JDC exposes network via config override in GlobalInfo.
309+
// ---------------------------------------------------------------------------
310+
#[tokio::test]
311+
async fn global_info_network_jdc_from_config_override() {
312+
start_tracing();
313+
let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low);
314+
let (_pool, pool_addr, jds_addr, _pool_monitoring) =
315+
start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await;
316+
317+
let (jdc, _jdc_addr, jdc_monitoring) = start_jdc_with_network_override(
318+
&[(pool_addr, jds_addr)],
319+
sv2_tp_config(tp_addr),
320+
vec![],
321+
vec![],
322+
true,
323+
None,
324+
Some("regtest".to_string()),
325+
);
326+
let jdc_mon = jdc_monitoring.expect("jdc monitoring should be enabled");
327+
328+
// Wait up to 30 seconds for JDC to connect and serve the monitoring endpoint.
329+
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
330+
loop {
331+
let body = fetch_api(jdc_mon, "/api/v1/global").await;
332+
let json: serde_json::Value = serde_json::from_str(&body).unwrap();
333+
if json["network"] == "regtest" {
334+
break;
335+
}
336+
if std::time::Instant::now() >= deadline {
337+
panic!(
338+
"jdc global info did not expose network within timeout; got: {}",
339+
json
340+
);
341+
}
342+
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
343+
}
344+
345+
shutdown_all!(jdc);
346+
}
347+
348+
// ---------------------------------------------------------------------------
349+
// 7. Translator starts cleanly when upstream_monitoring_url is unreachable.
309350
// ---------------------------------------------------------------------------
310351
#[tokio::test]
311352
async fn global_info_network_unreachable_upstream() {

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

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use stratum_apps::{
88
config_helpers::{opt_path_from_toml, CoinbaseRewardScript},
99
key_utils::{Secp256k1PublicKey, Secp256k1SecretKey},
1010
stratum_core::bitcoin::{Amount, TxOut},
11-
tp_type::TemplateProviderType,
11+
tp_type::{TemplateProviderType, VALID_NETWORKS},
1212
utils::types::{SharesBatchSize, SharesPerMinute},
1313
};
1414

@@ -59,6 +59,15 @@ pub struct JobDeclaratorClientConfig {
5959
monitoring_address: Option<SocketAddr>,
6060
#[serde(default)]
6161
monitoring_cache_refresh_secs: Option<u64>,
62+
/// Optional override for the Bitcoin network name exposed via `GET /api/v1/global`.
63+
/// When absent the network is inferred from the template provider config:
64+
/// - `Sv2Tp`: mapped from the sv2-tp port using well-known defaults (see
65+
/// `stratum_apps::tp_type::network_from_tp_port`). Returns `None` for non-standard ports.
66+
/// - `BitcoinCoreIpc`: taken directly from the `BitcoinNetwork` enum value.
67+
/// Values follow bitcoin-cli convention: `"main"`, `"test"`, `"testnet4"`,
68+
/// `"signet"`, `"regtest"`.
69+
#[serde(default)]
70+
network: Option<String>,
6271
}
6372

6473
impl JobDeclaratorClientConfig {
@@ -100,6 +109,7 @@ impl JobDeclaratorClientConfig {
100109
required_extensions,
101110
monitoring_address,
102111
monitoring_cache_refresh_secs,
112+
network: None,
103113
}
104114
}
105115

@@ -196,6 +206,35 @@ impl JobDeclaratorClientConfig {
196206
pub fn required_extensions(&self) -> &[u16] {
197207
&self.required_extensions
198208
}
209+
210+
/// Set the Bitcoin network override (builder style).
211+
/// Only needed for non-standard sv2-tp port setups; `BitcoinCoreIpc` configs derive the
212+
/// network automatically from the `BitcoinNetwork` enum value.
213+
pub fn with_network(mut self, network: Option<String>) -> Self {
214+
self.network = network;
215+
self
216+
}
217+
218+
/// Returns the effective Bitcoin network name: the explicit `network` override if set,
219+
/// otherwise inferred from `template_provider_type`.
220+
///
221+
/// Returns `None` if the explicit override is not one of the known values, or if using
222+
/// `Sv2Tp` with a non-standard port (set `network` explicitly for non-standard port setups).
223+
pub fn effective_network(&self) -> Option<String> {
224+
if let Some(ref n) = self.network {
225+
if !VALID_NETWORKS.contains(&n.as_str()) {
226+
tracing::warn!(
227+
"jdc config: network {:?} is not a recognised value \
228+
(expected one of {:?}); network will not be reported.",
229+
n,
230+
VALID_NETWORKS
231+
);
232+
return None;
233+
}
234+
return Some(n.clone());
235+
}
236+
self.template_provider_type.infer_network().map(|s| s.to_string())
237+
}
199238
}
200239

201240
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
@@ -302,3 +341,47 @@ impl Upstream {
302341
}
303342
}
304343
}
344+
345+
#[cfg(test)]
346+
mod tests {
347+
use stratum_apps::tp_type::{BitcoinNetwork, TemplateProviderType};
348+
349+
fn sv2_tp(address: &str) -> TemplateProviderType {
350+
TemplateProviderType::Sv2Tp {
351+
address: address.to_string(),
352+
public_key: None,
353+
}
354+
}
355+
356+
fn ipc_tp(network: BitcoinNetwork) -> TemplateProviderType {
357+
TemplateProviderType::BitcoinCoreIpc {
358+
network,
359+
data_dir: None,
360+
fee_threshold: 0,
361+
min_interval: 5,
362+
}
363+
}
364+
365+
#[test]
366+
fn infer_network_sv2tp_standard_ports() {
367+
assert_eq!(sv2_tp("127.0.0.1:18447").infer_network(), Some("regtest"));
368+
assert_eq!(sv2_tp("127.0.0.1:8442").infer_network(), Some("main"));
369+
assert_eq!(sv2_tp("127.0.0.1:18442").infer_network(), Some("test"));
370+
assert_eq!(sv2_tp("127.0.0.1:48442").infer_network(), Some("testnet4"));
371+
assert_eq!(sv2_tp("127.0.0.1:38442").infer_network(), Some("signet"));
372+
}
373+
374+
#[test]
375+
fn infer_network_sv2tp_nonstandard_port_returns_none() {
376+
assert_eq!(sv2_tp("127.0.0.1:4444").infer_network(), None);
377+
assert_eq!(sv2_tp("127.0.0.1:3333").infer_network(), None);
378+
}
379+
380+
#[test]
381+
fn infer_network_bitcoin_core_ipc() {
382+
assert_eq!(ipc_tp(BitcoinNetwork::Regtest).infer_network(), Some("regtest"));
383+
assert_eq!(ipc_tp(BitcoinNetwork::Mainnet).infer_network(), Some("main"));
384+
assert_eq!(ipc_tp(BitcoinNetwork::Testnet4).infer_network(), Some("testnet4"));
385+
assert_eq!(ipc_tp(BitcoinNetwork::Signet).infer_network(), Some("signet"));
386+
}
387+
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ impl JobDeclaratorClient {
136136
self.config.monitoring_cache_refresh_secs().unwrap_or(15),
137137
),
138138
)
139-
.expect("Failed to initialize monitoring server");
139+
.expect("Failed to initialize monitoring server")
140+
.with_network(self.config.effective_network());
140141

141142
// Create shutdown signal using cancellation token
142143
let cancellation_token_clone = self.cancellation_token.clone();
@@ -506,7 +507,8 @@ impl JobDeclaratorClient {
506507
Some(Arc::new(channel_manager_clone.clone())),
507508
std::time::Duration::from_secs(self.config.monitoring_cache_refresh_secs().unwrap_or(15)),
508509
)
509-
.expect("Failed to initialize monitoring server");
510+
.expect("Failed to initialize monitoring server")
511+
.with_network(self.config.effective_network());
510512

511513
let cancellation_token_clone = self.cancellation_token.clone();
512514
let fallback_coordinator_token = fallback_coordinator.token();

pool-apps/pool/src/lib/config.rs

Lines changed: 7 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,12 @@ use stratum_apps::{
1818
config_helpers::{opt_path_from_toml, CoinbaseRewardScript},
1919
key_utils::{Secp256k1PublicKey, Secp256k1SecretKey},
2020
stratum_core::bitcoin::{Amount, TxOut},
21-
tp_type::TemplateProviderType,
21+
tp_type::{TemplateProviderType, VALID_NETWORKS},
2222
utils::types::{SharesBatchSize, SharesPerMinute},
2323
};
2424

2525
use crate::error::PoolErrorKind;
2626

27-
/// Valid Bitcoin network names (bitcoin-cli / `getblockchaininfo` convention).
28-
const VALID_NETWORKS: &[&str] = &["main", "test", "testnet4", "signet", "regtest"];
29-
30-
/// Maps a well-known sv2-tp default port to a Bitcoin network name.
31-
/// Port assignments from `man sv2-tp`:
32-
/// 8442 → main, 18442 → test, 48442 → testnet4,
33-
/// 38442 → signet, 18447 → regtest
34-
fn network_from_tp_port(port: u16) -> Option<&'static str> {
35-
match port {
36-
8442 => Some("main"),
37-
18442 => Some("test"),
38-
48442 => Some("testnet4"),
39-
38442 => Some("signet"),
40-
18447 => Some("regtest"),
41-
_ => None,
42-
}
43-
}
44-
4527
/// Configuration for the Pool, including connection, authority, and coinbase settings.
4628
#[derive(Clone, Debug, serde::Deserialize)]
4729
pub struct PoolConfig {
@@ -236,12 +218,7 @@ impl PoolConfig {
236218
}
237219
return Some(n.clone());
238220
}
239-
if let TemplateProviderType::Sv2Tp { address, .. } = &self.template_provider_type {
240-
if let Ok(socket_addr) = address.parse::<std::net::SocketAddr>() {
241-
return network_from_tp_port(socket_addr.port()).map(|s| s.to_string());
242-
}
243-
}
244-
None
221+
self.template_provider_type.infer_network().map(|s| s.to_string())
245222
}
246223

247224
/// Set the Bitcoin network override (builder style).
@@ -307,21 +284,7 @@ impl ConnectionConfig {
307284
#[cfg(test)]
308285
mod tests {
309286
use super::*;
310-
311-
#[test]
312-
fn network_from_tp_port_known_ports() {
313-
assert_eq!(network_from_tp_port(8442), Some("main"));
314-
assert_eq!(network_from_tp_port(18442), Some("test"));
315-
assert_eq!(network_from_tp_port(48442), Some("testnet4"));
316-
assert_eq!(network_from_tp_port(38442), Some("signet"));
317-
assert_eq!(network_from_tp_port(18447), Some("regtest"));
318-
}
319-
320-
#[test]
321-
fn network_from_tp_port_unknown_port() {
322-
assert_eq!(network_from_tp_port(4444), None);
323-
assert_eq!(network_from_tp_port(0), None);
324-
}
287+
use stratum_apps::tp_type::network_from_tp_port;
325288

326289
fn sv2_tp_type(address: &str) -> TemplateProviderType {
327290
TemplateProviderType::Sv2Tp {
@@ -331,26 +294,14 @@ mod tests {
331294
}
332295

333296
#[test]
334-
fn effective_network_infers_from_tp_port() {
335-
let tp_type = sv2_tp_type("127.0.0.1:18447");
336-
// Build a minimal config manually using the serde path is complex; test the
337-
// helper function directly.
338-
assert_eq!(network_from_tp_port(18447), Some("regtest"));
339-
assert_eq!(network_from_tp_port(8442), Some("main"));
340-
// Confirm an unknown port yields None
341-
assert_eq!(network_from_tp_port(4444), None);
342-
// Confirm the address parser works as expected
343-
let port = "127.0.0.1:18447"
344-
.parse::<std::net::SocketAddr>()
345-
.unwrap()
346-
.port();
347-
assert_eq!(network_from_tp_port(port), Some("regtest"));
348-
drop(tp_type); // suppress unused warning
297+
fn infer_network_standard_tp_ports() {
298+
assert_eq!(sv2_tp_type("127.0.0.1:18447").infer_network(), Some("regtest"));
299+
assert_eq!(sv2_tp_type("127.0.0.1:8442").infer_network(), Some("main"));
300+
assert_eq!(sv2_tp_type("127.0.0.1:4444").infer_network(), None);
349301
}
350302

351303
#[test]
352304
fn valid_networks_covers_known_port_outputs() {
353-
// Every value network_from_tp_port can return must be in VALID_NETWORKS
354305
for port in [8442u16, 18442, 48442, 38442, 18447] {
355306
let name = network_from_tp_port(port).unwrap();
356307
assert!(

0 commit comments

Comments
 (0)