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-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/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 112f3327100..cad1f5e5103 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,41 @@ const DEFAULT_REQUEST_SETTINGS: RequestSettings = RequestSettings { max_decoding_message_size: None, }; +/// 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 { + 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 /// /// This is the main entry point for interacting with Dash Platform. @@ -747,18 +783,18 @@ 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); + + 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 +807,9 @@ 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); + + Self::new(address_list).with_network(Network::Mainnet) } /// Configure network type. @@ -1102,6 +1138,80 @@ mod test { use crate::SdkBuilder; + use super::Network; + + /// 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_sources_bootstrap_from_seeds() { + 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!( + !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_sources_bootstrap_from_seeds() { + 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!( + !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() + ); + } + #[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,