//! Types and implementation for Testnet consensus parameters use std::{collections::BTreeMap, fmt}; use crate::{ block::{self, Height}, parameters::{ constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT}, network_upgrade::TESTNET_ACTIVATION_HEIGHTS, subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR}, Network, NetworkKind, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER, }, work::difficulty::{ExpandedDifficulty, U256}, }; use super::{ magic::Magic, subsidy::{ FundingStreamReceiver, FundingStreamRecipient, FundingStreams, ParameterSubsidy, FIRST_HALVING_TESTNET, POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET, PRE_NU6_FUNDING_STREAMS_MAINNET, PRE_NU6_FUNDING_STREAMS_TESTNET, }, }; /// The Regtest NU5 activation height in tests // TODO: Serialize testnet parameters in Config then remove this and use a configured NU5 activation height. #[cfg(any(test, feature = "proptest-impl"))] pub const REGTEST_NU5_ACTIVATION_HEIGHT: u32 = 100; /// Reserved network names that should not be allowed for configured Testnets. pub const RESERVED_NETWORK_NAMES: [&str; 6] = [ "Mainnet", "Testnet", "Regtest", "MainnetKind", "TestnetKind", "RegtestKind", ]; /// Maximum length for a configured network name. pub const MAX_NETWORK_NAME_LENGTH: usize = 30; /// Maximum length for a configured human-readable prefix. pub const MAX_HRP_LENGTH: usize = 30; /// The block hash of the Regtest genesis block, `zcash-cli -regtest getblockhash 0` const REGTEST_GENESIS_HASH: &str = "029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327"; /// The block hash of the Testnet genesis block, `zcash-cli -testnet getblockhash 0` const TESTNET_GENESIS_HASH: &str = "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38"; /// Used to validate number of funding stream recipient addresses on configured Testnets. struct TestnetParameterSubsidyImpl; impl ParameterSubsidy for TestnetParameterSubsidyImpl { fn height_for_first_halving(&self) -> Height { FIRST_HALVING_TESTNET } } /// Configurable funding stream recipient for configured Testnets. #[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct ConfiguredFundingStreamRecipient { /// Funding stream receiver, see [`FundingStreams::recipients`] for more details. pub receiver: FundingStreamReceiver, /// The numerator for each funding stream receiver category, see [`FundingStreamRecipient::numerator`] for more details. pub numerator: u64, /// Addresses for the funding stream recipient, see [`FundingStreamRecipient::addresses`] for more details. pub addresses: Option>, } impl ConfiguredFundingStreamRecipient { /// Converts a [`ConfiguredFundingStreamRecipient`] to a [`FundingStreamReceiver`] and [`FundingStreamRecipient`]. pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) { ( self.receiver, FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()), ) } } /// Configurable funding streams for configured Testnets. #[derive(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. pub height_range: Option>, /// Funding stream recipients, see [`FundingStreams::recipients`] for more details. pub recipients: Option>, } impl ConfiguredFundingStreams { /// Converts a [`ConfiguredFundingStreams`] to a [`FundingStreams`], using the provided default values /// if `height_range` or `recipients` are None. fn convert_with_default(self, default_funding_streams: FundingStreams) -> FundingStreams { let height_range = self .height_range .unwrap_or(default_funding_streams.height_range().clone()); let recipients = self .recipients .map(|recipients| { recipients .into_iter() .map(ConfiguredFundingStreamRecipient::into_recipient) .collect() }) .unwrap_or(default_funding_streams.recipients().clone()); assert!( height_range.start < height_range.end, "funding stream end height must be above start height" ); let funding_streams = FundingStreams::new(height_range.clone(), recipients); // check that receivers have enough addresses. let expected_min_num_addresses = 1u32.checked_add(funding_stream_address_period( height_range .end .previous() .expect("end height must be above start height and genesis height"), &TestnetParameterSubsidyImpl, )) .expect("no overflow should happen in this sum") .checked_sub(funding_stream_address_period( height_range.start, &TestnetParameterSubsidyImpl, )) .expect("no overflow should happen in this sub") as usize; for (&receiver, recipient) in funding_streams.recipients() { if receiver == FundingStreamReceiver::Deferred { // The `Deferred` receiver doesn't need any addresses. continue; } assert!( recipient.addresses().len() >= expected_min_num_addresses, "recipients must have a sufficient number of addresses for height range, \ minimum num addresses required: {expected_min_num_addresses}" ); for address in recipient.addresses() { assert_eq!( address.network_kind(), NetworkKind::Testnet, "configured funding stream addresses must be for Testnet" ); } } // check that sum of receiver numerators is valid. let sum_numerators: u64 = funding_streams .recipients() .values() .map(|r| r.numerator()) .sum(); assert!( sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR, "sum of funding stream numerators must not be \ greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}" ); funding_streams } } /// Configurable activation heights for Regtest and configured Testnets. #[derive(Deserialize, Default, Clone)] #[serde(rename_all = "PascalCase", deny_unknown_fields)] pub struct ConfiguredActivationHeights { /// Activation height for `BeforeOverwinter` network upgrade. pub before_overwinter: Option, /// Activation height for `Overwinter` network upgrade. pub overwinter: Option, /// Activation height for `Sapling` network upgrade. pub sapling: Option, /// Activation height for `Blossom` network upgrade. pub blossom: Option, /// Activation height for `Heartwood` network upgrade. pub heartwood: Option, /// Activation height for `Canopy` network upgrade. pub canopy: Option, /// Activation height for `NU5` network upgrade. #[serde(rename = "NU5")] pub nu5: Option, /// Activation height for `NU6` network upgrade. #[serde(rename = "NU6")] pub nu6: Option, } /// Builder for the [`Parameters`] struct. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParametersBuilder { /// The name of this network to be used by the `Display` trait impl. network_name: String, /// The network magic, acts as an identifier for the network. network_magic: Magic, /// The genesis block hash genesis_hash: block::Hash, /// The network upgrade activation heights for this network, see [`Parameters::activation_heights`] for more details. activation_heights: BTreeMap, /// Slow start interval for this network slow_start_interval: Height, /// Pre-NU6 funding streams for this network pre_nu6_funding_streams: FundingStreams, /// Post-NU6 funding streams for this network post_nu6_funding_streams: FundingStreams, /// Target difficulty limit for this network target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks disable_pow: bool, } impl Default for ParametersBuilder { /// Creates a [`ParametersBuilder`] with all of the default Testnet parameters except `network_name`. fn default() -> Self { Self { network_name: "UnknownTestnet".to_string(), network_magic: magics::TESTNET, // # Correctness // // `Genesis` network upgrade activation height must always be 0 activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(), genesis_hash: TESTNET_GENESIS_HASH .parse() .expect("hard-coded hash parses"), slow_start_interval: SLOW_START_INTERVAL, // Testnet PoWLimit is defined as `2^251 - 1` on page 73 of the protocol specification: // // // `zcashd` converts the PoWLimit into a compact representation before // using it to perform difficulty filter checks. // // The Zcash specification converts to compact for the default difficulty // filter, but not for testnet minimum difficulty blocks. (ZIP 205 and // ZIP 208 don't specify this conversion either.) See #1277 for details. target_difficulty_limit: ExpandedDifficulty::from((U256::one() << 251) - 1) .to_compact() .to_expanded() .expect("difficulty limits are valid expanded values"), disable_pow: false, pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), post_nu6_funding_streams: POST_NU6_FUNDING_STREAMS_TESTNET.clone(), } } } impl ParametersBuilder { /// Sets the network name to be used in the [`Parameters`] being built. pub fn with_network_name(mut self, network_name: impl fmt::Display) -> Self { self.network_name = network_name.to_string(); assert!( !RESERVED_NETWORK_NAMES.contains(&self.network_name.as_str()), "cannot use reserved network name '{network_name}' as configured Testnet name, reserved names: {RESERVED_NETWORK_NAMES:?}" ); assert!( self.network_name.len() <= MAX_NETWORK_NAME_LENGTH, "network name {network_name} is too long, must be {MAX_NETWORK_NAME_LENGTH} characters or less" ); assert!( self.network_name .chars() .all(|x| x.is_alphanumeric() || x == '_'), "network name must include only alphanumeric characters or '_'" ); self } /// Sets the network name to be used in the [`Parameters`] being built. pub fn with_network_magic(mut self, network_magic: Magic) -> Self { assert!( [magics::MAINNET, magics::REGTEST] .into_iter() .all(|reserved_magic| network_magic != reserved_magic), "network magic should be distinct from reserved network magics" ); self.network_magic = network_magic; self } /// Parses the hex-encoded block hash and sets it as the genesis hash in the [`Parameters`] being built. pub fn with_genesis_hash(mut self, genesis_hash: impl fmt::Display) -> Self { self.genesis_hash = genesis_hash .to_string() .parse() .expect("configured genesis hash must parse"); self } /// Checks that the provided network upgrade activation heights are in the correct order, then /// sets them as the new network upgrade activation heights. pub fn with_activation_heights( mut self, ConfiguredActivationHeights { before_overwinter, overwinter, sapling, blossom, heartwood, canopy, nu5, nu6, }: ConfiguredActivationHeights, ) -> Self { use NetworkUpgrade::*; // # Correctness // // These must be in order so that later network upgrades overwrite prior ones // if multiple network upgrades are configured with the same activation height. let activation_heights: BTreeMap<_, _> = before_overwinter .into_iter() .map(|h| (h, BeforeOverwinter)) .chain(overwinter.into_iter().map(|h| (h, Overwinter))) .chain(sapling.into_iter().map(|h| (h, Sapling))) .chain(blossom.into_iter().map(|h| (h, Blossom))) .chain(heartwood.into_iter().map(|h| (h, Heartwood))) .chain(canopy.into_iter().map(|h| (h, Canopy))) .chain(nu5.into_iter().map(|h| (h, Nu5))) .chain(nu6.into_iter().map(|h| (h, Nu6))) .map(|(h, nu)| (h.try_into().expect("activation height must be valid"), nu)) .collect(); let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect(); // Check that the provided network upgrade activation heights are in the same order by height as the default testnet activation heights let mut activation_heights_iter = activation_heights.iter(); for expected_network_upgrade in NETWORK_UPGRADES_IN_ORDER { if !network_upgrades.contains(&expected_network_upgrade) { continue; } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() { assert_ne!( height, Height(0), "Height(0) is reserved for the `Genesis` upgrade" ); assert!( network_upgrade == expected_network_upgrade, "network upgrades must be activated in order, the correct order is {NETWORK_UPGRADES_IN_ORDER:?}" ); } } // # Correctness // // Height(0) must be reserved for the `NetworkUpgrade::Genesis`. self.activation_heights.split_off(&Height(1)); self.activation_heights.extend(activation_heights); self } /// Sets the slow start interval to be used in the [`Parameters`] being built. pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self { self.slow_start_interval = slow_start_interval; self } /// Sets pre-NU6 funding streams to be used in the [`Parameters`] being built. pub fn with_pre_nu6_funding_streams( mut self, funding_streams: ConfiguredFundingStreams, ) -> Self { self.pre_nu6_funding_streams = funding_streams.convert_with_default(PRE_NU6_FUNDING_STREAMS_TESTNET.clone()); self } /// Sets post-NU6 funding streams to be used in the [`Parameters`] being built. pub fn with_post_nu6_funding_streams( mut self, funding_streams: ConfiguredFundingStreams, ) -> Self { self.post_nu6_funding_streams = funding_streams.convert_with_default(POST_NU6_FUNDING_STREAMS_TESTNET.clone()); self } /// Sets the target difficulty limit to be used in the [`Parameters`] being built. // TODO: Accept a hex-encoded String instead? pub fn with_target_difficulty_limit( mut self, target_difficulty_limit: impl Into, ) -> Self { self.target_difficulty_limit = target_difficulty_limit .into() .to_compact() .to_expanded() .expect("difficulty limits are valid expanded values"); self } /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built. pub fn with_disable_pow(mut self, disable_pow: bool) -> Self { self.disable_pow = disable_pow; self } /// Converts the builder to a [`Parameters`] struct pub fn finish(self) -> Parameters { let Self { network_name, network_magic, genesis_hash, activation_heights, slow_start_interval, pre_nu6_funding_streams, post_nu6_funding_streams, target_difficulty_limit, disable_pow, } = self; Parameters { network_name, network_magic, genesis_hash, activation_heights, slow_start_interval, slow_start_shift: Height(slow_start_interval.0 / 2), pre_nu6_funding_streams, post_nu6_funding_streams, target_difficulty_limit, disable_pow, } } /// Converts the builder to a configured [`Network::Testnet`] pub fn to_network(self) -> Network { Network::new_configured_testnet(self.finish()) } /// Returns true if these [`Parameters`] should be compatible with the default Testnet parameters. pub fn is_compatible_with_default_parameters(&self) -> bool { let Self { network_name: _, network_magic, genesis_hash, activation_heights, slow_start_interval, pre_nu6_funding_streams, post_nu6_funding_streams, target_difficulty_limit, disable_pow, } = Self::default(); self.activation_heights == activation_heights && self.network_magic == network_magic && self.genesis_hash == genesis_hash && self.slow_start_interval == slow_start_interval && self.pre_nu6_funding_streams == pre_nu6_funding_streams && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow } } /// Network consensus parameters for test networks such as Regtest and the default Testnet. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Parameters { /// The name of this network to be used by the `Display` trait impl. network_name: String, /// The network magic, acts as an identifier for the network. network_magic: Magic, /// The genesis block hash genesis_hash: block::Hash, /// The network upgrade activation heights for this network. /// /// Note: This value is ignored by `Network::activation_list()` when `zebra-chain` is /// compiled with the `zebra-test` feature flag AND the `TEST_FAKE_ACTIVATION_HEIGHTS` /// environment variable is set. activation_heights: BTreeMap, /// Slow start interval for this network slow_start_interval: Height, /// Slow start shift for this network, always half the slow start interval slow_start_shift: Height, /// Pre-NU6 funding streams for this network pre_nu6_funding_streams: FundingStreams, /// Post-NU6 funding streams for this network post_nu6_funding_streams: FundingStreams, /// Target difficulty limit for this network target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks disable_pow: bool, } impl Default for Parameters { /// Returns an instance of the default public testnet [`Parameters`]. fn default() -> Self { Self { network_name: "Testnet".to_string(), ..Self::build().finish() } } } impl Parameters { /// Creates a new [`ParametersBuilder`]. pub fn build() -> ParametersBuilder { ParametersBuilder::default() } /// Accepts a [`ConfiguredActivationHeights`]. /// /// Creates an instance of [`Parameters`] with `Regtest` values. pub fn new_regtest( nu5_activation_height: Option, nu6_activation_height: Option, ) -> Self { #[cfg(any(test, feature = "proptest-impl"))] let nu5_activation_height = nu5_activation_height.or(Some(100)); Self { network_name: "Regtest".to_string(), network_magic: magics::REGTEST, ..Self::build() .with_genesis_hash(REGTEST_GENESIS_HASH) // This value is chosen to match zcashd, see: .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32])) .with_disable_pow(true) .with_slow_start_interval(Height::MIN) // Removes default Testnet activation heights if not configured, // most network upgrades are disabled by default for Regtest in zcashd .with_activation_heights(ConfiguredActivationHeights { canopy: Some(1), nu5: nu5_activation_height, nu6: nu6_activation_height, ..Default::default() }) .finish() } } /// Returns true if the instance of [`Parameters`] represents the default public Testnet. pub fn is_default_testnet(&self) -> bool { self == &Self::default() } /// Returns true if the instance of [`Parameters`] represents Regtest. pub fn is_regtest(&self) -> bool { if self.network_magic != magics::REGTEST { return false; } let Self { network_name, // Already checked network magic above network_magic: _, genesis_hash, // Activation heights are configurable on Regtest activation_heights: _, slow_start_interval, slow_start_shift, pre_nu6_funding_streams, post_nu6_funding_streams, target_difficulty_limit, disable_pow, } = Self::new_regtest(None, None); self.network_name == network_name && self.genesis_hash == genesis_hash && self.slow_start_interval == slow_start_interval && self.slow_start_shift == slow_start_shift && self.pre_nu6_funding_streams == pre_nu6_funding_streams && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow } /// Returns the network name pub fn network_name(&self) -> &str { &self.network_name } /// Returns the network magic pub fn network_magic(&self) -> Magic { self.network_magic } /// Returns the genesis hash pub fn genesis_hash(&self) -> block::Hash { self.genesis_hash } /// Returns the network upgrade activation heights pub fn activation_heights(&self) -> &BTreeMap { &self.activation_heights } /// Returns slow start interval for this network pub fn slow_start_interval(&self) -> Height { self.slow_start_interval } /// Returns slow start shift for this network pub fn slow_start_shift(&self) -> Height { self.slow_start_shift } /// Returns pre-NU6 funding streams for this network pub fn pre_nu6_funding_streams(&self) -> &FundingStreams { &self.pre_nu6_funding_streams } /// Returns post-NU6 funding streams for this network pub fn post_nu6_funding_streams(&self) -> &FundingStreams { &self.post_nu6_funding_streams } /// Returns the target difficulty limit for this network pub fn target_difficulty_limit(&self) -> ExpandedDifficulty { self.target_difficulty_limit } /// Returns true if proof-of-work validation should be disabled for this network pub fn disable_pow(&self) -> bool { self.disable_pow } } impl Network { /// 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 { params.disable_pow() } else { false } } /// Returns slow start interval for this network pub fn slow_start_interval(&self) -> Height { if let Self::Testnet(params) = self { params.slow_start_interval() } else { SLOW_START_INTERVAL } } /// Returns slow start shift for this network pub fn slow_start_shift(&self) -> Height { if let Self::Testnet(params) = self { params.slow_start_shift() } else { SLOW_START_SHIFT } } /// Returns pre-NU6 funding streams for this network pub fn pre_nu6_funding_streams(&self) -> &FundingStreams { if let Self::Testnet(params) = self { params.pre_nu6_funding_streams() } else { &PRE_NU6_FUNDING_STREAMS_MAINNET } } /// Returns post-NU6 funding streams for this network pub fn post_nu6_funding_streams(&self) -> &FundingStreams { if let Self::Testnet(params) = self { params.post_nu6_funding_streams() } else { &POST_NU6_FUNDING_STREAMS_MAINNET } } /// Returns post-Canopy funding streams for this network at the provided height pub fn funding_streams(&self, height: Height) -> &FundingStreams { if NetworkUpgrade::current(self, height) < NetworkUpgrade::Nu6 { self.pre_nu6_funding_streams() } else { self.post_nu6_funding_streams() } } }