From 70893b166a3663e12d648c8e3918536b03656725 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 10 Feb 2025 23:10:29 -0500 Subject: [PATCH] correctly serialize testnet params into config --- zebra-chain/src/parameters/network/subsidy.rs | 2 +- zebra-chain/src/parameters/network/testnet.rs | 82 ++++++++- zebra-network/src/config.rs | 155 ++++++++++++------ zebra-network/src/config/tests/vectors.rs | 19 +++ 4 files changed, 204 insertions(+), 54 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index df3d8c966f4..2fd60a5afbc 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -54,7 +54,7 @@ pub(crate) const FIRST_HALVING_TESTNET: Height = Height(1_116_000); const FIRST_HALVING_REGTEST: Height = Height(287); /// The funding stream receiver categories. -#[derive(Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum FundingStreamReceiver { /// The Electric Coin Company (Bootstrap Foundation) funding stream. #[serde(rename = "ECC")] diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 6045a7e2581..2b7fc4920b3 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -1,5 +1,5 @@ //! Types and implementation for Testnet consensus parameters -use std::{collections::BTreeMap, fmt}; +use std::{collections::BTreeMap, fmt, sync::Arc}; use crate::{ block::{self, Height, HeightDiff}, @@ -57,7 +57,7 @@ const TESTNET_GENESIS_HASH: &str = const PRE_BLOSSOM_REGTEST_HALVING_INTERVAL: HeightDiff = 144; /// Configurable funding stream recipient for configured Testnets. -#[derive(Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct ConfiguredFundingStreamRecipient { /// Funding stream receiver, see [`FundingStreams::recipients`] for more details. @@ -79,7 +79,7 @@ impl ConfiguredFundingStreamRecipient { } /// Configurable funding streams for configured Testnets. -#[derive(Deserialize, Clone, Default, Debug)] +#[derive(Serialize, Deserialize, Clone, Default, Debug)] #[serde(deny_unknown_fields)] pub struct ConfiguredFundingStreams { /// Start and end height for funding streams see [`FundingStreams::height_range`] for more details. @@ -88,6 +88,71 @@ pub struct ConfiguredFundingStreams { pub recipients: Option>, } +impl From<&FundingStreams> for ConfiguredFundingStreams { + fn from(value: &FundingStreams) -> Self { + Self { + height_range: Some(value.height_range().clone()), + recipients: Some( + value + .recipients() + .iter() + .map(|(receiver, recipient)| ConfiguredFundingStreamRecipient { + receiver: *receiver, + numerator: recipient.numerator(), + addresses: Some( + recipient + .addresses() + .iter() + .map(ToString::to_string) + .collect(), + ), + }) + .collect(), + ), + } + } +} + +impl From<&BTreeMap> for ConfiguredActivationHeights { + fn from(activation_heights: &BTreeMap) -> Self { + let mut configured_activation_heights = ConfiguredActivationHeights::default(); + + for (height, network_upgrade) in activation_heights.iter() { + match network_upgrade { + NetworkUpgrade::BeforeOverwinter => { + configured_activation_heights.before_overwinter = Some(height.0); + } + NetworkUpgrade::Overwinter => { + configured_activation_heights.overwinter = Some(height.0); + } + NetworkUpgrade::Sapling => { + configured_activation_heights.sapling = Some(height.0); + } + NetworkUpgrade::Blossom => { + configured_activation_heights.blossom = Some(height.0); + } + NetworkUpgrade::Heartwood => { + configured_activation_heights.heartwood = Some(height.0); + } + NetworkUpgrade::Canopy => { + configured_activation_heights.canopy = Some(height.0); + } + NetworkUpgrade::Nu5 => { + configured_activation_heights.nu5 = Some(height.0); + } + NetworkUpgrade::Nu6 => { + configured_activation_heights.nu6 = Some(height.0); + } + NetworkUpgrade::Genesis => { + continue; + } + } + } + + configured_activation_heights + } +} + impl ConfiguredFundingStreams { /// Returns an empty [`ConfiguredFundingStreams`]. fn empty() -> Self { @@ -185,7 +250,7 @@ fn check_funding_stream_address_period(funding_streams: &FundingStreams, network } /// Configurable activation heights for Regtest and configured Testnets. -#[derive(Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Default, Clone)] #[serde(rename_all = "PascalCase", deny_unknown_fields)] pub struct ConfiguredActivationHeights { /// Activation height for `BeforeOverwinter` network upgrade. @@ -754,6 +819,15 @@ impl Parameters { } impl Network { + /// Returns the parameters of this network if it is a Testnet. + pub fn parameters(&self) -> Option> { + if let Self::Testnet(parameters) = self { + Some(parameters.clone()) + } else { + None + } + } + /// Returns true if proof-of-work validation should be disabled for this network pub fn disable_pow(&self) -> bool { if let Self::Testnet(params) = self { diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 8619507fa0d..b401a5d4c93 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -4,6 +4,7 @@ use std::{ collections::HashSet, io::{self, ErrorKind}, net::{IpAddr, SocketAddr}, + sync::Arc, time::Duration, }; @@ -51,7 +52,7 @@ const MAX_SINGLE_SEED_PEER_DNS_RETRIES: usize = 0; /// Configuration for networking code. #[derive(Clone, Debug, Eq, PartialEq, Serialize)] -#[serde(deny_unknown_fields, default)] +#[serde(deny_unknown_fields, default, into = "DConfig")] pub struct Config { /// The address on which this node should listen for connections. /// @@ -580,60 +581,116 @@ impl Default for Config { } } -impl<'de> Deserialize<'de> for Config { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(deny_unknown_fields)] - struct DTestnetParameters { - network_name: Option, - network_magic: Option<[u8; 4]>, - slow_start_interval: Option, - target_difficulty_limit: Option, - disable_pow: Option, - genesis_hash: Option, - activation_heights: Option, - pre_nu6_funding_streams: Option, - post_nu6_funding_streams: Option, - pre_blossom_halving_interval: Option, +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct DTestnetParameters { + network_name: Option, + network_magic: Option<[u8; 4]>, + slow_start_interval: Option, + target_difficulty_limit: Option, + disable_pow: Option, + genesis_hash: Option, + activation_heights: Option, + pre_nu6_funding_streams: Option, + post_nu6_funding_streams: Option, + pre_blossom_halving_interval: Option, +} + +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields, default)] +struct DConfig { + listen_addr: String, + external_addr: Option, + network: NetworkKind, + testnet_parameters: Option, + initial_mainnet_peers: IndexSet, + initial_testnet_peers: IndexSet, + cache_dir: CacheDir, + peerset_initial_target_size: usize, + #[serde(alias = "new_peer_interval", with = "humantime_serde")] + crawl_new_peer_interval: Duration, + max_connections_per_ip: Option, +} + +impl Default for DConfig { + fn default() -> Self { + let config = Config::default(); + Self { + listen_addr: "0.0.0.0".to_string(), + external_addr: None, + network: Default::default(), + testnet_parameters: None, + initial_mainnet_peers: config.initial_mainnet_peers, + initial_testnet_peers: config.initial_testnet_peers, + cache_dir: config.cache_dir, + peerset_initial_target_size: config.peerset_initial_target_size, + crawl_new_peer_interval: config.crawl_new_peer_interval, + max_connections_per_ip: Some(config.max_connections_per_ip), } + } +} - #[derive(Deserialize)] - #[serde(deny_unknown_fields, default)] - struct DConfig { - listen_addr: String, - external_addr: Option, - network: NetworkKind, - testnet_parameters: Option, - initial_mainnet_peers: IndexSet, - initial_testnet_peers: IndexSet, - cache_dir: CacheDir, - peerset_initial_target_size: usize, - #[serde(alias = "new_peer_interval", with = "humantime_serde")] - crawl_new_peer_interval: Duration, - max_connections_per_ip: Option, +impl From> for DTestnetParameters { + fn from(params: Arc) -> Self { + Self { + network_name: Some(params.network_name().to_string()), + network_magic: Some(params.network_magic().0), + slow_start_interval: Some(params.slow_start_interval().0), + target_difficulty_limit: Some(params.target_difficulty_limit().to_string()), + disable_pow: Some(params.disable_pow()), + genesis_hash: Some(params.genesis_hash().to_string()), + activation_heights: Some(params.activation_heights().into()), + pre_nu6_funding_streams: Some(params.pre_nu6_funding_streams().into()), + post_nu6_funding_streams: Some(params.post_nu6_funding_streams().into()), + pre_blossom_halving_interval: Some( + params + .pre_blossom_halving_interval() + .try_into() + .expect("should convert"), + ), } + } +} - impl Default for DConfig { - fn default() -> Self { - let config = Config::default(); - Self { - listen_addr: "0.0.0.0".to_string(), - external_addr: None, - network: Default::default(), - testnet_parameters: None, - initial_mainnet_peers: config.initial_mainnet_peers, - initial_testnet_peers: config.initial_testnet_peers, - cache_dir: config.cache_dir, - peerset_initial_target_size: config.peerset_initial_target_size, - crawl_new_peer_interval: config.crawl_new_peer_interval, - max_connections_per_ip: Some(config.max_connections_per_ip), - } - } +impl From for DConfig { + fn from( + Config { + listen_addr, + external_addr, + network, + initial_mainnet_peers, + initial_testnet_peers, + cache_dir, + peerset_initial_target_size, + crawl_new_peer_interval, + max_connections_per_ip, + }: Config, + ) -> Self { + let testnet_parameters = network + .parameters() + .filter(|params| !params.is_default_testnet() && !params.is_regtest()) + .map(Into::into); + + DConfig { + listen_addr: listen_addr.to_string(), + external_addr: external_addr.map(|addr| addr.to_string()), + network: network.into(), + testnet_parameters, + initial_mainnet_peers, + initial_testnet_peers, + cache_dir, + peerset_initial_target_size, + crawl_new_peer_interval, + max_connections_per_ip: Some(max_connections_per_ip), } + } +} +impl<'de> Deserialize<'de> for Config { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { let DConfig { listen_addr, external_addr, diff --git a/zebra-network/src/config/tests/vectors.rs b/zebra-network/src/config/tests/vectors.rs index 6927698338f..3cef8c2df91 100644 --- a/zebra-network/src/config/tests/vectors.rs +++ b/zebra-network/src/config/tests/vectors.rs @@ -1,6 +1,7 @@ //! Fixed test vectors for zebra-network configuration. use static_assertions::const_assert; +use zebra_chain::parameters::testnet; use crate::{ constants::{INBOUND_PEER_LIMIT_MULTIPLIER, OUTBOUND_PEER_LIMIT_MULTIPLIER}, @@ -46,3 +47,21 @@ fn ensure_peer_connection_limits_consistent() { "default config should allow more inbound connections, to avoid connection exhaustion", ); } + +#[test] +fn testnet_params_serialization_roundtrip() { + let _init_guard = zebra_test::init(); + + let config = Config { + network: testnet::Parameters::build() + .with_disable_pow(true) + .to_network(), + initial_testnet_peers: [].into(), + ..Config::default() + }; + + let serialized = toml::to_string(&config).unwrap(); + let deserialized: Config = toml::from_str(&serialized).unwrap(); + + assert_eq!(config, deserialized); +}