Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ci-groups.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ groups:
- dashcore
- dashcore_hashes
- dashcore-private
- dash-network

spv:
- dash-spv
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["dash", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-manager", "key-wallet-ffi", "dash-spv", "dash-spv-ffi"]
members = ["dash", "dash-network", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-manager", "key-wallet-ffi", "dash-spv", "dash-spv-ffi"]
resolver = "2"

[workspace.package]
Expand Down
29 changes: 29 additions & 0 deletions dash-network/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "dash-network"
version = { workspace = true }
edition = "2024"
authors = ["Dash Core Team"]
description = "The `Network` enum and its pure helpers (magic bytes, activation heights, default P2P ports) extracted from `dashcore` into a minimal-dependency crate so that lightweight consumers can depend on it without pulling in the full protocol library."
license = "MIT"
repository = "https://github.com/dashpay/rust-dashcore"

[lib]
name = "dash_network"
path = "src/lib.rs"

[features]
default = []
# serde::{Serialize, Deserialize} impls for Network.
serde = ["dep:serde"]
# bincode::{Encode, Decode} impls for Network.
bincode = ["dep:bincode", "dep:bincode_derive"]
# C-ABI `FFINetwork` mirror + `dashcore_network_get_name` extern fn.
ffi = []

[dependencies]
serde = { version = "1.0.219", default-features = false, features = ["derive"], optional = true }
bincode = { version = "2.0.1", optional = true }
bincode_derive = { version = "2.0.1", optional = true }

[build-dependencies]
cbindgen = "0.29"
37 changes: 37 additions & 0 deletions dash-network/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::{env, fs, path::Path};

fn main() {
if std::env::var("CARGO_FEATURE_FFI").is_ok() {
generate_bindings();
}
}

fn generate_bindings() {
let crate_name = env::var("CARGO_PKG_NAME").unwrap();
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let out_dir = env::var("OUT_DIR").unwrap();

println!("cargo:rerun-if-changed=cbindgen.toml");
println!("cargo:rerun-if-changed=src/");

let target_dir = Path::new(&out_dir)
.ancestors()
.nth(3) // This line moves up to the target/<PROFILE> directory
.expect("Failed to find target dir");

let include_dir = target_dir.join("include").join(&crate_name);

fs::create_dir_all(&include_dir).unwrap();

let output_path = include_dir.join(format!("{}.h", &crate_name));

let config_path = Path::new(&crate_dir).join("cbindgen.toml");
let config = cbindgen::Config::from_file(&config_path).expect("Failed to read cbindgen.toml");

cbindgen::Builder::new()
.with_crate(&crate_dir)
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file(&output_path);
}
2 changes: 1 addition & 1 deletion dash/cbindgen.toml → dash-network/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language = "C"
header = "/* dashcore C bindings - Auto-generated by cbindgen */"
include_guard = "DASHCORE_H"
include_guard = "DASH_NETWORK_H"
autogen_warning = "/* Warning: This file is auto-generated by cbindgen. Do not modify manually. */"
include_version = true

Expand Down
4 changes: 4 additions & 0 deletions dash/src/ffi/network.rs → dash-network/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ impl From<FFINetwork> for Network {
}
}

/// Return a pointer to the canonical lowercase name of `network`.
///
/// The returned pointer is to a static null-terminated string owned by
/// `dash-network`; callers must not free it.
#[unsafe(no_mangle)]
pub extern "C" fn dashcore_network_get_name(network: FFINetwork) -> *const ffi::c_char {
match network {
Expand Down
219 changes: 219 additions & 0 deletions dash-network/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
//! The Dash [`Network`] enum and its pure helpers, extracted from the main
//! `dashcore` crate so that lightweight consumers can depend on it without
//! pulling in the full protocol library.
//!
//! This crate carries **no** dependencies beyond its optional `serde` /
//! `bincode` impls. If you need Network::known_genesis_block_hash — which
//! returns a `BlockHash` — use the extension trait provided by `dashcore`.
Comment thread
ZocoLini marked this conversation as resolved.
//!
//! # Example
//!
//! ```rust
//! use dash_network::Network;
//!
//! assert_eq!(Network::Mainnet.magic(), 0xBD6B0CBF);
//! assert_eq!(Network::from_magic(0xBD6B0CBF), Some(Network::Mainnet));
//! assert_eq!("testnet".parse::<Network>().unwrap(), Network::Testnet);
//! assert_eq!(Network::Mainnet.default_p2p_port(), 9999);
//! ```

use core::fmt;

#[cfg(feature = "ffi")]
pub mod ffi;

#[cfg(feature = "bincode")]
use bincode_derive::{Decode, Encode};

/// The Dash network to act on.
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[repr(u8)]
pub enum Network {
/// Dash mainnet, the production network for real transactions.
Mainnet,
/// Dash public test network for protocol-level testing without real funds.
Testnet,
/// Dash development network, an isolated environment for feature development and testing.
Devnet,
/// Local regression testing network for deterministic, offline testing with instant block generation.
Regtest,
}
Comment thread
ZocoLini marked this conversation as resolved.

impl Network {
/// Creates a `Network` from the network magic bytes.
///
/// # Examples
///
/// ```rust
/// use dash_network::Network;
///
/// assert_eq!(Some(Network::Mainnet), Network::from_magic(0xBD6B0CBF));
/// assert_eq!(None, Network::from_magic(0xFFFFFFFF));
/// ```
pub const fn from_magic(magic: u32) -> Option<Network> {
// Note: any new entries here must be added to `magic` below.
match magic {
0xBD6B0CBF => Some(Network::Mainnet),
0xFFCAE2CE => Some(Network::Testnet),
0xCEFFCAE2 => Some(Network::Devnet),
0xDCB7C1FC => Some(Network::Regtest),
_ => None,
}
}

/// Return the network magic bytes, which should be encoded little-endian
/// at the start of every message
///
/// # Examples
///
/// ```rust
/// use dash_network::Network;
///
/// let network = Network::Mainnet;
/// assert_eq!(network.magic(), 0xBD6B0CBF);
/// ```
Comment thread
ZocoLini marked this conversation as resolved.
pub const fn magic(self) -> u32 {
// Note: any new entries here must be added to `from_magic` above.
match self {
Network::Mainnet => 0xBD6B0CBF,
Network::Testnet => 0xFFCAE2CE,
Network::Devnet => 0xCEFFCAE2,
Network::Regtest => 0xDCB7C1FC,
}
}

/// The block height at which Dash consensus version 20 activates.
///
/// Devnet and regtest activate V20 immediately (height 0).
pub const fn v20_activation_height(self) -> u32 {
match self {
Network::Mainnet => 1_987_776,
Network::Testnet => 905_100,
// Devnet and regtest activate V20 immediately.
Network::Devnet | Network::Regtest => 0,
}
}

/// The default P2P port for this network.
///
/// Regtest's default is the typical Dash Core regtest value; devnets can
/// vary and should usually come from configuration.
///
/// # Examples
///
/// ```rust
/// use dash_network::Network;
///
/// assert_eq!(Network::Mainnet.default_p2p_port(), 9999);
/// assert_eq!(Network::Testnet.default_p2p_port(), 19999);
/// ```
pub const fn default_p2p_port(self) -> u16 {
match self {
Network::Mainnet => 9999,
Network::Testnet => 19999,
Network::Devnet => 19799,
Network::Regtest => 19899,
}
}
}

impl fmt::Display for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Network::Mainnet => "mainnet",
Network::Testnet => "testnet",
Network::Devnet => "devnet",
Network::Regtest => "regtest",
})
}
}

