Replaces Vec with HashMap, adds `ConfiguredFundingStreams` type and conversion logic with constraints.

Minor refactors
This commit is contained in:
Arya 2024-07-23 21:05:01 -04:00
parent 930c488bf7
commit 9a9464c9d1
11 changed files with 557 additions and 190 deletions

View File

@ -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<FundingStreamRecipient>,
/// 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<Height>,
/// Funding stream recipients by [`FundingStreamReceiver`].
recipients: HashMap<FundingStreamReceiver, FundingStreamRecipient>,
}
impl FundingStreams {
/// Creates a new [`FundingStreams`].
pub fn new(
height_range: std::ops::Range<Height>,
recipients: HashMap<FundingStreamReceiver, FundingStreamRecipient>,
) -> Self {
Self {
height_range,
recipients,
}
}
/// Returns height range where these [`FundingStreams`] should apply.
pub fn height_range(&self) -> &std::ops::Range<Height> {
&self.height_range
}
/// Returns recipients of these [`FundingStreams`].
pub fn recipients(&self) -> &[FundingStreamRecipient] {
pub fn recipients(&self) -> &HashMap<FundingStreamReceiver, FundingStreamRecipient> {
&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<String>,
/// Addresses for the funding stream recipient
addresses: Vec<transparent::Address>,
}
impl FundingStreamRecipient {
/// Creates a new [`FundingStreamRecipient`].
pub fn new(
receiver: FundingStreamReceiver,
name: impl Into<String>,
numerator: u64,
addresses: Vec<String>,
) -> Self {
pub fn new<I, T>(numerator: u64, addresses: I) -> Self
where
T: ToString,
I: IntoIterator<Item = T>,
{
Self {
receiver,
name: name.into(),
numerator,
addresses,
addresses: addresses
.into_iter()
.map(|addr| {
let addr = addr.to_string();
addr.parse()
.expect("funding stream address must deserialize")
})
.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(
FundingStreamReceiver::Ecc,
"Electric Coin Company",
7,
FUNDING_STREAM_ECC_ADDRESSES_MAINNET
.iter()
.map(ToString::to_string)
.collect(),
),
FundingStreamRecipient::new(
FundingStreamReceiver::ZcashFoundation,
"Zcash Foundation",
5,
FUNDING_STREAM_ZF_ADDRESSES_MAINNET
.iter()
.map(ToString::to_string)
.collect(),
),
FundingStreamRecipient::new(
FundingStreamReceiver::MajorGrants,
"Major Grants",
8,
FUNDING_STREAM_MG_ADDRESSES_MAINNET
.iter()
.map(ToString::to_string)
.collect(),
),
],
height_range: Height(1_046_400)..Height(2_726_400),
};
/// The pre-NU6 funding streams for Testnet
pub static ref PRE_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams {
recipients: vec![
FundingStreamRecipient::new(
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_MAINNET.iter()),
),
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_MAINNET),
),
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_MAINNET),
),
],
height_range: Height(1_028_500)..Height(2_796_000),
]
.into_iter()
.collect(),
};
/// 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),
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 {
height_range: Height(1_028_500)..Height(2_796_000),
recipients: [
(
FundingStreamReceiver::Ecc,
FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_TESTNET),
),
(
FundingStreamReceiver::ZcashFoundation,
FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_TESTNET),
),
(
FundingStreamReceiver::MajorGrants,
FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET),
),
]
.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)`,
// <https://zips.z.cash/protocol/protocol.pdf#fundingstreams>
//
// Note that the brackets make it so the post blossom halving interval is added to the total.
//
// In Rust, "integer division rounds towards zero":
// <https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators>
// 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")
}

View File

@ -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<String>,
}
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<std::ops::Range<Height>>,
/// Funding stream recipients, see [`FundingStreams::recipients`] for more details.
pub recipients: Option<Vec<ConfiguredFundingStreamRecipient>>,
}
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<u32>,
@ -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
}

View File

@ -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",
);
}

View File

@ -197,7 +197,7 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr
subsidy::funding_streams::funding_stream_address(height, network, receiver);
let has_expected_output =
subsidy::funding_streams::filter_outputs_by_address(coinbase, &address)
subsidy::funding_streams::filter_outputs_by_address(coinbase, address)
.iter()
.map(zebra_chain::transparent::Output::value)
.any(|value| value == expected_amount);

View File

@ -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)`,
// <https://zips.z.cash/protocol/protocol.pdf#fundingstreams>
//
// Note that the brackets make it so the post blossom halving interval is added to the total.
//
// In Rust, "integer division rounds towards zero":
// <https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators>
// 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

View File

@ -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(),
@ -77,7 +73,7 @@ fn test_funding_stream_addresses() -> Result<(), Report> {
);
// Asserts if address is not a P2SH address.
let _script = new_coinbase_script(&address);
let _script = new_coinbase_script(address);
}
}
}

View File

@ -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<String>,
network_magic: Option<[u8; 4]>,
@ -649,6 +650,8 @@ impl<'de> Deserialize<'de> for Config {
disable_pow: Option<bool>,
genesis_hash: Option<String>,
activation_heights: Option<ConfiguredActivationHeights>,
pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
}
#[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

View File

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

View File

@ -373,7 +373,7 @@ pub fn standard_coinbase_outputs(
// Optional TODO: move this into a zebra_consensus function?
let funding_streams: HashMap<
FundingStreamReceiver,
(Amount<NonNegative>, transparent::Address),
(Amount<NonNegative>, &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<FundingStreamReceiver, (Amount<NonNegative>, transparent::Address)>,
funding_streams: HashMap<FundingStreamReceiver, (Amount<NonNegative>, &transparent::Address)>,
miner_address: &transparent::Address,
miner_reward: Amount<NonNegative>,
like_zcashd: bool,
) -> Vec<(Amount<NonNegative>, transparent::Script)> {
// Combine all the funding streams with the miner reward.
let mut coinbase_outputs: Vec<(Amount<NonNegative>, transparent::Address)> = funding_streams
let mut coinbase_outputs: Vec<(Amount<NonNegative>, &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<NonNegative>, transparent::Script)> = coinbase_outputs
.iter()

View File

@ -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<NonNegative>,
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(),
}
}
}

View File

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