From b1c5a36703df9e409d145d10b83f6b1eb84669f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:17:45 +0000 Subject: [PATCH 1/3] fix(sdk): centralize mainnet and testnet builders (backport from PR #3533) Agent-Logs-Url: https://github.com/dashpay/platform/sessions/792a6a35-3c58-465b-91a3-31122aaf67e5 Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 --- packages/rs-sdk-ffi/src/sdk.rs | 80 +----------------------- packages/rs-sdk/src/sdk.rs | 110 ++++++++++++++++++++++++++++++--- packages/wasm-sdk/src/sdk.rs | 34 +--------- 3 files changed, 107 insertions(+), 117 deletions(-) diff --git a/packages/rs-sdk-ffi/src/sdk.rs b/packages/rs-sdk-ffi/src/sdk.rs index 5d212a7fd97..ae445c530a1 100644 --- a/packages/rs-sdk-ffi/src/sdk.rs +++ b/packages/rs-sdk-ffi/src/sdk.rs @@ -380,84 +380,8 @@ pub unsafe extern "C" fn dash_sdk_create_trusted(config: *const DashSDKConfig) - info!("dash_sdk_create_trusted: no DAPI addresses provided, using defaults for network"); // Use default addresses for the network match network { - Network::Testnet => { - // Fixed testnet DAPI addresses (hp-masternodes 1-29) - let default_addresses = [ - "https://68.67.122.1:1443", - "https://68.67.122.2:1443", - "https://68.67.122.3:1443", - "https://68.67.122.4:1443", - "https://68.67.122.5:1443", - "https://68.67.122.6:1443", - "https://68.67.122.7:1443", - "https://68.67.122.8:1443", - "https://68.67.122.9:1443", - "https://68.67.122.10:1443", - "https://68.67.122.11:1443", - "https://68.67.122.12:1443", - "https://68.67.122.13:1443", - "https://68.67.122.14:1443", - "https://68.67.122.15:1443", - "https://68.67.122.16:1443", - "https://68.67.122.17:1443", - "https://68.67.122.18:1443", - "https://68.67.122.19:1443", - "https://68.67.122.20:1443", - "https://68.67.122.21:1443", - "https://68.67.122.22:1443", - "https://68.67.122.23:1443", - "https://68.67.122.24:1443", - "https://68.67.122.25:1443", - "https://68.67.122.26:1443", - "https://68.67.122.27:1443", - "https://68.67.122.28:1443", - "https://68.67.122.29:1443", - ] - .join(","); - - info!( - addresses = default_addresses.as_str(), - "dash_sdk_create_trusted: using default testnet addresses" - ); - let address_list = match AddressList::from_str(&default_addresses) { - Ok(list) => list, - Err(e) => { - error!(error = %e, "dash_sdk_create_trusted: failed to parse default addresses"); - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InternalError, - format!("Failed to parse default addresses: {}", e), - )); - } - }; - SdkBuilder::new(address_list).with_network(network) - } - Network::Mainnet => { - // Use mainnet addresses from WASM SDK - let default_addresses = [ - "https://149.28.241.190:443", - "https://198.7.115.48:443", - "https://134.255.182.186:443", - "https://93.115.172.39:443", - "https://5.189.164.253:443", - "https://178.215.237.134:443", - "https://157.66.81.162:443", - "https://173.212.232.90:443", - ] - .join(","); - - info!("dash_sdk_create_trusted: using default mainnet addresses"); - let address_list = match AddressList::from_str(&default_addresses) { - Ok(list) => list, - Err(e) => { - error!(error = %e, "dash_sdk_create_trusted: failed to parse default addresses"); - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InternalError, - format!("Failed to parse default addresses: {}", e), - )); - } - }; - SdkBuilder::new(address_list).with_network(network) - } + Network::Testnet => SdkBuilder::new_testnet(), + Network::Mainnet => SdkBuilder::new_mainnet(), _ => { error!( ?network, diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 112f3327100..9d317117f06 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -25,6 +25,7 @@ use drive_proof_verifier::FromProof; pub use http::Uri; #[cfg(feature = "mocks")] use rs_dapi_client::mock::MockDapiClient; +use rs_dapi_client::Address; pub use rs_dapi_client::AddressList; pub use rs_dapi_client::RequestSettings; use rs_dapi_client::{ @@ -63,6 +64,46 @@ const DEFAULT_REQUEST_SETTINGS: RequestSettings = RequestSettings { max_decoding_message_size: None, }; +const DEFAULT_MAINNET_ADDRESSES: &[&str] = &[ + "https://149.28.241.190:443", + "https://198.7.115.48:443", + "https://134.255.182.186:443", + "https://93.115.172.39:443", + "https://5.189.164.253:443", + "https://178.215.237.134:443", + "https://157.66.81.162:443", + "https://173.212.232.90:443", +]; + +const DEFAULT_TESTNET_ADDRESSES: &[&str] = &[ + "https://52.12.176.90:1443", + "https://35.82.197.197:1443", + "https://44.240.98.102:1443", + "https://52.34.144.50:1443", + "https://44.239.39.153:1443", + "https://34.214.48.68:1443", + "https://35.164.23.245:1443", + "https://54.149.33.167:1443", + "https://52.24.124.162:1443", +]; + +fn parse_address_list(addresses: &[&str]) -> AddressList { + AddressList::from_iter(addresses.iter().map(|address| { + let uri = address + .parse::() + .expect("default SDK address must be a valid URI"); + Address::try_from(uri).expect("default SDK address must be a valid DAPI address") + })) +} + +fn default_address_list_for_network(network: Network) -> Option { + match network { + Network::Mainnet => Some(parse_address_list(DEFAULT_MAINNET_ADDRESSES)), + Network::Testnet => Some(parse_address_list(DEFAULT_TESTNET_ADDRESSES)), + _ => None, + } +} + /// Dash Platform SDK /// /// This is the main entry point for interacting with Dash Platform. @@ -747,18 +788,19 @@ impl SdkBuilder { Self::default() } - /// Create a new SdkBuilder instance preconfigured for testnet. NOT IMPLEMENTED YET. + /// Create a new SdkBuilder instance preconfigured for testnet. /// /// This is a helper method that preconfigures [SdkBuilder] for testnet use. /// Use this method if you want to connect to Dash Platform testnet during development and testing /// of your solution. pub fn new_testnet() -> Self { - unimplemented!( - "Testnet address list not implemented yet. Use new() and provide address list." - ) + let address_list = default_address_list_for_network(Network::Testnet) + .expect("testnet default address list must be available"); + + Self::new(address_list).with_network(Network::Testnet) } - /// Create a new SdkBuilder instance preconfigured for mainnet (production network). NOT IMPLEMENTED YET. + /// Create a new SdkBuilder instance preconfigured for mainnet (production network). /// /// This is a helper method that preconfigures [SdkBuilder] for production use. /// Use this method if you want to connect to Dash Platform mainnet with production-ready product. @@ -771,9 +813,10 @@ impl SdkBuilder { /// /// This method is unstable and can be changed in the future. pub fn new_mainnet() -> Self { - unimplemented!( - "Mainnet address list not implemented yet. Use new() and provide address list." - ) + let address_list = default_address_list_for_network(Network::Mainnet) + .expect("mainnet default address list must be available"); + + Self::new(address_list).with_network(Network::Mainnet) } /// Configure network type. @@ -1094,6 +1137,7 @@ pub fn prettify_proof(proof: &Proof) -> String { #[cfg(test)] mod test { + use std::collections::BTreeSet; use std::sync::Arc; use dapi_grpc::platform::v0::{GetIdentityRequest, ResponseMetadata}; @@ -1102,6 +1146,56 @@ mod test { use crate::SdkBuilder; + use super::{AddressList, Network, DEFAULT_MAINNET_ADDRESSES, DEFAULT_TESTNET_ADDRESSES}; + + fn live_address_set(address_list: &AddressList) -> BTreeSet { + address_list + .get_live_addresses() + .into_iter() + .map(|address| address.to_string()) + .collect() + } + + fn expected_address_set(addresses: &[&str]) -> BTreeSet { + super::parse_address_list(addresses) + .get_live_addresses() + .into_iter() + .map(|address| address.to_string()) + .collect() + } + + #[test] + fn new_testnet_uses_default_testnet_addresses() { + let builder = SdkBuilder::new_testnet(); + let address_list = builder + .addresses + .as_ref() + .expect("testnet builder should configure default addresses"); + + assert_eq!(builder.network, Network::Testnet); + assert_eq!(address_list.len(), DEFAULT_TESTNET_ADDRESSES.len()); + assert_eq!( + live_address_set(address_list), + expected_address_set(DEFAULT_TESTNET_ADDRESSES) + ); + } + + #[test] + fn new_mainnet_uses_default_mainnet_addresses() { + let builder = SdkBuilder::new_mainnet(); + let address_list = builder + .addresses + .as_ref() + .expect("mainnet builder should configure default addresses"); + + assert_eq!(builder.network, Network::Mainnet); + assert_eq!(address_list.len(), DEFAULT_MAINNET_ADDRESSES.len()); + assert_eq!( + live_address_set(address_list), + expected_address_set(DEFAULT_MAINNET_ADDRESSES) + ); + } + #[test_matrix(97..102, 100, 2, false; "valid height")] #[test_case(103, 100, 2, true; "invalid height")] fn test_verify_metadata_height( diff --git a/packages/wasm-sdk/src/sdk.rs b/packages/wasm-sdk/src/sdk.rs index cf42e313a53..d88a83844d8 100644 --- a/packages/wasm-sdk/src/sdk.rs +++ b/packages/wasm-sdk/src/sdk.rs @@ -18,29 +18,7 @@ fn parse_addresses(addresses: &'static [&str]) -> Vec
{ }) .collect() } -// Mainnet addresses from mnowatch.org -fn default_mainnet_addresses() -> Vec
{ - parse_addresses(&[ - "https://149.28.241.190:443", - "https://198.7.115.48:443", - "https://134.255.182.186:443", - "https://93.115.172.39:443", - "https://5.189.164.253:443", - ]) -} -// Testnet addresses from https://quorums.testnet.networks.dash.org/masternodes -fn default_testnet_addresses() -> Vec
{ - parse_addresses(&[ - "https://52.12.176.90:1443", - "https://35.82.197.197:1443", - "https://44.240.98.102:1443", - "https://52.34.144.50:1443", - "https://44.239.39.153:1443", - "https://34.214.48.68:1443", - "https://54.149.33.167:1443", - "https://52.24.124.162:1443", - ]) -} + fn default_local_addresses() -> Vec
{ parse_addresses(&["https://127.0.0.1:2443"]) } @@ -249,10 +227,7 @@ impl WasmSdkBuilder { #[wasm_bindgen(js_name = "mainnet")] pub fn new_mainnet() -> Self { - let address_list = dash_sdk::sdk::AddressList::from_iter(default_mainnet_addresses()); - let sdk_builder = SdkBuilder::new(address_list) - .with_network(dash_sdk::dpp::dashcore::Network::Mainnet) - .with_context_provider(WasmContext {}); + let sdk_builder = SdkBuilder::new_mainnet().with_context_provider(WasmContext {}); Self { inner: sdk_builder, @@ -262,10 +237,7 @@ impl WasmSdkBuilder { #[wasm_bindgen(js_name = "testnet")] pub fn new_testnet() -> Self { - let address_list = dash_sdk::sdk::AddressList::from_iter(default_testnet_addresses()); - let sdk_builder = SdkBuilder::new(address_list) - .with_network(dash_sdk::dpp::dashcore::Network::Testnet) - .with_context_provider(WasmContext {}); + let sdk_builder = SdkBuilder::new_testnet().with_context_provider(WasmContext {}); Self { inner: sdk_builder, From 39d39d6adb3427032c3c130648e5b6668b441cda Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:19:51 +0000 Subject: [PATCH 2/3] fix(sdk): harden default network address parsing (backport from PR #3533) Agent-Logs-Url: https://github.com/dashpay/platform/sessions/792a6a35-3c58-465b-91a3-31122aaf67e5 Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 --- packages/rs-sdk/src/sdk.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 9d317117f06..74a5bfead1d 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -91,16 +91,18 @@ fn parse_address_list(addresses: &[&str]) -> AddressList { AddressList::from_iter(addresses.iter().map(|address| { let uri = address .parse::() - .expect("default SDK address must be a valid URI"); - Address::try_from(uri).expect("default SDK address must be a valid DAPI address") + .unwrap_or_else(|_| panic!("default SDK address must be a valid URI: {address}")); + Address::try_from(uri).unwrap_or_else(|_| { + panic!("default SDK address must be a valid DAPI address: {address}") + }) })) } -fn default_address_list_for_network(network: Network) -> Option { +fn default_address_list_for_network(network: Network) -> AddressList { match network { - Network::Mainnet => Some(parse_address_list(DEFAULT_MAINNET_ADDRESSES)), - Network::Testnet => Some(parse_address_list(DEFAULT_TESTNET_ADDRESSES)), - _ => None, + Network::Mainnet => parse_address_list(DEFAULT_MAINNET_ADDRESSES), + Network::Testnet => parse_address_list(DEFAULT_TESTNET_ADDRESSES), + _ => panic!("default address list is only available for mainnet and testnet"), } } @@ -794,8 +796,7 @@ impl SdkBuilder { /// Use this method if you want to connect to Dash Platform testnet during development and testing /// of your solution. pub fn new_testnet() -> Self { - let address_list = default_address_list_for_network(Network::Testnet) - .expect("testnet default address list must be available"); + let address_list = default_address_list_for_network(Network::Testnet); Self::new(address_list).with_network(Network::Testnet) } @@ -813,8 +814,7 @@ impl SdkBuilder { /// /// This method is unstable and can be changed in the future. pub fn new_mainnet() -> Self { - let address_list = default_address_list_for_network(Network::Mainnet) - .expect("mainnet default address list must be available"); + let address_list = default_address_list_for_network(Network::Mainnet); Self::new(address_list).with_network(Network::Mainnet) } From eac553da1197b35c944f7d23895327b26390e089 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:01:16 +0200 Subject: [PATCH 3/3] feat(sdk): source mainnet/testnet bootstrap from dash-network-seeds (backport from PR #3533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hardcoded `DEFAULT_MAINNET_ADDRESSES` and `DEFAULT_TESTNET_ADDRESSES` constants in `SdkBuilder::new_mainnet()` / `new_testnet()` with a filtered query against `dash_network_seeds::evo_seeds(network)`. The seed lists are refreshed weekly upstream in `rust-dashcore` (same rev as the other dash-* workspace deps), giving us a single source of truth for bootstrap DAPI endpoints instead of a manually curated subset. Only Evo (HPMN) masternodes run Dash Platform, so we filter to those and build `https://:` URIs — discarding the Core P2P port on `seed.address`, which is not what DAPI clients need. Malformed entries are silently skipped rather than panicking; the DAPI client rotates across the remaining addresses. No reachability/SSL filter is applied because the upstream list already ships only HPMNs from the Core DMN list and many Platform nodes legitimately use self-signed certs. Tests now assert the bootstrap list is non-empty and every entry uses the correct platform HTTP port (443 mainnet, 1443 testnet), which is stable across weekly seed refreshes. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 1 + Cargo.toml | 1 + packages/rs-sdk/Cargo.toml | 1 + packages/rs-sdk/src/sdk.rs | 146 ++++++++++++++++++++----------------- 4 files changed, 84 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cad6c654044..974d2a979eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ dependencies = [ "dapi-grpc", "dash-async", "dash-context-provider", + "dash-network-seeds", "dash-platform-macros", "derive_more 1.0.0", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index 5389a29c402..2f87cfd9dfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ [workspace.dependencies] dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "ea33cbc84179666c25515dfc817ce32210953037" } +dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "ea33cbc84179666c25515dfc817ce32210953037" } dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "ea33cbc84179666c25515dfc817ce32210953037" } dash-spv-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "ea33cbc84179666c25515dfc817ce32210953037" } key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "ea33cbc84179666c25515dfc817ce32210953037" } diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index f0b2c188693..8cb766a4d2c 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -12,6 +12,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ ] } dapi-grpc = { path = "../dapi-grpc", default-features = false } rs-dapi-client = { path = "../rs-dapi-client", default-features = false } +dash-network-seeds = { workspace = true } drive = { path = "../rs-drive", default-features = false, features = [ "verify", ] } diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 74a5bfead1d..cad1f5e5103 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -64,46 +64,39 @@ const DEFAULT_REQUEST_SETTINGS: RequestSettings = RequestSettings { max_decoding_message_size: None, }; -const DEFAULT_MAINNET_ADDRESSES: &[&str] = &[ - "https://149.28.241.190:443", - "https://198.7.115.48:443", - "https://134.255.182.186:443", - "https://93.115.172.39:443", - "https://5.189.164.253:443", - "https://178.215.237.134:443", - "https://157.66.81.162:443", - "https://173.212.232.90:443", -]; - -const DEFAULT_TESTNET_ADDRESSES: &[&str] = &[ - "https://52.12.176.90:1443", - "https://35.82.197.197:1443", - "https://44.240.98.102:1443", - "https://52.34.144.50:1443", - "https://44.239.39.153:1443", - "https://34.214.48.68:1443", - "https://35.164.23.245:1443", - "https://54.149.33.167:1443", - "https://52.24.124.162:1443", -]; - -fn parse_address_list(addresses: &[&str]) -> AddressList { - AddressList::from_iter(addresses.iter().map(|address| { - let uri = address - .parse::() - .unwrap_or_else(|_| panic!("default SDK address must be a valid URI: {address}")); - Address::try_from(uri).unwrap_or_else(|_| { - panic!("default SDK address must be a valid DAPI address: {address}") - }) - })) -} - +/// Build the default DAPI bootstrap address list for `network` from +/// [`dash_network_seeds`]. +/// +/// The seed lists are single-source-of-truth, weekly-refreshed upstream in +/// `rust-dashcore`. We filter to Evo (HPMN) masternodes — the only ones that +/// run Dash Platform — and build `https://:` URIs. +/// The Core port on `seed.address` is intentionally discarded: DAPI clients +/// need the platform HTTP port, not the Core P2P port. +/// +/// Malformed upstream entries are silently skipped rather than panicking; +/// the DAPI client handles retry/rotation across the remaining addresses. +/// +/// ## Panics +/// +/// Panics on networks other than `Mainnet` and `Testnet` — no upstream +/// seed list exists for devnet/regtest. fn default_address_list_for_network(network: Network) -> AddressList { - match network { - Network::Mainnet => parse_address_list(DEFAULT_MAINNET_ADDRESSES), - Network::Testnet => parse_address_list(DEFAULT_TESTNET_ADDRESSES), - _ => panic!("default address list is only available for mainnet and testnet"), + if !matches!(network, Network::Mainnet | Network::Testnet) { + panic!("default address list is only available for mainnet and testnet"); } + let mut list = AddressList::new(); + for seed in dash_network_seeds::evo_seeds(network) { + let Some(port) = seed.platform_http_port else { + continue; + }; + let url = format!("https://{}:{}", seed.address.ip(), port); + if let Ok(uri) = url.parse::() { + if let Ok(address) = Address::try_from(uri) { + list.add(address); + } + } + } + list } /// Dash Platform SDK @@ -1137,7 +1130,6 @@ pub fn prettify_proof(proof: &Proof) -> String { #[cfg(test)] mod test { - use std::collections::BTreeSet; use std::sync::Arc; use dapi_grpc::platform::v0::{GetIdentityRequest, ResponseMetadata}; @@ -1146,26 +1138,15 @@ mod test { use crate::SdkBuilder; - use super::{AddressList, Network, DEFAULT_MAINNET_ADDRESSES, DEFAULT_TESTNET_ADDRESSES}; + use super::Network; - fn live_address_set(address_list: &AddressList) -> BTreeSet { - address_list - .get_live_addresses() - .into_iter() - .map(|address| address.to_string()) - .collect() - } - - fn expected_address_set(addresses: &[&str]) -> BTreeSet { - super::parse_address_list(addresses) - .get_live_addresses() - .into_iter() - .map(|address| address.to_string()) - .collect() - } + /// Mainnet Evo masternodes expose the Platform HTTP endpoint on 443. + const MAINNET_PLATFORM_HTTP_PORT: u16 = 443; + /// Testnet Evo masternodes expose the Platform HTTP endpoint on 1443. + const TESTNET_PLATFORM_HTTP_PORT: u16 = 1443; #[test] - fn new_testnet_uses_default_testnet_addresses() { + fn new_testnet_sources_bootstrap_from_seeds() { let builder = SdkBuilder::new_testnet(); let address_list = builder .addresses @@ -1173,15 +1154,21 @@ mod test { .expect("testnet builder should configure default addresses"); assert_eq!(builder.network, Network::Testnet); - assert_eq!(address_list.len(), DEFAULT_TESTNET_ADDRESSES.len()); - assert_eq!( - live_address_set(address_list), - expected_address_set(DEFAULT_TESTNET_ADDRESSES) + assert!( + !address_list.is_empty(), + "testnet must have at least one bootstrap address" ); + for address in address_list.get_live_addresses() { + assert_eq!( + address.uri().port_u16(), + Some(TESTNET_PLATFORM_HTTP_PORT), + "testnet bootstrap address must use the platform HTTP port", + ); + } } #[test] - fn new_mainnet_uses_default_mainnet_addresses() { + fn new_mainnet_sources_bootstrap_from_seeds() { let builder = SdkBuilder::new_mainnet(); let address_list = builder .addresses @@ -1189,10 +1176,39 @@ mod test { .expect("mainnet builder should configure default addresses"); assert_eq!(builder.network, Network::Mainnet); - assert_eq!(address_list.len(), DEFAULT_MAINNET_ADDRESSES.len()); - assert_eq!( - live_address_set(address_list), - expected_address_set(DEFAULT_MAINNET_ADDRESSES) + assert!( + !address_list.is_empty(), + "mainnet must have at least one bootstrap address" + ); + for address in address_list.get_live_addresses() { + assert_eq!( + address.uri().port_u16(), + Some(MAINNET_PLATFORM_HTTP_PORT), + "mainnet bootstrap address must use the platform HTTP port", + ); + } + } + + /// Smoke signal: the upstream seed lists are far larger than 10 entries on + /// both networks. If parsing drops most of them we want a loud test + /// failure rather than silently shipping a near-empty bootstrap list. + #[test] + fn bootstrap_counts_reasonable() { + let mainnet = SdkBuilder::new_mainnet() + .addresses + .expect("mainnet builder should configure default addresses"); + let testnet = SdkBuilder::new_testnet() + .addresses + .expect("testnet builder should configure default addresses"); + assert!( + mainnet.len() >= 10, + "expected >=10 mainnet bootstrap addresses, got {}", + mainnet.len() + ); + assert!( + testnet.len() >= 10, + "expected >=10 testnet bootstrap addresses, got {}", + testnet.len() ); }