impl core::str::FromStr for Network {
type Err = ParseNetworkError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"mainnet" | "main" => Ok(Network::Mainnet),
"testnet" | "test" => Ok(Network::Testnet),
"devnet" | "dev" => Ok(Network::Devnet),
"regtest" => Ok(Network::Regtest),
_ => Err(ParseNetworkError(s.to_string())),
}
}
}

/// Error returned from Network::from_str when the input doesn't name a
/// known network.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseNetworkError(pub String);

impl fmt::Display for ParseNetworkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown network type: {}", self.0)
}
}

impl std::error::Error for ParseNetworkError {}

// ---------- Tests ----------

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn magic_round_trip_covers_all_variants() {
for n in [Network::Mainnet, Network::Testnet, Network::Devnet, Network::Regtest] {
let magic = n.magic();
assert_eq!(Network::from_magic(magic), Some(n), "round-trip failed for {:?}", n);
}
}

#[test]
fn from_magic_unknown_is_none() {
assert_eq!(Network::from_magic(0), None);
assert_eq!(Network::from_magic(0xFFFFFFFF), None);
}

#[test]
fn from_str_accepts_aliases_and_is_case_insensitive() {
assert_eq!("MAINNET".parse::<Network>().unwrap(), Network::Mainnet);
assert_eq!("main".parse::<Network>().unwrap(), Network::Mainnet);
assert_eq!("Testnet".parse::<Network>().unwrap(), Network::Testnet);
assert_eq!("test".parse::<Network>().unwrap(), Network::Testnet);
assert_eq!("devnet".parse::<Network>().unwrap(), Network::Devnet);
assert_eq!("dev".parse::<Network>().unwrap(), Network::Devnet);
assert_eq!("regtest".parse::<Network>().unwrap(), Network::Regtest);
}

