From fa5145849b3de80b12c4ed198bd736304ccd7d4e Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Sun, 7 Nov 2021 16:55:37 -0300 Subject: [PATCH 01/17] validate funding stream addresses --- zebra-consensus/src/block/check.rs | 38 ++++- .../src/block/subsidy/funding_streams.rs | 133 +++++++++++++++-- zebra-consensus/src/block/subsidy/general.rs | 4 +- zebra-consensus/src/block/tests.rs | 2 +- zebra-consensus/src/error.rs | 7 +- zebra-consensus/src/parameters/subsidy.rs | 136 ++++++++++++++++++ 6 files changed, 297 insertions(+), 23 deletions(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 9c7b4a959dc..7ff82d27e4a 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -11,7 +11,10 @@ use zebra_chain::{ work::{difficulty::ExpandedDifficulty, equihash}, }; -use crate::{error::*, parameters::SLOW_START_INTERVAL}; +use crate::{ + error::*, + parameters::{subsidy::FundingStreamReceiver, SLOW_START_INTERVAL}, +}; use super::subsidy; @@ -134,6 +137,7 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro // 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 + // funding stream amount values let funding_streams = subsidy::funding_streams::funding_stream_values(height, network) .expect("We always expect a funding stream hashmap response even if empty"); @@ -143,17 +147,39 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro .collect(); let output_amounts = subsidy::general::output_amounts(coinbase); + // funding stream addresses + let address_ecc = subsidy::funding_streams::funding_stream_address( + height, + network, + FundingStreamReceiver::Ecc, + ); + let address_zf = subsidy::funding_streams::funding_stream_address( + height, + network, + FundingStreamReceiver::ZcashFoundation, + ); + let address_mg = subsidy::funding_streams::funding_stream_address( + height, + network, + FundingStreamReceiver::MajorGrants, + ); + + let ecc_address = subsidy::funding_streams::find_output_with_address(coinbase, address_ecc); + let zf_address = subsidy::funding_streams::find_output_with_address(coinbase, address_zf); + let mg_address = subsidy::funding_streams::find_output_with_address(coinbase, address_mg); + // Consensus rule:[Canopy onward] The coinbase transaction at block height `height` // MUST contain at least one output per funding stream `fs` active at `height`, // that pays `fs.Value(height)` zatoshi in the prescribed way to the stream's // recipient address represented by `fs.AddressList[fs.AddressIndex(height)] - - // TODO: We are only checking each fundign stream reward is present in the - // coinbase transaction outputs but not the recipient addresses. if funding_stream_amounts.is_subset(&output_amounts) { - Ok(()) + if !ecc_address.is_empty() && !zf_address.is_empty() && !mg_address.is_empty() { + Ok(()) + } else { + Err(SubsidyError::FundingStreamAddressNotFound)? + } } else { - Err(SubsidyError::FundingStreamNotFound)? + Err(SubsidyError::FundingStreamValueNotFound)? } } else { // Future halving, with no founders reward or funding streams diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 364124917cd..5796c96cb7f 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -1,29 +1,27 @@ -//! Funding Streams calculations. - [§7.7][7.7] +//! Funding Streams calculations. - [§7.8][7.8] //! -//! [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies use zebra_chain::{ amount::{Amount, Error, NonNegative}, block::Height, parameters::{Network, NetworkUpgrade::*}, + serialization::ZcashSerialize, + transaction::Transaction, + transparent::{Address, Output, Script}, }; -use crate::{ - block::subsidy::general::block_subsidy, - parameters::subsidy::{ - FundingStreamReceiver, FUNDING_STREAM_HEIGHT_RANGES, FUNDING_STREAM_RECEIVER_DENOMINATOR, - FUNDING_STREAM_RECEIVER_NUMERATORS, - }, -}; +use crate::{block::subsidy::general::block_subsidy, parameters::subsidy::*}; + +use std::{collections::HashMap, str::FromStr}; #[cfg(test)] mod tests; /// Returns the `fs.Value(height)` for each stream receiver -/// as described in [protocol specification §7.7][7.7] +/// as described in [protocol specification §7.8][7.8] /// -/// [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies -use std::collections::HashMap; +/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies pub fn funding_stream_values( height: Height, network: Network, @@ -50,3 +48,114 @@ pub fn funding_stream_values( } Ok(results) } + +/// Returns the minumum height after the first halving +/// as described in [protocol specification §7.10][7.10] +/// +/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams +fn height_for_halving(network: Network) -> Height { + // First halving on Mainnet is at Canopy + // while in Testnet is at block 1_116_000 + // https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams + match network { + Network::Mainnet => Canopy + .activation_height(network) + .expect("canopy activation height should be available"), + Network::Testnet => Height(1_116_000), + } +} + +/// 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 { + (height.0 + (POST_BLOSSOM_HALVING_INTERVAL.0) - (height_for_halving(network).0)) + / (FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL.0) +} + +/// Returns the position in the address slice for each funding stream +/// as described in [protocol specification §7.10][7.10] +/// +/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams +fn funding_stream_address_index(height: Height, network: Network) -> usize { + let num_addresses = match network { + Network::Mainnet => FUNDING_STREAMS_N_ADDRESSES_MAINNET, + Network::Testnet => FUNDING_STREAMS_N_ADDRESSES_TESTNET, + }; + + let index = 1u32 + .checked_add(funding_stream_address_period(height, network)) + .unwrap() + .checked_sub(funding_stream_address_period( + FUNDING_STREAM_HEIGHT_RANGES.get(&network).unwrap().start, + network, + )) + .unwrap() as usize; + assert!(index > 0 && index <= num_addresses); + + index +} + +/// Return the address corresponding to this height for this funding stream receiver. +pub fn funding_stream_address( + height: Height, + network: Network, + receiver: FundingStreamReceiver, +) -> Address { + let index = funding_stream_address_index(height, network) - 1; + + let address = match receiver { + FundingStreamReceiver::Ecc => match network { + Network::Mainnet => FUNDING_STREAM_ECC_ADDRESSES_MAINNET[index].to_string(), + Network::Testnet => FUNDING_STREAM_ECC_ADDRESSES_TESTNET[index].to_string(), + }, + FundingStreamReceiver::ZcashFoundation => match network { + Network::Mainnet => FUNDING_STREAM_ZF_ADDRESSES_MAINNET[index].to_string(), + Network::Testnet => FUNDING_STREAM_ZF_ADDRESSES_TESTNET[index].to_string(), + }, + FundingStreamReceiver::MajorGrants => match network { + Network::Mainnet => FUNDING_STREAM_MG_ADDRESSES_MAINNET[index].to_string(), + Network::Testnet => FUNDING_STREAM_MG_ADDRESSES_TESTNET[index].to_string(), + }, + }; + Address::from_str(&address).expect("Address should deserialize") +} + +/// Given a founders reward address, create a script and check if it is the same +/// as the given lock_script as described in [protocol specification §7.10][7.10] +/// +/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams. +pub fn check_script_form(lock_script: Script, address: Address) -> bool { + let mut address_hash = address + .zcash_serialize_to_vec() + .expect("we should get address bytes here"); + + address_hash = address_hash[2..22].to_vec(); + address_hash.insert(0, OpCode::Push20Bytes as u8); + address_hash.insert(0, OpCode::Hash160 as u8); + address_hash.insert(address_hash.len(), OpCode::Equal as u8); + if lock_script.as_raw_bytes().len() == address_hash.len() + && lock_script == Script::new(&address_hash) + { + return true; + } + false +} + +/// Returns a list of outputs in `Transaction`, which have a script address equal to `Address`. +pub fn find_output_with_address(transaction: &Transaction, address: Address) -> Vec { + transaction + .outputs() + .iter() + .filter(|o| check_script_form(o.lock_script.clone(), address)) + .cloned() + .collect() +} + +/// Script opcodes needed to compare the `lock_script` with the funding stream reward address. +pub enum OpCode { + Equal = 0x87, + Hash160 = 0xa9, + Push20Bytes = 0x14, +} diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 22068d5b783..eba741719ba 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -16,9 +16,9 @@ use crate::parameters::subsidy::*; /// The divisor used for halvings. /// -/// `1 << Halving(height)`, as described in [protocol specification §7.7][7.7] +/// `1 << Halving(height)`, as described in [protocol specification §7.8][7.8] /// -/// [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies pub fn halving_divisor(height: Height, network: Network) -> u64 { let blossom_height = Blossom .activation_height(network) diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index d5d6296c6ad..d8940ce18d7 100644 --- a/zebra-consensus/src/block/tests.rs +++ b/zebra-consensus/src/block/tests.rs @@ -482,7 +482,7 @@ fn funding_stream_validation_failure() -> Result<(), Report> { // Validate it let result = check::subsidy_is_valid(&block, network).unwrap_err(); let expected = BlockError::Transaction(TransactionError::Subsidy( - SubsidyError::FundingStreamNotFound, + SubsidyError::FundingStreamValueNotFound, )); assert_eq!(expected, result); diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 0622f06b5d4..3646b02e909 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -22,8 +22,11 @@ pub enum SubsidyError { #[error("founders reward output not found")] FoundersRewardNotFound, - #[error("funding stream output not found")] - FundingStreamNotFound, + #[error("funding stream output with value not found")] + FundingStreamValueNotFound, + + #[error("funding stream output with address not found")] + FundingStreamAddressNotFound, } #[derive(Error, Clone, Debug, PartialEq, Eq)] diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 2afadccd1d0..8e20d9ba5a7 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -82,3 +82,139 @@ lazy_static! { hash_map }; } + +/// Address change interval function here as a constant +/// as described in [protocol specification §7.9.1][7.9.1]. +/// +/// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams +pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: Height = + Height(POST_BLOSSOM_HALVING_INTERVAL.0 / 48); + +/// Number of addresses for each funding stream in the Mainnet +pub const FUNDING_STREAMS_N_ADDRESSES_MAINNET: usize = 48; + +/// List of addresses for the ECC funding stream in the Mainnet. +pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_N_ADDRESSES_MAINNET] = [ + "t3LmX1cxWPPPqL4TZHx42HU3U5ghbFjRiif", + "t3Toxk1vJQ6UjWQ42tUJz2rV2feUWkpbTDs", + "t3ZBdBe4iokmsjdhMuwkxEdqMCFN16YxKe6", + "t3ZuaJziLM8xZ32rjDUzVjVtyYdDSz8GLWB", + "t3bAtYWa4bi8VrtvqySxnbr5uqcG9czQGTZ", + "t3dktADfb5Rmxncpe1HS5BRS5Gcj7MZWYBi", + "t3hgskquvKKoCtvxw86yN7q8bzwRxNgUZmc", + "t3R1VrLzwcxAZzkX4mX3KGbWpNsgtYtMntj", + "t3ff6fhemqPMVujD3AQurxRxTdvS1pPSaa2", + "t3cEUQFG3KYnFG6qYhPxSNgGi3HDjUPwC3J", + "t3WR9F5U4QvUFqqx9zFmwT6xFqduqRRXnaa", + "t3PYc1LWngrdUrJJbHkYPCKvJuvJjcm85Ch", + "t3bgkjiUeatWNkhxY3cWyLbTxKksAfk561R", + "t3Z5rrR8zahxUpZ8itmCKhMSfxiKjUp5Dk5", + "t3PU1j7YW3fJ67jUbkGhSRto8qK2qXCUiW3", + "t3S3yaT7EwNLaFZCamfsxxKwamQW2aRGEkh", + "t3eutXKJ9tEaPSxZpmowhzKhPfJvmtwTEZK", + "t3gbTb7brxLdVVghSPSd3ycGxzHbUpukeDm", + "t3UCKW2LrHFqPMQFEbZn6FpjqnhAAbfpMYR", + "t3NyHsrnYbqaySoQqEQRyTWkjvM2PLkU7Uu", + "t3QEFL6acxuZwiXtW3YvV6njDVGjJ1qeaRo", + "t3PdBRr2S1XTDzrV8bnZkXF3SJcrzHWe1wj", + "t3ZWyRPpWRo23pKxTLtWsnfEKeq9T4XPxKM", + "t3he6QytKCTydhpztykFsSsb9PmBT5JBZLi", + "t3VWxWDsLb2TURNEP6tA1ZSeQzUmPKFNxRY", + "t3NmWLvZkbciNAipauzsFRMxoZGqmtJksbz", + "t3cKr4YxVPvPBG1mCvzaoTTdBNokohsRJ8n", + "t3T3smGZn6BoSFXWWXa1RaoQdcyaFjMfuYK", + "t3gkDUe9Gm4GGpjMk86TiJZqhztBVMiUSSA", + "t3eretuBeBXFHe5jAqeSpUS1cpxVh51fAeb", + "t3dN8g9zi2UGJdixGe9txeSxeofLS9t3yFQ", + "t3S799pq9sYBFwccRecoTJ3SvQXRHPrHqvx", + "t3fhYnv1S5dXwau7GED3c1XErzt4n4vDxmf", + "t3cmE3vsBc5xfDJKXXZdpydCPSdZqt6AcNi", + "t3h5fPdjJVHaH4HwynYDM5BB3J7uQaoUwKi", + "t3Ma35c68BgRX8sdLDJ6WR1PCrKiWHG4Da9", + "t3LokMKPL1J8rkJZvVpfuH7dLu6oUWqZKQK", + "t3WFFGbEbhJWnASZxVLw2iTJBZfJGGX73mM", + "t3L8GLEsUn4QHNaRYcX3EGyXmQ8kjpT1zTa", + "t3PgfByBhaBSkH8uq4nYJ9ZBX4NhGCJBVYm", + "t3WecsqKDhWXD4JAgBVcnaCC2itzyNZhJrv", + "t3ZG9cSfopnsMQupKW5v9sTotjcP5P6RTbn", + "t3hC1Ywb5zDwUYYV8LwhvF5rZ6m49jxXSG5", + "t3VgMqDL15ZcyQDeqBsBW3W6rzfftrWP2yB", + "t3LC94Y6BwLoDtBoK2NuewaEbnko1zvR9rm", + "t3cWCUZJR3GtALaTcatrrpNJ3MGbMFVLRwQ", + "t3YYF4rPLVxDcF9hHFsXyc5Yq1TFfbojCY6", + "t3XHAGxRP2FNfhAjxGjxbrQPYtQQjc3RCQD", +]; + +/// List of addresses for the Zcash Foundation funding stream in the Mainnet. +pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; 48] = + ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_N_ADDRESSES_MAINNET]; + +/// List of addresses for the Major Grants funding stream in the Mainnet. +pub const FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; 48] = + ["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_N_ADDRESSES_MAINNET]; + +/// Number of addresses for each funding stream in the Mainnet +pub const FUNDING_STREAMS_N_ADDRESSES_TESTNET: usize = 51; + +/// List of addresses for the ECC funding stream in the Testnet. +pub const FUNDING_STREAM_ECC_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_N_ADDRESSES_TESTNET] = [ + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t2NNHrgPpE388atmWSF4DxAb3xAoW5Yp45M", + "t2VMN28itPyMeMHBEd9Z1hm6YLkQcGA1Wwe", + "t2CHa1TtdfUV8UYhNm7oxbzRyfr8616BYh2", + "t2F77xtr28U96Z2bC53ZEdTnQSUAyDuoa67", + "t2ARrzhbgcpoVBDPivUuj6PzXzDkTBPqfcT", + "t278aQ8XbvFR15mecRguiJDQQVRNnkU8kJw", + "t2Dp1BGnZsrTXZoEWLyjHmg3EPvmwBnPDGB", + "t2KzeqXgf4ju33hiSqCuKDb8iHjPCjMq9iL", + "t2Nyxqv1BiWY1eUSiuxVw36oveawYuo18tr", + "t2DKFk5JRsVoiuinK8Ti6eM4Yp7v8BbfTyH", + "t2CUaBca4k1x36SC4q8Nc8eBoqkMpF3CaLg", + "t296SiKL7L5wvFmEdMxVLz1oYgd6fTfcbZj", + "t29fBCFbhgsjL3XYEZ1yk1TUh7eTusB6dPg", + "t2FGofLJXa419A76Gpf5ncxQB4gQXiQMXjK", + "t2ExfrnRVnRiXDvxerQ8nZbcUQvNvAJA6Qu", + "t28JUffLp47eKPRHKvwSPzX27i9ow8LSXHx", + "t2JXWPtrtyL861rFWMZVtm3yfgxAf4H7uPA", + "t2QdgbJoWfYHgyvEDEZBjHmgkr9yNJff3Hi", + "t2QW43nkco8r32ZGRN6iw6eSzyDjkMwCV3n", + "t2DgYDXMJTYLwNcxighQ9RCgPxMVATRcUdC", + "t2Bop7dg33HGZx3wunnQzi2R2ntfpjuti3M", + "t2HVeEwovcLq9RstAbYkqngXNEsCe2vjJh9", + "t2HxbP5keQSx7p592zWQ5bJ5GrMmGDsV2Xa", + "t2TJzUg2matao3mztBRJoWnJY6ekUau6tPD", + "t29pMzxmo6wod25YhswcjKv3AFRNiBZHuhj", + "t2QBQMRiJKYjshJpE6RhbF7GLo51yE6d4wZ", + "t2F5RqnqguzZeiLtYHFx4yYfy6pDnut7tw5", + "t2CHvyZANE7XCtg8AhZnrcHCC7Ys1jJhK13", + "t2BRzpMdrGWZJ2upsaNQv6fSbkbTy7EitLo", + "t2BFixHGQMAWDY67LyTN514xRAB94iEjXp3", + "t2Uvz1iVPzBEWfQBH1p7NZJsFhD74tKaG8V", + "t2CmFDj5q6rJSRZeHf1SdrowinyMNcj438n", + "t2ErNvWEReTfPDBaNizjMPVssz66aVZh1hZ", + "t2GeJQ8wBUiHKDVzVM5ZtKfY5reCg7CnASs", + "t2L2eFtkKv1G6j55kLytKXTGuir4raAy3yr", + "t2EK2b87dpPazb7VvmEGc8iR6SJ289RywGL", + "t2DJ7RKeZJxdA4nZn8hRGXE8NUyTzjujph9", + "t2K1pXo4eByuWpKLkssyMLe8QKUbxnfFC3H", + "t2TB4mbSpuAcCWkH94Leb27FnRxo16AEHDg", + "t2Phx4gVL4YRnNsH3jM1M7jE4Fo329E66Na", + "t2VQZGmeNomN8c3USefeLL9nmU6M8x8CVzC", + "t2RicCvTVTY5y9JkreSRv3Xs8q2K67YxHLi", + "t2JrSLxTGc8wtPDe9hwbaeUjCrCfc4iZnDD", + "t2Uh9Au1PDDSw117sAbGivKREkmMxVC5tZo", + "t2FDwoJKLeEBMTy3oP7RLQ1Fihhvz49a3Bv", + "t2FY18mrgtb7QLeHA8ShnxLXuW8cNQ2n1v8", + "t2L15TkDYum7dnQRBqfvWdRe8Yw3jVy9z7g", +]; + +/// List of addresses for the Zcash Foundation funding stream in the Testnet. +pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_N_ADDRESSES_TESTNET] = + ["t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v"; FUNDING_STREAMS_N_ADDRESSES_TESTNET]; + +/// List of addresses for the Major Grants funding stream in the Testnet. +pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_N_ADDRESSES_TESTNET] = + ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_N_ADDRESSES_TESTNET]; From 2c620ee23744fab0f64b7e5c4c26ea37ad4269fc Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 8 Nov 2021 13:56:11 -0300 Subject: [PATCH 02/17] simplify a bit funder stream address check --- zebra-consensus/src/block/check.rs | 33 +++++++++-------------- zebra-consensus/src/parameters/subsidy.rs | 12 +++++++++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 7ff82d27e4a..2c2a4c9c2c5 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -13,7 +13,9 @@ use zebra_chain::{ use crate::{ error::*, - parameters::{subsidy::FundingStreamReceiver, SLOW_START_INTERVAL}, + parameters::{ + subsidy::FundingStreamReceiver, FUNDING_STREAM_RECEIVERS_NUMBER, SLOW_START_INTERVAL, + }, }; use super::subsidy; @@ -148,32 +150,23 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro let output_amounts = subsidy::general::output_amounts(coinbase); // funding stream addresses - let address_ecc = subsidy::funding_streams::funding_stream_address( - height, - network, - FundingStreamReceiver::Ecc, - ); - let address_zf = subsidy::funding_streams::funding_stream_address( - height, - network, - FundingStreamReceiver::ZcashFoundation, - ); - let address_mg = subsidy::funding_streams::funding_stream_address( - height, - network, - FundingStreamReceiver::MajorGrants, - ); + let mut found_outputs = HashSet::::new(); + for receiver in FundingStreamReceiver::receivers() { + let address = + subsidy::funding_streams::funding_stream_address(height, network, receiver); - let ecc_address = subsidy::funding_streams::find_output_with_address(coinbase, address_ecc); - let zf_address = subsidy::funding_streams::find_output_with_address(coinbase, address_zf); - let mg_address = subsidy::funding_streams::find_output_with_address(coinbase, address_mg); + let outputs = subsidy::funding_streams::find_output_with_address(coinbase, address); + if !outputs.is_empty() { + found_outputs.insert(receiver); + } + } // Consensus rule:[Canopy onward] The coinbase transaction at block height `height` // MUST contain at least one output per funding stream `fs` active at `height`, // that pays `fs.Value(height)` zatoshi in the prescribed way to the stream's // recipient address represented by `fs.AddressList[fs.AddressIndex(height)] if funding_stream_amounts.is_subset(&output_amounts) { - if !ecc_address.is_empty() && !zf_address.is_empty() && !mg_address.is_empty() { + if found_outputs.len() == FUNDING_STREAM_RECEIVERS_NUMBER { Ok(()) } else { Err(SubsidyError::FundingStreamAddressNotFound)? diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 8e20d9ba5a7..e01aab5a709 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -53,6 +53,18 @@ pub enum FundingStreamReceiver { MajorGrants, } +impl FundingStreamReceiver { + /// Get a list of receiver types + pub fn receivers() -> Vec { + vec![Self::Ecc, Self::ZcashFoundation, Self::MajorGrants] + } +} + +/// The number of funding stream entities. +pub const FUNDING_STREAM_RECEIVERS_NUMBER: usize = 3; + +/// The funding stream receiver categories + /// Denominator as described in [protocol specification §7.9.1][7.9.1]. /// /// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams From a5636f24b60c930d54e7b82a6a776a3a8b43e269 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 9 Nov 2021 09:24:28 -0300 Subject: [PATCH 03/17] add integer division code comment --- zebra-consensus/src/block/subsidy/funding_streams.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 5796c96cb7f..97d64b556de 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -70,6 +70,11 @@ fn height_for_halving(network: Network) -> Height { /// /// [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() - post_blossom_halving_interval)/funding_stream_address_change_interval)`: + // https://zips.z.cash/protocol/protocol.pdf#fundingstreams + // - 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. (height.0 + (POST_BLOSSOM_HALVING_INTERVAL.0) - (height_for_halving(network).0)) / (FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL.0) } From 8fe12a8deaec0d5e9b1eff40b68292c72f118773 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 9 Nov 2021 09:40:27 -0300 Subject: [PATCH 04/17] document constant --- .../src/block/subsidy/funding_streams.rs | 4 +-- zebra-consensus/src/parameters/subsidy.rs | 36 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 97d64b556de..4da6d94b649 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -85,8 +85,8 @@ fn funding_stream_address_period(height: Height, network: Network) -> u32 { /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams fn funding_stream_address_index(height: Height, network: Network) -> usize { let num_addresses = match network { - Network::Mainnet => FUNDING_STREAMS_N_ADDRESSES_MAINNET, - Network::Testnet => FUNDING_STREAMS_N_ADDRESSES_TESTNET, + Network::Mainnet => FUNDING_STREAMS_NUM_ADDRESSES_MAINNET, + Network::Testnet => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET, }; let index = 1u32 diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index e01aab5a709..c0b8988473b 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -102,11 +102,15 @@ lazy_static! { pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: Height = Height(POST_BLOSSOM_HALVING_INTERVAL.0 / 48); -/// Number of addresses for each funding stream in the Mainnet -pub const FUNDING_STREAMS_N_ADDRESSES_MAINNET: usize = 48; +/// Number of addresses for each funding stream in the Mainnet. +/// 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 FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 48; /// List of addresses for the ECC funding stream in the Mainnet. -pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_N_ADDRESSES_MAINNET] = [ +pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = [ "t3LmX1cxWPPPqL4TZHx42HU3U5ghbFjRiif", "t3Toxk1vJQ6UjWQ42tUJz2rV2feUWkpbTDs", "t3ZBdBe4iokmsjdhMuwkxEdqMCFN16YxKe6", @@ -158,18 +162,22 @@ pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_N_ADDRESS ]; /// List of addresses for the Zcash Foundation funding stream in the Mainnet. -pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; 48] = - ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_N_ADDRESSES_MAINNET]; +pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = + ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET]; /// List of addresses for the Major Grants funding stream in the Mainnet. -pub const FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; 48] = - ["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_N_ADDRESSES_MAINNET]; +pub const FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = + ["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET]; -/// Number of addresses for each funding stream in the Mainnet -pub const FUNDING_STREAMS_N_ADDRESSES_TESTNET: usize = 51; +/// Number of addresses for each 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 FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 51; /// List of addresses for the ECC funding stream in the Testnet. -pub const FUNDING_STREAM_ECC_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_N_ADDRESSES_TESTNET] = [ +pub const FUNDING_STREAM_ECC_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] = [ "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", @@ -224,9 +232,9 @@ pub const FUNDING_STREAM_ECC_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_N_ADDRESS ]; /// List of addresses for the Zcash Foundation funding stream in the Testnet. -pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_N_ADDRESSES_TESTNET] = - ["t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v"; FUNDING_STREAMS_N_ADDRESSES_TESTNET]; +pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] = + ["t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET]; /// List of addresses for the Major Grants funding stream in the Testnet. -pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_N_ADDRESSES_TESTNET] = - ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_N_ADDRESSES_TESTNET]; +pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] = + ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET]; From f04dba29cc1967695e28acdd93aeefd14785dd16 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 9 Nov 2021 10:17:10 -0300 Subject: [PATCH 05/17] replace some unwraps --- zebra-consensus/src/block/subsidy/funding_streams.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 4da6d94b649..caa822b0739 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -91,15 +91,17 @@ fn funding_stream_address_index(height: Height, network: Network) -> usize { let index = 1u32 .checked_add(funding_stream_address_period(height, network)) - .unwrap() + .expect("no overflow should happen in this sum") .checked_sub(funding_stream_address_period( FUNDING_STREAM_HEIGHT_RANGES.get(&network).unwrap().start, network, )) - .unwrap() as usize; - assert!(index > 0 && index <= num_addresses); + .expect("no overflow should happen in this sub") as usize; - index + assert!(index > 0 && index <= num_addresses); + // spec formula will output an index starting at 1 but + // Zebra indices for addresses start at zero, return converted. + index - 1 } /// Return the address corresponding to this height for this funding stream receiver. @@ -108,7 +110,7 @@ pub fn funding_stream_address( network: Network, receiver: FundingStreamReceiver, ) -> Address { - let index = funding_stream_address_index(height, network) - 1; + let index = funding_stream_address_index(height, network); let address = match receiver { FundingStreamReceiver::Ecc => match network { From c833e059638074fb7bf858d1adc2abe760691f9a Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 9 Nov 2021 10:34:44 -0300 Subject: [PATCH 06/17] fix some doc comments --- zebra-consensus/src/parameters/subsidy.rs | 24 +++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index c0b8988473b..0003a79d135 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -45,7 +45,7 @@ pub const POST_BLOSSOM_HALVING_INTERVAL: Height = /// Usage: founders_reward = block_subsidy / FOUNDERS_FRACTION_DIVISOR pub const FOUNDERS_FRACTION_DIVISOR: u64 = 5; -/// The funding stream receiver categories +/// The funding stream receiver categories. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum FundingStreamReceiver { Ecc, @@ -60,21 +60,19 @@ impl FundingStreamReceiver { } } -/// The number of funding stream entities. +/// The number of funding stream receiver categories. pub const FUNDING_STREAM_RECEIVERS_NUMBER: usize = 3; -/// The funding stream receiver categories - -/// Denominator as described in [protocol specification §7.9.1][7.9.1]. +/// Denominator as described in [protocol specification §7.10.1][7.10.1]. /// -/// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams +/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100; lazy_static! { - /// The numerator for each funding stream receiving category - /// as described in [protocol specification §7.9.1][7.9.1]. + /// The numerator for each funding stream reciever category + /// as described in [protocol specification §7.10.1][7.10.1]. /// - /// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams pub static ref FUNDING_STREAM_RECEIVER_NUMERATORS: HashMap = { let mut hash_map = HashMap::new(); hash_map.insert(FundingStreamReceiver::Ecc, 7); @@ -84,9 +82,9 @@ lazy_static! { }; /// Start and end Heights for funding streams - /// as described in [protocol specification §7.9.1][7.9.1]. + /// as described in [protocol specification §7.10.1][7.10.1]. /// - /// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams pub static ref FUNDING_STREAM_HEIGHT_RANGES: HashMap> = { let mut hash_map = HashMap::new(); hash_map.insert(Network::Mainnet, Height(1_046_400)..Height(2_726_400)); @@ -96,9 +94,9 @@ lazy_static! { } /// Address change interval function here as a constant -/// as described in [protocol specification §7.9.1][7.9.1]. +/// as described in [protocol specification §7.10.1][7.10.1]. /// -/// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams +/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: Height = Height(POST_BLOSSOM_HALVING_INTERVAL.0 / 48); From d587bc6d7f30e7c595451919793f99fad5eb5575 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 9 Nov 2021 11:20:25 -0300 Subject: [PATCH 07/17] check at least one output has calculated address and amount --- zebra-consensus/src/block/check.rs | 9 ++++++++- zebra-consensus/src/block/subsidy/funding_streams.rs | 11 ++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 2c2a4c9c2c5..e88be147f09 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -155,7 +155,14 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro let address = subsidy::funding_streams::funding_stream_address(height, network, receiver); - let outputs = subsidy::funding_streams::find_output_with_address(coinbase, address); + let amount = *funding_streams + .get(&receiver) + .expect("funding_streams hashmap has all possible receivers."); + + // we should have at least one output with receiver address and amount + let outputs = subsidy::funding_streams::find_output_with_address_and_amount( + coinbase, address, amount, + ); if !outputs.is_empty() { found_outputs.insert(receiver); } diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index caa822b0739..08d162979a1 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -150,12 +150,17 @@ pub fn check_script_form(lock_script: Script, address: Address) -> bool { false } -/// Returns a list of outputs in `Transaction`, which have a script address equal to `Address`. -pub fn find_output_with_address(transaction: &Transaction, address: Address) -> Vec { +/// Returns a list of outputs in `Transaction`, which have a script address equal to `Address` +/// and a value equal to `Amount`. +pub fn find_output_with_address_and_amount( + transaction: &Transaction, + address: Address, + amount: Amount, +) -> Vec { transaction .outputs() .iter() - .filter(|o| check_script_form(o.lock_script.clone(), address)) + .filter(|o| check_script_form(o.lock_script.clone(), address) && o.value == amount) .cloned() .collect() } From e6bd525339603f3ffa8e1ec2f5a96766c2a1f2c0 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 9 Nov 2021 14:45:16 -0300 Subject: [PATCH 08/17] create a convinient storage for funding stream addresses --- .../src/block/subsidy/funding_streams.rs | 24 ++++++------------- zebra-consensus/src/parameters/subsidy.rs | 21 ++++++++++++++++ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 08d162979a1..9c350c5077e 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -104,29 +104,19 @@ fn funding_stream_address_index(height: Height, network: Network) -> usize { index - 1 } -/// Return the address corresponding to this height for this funding stream receiver. +/// Return the address corresponding to given height, network and funding stream receiver. pub fn funding_stream_address( height: Height, network: Network, receiver: FundingStreamReceiver, ) -> Address { let index = funding_stream_address_index(height, network); - - let address = match receiver { - FundingStreamReceiver::Ecc => match network { - Network::Mainnet => FUNDING_STREAM_ECC_ADDRESSES_MAINNET[index].to_string(), - Network::Testnet => FUNDING_STREAM_ECC_ADDRESSES_TESTNET[index].to_string(), - }, - FundingStreamReceiver::ZcashFoundation => match network { - Network::Mainnet => FUNDING_STREAM_ZF_ADDRESSES_MAINNET[index].to_string(), - Network::Testnet => FUNDING_STREAM_ZF_ADDRESSES_TESTNET[index].to_string(), - }, - FundingStreamReceiver::MajorGrants => match network { - Network::Mainnet => FUNDING_STREAM_MG_ADDRESSES_MAINNET[index].to_string(), - Network::Testnet => FUNDING_STREAM_MG_ADDRESSES_TESTNET[index].to_string(), - }, - }; - Address::from_str(&address).expect("Address should deserialize") + let address = &FUNDING_STREAM_ADDRESSES + .get(&network) + .unwrap() + .get(&receiver) + .unwrap()[index]; + Address::from_str(address).expect("Address should deserialize") } /// Given a founders reward address, create a script and check if it is the same diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 0003a79d135..40105cfcb8c 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -91,6 +91,27 @@ lazy_static! { hash_map.insert(Network::Testnet, Height(1_028_500)..Height(2_796_000)); hash_map }; + + /// Convinient storage for all addresses, for all receivers and networks + pub static ref FUNDING_STREAM_ADDRESSES: HashMap>> = { + let mut bigger_hash_map = HashMap::new(); + + // Mainnet addresses + let mut inner_hash_map = HashMap::new(); + inner_hash_map.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); + inner_hash_map.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); + inner_hash_map.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); + bigger_hash_map.insert(Network::Mainnet, inner_hash_map); + + // Testnet addresses + let mut inner_hash_map = HashMap::new(); + inner_hash_map.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); + inner_hash_map.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); + inner_hash_map.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); + bigger_hash_map.insert(Network::Testnet, inner_hash_map); + + bigger_hash_map + }; } /// Address change interval function here as a constant From 1547e4beca3c51f3f0aefb7f75a317cf6dd1721c Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 9 Nov 2021 14:57:45 -0300 Subject: [PATCH 09/17] replace some unwraps --- zebra-consensus/src/block/subsidy/funding_streams.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 9c350c5077e..a1edb5614b0 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -113,9 +113,9 @@ pub fn funding_stream_address( let index = funding_stream_address_index(height, network); let address = &FUNDING_STREAM_ADDRESSES .get(&network) - .unwrap() + .expect("there is always another hash map as value for a given valid network") .get(&receiver) - .unwrap()[index]; + .expect("in the inner hash map there is always a vector of strings with addresses")[index]; Address::from_str(address).expect("Address should deserialize") } From 7f9b1a3cfba95b4bb7aa32a2acded1552899dbd2 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 08:44:00 -0300 Subject: [PATCH 10/17] docs: change `7.7` protocol sections to `7.8` --- zebra-consensus/src/block/subsidy.rs | 4 ++-- .../src/block/subsidy/founders_reward.rs | 8 ++++---- zebra-consensus/src/block/subsidy/general.rs | 18 +++++++++--------- zebra-consensus/src/parameters/subsidy.rs | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/zebra-consensus/src/block/subsidy.rs b/zebra-consensus/src/block/subsidy.rs index dcd8839093d..1ad7572516c 100644 --- a/zebra-consensus/src/block/subsidy.rs +++ b/zebra-consensus/src/block/subsidy.rs @@ -1,6 +1,6 @@ -//! Validate coinbase transaction rewards as described in [§7.7][7.7] +//! Validate coinbase transaction rewards as described in [§7.8][7.8] //! -//! [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies /// Founders' Reward functions apply for blocks before Canopy. pub mod founders_reward; diff --git a/zebra-consensus/src/block/subsidy/founders_reward.rs b/zebra-consensus/src/block/subsidy/founders_reward.rs index 4cd1c852324..17f3e3c38af 100644 --- a/zebra-consensus/src/block/subsidy/founders_reward.rs +++ b/zebra-consensus/src/block/subsidy/founders_reward.rs @@ -1,6 +1,6 @@ -//! Founders' Reward calculations. - [§7.7][7.7] +//! Founders' Reward calculations. - [§7.8][7.8] //! -//! [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies use std::convert::TryFrom; @@ -13,9 +13,9 @@ use zebra_chain::{ use crate::block::subsidy::general::{block_subsidy, halving_divisor}; use crate::parameters::subsidy::FOUNDERS_FRACTION_DIVISOR; -/// `FoundersReward(height)` as described in [protocol specification §7.7][7.7] +/// `FoundersReward(height)` as described in [protocol specification §7.8][7.8] /// -/// [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies pub fn founders_reward(height: Height, network: Network) -> Result, Error> { if halving_divisor(height, network) == 1 { // this calculation is exact, because the block subsidy is divisible by diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index eba741719ba..2a463a6f795 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -1,6 +1,6 @@ -//! Block and Miner subsidies, halvings and target spacing modifiers. - [§7.7][7.7] +//! Block and Miner subsidies, halvings and target spacing modifiers. - [§7.8][7.8] //! -//! [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies use std::{collections::HashSet, convert::TryFrom}; @@ -43,9 +43,9 @@ pub fn halving_divisor(height: Height, network: Network) -> u64 { } } -/// `BlockSubsidy(height)` as described in [protocol specification §7.7][7.7] +/// `BlockSubsidy(height)` as described in [protocol specification §7.8][7.8] /// -/// [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies pub fn block_subsidy(height: Height, network: Network) -> Result, Error> { let blossom_height = Blossom .activation_height(network) @@ -69,9 +69,9 @@ pub fn block_subsidy(height: Height, network: Network) -> Result Canopy.activation_height(network).unwrap(), - // Based on "7.7 Calculation of Block Subsidy and Founders' Reward" + // Based on "7.8 Calculation of Block Subsidy and Founders' Reward" Network::Testnet => Height(1_116_000), }; @@ -218,7 +218,7 @@ mod test { let blossom_height = Blossom.activation_height(network).unwrap(); let first_halving_height = match network { Network::Mainnet => Canopy.activation_height(network).unwrap(), - // Based on "7.7 Calculation of Block Subsidy and Founders' Reward" + // Based on "7.8 Calculation of Block Subsidy and Founders' Reward" Network::Testnet => Height(1_116_000), }; @@ -244,7 +244,7 @@ mod test { ); // After the 2nd halving, the block subsidy is reduced to 1.5625 ZEC - // See "7.7 Calculation of Block Subsidy and Founders' Reward" + // See "7.8 Calculation of Block Subsidy and Founders' Reward" assert_eq!( Amount::try_from(156_250_000), block_subsidy( diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 40105cfcb8c..20b06fc93d2 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -10,9 +10,9 @@ use zebra_chain::{amount::COIN, block::Height, parameters::Network}; /// [slow-mining]: https://z.cash/support/faq/#what-is-slow-start-mining pub const SLOW_START_INTERVAL: Height = Height(20_000); -/// `SlowStartShift()` as described in [protocol specification §7.7][7.7] +/// `SlowStartShift()` as described in [protocol specification §7.8][7.8] /// -/// [7.7]: https://zips.z.cash/protocol/protocol.pdf#subsidies +/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies /// /// This calculation is exact, because `SLOW_START_INTERVAL` is divisible by 2. pub const SLOW_START_SHIFT: Height = Height(SLOW_START_INTERVAL.0 / 2); From 0915d8bfd42d873961d8e0a2d5cf42c064b5b1ea Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 08:50:01 -0300 Subject: [PATCH 11/17] change errors text --- zebra-consensus/src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 3646b02e909..23adcbb56b2 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -22,10 +22,10 @@ pub enum SubsidyError { #[error("founders reward output not found")] FoundersRewardNotFound, - #[error("funding stream output with value not found")] + #[error("funding stream output value not found")] FundingStreamValueNotFound, - #[error("funding stream output with address not found")] + #[error("funding stream output address not found")] FundingStreamAddressNotFound, } From 126f23beca18fd60c28998fd876b293cdb809e93 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 08:55:03 -0300 Subject: [PATCH 12/17] change function name --- zebra-consensus/src/block/subsidy/funding_streams.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index a1edb5614b0..27893ed4c4d 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -53,7 +53,7 @@ pub fn funding_stream_values( /// as described in [protocol specification §7.10][7.10] /// /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams -fn height_for_halving(network: Network) -> Height { +fn height_for_first_halving(network: Network) -> Height { // First halving on Mainnet is at Canopy // while in Testnet is at block 1_116_000 // https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams @@ -70,12 +70,12 @@ fn height_for_halving(network: Network) -> Height { /// /// [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() - post_blossom_halving_interval)/funding_stream_address_change_interval)`: + // - 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 // - 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. - (height.0 + (POST_BLOSSOM_HALVING_INTERVAL.0) - (height_for_halving(network).0)) + (height.0 + (POST_BLOSSOM_HALVING_INTERVAL.0) - (height_for_first_halving(network).0)) / (FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL.0) } From 3677de67c361a9388e9134b1cf749780501633d9 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 09:12:53 -0300 Subject: [PATCH 13/17] refactor `FundingStreamReceiver::receivers()` --- zebra-consensus/src/parameters/subsidy.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 20b06fc93d2..9da27ae83d0 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -55,13 +55,12 @@ pub enum FundingStreamReceiver { impl FundingStreamReceiver { /// Get a list of receiver types - pub fn receivers() -> Vec { - vec![Self::Ecc, Self::ZcashFoundation, Self::MajorGrants] + pub const fn receivers() -> [Self; 3] { + [Self::Ecc, Self::ZcashFoundation, Self::MajorGrants] } } - /// The number of funding stream receiver categories. -pub const FUNDING_STREAM_RECEIVERS_NUMBER: usize = 3; +pub const FUNDING_STREAM_RECEIVERS_NUMBER: usize = FundingStreamReceiver::receivers().len(); /// Denominator as described in [protocol specification §7.10.1][7.10.1]. /// From 8123a8298c31810e42d23f0bd5babc31de6807e2 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 09:27:00 -0300 Subject: [PATCH 14/17] refactor FUNDING_STREAM_ADDRESSES Co-authored-by: Janito Vaqueiro Ferreira Filho --- zebra-consensus/src/parameters/subsidy.rs | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 9da27ae83d0..b225676530a 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -93,23 +93,23 @@ lazy_static! { /// Convinient storage for all addresses, for all receivers and networks pub static ref FUNDING_STREAM_ADDRESSES: HashMap>> = { - let mut bigger_hash_map = HashMap::new(); + let mut addresses_by_network = HashMap::with_capacity(2); // Mainnet addresses - let mut inner_hash_map = HashMap::new(); - inner_hash_map.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); - inner_hash_map.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); - inner_hash_map.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); - bigger_hash_map.insert(Network::Mainnet, inner_hash_map); + let mut mainnet_addresses = HashMap::with_capacity(3); + mainnet_addresses.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); + mainnet_addresses.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); + mainnet_addresses.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); + addresses_by_network.insert(Network::Mainnet, mainnet_addresses); // Testnet addresses - let mut inner_hash_map = HashMap::new(); - inner_hash_map.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); - inner_hash_map.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); - inner_hash_map.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); - bigger_hash_map.insert(Network::Testnet, inner_hash_map); + let mut testnet_addresses = HashMap::with_capacity(3); + testnet_addresses.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); + testnet_addresses.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); + testnet_addresses.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); + addresses_by_network.insert(Network::Testnet, testnet_addresses); - bigger_hash_map + addresses_by_network }; } From d0e26e93c143b1163dbef32cd025e5b6cae2dacc Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 09:38:43 -0300 Subject: [PATCH 15/17] remove a `clone()` Co-authored-by: Janito Vaqueiro Ferreira Filho --- zebra-consensus/src/block/subsidy/funding_streams.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 27893ed4c4d..da8ce15bdaa 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -123,7 +123,7 @@ pub fn funding_stream_address( /// as the given lock_script as described in [protocol specification §7.10][7.10] /// /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams. -pub fn check_script_form(lock_script: Script, address: Address) -> bool { +pub fn check_script_form(lock_script: &Script, address: Address) -> bool { let mut address_hash = address .zcash_serialize_to_vec() .expect("we should get address bytes here"); @@ -133,7 +133,7 @@ pub fn check_script_form(lock_script: Script, address: Address) -> bool { address_hash.insert(0, OpCode::Hash160 as u8); address_hash.insert(address_hash.len(), OpCode::Equal as u8); if lock_script.as_raw_bytes().len() == address_hash.len() - && lock_script == Script::new(&address_hash) + && *lock_script == Script::new(&address_hash) { return true; } @@ -150,7 +150,7 @@ pub fn find_output_with_address_and_amount( transaction .outputs() .iter() - .filter(|o| check_script_form(o.lock_script.clone(), address) && o.value == amount) + .filter(|o| check_script_form(&o.lock_script, address) && o.value == amount) .cloned() .collect() } From 0f43eaf2b4ad4b2090db40b34f933decd96fbf87 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 11:02:11 -0300 Subject: [PATCH 16/17] fix consensus rule check --- zebra-consensus/src/block/check.rs | 54 +++++-------------- .../src/block/subsidy/funding_streams.rs | 11 ++-- zebra-consensus/src/block/subsidy/general.rs | 1 + zebra-consensus/src/block/tests.rs | 2 +- zebra-consensus/src/error.rs | 7 +-- 5 files changed, 21 insertions(+), 54 deletions(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index e88be147f09..1a1725cfb8b 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -4,19 +4,13 @@ use chrono::{DateTime, Utc}; use std::collections::HashSet; use zebra_chain::{ - amount::{Amount, NonNegative}, block::{Block, Hash, Header, Height}, parameters::{Network, NetworkUpgrade}, transaction, work::{difficulty::ExpandedDifficulty, equihash}, }; -use crate::{ - error::*, - parameters::{ - subsidy::FundingStreamReceiver, FUNDING_STREAM_RECEIVERS_NUMBER, SLOW_START_INTERVAL, - }, -}; +use crate::{error::*, parameters::SLOW_START_INTERVAL}; use super::subsidy; @@ -138,49 +132,29 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro // 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 - // funding stream amount values let funding_streams = subsidy::funding_streams::funding_stream_values(height, network) .expect("We always expect a funding stream hashmap response even if empty"); - let funding_stream_amounts: HashSet> = funding_streams - .iter() - .map(|(_receiver, amount)| *amount) - .collect(); - let output_amounts = subsidy::general::output_amounts(coinbase); - - // funding stream addresses - let mut found_outputs = HashSet::::new(); - for receiver in FundingStreamReceiver::receivers() { - let address = - subsidy::funding_streams::funding_stream_address(height, network, receiver); - - let amount = *funding_streams - .get(&receiver) - .expect("funding_streams hashmap has all possible receivers."); - - // we should have at least one output with receiver address and amount - let outputs = subsidy::funding_streams::find_output_with_address_and_amount( - coinbase, address, amount, - ); - if !outputs.is_empty() { - found_outputs.insert(receiver); - } - } - // Consensus rule:[Canopy onward] The coinbase transaction at block height `height` // MUST contain at least one output per funding stream `fs` active at `height`, // that pays `fs.Value(height)` zatoshi in the prescribed way to the stream's // recipient address represented by `fs.AddressList[fs.AddressIndex(height)] - if funding_stream_amounts.is_subset(&output_amounts) { - if found_outputs.len() == FUNDING_STREAM_RECEIVERS_NUMBER { - Ok(()) - } else { - Err(SubsidyError::FundingStreamAddressNotFound)? + for (receiver, expected_amount) in funding_streams { + let address = + subsidy::funding_streams::funding_stream_address(height, network, receiver); + + let has_expected_output = + subsidy::funding_streams::filter_outputs_by_address(coinbase, address) + .iter() + .map(zebra_chain::transparent::Output::value) + .any(|value| value == expected_amount); + + if !has_expected_output { + Err(SubsidyError::FundingStreamNotFound)?; } - } else { - Err(SubsidyError::FundingStreamValueNotFound)? } + Ok(()) } else { // Future halving, with no founders reward or funding streams Ok(()) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index da8ce15bdaa..d44ceac5d6a 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -140,17 +140,12 @@ pub fn check_script_form(lock_script: &Script, address: Address) -> bool { false } -/// Returns a list of outputs in `Transaction`, which have a script address equal to `Address` -/// and a value equal to `Amount`. -pub fn find_output_with_address_and_amount( - transaction: &Transaction, - address: Address, - amount: Amount, -) -> Vec { +/// Returns a list of outputs in `Transaction`, which have a script address equal to `Address`. +pub fn filter_outputs_by_address(transaction: &Transaction, address: Address) -> Vec { transaction .outputs() .iter() - .filter(|o| check_script_form(&o.lock_script, address) && o.value == amount) + .filter(|o| check_script_form(&o.lock_script, address)) .cloned() .collect() } diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 2a463a6f795..45aaf647092 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -103,6 +103,7 @@ pub fn find_output_with_amount( } /// Returns all output amounts in `Transaction`. +#[allow(dead_code)] pub fn output_amounts(transaction: &Transaction) -> HashSet> { transaction .outputs() diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index d8940ce18d7..d5d6296c6ad 100644 --- a/zebra-consensus/src/block/tests.rs +++ b/zebra-consensus/src/block/tests.rs @@ -482,7 +482,7 @@ fn funding_stream_validation_failure() -> Result<(), Report> { // Validate it let result = check::subsidy_is_valid(&block, network).unwrap_err(); let expected = BlockError::Transaction(TransactionError::Subsidy( - SubsidyError::FundingStreamValueNotFound, + SubsidyError::FundingStreamNotFound, )); assert_eq!(expected, result); diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 23adcbb56b2..653b6388055 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -22,11 +22,8 @@ pub enum SubsidyError { #[error("founders reward output not found")] FoundersRewardNotFound, - #[error("funding stream output value not found")] - FundingStreamValueNotFound, - - #[error("funding stream output address not found")] - FundingStreamAddressNotFound, + #[error("funding stream expected output not found")] + FundingStreamNotFound, } #[derive(Error, Clone, Debug, PartialEq, Eq)] From 4e486e9c162c45250c42c6675680f24492ade5c0 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 12 Nov 2021 11:28:20 -0300 Subject: [PATCH 17/17] use a constant for testnet first halving height Co-authored-by: teor --- zebra-consensus/src/block/subsidy/funding_streams.rs | 4 ++-- zebra-consensus/src/parameters/subsidy.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index d44ceac5d6a..1e422ea9fb2 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -55,13 +55,13 @@ pub fn funding_stream_values( /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams fn height_for_first_halving(network: Network) -> Height { // First halving on Mainnet is at Canopy - // while in Testnet is at block 1_116_000 + // while in Testnet is at block constant height of `1_116_000` // https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams match network { Network::Mainnet => Canopy .activation_height(network) .expect("canopy activation height should be available"), - Network::Testnet => Height(1_116_000), + Network::Testnet => FIRST_HALVING_TESTNET, } } diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index b225676530a..635b328ecd5 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -45,6 +45,12 @@ pub const POST_BLOSSOM_HALVING_INTERVAL: Height = /// Usage: founders_reward = block_subsidy / FOUNDERS_FRACTION_DIVISOR pub const FOUNDERS_FRACTION_DIVISOR: u64 = 5; +/// The first halving height in the testnet is at block height `1_116_000` +/// as specified in [protocol specification §7.10.1][7.10.1] +/// +/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams +pub const FIRST_HALVING_TESTNET: Height = Height(1_116_000); + /// The funding stream receiver categories. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum FundingStreamReceiver {