diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index 9bc69b9bca9..f8594ae0596 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -1,4 +1,6 @@ -//! Constants for Block Subsidy and Funding Streams +//! Constants and calculations for Block Subsidy and Funding Streams + +use std::collections::HashMap; use lazy_static::lazy_static; @@ -6,6 +8,7 @@ use crate::{ amount::COIN, block::{Height, HeightDiff}, parameters::{Network, NetworkUpgrade}, + transparent, }; /// The largest block subsidy, used before the first halving. @@ -36,9 +39,10 @@ pub const POST_BLOSSOM_HALVING_INTERVAL: HeightDiff = pub const FIRST_HALVING_TESTNET: Height = Height(1_116_000); /// The funding stream receiver categories. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum FundingStreamReceiver { /// The Electric Coin Company (Bootstrap Foundation) funding stream. + #[serde(rename = "ECC")] Ecc, /// The Zcash Foundation funding stream. @@ -48,6 +52,20 @@ pub enum FundingStreamReceiver { MajorGrants, } +impl FundingStreamReceiver { + /// The name for each funding stream receiver, as described in [ZIP-1014] and [`zcashd`]. + /// + /// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract + /// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32 + pub fn name(self) -> &'static str { + match self { + FundingStreamReceiver::Ecc => "Electric Coin Company", + FundingStreamReceiver::ZcashFoundation => "Zcash Foundation", + FundingStreamReceiver::MajorGrants => "Major Grants", + } + } +} + /// Denominator as described in [protocol specification §7.10.1][7.10.1]. /// /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams @@ -59,78 +77,86 @@ pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100; pub const FUNDING_STREAM_SPECIFICATION: &str = "https://zips.z.cash/zip-0214"; /// Funding stream recipients and height ranges. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Clone, Debug, Eq, PartialEq)] pub struct FundingStreams { - recipients: Vec, + /// Start and end Heights for funding streams + /// as described in [protocol specification §7.10.1][7.10.1]. + /// + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams height_range: std::ops::Range, + /// Funding stream recipients by [`FundingStreamReceiver`]. + recipients: HashMap, } impl FundingStreams { + /// Creates a new [`FundingStreams`]. + pub fn new( + height_range: std::ops::Range, + recipients: HashMap, + ) -> Self { + Self { + height_range, + recipients, + } + } + /// Returns height range where these [`FundingStreams`] should apply. pub fn height_range(&self) -> &std::ops::Range { &self.height_range } /// Returns recipients of these [`FundingStreams`]. - pub fn recipients(&self) -> &[FundingStreamRecipient] { + pub fn recipients(&self) -> &HashMap { &self.recipients } /// Returns a recipient with the provided receiver. - pub fn recipient_by_receiver( - &self, - receiver: FundingStreamReceiver, - ) -> Option<&FundingStreamRecipient> { - self.recipients - .iter() - .find(|recipient| recipient.receiver() == receiver) + pub fn recipient(&self, receiver: FundingStreamReceiver) -> Option<&FundingStreamRecipient> { + self.recipients.get(&receiver) } } /// A funding stream recipient as specified in [protocol specification §7.10.1][7.10.1] /// /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Clone, Debug, Eq, PartialEq)] pub struct FundingStreamRecipient { - receiver: FundingStreamReceiver, - name: String, + /// The numerator for each funding stream receiver category + /// as described in [protocol specification §7.10.1][7.10.1]. + /// + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams numerator: u64, - addresses: Vec, + /// Addresses for the funding stream recipient + addresses: Vec, } impl FundingStreamRecipient { /// Creates a new [`FundingStreamRecipient`]. - pub fn new( - receiver: FundingStreamReceiver, - name: impl Into, - numerator: u64, - addresses: Vec, - ) -> Self { + pub fn new(numerator: u64, addresses: I) -> Self + where + T: ToString, + I: IntoIterator, + { Self { - receiver, - name: name.into(), numerator, - addresses, + addresses: addresses + .into_iter() + .map(|addr| { + let addr = addr.to_string(); + addr.parse() + .expect(&format!("could not deserialize address: {addr}")) + }) + .collect(), } } - /// Returns the receiver of this funding stream. - pub fn receiver(&self) -> FundingStreamReceiver { - self.receiver - } - - /// Returns the name of this funding stream. - pub fn name(&self) -> &str { - &self.name - } - /// Returns the numerator for this funding stream. pub fn numerator(&self) -> u64 { self.numerator } /// Returns the receiver of this funding stream. - pub fn addresses(&self) -> &[String] { + pub fn addresses(&self) -> &[transparent::Address] { &self.addresses } } @@ -138,102 +164,66 @@ impl FundingStreamRecipient { lazy_static! { /// The pre-NU6 funding streams for Mainnet pub static ref PRE_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( + height_range: Height(1_046_400)..Height(2_726_400), + recipients: [ + ( FundingStreamReceiver::Ecc, - "Electric Coin Company", - 7, - FUNDING_STREAM_ECC_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter()), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::ZcashFoundation, - "Zcash Foundation", - 5, - FUNDING_STREAM_ZF_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_MAINNET), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET), ), - ], - height_range: Height(1_046_400)..Height(2_726_400), + ] + .into_iter() + .collect(), + }; + + /// The post-NU6 funding streams for Mainnet + pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { + height_range: Height(2_726_400)..Height(3_146_400), + recipients: [( + FundingStreamReceiver::MajorGrants, + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET) + )] + .into_iter() + .collect(), }; /// The pre-NU6 funding streams for Testnet pub static ref PRE_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( + height_range: Height(1_028_500)..Height(2_796_000), + recipients: [ + ( FundingStreamReceiver::Ecc, - "Electric Coin Company", - 7, - FUNDING_STREAM_ECC_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_TESTNET), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::ZcashFoundation, - "Zcash Foundation", - 5, - FUNDING_STREAM_ZF_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_TESTNET), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), ), - ], - height_range: Height(1_028_500)..Height(2_796_000), - }; - - /// The post-NU6 funding streams for Mainnet - pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( - FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), - ), - ], - height_range: Height(2_726_400)..Height(3_146_400), + ] + .into_iter() + .collect(), }; /// The post-NU6 funding streams for Testnet pub static ref POST_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( - FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), - ), - ], height_range: Height(2_942_000)..Height(3_362_000), + recipients: [( + FundingStreamReceiver::MajorGrants, + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), + )] + .into_iter() + .collect(), }; } @@ -312,7 +302,7 @@ pub trait ParameterSubsidy { } /// Network methods related to Block Subsidy and Funding Streams -impl ParameterSubsidy for Network { +impl ParameterSubsidy for &Network { fn height_for_first_halving(&self) -> Height { // First halving on Mainnet is at Canopy // while in Testnet is at block constant height of `1_116_000` @@ -326,6 +316,13 @@ impl ParameterSubsidy for Network { } } } + +impl ParameterSubsidy for Network { + fn height_for_first_halving(&self) -> Height { + (&self).height_for_first_halving() + } +} + /// List of addresses for the Zcash Foundation funding stream in the Mainnet. pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET]; @@ -403,3 +400,27 @@ pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRES /// List of addresses for the Major Grants funding stream in the Testnet. pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] = ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET]; + +/// Returns the address change period +/// as described in [protocol specification §7.10][7.10] +/// +/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams +pub fn funding_stream_address_period(height: Height, network: impl ParameterSubsidy) -> u32 { + // Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`, + // + // + // Note that the brackets make it so the post blossom halving interval is added to the total. + // + // In Rust, "integer division rounds towards zero": + // + // This is the same as `floor()`, because these numbers are all positive. + + 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; + + address_period + .try_into() + .expect("all values are positive and smaller than the input height") +} diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index d1a2a1953e0..902a822d26a 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -6,6 +6,7 @@ use crate::{ parameters::{ constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT}, network_upgrade::TESTNET_ACTIVATION_HEIGHTS, + subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR}, Network, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER, }, work::difficulty::{ExpandedDifficulty, U256}, @@ -14,7 +15,8 @@ use crate::{ use super::{ magic::Magic, subsidy::{ - FundingStreams, POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET, + 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, }, }; @@ -48,9 +50,116 @@ const REGTEST_GENESIS_HASH: &str = const TESTNET_GENESIS_HASH: &str = "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38"; +struct TestnetParameterSubsidyImpl; + +impl ParameterSubsidy for TestnetParameterSubsidyImpl { + fn height_for_first_halving(&self) -> Height { + FIRST_HALVING_TESTNET + } +} + +/// Configurable funding streams for Regtest and 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: Vec, +} + +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), + ) + } +} + +/// Configurable funding streams for Regtest and 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 { + 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 recipient in funding_streams.recipients().values() { + // TODO: Make an exception for the `Deferred` receiver. + 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}" + ); + } + + // 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")] +#[serde(rename_all = "PascalCase", deny_unknown_fields)] pub struct ConfiguredActivationHeights { /// Activation height for `BeforeOverwinter` network upgrade. pub before_overwinter: Option, @@ -247,6 +356,26 @@ impl ParametersBuilder { 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( @@ -527,7 +656,7 @@ impl Network { /// Returns post-NU6 funding streams for this network pub fn post_nu6_funding_streams(&self) -> &FundingStreams { if let Self::Testnet(params) = self { - params.pre_nu6_funding_streams() + params.post_nu6_funding_streams() } else { &POST_NU6_FUNDING_STREAMS_MAINNET } diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index 5df16d7d38d..95c28572225 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -6,8 +6,13 @@ use zcash_protocol::consensus::NetworkConstants as _; use crate::{ block::Height, parameters::{ + subsidy::{ + FundingStreamReceiver, FUNDING_STREAM_ECC_ADDRESSES_TESTNET, + POST_NU6_FUNDING_STREAMS_TESTNET, PRE_NU6_FUNDING_STREAMS_TESTNET, + }, testnet::{ - self, ConfiguredActivationHeights, MAX_NETWORK_NAME_LENGTH, RESERVED_NETWORK_NAMES, + self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, + ConfiguredFundingStreams, MAX_NETWORK_NAME_LENGTH, RESERVED_NETWORK_NAMES, }, Network, NetworkUpgrade, MAINNET_ACTIVATION_HEIGHTS, NETWORK_UPGRADES_IN_ORDER, TESTNET_ACTIVATION_HEIGHTS, @@ -295,3 +300,126 @@ fn check_full_activation_list() { ); } } + +#[test] +fn check_funding_streams() { + let configured_funding_streams = [ + Default::default(), + ConfiguredFundingStreams { + height_range: Some(Height(2_000_000)..Height(2_200_000)), + ..Default::default() + }, + ConfiguredFundingStreams { + height_range: Some(Height(20)..Height(30)), + recipients: None, + }, + ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 20, + addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET + .map(Into::into) + .to_vec(), + }]), + ..Default::default() + }, + ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 100, + addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET + .map(Into::into) + .to_vec(), + }]), + ..Default::default() + }, + ]; + + for configured_funding_streams in configured_funding_streams { + for is_pre_nu6 in [false, true] { + let (network_funding_streams, default_funding_streams) = if is_pre_nu6 { + ( + testnet::Parameters::build() + .with_pre_nu6_funding_streams(configured_funding_streams.clone()) + .to_network() + .pre_nu6_funding_streams() + .clone(), + PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), + ) + } else { + ( + testnet::Parameters::build() + .with_post_nu6_funding_streams(configured_funding_streams.clone()) + .to_network() + .post_nu6_funding_streams() + .clone(), + POST_NU6_FUNDING_STREAMS_TESTNET.clone(), + ) + }; + + let expected_height_range = configured_funding_streams + .height_range + .clone() + .unwrap_or(default_funding_streams.height_range().clone()); + + assert_eq!( + network_funding_streams.height_range().clone(), + expected_height_range, + "should use default start height when unconfigured" + ); + + let expected_recipients = configured_funding_streams + .recipients + .clone() + .map(|recipients| { + recipients + .into_iter() + .map(ConfiguredFundingStreamRecipient::into_recipient) + .collect() + }) + .unwrap_or(default_funding_streams.recipients().clone()); + + assert_eq!( + network_funding_streams.recipients().clone(), + expected_recipients, + "should use default start height when unconfigured" + ); + } + } + + std::panic::set_hook(Box::new(|_| {})); + + // should panic when there are fewer addresses than the max funding stream address index. + let expected_panic_num_addresses = std::panic::catch_unwind(|| { + testnet::Parameters::build().with_pre_nu6_funding_streams(ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 10, + addresses: vec![], + }]), + ..Default::default() + }); + }); + + // should panic when sum of numerators is greater than funding stream denominator. + let expected_panic_numerator = std::panic::catch_unwind(|| { + testnet::Parameters::build().with_pre_nu6_funding_streams(ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 101, + addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET + .map(Into::into) + .to_vec(), + }]), + ..Default::default() + }); + }); + + // drop panic hook before expecting errors. + let _ = std::panic::take_hook(); + + expected_panic_num_addresses.expect_err("should panic when there are too few addresses"); + expected_panic_numerator.expect_err( + "should panic when sum of numerators is greater than funding stream denominator", + ); +} diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 98f37d6998c..7f95a0133c1 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -2,7 +2,7 @@ //! //! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies -use std::{collections::HashMap, str::FromStr}; +use std::collections::HashMap; use zebra_chain::{ amount::{Amount, Error, NonNegative}, @@ -32,7 +32,7 @@ pub fn funding_stream_values( let funding_streams = network.funding_streams(height); if funding_streams.height_range().contains(&height) { let block_subsidy = block_subsidy(height, network)?; - for recipient in funding_streams.recipients() { + for (&receiver, recipient) in funding_streams.recipients() { // - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`: // https://zips.z.cash/protocol/protocol.pdf#subsidies // - In Rust, "integer division rounds towards zero": @@ -41,37 +41,13 @@ pub fn funding_stream_values( let amount_value = ((block_subsidy * recipient.numerator())? / FUNDING_STREAM_RECEIVER_DENOMINATOR)?; - results.insert(recipient.receiver(), amount_value); + results.insert(receiver, amount_value); } } } Ok(results) } -/// Returns the address change period -/// as described in [protocol specification §7.10][7.10] -/// -/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams -fn funding_stream_address_period(height: Height, network: &Network) -> u32 { - // Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`, - // - // - // Note that the brackets make it so the post blossom halving interval is added to the total. - // - // In Rust, "integer division rounds towards zero": - // - // This is the same as `floor()`, because these numbers are all positive. - - 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; - - address_period - .try_into() - .expect("all values are positive and smaller than the input height") -} - /// Returns the position in the address slice for each funding stream /// as described in [protocol specification §7.10][7.10] /// @@ -90,8 +66,9 @@ fn funding_stream_address_index(height: Height, network: &Network) -> usize { let num_addresses = funding_streams .recipients() - .first() - .map(|recipient| recipient.addresses().len()) + .iter() + .next() + .map(|(_, recipient)| recipient.addresses().len()) .unwrap_or_default(); assert!(index > 0 && index <= num_addresses); @@ -108,35 +85,24 @@ pub fn funding_stream_address( height: Height, network: &Network, receiver: FundingStreamReceiver, -) -> transparent::Address { +) -> &transparent::Address { let index = funding_stream_address_index(height, network); let funding_streams = network.funding_streams(height); - let address = &funding_streams - .recipient_by_receiver(receiver) + funding_streams + .recipient(receiver) // TODO: Change return type to option and return None here instead of panicking .unwrap() .addresses() .get(index) // TODO: Change return type to option and return None here instead of panicking - .unwrap(); - transparent::Address::from_str(address).expect("address should deserialize") + .unwrap() } /// Return a human-readable name and a specification URL for the funding stream `receiver`. pub fn funding_stream_recipient_info( - network: &Network, - height: Height, receiver: FundingStreamReceiver, -) -> (String, &'static str) { - let name = network - .funding_streams(height) - .recipient_by_receiver(receiver) - // TODO: Replace with optional return type - .unwrap() - .name() - .to_string(); - - (name, FUNDING_STREAM_SPECIFICATION) +) -> (&'static str, &'static str) { + (receiver.name(), FUNDING_STREAM_SPECIFICATION) } /// Given a funding stream P2SH address, create a script and check if it is the same diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 62362c12628..ff135e46ee4 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -64,12 +64,8 @@ fn test_funding_stream_values() -> Result<(), Report> { fn test_funding_stream_addresses() -> Result<(), Report> { let _init_guard = zebra_test::init(); for network in Network::iter() { - for funding_stream_recipient in network.pre_nu6_funding_streams().recipients() { - let receiver = funding_stream_recipient.receiver(); - for address in funding_stream_recipient.addresses() { - let address = - transparent::Address::from_str(address).expect("address should deserialize"); - + for (receiver, recipient) in network.pre_nu6_funding_streams().recipients() { + for address in recipient.addresses() { assert_eq!( address.network_kind(), network.kind(), diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 4f965267c65..00f4f8b4460 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -16,7 +16,7 @@ use tracing::Span; use zebra_chain::{ parameters::{ - testnet::{self, ConfiguredActivationHeights}, + testnet::{self, ConfiguredActivationHeights, ConfiguredFundingStreams}, Magic, Network, NetworkKind, }, work::difficulty::U256, @@ -641,6 +641,7 @@ impl<'de> Deserialize<'de> for Config { D: Deserializer<'de>, { #[derive(Deserialize)] + #[serde(deny_unknown_fields)] struct DTestnetParameters { network_name: Option, network_magic: Option<[u8; 4]>, @@ -649,6 +650,8 @@ impl<'de> Deserialize<'de> for Config { disable_pow: Option, genesis_hash: Option, activation_heights: Option, + pre_nu6_funding_streams: Option, + post_nu6_funding_streams: Option, } #[derive(Deserialize)] @@ -736,6 +739,8 @@ impl<'de> Deserialize<'de> for Config { disable_pow, genesis_hash, activation_heights, + pre_nu6_funding_streams, + post_nu6_funding_streams, }), ) => { let mut params_builder = testnet::Parameters::build(); @@ -758,6 +763,14 @@ 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 diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 12282a01dcc..7c75a02779c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -1199,10 +1199,7 @@ where .iter() .map(|(receiver, value)| { let address = funding_stream_address(height, &network, *receiver); - ( - *receiver, - FundingStream::new(&network, height, *receiver, *value, address), - ) + (*receiver, FundingStream::new(*receiver, *value, address)) }) .collect(); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index a5d3dc13093..899566ee9a8 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -373,7 +373,7 @@ pub fn standard_coinbase_outputs( // Optional TODO: move this into a zebra_consensus function? let funding_streams: HashMap< FundingStreamReceiver, - (Amount, transparent::Address), + (Amount, &transparent::Address), > = funding_streams .into_iter() .map(|(receiver, amount)| { @@ -398,17 +398,17 @@ pub fn standard_coinbase_outputs( /// If `like_zcashd` is true, try to match the coinbase transactions generated by `zcashd` /// in the `getblocktemplate` RPC. fn combine_coinbase_outputs( - funding_streams: HashMap, transparent::Address)>, + funding_streams: HashMap, &transparent::Address)>, miner_address: &transparent::Address, miner_reward: Amount, like_zcashd: bool, ) -> Vec<(Amount, transparent::Script)> { // Combine all the funding streams with the miner reward. - let mut coinbase_outputs: Vec<(Amount, transparent::Address)> = funding_streams + let mut coinbase_outputs: Vec<(Amount, &transparent::Address)> = funding_streams .into_iter() .map(|(_receiver, (amount, address))| (amount, address)) .collect(); - coinbase_outputs.push((miner_reward, miner_address.clone())); + coinbase_outputs.push((miner_reward, miner_address)); let mut coinbase_outputs: Vec<(Amount, transparent::Script)> = coinbase_outputs .iter() diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs index 5dd8221f3eb..f5ac478bf6b 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -2,8 +2,7 @@ use zebra_chain::{ amount::{Amount, NonNegative}, - block::Height, - parameters::{subsidy::FundingStreamReceiver, Network}, + parameters::subsidy::FundingStreamReceiver, transparent, }; use zebra_consensus::funding_stream_recipient_info; @@ -66,20 +65,18 @@ pub struct FundingStream { impl FundingStream { /// Convert a `receiver`, `value`, and `address` into a `FundingStream` response. pub fn new( - network: &Network, - height: Height, receiver: FundingStreamReceiver, value: Amount, - address: transparent::Address, + address: &transparent::Address, ) -> FundingStream { - let (recipient, specification) = funding_stream_recipient_info(network, height, receiver); + let (recipient, specification) = funding_stream_recipient_info(receiver); FundingStream { recipient: recipient.to_string(), specification: specification.to_string(), value: value.into(), value_zat: value, - address, + address: address.clone(), } } } diff --git a/zebrad/tests/common/configs/v1.9.0.toml b/zebrad/tests/common/configs/v1.9.0.toml index b99a1e38182..31062b62b80 100644 --- a/zebrad/tests/common/configs/v1.9.0.toml +++ b/zebrad/tests/common/configs/v1.9.0.toml @@ -74,6 +74,126 @@ Canopy = 1_028_500 NU5 = 1_842_420 NU6 = 2_000_000 +[network.testnet_parameters.pre_nu6_funding_streams.height_range] +start = 0 +end = 100 + +[[network.testnet_parameters.post_nu6_funding_streams.recipients]] +receiver = "ECC" +numerator = 7 +addresses = [ + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", +] + +[[network.testnet_parameters.post_nu6_funding_streams.recipients]] +receiver = "ZcashFoundation" +numerator = 5 +addresses = [ + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", +] + + [rpc] debug_force_finished_sync = false parallel_cpu_threads = 0