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 <aryasolhi@gmail.com>
This commit is contained in:
Alfredo Garcia 2024-10-10 15:26:54 -03:00 committed by GitHub
parent 8cd4d96085
commit f2e7bc95ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 586 additions and 109 deletions

View File

@ -48,7 +48,10 @@ pub const POST_BLOSSOM_HALVING_INTERVAL: HeightDiff =
/// as specified in [protocol specification §7.10.1][7.10.1] /// as specified in [protocol specification §7.10.1][7.10.1]
/// ///
/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams /// [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. /// The funding stream receiver categories.
#[derive(Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
@ -378,6 +381,20 @@ pub trait ParameterSubsidy {
/// ///
/// [7.10]: <https://zips.z.cash/protocol/protocol.pdf#fundingstreams> /// [7.10]: <https://zips.z.cash/protocol/protocol.pdf#fundingstreams>
fn height_for_first_halving(&self) -> Height; 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 /// Network methods related to Block Subsidy and Funding Streams
@ -390,10 +407,35 @@ impl ParameterSubsidy for Network {
Network::Mainnet => NetworkUpgrade::Canopy Network::Mainnet => NetworkUpgrade::Canopy
.activation_height(self) .activation_height(self)
.expect("canopy activation height should be available"), .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) => {
Network::Testnet(_params) => FIRST_HALVING_TESTNET, 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. /// List of addresses for the Zcash Foundation funding stream in the Mainnet.
@ -514,10 +556,54 @@ pub fn funding_stream_address_period<N: ParameterSubsidy>(height: Height, networ
let height_after_first_halving = height - network.height_for_first_halving(); let height_after_first_halving = height - network.height_for_first_halving();
let address_period = (height_after_first_halving + POST_BLOSSOM_HALVING_INTERVAL) let address_period = (height_after_first_halving + network.post_blossom_halving_interval())
/ FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL; / network.funding_stream_address_change_interval();
address_period address_period
.try_into() .try_into()
.expect("all values are positive and smaller than the input height") .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<Height> {
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()
}

View File

@ -2,7 +2,7 @@
use std::{collections::BTreeMap, fmt}; use std::{collections::BTreeMap, fmt};
use crate::{ use crate::{
block::{self, Height}, block::{self, Height, HeightDiff},
parameters::{ parameters::{
constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT}, constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT},
network_upgrade::TESTNET_ACTIVATION_HEIGHTS, network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
@ -15,9 +15,11 @@ use crate::{
use super::{ use super::{
magic::Magic, magic::Magic,
subsidy::{ subsidy::{
FundingStreamReceiver, FundingStreamRecipient, FundingStreams, ParameterSubsidy, FundingStreamReceiver, FundingStreamRecipient, FundingStreams,
FIRST_HALVING_TESTNET, POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET, BLOSSOM_POW_TARGET_SPACING_RATIO, POST_BLOSSOM_HALVING_INTERVAL,
PRE_NU6_FUNDING_STREAMS_MAINNET, PRE_NU6_FUNDING_STREAMS_TESTNET, 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 = const TESTNET_GENESIS_HASH: &str =
"05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38"; "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38";
/// Used to validate number of funding stream recipient addresses on configured Testnets. /// The halving height interval in the regtest is 6 hours.
struct TestnetParameterSubsidyImpl; /// [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;
impl ParameterSubsidy for TestnetParameterSubsidyImpl {
fn height_for_first_halving(&self) -> Height {
FIRST_HALVING_TESTNET
}
}
/// Configurable funding stream recipient for configured Testnets. /// Configurable funding stream recipient for configured Testnets.
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
@ -94,7 +91,12 @@ pub struct ConfiguredFundingStreams {
impl ConfiguredFundingStreams { impl ConfiguredFundingStreams {
/// Converts a [`ConfiguredFundingStreams`] to a [`FundingStreams`], using the provided default values /// Converts a [`ConfiguredFundingStreams`] to a [`FundingStreams`], using the provided default values
/// if `height_range` or `recipients` are None. /// 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 let height_range = self
.height_range .height_range
.unwrap_or(default_funding_streams.height_range().clone()); .unwrap_or(default_funding_streams.height_range().clone());
@ -116,43 +118,7 @@ impl ConfiguredFundingStreams {
let funding_streams = FundingStreams::new(height_range.clone(), recipients); let funding_streams = FundingStreams::new(height_range.clone(), recipients);
// check that receivers have enough addresses. check_funding_stream_address_period(&funding_streams, &network);
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. // 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. /// Configurable activation heights for Regtest and configured Testnets.
#[derive(Deserialize, Default, Clone)] #[derive(Deserialize, Default, Clone)]
#[serde(rename_all = "PascalCase", deny_unknown_fields)] #[serde(rename_all = "PascalCase", deny_unknown_fields)]
@ -213,10 +217,17 @@ pub struct ParametersBuilder {
pre_nu6_funding_streams: FundingStreams, pre_nu6_funding_streams: FundingStreams,
/// Post-NU6 funding streams for this network /// Post-NU6 funding streams for this network
post_nu6_funding_streams: FundingStreams, 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 for this network
target_difficulty_limit: ExpandedDifficulty, target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks /// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool, 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 { impl Default for ParametersBuilder {
@ -249,6 +260,9 @@ impl Default for ParametersBuilder {
disable_pow: false, disable_pow: false,
pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(),
post_nu6_funding_streams: POST_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 { ) -> Self {
use NetworkUpgrade::*; use NetworkUpgrade::*;
if self.should_lock_funding_stream_address_period {
panic!("activation heights on ParametersBuilder must not be set after setting funding streams");
}
// # Correctness // # Correctness
// //
// These must be in order so that later network upgrades overwrite prior ones // These must be in order so that later network upgrades overwrite prior ones
@ -377,7 +395,8 @@ impl ParametersBuilder {
funding_streams: ConfiguredFundingStreams, funding_streams: ConfiguredFundingStreams,
) -> Self { ) -> Self {
self.pre_nu6_funding_streams = 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 self
} }
@ -387,7 +406,8 @@ impl ParametersBuilder {
funding_streams: ConfiguredFundingStreams, funding_streams: ConfiguredFundingStreams,
) -> Self { ) -> Self {
self.post_nu6_funding_streams = 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 self
} }
@ -411,8 +431,20 @@ impl ParametersBuilder {
self 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 /// Converts the builder to a [`Parameters`] struct
pub fn finish(self) -> Parameters { fn finish(self) -> Parameters {
let Self { let Self {
network_name, network_name,
network_magic, network_magic,
@ -421,8 +453,11 @@ impl ParametersBuilder {
slow_start_interval, slow_start_interval,
pre_nu6_funding_streams, pre_nu6_funding_streams,
post_nu6_funding_streams, post_nu6_funding_streams,
should_lock_funding_stream_address_period: _,
target_difficulty_limit, target_difficulty_limit,
disable_pow, disable_pow,
pre_blossom_halving_interval,
post_blossom_halving_interval,
} = self; } = self;
Parameters { Parameters {
network_name, network_name,
@ -435,12 +470,29 @@ impl ParametersBuilder {
post_nu6_funding_streams, post_nu6_funding_streams,
target_difficulty_limit, target_difficulty_limit,
disable_pow, disable_pow,
pre_blossom_halving_interval,
post_blossom_halving_interval,
} }
} }
/// Converts the builder to a configured [`Network::Testnet`] /// 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 { 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. /// Returns true if these [`Parameters`] should be compatible with the default Testnet parameters.
@ -453,8 +505,11 @@ impl ParametersBuilder {
slow_start_interval, slow_start_interval,
pre_nu6_funding_streams, pre_nu6_funding_streams,
post_nu6_funding_streams, post_nu6_funding_streams,
should_lock_funding_stream_address_period: _,
target_difficulty_limit, target_difficulty_limit,
disable_pow, disable_pow,
pre_blossom_halving_interval,
post_blossom_halving_interval,
} = Self::default(); } = Self::default();
self.activation_heights == activation_heights self.activation_heights == activation_heights
@ -465,6 +520,8 @@ impl ParametersBuilder {
&& self.post_nu6_funding_streams == post_nu6_funding_streams && self.post_nu6_funding_streams == post_nu6_funding_streams
&& self.target_difficulty_limit == target_difficulty_limit && self.target_difficulty_limit == target_difficulty_limit
&& self.disable_pow == disable_pow && 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, target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks /// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool, 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 { impl Default for Parameters {
@ -523,24 +584,32 @@ impl Parameters {
#[cfg(any(test, feature = "proptest-impl"))] #[cfg(any(test, feature = "proptest-impl"))]
let nu5_activation_height = nu5_activation_height.or(Some(100)); 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: <https://github.com/zcash/zcash/blob/master/src/chainparams.cpp#L654>
.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 { Self {
network_name: "Regtest".to_string(), network_name: "Regtest".to_string(),
network_magic: magics::REGTEST, network_magic: magics::REGTEST,
..Self::build() ..parameters.finish()
.with_genesis_hash(REGTEST_GENESIS_HASH)
// This value is chosen to match zcashd, see: <https://github.com/zcash/zcash/blob/master/src/chainparams.cpp#L654>
.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()
} }
} }
@ -568,6 +637,8 @@ impl Parameters {
post_nu6_funding_streams, post_nu6_funding_streams,
target_difficulty_limit, target_difficulty_limit,
disable_pow, disable_pow,
pre_blossom_halving_interval,
post_blossom_halving_interval,
} = Self::new_regtest(None, None); } = Self::new_regtest(None, None);
self.network_name == network_name self.network_name == network_name
@ -578,6 +649,8 @@ impl Parameters {
&& self.post_nu6_funding_streams == post_nu6_funding_streams && self.post_nu6_funding_streams == post_nu6_funding_streams
&& self.target_difficulty_limit == target_difficulty_limit && self.target_difficulty_limit == target_difficulty_limit
&& self.disable_pow == disable_pow && 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 /// Returns the network name
@ -629,6 +702,16 @@ impl Parameters {
pub fn disable_pow(&self) -> bool { pub fn disable_pow(&self) -> bool {
self.disable_pow 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 { impl Network {

View File

@ -139,7 +139,7 @@ fn activates_network_upgrades_correctly() {
let expected_default_regtest_activation_heights = &[ let expected_default_regtest_activation_heights = &[
(Height(0), NetworkUpgrade::Genesis), (Height(0), NetworkUpgrade::Genesis),
(Height(1), NetworkUpgrade::Canopy), (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), (Height(100), NetworkUpgrade::Nu5),
]; ];

View File

@ -59,6 +59,7 @@ pub enum NetworkUpgrade {
#[serde(rename = "NU5")] #[serde(rename = "NU5")]
Nu5, Nu5,
/// The Zcash protocol after the NU6 upgrade. /// The Zcash protocol after the NU6 upgrade.
#[serde(rename = "NU6")]
Nu6, Nu6,
} }

View File

@ -23,40 +23,39 @@ use crate::{block::SubsidyError, funding_stream_values};
/// ///
/// Returns `None` if the divisor would overflow a `u64`. /// Returns `None` if the divisor would overflow a `u64`.
pub fn halving_divisor(height: Height, network: &Network) -> Option<u64> { pub fn halving_divisor(height: Height, network: &Network) -> Option<u64> {
// 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 let blossom_height = Blossom
.activation_height(network) .activation_height(network)
.expect("blossom activation height should be available"); .expect("blossom activation height should be available");
if height < blossom_height { let halving_index = if height < slow_start_shift {
let pre_blossom_height = height - network.slow_start_shift(); 0
let halving_shift = pre_blossom_height / PRE_BLOSSOM_HALVING_INTERVAL; } else if height < blossom_height {
let pre_blossom_height = height - slow_start_shift;
let halving_div = 1u64 pre_blossom_height / network.pre_blossom_halving_interval()
.checked_shl(
halving_shift
.try_into()
.expect("already checked for negatives"),
)
.expect("pre-blossom heights produce small shifts");
Some(halving_div)
} else { } 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 = let scaled_pre_blossom_height =
pre_blossom_height * HeightDiff::from(BLOSSOM_POW_TARGET_SPACING_RATIO); pre_blossom_height * HeightDiff::from(BLOSSOM_POW_TARGET_SPACING_RATIO);
let post_blossom_height = height - blossom_height; let post_blossom_height = height - blossom_height;
let halving_shift = (scaled_pre_blossom_height + post_blossom_height) / network.post_blossom_halving_interval()
(scaled_pre_blossom_height + post_blossom_height) / POST_BLOSSOM_HALVING_INTERVAL; };
// Some far-future shifts can be more than 63 bits halving_index
1u64.checked_shl( .try_into()
halving_shift .expect("already checked for negatives")
.try_into()
.expect("already checked for negatives"),
)
}
} }
/// `BlockSubsidy(height)` as described in [protocol specification §7.8][7.8] /// `BlockSubsidy(height)` as described in [protocol specification §7.8][7.8]
@ -503,4 +502,33 @@ mod test {
Ok(()) 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"
);
}
}
}
} }

View File

@ -597,6 +597,7 @@ impl<'de> Deserialize<'de> for Config {
activation_heights: Option<ConfiguredActivationHeights>, activation_heights: Option<ConfiguredActivationHeights>,
pre_nu6_funding_streams: Option<ConfiguredFundingStreams>, pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
post_nu6_funding_streams: Option<ConfiguredFundingStreams>, post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
pre_blossom_halving_interval: Option<u32>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -686,6 +687,7 @@ impl<'de> Deserialize<'de> for Config {
activation_heights, activation_heights,
pre_nu6_funding_streams, pre_nu6_funding_streams,
post_nu6_funding_streams, post_nu6_funding_streams,
pre_blossom_halving_interval,
}), }),
) => { ) => {
let mut params_builder = testnet::Parameters::build(); 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() { if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
params_builder = params_builder.with_target_difficulty_limit( params_builder = params_builder.with_target_difficulty_limit(
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) 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 // 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. // peers and the configured network parameters are incompatible with the default public Testnet.
if !params_builder.is_compatible_with_default_parameters() if !params_builder.is_compatible_with_default_parameters()

View File

@ -10,3 +10,7 @@ listen_addr = "127.0.0.1:0"
[state] [state]
cache_dir = "" cache_dir = ""
[network.testnet_parameters.activation_heights]
NU5 = 290
NU6 = 291

View File

@ -39,7 +39,8 @@ BASE_SCRIPTS= [
# Scripts that are run by the travis build process # Scripts that are run by the travis build process
# Longest test should go first, to favor running tests in parallel # Longest test should go first, to favor running tests in parallel
'reindex.py', 'reindex.py',
'getmininginfo.py'] 'getmininginfo.py',
'nuparams.py']
ZMQ_SCRIPTS = [ ZMQ_SCRIPTS = [
# ZMQ test can only be run if bitcoin was built with zmq-enabled. # ZMQ test can only be run if bitcoin was built with zmq-enabled.

View File

@ -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()

View File

@ -66,7 +66,7 @@ expression: info
"status": "pending" "status": "pending"
}, },
"c8e71055": { "c8e71055": {
"name": "Nu6", "name": "NU6",
"activationheight": 2976000, "activationheight": 2976000,
"status": "pending" "status": "pending"
} }

View File

@ -66,7 +66,7 @@ expression: info
"status": "pending" "status": "pending"
}, },
"c8e71055": { "c8e71055": {
"name": "Nu6", "name": "NU6",
"activationheight": 2976000, "activationheight": 2976000,
"status": "pending" "status": "pending"
} }