Skip to content

Commit

Permalink
change(consensus): Add lockbox funding stream (#8694)
Browse files Browse the repository at this point in the history
* Addresses clippy lints

* checks network magic and returns early from `is_regtest()`

* Moves  `subsidy.rs` to `zebra-chain`, refactors funding streams into structs, splits them into pre/post NU6 funding streams, and adds them as a field on `testnet::Parameters`

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

Minor refactors

* Empties recipients list

* Adds a comment on num_addresses calculation being invalid for configured Testnets, but that being okay since configured testnet parameters are checked when they're being built

* Documentation fixes, minor cleanup, renames a test, adds TODOs, and fixes test logic

* Removes unnecessary `ParameterSubsidy` impl for &Network, adds docs and TODOs

* Adds a "deferred" FundingStreamReceiver, adds a post-NU6 funding streams, updates the `miner_fees_are_valid()` and `subsidy_is_valid()` functions to check that the deferred pool contribution is valid and that there are no unclaimed block subsidies after NU6 activation, and adds some TODOs

* adds `lockbox_input_value()` fn

* Adds TODOs for linking to relevant ZIPs and updating height ranges

* Adds `nu6_lockbox_funding_stream` acceptance test

* updates funding stream values test to check post-NU6 funding streams too, adds Mainnet/Testnet NU6 activation heights, fixes lints/compilation issue

* Reverts Mainnet/Testnet NU6 activation height definitions, updates `test_funding_stream_values()` to use a configured testnet with the post-NU6 Mainnet funding streams height range

* reverts unnecessary refactor

* appease clippy

* Adds a test for `lockbox_input_value()`

* Applies suggestions from code review

* Fixes potential panic

* Fixes bad merge

* Update zebra-chain/src/parameters/network_upgrade.rs

* Updates acceptance test to check that invalid blocks are rejected

* Checks that the original valid block template at height 2 is accepted as a block submission

* Reverts changes for coinbase should balance exactly ZIP

* updates test name

* Updates deferred pool funding stream name to "Lockbox", moves post-NU6 height ranges to constants, updates TODO

* Updates `get_block_subsidy()` RPC method to exclude lockbox funding stream from `fundingstreams` field

* Adds a TODO for updating `FundingStreamReceiver::name()` method docs

* Updates `FundingStreamRecipient::new()` to accept an iterator of items instead of an option of an iterator, updates a comment quoting the coinbase transaction balance consensus rule to note that the current code is inconsistent with the protocol spec, adds a TODO for updating the quote there once the protocol spec has been updated.

* Uses FPF Testnet address for post-NU6 testnet funding streams

* Updates the NU6 consensus branch id

---------

Co-authored-by: Pili Guerra <[email protected]>
  • Loading branch information
arya2 and mpguerra authored Aug 1, 2024
1 parent 45261a2 commit e56ee4c
Show file tree
Hide file tree
Showing 14 changed files with 587 additions and 71 deletions.
4 changes: 4 additions & 0 deletions zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,7 @@ required-features = ["bench"]
[[bench]]
name = "redpallas"
harness = false

[lints.rust]
# TODO: Remove this once it's no longer needed for NU6.
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nu6"))'] }
76 changes: 71 additions & 5 deletions zebra-chain/src/parameters/network/subsidy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,25 @@ pub enum FundingStreamReceiver {

/// The Major Grants (Zcash Community Grants) funding stream.
MajorGrants,
/// The deferred pool contribution.
// TODO: Add link to lockbox stream ZIP
Deferred,
}

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
// TODO: Update method documentation with a reference to https://zips.z.cash/draft-nuttycom-funding-allocation once its
// status is updated to 'Proposed'.
pub fn name(self) -> &'static str {
match self {
FundingStreamReceiver::Ecc => "Electric Coin Company",
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
FundingStreamReceiver::MajorGrants => "Major Grants",
// TODO: Find out what this should be called and update the funding stream name.
FundingStreamReceiver::Deferred => "Lockbox",
}
}
}
Expand Down Expand Up @@ -181,7 +188,7 @@ lazy_static! {
recipients: [
(
FundingStreamReceiver::Ecc,
FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter()),
FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET),
),
(
FundingStreamReceiver::ZcashFoundation,
Expand All @@ -199,8 +206,21 @@ lazy_static! {
/// The post-NU6 funding streams for Mainnet
// TODO: Add a reference to lockbox stream ZIP, this is currently based on https://zips.z.cash/draft-nuttycom-funding-allocation
pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams {
height_range: Height(2_726_400)..Height(3_146_400),
recipients: HashMap::new()
// TODO: Adjust this height range and recipient list once a proposal is selected
height_range: POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET,
recipients: [
(
FundingStreamReceiver::Deferred,
FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
),
(
FundingStreamReceiver::MajorGrants,
// TODO: Update these addresses
FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET),
),
]
.into_iter()
.collect()
};

/// The pre-NU6 funding streams for Testnet as described in [protocol specification §7.10.1][7.10.1]
Expand Down Expand Up @@ -229,11 +249,45 @@ lazy_static! {
// TODO: Add a reference to lockbox stream ZIP, this is currently based on the number of blocks between the
// start and end heights for Mainnet in https://zips.z.cash/draft-nuttycom-funding-allocation
pub static ref POST_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams {
height_range: Height(2_942_000)..Height(3_362_000),
recipients: HashMap::new()
// TODO: Adjust this height range and recipient list once a proposal is selected
height_range: POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET,
recipients: [
(
FundingStreamReceiver::Deferred,
FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
),
(
FundingStreamReceiver::MajorGrants,
// TODO: Update these addresses
FundingStreamRecipient::new(8, POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET),
),
]
.into_iter()
.collect()
};
}

/// The start height of post-NU6 funding streams on Mainnet
// TODO: Add a reference to lockbox stream ZIP, this is currently based on https://zips.z.cash/draft-nuttycom-funding-allocation
const POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET: u32 = 2_726_400;

/// The start height of post-NU6 funding streams on Testnet
// TODO: Add a reference to lockbox stream ZIP, this is currently based on https://zips.z.cash/draft-nuttycom-funding-allocation
const POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET: u32 = 2_942_000;

/// The number of blocks contained in the post-NU6 funding streams height ranges on Mainnet or Testnet.
const POST_NU6_FUNDING_STREAM_NUM_BLOCKS: u32 = 420_000;

/// The post-NU6 funding stream height range on Mainnet
const POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET: std::ops::Range<Height> =
Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET)
..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);

