From bf1278a070c2c4bc5f39842b921038348f7a049f Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 25 Jul 2024 16:46:33 -0400 Subject: [PATCH 01/35] Addresses clippy lints --- zebra-chain/src/serialization/arbitrary.rs | 1 + zebra-consensus/src/block/check.rs | 5 +- zebra-consensus/src/transaction/check.rs | 2 + zebra-consensus/src/transaction/tests/prop.rs | 6 +- .../types/submit_block.rs | 3 +- zebra-rpc/src/queue.rs | 4 +- .../src/server/http_request_compatibility.rs | 10 +-- zebra-rpc/src/sync.rs | 1 + .../src/bin/scanning-results-reader/main.rs | 2 +- .../finalized_state/zebra_db/transparent.rs | 4 + .../src/service/non_finalized_state.rs | 1 + zebra-state/src/tests/setup.rs | 4 +- zebrad/src/components/sync.rs | 2 + zebrad/src/components/sync/end_of_support.rs | 2 +- zebrad/tests/acceptance.rs | 36 ++------ zebrad/tests/common/config.rs | 21 ----- .../common/configs/shieldedscan-v1.5.0.toml | 80 ----------------- .../common/configs/shieldedscan-v1.6.0.toml | 89 ------------------- .../common/lightwalletd/wallet_grpc_test.rs | 3 +- 19 files changed, 36 insertions(+), 240 deletions(-) delete mode 100644 zebrad/tests/common/configs/shieldedscan-v1.5.0.toml delete mode 100644 zebrad/tests/common/configs/shieldedscan-v1.6.0.toml diff --git a/zebra-chain/src/serialization/arbitrary.rs b/zebra-chain/src/serialization/arbitrary.rs index ab1b5abfba3..95de4363f3a 100644 --- a/zebra-chain/src/serialization/arbitrary.rs +++ b/zebra-chain/src/serialization/arbitrary.rs @@ -27,6 +27,7 @@ impl Arbitrary for DateTime32 { /// - making durations and intervals 3 seconds or longer, /// - avoiding complex time-based calculations, and /// - avoiding relying on subsecond precision or time order. +/// /// When monotonic times are needed, use the opaque `std::time::Instant` type. /// /// # Usage diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 349d14bb611..d18893ecb73 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -97,6 +97,7 @@ pub fn difficulty_threshold_is_valid( /// Returns `Ok(())` if `hash` passes: /// - the target difficulty limit for `network` (PoWLimit), and /// - the difficulty filter, +/// /// based on the fields in `header`. /// /// If the block is invalid, returns an error containing `height` and `hash`. @@ -286,8 +287,8 @@ pub fn time_is_valid_at( /// # Consensus rules: /// /// - A SHA-256d hash in internal byte order. The merkle root is derived from the -/// hashes of all transactions included in this block, ensuring that none of -/// those transactions can be modified without modifying the header. [7.6] +/// hashes of all transactions included in this block, ensuring that none of +/// those transactions can be modified without modifying the header. [7.6] /// /// # Panics /// diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index e96168d4c35..66e3d0be595 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -49,9 +49,11 @@ use crate::error::TransactionError; /// /// > the nTime field MUST represent a time strictly greater than the median of the /// > timestamps of the past PoWMedianBlockSpan blocks. +/// /// /// /// > The transaction can be added to any block whose block time is greater than the locktime. +/// /// /// /// If the transaction's lock time is less than the median-time-past, diff --git a/zebra-consensus/src/transaction/tests/prop.rs b/zebra-consensus/src/transaction/tests/prop.rs index 99a09306841..f45b4731de0 100644 --- a/zebra-consensus/src/transaction/tests/prop.rs +++ b/zebra-consensus/src/transaction/tests/prop.rs @@ -275,11 +275,11 @@ fn sapling_onwards_strategy() -> impl Strategy /// # Panics /// /// - if `transaction_version` is not `4` or `5` (the only transaction versions that are currently -/// supported by the transaction verifier) +/// supported by the transaction verifier) /// - if `relative_source_heights` has more than `u32::MAX` items (see -/// [`mock_transparent_transfers`] for details) +/// [`mock_transparent_transfers`] for details) /// - if any item of `relative_source_heights` is not in the range `0.0..1.0` (see -/// [`scale_block_height`] for details) +/// [`scale_block_height`] for details) fn mock_transparent_transaction( network: &Network, block_height: block::Height, diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index 7674f3d5657..2513af85aa6 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -9,10 +9,9 @@ use crate::methods::get_block_template_rpcs::GetBlockTemplateRpc; /// See notes for [`GetBlockTemplateRpc::submit_block`] method #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] pub struct JsonParameters { - /// The workid for the block template. + /// The workid for the block template. Currently unused. /// /// > If the server provided a workid, it MUST be included with submissions, - /// currently unused. /// /// Rationale: /// diff --git a/zebra-rpc/src/queue.rs b/zebra-rpc/src/queue.rs index fa60a360886..4504cd2070f 100644 --- a/zebra-rpc/src/queue.rs +++ b/zebra-rpc/src/queue.rs @@ -128,9 +128,9 @@ impl Runner { /// In this loop, get the transactions that are in the queue and: /// - Check if they are now in the mempool and if so, delete the transaction from the queue. /// - Check if the transaction is now part of a block in the state and if so, - /// delete the transaction from the queue. + /// delete the transaction from the queue. /// - With the transactions left in the queue, retry sending them to the mempool ignoring - /// the result of this operation. + /// the result of this operation. /// /// Additionally, each iteration of the above loop, will receive and insert to the queue /// transactions that are pending in the channel. diff --git a/zebra-rpc/src/server/http_request_compatibility.rs b/zebra-rpc/src/server/http_request_compatibility.rs index 73e206f4cdf..fede0e2bef0 100644 --- a/zebra-rpc/src/server/http_request_compatibility.rs +++ b/zebra-rpc/src/server/http_request_compatibility.rs @@ -116,12 +116,12 @@ impl FixHttpRequestMiddleware { /// # Security /// /// - `content-type` headers exist so that applications know they are speaking the correct protocol with the correct format. - /// We can be a bit flexible, but there are some types (such as binary) we shouldn't allow. - /// In particular, the "application/x-www-form-urlencoded" header should be rejected, so browser forms can't be used to attack - /// a local RPC port. See "The Role of Routers in the CSRF Attack" in - /// + /// We can be a bit flexible, but there are some types (such as binary) we shouldn't allow. + /// In particular, the "application/x-www-form-urlencoded" header should be rejected, so browser forms can't be used to attack + /// a local RPC port. See "The Role of Routers in the CSRF Attack" in + /// /// - Checking all the headers is secure, but only because hyper has custom code that just reads the first content-type header. - /// + /// pub fn insert_or_replace_content_type_header(headers: &mut header::HeaderMap) { if !headers.contains_key(header::CONTENT_TYPE) || headers diff --git a/zebra-rpc/src/sync.rs b/zebra-rpc/src/sync.rs index 53eed4072f0..fd323ef64bb 100644 --- a/zebra-rpc/src/sync.rs +++ b/zebra-rpc/src/sync.rs @@ -29,6 +29,7 @@ use crate::{ /// - Returns an error, or /// - Returns the block hash of a block that the read state already contains, /// (so that there's nothing for the syncer to do except wait for the next chain tip change). +/// /// See the [`TrustedChainSync::wait_for_chain_tip_change()`] method documentation for more information. const POLL_DELAY: Duration = Duration::from_millis(200); diff --git a/zebra-scan/src/bin/scanning-results-reader/main.rs b/zebra-scan/src/bin/scanning-results-reader/main.rs index f6f48eb404e..bc7d5674f3a 100644 --- a/zebra-scan/src/bin/scanning-results-reader/main.rs +++ b/zebra-scan/src/bin/scanning-results-reader/main.rs @@ -55,7 +55,7 @@ pub fn main() { for txid in txids.iter() { let tx = Transaction::read( - &hex::decode(&fetch_tx_via_rpc(txid.encode_hex())) + &hex::decode(fetch_tx_via_rpc(txid.encode_hex())) .expect("RPC response should be decodable from hex string to bytes")[..], BranchId::for_height(&network, height), ) diff --git a/zebra-state/src/service/finalized_state/zebra_db/transparent.rs b/zebra-state/src/service/finalized_state/zebra_db/transparent.rs index 149dcf6a26a..edfcf509b76 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/transparent.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/transparent.rs @@ -389,6 +389,7 @@ impl DiskWriteBatch { /// - insert created UTXOs, /// - insert transparent address UTXO index entries, and /// - insert transparent address transaction entries, + /// /// without actually writing anything. /// /// Also modifies the `address_balances` for these new UTXOs. @@ -466,6 +467,7 @@ impl DiskWriteBatch { /// Adds the following changes to this batch: /// - delete spent UTXOs, and /// - delete transparent address UTXO index entries, + /// /// without actually writing anything. /// /// Also modifies the `address_balances` for these new UTXOs. @@ -523,6 +525,7 @@ impl DiskWriteBatch { /// Adds the following changes to this batch: /// - index spending transactions for each spent transparent output /// (this is different from the transaction that created the output), + /// /// without actually writing anything. /// /// # Errors @@ -573,6 +576,7 @@ impl DiskWriteBatch { /// Prepare a database batch containing `finalized.block`'s: /// - transparent address balance changes, + /// /// and return it (without actually writing anything). /// /// # Errors diff --git a/zebra-state/src/service/non_finalized_state.rs b/zebra-state/src/service/non_finalized_state.rs index 7ca7881f30e..08d64455024 100644 --- a/zebra-state/src/service/non_finalized_state.rs +++ b/zebra-state/src/service/non_finalized_state.rs @@ -182,6 +182,7 @@ impl NonFinalizedState { // Chain::cmp uses the partial cumulative work, and the hash of the tip block. // Neither of these fields has interior mutability. // (And when the tip block is dropped for a chain, the chain is also dropped.) + #[allow(clippy::mutable_key_type)] let chains = mem::take(&mut self.chain_set); let mut chains = chains.into_iter(); diff --git a/zebra-state/src/tests/setup.rs b/zebra-state/src/tests/setup.rs index cc53a0d7ee5..b5f3fe1bed2 100644 --- a/zebra-state/src/tests/setup.rs +++ b/zebra-state/src/tests/setup.rs @@ -28,9 +28,9 @@ use crate::{ /// - `transaction_has_valid_network_upgrade`: See `LedgerState::height_strategy` for details. /// Note: `false` allows zero or more invalid network upgrades. /// - `blocks_after_nu_activation`: The number of blocks the strategy will generate -/// after the provided `network_upgrade`. +/// after the provided `network_upgrade`. /// - `network_upgrade` - The network upgrade that we are using to simulate from where the -/// legacy chain checks should start to apply. +/// legacy chain checks should start to apply. /// /// Returns: /// A generated arbitrary strategy for the provided arguments. diff --git a/zebrad/src/components/sync.rs b/zebrad/src/components/sync.rs index e51ee06f06f..06ab0b70cef 100644 --- a/zebrad/src/components/sync.rs +++ b/zebrad/src/components/sync.rs @@ -126,6 +126,7 @@ pub const PEER_GOSSIP_DELAY: Duration = Duration::from_secs(7); /// This timeout makes sure that the syncer doesn't hang when: /// - the lookahead queue is full, and /// - we are waiting for a request that is stuck. +/// /// See [`BLOCK_VERIFY_TIMEOUT`] for details. /// /// ## Correctness @@ -149,6 +150,7 @@ pub(super) const BLOCK_DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(20); /// - are for blocks a long way ahead of the current tip, or /// - are for invalid blocks which will never verify, because they depend on /// missing blocks or transactions. +/// /// These conditions can happen during normal operation - they are not bugs. /// /// This timeout also mitigates or hides the following kinds of bugs: diff --git a/zebrad/src/components/sync/end_of_support.rs b/zebrad/src/components/sync/end_of_support.rs index 0f6041a400c..191d44df351 100644 --- a/zebrad/src/components/sync/end_of_support.rs +++ b/zebrad/src/components/sync/end_of_support.rs @@ -21,7 +21,7 @@ pub const ESTIMATED_RELEASE_HEIGHT: u32 = 2_562_000; /// Notes: /// /// - Zebra will exit with a panic if the current tip height is bigger than the `ESTIMATED_RELEASE_HEIGHT` -/// plus this number of days. +/// plus this number of days. /// - Currently set to 16 weeks. pub const EOS_PANIC_AFTER: u32 = 112; diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index a131ec260d9..4cc2267049d 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -27,11 +27,11 @@ //! Some of them require environment variables or directories to be present: //! //! - `FULL_SYNC_MAINNET_TIMEOUT_MINUTES` env variable: The total number of minutes we -//! will allow this test to run or give up. Value for the Mainnet full sync tests. +//! will allow this test to run or give up. Value for the Mainnet full sync tests. //! - `FULL_SYNC_TESTNET_TIMEOUT_MINUTES` env variable: The total number of minutes we -//! will allow this test to run or give up. Value for the Testnet ful sync tests. +//! will allow this test to run or give up. Value for the Testnet ful sync tests. //! - `/zebrad-cache` directory: For some sync tests, this needs to be created in -//! the file system, the created directory should have write permissions. +//! the file system, the created directory should have write permissions. //! //! Here are some examples on how to run each of the tests: //! @@ -811,28 +811,13 @@ fn last_config_is_stored() -> Result<()> { \n\ Take the missing config file logged above, \n\ and commit it to Zebra's git repository as:\n\ - zebrad/tests/common/configs/{}.toml \n\ + zebrad/tests/common/configs/.toml \n\ \n\ Or run: \n\ - cargo build {}--bin zebrad && \n\ + cargo build --bin zebrad && \n\ zebrad generate | \n\ sed 's/cache_dir = \".*\"/cache_dir = \"cache_dir\"/' > \n\ - zebrad/tests/common/configs/{}.toml", - if cfg!(feature = "shielded-scan") { - SHIELDED_SCAN_CONFIG_PREFIX - } else { - "" - }, - if cfg!(feature = "shielded-scan") { - "--features=shielded-scan " - } else { - "" - }, - if cfg!(feature = "shielded-scan") { - SHIELDED_SCAN_CONFIG_PREFIX - } else { - "" - }, + zebrad/tests/common/configs/.toml", )) } @@ -963,7 +948,6 @@ fn stored_configs_parsed_correctly() -> Result<()> { // ignore files starting with shieldedscan prefix // if we were not built with the shielded-scan feature. - #[cfg(not(feature = "shielded-scan"))] if config_file_name.starts_with(SHIELDED_SCAN_CONFIG_PREFIX) { tracing::info!(?config_file_path, "skipping shielded-scan config file path"); continue; @@ -1021,14 +1005,6 @@ fn stored_configs_work() -> Result<()> { continue; } - // ignore files starting with shieldedscan prefix - // if we were not built with the shielded-scan feature. - #[cfg(not(feature = "shielded-scan"))] - if config_file_name.starts_with(SHIELDED_SCAN_CONFIG_PREFIX) { - tracing::info!(?config_file_path, "skipping shielded-scan config file path"); - continue; - } - let run_dir = testdir()?; let stored_config_path = config_file_full_path(config_file.path()); diff --git a/zebrad/tests/common/config.rs b/zebrad/tests/common/config.rs index ed399e9a26d..d6ee45f52ee 100644 --- a/zebrad/tests/common/config.rs +++ b/zebrad/tests/common/config.rs @@ -80,27 +80,6 @@ pub fn default_test_config(net: &Network) -> Result { mining.miner_address = Some(miner_address.parse().expect("hard-coded address is valid")); } - #[cfg(feature = "shielded-scan")] - { - let mut shielded_scan = zebra_scan::Config::ephemeral(); - shielded_scan.db_config_mut().cache_dir = "zebra-scan".into(); - - let config = ZebradConfig { - network, - state, - sync, - mempool, - consensus, - tracing, - mining, - shielded_scan, - ..ZebradConfig::default() - }; - - Ok(config) - } - - #[cfg(not(feature = "shielded-scan"))] Ok(ZebradConfig { network, state, diff --git a/zebrad/tests/common/configs/shieldedscan-v1.5.0.toml b/zebrad/tests/common/configs/shieldedscan-v1.5.0.toml deleted file mode 100644 index e701e9ff3e8..00000000000 --- a/zebrad/tests/common/configs/shieldedscan-v1.5.0.toml +++ /dev/null @@ -1,80 +0,0 @@ -# Default configuration for zebrad. -# -# This file can be used as a skeleton for custom configs. -# -# Unspecified fields use default values. Optional fields are Some(field) if the -# field is present and None if it is absent. -# -# This file is generated as an example using zebrad's current defaults. -# You should set only the config options you want to keep, and delete the rest. -# Only a subset of fields are present in the skeleton, since optional values -# whose default is None are omitted. -# -# The config format (including a complete list of sections and fields) is -# documented here: -# https://docs.rs/zebrad/latest/zebrad/config/struct.ZebradConfig.html -# -# zebrad attempts to load configs in the following order: -# -# 1. The -c flag on the command line, e.g., `zebrad -c myconfig.toml start`; -# 2. The file `zebrad.toml` in the users's preference directory (platform-dependent); -# 3. The default config. - -[consensus] -checkpoint_sync = true - -[mempool] -eviction_memory_time = "1h" -tx_cost_limit = 80000000 - -[metrics] - -[mining] -debug_like_zcashd = true - -[network] -cache_dir = true -crawl_new_peer_interval = "1m 1s" -initial_mainnet_peers = [ - "dnsseed.z.cash:8233", - "dnsseed.str4d.xyz:8233", - "mainnet.seeder.zfnd.org:8233", - "mainnet.is.yolo.money:8233", -] -initial_testnet_peers = [ - "dnsseed.testnet.z.cash:18233", - "testnet.seeder.zfnd.org:18233", - "testnet.is.yolo.money:18233", -] -listen_addr = "0.0.0.0:8233" -max_connections_per_ip = 1 -network = "Mainnet" -peerset_initial_target_size = 25 - -[rpc] -debug_force_finished_sync = false -parallel_cpu_threads = 0 - -[shielded_scan] -cache_dir = "cache_dir" -delete_old_database = true -ephemeral = false - -[shielded_scan.sapling_keys_to_scan] - -[state] -cache_dir = "cache_dir" -delete_old_database = true -ephemeral = false - -[sync] -checkpoint_verify_concurrency_limit = 1000 -download_concurrency_limit = 50 -full_verify_concurrency_limit = 20 -parallel_cpu_threads = 0 - -[tracing] -buffer_limit = 128000 -force_use_color = false -use_color = true -use_journald = false diff --git a/zebrad/tests/common/configs/shieldedscan-v1.6.0.toml b/zebrad/tests/common/configs/shieldedscan-v1.6.0.toml deleted file mode 100644 index 7cd2eb43f92..00000000000 --- a/zebrad/tests/common/configs/shieldedscan-v1.6.0.toml +++ /dev/null @@ -1,89 +0,0 @@ -# Default configuration for zebrad. -# -# This file can be used as a skeleton for custom configs. -# -# Unspecified fields use default values. Optional fields are Some(field) if the -# field is present and None if it is absent. -# -# This file is generated as an example using zebrad's current defaults. -# You should set only the config options you want to keep, and delete the rest. -# Only a subset of fields are present in the skeleton, since optional values -# whose default is None are omitted. -# -# The config format (including a complete list of sections and fields) is -# documented here: -# https://docs.rs/zebrad/latest/zebrad/config/struct.ZebradConfig.html -# -# zebrad attempts to load configs in the following order: -# -# 1. The -c flag on the command line, e.g., `zebrad -c myconfig.toml start`; -# 2. The file `zebrad.toml` in the users's preference directory (platform-dependent); -# 3. The default config. -# -# The user's preference directory and the default path to the `zebrad` config are platform dependent, -# based on `dirs::preference_dir`, see https://docs.rs/dirs/latest/dirs/fn.preference_dir.html : -# -# | Platform | Value | Example | -# | -------- | ------------------------------------- | ---------------------------------------------- | -# | Linux | `$XDG_CONFIG_HOME` or `$HOME/.config` | `/home/alice/.config/zebrad.toml` | -# | macOS | `$HOME/Library/Preferences` | `/Users/Alice/Library/Preferences/zebrad.toml` | -# | Windows | `{FOLDERID_RoamingAppData}` | `C:\Users\Alice\AppData\Local\zebrad.toml` | - -[consensus] -checkpoint_sync = true - -[mempool] -eviction_memory_time = "1h" -tx_cost_limit = 80000000 - -[metrics] - -[mining] -debug_like_zcashd = true - -[network] -cache_dir = true -crawl_new_peer_interval = "1m 1s" -initial_mainnet_peers = [ - "dnsseed.z.cash:8233", - "dnsseed.str4d.xyz:8233", - "mainnet.seeder.zfnd.org:8233", - "mainnet.is.yolo.money:8233", -] -initial_testnet_peers = [ - "dnsseed.testnet.z.cash:18233", - "testnet.seeder.zfnd.org:18233", - "testnet.is.yolo.money:18233", -] -listen_addr = "0.0.0.0:8233" -max_connections_per_ip = 1 -network = "Mainnet" -peerset_initial_target_size = 25 - -[rpc] -debug_force_finished_sync = false -parallel_cpu_threads = 0 - -[shielded_scan] -cache_dir = "cache_dir" -delete_old_database = true -ephemeral = false - -[shielded_scan.sapling_keys_to_scan] - -[state] -cache_dir = "cache_dir" -delete_old_database = true -ephemeral = false - -[sync] -checkpoint_verify_concurrency_limit = 1000 -download_concurrency_limit = 50 -full_verify_concurrency_limit = 20 -parallel_cpu_threads = 0 - -[tracing] -buffer_limit = 128000 -force_use_color = false -use_color = true -use_journald = false \ No newline at end of file diff --git a/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs index 03274868d88..702bb740142 100644 --- a/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs +++ b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs @@ -31,8 +31,7 @@ //! //! - `GetLightdInfo`: Covered. //! -//! - `Ping`: Not covered and it will never be. `Ping` is only used for testing -//! purposes. +//! - `Ping`: Not covered and it will never be. `Ping` is only used for testing purposes. use color_eyre::eyre::Result; use hex_literal::hex; From aa88979ede5dce8afecafce2e858571a93d63587 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 22 Jul 2024 19:27:20 -0400 Subject: [PATCH 02/35] checks network magic and returns early from `is_regtest()` --- zebra-chain/src/parameters/network/testnet.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 415b78a114d..d48c30717f6 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -383,9 +383,14 @@ impl Parameters { /// Returns true if the instance of [`Parameters`] represents Regtest. pub fn is_regtest(&self) -> bool { + if self.network_magic != magics::REGTEST { + return false; + } + let Self { network_name, - network_magic, + // Already checked network magic above + network_magic: _, genesis_hash, // Activation heights are configurable on Regtest activation_heights: _, @@ -396,7 +401,6 @@ impl Parameters { } = Self::new_regtest(None, None); self.network_name == network_name - && self.network_magic == network_magic && self.genesis_hash == genesis_hash && self.slow_start_interval == slow_start_interval && self.slow_start_shift == slow_start_shift From d9a53e6d7c7247488ccdae9c87410c83d09cd360 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 12:40:20 -0400 Subject: [PATCH 03/35] 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` --- zebra-chain/src/parameters.rs | 2 +- zebra-chain/src/parameters/network.rs | 1 + .../src/parameters/network}/subsidy.rs | 249 ++++++++++++------ zebra-chain/src/parameters/network/testnet.rs | 67 ++++- .../src/block/subsidy/funding_streams.rs | 57 ++-- .../block/subsidy/funding_streams/tests.rs | 17 +- zebra-consensus/src/block/subsidy/general.rs | 4 +- zebra-consensus/src/lib.rs | 2 - zebra-consensus/src/parameters.rs | 17 -- .../src/methods/get_block_template_rpcs.rs | 12 +- .../get_block_template_rpcs/constants.rs | 6 +- .../get_block_template.rs | 6 +- .../get_block_template_rpcs/types/subsidy.rs | 8 +- 13 files changed, 304 insertions(+), 144 deletions(-) rename {zebra-consensus/src/parameters => zebra-chain/src/parameters/network}/subsidy.rs (64%) delete mode 100644 zebra-consensus/src/parameters.rs diff --git a/zebra-chain/src/parameters.rs b/zebra-chain/src/parameters.rs index bfe806556a4..ebfa401d7b7 100644 --- a/zebra-chain/src/parameters.rs +++ b/zebra-chain/src/parameters.rs @@ -22,7 +22,7 @@ mod transaction; pub mod arbitrary; pub use genesis::*; -pub use network::{magic::Magic, testnet, Network, NetworkKind}; +pub use network::{magic::Magic, subsidy, testnet, Network, NetworkKind}; pub use network_upgrade::*; pub use transaction::*; diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index 093b53bf36b..8cec6c16c1e 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -10,6 +10,7 @@ use crate::{ }; pub mod magic; +pub mod subsidy; pub mod testnet; #[cfg(test)] diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs similarity index 64% rename from zebra-consensus/src/parameters/subsidy.rs rename to zebra-chain/src/parameters/network/subsidy.rs index 268221c6669..9bc69b9bca9 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -1,13 +1,11 @@ //! Constants for Block Subsidy and Funding Streams -use std::collections::HashMap; - use lazy_static::lazy_static; -use zebra_chain::{ +use crate::{ amount::COIN, block::{Height, HeightDiff}, - parameters::{Network, NetworkKind, NetworkUpgrade}, + parameters::{Network, NetworkUpgrade}, }; /// The largest block subsidy, used before the first halving. @@ -60,76 +58,182 @@ pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100; /// [ZIP-214]: https://zips.z.cash/zip-0214 pub const FUNDING_STREAM_SPECIFICATION: &str = "https://zips.z.cash/zip-0214"; -// TODO: use a struct for the info for each funding stream, like zcashd does: -// https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32 +/// Funding stream recipients and height ranges. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FundingStreams { + recipients: Vec, + height_range: std::ops::Range, +} + +impl FundingStreams { + /// Returns height range where these [`FundingStreams`] should apply. + pub fn height_range(&self) -> &std::ops::Range { + &self.height_range + } + + /// Returns recipients of these [`FundingStreams`]. + pub fn recipients(&self) -> &[FundingStreamRecipient] { + &self.recipients + } + + /// Returns a recipient with the provided receiver. + pub fn recipient_by_receiver( + &self, + receiver: FundingStreamReceiver, + ) -> Option<&FundingStreamRecipient> { + self.recipients + .iter() + .find(|recipient| recipient.receiver() == receiver) + } +} + +/// A funding stream recipient as specified in [protocol specification §7.10.1][7.10.1] +/// +/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FundingStreamRecipient { + receiver: FundingStreamReceiver, + name: String, + numerator: u64, + addresses: Vec, +} + +impl FundingStreamRecipient { + /// Creates a new [`FundingStreamRecipient`]. + pub fn new( + receiver: FundingStreamReceiver, + name: impl Into, + numerator: u64, + addresses: Vec, + ) -> Self { + Self { + receiver, + name: name.into(), + numerator, + addresses, + } + } + + /// Returns the receiver of this funding stream. + pub fn receiver(&self) -> FundingStreamReceiver { + self.receiver + } + + /// Returns the name of this funding stream. + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the numerator for this funding stream. + pub fn numerator(&self) -> u64 { + self.numerator + } + + /// Returns the receiver of this funding stream. + pub fn addresses(&self) -> &[String] { + &self.addresses + } +} + lazy_static! { - /// The name for each funding stream receiver, as described in [ZIP-1014] and [`zcashd`]. - /// - /// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract - /// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32 - pub static ref FUNDING_STREAM_NAMES: HashMap = { - let mut hash_map = HashMap::new(); - hash_map.insert(FundingStreamReceiver::Ecc, "Electric Coin Company"); - hash_map.insert(FundingStreamReceiver::ZcashFoundation, "Zcash Foundation"); - hash_map.insert(FundingStreamReceiver::MajorGrants, "Major Grants"); - hash_map + /// The pre-NU6 funding streams for Mainnet + pub static ref PRE_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { + recipients: vec![ + FundingStreamRecipient::new( + FundingStreamReceiver::Ecc, + "Electric Coin Company", + 7, + FUNDING_STREAM_ECC_ADDRESSES_MAINNET + .iter() + .map(ToString::to_string) + .collect(), + ), + FundingStreamRecipient::new( + FundingStreamReceiver::ZcashFoundation, + "Zcash Foundation", + 5, + FUNDING_STREAM_ZF_ADDRESSES_MAINNET + .iter() + .map(ToString::to_string) + .collect(), + ), + FundingStreamRecipient::new( + FundingStreamReceiver::MajorGrants, + "Major Grants", + 8, + FUNDING_STREAM_MG_ADDRESSES_MAINNET + .iter() + .map(ToString::to_string) + .collect(), + ), + ], + height_range: Height(1_046_400)..Height(2_726_400), }; - - /// The numerator for each funding stream receiver category - /// as described in [protocol specification §7.10.1][7.10.1]. - /// - /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams - pub static ref FUNDING_STREAM_RECEIVER_NUMERATORS: HashMap = { - let mut hash_map = HashMap::new(); - hash_map.insert(FundingStreamReceiver::Ecc, 7); - hash_map.insert(FundingStreamReceiver::ZcashFoundation, 5); - hash_map.insert(FundingStreamReceiver::MajorGrants, 8); - hash_map + /// The pre-NU6 funding streams for Testnet + pub static ref PRE_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { + recipients: vec![ + FundingStreamRecipient::new( + FundingStreamReceiver::Ecc, + "Electric Coin Company", + 7, + FUNDING_STREAM_ECC_ADDRESSES_TESTNET + .iter() + .map(ToString::to_string) + .collect(), + ), + FundingStreamRecipient::new( + FundingStreamReceiver::ZcashFoundation, + "Zcash Foundation", + 5, + FUNDING_STREAM_ZF_ADDRESSES_TESTNET + .iter() + .map(ToString::to_string) + .collect(), + ), + FundingStreamRecipient::new( + FundingStreamReceiver::MajorGrants, + "Major Grants", + 8, + FUNDING_STREAM_MG_ADDRESSES_TESTNET + .iter() + .map(ToString::to_string) + .collect(), + ), + ], + height_range: Height(1_028_500)..Height(2_796_000), }; - /// Start and end Heights for funding streams - /// as described in [protocol specification §7.10.1][7.10.1]. - /// - /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams - // TODO: Move the value here to a field on `testnet::Parameters` (#8367) - pub static ref FUNDING_STREAM_HEIGHT_RANGES: HashMap> = { - let mut hash_map = HashMap::new(); - hash_map.insert(NetworkKind::Mainnet, Height(1_046_400)..Height(2_726_400)); - hash_map.insert(NetworkKind::Testnet, Height(1_028_500)..Height(2_796_000)); - hash_map.insert(NetworkKind::Regtest, Height(1_028_500)..Height(2_796_000)); - hash_map + /// The post-NU6 funding streams for Mainnet + pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { + recipients: vec![ + FundingStreamRecipient::new( + FundingStreamReceiver::MajorGrants, + "Major Grants", + 8, + FUNDING_STREAM_MG_ADDRESSES_MAINNET + .iter() + .map(ToString::to_string) + .collect(), + ), + ], + height_range: Height(2_726_400)..Height(3_146_400), }; - /// Convenient storage for all addresses, for all receivers and networks - pub static ref FUNDING_STREAM_ADDRESSES: HashMap>> = { - 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(NetworkKind::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(NetworkKind::Testnet, testnet_addresses); - - - // Regtest addresses - // TODO: Move the value here to a field on `testnet::Parameters` (#8367) - // There are no funding stream addresses on Regtest in zcashd, zebrad should do the same for compatibility. - let mut regtest_addresses = HashMap::with_capacity(3); - regtest_addresses.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); - regtest_addresses.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); - regtest_addresses.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); - addresses_by_network.insert(NetworkKind::Testnet, regtest_addresses); - - addresses_by_network + /// The post-NU6 funding streams for Testnet + pub static ref POST_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { + recipients: vec![ + FundingStreamRecipient::new( + FundingStreamReceiver::MajorGrants, + "Major Grants", + 8, + FUNDING_STREAM_MG_ADDRESSES_TESTNET + .iter() + .map(ToString::to_string) + .collect(), + ), + ], + height_range: Height(2_942_000)..Height(3_362_000), }; } @@ -200,9 +304,6 @@ pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRE /// Functionality specific to block subsidy-related consensus rules pub trait ParameterSubsidy { - /// Number of addresses for each funding stream in the Network. - /// [7.10]: - fn num_funding_streams(&self) -> usize; /// Returns the minimum height after the first halving /// as described in [protocol specification §7.10][7.10] /// @@ -212,14 +313,6 @@ pub trait ParameterSubsidy { /// Network methods related to Block Subsidy and Funding Streams impl ParameterSubsidy for Network { - fn num_funding_streams(&self) -> usize { - match self { - Network::Mainnet => FUNDING_STREAMS_NUM_ADDRESSES_MAINNET, - // TODO: Check what zcashd does here, consider adding a field to `NetworkParamters` to make this configurable. - Network::Testnet(_params) => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET, - } - } - fn height_for_first_halving(&self) -> Height { // First halving on Mainnet is at Canopy // while in Testnet is at block constant height of `1_116_000` diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index d48c30717f6..d1a2a1953e0 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -11,7 +11,13 @@ use crate::{ work::difficulty::{ExpandedDifficulty, U256}, }; -use super::magic::Magic; +use super::{ + magic::Magic, + subsidy::{ + FundingStreams, POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET, + PRE_NU6_FUNDING_STREAMS_MAINNET, PRE_NU6_FUNDING_STREAMS_TESTNET, + }, +}; /// The Regtest NU5 activation height in tests // TODO: Serialize testnet parameters in Config then remove this and use a configured NU5 activation height. @@ -79,6 +85,10 @@ pub struct ParametersBuilder { activation_heights: BTreeMap, /// Slow start interval for this network slow_start_interval: Height, + /// Pre-NU6 funding streams for this network + pre_nu6_funding_streams: FundingStreams, + /// Post-NU6 funding streams for this network + post_nu6_funding_streams: FundingStreams, /// Target difficulty limit for this network target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks @@ -113,6 +123,8 @@ impl Default for ParametersBuilder { .to_expanded() .expect("difficulty limits are valid expanded values"), disable_pow: false, + pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), + post_nu6_funding_streams: POST_NU6_FUNDING_STREAMS_TESTNET.clone(), } } } @@ -263,6 +275,8 @@ impl ParametersBuilder { genesis_hash, activation_heights, slow_start_interval, + pre_nu6_funding_streams, + post_nu6_funding_streams, target_difficulty_limit, disable_pow, } = self; @@ -273,6 +287,8 @@ impl ParametersBuilder { activation_heights, slow_start_interval, slow_start_shift: Height(slow_start_interval.0 / 2), + pre_nu6_funding_streams, + post_nu6_funding_streams, target_difficulty_limit, disable_pow, } @@ -291,6 +307,8 @@ impl ParametersBuilder { genesis_hash, activation_heights, slow_start_interval, + pre_nu6_funding_streams, + post_nu6_funding_streams, target_difficulty_limit, disable_pow, } = Self::default(); @@ -299,6 +317,8 @@ impl ParametersBuilder { && self.network_magic == network_magic && self.genesis_hash == genesis_hash && self.slow_start_interval == slow_start_interval + && self.pre_nu6_funding_streams == pre_nu6_funding_streams + && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow } @@ -323,6 +343,10 @@ pub struct Parameters { slow_start_interval: Height, /// Slow start shift for this network, always half the slow start interval slow_start_shift: Height, + /// Pre-NU6 funding streams for this network + pre_nu6_funding_streams: FundingStreams, + /// Post-NU6 funding streams for this network + post_nu6_funding_streams: FundingStreams, /// Target difficulty limit for this network target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks @@ -396,6 +420,8 @@ impl Parameters { activation_heights: _, slow_start_interval, slow_start_shift, + pre_nu6_funding_streams, + post_nu6_funding_streams, target_difficulty_limit, disable_pow, } = Self::new_regtest(None, None); @@ -404,6 +430,8 @@ impl Parameters { && self.genesis_hash == genesis_hash && self.slow_start_interval == slow_start_interval && self.slow_start_shift == slow_start_shift + && self.pre_nu6_funding_streams == pre_nu6_funding_streams + && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow } @@ -438,6 +466,16 @@ impl Parameters { self.slow_start_shift } + /// Returns pre-NU6 funding streams for this network + pub fn pre_nu6_funding_streams(&self) -> &FundingStreams { + &self.pre_nu6_funding_streams + } + + /// Returns post-NU6 funding streams for this network + pub fn post_nu6_funding_streams(&self) -> &FundingStreams { + &self.post_nu6_funding_streams + } + /// Returns the target difficulty limit for this network pub fn target_difficulty_limit(&self) -> ExpandedDifficulty { self.target_difficulty_limit @@ -476,4 +514,31 @@ impl Network { SLOW_START_SHIFT } } + + /// Returns pre-NU6 funding streams for this network + pub fn pre_nu6_funding_streams(&self) -> &FundingStreams { + if let Self::Testnet(params) = self { + params.pre_nu6_funding_streams() + } else { + &PRE_NU6_FUNDING_STREAMS_MAINNET + } + } + + /// Returns post-NU6 funding streams for this network + pub fn post_nu6_funding_streams(&self) -> &FundingStreams { + if let Self::Testnet(params) = self { + params.pre_nu6_funding_streams() + } else { + &POST_NU6_FUNDING_STREAMS_MAINNET + } + } + + /// Returns funding streams for this network at the provided height + pub fn funding_streams(&self, height: Height) -> &FundingStreams { + if NetworkUpgrade::current(self, height) < NetworkUpgrade::Nu6 { + self.pre_nu6_funding_streams() + } else { + self.post_nu6_funding_streams() + } + } } diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index c3389782b17..98f37d6998c 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -7,12 +7,12 @@ use std::{collections::HashMap, str::FromStr}; use zebra_chain::{ amount::{Amount, Error, NonNegative}, block::Height, - parameters::{Network, NetworkUpgrade::*}, + parameters::{subsidy::*, Network, NetworkUpgrade::*}, transaction::Transaction, transparent::{self, Script}, }; -use crate::{block::subsidy::general::block_subsidy, parameters::subsidy::*}; +use crate::block::subsidy::general::block_subsidy; #[cfg(test)] mod tests; @@ -29,19 +29,19 @@ pub fn funding_stream_values( let mut results = HashMap::new(); if height >= canopy_height { - let range = FUNDING_STREAM_HEIGHT_RANGES.get(&network.kind()).unwrap(); - if range.contains(&height) { + let funding_streams = network.funding_streams(height); + if funding_streams.height_range().contains(&height) { let block_subsidy = block_subsidy(height, network)?; - for (&receiver, &numerator) in FUNDING_STREAM_RECEIVER_NUMERATORS.iter() { + for recipient in funding_streams.recipients() { // - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`: // https://zips.z.cash/protocol/protocol.pdf#subsidies // - In Rust, "integer division rounds towards zero": // https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators // This is the same as `floor()`, because these numbers are all positive. - let amount_value = - ((block_subsidy * numerator)? / FUNDING_STREAM_RECEIVER_DENOMINATOR)?; + let amount_value = ((block_subsidy * recipient.numerator())? + / FUNDING_STREAM_RECEIVER_DENOMINATOR)?; - results.insert(receiver, amount_value); + results.insert(recipient.receiver(), amount_value); } } } @@ -77,20 +77,23 @@ 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 = network.num_funding_streams(); + let funding_streams = network.funding_streams(height); 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.kind()) - .unwrap() - .start, + funding_streams.height_range().start, network, )) .expect("no overflow should happen in this sub") as usize; + let num_addresses = funding_streams + .recipients() + .first() + .map(|recipient| recipient.addresses().len()) + .unwrap_or_default(); + 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. @@ -107,21 +110,31 @@ pub fn funding_stream_address( receiver: FundingStreamReceiver, ) -> transparent::Address { let index = funding_stream_address_index(height, network); - let address = &FUNDING_STREAM_ADDRESSES - .get(&network.kind()) - .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]; + let funding_streams = network.funding_streams(height); + let address = &funding_streams + .recipient_by_receiver(receiver) + // TODO: Change return type to option and return None here instead of panicking + .unwrap() + .addresses() + .get(index) + // TODO: Change return type to option and return None here instead of panicking + .unwrap(); transparent::Address::from_str(address).expect("address should deserialize") } /// Return a human-readable name and a specification URL for the funding stream `receiver`. pub fn funding_stream_recipient_info( + network: &Network, + height: Height, receiver: FundingStreamReceiver, -) -> (&'static str, &'static str) { - let name = FUNDING_STREAM_NAMES - .get(&receiver) - .expect("all funding streams have a name"); +) -> (String, &'static str) { + let name = network + .funding_streams(height) + .recipient_by_receiver(receiver) + // TODO: Replace with optional return type + .unwrap() + .name() + .to_string(); (name, FUNDING_STREAM_SPECIFICATION) } diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 7f6011a422c..62362c12628 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -1,6 +1,7 @@ //! Tests for funding streams. use color_eyre::Report; +use zebra_chain::parameters::subsidy::FundingStreamReceiver; use super::*; @@ -44,7 +45,8 @@ fn test_funding_stream_values() -> Result<(), Report> { ); // funding stream period is ending - let range = FUNDING_STREAM_HEIGHT_RANGES.get(&network.kind()).unwrap(); + let funding_streams = network.pre_nu6_funding_streams(); + let range = funding_streams.height_range(); let end = range.end; let last = end - 1; @@ -61,15 +63,16 @@ fn test_funding_stream_values() -> Result<(), Report> { #[test] fn test_funding_stream_addresses() -> Result<(), Report> { let _init_guard = zebra_test::init(); - - for (network, receivers) in FUNDING_STREAM_ADDRESSES.iter() { - for (receiver, addresses) in receivers { - for address in addresses { + for network in Network::iter() { + for funding_stream_recipient in network.pre_nu6_funding_streams().recipients() { + let receiver = funding_stream_recipient.receiver(); + for address in funding_stream_recipient.addresses() { let address = transparent::Address::from_str(address).expect("address should deserialize"); + assert_eq!( - &address.network_kind(), - network, + address.network_kind(), + network.kind(), "incorrect network for {receiver:?} funding stream address constant: {address}", ); diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index d7013c84ed4..193f32cb2b3 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -7,11 +7,11 @@ use std::collections::HashSet; use zebra_chain::{ amount::{Amount, Error, NonNegative}, block::{Height, HeightDiff}, - parameters::{Network, NetworkUpgrade::*}, + parameters::{subsidy::*, Network, NetworkUpgrade::*}, transaction::Transaction, }; -use crate::{funding_stream_values, parameters::subsidy::*}; +use crate::funding_stream_values; /// The divisor used for halvings. /// diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index 8ba8fa3a2be..66323a3b550 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -36,7 +36,6 @@ mod block; mod checkpoint; -mod parameters; mod primitives; mod script; @@ -64,7 +63,6 @@ pub use checkpoint::{ }; pub use config::Config; pub use error::BlockError; -pub use parameters::{FundingStreamReceiver, ParameterSubsidy}; pub use primitives::{ed25519, groth16, halo2, redjubjub, redpallas}; pub use router::RouterError; diff --git a/zebra-consensus/src/parameters.rs b/zebra-consensus/src/parameters.rs deleted file mode 100644 index 0b3798c0ba2..00000000000 --- a/zebra-consensus/src/parameters.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! The consensus parameters for each Zcash network. -//! -//! This module contains the consensus parameters which are required for -//! verification. -//! -//! Some consensus parameters change based on network upgrades. Each network -//! upgrade happens at a particular block height. Some parameters have a value -//! (or function) before the upgrade height, at the upgrade height, and after -//! the upgrade height. (For example, the value of the reserved field in the -//! block header during the Heartwood upgrade.) -//! -//! Typically, consensus parameters are accessed via a function that takes a -//! `Network` and `block::Height`. - -pub mod subsidy; - -pub use subsidy::*; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index a1c368ac4e0..12282a01dcc 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -14,7 +14,7 @@ use zebra_chain::{ block::{self, Block, Height, TryIntoHeight}, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, - parameters::{Network, NetworkKind, POW_AVERAGING_WINDOW}, + parameters::{subsidy::ParameterSubsidy, Network, NetworkKind, POW_AVERAGING_WINDOW}, primitives, serialization::ZcashDeserializeInto, transparent::{ @@ -22,10 +22,7 @@ use zebra_chain::{ }, work::difficulty::{ParameterDifficulty as _, U256}, }; -use zebra_consensus::{ - funding_stream_address, funding_stream_values, miner_subsidy, ParameterSubsidy as _, - RouterError, -}; +use zebra_consensus::{funding_stream_address, funding_stream_values, miner_subsidy, RouterError}; use zebra_network::AddressBookPeers; use zebra_node_services::mempool; use zebra_state::{ReadRequest, ReadResponse}; @@ -1202,7 +1199,10 @@ where .iter() .map(|(receiver, value)| { let address = funding_stream_address(height, &network, *receiver); - (*receiver, FundingStream::new(*receiver, *value, address)) + ( + *receiver, + FundingStream::new(&network, height, *receiver, *value, address), + ) }) .collect(); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs b/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs index 75b26055c8e..3fd4696980d 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs @@ -2,8 +2,10 @@ use jsonrpc_core::ErrorCode; -use zebra_chain::block; -use zebra_consensus::FundingStreamReceiver::{self, *}; +use zebra_chain::{ + block, + parameters::subsidy::FundingStreamReceiver::{self, *}, +}; /// When long polling, the amount of time we wait between mempool queries. /// (And sync status queries, which we do right before mempool queries.) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index 62026b1bfad..a5d3dc13093 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -14,14 +14,12 @@ use zebra_chain::{ }, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, - parameters::{Network, NetworkUpgrade}, + parameters::{subsidy::FundingStreamReceiver, Network, NetworkUpgrade}, serialization::ZcashDeserializeInto, transaction::{Transaction, UnminedTx, VerifiedUnminedTx}, transparent, }; -use zebra_consensus::{ - funding_stream_address, funding_stream_values, miner_subsidy, FundingStreamReceiver, -}; +use zebra_consensus::{funding_stream_address, funding_stream_values, miner_subsidy}; use zebra_node_services::mempool; use zebra_state::GetBlockTemplateChainInfo; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs index 55572390733..5dd8221f3eb 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -2,9 +2,11 @@ use zebra_chain::{ amount::{Amount, NonNegative}, + block::Height, + parameters::{subsidy::FundingStreamReceiver, Network}, transparent, }; -use zebra_consensus::{funding_stream_recipient_info, FundingStreamReceiver}; +use zebra_consensus::funding_stream_recipient_info; use crate::methods::get_block_template_rpcs::types::zec::Zec; @@ -64,11 +66,13 @@ pub struct FundingStream { impl FundingStream { /// Convert a `receiver`, `value`, and `address` into a `FundingStream` response. pub fn new( + network: &Network, + height: Height, receiver: FundingStreamReceiver, value: Amount, address: transparent::Address, ) -> FundingStream { - let (recipient, specification) = funding_stream_recipient_info(receiver); + let (recipient, specification) = funding_stream_recipient_info(network, height, receiver); FundingStream { recipient: recipient.to_string(), From 124e24966d8479fc561523c43e2f4f79e61070e0 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 21:05:01 -0400 Subject: [PATCH 04/35] Replaces Vec with HashMap, adds `ConfiguredFundingStreams` type and conversion logic with constraints. Minor refactors --- zebra-chain/src/parameters/network/subsidy.rs | 245 ++++++++++-------- zebra-chain/src/parameters/network/testnet.rs | 135 +++++++++- .../src/parameters/network/tests/vectors.rs | 130 +++++++++- zebra-consensus/src/block/check.rs | 2 +- .../src/block/subsidy/funding_streams.rs | 58 +---- .../block/subsidy/funding_streams/tests.rs | 10 +- zebra-network/src/config.rs | 15 +- .../src/methods/get_block_template_rpcs.rs | 5 +- .../get_block_template.rs | 8 +- .../get_block_template_rpcs/types/subsidy.rs | 11 +- zebrad/tests/common/configs/v1.9.0.toml | 120 +++++++++ 11 files changed, 553 insertions(+), 186 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index 9bc69b9bca9..af04057c425 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -1,4 +1,6 @@ -//! Constants for Block Subsidy and Funding Streams +//! Constants and calculations for Block Subsidy and Funding Streams + +use std::collections::HashMap; use lazy_static::lazy_static; @@ -6,6 +8,7 @@ use crate::{ amount::COIN, block::{Height, HeightDiff}, parameters::{Network, NetworkUpgrade}, + transparent, }; /// The largest block subsidy, used before the first halving. @@ -36,9 +39,10 @@ pub const POST_BLOSSOM_HALVING_INTERVAL: HeightDiff = pub const FIRST_HALVING_TESTNET: Height = Height(1_116_000); /// The funding stream receiver categories. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum FundingStreamReceiver { /// The Electric Coin Company (Bootstrap Foundation) funding stream. + #[serde(rename = "ECC")] Ecc, /// The Zcash Foundation funding stream. @@ -48,6 +52,20 @@ pub enum FundingStreamReceiver { MajorGrants, } +impl FundingStreamReceiver { + /// The name for each funding stream receiver, as described in [ZIP-1014] and [`zcashd`]. + /// + /// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract + /// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32 + pub fn name(self) -> &'static str { + match self { + FundingStreamReceiver::Ecc => "Electric Coin Company", + FundingStreamReceiver::ZcashFoundation => "Zcash Foundation", + FundingStreamReceiver::MajorGrants => "Major Grants", + } + } +} + /// Denominator as described in [protocol specification §7.10.1][7.10.1]. /// /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams @@ -59,78 +77,86 @@ pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100; pub const FUNDING_STREAM_SPECIFICATION: &str = "https://zips.z.cash/zip-0214"; /// Funding stream recipients and height ranges. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Clone, Debug, Eq, PartialEq)] pub struct FundingStreams { - recipients: Vec, + /// Start and end Heights for funding streams + /// as described in [protocol specification §7.10.1][7.10.1]. + /// + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams height_range: std::ops::Range, + /// Funding stream recipients by [`FundingStreamReceiver`]. + recipients: HashMap, } impl FundingStreams { + /// Creates a new [`FundingStreams`]. + pub fn new( + height_range: std::ops::Range, + recipients: HashMap, + ) -> Self { + Self { + height_range, + recipients, + } + } + /// Returns height range where these [`FundingStreams`] should apply. pub fn height_range(&self) -> &std::ops::Range { &self.height_range } /// Returns recipients of these [`FundingStreams`]. - pub fn recipients(&self) -> &[FundingStreamRecipient] { + pub fn recipients(&self) -> &HashMap { &self.recipients } /// Returns a recipient with the provided receiver. - pub fn recipient_by_receiver( - &self, - receiver: FundingStreamReceiver, - ) -> Option<&FundingStreamRecipient> { - self.recipients - .iter() - .find(|recipient| recipient.receiver() == receiver) + pub fn recipient(&self, receiver: FundingStreamReceiver) -> Option<&FundingStreamRecipient> { + self.recipients.get(&receiver) } } /// A funding stream recipient as specified in [protocol specification §7.10.1][7.10.1] /// /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Clone, Debug, Eq, PartialEq)] pub struct FundingStreamRecipient { - receiver: FundingStreamReceiver, - name: String, + /// The numerator for each funding stream receiver category + /// as described in [protocol specification §7.10.1][7.10.1]. + /// + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams numerator: u64, - addresses: Vec, + /// Addresses for the funding stream recipient + addresses: Vec, } impl FundingStreamRecipient { /// Creates a new [`FundingStreamRecipient`]. - pub fn new( - receiver: FundingStreamReceiver, - name: impl Into, - numerator: u64, - addresses: Vec, - ) -> Self { + pub fn new(numerator: u64, addresses: I) -> Self + where + T: ToString, + I: IntoIterator, + { Self { - receiver, - name: name.into(), numerator, - addresses, + addresses: addresses + .into_iter() + .map(|addr| { + let addr = addr.to_string(); + addr.parse() + .expect("funding stream address must deserialize") + }) + .collect(), } } - /// Returns the receiver of this funding stream. - pub fn receiver(&self) -> FundingStreamReceiver { - self.receiver - } - - /// Returns the name of this funding stream. - pub fn name(&self) -> &str { - &self.name - } - /// Returns the numerator for this funding stream. pub fn numerator(&self) -> u64 { self.numerator } /// Returns the receiver of this funding stream. - pub fn addresses(&self) -> &[String] { + pub fn addresses(&self) -> &[transparent::Address] { &self.addresses } } @@ -138,102 +164,66 @@ impl FundingStreamRecipient { lazy_static! { /// The pre-NU6 funding streams for Mainnet pub static ref PRE_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( + height_range: Height(1_046_400)..Height(2_726_400), + recipients: [ + ( FundingStreamReceiver::Ecc, - "Electric Coin Company", - 7, - FUNDING_STREAM_ECC_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter()), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::ZcashFoundation, - "Zcash Foundation", - 5, - FUNDING_STREAM_ZF_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_MAINNET), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET), ), - ], - height_range: Height(1_046_400)..Height(2_726_400), + ] + .into_iter() + .collect(), + }; + + /// The post-NU6 funding streams for Mainnet + pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { + height_range: Height(2_726_400)..Height(3_146_400), + recipients: [( + FundingStreamReceiver::MajorGrants, + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET) + )] + .into_iter() + .collect(), }; /// The pre-NU6 funding streams for Testnet pub static ref PRE_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( + height_range: Height(1_028_500)..Height(2_796_000), + recipients: [ + ( FundingStreamReceiver::Ecc, - "Electric Coin Company", - 7, - FUNDING_STREAM_ECC_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_TESTNET), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::ZcashFoundation, - "Zcash Foundation", - 5, - FUNDING_STREAM_ZF_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_TESTNET), ), - FundingStreamRecipient::new( + ( FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), ), - ], - height_range: Height(1_028_500)..Height(2_796_000), - }; - - /// The post-NU6 funding streams for Mainnet - pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( - FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_MAINNET - .iter() - .map(ToString::to_string) - .collect(), - ), - ], - height_range: Height(2_726_400)..Height(3_146_400), + ] + .into_iter() + .collect(), }; /// The post-NU6 funding streams for Testnet pub static ref POST_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { - recipients: vec![ - FundingStreamRecipient::new( - FundingStreamReceiver::MajorGrants, - "Major Grants", - 8, - FUNDING_STREAM_MG_ADDRESSES_TESTNET - .iter() - .map(ToString::to_string) - .collect(), - ), - ], height_range: Height(2_942_000)..Height(3_362_000), + recipients: [( + FundingStreamReceiver::MajorGrants, + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), + )] + .into_iter() + .collect(), }; } @@ -312,7 +302,7 @@ pub trait ParameterSubsidy { } /// Network methods related to Block Subsidy and Funding Streams -impl ParameterSubsidy for Network { +impl ParameterSubsidy for &Network { fn height_for_first_halving(&self) -> Height { // First halving on Mainnet is at Canopy // while in Testnet is at block constant height of `1_116_000` @@ -326,6 +316,13 @@ impl ParameterSubsidy for Network { } } } + +impl ParameterSubsidy for Network { + fn height_for_first_halving(&self) -> Height { + (&self).height_for_first_halving() + } +} + /// List of addresses for the Zcash Foundation funding stream in the Mainnet. pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET]; @@ -403,3 +400,27 @@ pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRES /// List of addresses for the Major Grants funding stream in the Testnet. pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] = ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET]; + +/// Returns the address change period +/// as described in [protocol specification §7.10][7.10] +/// +/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams +pub fn funding_stream_address_period(height: Height, network: impl ParameterSubsidy) -> u32 { + // Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`, + // + // + // Note that the brackets make it so the post blossom halving interval is added to the total. + // + // In Rust, "integer division rounds towards zero": + // + // This is the same as `floor()`, because these numbers are all positive. + + let height_after_first_halving = height - network.height_for_first_halving(); + + let address_period = (height_after_first_halving + POST_BLOSSOM_HALVING_INTERVAL) + / FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL; + + address_period + .try_into() + .expect("all values are positive and smaller than the input height") +} diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index d1a2a1953e0..902a822d26a 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -6,6 +6,7 @@ use crate::{ parameters::{ constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT}, network_upgrade::TESTNET_ACTIVATION_HEIGHTS, + subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR}, Network, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER, }, work::difficulty::{ExpandedDifficulty, U256}, @@ -14,7 +15,8 @@ use crate::{ use super::{ magic::Magic, subsidy::{ - FundingStreams, POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET, + FundingStreamReceiver, FundingStreamRecipient, FundingStreams, ParameterSubsidy, + FIRST_HALVING_TESTNET, POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET, PRE_NU6_FUNDING_STREAMS_MAINNET, PRE_NU6_FUNDING_STREAMS_TESTNET, }, }; @@ -48,9 +50,116 @@ const REGTEST_GENESIS_HASH: &str = const TESTNET_GENESIS_HASH: &str = "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38"; +struct TestnetParameterSubsidyImpl; + +impl ParameterSubsidy for TestnetParameterSubsidyImpl { + fn height_for_first_halving(&self) -> Height { + FIRST_HALVING_TESTNET + } +} + +/// Configurable funding streams for Regtest and configured Testnets. +#[derive(Deserialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +pub struct ConfiguredFundingStreamRecipient { + /// Funding stream receiver, see [`FundingStreams::recipients`] for more details. + pub receiver: FundingStreamReceiver, + /// The numerator for each funding stream receiver category, see [`FundingStreamRecipient::numerator`] for more details. + pub numerator: u64, + /// Addresses for the funding stream recipient, see [`FundingStreamRecipient::addresses`] for more details. + pub addresses: Vec, +} + +impl ConfiguredFundingStreamRecipient { + /// Converts a [`ConfiguredFundingStreamRecipient`] to a [`FundingStreamReceiver`] and [`FundingStreamRecipient`]. + pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) { + ( + self.receiver, + FundingStreamRecipient::new(self.numerator, self.addresses), + ) + } +} + +/// Configurable funding streams for Regtest and configured Testnets. +#[derive(Deserialize, Clone, Default, Debug)] +#[serde(deny_unknown_fields)] +pub struct ConfiguredFundingStreams { + /// Start and end height for funding streams see [`FundingStreams::height_range`] for more details. + pub height_range: Option>, + /// Funding stream recipients, see [`FundingStreams::recipients`] for more details. + pub recipients: Option>, +} + +impl ConfiguredFundingStreams { + fn convert_with_default(self, default_funding_streams: FundingStreams) -> FundingStreams { + let height_range = self + .height_range + .unwrap_or(default_funding_streams.height_range().clone()); + + let recipients = self + .recipients + .map(|recipients| { + recipients + .into_iter() + .map(ConfiguredFundingStreamRecipient::into_recipient) + .collect() + }) + .unwrap_or(default_funding_streams.recipients().clone()); + + assert!( + height_range.start < height_range.end, + "funding stream end height must be above start height" + ); + + let funding_streams = FundingStreams::new(height_range.clone(), recipients); + + // check that receivers have enough addresses. + + let expected_min_num_addresses = + 1u32.checked_add(funding_stream_address_period( + height_range + .end + .previous() + .expect("end height must be above start height and genesis height"), + TestnetParameterSubsidyImpl, + )) + .expect("no overflow should happen in this sum") + .checked_sub(funding_stream_address_period( + height_range.start, + TestnetParameterSubsidyImpl, + )) + .expect("no overflow should happen in this sub") as usize; + + for recipient in funding_streams.recipients().values() { + // TODO: Make an exception for the `Deferred` receiver. + assert!( + recipient.addresses().len() >= expected_min_num_addresses, + "recipients must have a sufficient number of addresses for height range, \ + minimum num addresses required: {expected_min_num_addresses}" + ); + } + + // check that sum of receiver numerators is valid. + + let sum_numerators: u64 = funding_streams + .recipients() + .values() + .map(|r| r.numerator()) + .sum(); + + assert!( + sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR, + "sum of funding stream numerators must not be \ + greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}" + ); + + funding_streams + } +} + /// Configurable activation heights for Regtest and configured Testnets. #[derive(Deserialize, Default, Clone)] -#[serde(rename_all = "PascalCase")] +#[serde(rename_all = "PascalCase", deny_unknown_fields)] pub struct ConfiguredActivationHeights { /// Activation height for `BeforeOverwinter` network upgrade. pub before_overwinter: Option, @@ -247,6 +356,26 @@ impl ParametersBuilder { self } + /// Sets pre-NU6 funding streams to be used in the [`Parameters`] being built. + pub fn with_pre_nu6_funding_streams( + mut self, + funding_streams: ConfiguredFundingStreams, + ) -> Self { + self.pre_nu6_funding_streams = + funding_streams.convert_with_default(PRE_NU6_FUNDING_STREAMS_TESTNET.clone()); + self + } + + /// Sets post-NU6 funding streams to be used in the [`Parameters`] being built. + pub fn with_post_nu6_funding_streams( + mut self, + funding_streams: ConfiguredFundingStreams, + ) -> Self { + self.post_nu6_funding_streams = + funding_streams.convert_with_default(POST_NU6_FUNDING_STREAMS_TESTNET.clone()); + self + } + /// Sets the target difficulty limit to be used in the [`Parameters`] being built. // TODO: Accept a hex-encoded String instead? pub fn with_target_difficulty_limit( @@ -527,7 +656,7 @@ impl Network { /// Returns post-NU6 funding streams for this network pub fn post_nu6_funding_streams(&self) -> &FundingStreams { if let Self::Testnet(params) = self { - params.pre_nu6_funding_streams() + params.post_nu6_funding_streams() } else { &POST_NU6_FUNDING_STREAMS_MAINNET } diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index 5df16d7d38d..95c28572225 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -6,8 +6,13 @@ use zcash_protocol::consensus::NetworkConstants as _; use crate::{ block::Height, parameters::{ + subsidy::{ + FundingStreamReceiver, FUNDING_STREAM_ECC_ADDRESSES_TESTNET, + POST_NU6_FUNDING_STREAMS_TESTNET, PRE_NU6_FUNDING_STREAMS_TESTNET, + }, testnet::{ - self, ConfiguredActivationHeights, MAX_NETWORK_NAME_LENGTH, RESERVED_NETWORK_NAMES, + self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, + ConfiguredFundingStreams, MAX_NETWORK_NAME_LENGTH, RESERVED_NETWORK_NAMES, }, Network, NetworkUpgrade, MAINNET_ACTIVATION_HEIGHTS, NETWORK_UPGRADES_IN_ORDER, TESTNET_ACTIVATION_HEIGHTS, @@ -295,3 +300,126 @@ fn check_full_activation_list() { ); } } + +#[test] +fn check_funding_streams() { + let configured_funding_streams = [ + Default::default(), + ConfiguredFundingStreams { + height_range: Some(Height(2_000_000)..Height(2_200_000)), + ..Default::default() + }, + ConfiguredFundingStreams { + height_range: Some(Height(20)..Height(30)), + recipients: None, + }, + ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 20, + addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET + .map(Into::into) + .to_vec(), + }]), + ..Default::default() + }, + ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 100, + addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET + .map(Into::into) + .to_vec(), + }]), + ..Default::default() + }, + ]; + + for configured_funding_streams in configured_funding_streams { + for is_pre_nu6 in [false, true] { + let (network_funding_streams, default_funding_streams) = if is_pre_nu6 { + ( + testnet::Parameters::build() + .with_pre_nu6_funding_streams(configured_funding_streams.clone()) + .to_network() + .pre_nu6_funding_streams() + .clone(), + PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), + ) + } else { + ( + testnet::Parameters::build() + .with_post_nu6_funding_streams(configured_funding_streams.clone()) + .to_network() + .post_nu6_funding_streams() + .clone(), + POST_NU6_FUNDING_STREAMS_TESTNET.clone(), + ) + }; + + let expected_height_range = configured_funding_streams + .height_range + .clone() + .unwrap_or(default_funding_streams.height_range().clone()); + + assert_eq!( + network_funding_streams.height_range().clone(), + expected_height_range, + "should use default start height when unconfigured" + ); + + let expected_recipients = configured_funding_streams + .recipients + .clone() + .map(|recipients| { + recipients + .into_iter() + .map(ConfiguredFundingStreamRecipient::into_recipient) + .collect() + }) + .unwrap_or(default_funding_streams.recipients().clone()); + + assert_eq!( + network_funding_streams.recipients().clone(), + expected_recipients, + "should use default start height when unconfigured" + ); + } + } + + std::panic::set_hook(Box::new(|_| {})); + + // should panic when there are fewer addresses than the max funding stream address index. + let expected_panic_num_addresses = std::panic::catch_unwind(|| { + testnet::Parameters::build().with_pre_nu6_funding_streams(ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 10, + addresses: vec![], + }]), + ..Default::default() + }); + }); + + // should panic when sum of numerators is greater than funding stream denominator. + let expected_panic_numerator = std::panic::catch_unwind(|| { + testnet::Parameters::build().with_pre_nu6_funding_streams(ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 101, + addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET + .map(Into::into) + .to_vec(), + }]), + ..Default::default() + }); + }); + + // drop panic hook before expecting errors. + let _ = std::panic::take_hook(); + + expected_panic_num_addresses.expect_err("should panic when there are too few addresses"); + expected_panic_numerator.expect_err( + "should panic when sum of numerators is greater than funding stream denominator", + ); +} diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index d18893ecb73..0463a9c41a9 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -198,7 +198,7 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr subsidy::funding_streams::funding_stream_address(height, network, receiver); let has_expected_output = - subsidy::funding_streams::filter_outputs_by_address(coinbase, &address) + subsidy::funding_streams::filter_outputs_by_address(coinbase, address) .iter() .map(zebra_chain::transparent::Output::value) .any(|value| value == expected_amount); diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 98f37d6998c..7f95a0133c1 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -2,7 +2,7 @@ //! //! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies -use std::{collections::HashMap, str::FromStr}; +use std::collections::HashMap; use zebra_chain::{ amount::{Amount, Error, NonNegative}, @@ -32,7 +32,7 @@ pub fn funding_stream_values( let funding_streams = network.funding_streams(height); if funding_streams.height_range().contains(&height) { let block_subsidy = block_subsidy(height, network)?; - for recipient in funding_streams.recipients() { + for (&receiver, recipient) in funding_streams.recipients() { // - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`: // https://zips.z.cash/protocol/protocol.pdf#subsidies // - In Rust, "integer division rounds towards zero": @@ -41,37 +41,13 @@ pub fn funding_stream_values( let amount_value = ((block_subsidy * recipient.numerator())? / FUNDING_STREAM_RECEIVER_DENOMINATOR)?; - results.insert(recipient.receiver(), amount_value); + results.insert(receiver, amount_value); } } } Ok(results) } -/// Returns the address change period -/// as described in [protocol specification §7.10][7.10] -/// -/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams -fn funding_stream_address_period(height: Height, network: &Network) -> u32 { - // Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`, - // - // - // Note that the brackets make it so the post blossom halving interval is added to the total. - // - // In Rust, "integer division rounds towards zero": - // - // This is the same as `floor()`, because these numbers are all positive. - - let height_after_first_halving = height - network.height_for_first_halving(); - - let address_period = (height_after_first_halving + POST_BLOSSOM_HALVING_INTERVAL) - / FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL; - - address_period - .try_into() - .expect("all values are positive and smaller than the input height") -} - /// Returns the position in the address slice for each funding stream /// as described in [protocol specification §7.10][7.10] /// @@ -90,8 +66,9 @@ fn funding_stream_address_index(height: Height, network: &Network) -> usize { let num_addresses = funding_streams .recipients() - .first() - .map(|recipient| recipient.addresses().len()) + .iter() + .next() + .map(|(_, recipient)| recipient.addresses().len()) .unwrap_or_default(); assert!(index > 0 && index <= num_addresses); @@ -108,35 +85,24 @@ pub fn funding_stream_address( height: Height, network: &Network, receiver: FundingStreamReceiver, -) -> transparent::Address { +) -> &transparent::Address { let index = funding_stream_address_index(height, network); let funding_streams = network.funding_streams(height); - let address = &funding_streams - .recipient_by_receiver(receiver) + funding_streams + .recipient(receiver) // TODO: Change return type to option and return None here instead of panicking .unwrap() .addresses() .get(index) // TODO: Change return type to option and return None here instead of panicking - .unwrap(); - transparent::Address::from_str(address).expect("address should deserialize") + .unwrap() } /// Return a human-readable name and a specification URL for the funding stream `receiver`. pub fn funding_stream_recipient_info( - network: &Network, - height: Height, receiver: FundingStreamReceiver, -) -> (String, &'static str) { - let name = network - .funding_streams(height) - .recipient_by_receiver(receiver) - // TODO: Replace with optional return type - .unwrap() - .name() - .to_string(); - - (name, FUNDING_STREAM_SPECIFICATION) +) -> (&'static str, &'static str) { + (receiver.name(), FUNDING_STREAM_SPECIFICATION) } /// Given a funding stream P2SH address, create a script and check if it is the same diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 62362c12628..8315a132d00 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -64,12 +64,8 @@ fn test_funding_stream_values() -> Result<(), Report> { fn test_funding_stream_addresses() -> Result<(), Report> { let _init_guard = zebra_test::init(); for network in Network::iter() { - for funding_stream_recipient in network.pre_nu6_funding_streams().recipients() { - let receiver = funding_stream_recipient.receiver(); - for address in funding_stream_recipient.addresses() { - let address = - transparent::Address::from_str(address).expect("address should deserialize"); - + for (receiver, recipient) in network.pre_nu6_funding_streams().recipients() { + for address in recipient.addresses() { assert_eq!( address.network_kind(), network.kind(), @@ -77,7 +73,7 @@ fn test_funding_stream_addresses() -> Result<(), Report> { ); // Asserts if address is not a P2SH address. - let _script = new_coinbase_script(&address); + let _script = new_coinbase_script(address); } } } diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 4f965267c65..00f4f8b4460 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -16,7 +16,7 @@ use tracing::Span; use zebra_chain::{ parameters::{ - testnet::{self, ConfiguredActivationHeights}, + testnet::{self, ConfiguredActivationHeights, ConfiguredFundingStreams}, Magic, Network, NetworkKind, }, work::difficulty::U256, @@ -641,6 +641,7 @@ impl<'de> Deserialize<'de> for Config { D: Deserializer<'de>, { #[derive(Deserialize)] + #[serde(deny_unknown_fields)] struct DTestnetParameters { network_name: Option, network_magic: Option<[u8; 4]>, @@ -649,6 +650,8 @@ impl<'de> Deserialize<'de> for Config { disable_pow: Option, genesis_hash: Option, activation_heights: Option, + pre_nu6_funding_streams: Option, + post_nu6_funding_streams: Option, } #[derive(Deserialize)] @@ -736,6 +739,8 @@ impl<'de> Deserialize<'de> for Config { disable_pow, genesis_hash, activation_heights, + pre_nu6_funding_streams, + post_nu6_funding_streams, }), ) => { let mut params_builder = testnet::Parameters::build(); @@ -758,6 +763,14 @@ impl<'de> Deserialize<'de> for Config { ); } + if let Some(funding_streams) = pre_nu6_funding_streams { + params_builder = params_builder.with_pre_nu6_funding_streams(funding_streams); + } + + if let Some(funding_streams) = post_nu6_funding_streams { + params_builder = params_builder.with_post_nu6_funding_streams(funding_streams); + } + if let Some(target_difficulty_limit) = target_difficulty_limit.clone() { params_builder = params_builder.with_target_difficulty_limit( target_difficulty_limit diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 12282a01dcc..7c75a02779c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -1199,10 +1199,7 @@ where .iter() .map(|(receiver, value)| { let address = funding_stream_address(height, &network, *receiver); - ( - *receiver, - FundingStream::new(&network, height, *receiver, *value, address), - ) + (*receiver, FundingStream::new(*receiver, *value, address)) }) .collect(); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index a5d3dc13093..899566ee9a8 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -373,7 +373,7 @@ pub fn standard_coinbase_outputs( // Optional TODO: move this into a zebra_consensus function? let funding_streams: HashMap< FundingStreamReceiver, - (Amount, transparent::Address), + (Amount, &transparent::Address), > = funding_streams .into_iter() .map(|(receiver, amount)| { @@ -398,17 +398,17 @@ pub fn standard_coinbase_outputs( /// If `like_zcashd` is true, try to match the coinbase transactions generated by `zcashd` /// in the `getblocktemplate` RPC. fn combine_coinbase_outputs( - funding_streams: HashMap, transparent::Address)>, + funding_streams: HashMap, &transparent::Address)>, miner_address: &transparent::Address, miner_reward: Amount, like_zcashd: bool, ) -> Vec<(Amount, transparent::Script)> { // Combine all the funding streams with the miner reward. - let mut coinbase_outputs: Vec<(Amount, transparent::Address)> = funding_streams + let mut coinbase_outputs: Vec<(Amount, &transparent::Address)> = funding_streams .into_iter() .map(|(_receiver, (amount, address))| (amount, address)) .collect(); - coinbase_outputs.push((miner_reward, miner_address.clone())); + coinbase_outputs.push((miner_reward, miner_address)); let mut coinbase_outputs: Vec<(Amount, transparent::Script)> = coinbase_outputs .iter() diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs index 5dd8221f3eb..f5ac478bf6b 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -2,8 +2,7 @@ use zebra_chain::{ amount::{Amount, NonNegative}, - block::Height, - parameters::{subsidy::FundingStreamReceiver, Network}, + parameters::subsidy::FundingStreamReceiver, transparent, }; use zebra_consensus::funding_stream_recipient_info; @@ -66,20 +65,18 @@ pub struct FundingStream { impl FundingStream { /// Convert a `receiver`, `value`, and `address` into a `FundingStream` response. pub fn new( - network: &Network, - height: Height, receiver: FundingStreamReceiver, value: Amount, - address: transparent::Address, + address: &transparent::Address, ) -> FundingStream { - let (recipient, specification) = funding_stream_recipient_info(network, height, receiver); + let (recipient, specification) = funding_stream_recipient_info(receiver); FundingStream { recipient: recipient.to_string(), specification: specification.to_string(), value: value.into(), value_zat: value, - address, + address: address.clone(), } } } diff --git a/zebrad/tests/common/configs/v1.9.0.toml b/zebrad/tests/common/configs/v1.9.0.toml index b99a1e38182..31062b62b80 100644 --- a/zebrad/tests/common/configs/v1.9.0.toml +++ b/zebrad/tests/common/configs/v1.9.0.toml @@ -74,6 +74,126 @@ Canopy = 1_028_500 NU5 = 1_842_420 NU6 = 2_000_000 +[network.testnet_parameters.pre_nu6_funding_streams.height_range] +start = 0 +end = 100 + +[[network.testnet_parameters.post_nu6_funding_streams.recipients]] +receiver = "ECC" +numerator = 7 +addresses = [ + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", +] + +[[network.testnet_parameters.post_nu6_funding_streams.recipients]] +receiver = "ZcashFoundation" +numerator = 5 +addresses = [ + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", + "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", +] + + [rpc] debug_force_finished_sync = false parallel_cpu_threads = 0 From cbf7ef3f406eb00516da932a66e6650129af3775 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 21:10:04 -0400 Subject: [PATCH 05/35] Empties recipients list --- zebra-chain/src/parameters/network/subsidy.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index af04057c425..72c247a81fe 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -186,12 +186,7 @@ lazy_static! { /// The post-NU6 funding streams for Mainnet pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { height_range: Height(2_726_400)..Height(3_146_400), - recipients: [( - FundingStreamReceiver::MajorGrants, - FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET) - )] - .into_iter() - .collect(), + recipients: HashMap::new() }; /// The pre-NU6 funding streams for Testnet @@ -218,12 +213,7 @@ lazy_static! { /// The post-NU6 funding streams for Testnet pub static ref POST_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { height_range: Height(2_942_000)..Height(3_362_000), - recipients: [( - FundingStreamReceiver::MajorGrants, - FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), - )] - .into_iter() - .collect(), + recipients: HashMap::new() }; } From 54af589782476e9130a1a7b4bf037f86b2eef493 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 21:16:11 -0400 Subject: [PATCH 06/35] 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 --- zebra-consensus/src/block/subsidy/funding_streams.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 7f95a0133c1..fca24b9516f 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -64,6 +64,9 @@ fn funding_stream_address_index(height: Height, network: &Network) -> usize { )) .expect("no overflow should happen in this sub") as usize; + // Funding stream recipients may not have the same number of addresses on configured Testnets, + // the number of addresses for each recipient should be validated for a configured height range + // when configured Testnet parameters are built. let num_addresses = funding_streams .recipients() .iter() From db434c33c8bd8a1d5430c5f8fff9c5a5e5b9926b Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 22:20:56 -0400 Subject: [PATCH 07/35] Documentation fixes, minor cleanup, renames a test, adds TODOs, and fixes test logic --- zebra-chain/src/parameters/network/subsidy.rs | 14 ++++++++++- zebra-chain/src/parameters/network/testnet.rs | 19 +++++++++++--- .../src/parameters/network/tests/vectors.rs | 25 ++++++++++++++++--- .../src/block/subsidy/funding_streams.rs | 8 +++--- .../block/subsidy/funding_streams/tests.rs | 14 ++++++++--- zebrad/tests/common/configs/v1.9.0.toml | 1 - 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index 72c247a81fe..31e76e1ed1a 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -1,4 +1,16 @@ //! Constants and calculations for Block Subsidy and Funding Streams +//! +//! This module contains the consensus parameters which are required for +//! verification. +//! +//! Some consensus parameters change based on network upgrades. Each network +//! upgrade happens at a particular block height. Some parameters have a value +//! (or function) before the upgrade height, at the upgrade height, and after +//! the upgrade height. (For example, the value of the reserved field in the +//! block header during the Heartwood upgrade.) +//! +//! Typically, consensus parameters are accessed via a function that takes a +//! `Network` and `block::Height`. use std::collections::HashMap; @@ -403,7 +415,7 @@ pub fn funding_stream_address_period(height: Height, network: impl ParameterSubs // // In Rust, "integer division rounds towards zero": // - // This is the same as `floor()`, because these numbers are all positive. + // This is the same as `floor()`, because these numbers are all positive. let height_after_first_halving = height - network.height_for_first_halving(); diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 902a822d26a..79c1b6b2341 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -7,7 +7,7 @@ use crate::{ constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT}, network_upgrade::TESTNET_ACTIVATION_HEIGHTS, subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR}, - Network, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER, + Network, NetworkKind, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER, }, work::difficulty::{ExpandedDifficulty, U256}, }; @@ -50,6 +50,7 @@ const REGTEST_GENESIS_HASH: &str = const TESTNET_GENESIS_HASH: &str = "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38"; +/// Used to validate number of funding stream recipient addresses on configured Testnets. struct TestnetParameterSubsidyImpl; impl ParameterSubsidy for TestnetParameterSubsidyImpl { @@ -58,7 +59,7 @@ impl ParameterSubsidy for TestnetParameterSubsidyImpl { } } -/// Configurable funding streams for Regtest and configured Testnets. +/// Configurable funding stream recipient for configured Testnets. #[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct ConfiguredFundingStreamRecipient { @@ -80,7 +81,7 @@ impl ConfiguredFundingStreamRecipient { } } -/// Configurable funding streams for Regtest and configured Testnets. +/// Configurable funding streams for configured Testnets. #[derive(Deserialize, Clone, Default, Debug)] #[serde(deny_unknown_fields)] pub struct ConfiguredFundingStreams { @@ -91,6 +92,8 @@ pub struct ConfiguredFundingStreams { } impl ConfiguredFundingStreams { + /// Converts a [`ConfiguredFundingStreams`] to a [`FundingStreams`], using the provided default values + /// if `height_range` or `recipients` are None. fn convert_with_default(self, default_funding_streams: FundingStreams) -> FundingStreams { let height_range = self .height_range @@ -137,6 +140,14 @@ impl ConfiguredFundingStreams { "recipients must have a sufficient number of addresses for height range, \ minimum num addresses required: {expected_min_num_addresses}" ); + + for address in recipient.addresses() { + assert_eq!( + address.network_kind(), + NetworkKind::Testnet, + "configured funding stream addresses must be for Testnet" + ); + } } // check that sum of receiver numerators is valid. @@ -662,7 +673,7 @@ impl Network { } } - /// Returns funding streams for this network at the provided height + /// Returns post-Canopy funding streams for this network at the provided height pub fn funding_streams(&self, height: Height) -> &FundingStreams { if NetworkUpgrade::current(self, height) < NetworkUpgrade::Nu6 { self.pre_nu6_funding_streams() diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index 95c28572225..c6020590ab4 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -7,8 +7,9 @@ use crate::{ block::Height, parameters::{ subsidy::{ - FundingStreamReceiver, FUNDING_STREAM_ECC_ADDRESSES_TESTNET, - POST_NU6_FUNDING_STREAMS_TESTNET, PRE_NU6_FUNDING_STREAMS_TESTNET, + FundingStreamReceiver, FUNDING_STREAM_ECC_ADDRESSES_MAINNET, + FUNDING_STREAM_ECC_ADDRESSES_TESTNET, POST_NU6_FUNDING_STREAMS_TESTNET, + PRE_NU6_FUNDING_STREAMS_TESTNET, }, testnet::{ self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, @@ -301,8 +302,10 @@ fn check_full_activation_list() { } } +/// Tests that a set of constraints are enforced when building Testnet parameters, +/// and that funding stream configurations that should be valid can be built. #[test] -fn check_funding_streams() { +fn check_configured_funding_stream_constraints() { let configured_funding_streams = [ Default::default(), ConfiguredFundingStreams { @@ -415,6 +418,20 @@ fn check_funding_streams() { }); }); + // should panic when recipient addresses are for Mainnet. + let expected_panic_wrong_addr_network = std::panic::catch_unwind(|| { + testnet::Parameters::build().with_pre_nu6_funding_streams(ConfiguredFundingStreams { + recipients: Some(vec![ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Ecc, + numerator: 10, + addresses: FUNDING_STREAM_ECC_ADDRESSES_MAINNET + .map(Into::into) + .to_vec(), + }]), + ..Default::default() + }); + }); + // drop panic hook before expecting errors. let _ = std::panic::take_hook(); @@ -422,4 +439,6 @@ fn check_funding_streams() { expected_panic_numerator.expect_err( "should panic when sum of numerators is greater than funding stream denominator", ); + expected_panic_wrong_addr_network + .expect_err("should panic when recipient addresses are for Mainnet"); } diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index fca24b9516f..886ca77ff24 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -69,10 +69,12 @@ fn funding_stream_address_index(height: Height, network: &Network) -> usize { // when configured Testnet parameters are built. let num_addresses = funding_streams .recipients() - .iter() + .values() .next() - .map(|(_, recipient)| recipient.addresses().len()) - .unwrap_or_default(); + // TODO: Return an Option from this function and replace `.unwrap()` with `?` + .unwrap() + .addresses() + .len(); assert!(index > 0 && index <= num_addresses); // spec formula will output an index starting at 1 but diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 8315a132d00..da2dc225179 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -1,7 +1,7 @@ //! Tests for funding streams. use color_eyre::Report; -use zebra_chain::parameters::subsidy::FundingStreamReceiver; +use zebra_chain::parameters::{subsidy::FundingStreamReceiver, NetworkKind}; use super::*; @@ -45,8 +45,8 @@ fn test_funding_stream_values() -> Result<(), Report> { ); // funding stream period is ending - let funding_streams = network.pre_nu6_funding_streams(); - let range = funding_streams.height_range(); + // TODO: Check post-NU6 funding streams here as well. + let range = network.pre_nu6_funding_streams().height_range(); let end = range.end; let last = end - 1; @@ -66,9 +66,15 @@ fn test_funding_stream_addresses() -> Result<(), Report> { for network in Network::iter() { for (receiver, recipient) in network.pre_nu6_funding_streams().recipients() { for address in recipient.addresses() { + let expected_network_kind = match network.kind() { + NetworkKind::Mainnet => NetworkKind::Mainnet, + // `Regtest` uses `Testnet` transparent addresses. + NetworkKind::Testnet | NetworkKind::Regtest => NetworkKind::Testnet, + }; + assert_eq!( address.network_kind(), - network.kind(), + expected_network_kind, "incorrect network for {receiver:?} funding stream address constant: {address}", ); diff --git a/zebrad/tests/common/configs/v1.9.0.toml b/zebrad/tests/common/configs/v1.9.0.toml index 31062b62b80..11bcf62107a 100644 --- a/zebrad/tests/common/configs/v1.9.0.toml +++ b/zebrad/tests/common/configs/v1.9.0.toml @@ -193,7 +193,6 @@ addresses = [ "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz", ] - [rpc] debug_force_finished_sync = false parallel_cpu_threads = 0 From 7d7f606bfa68a887d05990524c06cad4fdf12294 Mon Sep 17 00:00:00 2001 From: ar Date: Wed, 24 Jul 2024 18:55:08 -0400 Subject: [PATCH 08/35] Removes unnecessary `ParameterSubsidy` impl for &Network, adds docs and TODOs --- zebra-chain/src/parameters/network/subsidy.rs | 19 +++++++++---------- zebra-chain/src/parameters/network/testnet.rs | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index 31e76e1ed1a..7528058d5bd 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -174,7 +174,8 @@ impl FundingStreamRecipient { } lazy_static! { - /// The pre-NU6 funding streams for Mainnet + /// The pre-NU6 funding streams for Mainnet as described in [protocol specification §7.10.1][7.10.1] + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams pub static ref PRE_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams { height_range: Height(1_046_400)..Height(2_726_400), recipients: [ @@ -196,12 +197,14 @@ 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() }; - /// The pre-NU6 funding streams for Testnet + /// The pre-NU6 funding streams for Testnet as described in [protocol specification §7.10.1][7.10.1] + /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams pub static ref PRE_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams { height_range: Height(1_028_500)..Height(2_796_000), recipients: [ @@ -223,6 +226,8 @@ lazy_static! { }; /// The post-NU6 funding streams for Testnet + // 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() @@ -304,7 +309,7 @@ pub trait ParameterSubsidy { } /// Network methods related to Block Subsidy and Funding Streams -impl ParameterSubsidy for &Network { +impl ParameterSubsidy for Network { fn height_for_first_halving(&self) -> Height { // First halving on Mainnet is at Canopy // while in Testnet is at block constant height of `1_116_000` @@ -319,12 +324,6 @@ impl ParameterSubsidy for &Network { } } -impl ParameterSubsidy for Network { - fn height_for_first_halving(&self) -> Height { - (&self).height_for_first_halving() - } -} - /// List of addresses for the Zcash Foundation funding stream in the Mainnet. pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET]; @@ -407,7 +406,7 @@ pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRES /// as described in [protocol specification §7.10][7.10] /// /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams -pub fn funding_stream_address_period(height: Height, network: impl ParameterSubsidy) -> u32 { +pub fn funding_stream_address_period(height: Height, network: &N) -> u32 { // Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`, // // diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 79c1b6b2341..97ffb20a9d4 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -124,12 +124,12 @@ impl ConfiguredFundingStreams { .end .previous() .expect("end height must be above start height and genesis height"), - TestnetParameterSubsidyImpl, + &TestnetParameterSubsidyImpl, )) .expect("no overflow should happen in this sum") .checked_sub(funding_stream_address_period( height_range.start, - TestnetParameterSubsidyImpl, + &TestnetParameterSubsidyImpl, )) .expect("no overflow should happen in this sub") as usize; From 77a6f2c14f898945d30ad01f8418a67895554b8b Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 22:56:17 -0400 Subject: [PATCH 09/35] 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 --- zebra-chain/src/parameters/network/subsidy.rs | 67 ++++++++++++++----- zebra-chain/src/parameters/network/testnet.rs | 2 +- .../src/parameters/network/tests/vectors.rs | 34 ++++++---- zebra-chain/src/parameters/network_upgrade.rs | 5 +- zebra-consensus/src/block/check.rs | 34 ++++++++-- .../src/block/subsidy/funding_streams.rs | 21 ++---- .../get_block_template.rs | 8 +-- .../get_block_template_rpcs/types/subsidy.rs | 7 +- zebrad/Cargo.toml | 2 +- 9 files changed, 117 insertions(+), 63 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index 7528058d5bd..ca375fc02b5 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -62,6 +62,9 @@ pub enum FundingStreamReceiver { /// The Major Grants (Zcash Community Grants) funding stream. MajorGrants, + + /// The deferred pool contribution. + Deferred, } impl FundingStreamReceiver { @@ -74,6 +77,8 @@ impl FundingStreamReceiver { 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 => "Deferred Fund", } } } @@ -144,21 +149,25 @@ pub struct FundingStreamRecipient { impl FundingStreamRecipient { /// Creates a new [`FundingStreamRecipient`]. - pub fn new(numerator: u64, addresses: I) -> Self + pub fn new(numerator: u64, addresses: Option) -> Self where T: ToString, I: IntoIterator, { Self { numerator, - addresses: addresses - .into_iter() - .map(|addr| { - let addr = addr.to_string(); - addr.parse() - .expect("funding stream address must deserialize") - }) - .collect(), + addresses: if let Some(addresses) = addresses { + addresses + .into_iter() + .map(|addr| { + let addr = addr.to_string(); + addr.parse() + .expect("funding stream address must deserialize") + }) + .collect() + } else { + vec![] + }, } } @@ -181,15 +190,15 @@ lazy_static! { recipients: [ ( FundingStreamReceiver::Ecc, - FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter()), + FundingStreamRecipient::new(7, Some(FUNDING_STREAM_ECC_ADDRESSES_MAINNET)), ), ( FundingStreamReceiver::ZcashFoundation, - FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_MAINNET), + FundingStreamRecipient::new(5, Some(FUNDING_STREAM_ZF_ADDRESSES_MAINNET)), ), ( FundingStreamReceiver::MajorGrants, - FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET), + FundingStreamRecipient::new(8, Some(FUNDING_STREAM_MG_ADDRESSES_MAINNET)), ), ] .into_iter() @@ -200,7 +209,19 @@ lazy_static! { // 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() + recipients: [ + ( + FundingStreamReceiver::Deferred, + FundingStreamRecipient::new::<[&str; 0], &str>(12, None), + ), + ( + FundingStreamReceiver::MajorGrants, + // TODO: Update these addresses + FundingStreamRecipient::new(8, Some(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] @@ -210,15 +231,15 @@ lazy_static! { recipients: [ ( FundingStreamReceiver::Ecc, - FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_TESTNET), + FundingStreamRecipient::new(7, Some(FUNDING_STREAM_ECC_ADDRESSES_TESTNET)), ), ( FundingStreamReceiver::ZcashFoundation, - FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_TESTNET), + FundingStreamRecipient::new(5, Some(FUNDING_STREAM_ZF_ADDRESSES_TESTNET)), ), ( FundingStreamReceiver::MajorGrants, - FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), + FundingStreamRecipient::new(8, Some(FUNDING_STREAM_MG_ADDRESSES_TESTNET)), ), ] .into_iter() @@ -230,7 +251,19 @@ lazy_static! { // 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() + recipients: [ + ( + FundingStreamReceiver::Deferred, + FundingStreamRecipient::new::<[&str; 0], &str>(12, None), + ), + ( + FundingStreamReceiver::MajorGrants, + // TODO: Update these addresses + FundingStreamRecipient::new(8, Some(FUNDING_STREAM_MG_ADDRESSES_TESTNET)), + ), + ] + .into_iter() + .collect() }; } diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 97ffb20a9d4..2ba879ff82f 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -68,7 +68,7 @@ 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, + pub addresses: Option>, } impl ConfiguredFundingStreamRecipient { diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index c6020590ab4..c839a26c116 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -320,9 +320,11 @@ 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() }, @@ -330,9 +332,11 @@ fn check_configured_funding_stream_constraints() { 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() }, @@ -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() }); @@ -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() }); @@ -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() }); diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 2000c968f00..815750bae21 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -214,8 +214,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. @@ -530,6 +529,8 @@ impl From 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, } } } diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 0463a9c41a9..6329fdfaa06 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -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 _}, @@ -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 matches!(halving_div, 1 | 2 | 4) { // 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 @@ -194,8 +194,15 @@ 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()` + 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) @@ -236,7 +243,10 @@ 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"); - + 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, @@ -246,9 +256,19 @@ pub fn miner_fees_are_valid( // https://zips.z.cash/protocol/protocol.pdf#txnconsensus 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)?; + + let should_allow_unclaimed_subsidy = + NetworkUpgrade::current(network, height) <= NetworkUpgrade::Nu5; + + let is_invalid_miner_fee = if should_allow_unclaimed_subsidy { + left > right + } else { + left != right + }; - if left > right { + if is_invalid_miner_fee { Err(SubsidyError::InvalidMinerFees)?; } diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 886ca77ff24..955278af3b6 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -52,7 +52,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 funding_stream_address_index(height: Height, network: &Network) -> usize { +fn funding_stream_address_index(height: Height, network: &Network) -> Option { let funding_streams = network.funding_streams(height); let index = 1u32 @@ -70,16 +70,14 @@ fn funding_stream_address_index(height: Height, network: &Network) -> usize { let num_addresses = funding_streams .recipients() .values() - .next() - // TODO: Return an Option from this function and replace `.unwrap()` with `?` - .unwrap() + .next()? .addresses() .len(); 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 + Some(index - 1) } /// Return the address corresponding to given height, network and funding stream receiver. @@ -90,17 +88,10 @@ pub fn funding_stream_address( height: Height, network: &Network, receiver: FundingStreamReceiver, -) -> &transparent::Address { - let index = funding_stream_address_index(height, network); +) -> Option<&transparent::Address> { + let index = funding_stream_address_index(height, network)?; let funding_streams = network.funding_streams(height); - funding_streams - .recipient(receiver) - // TODO: Change return type to option and return None here instead of panicking - .unwrap() - .addresses() - .get(index) - // TODO: Change return type to option and return None here instead of panicking - .unwrap() + funding_streams.recipient(receiver)?.addresses().get(index) } /// Return a human-readable name and a specification URL for the funding stream `receiver`. diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index 899566ee9a8..de8c3b50cf4 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -376,11 +376,11 @@ pub fn standard_coinbase_outputs( (Amount, &transparent::Address), > = funding_streams .into_iter() - .map(|(receiver, amount)| { - ( + .filter_map(|(receiver, amount)| { + Some(( receiver, - (amount, funding_stream_address(height, network, receiver)), - ) + (amount, funding_stream_address(height, network, receiver)?), + )) }) .collect(); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs index f5ac478bf6b..df4a3ef6010 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -59,7 +59,8 @@ pub struct FundingStream { /// /// The current Zcash funding streams only use transparent addresses, /// so Zebra doesn't support Sapling addresses in this RPC. - pub address: transparent::Address, + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option, } impl FundingStream { @@ -67,7 +68,7 @@ impl FundingStream { pub fn new( receiver: FundingStreamReceiver, value: Amount, - address: &transparent::Address, + address: Option<&transparent::Address>, ) -> FundingStream { let (recipient, specification) = funding_stream_recipient_info(receiver); @@ -76,7 +77,7 @@ impl FundingStream { specification: specification.to_string(), value: value.into(), value_zat: value, - address: address.clone(), + address: address.cloned(), } } } diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index 66469215ab7..dcab48716f7 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -299,4 +299,4 @@ zebra-grpc = { path = "../zebra-grpc", version = "0.1.0-alpha.5" } zebra-utils = { path = "../zebra-utils", version = "1.0.0-beta.38" } [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)', 'cfg(zcash_unstable = "nu6")'] } From dbc7ca90cbe34a0795013e1a2d4fe164c29add87 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 23:03:38 -0400 Subject: [PATCH 10/35] adds `lockbox_input_value()` fn --- zebra-consensus/src/block/subsidy/general.rs | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 193f32cb2b3..57371c5d465 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -118,6 +118,36 @@ pub fn output_amounts(transaction: &Transaction) -> HashSet> .collect() } +/// Lockbox funding stream total input value for a block height. +/// +/// Assumes a constant funding stream amount per block. +// TODO: Cache the lockbox value balance in zebra-state (will be required for tracking lockbox +// value balance after ZSF ZIPs or after a ZIP for spending from the deferred pool) +#[allow(dead_code)] +fn lockbox_input_value(network: &Network, height: Height) -> Amount { + let Some(nu6_activation_height) = Nu6.activation_height(network) else { + return Amount::zero(); + }; + + let &deferred_amount_per_block = funding_stream_values(nu6_activation_height, network) + .expect("we always expect a funding stream hashmap response even if empty") + .get(&FundingStreamReceiver::Deferred) + .expect("we expect a lockbox funding stream after NU5"); + + let post_nu6_funding_stream_height_range = network.post_nu6_funding_streams().height_range(); + + // `min(height, last_height_with_deferred_pool_contribution) - (nu6_activation_height - 1)`, + // We decrement NU6 activation height since it's an inclusive lower bound. + // Funding stream height range end bound is not incremented since it's an exclusive end bound + let num_blocks_with_lockbox_output = (height.0 + 1) + .min(post_nu6_funding_stream_height_range.end.0) + .checked_sub(post_nu6_funding_stream_height_range.start.0) + .unwrap_or_default(); + + (deferred_amount_per_block * num_blocks_with_lockbox_output.into()) + .expect("lockbox input value should fit in Amount") +} + #[cfg(test)] mod test { use super::*; From 0f962c5e7885655e10911e286f77a5d56081e5e3 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 23:09:41 -0400 Subject: [PATCH 11/35] Adds TODOs for linking to relevant ZIPs and updating height ranges --- zebra-chain/src/parameters/network/subsidy.rs | 3 +++ zebra-consensus/src/block/check.rs | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index ca375fc02b5..5f0f2afaed0 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -64,6 +64,7 @@ pub enum FundingStreamReceiver { MajorGrants, /// The deferred pool contribution. + // TODO: Add link to lockbox stream ZIP Deferred, } @@ -208,6 +209,7 @@ 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 { + // TODO: Adjust this height range and recipient list once a proposal is selected height_range: Height(2_726_400)..Height(3_146_400), recipients: [ ( @@ -250,6 +252,7 @@ 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 { + // TODO: Adjust this height range and recipient list once a proposal is selected height_range: Height(2_942_000)..Height(3_362_000), recipients: [ ( diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 6329fdfaa06..1f177847bd9 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -196,6 +196,7 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr for (receiver, expected_amount) in funding_streams { if receiver == FundingStreamReceiver::Deferred { // The deferred pool contribution is checked in `miner_fees_are_valid()` + // TODO: Add link to lockbox stream ZIP continue; } @@ -243,10 +244,13 @@ 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, @@ -259,9 +263,9 @@ pub fn miner_fees_are_valid( let right = (block_subsidy + block_miner_fees - expected_deferred_amount) .map_err(|_| SubsidyError::SumOverflow)?; + // TODO: Add link to exact coinbase balance ZIP let should_allow_unclaimed_subsidy = NetworkUpgrade::current(network, height) <= NetworkUpgrade::Nu5; - let is_invalid_miner_fee = if should_allow_unclaimed_subsidy { left > right } else { From 9bca31f1a30fa9f305e28f5dd16c54be4e931a44 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 23:27:57 -0400 Subject: [PATCH 12/35] Adds `nu6_lockbox_funding_stream` acceptance test --- zebra-chain/src/parameters/network/testnet.rs | 8 +- .../src/methods/get_block_template_rpcs.rs | 7 +- .../get_block_template.rs | 7 + .../types/get_block_template/proposal.rs | 5 + zebrad/tests/acceptance.rs | 126 +++++++++++++++++- 5 files changed, 144 insertions(+), 9 deletions(-) diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 2ba879ff82f..8680b91550e 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -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, \ diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 7c75a02779c..b9eccb90ad9 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -636,12 +636,7 @@ where // // Optional TODO: // - add `async changed()` method to ChainSyncStatus (like `ChainTip`) - // TODO: - // - Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361) - // - Check the field in `sync_status` so it applies to the mempool as well. - if !network.disable_pow() { - check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?; - } + check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?; // TODO: return an error if we have no peers, like `zcashd` does, // and add a developer config that mines regardless of how many peers we have. // https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/miner.cpp#L865-L880 diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index de8c3b50cf4..f904f4b02c5 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -164,6 +164,13 @@ where Tip: ChainTip + Clone + Send + Sync + 'static, SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static, { + // TODO: + // - Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361) + // - Check the field in `sync_status` so it applies to the mempool as well. + if network.disable_pow() { + return Ok(()); + } + // The tip estimate may not be the same as the one coming from the state // but this is ok for an estimate let (estimated_distance_to_chain_tip, local_tip_height) = latest_chain_tip diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index ccb33a35f26..fc0805b533d 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -59,6 +59,11 @@ impl ProposalResponse { ProposalResponse::Rejected(final_error.to_string()) } + + /// Returns true if self is [`ProposalResponse::Valid`] + pub fn is_valid(&self) -> bool { + matches!(self, Self::Valid) + } } impl From for Response { diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 4cc2267049d..2de7138c345 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -156,7 +156,10 @@ use serde_json::Value; use tower::ServiceExt; use zebra_chain::{ block::{self, genesis::regtest_genesis_block, Height}, - parameters::Network::{self, *}, + parameters::{ + testnet::ConfiguredFundingStreams, + Network::{self, *}, + }, }; use zebra_consensus::ParameterCheckpoint; use zebra_network::constants::PORT_IN_USE_ERROR; @@ -3228,3 +3231,124 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { Ok(()) } + +/// Test successful block template submission as a block proposal on a custom Testnet. +/// +/// This test can be run locally with: +/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_lockbox_funding_stream --exact --show-output` +#[tokio::test(flavor = "multi_thread")] +#[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] +async fn nu6_lockbox_funding_stream() -> Result<()> { + use zebra_chain::{ + chain_sync_status::MockSyncStatus, + parameters::{ + testnet::{self, ConfiguredActivationHeights}, + NetworkUpgrade, + }, + serialization::ZcashSerialize, + work::difficulty::U256, + }; + use zebra_network::address_book_peers::MockAddressBookPeers; + use zebra_node_services::mempool; + use zebra_rpc::methods::{ + get_block_template_rpcs::{ + get_block_template::{proposal_block_from_template, GetBlockTemplateRequestMode}, + types::get_block_template, + }, + hex_data::HexData, + GetBlockTemplateRpc, GetBlockTemplateRpcImpl, + }; + use zebra_test::mock_service::MockService; + + let network = testnet::Parameters::build() + // Regtest genesis hash + .with_genesis_hash("029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327") + .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32])) + .with_disable_pow(true) + .with_slow_start_interval(Height::MIN) + .with_activation_heights(ConfiguredActivationHeights { + nu6: Some(1), + ..Default::default() + }) + .with_post_nu6_funding_streams(ConfiguredFundingStreams { + // Start checking funding streams from block height 1 + height_range: Some(Height(1)..Height(100)), + // Use default post-NU6 recipients + recipients: None, + }) + .to_network(); + + let default_test_config = default_test_config(&network)?; + let (state, read_state, latest_chain_tip, _chain_tip_change) = + zebra_state::init_test_services(&network); + + let ( + block_verifier_router, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::router::init(zebra_consensus::Config::default(), &network, state.clone()) + .await; + + let genesis_hash = block_verifier_router + .clone() + .oneshot(zebra_consensus::Request::Commit(regtest_genesis_block())) + .await + .expect("should validate Regtest genesis block"); + + let mut mempool = MockService::build() + .with_max_request_delay(Duration::from_secs(5)) + .for_unit_tests(); + let mut mock_sync_status = MockSyncStatus::default(); + mock_sync_status.set_is_close_to_tip(true); + + let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( + &network, + default_test_config.mining, + mempool.clone(), + read_state, + latest_chain_tip, + block_verifier_router, + mock_sync_status, + MockAddressBookPeers::default(), + ); + + let mock_mempool_request_handler = async move { + mempool + .expect_request(mempool::Request::FullTransactions) + .await + .respond(mempool::Response::FullTransactions { + transactions: vec![], + // tip hash needs to match chain info for long poll requests + last_seen_tip_hash: genesis_hash, + }); + }; + let block_template_fut = get_block_template_rpc_impl.get_block_template(None); + + let (block_template, _) = tokio::join!(block_template_fut, mock_mempool_request_handler); + let get_block_template::Response::TemplateMode(block_template) = + block_template.expect("unexpected error in getblocktemplate RPC call") + else { + panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response") + }; + + let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?; + let hex_proposal_block = HexData(proposal_block.zcash_serialize_to_vec()?); + + let get_block_template::Response::ProposalMode(submit_result) = get_block_template_rpc_impl + .get_block_template(Some(get_block_template::JsonParameters { + mode: GetBlockTemplateRequestMode::Proposal, + data: Some(hex_proposal_block), + ..Default::default() + })) + .await? + else { + panic!( + "this getblocktemplate call should return the `ProposalMode` variant of the response" + ) + }; + + assert!(submit_result.is_valid(), "block proposal should succeed"); + + Ok(()) +} From 0d22b680b0143bac9665aed0d62b0ed404183b44 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 23 Jul 2024 23:53:15 -0400 Subject: [PATCH 13/35] updates funding stream values test to check post-NU6 funding streams too, adds Mainnet/Testnet NU6 activation heights, fixes lints/compilation issue --- zebra-chain/Cargo.toml | 3 +++ zebra-chain/src/parameters/network_upgrade.rs | 4 ++-- .../block/subsidy/funding_streams/tests.rs | 21 ++++++++++++++++++- zebrad/Cargo.toml | 2 +- zebrad/tests/acceptance.rs | 7 ++----- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index b8f752b32ad..b234123adfd 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -176,3 +176,6 @@ required-features = ["bench"] [[bench]] name = "redpallas" harness = false + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nu6"))'] } diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 815750bae21..708d59f9198 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -88,7 +88,7 @@ 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. + (block::Height(2_726_400), Nu6), ]; /// Fake mainnet network upgrade activation heights, used in tests. @@ -124,7 +124,7 @@ 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. + (block::Height(2_942_000), Nu6), ]; /// Fake testnet network upgrade activation heights, used in tests. diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index da2dc225179..3eca809a7a8 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -45,7 +45,6 @@ fn test_funding_stream_values() -> Result<(), Report> { ); // funding stream period is ending - // TODO: Check post-NU6 funding streams here as well. let range = network.pre_nu6_funding_streams().height_range(); let end = range.end; let last = end - 1; @@ -56,6 +55,26 @@ fn test_funding_stream_values() -> Result<(), Report> { ); assert!(funding_stream_values(end, network)?.is_empty()); + let mut hash_map = HashMap::new(); + hash_map.insert( + FundingStreamReceiver::Deferred, + Amount::try_from(18_750_000)?, + ); + hash_map.insert( + FundingStreamReceiver::MajorGrants, + Amount::try_from(12_500_000)?, + ); + + let nu6_height = Nu6.activation_height(network).unwrap(); + + for height in [ + nu6_height, + Height(nu6_height.0 + 1), + Height(nu6_height.0 + 1), + ] { + assert_eq!(funding_stream_values(height, network).unwrap(), hash_map); + } + Ok(()) } diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index dcab48716f7..6edf222b056 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -299,4 +299,4 @@ zebra-grpc = { path = "../zebra-grpc", version = "0.1.0-alpha.5" } zebra-utils = { path = "../zebra-utils", version = "1.0.0-beta.38" } [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)', 'cfg(zcash_unstable = "nu6")'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)', 'cfg(zcash_unstable, values("nu6"))'] } diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 2de7138c345..1bb988fd5ac 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -156,10 +156,7 @@ use serde_json::Value; use tower::ServiceExt; use zebra_chain::{ block::{self, genesis::regtest_genesis_block, Height}, - parameters::{ - testnet::ConfiguredFundingStreams, - Network::{self, *}, - }, + parameters::Network::{self, *}, }; use zebra_consensus::ParameterCheckpoint; use zebra_network::constants::PORT_IN_USE_ERROR; @@ -3242,7 +3239,7 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { use zebra_chain::{ chain_sync_status::MockSyncStatus, parameters::{ - testnet::{self, ConfiguredActivationHeights}, + testnet::{self, ConfiguredActivationHeights, ConfiguredFundingStreams}, NetworkUpgrade, }, serialization::ZcashSerialize, From 039c9a1470c4ef30cd7915b261590b6b6131aaa0 Mon Sep 17 00:00:00 2001 From: ar Date: Wed, 24 Jul 2024 10:21:02 -0400 Subject: [PATCH 14/35] 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 --- zebra-chain/src/parameters/network_upgrade.rs | 6 ++- .../block/subsidy/funding_streams/tests.rs | 44 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 708d59f9198..121a5bdcf13 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -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), - (block::Height(2_726_400), Nu6), + // TODO: Add NU6 + // (block::Height(2_726_400), Nu6), ]; /// Fake mainnet network upgrade activation heights, used in tests. @@ -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), - (block::Height(2_942_000), Nu6), + // TODO: Add NU6 + // (block::Height(2_942_000), Nu6), ]; /// Fake testnet network upgrade activation heights, used in tests. diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 3eca809a7a8..8c5fa2de7b0 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -1,7 +1,14 @@ //! Tests for funding streams. use color_eyre::Report; -use zebra_chain::parameters::{subsidy::FundingStreamReceiver, NetworkKind}; +use zebra_chain::parameters::{ + subsidy::FundingStreamReceiver, + testnet::{ + self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, + ConfiguredFundingStreams, + }, + NetworkKind, +}; use super::*; @@ -55,6 +62,37 @@ fn test_funding_stream_values() -> Result<(), Report> { ); assert!(funding_stream_values(end, network)?.is_empty()); + // TODO: Replace this with Mainnet once there's an NU6 activation height defined for Mainnet + let network = testnet::Parameters::build() + .with_activation_heights(ConfiguredActivationHeights { + blossom: Some(Blossom.activation_height(network).unwrap().0), + nu6: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().start.0), + ..Default::default() + }) + .with_post_nu6_funding_streams(ConfiguredFundingStreams { + // Start checking funding streams from block height 1 + height_range: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().clone()), + // Use default post-NU6 recipients + recipients: Some( + POST_NU6_FUNDING_STREAMS_TESTNET + .recipients() + .into_iter() + .map(|(&receiver, recipient)| ConfiguredFundingStreamRecipient { + receiver, + numerator: recipient.numerator(), + addresses: Some( + recipient + .addresses() + .into_iter() + .map(|addr| addr.to_string()) + .collect(), + ), + }) + .collect(), + ), + }) + .to_network(); + let mut hash_map = HashMap::new(); hash_map.insert( FundingStreamReceiver::Deferred, @@ -65,14 +103,14 @@ fn test_funding_stream_values() -> Result<(), Report> { Amount::try_from(12_500_000)?, ); - let nu6_height = Nu6.activation_height(network).unwrap(); + let nu6_height = Nu6.activation_height(&network).unwrap(); for height in [ nu6_height, Height(nu6_height.0 + 1), Height(nu6_height.0 + 1), ] { - assert_eq!(funding_stream_values(height, network).unwrap(), hash_map); + assert_eq!(funding_stream_values(height, &network).unwrap(), hash_map); } Ok(()) From 2f145171d7dd02487fa0d32bcef118c7eb1e618e Mon Sep 17 00:00:00 2001 From: ar Date: Wed, 24 Jul 2024 10:22:50 -0400 Subject: [PATCH 15/35] reverts unnecessary refactor --- zebra-consensus/src/block/check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 1f177847bd9..3af0df70c0c 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -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 matches!(halving_div, 1 | 2 | 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 From 33a5545ddabdb032c66667c36aa7096b28d3ed87 Mon Sep 17 00:00:00 2001 From: ar Date: Wed, 24 Jul 2024 10:32:10 -0400 Subject: [PATCH 16/35] appease clippy --- zebra-consensus/src/block/subsidy/funding_streams/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 8c5fa2de7b0..96d881f70f2 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -76,14 +76,14 @@ fn test_funding_stream_values() -> Result<(), Report> { recipients: Some( POST_NU6_FUNDING_STREAMS_TESTNET .recipients() - .into_iter() + .iter() .map(|(&receiver, recipient)| ConfiguredFundingStreamRecipient { receiver, numerator: recipient.numerator(), addresses: Some( recipient .addresses() - .into_iter() + .iter() .map(|addr| addr.to_string()) .collect(), ), From 1183d49128f2e982a56d7e854d68a058ffe6dcf3 Mon Sep 17 00:00:00 2001 From: ar Date: Wed, 24 Jul 2024 18:39:11 -0400 Subject: [PATCH 17/35] Adds a test for `lockbox_input_value()` --- zebra-consensus/src/block/subsidy/general.rs | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 57371c5d465..5c53f43cafb 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -152,6 +152,10 @@ fn lockbox_input_value(network: &Network, height: Height) -> Amount mod test { use super::*; use color_eyre::Report; + use zebra_chain::parameters::testnet::{ + self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, + ConfiguredFundingStreams, + }; #[test] fn halving_test() -> Result<(), Report> { @@ -421,4 +425,71 @@ mod test { Ok(()) } + + #[test] + fn check_lockbox_input_value() -> Result<(), Report> { + let _init_guard = zebra_test::init(); + + let network = testnet::Parameters::build() + .with_activation_heights(ConfiguredActivationHeights { + blossom: Some(Blossom.activation_height(&Network::Mainnet).unwrap().0), + nu6: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().start.0), + ..Default::default() + }) + .with_post_nu6_funding_streams(ConfiguredFundingStreams { + // Start checking funding streams from block height 1 + height_range: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().clone()), + // Use default post-NU6 recipients + recipients: Some( + POST_NU6_FUNDING_STREAMS_TESTNET + .recipients() + .iter() + .map(|(&receiver, recipient)| ConfiguredFundingStreamRecipient { + receiver, + numerator: recipient.numerator(), + addresses: Some( + recipient + .addresses() + .iter() + .map(|addr| addr.to_string()) + .collect(), + ), + }) + .collect(), + ), + }) + .to_network(); + + let nu6_height = Nu6.activation_height(&network).unwrap(); + let post_nu6_funding_streams = network.post_nu6_funding_streams(); + let height_range = post_nu6_funding_streams.height_range(); + + let last_funding_stream_height = post_nu6_funding_streams + .height_range() + .end + .previous() + .expect("the previous height should be valid"); + + assert_eq!( + Amount::::zero(), + lockbox_input_value(&network, Height::MIN) + ); + + let expected_lockbox_value: Amount = Amount::try_from(18_750_000)?; + assert_eq!( + expected_lockbox_value, + lockbox_input_value(&network, nu6_height) + ); + + let num_blocks_total = height_range.end.0 - height_range.start.0; + let expected_input_per_block: Amount = Amount::try_from(18_750_000)?; + let expected_lockbox_value = (expected_input_per_block * num_blocks_total.into())?; + + assert_eq!( + expected_lockbox_value, + lockbox_input_value(&network, last_funding_stream_height) + ); + + Ok(()) + } } From b523bf5760ce7d5f38998615214b4e7c31e615ba Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 25 Jul 2024 15:31:28 -0400 Subject: [PATCH 18/35] Applies suggestions from code review --- zebra-chain/Cargo.toml | 1 + .../src/methods/get_block_template_rpcs/get_block_template.rs | 1 + zebrad/Cargo.toml | 1 + 3 files changed, 3 insertions(+) diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index b234123adfd..309aa725576 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -178,4 +178,5 @@ 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"))'] } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index f904f4b02c5..00fb4d8a9d2 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -154,6 +154,7 @@ where // - State and syncer checks /// Returns an error if Zebra is not synced to the consensus chain tip. +/// Returns early with `Ok(())` if Proof-of-Work is disabled on the provided `network`. /// This error might be incorrect if the local clock is skewed. pub fn check_synced_to_tip( network: &Network, diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index 6edf222b056..154898e186f 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -299,4 +299,5 @@ zebra-grpc = { path = "../zebra-grpc", version = "0.1.0-alpha.5" } zebra-utils = { path = "../zebra-utils", version = "1.0.0-beta.38" } [lints.rust] +# TODO: Remove 'cfg(zcash_unstable, values("nu6"))' once it's no longer needed for NU6. unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)', 'cfg(zcash_unstable, values("nu6"))'] } From 7df2e2863f75c2b85323e8906f9f30ee0bacc73f Mon Sep 17 00:00:00 2001 From: Arya Date: Fri, 26 Jul 2024 13:37:04 -0400 Subject: [PATCH 19/35] Fixes potential panic --- .../src/block/subsidy/funding_streams.rs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 955278af3b6..bdf72809f72 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -52,8 +52,17 @@ 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 funding_stream_address_index(height: Height, network: &Network) -> Option { +fn funding_stream_address_index( + height: Height, + network: &Network, + receiver: FundingStreamReceiver, +) -> Option { + if receiver == FundingStreamReceiver::Deferred { + return None; + } + let funding_streams = network.funding_streams(height); + let num_addresses = funding_streams.recipient(receiver)?.addresses().len(); let index = 1u32 .checked_add(funding_stream_address_period(height, network)) @@ -64,16 +73,6 @@ fn funding_stream_address_index(height: Height, network: &Network) -> Option 0 && index <= num_addresses); // spec formula will output an index starting at 1 but // Zebra indices for addresses start at zero, return converted. @@ -89,7 +88,7 @@ pub fn funding_stream_address( network: &Network, receiver: FundingStreamReceiver, ) -> Option<&transparent::Address> { - let index = funding_stream_address_index(height, network)?; + let index = funding_stream_address_index(height, network, receiver)?; let funding_streams = network.funding_streams(height); funding_streams.recipient(receiver)?.addresses().get(index) } From 7173af73f49efacb26dda2bf8bd864238d8b1612 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Jul 2024 14:07:48 -0400 Subject: [PATCH 20/35] Fixes bad merge --- zebra-consensus/src/block/subsidy/funding_streams.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index f9ca215e705..bdf72809f72 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -73,18 +73,6 @@ fn funding_stream_address_index( )) .expect("no overflow should happen in this sub") as usize; - // Funding stream recipients may not have the same number of addresses on configured Testnets, - // the number of addresses for each recipient should be validated for a configured height range - // when configured Testnet parameters are built. - let num_addresses = funding_streams - .recipients() - .values() - .next() - // TODO: Return an Option from this function and replace `.unwrap()` with `?` - .unwrap() - .addresses() - .len(); - 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. From c6244e66fc4ed953c00f1e28de8933ea4a717060 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Jul 2024 15:26:24 -0400 Subject: [PATCH 21/35] Update zebra-chain/src/parameters/network_upgrade.rs --- zebra-chain/src/parameters/network_upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 121a5bdcf13..b71e5dd363e 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -216,7 +216,7 @@ pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = (Heartwood, ConsensusBranchId(0xf5b9230b)), (Canopy, ConsensusBranchId(0xe9ff75a6)), (Nu5, ConsensusBranchId(0xc2d6d0b4)), - (Nu6, ConsensusBranchId(0xc8e71055)), + (Nu6, ConsensusBranchId(0x7fffffff)), ]; /// The target block spacing before Blossom. From 166bc23f0671767f0bb95ff1357adca11cd2f39f Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Jul 2024 16:58:44 -0400 Subject: [PATCH 22/35] Updates acceptance test to check that invalid blocks are rejected --- zebrad/tests/acceptance.rs | 200 +++++++++++++++++++++++++++++++++---- 1 file changed, 181 insertions(+), 19 deletions(-) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 1bb988fd5ac..21fe5542ea4 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3229,17 +3229,23 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { Ok(()) } -/// Test successful block template submission as a block proposal on a custom Testnet. +/// Test successful block template submission as a block proposal or submission on a custom Testnet. /// /// This test can be run locally with: /// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_lockbox_funding_stream --exact --show-output` +/// Note: The consensus branch id for NU6 in Zebra (defined in the `CONSENSUS_BRANCH_IDS` constant) must match the one in the `zcash_protocol` crate, currently `0xc8e71055`. #[tokio::test(flavor = "multi_thread")] -#[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] -async fn nu6_lockbox_funding_stream() -> Result<()> { +// #[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] +#[cfg(feature = "getblocktemplate-rpcs")] +async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { use zebra_chain::{ chain_sync_status::MockSyncStatus, parameters::{ - testnet::{self, ConfiguredActivationHeights, ConfiguredFundingStreams}, + subsidy::{FundingStreamReceiver, FUNDING_STREAM_MG_ADDRESSES_TESTNET}, + testnet::{ + self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, + ConfiguredFundingStreams, + }, NetworkUpgrade, }, serialization::ZcashSerialize, @@ -3249,15 +3255,22 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { use zebra_node_services::mempool; use zebra_rpc::methods::{ get_block_template_rpcs::{ - get_block_template::{proposal_block_from_template, GetBlockTemplateRequestMode}, + get_block_template::{ + fetch_state_tip_and_local_time, generate_coinbase_and_roots, + proposal_block_from_template, GetBlockTemplate, GetBlockTemplateRequestMode, + }, types::get_block_template, + types::submit_block, }, hex_data::HexData, GetBlockTemplateRpc, GetBlockTemplateRpcImpl, }; use zebra_test::mock_service::MockService; + let _init_guard = zebra_test::init(); + + tracing::info!("running nu6_funding_streams_and_coinbase_balance test"); - let network = testnet::Parameters::build() + let base_network_params = testnet::Parameters::build() // Regtest genesis hash .with_genesis_hash("029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327") .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32])) @@ -3266,7 +3279,10 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { .with_activation_heights(ConfiguredActivationHeights { nu6: Some(1), ..Default::default() - }) + }); + + let network = base_network_params + .clone() .with_post_nu6_funding_streams(ConfiguredFundingStreams { // Start checking funding streams from block height 1 height_range: Some(Height(1)..Height(100)), @@ -3275,7 +3291,15 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { }) .to_network(); + tracing::info!("built configured Testnet, starting state service and block verifier"); + let default_test_config = default_test_config(&network)?; + let mining_config = default_test_config.mining; + let miner_address = mining_config + .miner_address + .clone() + .expect("hard-coded config should have a miner address"); + let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::init_test_services(&network); @@ -3287,6 +3311,8 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { ) = zebra_consensus::router::init(zebra_consensus::Config::default(), &network, state.clone()) .await; + tracing::info!("started state service and block verifier, committing Regtest genesis block"); + let genesis_hash = block_verifier_router .clone() .oneshot(zebra_consensus::Request::Commit(regtest_genesis_block())) @@ -3301,16 +3327,16 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( &network, - default_test_config.mining, + mining_config, mempool.clone(), - read_state, + read_state.clone(), latest_chain_tip, block_verifier_router, mock_sync_status, MockAddressBookPeers::default(), ); - let mock_mempool_request_handler = async move { + let make_mock_mempool_request_handler = || async move { mempool .expect_request(mempool::Request::FullTransactions) .await @@ -3320,8 +3346,9 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { last_seen_tip_hash: genesis_hash, }); }; - let block_template_fut = get_block_template_rpc_impl.get_block_template(None); + let block_template_fut = get_block_template_rpc_impl.get_block_template(None); + let mock_mempool_request_handler = make_mock_mempool_request_handler.clone()(); let (block_template, _) = tokio::join!(block_template_fut, mock_mempool_request_handler); let get_block_template::Response::TemplateMode(block_template) = block_template.expect("unexpected error in getblocktemplate RPC call") @@ -3332,20 +3359,155 @@ async fn nu6_lockbox_funding_stream() -> Result<()> { let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?; let hex_proposal_block = HexData(proposal_block.zcash_serialize_to_vec()?); - let get_block_template::Response::ProposalMode(submit_result) = get_block_template_rpc_impl - .get_block_template(Some(get_block_template::JsonParameters { - mode: GetBlockTemplateRequestMode::Proposal, - data: Some(hex_proposal_block), - ..Default::default() - })) - .await? + // Check that the block template is a valid block proposal + let get_block_template::Response::ProposalMode(block_proposal_result) = + get_block_template_rpc_impl + .get_block_template(Some(get_block_template::JsonParameters { + mode: GetBlockTemplateRequestMode::Proposal, + data: Some(hex_proposal_block), + ..Default::default() + })) + .await? else { panic!( "this getblocktemplate call should return the `ProposalMode` variant of the response" ) }; - assert!(submit_result.is_valid(), "block proposal should succeed"); + assert!( + block_proposal_result.is_valid(), + "block proposal should succeed" + ); + + // Submit the same block + let submit_block_response = get_block_template_rpc_impl + .submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None) + .await?; + + assert_eq!( + submit_block_response, + submit_block::Response::Accepted, + "valid block should be accepted" + ); + + // Use an invalid coinbase transaction (with an output value greater than the `block_subsidy + miner_fees - expected_lockbox_funding_stream`) + + let make_configured_recipients_with_lockbox_numerator = |numerator| { + Some(vec![ + ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::Deferred, + numerator, + addresses: None, + }, + ConfiguredFundingStreamRecipient { + receiver: FundingStreamReceiver::MajorGrants, + numerator: 8, + addresses: Some( + FUNDING_STREAM_MG_ADDRESSES_TESTNET + .map(ToString::to_string) + .to_vec(), + ), + }, + ]) + }; + + // Gets the next block template + let block_template_fut = get_block_template_rpc_impl.get_block_template(None); + let mock_mempool_request_handler = make_mock_mempool_request_handler.clone()(); + let (block_template, _) = tokio::join!(block_template_fut, mock_mempool_request_handler); + let get_block_template::Response::TemplateMode(block_template) = + block_template.expect("unexpected error in getblocktemplate RPC call") + else { + panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response") + }; + + let zebra_state::GetBlockTemplateChainInfo { history_tree, .. } = + fetch_state_tip_and_local_time(read_state.clone()).await?; + + let network = base_network_params + .clone() + .with_post_nu6_funding_streams(ConfiguredFundingStreams { + height_range: Some(Height(1)..Height(100)), + recipients: make_configured_recipients_with_lockbox_numerator(0), + }) + .to_network(); + + let (coinbase_txn, default_roots) = generate_coinbase_and_roots( + &network, + Height(block_template.height), + &miner_address, + &[], + history_tree.clone(), + true, + vec![], + ); + + let block_template = GetBlockTemplate { + coinbase_txn, + block_commitments_hash: default_roots.block_commitments_hash, + light_client_root_hash: default_roots.block_commitments_hash, + final_sapling_root_hash: default_roots.block_commitments_hash, + default_roots, + ..(*block_template) + }; + + let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?; + + // Submit the invalid block with an excessive coinbase output value + let submit_block_response = get_block_template_rpc_impl + .submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None) + .await?; + + tracing::info!(?submit_block_response, "submitted invalid block"); + + assert_eq!( + submit_block_response, + submit_block::Response::ErrorResponse(submit_block::ErrorResponse::Rejected), + "invalid block with excessive coinbase output value should be rejected" + ); + + // Use an invalid coinbase transaction (with an output value less than than the `block_subsidy + miner_fees - expected_lockbox_funding_stream`) + let network = base_network_params + .clone() + .with_post_nu6_funding_streams(ConfiguredFundingStreams { + height_range: Some(Height(1)..Height(100)), + recipients: make_configured_recipients_with_lockbox_numerator(20), + }) + .to_network(); + + let (coinbase_txn, default_roots) = generate_coinbase_and_roots( + &network, + Height(block_template.height), + &miner_address, + &[], + history_tree.clone(), + true, + vec![], + ); + + let block_template = GetBlockTemplate { + coinbase_txn, + block_commitments_hash: default_roots.block_commitments_hash, + light_client_root_hash: default_roots.block_commitments_hash, + final_sapling_root_hash: default_roots.block_commitments_hash, + default_roots, + ..block_template + }; + + let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?; + + // Submit the invalid block with an excessive coinbase output value + let submit_block_response = get_block_template_rpc_impl + .submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None) + .await?; + + tracing::info!(?submit_block_response, "submitted invalid block"); + + assert_eq!( + submit_block_response, + submit_block::Response::ErrorResponse(submit_block::ErrorResponse::Rejected), + "invalid block with insufficient coinbase output value should be rejected" + ); Ok(()) } From c113601cb57c2be0ea98b194886fb7e62ee75e3b Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Jul 2024 17:02:26 -0400 Subject: [PATCH 23/35] Checks that the original valid block template at height 2 is accepted as a block submission --- zebrad/tests/acceptance.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 21fe5542ea4..57991df5997 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3421,6 +3421,8 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response") }; + let valid_original_block_template = block_template.clone(); + let zebra_state::GetBlockTemplateChainInfo { history_tree, .. } = fetch_state_tip_and_local_time(read_state.clone()).await?; @@ -3509,5 +3511,18 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { "invalid block with insufficient coinbase output value should be rejected" ); + // Check that the original block template can be submitted successfully + let proposal_block = + proposal_block_from_template(&valid_original_block_template, None, NetworkUpgrade::Nu6)?; + let submit_block_response = get_block_template_rpc_impl + .submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None) + .await?; + + assert_eq!( + submit_block_response, + submit_block::Response::Accepted, + "valid block should be accepted" + ); + Ok(()) } From 4627c0854dc40d65cf7a33ee16645a7c8834ef48 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Jul 2024 18:07:38 -0400 Subject: [PATCH 24/35] Reverts changes for coinbase should balance exactly ZIP --- zebra-consensus/src/block/check.rs | 11 +------ zebrad/tests/acceptance.rs | 48 ++---------------------------- 2 files changed, 3 insertions(+), 56 deletions(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 3af0df70c0c..0a72fc18ff7 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -263,16 +263,7 @@ pub fn miner_fees_are_valid( let right = (block_subsidy + block_miner_fees - expected_deferred_amount) .map_err(|_| SubsidyError::SumOverflow)?; - // TODO: Add link to exact coinbase balance ZIP - let should_allow_unclaimed_subsidy = - NetworkUpgrade::current(network, height) <= NetworkUpgrade::Nu5; - let is_invalid_miner_fee = if should_allow_unclaimed_subsidy { - left > right - } else { - left != right - }; - - if is_invalid_miner_fee { + if left > right { Err(SubsidyError::InvalidMinerFees)?; } diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 57991df5997..15a70025024 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3232,11 +3232,10 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { /// Test successful block template submission as a block proposal or submission on a custom Testnet. /// /// This test can be run locally with: -/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_lockbox_funding_stream --exact --show-output` +/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_funding_streams_and_coinbase_balance --exact --show-output` /// Note: The consensus branch id for NU6 in Zebra (defined in the `CONSENSUS_BRANCH_IDS` constant) must match the one in the `zcash_protocol` crate, currently `0xc8e71055`. #[tokio::test(flavor = "multi_thread")] -// #[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] -#[cfg(feature = "getblocktemplate-rpcs")] +#[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { use zebra_chain::{ chain_sync_status::MockSyncStatus, @@ -3468,49 +3467,6 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { "invalid block with excessive coinbase output value should be rejected" ); - // Use an invalid coinbase transaction (with an output value less than than the `block_subsidy + miner_fees - expected_lockbox_funding_stream`) - let network = base_network_params - .clone() - .with_post_nu6_funding_streams(ConfiguredFundingStreams { - height_range: Some(Height(1)..Height(100)), - recipients: make_configured_recipients_with_lockbox_numerator(20), - }) - .to_network(); - - let (coinbase_txn, default_roots) = generate_coinbase_and_roots( - &network, - Height(block_template.height), - &miner_address, - &[], - history_tree.clone(), - true, - vec![], - ); - - let block_template = GetBlockTemplate { - coinbase_txn, - block_commitments_hash: default_roots.block_commitments_hash, - light_client_root_hash: default_roots.block_commitments_hash, - final_sapling_root_hash: default_roots.block_commitments_hash, - default_roots, - ..block_template - }; - - let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?; - - // Submit the invalid block with an excessive coinbase output value - let submit_block_response = get_block_template_rpc_impl - .submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None) - .await?; - - tracing::info!(?submit_block_response, "submitted invalid block"); - - assert_eq!( - submit_block_response, - submit_block::Response::ErrorResponse(submit_block::ErrorResponse::Rejected), - "invalid block with insufficient coinbase output value should be rejected" - ); - // Check that the original block template can be submitted successfully let proposal_block = proposal_block_from_template(&valid_original_block_template, None, NetworkUpgrade::Nu6)?; From 126fdab77fce5b4ecfa8e7265b4fea795889478c Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 31 Jul 2024 21:05:57 -0400 Subject: [PATCH 25/35] updates test name --- zebrad/tests/acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 15a70025024..03e6f97c2f1 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3232,11 +3232,11 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { /// Test successful block template submission as a block proposal or submission on a custom Testnet. /// /// This test can be run locally with: -/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_funding_streams_and_coinbase_balance --exact --show-output` +/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_funding_streams --exact --show-output` /// Note: The consensus branch id for NU6 in Zebra (defined in the `CONSENSUS_BRANCH_IDS` constant) must match the one in the `zcash_protocol` crate, currently `0xc8e71055`. #[tokio::test(flavor = "multi_thread")] #[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] -async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { +async fn nu6_funding_streams() -> Result<()> { use zebra_chain::{ chain_sync_status::MockSyncStatus, parameters::{ From bf4943270366430d218fb6a4900fc5ab76aba7b3 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 31 Jul 2024 21:21:45 -0400 Subject: [PATCH 26/35] Updates deferred pool funding stream name to "Lockbox", moves post-NU6 height ranges to constants, updates TODO --- zebra-chain/src/parameters/network/subsidy.rs | 27 ++++++++++++++++--- zebra-consensus/src/block/subsidy/general.rs | 2 +- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index 8bb8019059f..b0048191b23 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -78,7 +78,7 @@ impl FundingStreamReceiver { FundingStreamReceiver::ZcashFoundation => "Zcash Foundation", FundingStreamReceiver::MajorGrants => "Major Grants", // TODO: Find out what this should be called and update the funding stream name - FundingStreamReceiver::Deferred => "Deferred Fund", + FundingStreamReceiver::Deferred => "Lockbox", } } } @@ -209,7 +209,7 @@ lazy_static! { // 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 { // TODO: Adjust this height range and recipient list once a proposal is selected - height_range: Height(2_726_400)..Height(3_146_400), + height_range: POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET, recipients: [ ( FundingStreamReceiver::Deferred, @@ -252,7 +252,7 @@ lazy_static! { // 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 { // TODO: Adjust this height range and recipient list once a proposal is selected - height_range: Height(2_942_000)..Height(3_362_000), + height_range: POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET, recipients: [ ( FundingStreamReceiver::Deferred, @@ -269,6 +269,27 @@ lazy_static! { }; } +/// 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(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(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]. /// diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 5c53f43cafb..0485ab0735c 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -122,7 +122,7 @@ pub fn output_amounts(transaction: &Transaction) -> HashSet> /// /// Assumes a constant funding stream amount per block. // TODO: Cache the lockbox value balance in zebra-state (will be required for tracking lockbox -// value balance after ZSF ZIPs or after a ZIP for spending from the deferred pool) +// value balance after the Zcash Sustainability Fund ZIPs or after a ZIP for spending from the deferred pool) #[allow(dead_code)] fn lockbox_input_value(network: &Network, height: Height) -> Amount { let Some(nu6_activation_height) = Nu6.activation_height(network) else { From 14fbfb454f9fd58cb4c96b8ca9821048bb699a4f Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 31 Jul 2024 21:23:43 -0400 Subject: [PATCH 27/35] Updates `get_block_subsidy()` RPC method to exclude lockbox funding stream from `fundingstreams` field --- zebra-rpc/src/methods/get_block_template_rpcs.rs | 6 +++--- .../src/methods/get_block_template_rpcs/types/subsidy.rs | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index b9eccb90ad9..f191c3a287c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -1192,9 +1192,9 @@ where })?; let mut funding_streams: Vec<_> = funding_streams .iter() - .map(|(receiver, value)| { - let address = funding_stream_address(height, &network, *receiver); - (*receiver, FundingStream::new(*receiver, *value, address)) + .filter_map(|(receiver, value)| { + let address = funding_stream_address(height, &network, *receiver)?; + Some((*receiver, FundingStream::new(*receiver, *value, address))) }) .collect(); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs index df4a3ef6010..f5ac478bf6b 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -59,8 +59,7 @@ pub struct FundingStream { /// /// The current Zcash funding streams only use transparent addresses, /// so Zebra doesn't support Sapling addresses in this RPC. - #[serde(skip_serializing_if = "Option::is_none")] - pub address: Option, + pub address: transparent::Address, } impl FundingStream { @@ -68,7 +67,7 @@ impl FundingStream { pub fn new( receiver: FundingStreamReceiver, value: Amount, - address: Option<&transparent::Address>, + address: &transparent::Address, ) -> FundingStream { let (recipient, specification) = funding_stream_recipient_info(receiver); @@ -77,7 +76,7 @@ impl FundingStream { specification: specification.to_string(), value: value.into(), value_zat: value, - address: address.cloned(), + address: address.clone(), } } } From 7987c7636548d54831d50f1c8215b9ad8c553fe1 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 31 Jul 2024 21:27:43 -0400 Subject: [PATCH 28/35] Adds a TODO for updating `FundingStreamReceiver::name()` method docs --- zebra-chain/src/parameters/network/subsidy.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index b0048191b23..a469f64c980 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -72,12 +72,14 @@ impl FundingStreamReceiver { /// /// [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 + // TODO: Find out what this should be called and update the funding stream name. FundingStreamReceiver::Deferred => "Lockbox", } } From e5eaf38ba10e56a51f02234d80d548a164e0033f Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 31 Jul 2024 21:40:03 -0400 Subject: [PATCH 29/35] 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. --- zebra-chain/src/parameters/network/subsidy.rs | 42 +++++++++---------- zebra-chain/src/parameters/network/testnet.rs | 2 +- zebra-consensus/src/block/check.rs | 6 +++ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index a469f64c980..9c06112de4f 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -151,25 +151,21 @@ pub struct FundingStreamRecipient { impl FundingStreamRecipient { /// Creates a new [`FundingStreamRecipient`]. - pub fn new(numerator: u64, addresses: Option) -> Self + pub fn new(numerator: u64, addresses: I) -> Self where T: ToString, I: IntoIterator, { Self { numerator, - addresses: if let Some(addresses) = addresses { - addresses - .into_iter() - .map(|addr| { - let addr = addr.to_string(); - addr.parse() - .expect("funding stream address must deserialize") - }) - .collect() - } else { - vec![] - }, + addresses: addresses + .into_iter() + .map(|addr| { + let addr = addr.to_string(); + addr.parse() + .expect("funding stream address must deserialize") + }) + .collect(), } } @@ -192,15 +188,15 @@ lazy_static! { recipients: [ ( FundingStreamReceiver::Ecc, - FundingStreamRecipient::new(7, Some(FUNDING_STREAM_ECC_ADDRESSES_MAINNET)), + FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET), ), ( FundingStreamReceiver::ZcashFoundation, - FundingStreamRecipient::new(5, Some(FUNDING_STREAM_ZF_ADDRESSES_MAINNET)), + FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_MAINNET), ), ( FundingStreamReceiver::MajorGrants, - FundingStreamRecipient::new(8, Some(FUNDING_STREAM_MG_ADDRESSES_MAINNET)), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET), ), ] .into_iter() @@ -215,12 +211,12 @@ lazy_static! { recipients: [ ( FundingStreamReceiver::Deferred, - FundingStreamRecipient::new::<[&str; 0], &str>(12, None), + FundingStreamRecipient::new::<[&str; 0], &str>(12, []), ), ( FundingStreamReceiver::MajorGrants, // TODO: Update these addresses - FundingStreamRecipient::new(8, Some(FUNDING_STREAM_MG_ADDRESSES_MAINNET)), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET), ), ] .into_iter() @@ -234,15 +230,15 @@ lazy_static! { recipients: [ ( FundingStreamReceiver::Ecc, - FundingStreamRecipient::new(7, Some(FUNDING_STREAM_ECC_ADDRESSES_TESTNET)), + FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_TESTNET), ), ( FundingStreamReceiver::ZcashFoundation, - FundingStreamRecipient::new(5, Some(FUNDING_STREAM_ZF_ADDRESSES_TESTNET)), + FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_TESTNET), ), ( FundingStreamReceiver::MajorGrants, - FundingStreamRecipient::new(8, Some(FUNDING_STREAM_MG_ADDRESSES_TESTNET)), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), ), ] .into_iter() @@ -258,12 +254,12 @@ lazy_static! { recipients: [ ( FundingStreamReceiver::Deferred, - FundingStreamRecipient::new::<[&str; 0], &str>(12, None), + FundingStreamRecipient::new::<[&str; 0], &str>(12, []), ), ( FundingStreamReceiver::MajorGrants, // TODO: Update these addresses - FundingStreamRecipient::new(8, Some(FUNDING_STREAM_MG_ADDRESSES_TESTNET)), + FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), ), ] .into_iter() diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 8680b91550e..80cb8419c77 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -76,7 +76,7 @@ impl ConfiguredFundingStreamRecipient { pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) { ( self.receiver, - FundingStreamRecipient::new(self.numerator, self.addresses), + FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()), ) } } diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 0a72fc18ff7..2e27bc42aa8 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -258,6 +258,12 @@ pub fn miner_fees_are_valid( // > 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 - expected_deferred_amount) From 8949301ab8e566738ab0c5dd4e609832cea66947 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 1 Aug 2024 10:06:51 -0400 Subject: [PATCH 30/35] Uses FPF Testnet address for post-NU6 testnet funding streams --- zebra-chain/src/parameters/network/subsidy.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/parameters/network/subsidy.rs b/zebra-chain/src/parameters/network/subsidy.rs index 9c06112de4f..739fda8ec3e 100644 --- a/zebra-chain/src/parameters/network/subsidy.rs +++ b/zebra-chain/src/parameters/network/subsidy.rs @@ -259,7 +259,7 @@ lazy_static! { ( FundingStreamReceiver::MajorGrants, // TODO: Update these addresses - FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET), + FundingStreamRecipient::new(8, POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET), ), ] .into_iter() @@ -456,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] /// From cdb168423827eab675653d60aacd8bb99809c8d7 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 1 Aug 2024 12:02:54 -0400 Subject: [PATCH 31/35] Updates the NU6 consensus branch id --- zebra-chain/src/parameters/network_upgrade.rs | 2 +- zebrad/tests/acceptance.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index b71e5dd363e..121a5bdcf13 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -216,7 +216,7 @@ pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = (Heartwood, ConsensusBranchId(0xf5b9230b)), (Canopy, ConsensusBranchId(0xe9ff75a6)), (Nu5, ConsensusBranchId(0xc2d6d0b4)), - (Nu6, ConsensusBranchId(0x7fffffff)), + (Nu6, ConsensusBranchId(0xc8e71055)), ]; /// The target block spacing before Blossom. diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 03e6f97c2f1..b262d3c9d68 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3233,7 +3233,6 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { /// /// This test can be run locally with: /// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_funding_streams --exact --show-output` -/// Note: The consensus branch id for NU6 in Zebra (defined in the `CONSENSUS_BRANCH_IDS` constant) must match the one in the `zcash_protocol` crate, currently `0xc8e71055`. #[tokio::test(flavor = "multi_thread")] #[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] async fn nu6_funding_streams() -> Result<()> { From 6057fdf125e544d2abef4331967c337424f3bce7 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Jul 2024 18:20:45 -0400 Subject: [PATCH 32/35] checks that coinbase transactions balance exactly --- zebra-consensus/src/block/check.rs | 11 +++++++- zebrad/tests/acceptance.rs | 43 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 2e27bc42aa8..45c8efb467c 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -269,7 +269,16 @@ pub fn miner_fees_are_valid( let right = (block_subsidy + block_miner_fees - expected_deferred_amount) .map_err(|_| SubsidyError::SumOverflow)?; - if left > right { + // TODO: Add link to exact coinbase balance ZIP + let should_allow_unclaimed_subsidy = + NetworkUpgrade::current(network, height) < NetworkUpgrade::Nu6; + let is_invalid_miner_fee = if should_allow_unclaimed_subsidy { + left > right + } else { + left != right + }; + + if is_invalid_miner_fee { Err(SubsidyError::InvalidMinerFees)?; } diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index b262d3c9d68..e0c89356c13 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3466,6 +3466,49 @@ async fn nu6_funding_streams() -> Result<()> { "invalid block with excessive coinbase output value should be rejected" ); + // Use an invalid coinbase transaction (with an output value less than than the `block_subsidy + miner_fees - expected_lockbox_funding_stream`) + let network = base_network_params + .clone() + .with_post_nu6_funding_streams(ConfiguredFundingStreams { + height_range: Some(Height(1)..Height(100)), + recipients: make_configured_recipients_with_lockbox_numerator(20), + }) + .to_network(); + + let (coinbase_txn, default_roots) = generate_coinbase_and_roots( + &network, + Height(block_template.height), + &miner_address, + &[], + history_tree.clone(), + true, + vec![], + ); + + let block_template = GetBlockTemplate { + coinbase_txn, + block_commitments_hash: default_roots.block_commitments_hash, + light_client_root_hash: default_roots.block_commitments_hash, + final_sapling_root_hash: default_roots.block_commitments_hash, + default_roots, + ..block_template + }; + + let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?; + + // Submit the invalid block with an excessive coinbase input value + let submit_block_response = get_block_template_rpc_impl + .submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None) + .await?; + + tracing::info!(?submit_block_response, "submitted invalid block"); + + assert_eq!( + submit_block_response, + submit_block::Response::ErrorResponse(submit_block::ErrorResponse::Rejected), + "invalid block with insufficient coinbase output value should be rejected" + ); + // Check that the original block template can be submitted successfully let proposal_block = proposal_block_from_template(&valid_original_block_template, None, NetworkUpgrade::Nu6)?; From f308d4905c7614b14a38722ba8aa79d7af60964c Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 1 Aug 2024 12:45:22 -0400 Subject: [PATCH 33/35] updates test name --- zebrad/tests/acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index e0c89356c13..86ee061f385 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3232,10 +3232,10 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { /// Test successful block template submission as a block proposal or submission on a custom Testnet. /// /// This test can be run locally with: -/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_funding_streams --exact --show-output` +/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_funding_streams_and_coinbase_balance --exact --show-output` #[tokio::test(flavor = "multi_thread")] #[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))] -async fn nu6_funding_streams() -> Result<()> { +async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { use zebra_chain::{ chain_sync_status::MockSyncStatus, parameters::{ From ad2a425f700f38046005ebd46c7860439471e252 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 8 Aug 2024 13:57:29 +0200 Subject: [PATCH 34/35] Add a TODO --- zebra-consensus/src/block/subsidy/general.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 233f7935c1c..03ebac36d21 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -2,6 +2,8 @@ //! //! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies +// TODO: Move the contents of this mod to the parent mod and remove this mod. + use std::collections::HashSet; use zebra_chain::{ From 78dc6b65c7762f5477ec1d99f5a755de236e5b41 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 8 Aug 2024 14:43:38 +0200 Subject: [PATCH 35/35] Refactor `miner_fees_are_valid` --- zebra-consensus/src/block.rs | 4 ++- zebra-consensus/src/block/check.rs | 38 +++++++++++---------- zebra-consensus/src/block/tests.rs | 55 +++++++++++++++--------------- 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index b7fd6740ecc..2b0013a5a3d 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -302,10 +302,12 @@ where })?; check::miner_fees_are_valid( - &block, + &coinbase_tx, + height, block_miner_fees, expected_block_subsidy, expected_deferred_amount, + &network, )?; // Finally, submit the block for contextual verification. diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index d59706f5580..189cfdc8493 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -8,7 +8,7 @@ use zebra_chain::{ amount::{Amount, Error as AmountError, NonNegative}, block::{Block, Hash, Header, Height}, parameters::{subsidy::FundingStreamReceiver, Network, NetworkUpgrade}, - transaction, + transaction::{self, Transaction}, work::{ difficulty::{ExpandedDifficulty, ParameterDifficulty as _}, equihash, @@ -234,22 +234,24 @@ pub fn subsidy_is_valid( /// /// [7.1.2]: https://zips.z.cash/protocol/protocol.pdf#txnconsensus pub fn miner_fees_are_valid( - block: &Block, + coinbase_tx: &Transaction, + height: Height, block_miner_fees: Amount, expected_block_subsidy: Amount, expected_deferred_amount: Amount, + network: &Network, ) -> Result<(), BlockError> { - let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?; - - let transparent_value_balance: Amount = subsidy::general::output_amounts(coinbase) + let transparent_value_balance = subsidy::general::output_amounts(coinbase_tx) .iter() .sum::, AmountError>>() .map_err(|_| SubsidyError::SumOverflow)? .constrain() .expect("positive value always fit in `NegativeAllowed`"); - let sapling_value_balance = coinbase.sapling_value_balance().sapling_amount(); - let orchard_value_balance = coinbase.orchard_value_balance().orchard_amount(); + let sapling_value_balance = coinbase_tx.sapling_value_balance().sapling_amount(); + let orchard_value_balance = coinbase_tx.orchard_value_balance().orchard_amount(); + // TODO: Update the quote below once its been updated for NU6. + // // # Consensus // // > The total value in zatoshi of transparent outputs from a coinbase transaction, @@ -260,27 +262,27 @@ pub fn miner_fees_are_valid( // // 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 = (expected_block_subsidy + block_miner_fees - expected_deferred_amount) .map_err(|_| SubsidyError::SumOverflow)?; - // TODO: Add link to exact coinbase balance ZIP - let should_allow_unclaimed_subsidy = - NetworkUpgrade::current(network, height) < NetworkUpgrade::Nu6; - let is_invalid_miner_fee = if should_allow_unclaimed_subsidy { + // TODO: Updadte the quotes below if the final phrasing changes in the spec for NU6. + // + // # Consensus + // + // > [Pre-NU6] The total output of a coinbase transaction MUST NOT be greater than its total + // input. + // + // > [NU6 onward] The total output of a coinbase transaction MUST be equal to its total input. + if if NetworkUpgrade::current(network, height) < NetworkUpgrade::Nu6 { left > right } else { left != right + } { + Err(SubsidyError::InvalidMinerFees)? }; - if is_invalid_miner_fee { - Err(SubsidyError::InvalidMinerFees)?; - } - Ok(()) } diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index 8f202a716e2..d8f74bf7b2f 100644 --- a/zebra-consensus/src/block/tests.rs +++ b/zebra-consensus/src/block/tests.rs @@ -509,8 +509,12 @@ fn miner_fees_validation_for_network(network: Network) -> Result<(), Report> { for (&height, block) in block_iter { let height = Height(height); if height > network.slow_start_shift() { - let block = Block::zcash_deserialize(&block[..]).expect("block should deserialize"); + let coinbase_tx = check::coinbase_is_first( + &Block::zcash_deserialize(&block[..]).expect("block should deserialize"), + )?; + let expected_block_subsidy = block_subsidy(height, &network)?; + // TODO: Add link to lockbox stream ZIP let expected_deferred_amount = subsidy::funding_streams::funding_stream_values( height, @@ -521,17 +525,16 @@ fn miner_fees_validation_for_network(network: Network) -> Result<(), Report> { .remove(&FundingStreamReceiver::Deferred) .unwrap_or_default(); - // fake the miner fee to a big amount - let miner_fees = Amount::try_from(MAX_MONEY / 2).unwrap(); - - // Validate - let result = check::miner_fees_are_valid( - &block, - miner_fees, + assert!(check::miner_fees_are_valid( + &coinbase_tx, + height, + // Set the miner fees to a high-enough amount. + Amount::try_from(MAX_MONEY / 2).unwrap(), expected_block_subsidy, expected_deferred_amount, - ); - assert!(result.is_ok()); + &network, + ) + .is_ok(),); } } @@ -542,9 +545,8 @@ fn miner_fees_validation_for_network(network: Network) -> Result<(), Report> { fn miner_fees_validation_failure() -> Result<(), Report> { let _init_guard = zebra_test::init(); let network = Network::Mainnet; - let block = - Arc::::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_347499_BYTES[..]) - .expect("block should deserialize"); + let block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_347499_BYTES[..]) + .expect("block should deserialize"); let height = block.coinbase_height().expect("valid coinbase height"); let expected_block_subsidy = block_subsidy(height, &network)?; // TODO: Add link to lockbox stream ZIP @@ -554,22 +556,21 @@ fn miner_fees_validation_failure() -> Result<(), Report> { .remove(&FundingStreamReceiver::Deferred) .unwrap_or_default(); - // fake the miner fee to a small amount - let miner_fees = Amount::zero(); - - // Validate - let result = check::miner_fees_are_valid( - &block, - miner_fees, - expected_block_subsidy, - expected_deferred_amount, + assert_eq!( + check::miner_fees_are_valid( + check::coinbase_is_first(&block)?.as_ref(), + height, + // Set the miner fee to an invalid amount. + Amount::zero(), + expected_block_subsidy, + expected_deferred_amount, + &network + ), + Err(BlockError::Transaction(TransactionError::Subsidy( + SubsidyError::InvalidMinerFees, + ))) ); - let expected = Err(BlockError::Transaction(TransactionError::Subsidy( - SubsidyError::InvalidMinerFees, - ))); - assert_eq!(expected, result); - Ok(()) }