Validate funding stream addresses (#3040)

* validate funding stream addresses

* simplify a bit funder stream address check

* add integer division code comment

* document constant

* replace some unwraps

* fix some doc comments

* check at least one output has calculated address and amount

* create a convinient storage for funding stream addresses

* replace some unwraps

* docs: change `7.7` protocol sections to `7.8`

* change errors text

* change function name

* refactor `FundingStreamReceiver::receivers()`

* refactor FUNDING_STREAM_ADDRESSES

Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>

* remove a `clone()`

Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>

* fix consensus rule check

* use a constant for testnet first halving height

Co-authored-by: teor <teor@riseup.net>

Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
Alfredo Garcia 2021-11-12 15:20:31 -03:00 committed by GitHub
parent d6f3b3dc9a
commit d321e8f0cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 341 additions and 54 deletions

View File

@ -4,7 +4,6 @@ use chrono::{DateTime, Utc};
use std::collections::HashSet;
use zebra_chain::{
amount::{Amount, NonNegative},
block::{Block, Hash, Header, Height},
parameters::{Network, NetworkUpgrade},
transaction,
@ -133,28 +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<Amount<NonNegative>> = funding_streams
.iter()
.map(|(_receiver, amount)| *amount)
.collect();
let output_amounts = subsidy::general::output_amounts(coinbase);
// 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)]
for (receiver, expected_amount) in funding_streams {
let address =
subsidy::funding_streams::funding_stream_address(height, network, receiver);
// 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(())
} else {
Err(SubsidyError::FundingStreamNotFound)?
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)?;
}
}
Ok(())
} else {
// Future halving, with no founders reward or funding streams
Ok(())

View File

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

View File

@ -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<Amount<NonNegative>, Error> {
if halving_divisor(height, network) == 1 {
// this calculation is exact, because the block subsidy is divisible by

View File

@ -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,111 @@ 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_first_halving(network: Network) -> Height {
// First halving on Mainnet is at Canopy
// 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 => FIRST_HALVING_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
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
// - 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_first_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_NUM_ADDRESSES_MAINNET,
Network::Testnet => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET,
};
let index = 1u32
.checked_add(funding_stream_address_period(height, network))
.expect("no overflow should happen in this sum")
.checked_sub(funding_stream_address_period(
FUNDING_STREAM_HEIGHT_RANGES.get(&network).unwrap().start,
network,
))
.expect("no overflow should happen in this sub") as usize;
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 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 = &FUNDING_STREAM_ADDRESSES
.get(&network)
.expect("there is always another hash map as value for a given valid network")
.get(&receiver)
.expect("in the inner hash map there is always a vector of strings with addresses")[index];
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 filter_outputs_by_address(transaction: &Transaction, address: Address) -> Vec<Output> {
transaction
.outputs()
.iter()
.filter(|o| check_script_form(&o.lock_script, 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,
}

View File

@ -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};
@ -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)
@ -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<Amount<NonNegative>, Error> {
let blossom_height = Blossom
.activation_height(network)
@ -69,9 +69,9 @@ pub fn block_subsidy(height: Height, network: Network) -> Result<Amount<NonNegat
}
}
/// `MinerSubsidy(height)` as described in [protocol specification §7.7][7.7]
/// `MinerSubsidy(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
///
/// `non_miner_reward` is the founders reward or funding stream value.
/// If all the rewards for a block go to the miner, use `None`.
@ -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<Amount<NonNegative>> {
transaction
.outputs()
@ -131,7 +132,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),
};
@ -218,7 +219,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 +245,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(

View File

@ -22,7 +22,7 @@ pub enum SubsidyError {
#[error("founders reward output not found")]
FoundersRewardNotFound,
#[error("funding stream output not found")]
#[error("funding stream expected output not found")]
FundingStreamNotFound,
}

View File

@ -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);
@ -45,7 +45,13 @@ 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 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 {
Ecc,
@ -53,16 +59,25 @@ pub enum FundingStreamReceiver {
MajorGrants,
}
/// Denominator as described in [protocol specification §7.9.1][7.9.1].
impl FundingStreamReceiver {
/// Get a list of receiver types
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 = FundingStreamReceiver::receivers().len();
/// 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<FundingStreamReceiver, u64> = {
let mut hash_map = HashMap::new();
hash_map.insert(FundingStreamReceiver::Ecc, 7);
@ -72,13 +87,178 @@ 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<Network, std::ops::Range<Height>> = {
let mut hash_map = HashMap::new();
hash_map.insert(Network::Mainnet, Height(1_046_400)..Height(2_726_400));
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<Network, HashMap<FundingStreamReceiver, Vec<String>>> = {
let mut addresses_by_network = HashMap::with_capacity(2);
// Mainnet addresses
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 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);
addresses_by_network
};
}
/// Address change interval function here as a constant
/// as described in [protocol specification §7.10.1][7.10.1].
///
/// [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);
/// 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_NUM_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; 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; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
/// 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_NUM_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_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_NUM_ADDRESSES_TESTNET] =
["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];