/// The post-NU6 funding stream height range on Testnet
const POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET: std::ops::Range<Height> =
Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET)
..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);

/// Address change interval function here as a constant
/// as described in [protocol specification §7.10.1][7.10.1].
///
Expand Down Expand Up @@ -402,6 +456,18 @@ pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRES
pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];

/// Number of addresses for each post-NU6 funding stream in the Testnet.
/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
/// however we know this value beforehand so we prefer to make it a constant instead.
///
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 13;

/// List of addresses for the Major Grants post-NU6 funding stream in the Testnet administered by the Financial Privacy Fund (FPF).
pub const POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET: [&str;
POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
["t2HifwjUj9uyxr9bknR8LFuQbc98c3vkXtu"; POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];

/// Returns the address change period
/// as described in [protocol specification §7.10][7.10]
///
Expand Down
12 changes: 8 additions & 4 deletions zebra-chain/src/parameters/network/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ pub struct ConfiguredFundingStreamRecipient {
/// 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>,
pub addresses: Option<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),
FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()),
)
}
}
Expand Down Expand Up @@ -133,8 +133,12 @@ impl ConfiguredFundingStreams {
))
.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.
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, \
Expand Down
34 changes: 21 additions & 13 deletions zebra-chain/src/parameters/network/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,19 +320,23 @@ fn check_configured_funding_stream_constraints() {
recipients: Some(vec![ConfiguredFundingStreamRecipient {
receiver: FundingStreamReceiver::Ecc,
numerator: 20,
addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET
.map(Into::into)
.to_vec(),
addresses: Some(
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(),
addresses: Some(
FUNDING_STREAM_ECC_ADDRESSES_TESTNET
.map(Into::into)
.to_vec(),
),
}]),
..Default::default()
},
Expand Down Expand Up @@ -398,7 +402,7 @@ fn check_configured_funding_stream_constraints() {
recipients: Some(vec![ConfiguredFundingStreamRecipient {
receiver: FundingStreamReceiver::Ecc,
numerator: 10,
addresses: vec![],
addresses: Some(vec![]),
}]),
..Default::default()
});
Expand All @@ -410,9 +414,11 @@ fn check_configured_funding_stream_constraints() {
recipients: Some(vec![ConfiguredFundingStreamRecipient {
receiver: FundingStreamReceiver::Ecc,
numerator: 101,
addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET
.map(Into::into)
.to_vec(),
addresses: Some(
FUNDING_STREAM_ECC_ADDRESSES_TESTNET
.map(Into::into)
.to_vec(),
),
}]),
..Default::default()
});
Expand All @@ -424,9 +430,11 @@ fn check_configured_funding_stream_constraints() {
recipients: Some(vec![ConfiguredFundingStreamRecipient {
receiver: FundingStreamReceiver::Ecc,
numerator: 10,
addresses: FUNDING_STREAM_ECC_ADDRESSES_MAINNET
.map(Into::into)
.to_vec(),
addresses: Some(
FUNDING_STREAM_ECC_ADDRESSES_MAINNET
.map(Into::into)
.to_vec(),
),
}]),
..Default::default()
});
Expand Down
11 changes: 7 additions & 4 deletions zebra-chain/src/parameters/network_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)]
(block::Height(903_000), Heartwood),
(block::Height(1_046_400), Canopy),
(block::Height(1_687_104), Nu5),
// TODO: Add NU6.
// TODO: Add NU6
// (block::Height(2_726_400), Nu6),
];