#[test]
fn from_str_rejects_nonsense() {
let err = "bogus".parse::<Network>().unwrap_err();
assert_eq!(err.0, "bogus");
}

#[test]
fn display_matches_canonical_lowercase() {
assert_eq!(Network::Mainnet.to_string(), "mainnet");
assert_eq!(Network::Testnet.to_string(), "testnet");
assert_eq!(Network::Devnet.to_string(), "devnet");
assert_eq!(Network::Regtest.to_string(), "regtest");
}

#[test]
fn activation_heights_are_stable() {
assert_eq!(Network::Mainnet.v20_activation_height(), 1_987_776);
assert_eq!(Network::Testnet.v20_activation_height(), 905_100);
assert_eq!(Network::Devnet.v20_activation_height(), 0);
assert_eq!(Network::Regtest.v20_activation_height(), 0);
}

#[test]
fn default_p2p_ports_match_conventions() {
assert_eq!(Network::Mainnet.default_p2p_port(), 9999);
assert_eq!(Network::Testnet.default_p2p_port(), 19999);
}
Comment thread
ZocoLini marked this conversation as resolved.
}
3 changes: 2 additions & 1 deletion dash-spv-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ crate-type = ["cdylib", "staticlib", "rlib"]

[dependencies]
dash-spv = { path = "../dash-spv" }
dashcore = { path = "../dash", package = "dashcore", features= ["ffi"] }
dashcore = { path = "../dash" }
dash-network = { path = "../dash-network", features = ["ffi"] }
tokio = { version = "1", features = ["full"] }
tokio-util = "0.7"
hex = "0.4"
Expand Down
2 changes: 1 addition & 1 deletion dash-spv-ffi/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ include_guard = "DASH_SPV_FFI_H"
autogen_warning = "/* Warning: This file is auto-generated by cbindgen. Do not modify manually. */"
include_version = true
cpp_compat = true
includes = ["../key-wallet-ffi/key-wallet-ffi.h", "../dashcore/dashcore.h"]
includes = ["../key-wallet-ffi/key-wallet-ffi.h", "../dash-network/dash-network.h"]

[export]
include = ["FFI"]
Expand Down
3 changes: 1 addition & 2 deletions dash-spv-ffi/src/bin/ffi_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ use std::os::raw::{c_char, c_void};
use std::ptr;

use clap::{Arg, ArgAction, Command};

use dash_network::ffi::FFINetwork;
use dash_spv_ffi::*;
use dashcore::ffi::FFINetwork;
use key_wallet_ffi::managed_account::FFITransactionRecord;
use key_wallet_ffi::types::FFITransactionContext;
use key_wallet_ffi::wallet_manager::wallet_manager_add_wallet_from_mnemonic;
Expand Down
3 changes: 1 addition & 2 deletions dash-spv-ffi/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{null_check, set_last_error, FFIErrorCode, FFIMempoolStrategy};
use dash_spv::{ClientConfig, ValidationMode};
use dashcore::ffi::FFINetwork;

use dash_network::ffi::FFINetwork;
use std::ffi::CStr;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::os::raw::c_char;
Expand Down Expand Up @@ -119,7 +119,6 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer(
dashcore::Network::Testnet => 19999,
dashcore::Network::Regtest => 19899,
dashcore::Network::Devnet => 29999,
_ => 9999,
};

let addr_str = match CStr::from_ptr(addr).to_str() {
Expand Down
9 changes: 4 additions & 5 deletions dash-spv-ffi/tests/dashd_sync/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;

use super::callbacks::{
create_network_callbacks, create_sync_callbacks, create_wallet_callbacks, CallbackTracker,
};
use dash_network::ffi::FFINetwork;
use dash_spv::logging::{LogFileConfig, LoggingConfig, LoggingGuard};
use dash_spv::test_utils::{retain_test_dir, SYNC_TIMEOUT};
use dash_spv_ffi::client::{
Expand All @@ -21,7 +25,6 @@ use dash_spv_ffi::config::{
};
use dash_spv_ffi::types::FFIWalletManager as FFIWalletManagerOpaque;
use dash_spv_ffi::FFIEventCallbacks;
use dashcore::ffi::FFINetwork;
use dashcore::hashes::Hash;
use dashcore::{Address, Txid};
use key_wallet_ffi::managed_account::{
Expand All @@ -43,10 +46,6 @@ use key_wallet_ffi::{
};
use tempfile::TempDir;

use super::callbacks::{
create_network_callbacks, create_sync_callbacks, create_wallet_callbacks, CallbackTracker,
};

/// State that stays fixed across client restarts (temp dir, logging, config).
struct FixedState {
_temp_dir: TempDir,
Expand Down
Loading
Loading