use crate::{NetworkParameters, ProtocolVersion, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NetworkConstants {
pub magic_bytes: [u8; 4],
pub default_port: u16,
pub genesis_hash: [u8; 32],
pub max_target: u32,
pub halving_interval: u64,
pub network_name: String,
pub is_testnet: bool,
pub dns_seeds: Vec<String>,
pub checkpoints: Vec<Checkpoint>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Checkpoint {
pub height: u64,
pub hash: [u8; 32],
pub timestamp: u64,
}
impl NetworkConstants {
pub fn for_version(version: ProtocolVersion) -> Result<Self> {
match version {
ProtocolVersion::BitcoinV1 => Self::mainnet(),
ProtocolVersion::Testnet3 => Self::testnet(),
ProtocolVersion::Regtest => Self::regtest(),
}
}
pub fn mainnet() -> Result<Self> {
Ok(Self {
magic_bytes: [0xf9, 0xbe, 0xb4, 0xd9], default_port: 8333,
genesis_hash: [
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63,
0xf7, 0x4f, 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, 0x68, 0xd6, 0x19, 0x00,
0x00, 0x00, 0x00, 0x00,
],
max_target: 0x1d00ffff,
halving_interval: 210000,
network_name: "mainnet".to_string(),
is_testnet: false,
dns_seeds: vec![
"seed.bitcoin.sipa.be".to_string(),
"dnsseed.bluematt.me".to_string(),
"dnsseed.bitcoin.dashjr.org".to_string(),
"seed.bitcoinstats.com".to_string(),
"seed.bitcoin.jonasschnelli.ch".to_string(),
"seed.btc.petertodd.org".to_string(),
],
checkpoints: Self::mainnet_checkpoints(),
})
}
pub fn testnet() -> Result<Self> {
Ok(Self {
magic_bytes: [0x0b, 0x11, 0x09, 0x07], default_port: 18333,
genesis_hash: [
0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71, 0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce,
0xc3, 0xae, 0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad, 0x01, 0xea, 0x33, 0x09,
0x00, 0x00, 0x00, 0x00,
],
max_target: 0x1d00ffff,
halving_interval: 210000,
network_name: "testnet".to_string(),
is_testnet: true,
dns_seeds: vec![
"testnet-seed.bitcoin.jonasschnelli.ch".to_string(),
"seed.tbtc.petertodd.org".to_string(),
"seed.testnet.bitcoin.sprovoost.nl".to_string(),
"testnet-seed.bluematt.me".to_string(),
],
checkpoints: Self::testnet_checkpoints(),
})
}
pub fn regtest() -> Result<Self> {
Ok(Self {
magic_bytes: [0xfa, 0xbf, 0xb5, 0xda], default_port: 18444,
genesis_hash: [
0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb,
0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c,
0xf1, 0x88, 0x91, 0x0f,
],
max_target: 0x207fffff, halving_interval: 150, network_name: "regtest".to_string(),
is_testnet: true,
dns_seeds: vec![], checkpoints: vec![], })
}
fn mainnet_checkpoints() -> Vec<Checkpoint> {
vec![
Checkpoint {
height: 11111,
hash: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
timestamp: 1231006505,
},
]
}
fn testnet_checkpoints() -> Vec<Checkpoint> {
vec![
Checkpoint {
height: 11111,
hash: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
timestamp: 1296688602,
},
]
}
}
impl NetworkParameters {
pub fn from_constants(constants: &NetworkConstants) -> Result<Self> {
Ok(NetworkParameters {
magic_bytes: constants.magic_bytes,
default_port: constants.default_port,
genesis_block: crate::genesis::mainnet_genesis(),
max_target: constants.max_target,
halving_interval: constants.halving_interval,
network_name: constants.network_name.clone(),
is_testnet: constants.is_testnet,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_constants() {
let mainnet = NetworkConstants::mainnet().unwrap();
assert_eq!(mainnet.magic_bytes, [0xf9, 0xbe, 0xb4, 0xd9]);
assert_eq!(mainnet.default_port, 8333);
assert!(!mainnet.is_testnet);
assert!(!mainnet.dns_seeds.is_empty());
let testnet = NetworkConstants::testnet().unwrap();
assert_eq!(testnet.magic_bytes, [0x0b, 0x11, 0x09, 0x07]);
assert_eq!(testnet.default_port, 18333);
assert!(testnet.is_testnet);
let regtest = NetworkConstants::regtest().unwrap();
assert_eq!(regtest.magic_bytes, [0xfa, 0xbf, 0xb5, 0xda]);
assert_eq!(regtest.default_port, 18444);
assert!(regtest.is_testnet);
assert!(regtest.dns_seeds.is_empty());
}
#[test]
fn test_network_parameters_from_constants() {
let constants = NetworkConstants::mainnet().unwrap();
let params = NetworkParameters::from_constants(&constants).unwrap();
assert_eq!(params.magic_bytes, constants.magic_bytes);
assert_eq!(params.default_port, constants.default_port);
assert_eq!(params.network_name, constants.network_name);
assert_eq!(params.is_testnet, constants.is_testnet);
}
#[test]
fn test_network_constants_for_version() {
let mainnet = NetworkConstants::for_version(ProtocolVersion::BitcoinV1).unwrap();
assert_eq!(mainnet.network_name, "mainnet");
assert!(!mainnet.is_testnet);
let testnet = NetworkConstants::for_version(ProtocolVersion::Testnet3).unwrap();
assert_eq!(testnet.network_name, "testnet");
assert!(testnet.is_testnet);
let regtest = NetworkConstants::for_version(ProtocolVersion::Regtest).unwrap();
assert_eq!(regtest.network_name, "regtest");
assert!(regtest.is_testnet);
}
#[test]
fn test_genesis_hashes() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert_ne!(mainnet.genesis_hash, testnet.genesis_hash);
assert_ne!(testnet.genesis_hash, regtest.genesis_hash);
assert_ne!(mainnet.genesis_hash, regtest.genesis_hash);
assert_ne!(mainnet.genesis_hash, [0u8; 32]);
assert_ne!(testnet.genesis_hash, [0u8; 32]);
assert_ne!(regtest.genesis_hash, [0u8; 32]);
}
#[test]
fn test_dns_seeds() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert!(!mainnet.dns_seeds.is_empty());
assert!(mainnet
.dns_seeds
.iter()
.any(|seed| seed.contains("bitcoin")));
assert!(!testnet.dns_seeds.is_empty());
assert!(testnet
.dns_seeds
.iter()
.any(|seed| seed.contains("testnet")));
assert!(regtest.dns_seeds.is_empty());
}
#[test]
fn test_checkpoints() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert!(!mainnet.checkpoints.is_empty());
assert!(!testnet.checkpoints.is_empty());
assert!(regtest.checkpoints.is_empty());
for checkpoints in [&mainnet.checkpoints, &testnet.checkpoints] {
for i in 1..checkpoints.len() {
assert!(checkpoints[i].height > checkpoints[i - 1].height);
}
}
}
#[test]
fn test_max_targets() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert_eq!(mainnet.max_target, 0x1d00ffff);
assert_eq!(testnet.max_target, 0x1d00ffff);
assert_eq!(regtest.max_target, 0x207fffff);
assert!(regtest.max_target > mainnet.max_target);
}
#[test]
fn test_halving_intervals() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert_eq!(mainnet.halving_interval, 210000);
assert_eq!(testnet.halving_interval, 210000);
assert_eq!(regtest.halving_interval, 150);
assert!(regtest.halving_interval < mainnet.halving_interval);
}
#[test]
fn test_network_constants_serialization() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
for constants in [mainnet, testnet, regtest] {
let json = serde_json::to_string(&constants).unwrap();
let deserialized: NetworkConstants = serde_json::from_str(&json).unwrap();
assert_eq!(constants.magic_bytes, deserialized.magic_bytes);
assert_eq!(constants.default_port, deserialized.default_port);
assert_eq!(constants.genesis_hash, deserialized.genesis_hash);
assert_eq!(constants.max_target, deserialized.max_target);
assert_eq!(constants.halving_interval, deserialized.halving_interval);
assert_eq!(constants.network_name, deserialized.network_name);
assert_eq!(constants.is_testnet, deserialized.is_testnet);
assert_eq!(constants.dns_seeds, deserialized.dns_seeds);
assert_eq!(constants.checkpoints, deserialized.checkpoints);
}
}
#[test]
fn test_checkpoint_serialization() {
let checkpoint = Checkpoint {
height: 1000,
hash: [
0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
0x01, 0x02, 0x03, 0x04,
],
timestamp: 1234567890,
};
let json = serde_json::to_string(&checkpoint).unwrap();
let deserialized: Checkpoint = serde_json::from_str(&json).unwrap();
assert_eq!(checkpoint.height, deserialized.height);
assert_eq!(checkpoint.hash, deserialized.hash);
assert_eq!(checkpoint.timestamp, deserialized.timestamp);
}
#[test]
fn test_network_constants_equality() {
let mainnet1 = NetworkConstants::mainnet().unwrap();
let mainnet2 = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
assert_eq!(mainnet1, mainnet2);
assert_ne!(mainnet1, testnet);
}
#[test]
fn test_checkpoint_equality() {
let checkpoint1 = Checkpoint {
height: 1000,
hash: [0x01; 32],
timestamp: 1234567890,
};
let checkpoint2 = Checkpoint {
height: 1000,
hash: [0x01; 32],
timestamp: 1234567890,
};
let checkpoint3 = Checkpoint {
height: 1001,
hash: [0x01; 32],
timestamp: 1234567890,
};
assert_eq!(checkpoint1, checkpoint2);
assert_ne!(checkpoint1, checkpoint3);
}
#[test]
fn test_network_parameters_consistency() {
let mainnet_constants = NetworkConstants::mainnet().unwrap();
let mainnet_params = NetworkParameters::from_constants(&mainnet_constants).unwrap();
assert_eq!(mainnet_params.magic_bytes, mainnet_constants.magic_bytes);
assert_eq!(mainnet_params.default_port, mainnet_constants.default_port);
assert_eq!(mainnet_params.max_target, mainnet_constants.max_target);
assert_eq!(
mainnet_params.halving_interval,
mainnet_constants.halving_interval
);
assert_eq!(mainnet_params.network_name, mainnet_constants.network_name);
assert_eq!(mainnet_params.is_testnet, mainnet_constants.is_testnet);
}
#[test]
fn test_all_networks_have_unique_magic_bytes() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert_ne!(mainnet.magic_bytes, testnet.magic_bytes);
assert_ne!(testnet.magic_bytes, regtest.magic_bytes);
assert_ne!(mainnet.magic_bytes, regtest.magic_bytes);
}
#[test]
fn test_all_networks_have_unique_ports() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert_ne!(mainnet.default_port, testnet.default_port);
assert_ne!(testnet.default_port, regtest.default_port);
assert_ne!(mainnet.default_port, regtest.default_port);
}
#[test]
fn test_network_names() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert_eq!(mainnet.network_name, "mainnet");
assert_eq!(testnet.network_name, "testnet");
assert_eq!(regtest.network_name, "regtest");
}
#[test]
fn test_testnet_flags() {
let mainnet = NetworkConstants::mainnet().unwrap();
let testnet = NetworkConstants::testnet().unwrap();
let regtest = NetworkConstants::regtest().unwrap();
assert!(!mainnet.is_testnet);
assert!(testnet.is_testnet);
assert!(regtest.is_testnet);
}
}