/// Fake mainnet network upgrade activation heights, used in tests.
Expand Down Expand Up @@ -124,7 +125,8 @@ pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)]
(block::Height(903_800), Heartwood),
(block::Height(1_028_500), Canopy),
(block::Height(1_842_420), Nu5),
// TODO: Add NU6.
// TODO: Add NU6
// (block::Height(2_942_000), Nu6),
];

/// Fake testnet network upgrade activation heights, used in tests.
Expand Down Expand Up @@ -214,8 +216,7 @@ pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] =
(Heartwood, ConsensusBranchId(0xf5b9230b)),
(Canopy, ConsensusBranchId(0xe9ff75a6)),
(Nu5, ConsensusBranchId(0xc2d6d0b4)),
// TODO: Use the real consensus branch ID once it's specified.
(Nu6, ConsensusBranchId(0xdeadc0de)),
(Nu6, ConsensusBranchId(0xc8e71055)),
];

/// The target block spacing before Blossom.
Expand Down Expand Up @@ -530,6 +531,8 @@ impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
#[cfg(zcash_unstable = "nu6")]
zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
}
}
}
Expand Down
31 changes: 26 additions & 5 deletions zebra-consensus/src/block/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use chrono::{DateTime, Utc};
use zebra_chain::{
amount::{Amount, Error as AmountError, NonNegative},
block::{Block, Hash, Header, Height},
parameters::{Network, NetworkUpgrade},
parameters::{subsidy::FundingStreamReceiver, Network, NetworkUpgrade},
transaction,
work::{
difficulty::{ExpandedDifficulty, ParameterDifficulty as _},
Expand Down Expand Up @@ -177,7 +177,7 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr
// Founders rewards are paid up to Canopy activation, on both mainnet and testnet.
// But we checkpoint in Canopy so founders reward does not apply for Zebra.
unreachable!("we cannot verify consensus rules before Canopy activation");
} else if halving_div < 4 {
} else if halving_div < 8 {
// Funding streams are paid from Canopy activation to the second halving
// Note: Canopy activation is at the first halving on mainnet, but not on testnet
// ZIP-1014 only applies to mainnet, ZIP-214 contains the specific rules for testnet
Expand All @@ -194,8 +194,16 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr
//
// https://zips.z.cash/protocol/protocol.pdf#fundingstreams
for (receiver, expected_amount) in funding_streams {
let address =
subsidy::funding_streams::funding_stream_address(height, network, receiver);
if receiver == FundingStreamReceiver::Deferred {
// The deferred pool contribution is checked in `miner_fees_are_valid()`
// TODO: Add link to lockbox stream ZIP
continue;
}

let address = subsidy::funding_streams::funding_stream_address(
height, network, receiver,
)
.expect("funding stream receivers other than the deferred pool must have an address");

let has_expected_output =
subsidy::funding_streams::filter_outputs_by_address(coinbase, address)
Expand Down Expand Up @@ -237,16 +245,29 @@ pub fn miner_fees_are_valid(
let block_subsidy = subsidy::general::block_subsidy(height, network)
.expect("a valid block subsidy for this height and network");

// TODO: Add link to lockbox stream ZIP
let expected_deferred_amount = subsidy::funding_streams::funding_stream_values(height, network)
.expect("we always expect a funding stream hashmap response even if empty")
.remove(&FundingStreamReceiver::Deferred)
.unwrap_or_default();

// # Consensus
//
// > The total value in zatoshi of transparent outputs from a coinbase transaction,
// > minus vbalanceSapling, minus vbalanceOrchard, MUST NOT be greater than the value
// > in zatoshi of block subsidy plus the transaction fees paid by transactions in this block.
//
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
//
// The expected lockbox funding stream output of the coinbase transaction is also subtracted
// from the block subsidy value plus the transaction fees paid by transactions in this block.
//
// TODO: Update the quote from the protocol specification once its been updated to reflect the changes in
// https://zips.z.cash/draft-nuttycom-funding-allocation and https://zips.z.cash/draft-hopwood-coinbase-balance.
let left = (transparent_value_balance - sapling_value_balance - orchard_value_balance)
.map_err(|_| SubsidyError::SumOverflow)?;
let right = (block_subsidy + block_miner_fees).map_err(|_| SubsidyError::SumOverflow)?;
let right = (block_subsidy + block_miner_fees - expected_deferred_amount)
.map_err(|_| SubsidyError::SumOverflow)?;

if left > right {
Err(SubsidyError::InvalidMinerFees)?;
Expand Down
Loading

0 comments on commit e56ee4c

Please sign in to comment.