From f2e7bc95ce8904d06e3a52df49ee3feba179cb98 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Thu, 10 Oct 2024 15:26:54 -0300 Subject: [PATCH] feat(regtest): Add regtest halving interval and port test (#8888) * add halving interval to regtest and to custom testnet * add nuparams.py rpc test * fix inconsistency in nu6 name in rpc methods * rename `halving_interval` to `pre_blossom_halving_interval` in the config * make fixes * Suggestion for "feat(regtest): Add regtest halving interval and port test" (#8894) * adds `height_for_halving_index()` and `num_halvings()` fns * avoid unnecessary panic * avoid using constant pre/post blossom halving intervals in num_halvings() * make regtest and testnet constant more private * move `height_for_halving_index` * fmt * add a `funding_stream_address_change_interval` method * add checked operations to `height_for_halving_index` fn * add post_blossom interval as paramneters + other refactors * rename function * fix docs * move constant * Updates `new_regtest()` method to return a Testnet without funding streams, updates funding stream setter methods to set a flag indicating that parameters affecting the funding stream address period should be locked, updates the setter methods for parameters that affect the funding stream address period to panic if those parameters should be locked. (#8921) --------- Co-authored-by: Arya --- zebra-chain/src/parameters/network/subsidy.rs | 96 ++++++- zebra-chain/src/parameters/network/testnet.rs | 221 ++++++++++----- .../src/parameters/network/tests/vectors.rs | 2 +- zebra-chain/src/parameters/network_upgrade.rs | 1 + zebra-consensus/src/block/subsidy/general.rs | 74 +++-- zebra-network/src/config.rs | 24 +- zebra-rpc/qa/base_config.toml | 4 + zebra-rpc/qa/pull-tester/rpc-tests.py | 3 +- zebra-rpc/qa/rpc-tests/nuparams.py | 266 ++++++++++++++++++ .../get_blockchain_info@testnet_10.snap | 2 +- ..._info_future_nu6_height@nu6testnet_10.snap | 2 +- 11 files changed, 586 insertions(+), 109 deletions(-) create mode 100755 zebra-rpc/qa/rpc-tests/nuparams.py diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index c7ff373f55d..cb043f6b05c 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -48,7 +48,10 @@ pub const POST_BLOSSOM_HALVING_INTERVAL: HeightDiff = /// as specified in [protocol specification §7.10.1][7.10.1] /// /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams -pub const FIRST_HALVING_TESTNET: Height = Height(1_116_000); +pub(crate) const FIRST_HALVING_TESTNET: Height = Height(1_116_000); + +/// The first halving height in the regtest is at block height `287`. +const FIRST_HALVING_REGTEST: Height = Height(287); /// The funding stream receiver categories. #[derive(Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -378,6 +381,20 @@ pub trait ParameterSubsidy { /// /// [7.10]: fn height_for_first_halving(&self) -> Height; + + /// Returns the halving interval after Blossom + fn post_blossom_halving_interval(&self) -> HeightDiff; + + /// Returns the halving interval before Blossom + fn pre_blossom_halving_interval(&self) -> HeightDiff; + + /// Returns the address change interval for funding streams + /// as described in [protocol specification §7.10][7.10]. + /// + /// > FSRecipientChangeInterval := PostBlossomHalvingInterval / 48 + /// + /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams + fn funding_stream_address_change_interval(&self) -> HeightDiff; } /// Network methods related to Block Subsidy and Funding Streams @@ -390,10 +407,35 @@ impl ParameterSubsidy for Network { Network::Mainnet => NetworkUpgrade::Canopy .activation_height(self) .expect("canopy activation height should be available"), - // TODO: Check what zcashd does here, consider adding a field to `testnet::Parameters` to make this configurable. - Network::Testnet(_params) => FIRST_HALVING_TESTNET, + Network::Testnet(params) => { + if params.is_regtest() { + FIRST_HALVING_REGTEST + } else if params.is_default_testnet() { + FIRST_HALVING_TESTNET + } else { + height_for_halving(1, self).expect("first halving height should be available") + } + } + } + } + + fn post_blossom_halving_interval(&self) -> HeightDiff { + match self { + Network::Mainnet => POST_BLOSSOM_HALVING_INTERVAL, + Network::Testnet(params) => params.post_blossom_halving_interval(), + } + } + + fn pre_blossom_halving_interval(&self) -> HeightDiff { + match self { + Network::Mainnet => PRE_BLOSSOM_HALVING_INTERVAL, + Network::Testnet(params) => params.pre_blossom_halving_interval(), } } + + fn funding_stream_address_change_interval(&self) -> HeightDiff { + self.post_blossom_halving_interval() / 48 + } } /// List of addresses for the Zcash Foundation funding stream in the Mainnet. @@ -514,10 +556,54 @@ pub fn funding_stream_address_period(height: Height, networ let height_after_first_halving = height - network.height_for_first_halving(); - let address_period = (height_after_first_halving + POST_BLOSSOM_HALVING_INTERVAL) - / FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL; + let address_period = (height_after_first_halving + network.post_blossom_halving_interval()) + / network.funding_stream_address_change_interval(); address_period .try_into() .expect("all values are positive and smaller than the input height") } + +/// The first block height of the halving at the provided halving index for a network. +/// +/// See `Halving(height)`, as described in [protocol specification §7.8][7.8] +/// +/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies +pub fn height_for_halving(halving: u32, network: &Network) -> Option { + if halving == 0 { + return Some(Height(0)); + } + + let slow_start_shift = i64::from(network.slow_start_shift().0); + let blossom_height = i64::from( + NetworkUpgrade::Blossom + .activation_height(network) + .expect("blossom activation height should be available") + .0, + ); + let pre_blossom_halving_interval = network.pre_blossom_halving_interval(); + let halving_index = i64::from(halving); + + let unscaled_height = halving_index + .checked_mul(pre_blossom_halving_interval) + .expect("Multiplication overflow: consider reducing the halving interval"); + + let pre_blossom_height = unscaled_height + .min(blossom_height) + .checked_add(slow_start_shift) + .expect("Addition overflow: consider reducing the halving interval"); + + let post_blossom_height = 0 + .max(unscaled_height - blossom_height) + .checked_mul(i64::from(BLOSSOM_POW_TARGET_SPACING_RATIO)) + .expect("Multiplication overflow: consider reducing the halving interval") + .checked_add(slow_start_shift) + .expect("Addition overflow: consider reducing the halving interval"); + + let height = pre_blossom_height + .checked_add(post_blossom_height) + .expect("Addition overflow: consider reducing the halving interval"); + + let height = u32::try_from(height).ok()?; + height.try_into().ok() +} diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 80cb8419c77..96ecfcdf3ff 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -2,7 +2,7 @@ use std::{collections::BTreeMap, fmt}; use crate::{ - block::{self, Height}, + block::{self, Height, HeightDiff}, parameters::{ constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT}, network_upgrade::TESTNET_ACTIVATION_HEIGHTS, @@ -15,9 +15,11 @@ use crate::{ 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, + FundingStreamReceiver, FundingStreamRecipient, FundingStreams, + BLOSSOM_POW_TARGET_SPACING_RATIO, POST_BLOSSOM_HALVING_INTERVAL, + POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET, + PRE_BLOSSOM_HALVING_INTERVAL, PRE_NU6_FUNDING_STREAMS_MAINNET, + PRE_NU6_FUNDING_STREAMS_TESTNET, }, }; @@ -50,14 +52,9 @@ const REGTEST_GENESIS_HASH: &str = 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 - } -} +/// The halving height interval in the regtest is 6 hours. +/// [zcashd regtest halving interval](https://github.com/zcash/zcash/blob/v5.10.0/src/consensus/params.h#L252) +const PRE_BLOSSOM_REGTEST_HALVING_INTERVAL: HeightDiff = 144; /// Configurable funding stream recipient for configured Testnets. #[derive(Deserialize, Clone, Debug)] @@ -94,7 +91,12 @@ pub struct ConfiguredFundingStreams { 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 { + fn convert_with_default( + self, + default_funding_streams: FundingStreams, + parameters_builder: &ParametersBuilder, + ) -> FundingStreams { + let network = parameters_builder.to_network_unchecked(); let height_range = self .height_range .unwrap_or(default_funding_streams.height_range().clone()); @@ -116,43 +118,7 @@ impl ConfiguredFundingStreams { 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_funding_stream_address_period(&funding_streams, &network); // check that sum of receiver numerators is valid. @@ -172,6 +138,44 @@ impl ConfiguredFundingStreams { } } +/// Checks that the provided [`FundingStreams`] has sufficient recipient addresses for the +/// funding stream address period of the provided [`Network`]. +fn check_funding_stream_address_period(funding_streams: &FundingStreams, network: &Network) { + let height_range = funding_streams.height_range(); + 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"), + network, + )) + .expect("no overflow should happen in this sum") + .checked_sub(funding_stream_address_period(height_range.start, network)) + .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" + ); + } + } +} + /// Configurable activation heights for Regtest and configured Testnets. #[derive(Deserialize, Default, Clone)] #[serde(rename_all = "PascalCase", deny_unknown_fields)] @@ -213,10 +217,17 @@ pub struct ParametersBuilder { pre_nu6_funding_streams: FundingStreams, /// Post-NU6 funding streams for this network post_nu6_funding_streams: FundingStreams, + /// A flag indicating whether to allow changes to fields that affect + /// the funding stream address period. + should_lock_funding_stream_address_period: bool, /// 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, + /// The pre-Blossom halving interval for this network + pre_blossom_halving_interval: HeightDiff, + /// The post-Blossom halving interval for this network + post_blossom_halving_interval: HeightDiff, } impl Default for ParametersBuilder { @@ -249,6 +260,9 @@ impl Default for ParametersBuilder { disable_pow: false, pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), post_nu6_funding_streams: POST_NU6_FUNDING_STREAMS_TESTNET.clone(), + should_lock_funding_stream_address_period: false, + pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL, + post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL, } } } @@ -318,6 +332,10 @@ impl ParametersBuilder { ) -> Self { use NetworkUpgrade::*; + if self.should_lock_funding_stream_address_period { + panic!("activation heights on ParametersBuilder must not be set after setting funding streams"); + } + // # Correctness // // These must be in order so that later network upgrades overwrite prior ones @@ -377,7 +395,8 @@ impl ParametersBuilder { funding_streams: ConfiguredFundingStreams, ) -> Self { self.pre_nu6_funding_streams = - funding_streams.convert_with_default(PRE_NU6_FUNDING_STREAMS_TESTNET.clone()); + funding_streams.convert_with_default(PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), &self); + self.should_lock_funding_stream_address_period = true; self } @@ -387,7 +406,8 @@ impl ParametersBuilder { funding_streams: ConfiguredFundingStreams, ) -> Self { self.post_nu6_funding_streams = - funding_streams.convert_with_default(POST_NU6_FUNDING_STREAMS_TESTNET.clone()); + funding_streams.convert_with_default(POST_NU6_FUNDING_STREAMS_TESTNET.clone(), &self); + self.should_lock_funding_stream_address_period = true; self } @@ -411,8 +431,20 @@ impl ParametersBuilder { self } + /// Sets the pre and post Blosssom halving intervals to be used in the [`Parameters`] being built. + pub fn with_halving_interval(mut self, pre_blossom_halving_interval: HeightDiff) -> Self { + if self.should_lock_funding_stream_address_period { + panic!("halving interval on ParametersBuilder must not be set after setting funding streams"); + } + + self.pre_blossom_halving_interval = pre_blossom_halving_interval; + self.post_blossom_halving_interval = + self.pre_blossom_halving_interval * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff); + self + } + /// Converts the builder to a [`Parameters`] struct - pub fn finish(self) -> Parameters { + fn finish(self) -> Parameters { let Self { network_name, network_magic, @@ -421,8 +453,11 @@ impl ParametersBuilder { slow_start_interval, pre_nu6_funding_streams, post_nu6_funding_streams, + should_lock_funding_stream_address_period: _, target_difficulty_limit, disable_pow, + pre_blossom_halving_interval, + post_blossom_halving_interval, } = self; Parameters { network_name, @@ -435,12 +470,29 @@ impl ParametersBuilder { post_nu6_funding_streams, target_difficulty_limit, disable_pow, + pre_blossom_halving_interval, + post_blossom_halving_interval, } } /// Converts the builder to a configured [`Network::Testnet`] + fn to_network_unchecked(&self) -> Network { + Network::new_configured_testnet(self.clone().finish()) + } + + /// Checks funding streams and converts the builder to a configured [`Network::Testnet`] pub fn to_network(self) -> Network { - Network::new_configured_testnet(self.finish()) + let network = self.to_network_unchecked(); + + // Final check that the configured funding streams will be valid for these Testnet parameters. + // TODO: Always check funding stream address period once the testnet parameters are being serialized (#8920). + #[cfg(not(any(test, feature = "proptest-impl")))] + { + check_funding_stream_address_period(&self.pre_nu6_funding_streams, &network); + check_funding_stream_address_period(&self.post_nu6_funding_streams, &network); + } + + network } /// Returns true if these [`Parameters`] should be compatible with the default Testnet parameters. @@ -453,8 +505,11 @@ impl ParametersBuilder { slow_start_interval, pre_nu6_funding_streams, post_nu6_funding_streams, + should_lock_funding_stream_address_period: _, target_difficulty_limit, disable_pow, + pre_blossom_halving_interval, + post_blossom_halving_interval, } = Self::default(); self.activation_heights == activation_heights @@ -465,6 +520,8 @@ impl ParametersBuilder { && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow + && self.pre_blossom_halving_interval == pre_blossom_halving_interval + && self.post_blossom_halving_interval == post_blossom_halving_interval } } @@ -495,6 +552,10 @@ pub struct Parameters { target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks disable_pow: bool, + /// Pre-Blossom halving interval for this network + pre_blossom_halving_interval: HeightDiff, + /// Post-Blossom halving interval for this network + post_blossom_halving_interval: HeightDiff, } impl Default for Parameters { @@ -523,24 +584,32 @@ impl Parameters { #[cfg(any(test, feature = "proptest-impl"))] let nu5_activation_height = nu5_activation_height.or(Some(100)); + let parameters = 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() + }) + .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL); + + // TODO: Always clear funding streams on Regtest once the testnet parameters are being serialized (#8920). + #[cfg(not(any(test, feature = "proptest-impl")))] + let parameters = parameters + .with_pre_nu6_funding_streams(ConfiguredFundingStreams::default()) + .with_post_nu6_funding_streams(ConfiguredFundingStreams::default()); + 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() + ..parameters.finish() } } @@ -568,6 +637,8 @@ impl Parameters { post_nu6_funding_streams, target_difficulty_limit, disable_pow, + pre_blossom_halving_interval, + post_blossom_halving_interval, } = Self::new_regtest(None, None); self.network_name == network_name @@ -578,6 +649,8 @@ impl Parameters { && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow + && self.pre_blossom_halving_interval == pre_blossom_halving_interval + && self.post_blossom_halving_interval == post_blossom_halving_interval } /// Returns the network name @@ -629,6 +702,16 @@ impl Parameters { pub fn disable_pow(&self) -> bool { self.disable_pow } + + /// Returns the pre-Blossom halving interval for this network + pub fn pre_blossom_halving_interval(&self) -> HeightDiff { + self.pre_blossom_halving_interval + } + + /// Returns the post-Blossom halving interval for this network + pub fn post_blossom_halving_interval(&self) -> HeightDiff { + self.post_blossom_halving_interval + } } impl Network { diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index c839a26c116..4282c86844f 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -139,7 +139,7 @@ fn activates_network_upgrades_correctly() { let expected_default_regtest_activation_heights = &[ (Height(0), NetworkUpgrade::Genesis), (Height(1), NetworkUpgrade::Canopy), - // TODO: Remove this once the testnet parameters are being serialized. + // TODO: Remove this once the testnet parameters are being serialized (#8920). (Height(100), NetworkUpgrade::Nu5), ]; diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 356ae86577f..957b96de944 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -59,6 +59,7 @@ pub enum NetworkUpgrade { #[serde(rename = "NU5")] Nu5, /// The Zcash protocol after the NU6 upgrade. + #[serde(rename = "NU6")] Nu6, } diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 03ebac36d21..d871751da34 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -23,40 +23,39 @@ use crate::{block::SubsidyError, funding_stream_values}; /// /// Returns `None` if the divisor would overflow a `u64`. pub fn halving_divisor(height: Height, network: &Network) -> Option { + // Some far-future shifts can be more than 63 bits + 1u64.checked_shl(num_halvings(height, network)) +} + +/// The halving index for a block height and network. +/// +/// `Halving(height)`, as described in [protocol specification §7.8][7.8] +/// +/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies +pub fn num_halvings(height: Height, network: &Network) -> u32 { + let slow_start_shift = network.slow_start_shift(); let blossom_height = Blossom .activation_height(network) .expect("blossom activation height should be available"); - if height < blossom_height { - let pre_blossom_height = height - network.slow_start_shift(); - let halving_shift = pre_blossom_height / PRE_BLOSSOM_HALVING_INTERVAL; - - let halving_div = 1u64 - .checked_shl( - halving_shift - .try_into() - .expect("already checked for negatives"), - ) - .expect("pre-blossom heights produce small shifts"); - - Some(halving_div) + let halving_index = if height < slow_start_shift { + 0 + } else if height < blossom_height { + let pre_blossom_height = height - slow_start_shift; + pre_blossom_height / network.pre_blossom_halving_interval() } else { - let pre_blossom_height = blossom_height - network.slow_start_shift(); + let pre_blossom_height = blossom_height - slow_start_shift; let scaled_pre_blossom_height = pre_blossom_height * HeightDiff::from(BLOSSOM_POW_TARGET_SPACING_RATIO); let post_blossom_height = height - blossom_height; - let halving_shift = - (scaled_pre_blossom_height + post_blossom_height) / POST_BLOSSOM_HALVING_INTERVAL; + (scaled_pre_blossom_height + post_blossom_height) / network.post_blossom_halving_interval() + }; - // Some far-future shifts can be more than 63 bits - 1u64.checked_shl( - halving_shift - .try_into() - .expect("already checked for negatives"), - ) - } + halving_index + .try_into() + .expect("already checked for negatives") } /// `BlockSubsidy(height)` as described in [protocol specification §7.8][7.8] @@ -503,4 +502,33 @@ mod test { Ok(()) } + + #[test] + fn check_height_for_num_halvings() { + for network in Network::iter() { + for halving in 1..1000 { + let Some(height_for_halving) = + zebra_chain::parameters::subsidy::height_for_halving(halving, &network) + else { + panic!("could not find height for halving {halving}"); + }; + + let prev_height = height_for_halving + .previous() + .expect("there should be a previous height"); + + assert_eq!( + halving, + num_halvings(height_for_halving, &network), + "num_halvings should match the halving index" + ); + + assert_eq!( + halving - 1, + num_halvings(prev_height, &network), + "num_halvings for the prev height should be 1 less than the halving index" + ); + } + } + } } diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 7936ea0e787..8619507fa0d 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -597,6 +597,7 @@ impl<'de> Deserialize<'de> for Config { activation_heights: Option, pre_nu6_funding_streams: Option, post_nu6_funding_streams: Option, + pre_blossom_halving_interval: Option, } #[derive(Deserialize)] @@ -686,6 +687,7 @@ impl<'de> Deserialize<'de> for Config { activation_heights, pre_nu6_funding_streams, post_nu6_funding_streams, + pre_blossom_halving_interval, }), ) => { let mut params_builder = testnet::Parameters::build(); @@ -708,14 +710,6 @@ impl<'de> Deserialize<'de> for Config { ); } - if let Some(funding_streams) = pre_nu6_funding_streams { - params_builder = params_builder.with_pre_nu6_funding_streams(funding_streams); - } - - if let Some(funding_streams) = post_nu6_funding_streams { - params_builder = params_builder.with_post_nu6_funding_streams(funding_streams); - } - if let Some(target_difficulty_limit) = target_difficulty_limit.clone() { params_builder = params_builder.with_target_difficulty_limit( target_difficulty_limit @@ -733,6 +727,20 @@ impl<'de> Deserialize<'de> for Config { params_builder = params_builder.with_activation_heights(activation_heights) } + if let Some(halving_interval) = pre_blossom_halving_interval { + params_builder = params_builder.with_halving_interval(halving_interval.into()) + } + + // Set configured funding streams after setting any parameters that affect the funding stream address period. + + if let Some(funding_streams) = pre_nu6_funding_streams { + params_builder = params_builder.with_pre_nu6_funding_streams(funding_streams); + } + + if let Some(funding_streams) = post_nu6_funding_streams { + params_builder = params_builder.with_post_nu6_funding_streams(funding_streams); + } + // Return an error if the initial testnet peers includes any of the default initial Mainnet or Testnet // peers and the configured network parameters are incompatible with the default public Testnet. if !params_builder.is_compatible_with_default_parameters() diff --git a/zebra-rpc/qa/base_config.toml b/zebra-rpc/qa/base_config.toml index 502a2a75b1d..c0cc5391f4b 100644 --- a/zebra-rpc/qa/base_config.toml +++ b/zebra-rpc/qa/base_config.toml @@ -10,3 +10,7 @@ listen_addr = "127.0.0.1:0" [state] cache_dir = "" + +[network.testnet_parameters.activation_heights] +NU5 = 290 +NU6 = 291 diff --git a/zebra-rpc/qa/pull-tester/rpc-tests.py b/zebra-rpc/qa/pull-tester/rpc-tests.py index 00194f0aa53..e8f48ac9861 100755 --- a/zebra-rpc/qa/pull-tester/rpc-tests.py +++ b/zebra-rpc/qa/pull-tester/rpc-tests.py @@ -39,7 +39,8 @@ # Scripts that are run by the travis build process # Longest test should go first, to favor running tests in parallel 'reindex.py', - 'getmininginfo.py'] + 'getmininginfo.py', + 'nuparams.py'] ZMQ_SCRIPTS = [ # ZMQ test can only be run if bitcoin was built with zmq-enabled. diff --git a/zebra-rpc/qa/rpc-tests/nuparams.py b/zebra-rpc/qa/rpc-tests/nuparams.py new file mode 100755 index 00000000000..5ed74e4ac34 --- /dev/null +++ b/zebra-rpc/qa/rpc-tests/nuparams.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Zcash developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php . + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + start_nodes, + nuparams, + nustr, + OVERWINTER_BRANCH_ID, + SAPLING_BRANCH_ID, + BLOSSOM_BRANCH_ID, + HEARTWOOD_BRANCH_ID, + CANOPY_BRANCH_ID, + NU5_BRANCH_ID, + NU6_BRANCH_ID, +) +from decimal import Decimal + + +class NuparamsTest(BitcoinTestFramework): + ''' + Test that unspecified network upgrades are activated automatically; + this is really more of a test of the test framework. + ''' + + def __init__(self): + super().__init__() + self.num_nodes = 1 + self.cache_behavior = 'clean' + + def setup_network(self, split=False): + args = [[] * self.num_nodes] + + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, args) + self.is_network_split = False + self.sync_all() + + def run_test(self): + node = self.nodes[0] + # No blocks have been created, only the genesis block exists (height 0) + bci = node.getblockchaininfo() + print(bci) + assert_equal(bci['blocks'], 0) + upgrades = bci['upgrades'] + + overwinter = upgrades[nustr(OVERWINTER_BRANCH_ID)] + assert_equal(overwinter['name'], 'Overwinter') + assert_equal(overwinter['activationheight'], 1) + assert_equal(overwinter['status'], 'pending') + + sapling = upgrades[nustr(SAPLING_BRANCH_ID)] + assert_equal(sapling['name'], 'Sapling') + assert_equal(sapling['activationheight'], 1) + assert_equal(sapling['status'], 'pending') + + blossom = upgrades[nustr(BLOSSOM_BRANCH_ID)] + assert_equal(blossom['name'], 'Blossom') + assert_equal(blossom['activationheight'], 1) + assert_equal(blossom['status'], 'pending') + + heartwood = upgrades[nustr(HEARTWOOD_BRANCH_ID)] + assert_equal(heartwood['name'], 'Heartwood') + assert_equal(heartwood['activationheight'], 1) + assert_equal(heartwood['status'], 'pending') + + canopy = upgrades[nustr(CANOPY_BRANCH_ID)] + assert_equal(canopy['name'], 'Canopy') + assert_equal(canopy['activationheight'], 1) + assert_equal(canopy['status'], 'pending') + + nu5 = upgrades[nustr(NU5_BRANCH_ID)] + assert_equal(nu5['name'], 'NU5') + assert_equal(nu5['activationheight'], 290) + assert_equal(nu5['status'], 'pending') + + nu6 = upgrades[nustr(NU6_BRANCH_ID)] + assert_equal(nu6['name'], 'NU6') + assert_equal(nu6['activationheight'], 291) + assert_equal(nu6['status'], 'pending') + + # Zebra can't call `getblocksubsidy` before the first halving. + + # Zebra regtest mode hardcodes Canopy, Heartwood, Blossom, Sapling and Overwinter + # to activate at height 1. + node.generate(1) + + bci = node.getblockchaininfo() + assert_equal(bci['blocks'], 1) + upgrades = bci['upgrades'] + + overwinter = upgrades[nustr(OVERWINTER_BRANCH_ID)] + assert_equal(overwinter['name'], 'Overwinter') + assert_equal(overwinter['activationheight'], 1) + assert_equal(overwinter['status'], 'active') + + sapling = upgrades[nustr(SAPLING_BRANCH_ID)] + assert_equal(sapling['name'], 'Sapling') + assert_equal(sapling['activationheight'], 1) + assert_equal(sapling['status'], 'active') + + blossom = upgrades[nustr(BLOSSOM_BRANCH_ID)] + assert_equal(blossom['name'], 'Blossom') + assert_equal(blossom['activationheight'], 1) + assert_equal(blossom['status'], 'active') + + heartwood = upgrades[nustr(HEARTWOOD_BRANCH_ID)] + assert_equal(heartwood['name'], 'Heartwood') + assert_equal(heartwood['activationheight'], 1) + assert_equal(heartwood['status'], 'active') + + canopy = upgrades[nustr(CANOPY_BRANCH_ID)] + assert_equal(canopy['name'], 'Canopy') + assert_equal(canopy['activationheight'], 1) + assert_equal(canopy['status'], 'active') + + nu5 = upgrades[nustr(NU5_BRANCH_ID)] + assert_equal(nu5['name'], 'NU5') + assert_equal(nu5['activationheight'], 290) + assert_equal(nu5['status'], 'pending') + + nu6 = upgrades[nustr(NU6_BRANCH_ID)] + assert_equal(nu6['name'], 'NU6') + assert_equal(nu6['activationheight'], 291) + assert_equal(nu6['status'], 'pending') + + # Zebra can't call `getblocksubsidy` before the first halving. + + # Activate First Halving + node.generate(287) + bci = node.getblockchaininfo() + assert_equal(bci['blocks'], 288) + upgrades = bci['upgrades'] + + overwinter = upgrades[nustr(OVERWINTER_BRANCH_ID)] + assert_equal(overwinter['name'], 'Overwinter') + assert_equal(overwinter['activationheight'], 1) + assert_equal(overwinter['status'], 'active') + + sapling = upgrades[nustr(SAPLING_BRANCH_ID)] + assert_equal(sapling['name'], 'Sapling') + assert_equal(sapling['activationheight'], 1) + assert_equal(sapling['status'], 'active') + + blossom = upgrades[nustr(BLOSSOM_BRANCH_ID)] + assert_equal(blossom['name'], 'Blossom') + assert_equal(blossom['activationheight'], 1) + assert_equal(blossom['status'], 'active') + + heartwood = upgrades[nustr(HEARTWOOD_BRANCH_ID)] + assert_equal(heartwood['name'], 'Heartwood') + assert_equal(heartwood['activationheight'], 1) + assert_equal(heartwood['status'], 'active') + + canopy = upgrades[nustr(CANOPY_BRANCH_ID)] + assert_equal(canopy['name'], 'Canopy') + assert_equal(canopy['activationheight'], 1) + assert_equal(canopy['status'], 'active') + + nu5 = upgrades[nustr(NU5_BRANCH_ID)] + assert_equal(nu5['name'], 'NU5') + assert_equal(nu5['activationheight'], 290) + assert_equal(nu5['status'], 'pending') + + nu6 = upgrades[nustr(NU6_BRANCH_ID)] + assert_equal(nu6['name'], 'NU6') + assert_equal(nu6['activationheight'], 291) + assert_equal(nu6['status'], 'pending') + + # The founders' reward ends at Canopy and there are no funding streams + # configured by default for regtest. + assert_equal(node.getblocksubsidy()["miner"], Decimal("3.125")) + + # Activate NU5 + node.generate(2) + bci = node.getblockchaininfo() + assert_equal(bci['blocks'], 290) + upgrades = bci['upgrades'] + + overwinter = upgrades[nustr(OVERWINTER_BRANCH_ID)] + assert_equal(overwinter['name'], 'Overwinter') + assert_equal(overwinter['activationheight'], 1) + assert_equal(overwinter['status'], 'active') + + sapling = upgrades[nustr(SAPLING_BRANCH_ID)] + assert_equal(sapling['name'], 'Sapling') + assert_equal(sapling['activationheight'], 1) + assert_equal(sapling['status'], 'active') + + blossom = upgrades[nustr(BLOSSOM_BRANCH_ID)] + assert_equal(blossom['name'], 'Blossom') + assert_equal(blossom['activationheight'], 1) + assert_equal(blossom['status'], 'active') + + heartwood = upgrades[nustr(HEARTWOOD_BRANCH_ID)] + assert_equal(heartwood['name'], 'Heartwood') + assert_equal(heartwood['activationheight'], 1) + assert_equal(heartwood['status'], 'active') + + canopy = upgrades[nustr(CANOPY_BRANCH_ID)] + assert_equal(canopy['name'], 'Canopy') + assert_equal(canopy['activationheight'], 1) + assert_equal(canopy['status'], 'active') + + nu5 = upgrades[nustr(NU5_BRANCH_ID)] + assert_equal(nu5['name'], 'NU5') + assert_equal(nu5['activationheight'], 290) + assert_equal(nu5['status'], 'active') + + nu6 = upgrades[nustr(NU6_BRANCH_ID)] + assert_equal(nu6['name'], 'NU6') + assert_equal(nu6['activationheight'], 291) + assert_equal(nu6['status'], 'pending') + + # Block subsidy remains the same after NU5 + assert_equal(node.getblocksubsidy()["miner"], Decimal("3.125")) + + # Activate NU6 + node.generate(1) + bci = node.getblockchaininfo() + assert_equal(bci['blocks'], 291) + upgrades = bci['upgrades'] + + overwinter = upgrades[nustr(OVERWINTER_BRANCH_ID)] + assert_equal(overwinter['name'], 'Overwinter') + assert_equal(overwinter['activationheight'], 1) + assert_equal(overwinter['status'], 'active') + + sapling = upgrades[nustr(SAPLING_BRANCH_ID)] + assert_equal(sapling['name'], 'Sapling') + assert_equal(sapling['activationheight'], 1) + assert_equal(sapling['status'], 'active') + + blossom = upgrades[nustr(BLOSSOM_BRANCH_ID)] + assert_equal(blossom['name'], 'Blossom') + assert_equal(blossom['activationheight'], 1) + assert_equal(blossom['status'], 'active') + + heartwood = upgrades[nustr(HEARTWOOD_BRANCH_ID)] + assert_equal(heartwood['name'], 'Heartwood') + assert_equal(heartwood['activationheight'], 1) + assert_equal(heartwood['status'], 'active') + + canopy = upgrades[nustr(CANOPY_BRANCH_ID)] + assert_equal(canopy['name'], 'Canopy') + assert_equal(canopy['activationheight'], 1) + assert_equal(canopy['status'], 'active') + + nu5 = upgrades[nustr(NU5_BRANCH_ID)] + assert_equal(nu5['name'], 'NU5') + assert_equal(nu5['activationheight'], 290) + assert_equal(nu5['status'], 'active') + + nu6 = upgrades[nustr(NU6_BRANCH_ID)] + assert_equal(nu6['name'], 'NU6') + assert_equal(nu6['activationheight'], 291) + assert_equal(nu6['status'], 'active') + + # Block subsidy remains the same after NU6 as there are not funding streams + # nor lockbox configured by default for regtest. + assert_equal(node.getblocksubsidy()["miner"], Decimal("3.125")) + +if __name__ == '__main__': + NuparamsTest().main() \ No newline at end of file diff --git a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap index 0460bb7420f..3bea6c01509 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap @@ -66,7 +66,7 @@ expression: info "status": "pending" }, "c8e71055": { - "name": "Nu6", + "name": "NU6", "activationheight": 2976000, "status": "pending" } diff --git a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info_future_nu6_height@nu6testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info_future_nu6_height@nu6testnet_10.snap index 78e70ab5e26..0f4f6fe26a6 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info_future_nu6_height@nu6testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info_future_nu6_height@nu6testnet_10.snap @@ -66,7 +66,7 @@ expression: info "status": "pending" }, "c8e71055": { - "name": "Nu6", + "name": "NU6", "activationheight": 2976000, "status": "pending" }