From 7b009b34c4e2032c57d11cad2e92058ae981d173 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 18:22:07 -0400 Subject: [PATCH 01/19] Always activate Canopy at Height(1) on Regtest --- zebra-chain/src/parameters/network.rs | 6 ++---- zebra-chain/src/parameters/network/testnet.rs | 10 +++++++--- zebra-chain/src/parameters/network/tests/vectors.rs | 2 +- zebra-network/src/config.rs | 12 +++++------- zebrad/tests/common/configs/v1.7.0.toml | 2 ++ 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index 1f886549941..424983f6dcc 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -11,8 +11,6 @@ use crate::{ parameters::NetworkUpgrade, }; -use self::testnet::ConfiguredActivationHeights; - pub mod testnet; #[cfg(test)] @@ -167,8 +165,8 @@ impl Network { } /// Creates a new [`Network::Testnet`] with `Regtest` parameters and the provided network upgrade activation heights. - pub fn new_regtest(activation_heights: ConfiguredActivationHeights) -> Self { - Self::new_configured_testnet(testnet::Parameters::new_regtest(activation_heights)) + pub fn new_regtest(nu5_activation_height: Option) -> Self { + Self::new_configured_testnet(testnet::Parameters::new_regtest(nu5_activation_height)) } /// Returns true if the network is the default Testnet, or false otherwise. diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 1533d7f83df..276eb3516ac 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -310,7 +310,7 @@ impl Parameters { /// Accepts a [`ConfiguredActivationHeights`]. /// /// Creates an instance of [`Parameters`] with `Regtest` values. - pub fn new_regtest(activation_heights: ConfiguredActivationHeights) -> Self { + pub fn new_regtest(nu5_activation_height: Option) -> Self { Self { network_name: "Regtest".to_string(), ..Self::build() @@ -323,7 +323,11 @@ impl Parameters { ) // Removes default Testnet activation heights if not configured, // most network upgrades are disabled by default for Regtest in zcashd - .with_activation_heights(activation_heights) + .with_activation_heights(ConfiguredActivationHeights { + canopy: Some(1), + nu5: nu5_activation_height, + ..Default::default() + }) .finish() } } @@ -344,7 +348,7 @@ impl Parameters { hrp_sapling_extended_full_viewing_key, hrp_sapling_payment_address, disable_pow, - } = Self::new_regtest(ConfiguredActivationHeights::default()); + } = Self::new_regtest(None); self.network_name == network_name && self.genesis_hash == genesis_hash diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index d794b047142..595771e4a0c 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -135,7 +135,7 @@ fn activates_network_upgrades_correctly() { let expected_default_regtest_activation_heights = &[ (Height(0), NetworkUpgrade::Genesis), - (Height(1), NetworkUpgrade::BeforeOverwinter), + (Height(1), NetworkUpgrade::Canopy), ]; for (network, expected_activation_heights) in [ diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index b630c30c267..ef78483683c 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -230,9 +230,7 @@ impl Config { Network::Testnet(params) if params.is_default_testnet() => { self.initial_testnet_peers.clone() } - // TODO: Check if the network is an incompatible custom testnet (_not_ Regtest), then panic if `initial_testnet_peers` - // contains any of the default testnet peers, or return `initial_testnet_peers` otherwise. See: - // + // TODO: Add a `disable_peers` field to `Network` to check instead of `is_default_testnet()` (#8361) Network::Testnet(_params) => IndexSet::new(), } } @@ -639,7 +637,7 @@ impl<'de> Deserialize<'de> for Config { listen_addr: String, network: NetworkKind, testnet_parameters: Option, - regtest_activation_heights: ConfiguredActivationHeights, + regtest_nu5_activation_height: Option, initial_mainnet_peers: IndexSet, initial_testnet_peers: IndexSet, cache_dir: CacheDir, @@ -656,7 +654,7 @@ impl<'de> Deserialize<'de> for Config { listen_addr: "0.0.0.0".to_string(), network: Default::default(), testnet_parameters: None, - regtest_activation_heights: ConfiguredActivationHeights::default(), + regtest_nu5_activation_height: None, initial_mainnet_peers: config.initial_mainnet_peers, initial_testnet_peers: config.initial_testnet_peers, cache_dir: config.cache_dir, @@ -671,7 +669,7 @@ impl<'de> Deserialize<'de> for Config { listen_addr, network: network_kind, testnet_parameters, - regtest_activation_heights, + regtest_nu5_activation_height, initial_mainnet_peers, initial_testnet_peers, cache_dir, @@ -700,7 +698,7 @@ impl<'de> Deserialize<'de> for Config { let network = match (network_kind, testnet_parameters) { (NetworkKind::Mainnet, _) => Network::Mainnet, (NetworkKind::Testnet, None) => Network::new_default_testnet(), - (NetworkKind::Regtest, _) => Network::new_regtest(regtest_activation_heights), + (NetworkKind::Regtest, _) => Network::new_regtest(regtest_nu5_activation_height), ( NetworkKind::Testnet, Some(DTestnetParameters { diff --git a/zebrad/tests/common/configs/v1.7.0.toml b/zebrad/tests/common/configs/v1.7.0.toml index f29e3937859..dfb64afd5d0 100644 --- a/zebrad/tests/common/configs/v1.7.0.toml +++ b/zebrad/tests/common/configs/v1.7.0.toml @@ -60,6 +60,8 @@ max_connections_per_ip = 1 network = "Regtest" peerset_initial_target_size = 25 +regtest_nu5_activation_height = 1 + [network.testnet_parameters] network_name = "ConfiguredTestnet_1" From 21e838a09ac6bec05addb1e92961296fc84bed55 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 22 Apr 2024 21:18:45 -0400 Subject: [PATCH 02/19] Ignores the zip 212 grace period on configured Testnets and Regtest --- zebra-chain/src/parameters/network.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index 424983f6dcc..aa1337e99ca 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -240,6 +240,12 @@ impl Network { /// Mandatory checkpoints are a Zebra-specific feature. /// If a Zcash consensus rule only applies before the mandatory checkpoint, /// Zebra can skip validation of that rule. + /// + /// ZIP-212 grace period is only applied to default networks. + // TODO: + // - Support constructing pre-Canopy coinbase tx and block templates and return `Height::MAX` instead of panicking + // when Canopy activation height is `None` (#8434) + // - Add semantic block validation during the ZIP-212 grace period and update this method to always apply the ZIP-212 grace period pub fn mandatory_checkpoint_height(&self) -> Height { // Currently this is after the ZIP-212 grace period. // @@ -249,8 +255,17 @@ impl Network { .activation_height(self) .expect("Canopy activation height must be present for both networks"); - (canopy_activation + ZIP_212_GRACE_PERIOD_DURATION) - .expect("ZIP-212 grace period ends at a valid block height") + let is_a_default_network = match self { + Network::Mainnet => true, + Network::Testnet(params) => params.is_default_testnet(), + }; + + if is_a_default_network { + (canopy_activation + ZIP_212_GRACE_PERIOD_DURATION) + .expect("ZIP-212 grace period ends at a valid block height") + } else { + canopy_activation + } } /// Return the network name as defined in From 52ff68f4c8caad8be611450b15872ca957a57fc9 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 19:03:34 -0400 Subject: [PATCH 03/19] - Returns early when there is no Heartwood activation height when creating or updating HistoryTree - Skips call to `check::legacy_chain()` when no NU5 activation height is set (it would return immediately anyway) - Replaces `.map()` with `.filter_map()` in `NetworkUpgrade::target_spacings()` - Removes outdated TODO --- zebra-chain/src/history_tree.rs | 8 +++++--- zebra-chain/src/parameters/network_upgrade.rs | 10 +++------- zebra-consensus/src/transaction/check.rs | 1 - zebra-state/src/service.rs | 9 ++++----- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/zebra-chain/src/history_tree.rs b/zebra-chain/src/history_tree.rs index d3a45f15f8b..59dcb13a69d 100644 --- a/zebra-chain/src/history_tree.rs +++ b/zebra-chain/src/history_tree.rs @@ -428,9 +428,11 @@ impl HistoryTree { sapling_root: &sapling::tree::Root, orchard_root: &orchard::tree::Root, ) -> Result { - let heartwood_height = NetworkUpgrade::Heartwood - .activation_height(network) - .expect("Heartwood height is known"); + let Some(heartwood_height) = NetworkUpgrade::Heartwood.activation_height(network) else { + // Return early if there is no Heartwood activation height. + return Ok(HistoryTree(None)); + }; + match block .coinbase_height() .expect("must have height") diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index de546e3b422..4297eb76436 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -402,14 +402,10 @@ impl NetworkUpgrade { ), ] .into_iter() - .map(move |(upgrade, spacing_seconds)| { - let activation_height = upgrade - .activation_height(network) - .expect("missing activation height for target spacing change"); - + .filter_map(move |(upgrade, spacing_seconds)| { + let activation_height = upgrade.activation_height(network)?; let target_spacing = Duration::seconds(spacing_seconds); - - (activation_height, target_spacing) + Some((activation_height, target_spacing)) }) } diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index 432f3d2f641..e96168d4c35 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -356,7 +356,6 @@ pub fn coinbase_expiry_height( ) -> Result<(), TransactionError> { let expiry_height = coinbase.expiry_height(); - // TODO: replace `if let` with `expect` after NU5 mainnet activation if let Some(nu5_activation_height) = NetworkUpgrade::Nu5.activation_height(network) { // # Consensus // diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 203c1d34c70..0597be88897 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -422,11 +422,10 @@ impl StateService { tracing::info!("starting legacy chain check"); let timer = CodeTimer::start(); - if let Some(tip) = state.best_tip() { - let nu5_activation_height = NetworkUpgrade::Nu5 - .activation_height(network) - .expect("NU5 activation height is set"); - + if let (Some(tip), Some(nu5_activation_height)) = ( + state.best_tip(), + NetworkUpgrade::Nu5.activation_height(network), + ) { if let Err(error) = check::legacy_chain( nu5_activation_height, any_ancestor_blocks( From 37f9b76d1b92b42d9bc8aa95e50e7aed4ff72789 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 19:05:51 -0400 Subject: [PATCH 04/19] - When proof of work is disabled, skips checking if Zebra is synced to the network tip in the getblocktemplate method --- zebra-rpc/src/methods/get_block_template_rpcs.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 9ef9c581e0a..26edc82117c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -638,8 +638,10 @@ where // // Optional TODO: // - add `async changed()` method to ChainSyncStatus (like `ChainTip`) - check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?; - + // TODO: Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361) + if !network.disable_pow() { + 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 From 2e954955cf09f6dcdf1d3a537532743a3893a3aa Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 19:07:37 -0400 Subject: [PATCH 05/19] Sets full_verifier_utxo_lookahead to Height::MIN instead of panicking --- zebra-state/src/service.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 0597be88897..994e8ad6b32 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -395,8 +395,7 @@ impl StateService { - HeightDiff::try_from(checkpoint_verify_concurrency_limit) .expect("fits in HeightDiff"); let full_verifier_utxo_lookahead = - full_verifier_utxo_lookahead.expect("unexpected negative height"); - + full_verifier_utxo_lookahead.unwrap_or(block::Height::MIN); let non_finalized_state_queued_blocks = QueuedBlocks::default(); let pending_utxos = PendingUtxos::default(); From 7f1013adf72d9fd2834aa0f02a7cad147cf82e9a Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 19:12:49 -0400 Subject: [PATCH 06/19] When network is regtest, skips starting sync task and commits the genesis block if it's missing in the state --- zebrad/src/commands/start.rs | 30 ++++++++++++++++++++++++++---- zebrad/src/components/sync.rs | 2 +- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index dbe2368f37b..7efabf08ecc 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -82,10 +82,11 @@ use abscissa_core::{config, Command, FrameworkError}; use color_eyre::eyre::{eyre, Report}; use futures::FutureExt; use tokio::{pin, select, sync::oneshot}; -use tower::{builder::ServiceBuilder, util::BoxService}; +use tower::{builder::ServiceBuilder, util::BoxService, ServiceExt}; use tracing_futures::Instrument; -use zebra_consensus::router::BackgroundTaskHandles; +use zebra_chain::block::genesis::regtest_genesis_block; +use zebra_consensus::{router::BackgroundTaskHandles, ParameterCheckpoint}; use zebra_rpc::server::RpcServer; use crate::{ @@ -177,7 +178,7 @@ impl StartCmd { .await; info!("initializing syncer"); - let (syncer, sync_status) = ChainSync::new( + let (mut syncer, sync_status) = ChainSync::new( &config, max_checkpoint_height, peer_set.clone(), @@ -300,7 +301,28 @@ impl StartCmd { ); info!("spawning syncer task"); - let syncer_task_handle = tokio::spawn(syncer.sync().in_current_span()); + let syncer_task_handle = if config.network.network.is_regtest() { + if !syncer + .state_contains(config.network.network.genesis_hash()) + .await? + { + let genesis_hash = block_verifier_router + .clone() + .oneshot(zebra_consensus::Request::Commit(regtest_genesis_block())) + .await + .expect("should validate Regtest genesis block"); + + assert_eq!( + genesis_hash, + config.network.network.genesis_hash(), + "validated block hash should match network genesis hash" + ) + } + + tokio::spawn(std::future::pending().in_current_span()) + } else { + tokio::spawn(syncer.sync().in_current_span()) + }; #[cfg(feature = "shielded-scan")] // Spawn never ending scan task only if we have keys to scan for. diff --git a/zebrad/src/components/sync.rs b/zebrad/src/components/sync.rs index cbfd5e7b64c..e51ee06f06f 100644 --- a/zebrad/src/components/sync.rs +++ b/zebrad/src/components/sync.rs @@ -1136,7 +1136,7 @@ where /// Returns `true` if the hash is present in the state, and `false` /// if the hash is not present in the state. - async fn state_contains(&mut self, hash: block::Hash) -> Result { + pub(crate) async fn state_contains(&mut self, hash: block::Hash) -> Result { match self .state .ready() From 071cb1c0e5dc0bc6c828ad9e8ced37dc2ad0f7e7 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 19:44:16 -0400 Subject: [PATCH 07/19] updates/fixes test config --- zebrad/tests/common/configs/v1.7.0.toml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/zebrad/tests/common/configs/v1.7.0.toml b/zebrad/tests/common/configs/v1.7.0.toml index dfb64afd5d0..a2f0c36602d 100644 --- a/zebrad/tests/common/configs/v1.7.0.toml +++ b/zebrad/tests/common/configs/v1.7.0.toml @@ -74,15 +74,6 @@ Heartwood = 903_800 Canopy = 1_028_500 NU5 = 1_842_420 -[network.regtest_activation_heights] -BeforeOverwinter = 1 -Overwinter = 207_500 -Sapling = 280_000 -Blossom = 584_000 -Heartwood = 903_800 -Canopy = 1_028_500 -NU5 = 1_842_420 - [rpc] debug_force_finished_sync = false parallel_cpu_threads = 0 From 088e4e4904d41a4cabecc0f6ce7c919d2cb86ed5 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 20:05:55 -0400 Subject: [PATCH 08/19] Adds test for committing Canopy blocks on Regtest --- zebra-chain/src/parameters/network.rs | 3 +- zebra-consensus/src/parameters/subsidy.rs | 13 +++- zebra-network/src/constants.rs | 1 + zebrad/tests/acceptance.rs | 11 +++ zebrad/tests/common/mod.rs | 3 + zebrad/tests/common/regtest.rs | 87 +++++++++++++++++++++++ 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 zebrad/tests/common/regtest.rs diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index aa1337e99ca..2bbaf118819 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -199,14 +199,13 @@ impl Network { pub fn kind(&self) -> NetworkKind { match self { Network::Mainnet => NetworkKind::Mainnet, - // TODO: Return `NetworkKind::Regtest` if the parameters match the default Regtest params + Network::Testnet(params) if params.is_regtest() => NetworkKind::Regtest, Network::Testnet(_) => NetworkKind::Testnet, } } /// Returns an iterator over [`Network`] variants. pub fn iter() -> impl Iterator { - // TODO: Use default values of `Testnet` variant when adding fields for #7845. [Self::Mainnet, Self::new_default_testnet()].into_iter() } diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index e7cf8f91b61..119442e9d71 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -109,12 +109,11 @@ lazy_static! { 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 }; /// Convenient storage for all addresses, for all receivers and networks - // 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. pub static ref FUNDING_STREAM_ADDRESSES: HashMap>> = { let mut addresses_by_network = HashMap::with_capacity(2); @@ -132,6 +131,16 @@ lazy_static! { 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 }; } diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 363c05717a0..4e49698b747 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -399,6 +399,7 @@ lazy_static! { hash_map.insert(NetworkKind::Mainnet, Version::min_specified_for_upgrade(&Mainnet, Nu5)); hash_map.insert(NetworkKind::Testnet, Version::min_specified_for_upgrade(&Network::new_default_testnet(), Nu5)); + hash_map.insert(NetworkKind::Regtest, Version::min_specified_for_upgrade(&Network::new_regtest(None), Nu5)); hash_map }; diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 0951a869e1d..5b67f95576c 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3127,3 +3127,14 @@ async fn validate_regtest_genesis_block() { "validated block hash should match network genesis hash" ) } + +/// Test successful `getblocktemplate` and `submitblock` RPC calls on Regtest on Canopy. +/// +/// See [`common::regtest::submit_blocks`] for more information. +// TODO: Test this with an NU5 activation height too once config can be serialized. +#[tokio::test] +#[cfg(feature = "getblocktemplate-rpcs")] +async fn regtest_submit_blocks() -> Result<()> { + common::regtest::submit_blocks_test().await?; + Ok(()) +} diff --git a/zebrad/tests/common/mod.rs b/zebrad/tests/common/mod.rs index 70383124834..3ff3f63db3a 100644 --- a/zebrad/tests/common/mod.rs +++ b/zebrad/tests/common/mod.rs @@ -24,5 +24,8 @@ pub mod checkpoints; #[cfg(feature = "getblocktemplate-rpcs")] pub mod get_block_template_rpcs; +#[cfg(feature = "getblocktemplate-rpcs")] +pub mod regtest; + #[cfg(feature = "shielded-scan")] pub mod shielded_scan; diff --git a/zebrad/tests/common/regtest.rs b/zebrad/tests/common/regtest.rs new file mode 100644 index 00000000000..730bb391e3a --- /dev/null +++ b/zebrad/tests/common/regtest.rs @@ -0,0 +1,87 @@ +//! Test submitblock RPC method on Regtest. +//! +//! This test will get block templates via the `getblocktemplate` RPC method and submit them as new blocks +//! via the `submitblock` RPC method on Regtest. + +use std::{net::SocketAddr, time::Duration}; + +use color_eyre::eyre::{Context, Result}; +use tracing::*; + +use zebra_chain::{parameters::Network, serialization::ZcashSerialize}; +use zebra_node_services::rpc_client::RpcRequestClient; +use zebra_rpc::methods::get_block_template_rpcs::get_block_template::{ + proposal::TimeSource, proposal_block_from_template, GetBlockTemplate, +}; +use zebra_test::args; + +use crate::common::{ + config::{random_known_rpc_port_config, testdir}, + launch::ZebradTestDirExt, +}; + +/// Number of blocks that should be submitted before the test is considered successful. +const NUM_BLOCKS_TO_SUBMIT: usize = 200; + +pub(crate) async fn submit_blocks_test() -> Result<()> { + let _init_guard = zebra_test::init(); + info!("starting regtest submit_blocks test"); + + let network = Network::new_regtest(None); + let mut config = random_known_rpc_port_config(false, &network)?; + let rpc_address = config.rpc.listen_addr.unwrap(); + + let mut zebrad = testdir()? + .with_config(&mut config)? + .spawn_child(args!["start"])?; + + info!("waiting for zebrad to start"); + + tokio::time::sleep(Duration::from_secs(30)).await; + + info!("attempting to submit blocks"); + submit_blocks(rpc_address).await?; + + zebrad.kill(false)?; + + let output = zebrad.wait_with_output()?; + let output = output.assert_failure()?; + + // [Note on port conflict](#Note on port conflict) + output + .assert_was_killed() + .wrap_err("Possible port conflict. Are there other acceptance tests running?") +} + +/// Get block templates and submit blocks +async fn submit_blocks(rpc_address: SocketAddr) -> Result<()> { + let client = RpcRequestClient::new(rpc_address); + + for _ in 0..NUM_BLOCKS_TO_SUBMIT { + let block_template: GetBlockTemplate = client + .json_result_from_call("getblocktemplate", "[]".to_string()) + .await + .expect("response should be success output with a serialized `GetBlockTemplate`"); + + let block_data = hex::encode( + proposal_block_from_template(&block_template, TimeSource::default())? + .zcash_serialize_to_vec()?, + ); + + let submit_block_response = client + .text_from_call("submitblock", format!(r#"["{block_data}"]"#)) + .await?; + + let was_submission_successful = submit_block_response.contains(r#""result":null"#); + + info!(was_submission_successful, "submitted block"); + + // Check that the block was validated and committed. + assert!( + submit_block_response.contains(r#""result":null"#), + "unexpected response from submitblock RPC, should be null, was: {submit_block_response}" + ); + } + + Ok(()) +} From 5564c38a81c26341151f69c45bb7c48a9e25d60b Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 21:36:51 -0400 Subject: [PATCH 09/19] - Updates median time past and difficulty checks to use fewer than 11/17/28 blocks --- zebra-chain/src/work/difficulty.rs | 2 +- .../src/methods/get_block_template_rpcs.rs | 4 +- zebra-state/src/service/check/difficulty.rs | 55 +++++++++++-------- zebra-state/src/service/read/difficulty.rs | 36 ++++++------ zebra-state/src/service/read/find.rs | 19 +++---- zebrad/tests/common/regtest.rs | 1 + 6 files changed, 61 insertions(+), 56 deletions(-) diff --git a/zebra-chain/src/work/difficulty.rs b/zebra-chain/src/work/difficulty.rs index c13210923f8..8c9908b0408 100644 --- a/zebra-chain/src/work/difficulty.rs +++ b/zebra-chain/src/work/difficulty.rs @@ -699,7 +699,7 @@ impl ParameterDifficulty for Network { /* 2^243 - 1 */ Network::Mainnet => (U256::one() << 243) - 1, /* 2^251 - 1 */ - // TODO: Add a `target_difficulty_limit` field to `testnet::Parameters` to return here. + // TODO: Add a `target_difficulty_limit` field to `testnet::Parameters` to return here. (`U256::from_big_endian(&[0x0f].repeat(8))` for Regtest) Network::Testnet(_params) => (U256::one() << 251) - 1, }; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 26edc82117c..77267d006f0 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -638,7 +638,9 @@ 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) + // 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())?; } diff --git a/zebra-state/src/service/check/difficulty.rs b/zebra-state/src/service/check/difficulty.rs index 8c48e25fba1..85e214ecf72 100644 --- a/zebra-state/src/service/check/difficulty.rs +++ b/zebra-state/src/service/check/difficulty.rs @@ -64,14 +64,14 @@ pub(crate) struct AdjustedDifficulty { /// The `header.difficulty_threshold`s from the previous /// `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks, in reverse height /// order. - relevant_difficulty_thresholds: [CompactDifficulty; POW_ADJUSTMENT_BLOCK_SPAN], + relevant_difficulty_thresholds: Vec, /// The `header.time`s from the previous /// `PoWAveragingWindow + PoWMedianBlockSpan` (28) blocks, in reverse height /// order. /// /// Only the first and last `PoWMedianBlockSpan` times are used. Times /// `11..=16` are ignored. - relevant_times: [DateTime; POW_ADJUSTMENT_BLOCK_SPAN], + relevant_times: Vec>, } impl AdjustedDifficulty { @@ -138,13 +138,6 @@ impl AdjustedDifficulty { .take(POW_ADJUSTMENT_BLOCK_SPAN) .unzip::<_, _, Vec<_>, Vec<_>>(); - let relevant_difficulty_thresholds = relevant_difficulty_thresholds - .try_into() - .expect("not enough context: difficulty adjustment needs at least 28 (PoWAveragingWindow + PoWMedianBlockSpan) headers"); - let relevant_times = relevant_times - .try_into() - .expect("not enough context: difficulty adjustment needs at least 28 (PoWAveragingWindow + PoWMedianBlockSpan) headers"); - AdjustedDifficulty { candidate_time: candidate_header_time, candidate_height, @@ -226,7 +219,11 @@ impl AdjustedDifficulty { // specification is unreachable. let averaging_window_thresholds = - &self.relevant_difficulty_thresholds[0..POW_AVERAGING_WINDOW]; + if self.relevant_difficulty_thresholds.len() >= POW_AVERAGING_WINDOW { + &self.relevant_difficulty_thresholds[0..POW_AVERAGING_WINDOW] + } else { + return self.network.target_difficulty_limit(); + }; // Since the PoWLimits are `2^251 βˆ’ 1` for Testnet, and `2^243 βˆ’ 1` for // Mainnet, the sum of 17 `ExpandedDifficulty` will be less than or equal @@ -297,10 +294,14 @@ impl AdjustedDifficulty { fn median_timespan(&self) -> Duration { let newer_median = self.median_time_past(); - let older_times: [DateTime; POW_MEDIAN_BLOCK_SPAN] = self.relevant_times - [POW_AVERAGING_WINDOW..] - .try_into() - .expect("relevant times is the correct length"); + let older_times: Vec<_> = self + .relevant_times + .iter() + .rev() + .cloned() + .take(POW_MEDIAN_BLOCK_SPAN) + .collect(); + let older_median = AdjustedDifficulty::median_time(older_times); // `ActualTimespan` in the Zcash specification @@ -314,22 +315,30 @@ impl AdjustedDifficulty { /// Zcash specification. (These functions are identical, but they are /// specified in slightly different ways.) pub fn median_time_past(&self) -> DateTime { - let median_times: [DateTime; POW_MEDIAN_BLOCK_SPAN] = self.relevant_times - [0..POW_MEDIAN_BLOCK_SPAN] - .try_into() - .expect("relevant times is the correct length"); + let median_times: Vec> = self + .relevant_times + .iter() + .take(POW_MEDIAN_BLOCK_SPAN) + .cloned() + .collect(); AdjustedDifficulty::median_time(median_times) } /// Calculate the median of the `median_block_span_times`: the `time`s from a - /// slice of `PoWMedianBlockSpan` (11) blocks in the relevant chain. + /// Vec of `PoWMedianBlockSpan` (11) or fewer blocks in the relevant chain. /// /// Implements `MedianTime` from the Zcash specification. - pub(crate) fn median_time( - mut median_block_span_times: [DateTime; POW_MEDIAN_BLOCK_SPAN], - ) -> DateTime { + /// + /// # Panics + /// + /// If provided an empty Vec + pub(crate) fn median_time(mut median_block_span_times: Vec>) -> DateTime { median_block_span_times.sort_unstable(); - median_block_span_times[POW_MEDIAN_BLOCK_SPAN / 2] + + // > median(𝑆) := sorted(𝑆)ceiling((length(𝑆)+1)/2) + // , section 7.7.3, Difficulty Adjustment (p. 132) + let median_idx = (POW_MEDIAN_BLOCK_SPAN / 2).min(median_block_span_times.len() / 2); + median_block_span_times[median_idx] } } diff --git a/zebra-state/src/service/read/difficulty.rs b/zebra-state/src/service/read/difficulty.rs index 16d7c67b73f..cbb0e519157 100644 --- a/zebra-state/src/service/read/difficulty.rs +++ b/zebra-state/src/service/read/difficulty.rs @@ -152,15 +152,7 @@ pub fn solution_rate( fn best_relevant_chain_and_history_tree( non_finalized_state: &NonFinalizedState, db: &ZebraDb, -) -> Result< - ( - Height, - block::Hash, - [Arc; POW_ADJUSTMENT_BLOCK_SPAN], - Arc, - ), - BoxError, -> { +) -> Result<(Height, block::Hash, Vec>, Arc), BoxError> { let state_tip_before_queries = read::best_tip(non_finalized_state, db).ok_or_else(|| { BoxError::from("Zebra's state is empty, wait until it syncs to the chain tip") })?; @@ -171,9 +163,10 @@ fn best_relevant_chain_and_history_tree( .into_iter() .take(POW_ADJUSTMENT_BLOCK_SPAN) .collect(); - let best_relevant_chain = best_relevant_chain.try_into().map_err(|_error| { - "Zebra's state only has a few blocks, wait until it syncs to the chain tip" - })?; + + if best_relevant_chain.is_empty() { + return Err("missing genesis block, wait until it is committed".into()); + }; let history_tree = history_tree( non_finalized_state.best_chain(), @@ -206,7 +199,7 @@ fn best_relevant_chain_and_history_tree( /// /// See [`get_block_template_chain_info()`] for details. fn difficulty_time_and_history_tree( - relevant_chain: [Arc; POW_ADJUSTMENT_BLOCK_SPAN], + relevant_chain: Vec>, tip_height: Height, tip_hash: block::Hash, network: &Network, @@ -223,10 +216,11 @@ fn difficulty_time_and_history_tree( // > the median-time-past of that block. // https://zips.z.cash/protocol/protocol.pdf#blockheader let median_time_past = calculate_median_time_past( - relevant_chain[0..POW_MEDIAN_BLOCK_SPAN] - .to_vec() - .try_into() - .expect("slice is correct size"), + relevant_chain + .iter() + .take(POW_MEDIAN_BLOCK_SPAN) + .cloned() + .collect(), ); let min_time = median_time_past @@ -307,9 +301,13 @@ fn adjust_difficulty_and_time_for_testnet( .try_into() .expect("valid blocks have in-range times"); - let minimum_difficulty_spacing = + let Some(minimum_difficulty_spacing) = NetworkUpgrade::minimum_difficulty_spacing_for_height(network, previous_block_height) - .expect("just checked testnet, and the RPC returns an error for low heights"); + else { + // Returns early if the testnet minimum difficulty consensus rule is not active + return; + }; + let minimum_difficulty_spacing: Duration32 = minimum_difficulty_spacing .try_into() .expect("small positive values are in-range"); diff --git a/zebra-state/src/service/read/find.rs b/zebra-state/src/service/read/find.rs index 44491876ec9..187510c9b67 100644 --- a/zebra-state/src/service/read/find.rs +++ b/zebra-state/src/service/read/find.rs @@ -596,7 +596,7 @@ pub fn next_median_time_past( fn best_relevant_chain( non_finalized_state: &NonFinalizedState, db: &ZebraDb, -) -> Result<[Arc; POW_MEDIAN_BLOCK_SPAN], BoxError> { +) -> Result>, BoxError> { let state_tip_before_queries = read::best_tip(non_finalized_state, db).ok_or_else(|| { BoxError::from("Zebra's state is empty, wait until it syncs to the chain tip") })?; @@ -607,9 +607,10 @@ fn best_relevant_chain( .into_iter() .take(POW_MEDIAN_BLOCK_SPAN) .collect(); - let best_relevant_chain = best_relevant_chain.try_into().map_err(|_error| { - "Zebra's state only has a few blocks, wait until it syncs to the chain tip" - })?; + + if best_relevant_chain.is_empty() { + return Err("missing genesis block, wait until it is committed".into()); + }; let state_tip_after_queries = read::best_tip(non_finalized_state, db).expect("already checked for an empty tip"); @@ -628,9 +629,7 @@ fn best_relevant_chain( /// The `relevant_chain` has blocks in reverse height order. /// /// See [`next_median_time_past()`] for details. -pub(crate) fn calculate_median_time_past( - relevant_chain: [Arc; POW_MEDIAN_BLOCK_SPAN], -) -> DateTime32 { +pub(crate) fn calculate_median_time_past(relevant_chain: Vec>) -> DateTime32 { let relevant_data: Vec> = relevant_chain .iter() .map(|block| block.header.time) @@ -640,11 +639,7 @@ pub(crate) fn calculate_median_time_past( // > preceding PoWMedianBlockSpan blocks (or all preceding blocks if there are fewer than // > PoWMedianBlockSpan). The median-time-past of a genesis block is not defined. // https://zips.z.cash/protocol/protocol.pdf#blockheader - let median_time_past = AdjustedDifficulty::median_time( - relevant_data - .try_into() - .expect("always has the correct length due to function argument type"), - ); + let median_time_past = AdjustedDifficulty::median_time(relevant_data); DateTime32::try_from(median_time_past).expect("valid blocks have in-range times") } diff --git a/zebrad/tests/common/regtest.rs b/zebrad/tests/common/regtest.rs index 730bb391e3a..2fe9b5fdcaf 100644 --- a/zebrad/tests/common/regtest.rs +++ b/zebrad/tests/common/regtest.rs @@ -29,6 +29,7 @@ pub(crate) async fn submit_blocks_test() -> Result<()> { let network = Network::new_regtest(None); let mut config = random_known_rpc_port_config(false, &network)?; + config.mempool.debug_enable_at_height = Some(0); let rpc_address = config.rpc.listen_addr.unwrap(); let mut zebrad = testdir()? From 445b8f1596f0d0d86c59353ea8b21c076c5353ff Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 22:51:13 -0400 Subject: [PATCH 10/19] uses SLOW_START_INTERVAL of 0 if PoW is disabled, adds TODOs --- zebra-consensus/src/block/check.rs | 12 ++++++++++-- zebra-consensus/src/block/subsidy/general.rs | 20 +++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 797df65c5b9..e40723f18c5 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -157,10 +157,18 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr .activation_height(network) .expect("Canopy activation height is known"); - if height < SLOW_START_INTERVAL { + // TODO: Add this as a field on `testnet::Parameters` instead of checking `disable_pow()`, this is 0 for Regtest in zcashd, + // see + let slow_start_interval = if network.disable_pow() { + Height(0) + } else { + SLOW_START_INTERVAL + }; + + if height < slow_start_interval { unreachable!( "unsupported block height: callers should handle blocks below {:?}", - SLOW_START_INTERVAL + slow_start_interval ) } else if halving_div.count_ones() != 1 { unreachable!("invalid halving divisor: the halving divisor must be a non-zero power of two") diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 5397a0ec735..5e13d653225 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -25,12 +25,20 @@ pub fn halving_divisor(height: Height, network: &Network) -> Option { .activation_height(network) .expect("blossom activation height should be available"); - if height < SLOW_START_SHIFT { + // TODO: Add this as a field on `testnet::Parameters` instead of checking `disable_pow()`, this is 0 for Regtest in zcashd, + // see + let slow_start_shift = if network.disable_pow() { + Height(0) + } else { + SLOW_START_SHIFT + }; + + if height < slow_start_shift { unreachable!( - "unsupported block height {height:?}: checkpoints should handle blocks below {SLOW_START_SHIFT:?}", + "unsupported block height {height:?}: checkpoints should handle blocks below {slow_start_shift:?}", ) } else if height < blossom_height { - let pre_blossom_height = height - SLOW_START_SHIFT; + let pre_blossom_height = height - slow_start_shift; let halving_shift = pre_blossom_height / PRE_BLOSSOM_HALVING_INTERVAL; let halving_div = 1u64 @@ -43,7 +51,7 @@ pub fn halving_divisor(height: Height, network: &Network) -> Option { Some(halving_div) } else { - let pre_blossom_height = blossom_height - SLOW_START_SHIFT; + let pre_blossom_height = blossom_height - slow_start_shift; let scaled_pre_blossom_height = pre_blossom_height * HeightDiff::from(BLOSSOM_POW_TARGET_SPACING_RATIO); @@ -77,7 +85,9 @@ pub fn block_subsidy(height: Height, network: &Network) -> Result + if height < SLOW_START_SHIFT && !network.disable_pow() { unreachable!( "unsupported block height {height:?}: callers should handle blocks below {SLOW_START_INTERVAL:?}", ) From 8fbf09e18be591f11fb262b8c0e2493e3a430ea4 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 23:23:11 -0400 Subject: [PATCH 11/19] Update getblocktemplate method to return reserved chain history activation root hash, uses Nu5 at height 1 Test passes. --- zebra-chain/src/block/commitment.rs | 2 +- zebra-chain/src/parameters/network.rs | 2 ++ zebra-chain/src/parameters/network/testnet.rs | 5 ++-- .../get_block_template.rs | 30 ++++++++++++------- zebra-state/src/service/check.rs | 2 +- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/zebra-chain/src/block/commitment.rs b/zebra-chain/src/block/commitment.rs index 3c88972b630..4c95d3a272d 100644 --- a/zebra-chain/src/block/commitment.rs +++ b/zebra-chain/src/block/commitment.rs @@ -115,7 +115,7 @@ impl Commitment { Ok(root) => Ok(FinalSaplingRoot(root)), _ => Err(InvalidSapingRootBytes), }, - Heartwood if Some(height) == Heartwood.activation_height(network) => { + Heartwood | Canopy | Nu5 if Some(height) == Heartwood.activation_height(network) => { if bytes == CHAIN_HISTORY_ACTIVATION_RESERVED { Ok(ChainHistoryActivationReserved) } else { diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index 2bbaf118819..094515a6408 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -264,6 +264,8 @@ impl Network { .expect("ZIP-212 grace period ends at a valid block height") } else { canopy_activation + .previous() + .expect("Canopy activation must be above Genesis height") } } diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 276eb3516ac..057e370b9fc 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -310,7 +310,7 @@ impl Parameters { /// Accepts a [`ConfiguredActivationHeights`]. /// /// Creates an instance of [`Parameters`] with `Regtest` values. - pub fn new_regtest(nu5_activation_height: Option) -> Self { + pub fn new_regtest(_nu5_activation_height: Option) -> Self { Self { network_name: "Regtest".to_string(), ..Self::build() @@ -324,8 +324,7 @@ impl Parameters { // Removes default Testnet activation heights if not configured, // most network upgrades are disabled by default for Regtest in zcashd .with_activation_heights(ConfiguredActivationHeights { - canopy: Some(1), - nu5: nu5_activation_height, + nu5: Some(1), ..Default::default() }) .finish() 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 beb8539de09..ce623e35ba3 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 @@ -10,11 +10,11 @@ use zebra_chain::{ block::{ self, merkle::{self, AuthDataRoot}, - Block, ChainHistoryBlockTxAuthCommitmentHash, Height, + Block, ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Height, }, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, - parameters::Network, + parameters::{Network, NetworkUpgrade}, serialization::ZcashDeserializeInto, transaction::{Transaction, UnminedTx, VerifiedUnminedTx}, transparent, @@ -305,7 +305,15 @@ pub fn generate_coinbase_and_roots( // Calculate block default roots // // TODO: move expensive root, hash, and tree cryptography to a rayon thread? - let default_roots = calculate_default_root_hashes(&coinbase_txn, mempool_txs, history_tree); + let chain_history_root = history_tree + .hash() + .or_else(|| { + (NetworkUpgrade::Heartwood.activation_height(network) == Some(height)) + .then_some([0; 32].into()) + }) + .expect("history tree can't be empty"); + let default_roots = + calculate_default_root_hashes(&coinbase_txn, mempool_txs, chain_history_root); let coinbase_txn = TransactionTemplate::from_coinbase(&coinbase_txn, miner_fee); @@ -434,16 +442,18 @@ fn combine_coinbase_outputs( pub fn calculate_default_root_hashes( coinbase_txn: &UnminedTx, mempool_txs: &[VerifiedUnminedTx], - history_tree: Arc, + chain_history_root: ChainHistoryMmrRootHash, ) -> DefaultRoots { let (merkle_root, auth_data_root) = calculate_transaction_roots(coinbase_txn, mempool_txs); - let chain_history_root = history_tree.hash().expect("history tree can't be empty"); - - let block_commitments_hash = ChainHistoryBlockTxAuthCommitmentHash::from_commitments( - &chain_history_root, - &auth_data_root, - ); + let block_commitments_hash = if chain_history_root == [0; 32].into() { + [0; 32].into() + } else { + ChainHistoryBlockTxAuthCommitmentHash::from_commitments( + &chain_history_root, + &auth_data_root, + ) + }; DefaultRoots { merkle_root, diff --git a/zebra-state/src/service/check.rs b/zebra-state/src/service/check.rs index 30144d8d3fe..9d0564a1d89 100644 --- a/zebra-state/src/service/check.rs +++ b/zebra-state/src/service/check.rs @@ -80,7 +80,7 @@ where .expect("valid blocks have a coinbase height"); check::height_one_more_than_parent_height(parent_height, semantically_verified.height)?; - if relevant_chain.len() < POW_ADJUSTMENT_BLOCK_SPAN { + if relevant_chain.is_empty() { // skip this check during tests if we don't have enough blocks in the chain // process_queued also checks the chain length, so we can skip this assertion during testing // (tests that want to check this code should use the correct number of blocks) From 97b5db89fe55e2ce9b98112a6335dccd1d3073c1 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 23:28:55 -0400 Subject: [PATCH 12/19] Updates test to expect NU5 as the default nu activation at Height(1) --- zebra-chain/src/block/commitment.rs | 2 +- zebra-chain/src/parameters/network/tests/vectors.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-chain/src/block/commitment.rs b/zebra-chain/src/block/commitment.rs index 4c95d3a272d..e36fffb7d11 100644 --- a/zebra-chain/src/block/commitment.rs +++ b/zebra-chain/src/block/commitment.rs @@ -115,7 +115,7 @@ impl Commitment { Ok(root) => Ok(FinalSaplingRoot(root)), _ => Err(InvalidSapingRootBytes), }, - Heartwood | Canopy | Nu5 if Some(height) == Heartwood.activation_height(network) => { + Heartwood | Nu5 if Some(height) == Heartwood.activation_height(network) => { if bytes == CHAIN_HISTORY_ACTIVATION_RESERVED { Ok(ChainHistoryActivationReserved) } else { diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index 595771e4a0c..49fc3d2be38 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -135,7 +135,7 @@ fn activates_network_upgrades_correctly() { let expected_default_regtest_activation_heights = &[ (Height(0), NetworkUpgrade::Genesis), - (Height(1), NetworkUpgrade::Canopy), + (Height(1), NetworkUpgrade::Nu5), ]; for (network, expected_activation_heights) in [ From 40951d5f94e679cc822a7c129f402a242d806752 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 29 Apr 2024 23:35:17 -0400 Subject: [PATCH 13/19] Removes invalid difficulty snapshot --- .../tests/snapshot/get_block_template_rpcs.rs | 16 ---------------- ..._difficulty_invalid_populated@testnet_10.snap | 10 ---------- 2 files changed, 26 deletions(-) delete mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty_invalid_populated@testnet_10.snap diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index cc7d0b908f7..7517b3e4dbe 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -467,11 +467,6 @@ pub async fn test_responses( snapshot_rpc_getdifficulty_valid("mock", mock_get_difficulty, &settings); - // Now use the populated state - - let populated_get_difficulty = get_block_template_rpc.get_difficulty().await; - snapshot_rpc_getdifficulty_invalid("populated", populated_get_difficulty, &settings); - // `z_listunifiedreceivers` let ua1 = String::from("u1l8xunezsvhq8fgzfl7404m450nwnd76zshscn6nfys7vyz2ywyh4cc5daaq0c7q2su5lqfh23sp7fkf3kt27ve5948mzpfdvckzaect2jtte308mkwlycj2u0eac077wu70vqcetkxf"); @@ -607,17 +602,6 @@ fn snapshot_rpc_getdifficulty_valid( }); } -/// Snapshot invalid `getdifficulty` response, using `cargo insta` and JSON serialization. -fn snapshot_rpc_getdifficulty_invalid( - variant: &'static str, - difficulty: Result, - settings: &insta::Settings, -) { - settings.bind(|| { - insta::assert_json_snapshot!(format!("get_difficulty_invalid_{variant}"), difficulty) - }); -} - /// Snapshot `snapshot_rpc_z_listunifiedreceivers` response, using `cargo insta` and JSON serialization. fn snapshot_rpc_z_listunifiedreceivers( variant: &'static str, diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty_invalid_populated@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty_invalid_populated@testnet_10.snap deleted file mode 100644 index 0216bc8b23a..00000000000 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty_invalid_populated@testnet_10.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs -expression: difficulty ---- -{ - "Err": { - "code": 0, - "message": "Zebra's state only has a few blocks, wait until it syncs to the chain tip" - } -} From e7a09aecf985756c3f0de47b4dd871a616004ff0 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 30 Apr 2024 17:04:38 -0400 Subject: [PATCH 14/19] fixes tests --- zebra-state/src/service/check.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/zebra-state/src/service/check.rs b/zebra-state/src/service/check.rs index 9d0564a1d89..e80ef0ffd0f 100644 --- a/zebra-state/src/service/check.rs +++ b/zebra-state/src/service/check.rs @@ -80,23 +80,25 @@ where .expect("valid blocks have a coinbase height"); check::height_one_more_than_parent_height(parent_height, semantically_verified.height)?; - if relevant_chain.is_empty() { - // skip this check during tests if we don't have enough blocks in the chain - // process_queued also checks the chain length, so we can skip this assertion during testing - // (tests that want to check this code should use the correct number of blocks) - // - // TODO: accept a NotReadyToBeCommitted error in those tests instead - #[cfg(test)] + // skip this check during tests if we don't have enough blocks in the chain + // process_queued also checks the chain length, so we can skip this assertion during testing + // (tests that want to check this code should use the correct number of blocks) + // + // TODO: accept a NotReadyToBeCommitted error in those tests instead + #[cfg(test)] + if relevant_chain.len() < POW_ADJUSTMENT_BLOCK_SPAN { return Ok(()); + } - // In production, blocks without enough context are invalid. - // - // The BlockVerifierRouter makes sure that the first 1 million blocks (or more) are - // checkpoint verified. The state queues and block write task make sure that blocks are - // committed in strict height order. But this function is only called on semantically - // verified blocks, so there will be at least 1 million blocks in the state when it is - // called. So this error should never happen. - #[cfg(not(test))] + // In production, blocks without enough context are invalid. + // + // The BlockVerifierRouter makes sure that the first 1 million blocks (or more) are + // checkpoint verified. The state queues and block write task make sure that blocks are + // committed in strict height order. But this function is only called on semantically + // verified blocks, so there will be at least 1 million blocks in the state when it is + // called. So this error should never happen. + #[cfg(not(test))] + if relevant_chain.is_empty() { return Err(ValidateContextError::NotReadyToBeCommitted); } From 855ad12f2495b7c03b21641fcb198c7573060bc6 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 30 Apr 2024 17:07:32 -0400 Subject: [PATCH 15/19] removes regtest NU5 activation height config field --- zebra-chain/src/parameters/network.rs | 4 ++-- zebra-chain/src/parameters/network/testnet.rs | 4 ++-- zebra-chain/src/parameters/network/tests/vectors.rs | 4 ++-- zebra-consensus/src/checkpoint/list/tests.rs | 2 +- zebra-network/src/config.rs | 5 +---- zebra-network/src/constants.rs | 2 +- zebrad/tests/acceptance.rs | 2 +- zebrad/tests/common/configs/v1.7.0.toml | 2 -- zebrad/tests/common/regtest.rs | 2 +- 9 files changed, 11 insertions(+), 16 deletions(-) diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index 094515a6408..98135d40ec8 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -165,8 +165,8 @@ impl Network { } /// Creates a new [`Network::Testnet`] with `Regtest` parameters and the provided network upgrade activation heights. - pub fn new_regtest(nu5_activation_height: Option) -> Self { - Self::new_configured_testnet(testnet::Parameters::new_regtest(nu5_activation_height)) + pub fn new_regtest() -> Self { + Self::new_configured_testnet(testnet::Parameters::new_regtest()) } /// Returns true if the network is the default Testnet, or false otherwise. diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 057e370b9fc..aa1b1e192ff 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -310,7 +310,7 @@ impl Parameters { /// Accepts a [`ConfiguredActivationHeights`]. /// /// Creates an instance of [`Parameters`] with `Regtest` values. - pub fn new_regtest(_nu5_activation_height: Option) -> Self { + pub fn new_regtest() -> Self { Self { network_name: "Regtest".to_string(), ..Self::build() @@ -347,7 +347,7 @@ impl Parameters { hrp_sapling_extended_full_viewing_key, hrp_sapling_payment_address, disable_pow, - } = Self::new_regtest(None); + } = Self::new_regtest(); self.network_name == network_name && self.genesis_hash == genesis_hash diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index 49fc3d2be38..0ac782c11ff 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -142,7 +142,7 @@ fn activates_network_upgrades_correctly() { (Network::Mainnet, MAINNET_ACTIVATION_HEIGHTS), (Network::new_default_testnet(), TESTNET_ACTIVATION_HEIGHTS), ( - Network::new_regtest(Default::default()), + Network::new_regtest(), expected_default_regtest_activation_heights, ), ] { @@ -193,7 +193,7 @@ fn check_configured_network_name() { "Mainnet should be displayed as 'Mainnet'" ); assert_eq!( - Network::new_regtest(Default::default()).to_string(), + Network::new_regtest().to_string(), "Regtest", "Regtest should be displayed as 'Regtest'" ); diff --git a/zebra-consensus/src/checkpoint/list/tests.rs b/zebra-consensus/src/checkpoint/list/tests.rs index 1df05327d64..b266d99ffec 100644 --- a/zebra-consensus/src/checkpoint/list/tests.rs +++ b/zebra-consensus/src/checkpoint/list/tests.rs @@ -237,7 +237,7 @@ fn checkpoint_list_load_hard_coded() -> Result<(), BoxError> { let _ = Mainnet.checkpoint_list(); let _ = Network::new_default_testnet().checkpoint_list(); - let _ = Network::new_regtest(Default::default()).checkpoint_list(); + let _ = Network::new_regtest().checkpoint_list(); Ok(()) } diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index ef78483683c..23763b7655a 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -637,7 +637,6 @@ impl<'de> Deserialize<'de> for Config { listen_addr: String, network: NetworkKind, testnet_parameters: Option, - regtest_nu5_activation_height: Option, initial_mainnet_peers: IndexSet, initial_testnet_peers: IndexSet, cache_dir: CacheDir, @@ -654,7 +653,6 @@ impl<'de> Deserialize<'de> for Config { listen_addr: "0.0.0.0".to_string(), network: Default::default(), testnet_parameters: None, - regtest_nu5_activation_height: None, initial_mainnet_peers: config.initial_mainnet_peers, initial_testnet_peers: config.initial_testnet_peers, cache_dir: config.cache_dir, @@ -669,7 +667,6 @@ impl<'de> Deserialize<'de> for Config { listen_addr, network: network_kind, testnet_parameters, - regtest_nu5_activation_height, initial_mainnet_peers, initial_testnet_peers, cache_dir, @@ -698,7 +695,7 @@ impl<'de> Deserialize<'de> for Config { let network = match (network_kind, testnet_parameters) { (NetworkKind::Mainnet, _) => Network::Mainnet, (NetworkKind::Testnet, None) => Network::new_default_testnet(), - (NetworkKind::Regtest, _) => Network::new_regtest(regtest_nu5_activation_height), + (NetworkKind::Regtest, _) => Network::new_regtest(), ( NetworkKind::Testnet, Some(DTestnetParameters { diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 4e49698b747..3b015a6e210 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -399,7 +399,7 @@ lazy_static! { hash_map.insert(NetworkKind::Mainnet, Version::min_specified_for_upgrade(&Mainnet, Nu5)); hash_map.insert(NetworkKind::Testnet, Version::min_specified_for_upgrade(&Network::new_default_testnet(), Nu5)); - hash_map.insert(NetworkKind::Regtest, Version::min_specified_for_upgrade(&Network::new_regtest(None), Nu5)); + hash_map.insert(NetworkKind::Regtest, Version::min_specified_for_upgrade(&Network::new_regtest(), Nu5)); hash_map }; diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 5b67f95576c..d8e2edf6c67 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3107,7 +3107,7 @@ async fn scan_task_commands() -> Result<()> { async fn validate_regtest_genesis_block() { let _init_guard = zebra_test::init(); - let network = Network::new_regtest(Default::default()); + let network = Network::new_regtest(); let state = zebra_state::init_test(&network); let ( block_verifier_router, diff --git a/zebrad/tests/common/configs/v1.7.0.toml b/zebrad/tests/common/configs/v1.7.0.toml index a2f0c36602d..0cfa685bc65 100644 --- a/zebrad/tests/common/configs/v1.7.0.toml +++ b/zebrad/tests/common/configs/v1.7.0.toml @@ -60,8 +60,6 @@ max_connections_per_ip = 1 network = "Regtest" peerset_initial_target_size = 25 -regtest_nu5_activation_height = 1 - [network.testnet_parameters] network_name = "ConfiguredTestnet_1" diff --git a/zebrad/tests/common/regtest.rs b/zebrad/tests/common/regtest.rs index 2fe9b5fdcaf..551508de45c 100644 --- a/zebrad/tests/common/regtest.rs +++ b/zebrad/tests/common/regtest.rs @@ -27,7 +27,7 @@ pub(crate) async fn submit_blocks_test() -> Result<()> { let _init_guard = zebra_test::init(); info!("starting regtest submit_blocks test"); - let network = Network::new_regtest(None); + let network = Network::new_regtest(); let mut config = random_known_rpc_port_config(false, &network)?; config.mempool.debug_enable_at_height = Some(0); let rpc_address = config.rpc.listen_addr.unwrap(); From 8128a07021d552788d31cb4b25c98386a12ed7e1 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 30 Apr 2024 22:43:28 -0400 Subject: [PATCH 16/19] Apply suggestions from code review --- zebra-consensus/src/block/subsidy/general.rs | 2 +- zebra-state/src/service/check.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 5e13d653225..ab3c1b1a0d2 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -87,7 +87,7 @@ pub fn block_subsidy(height: Height, network: &Network) -> Result - if height < SLOW_START_SHIFT && !network.disable_pow() { + if height < SLOW_START_INTERVAL && !network.disable_pow() { unreachable!( "unsupported block height {height:?}: callers should handle blocks below {SLOW_START_INTERVAL:?}", ) diff --git a/zebra-state/src/service/check.rs b/zebra-state/src/service/check.rs index e80ef0ffd0f..8d9b4e1b264 100644 --- a/zebra-state/src/service/check.rs +++ b/zebra-state/src/service/check.rs @@ -96,7 +96,7 @@ where // checkpoint verified. The state queues and block write task make sure that blocks are // committed in strict height order. But this function is only called on semantically // verified blocks, so there will be at least 1 million blocks in the state when it is - // called. So this error should never happen. + // called. So this error should never happen on Mainnet or the default Testnet. #[cfg(not(test))] if relevant_chain.is_empty() { return Err(ValidateContextError::NotReadyToBeCommitted); From aeebad7fff2c4a96ab4657931e91bf834d62b04a Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 1 May 2024 14:33:22 -0400 Subject: [PATCH 17/19] Update zebra-state/src/service/check/difficulty.rs Co-authored-by: Marek --- zebra-state/src/service/check/difficulty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-state/src/service/check/difficulty.rs b/zebra-state/src/service/check/difficulty.rs index 85e214ecf72..21c13160329 100644 --- a/zebra-state/src/service/check/difficulty.rs +++ b/zebra-state/src/service/check/difficulty.rs @@ -336,7 +336,7 @@ impl AdjustedDifficulty { pub(crate) fn median_time(mut median_block_span_times: Vec>) -> DateTime { median_block_span_times.sort_unstable(); - // > median(𝑆) := sorted(𝑆)ceiling((length(𝑆)+1)/2) + // > median(𝑆) := sorted(𝑆)_{ceiling((length(𝑆)+1)/2)} // , section 7.7.3, Difficulty Adjustment (p. 132) let median_idx = (POW_MEDIAN_BLOCK_SPAN / 2).min(median_block_span_times.len() / 2); median_block_span_times[median_idx] From 5be5ee1aeb7e2889bed34b87cd30810a5136e29d Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 1 May 2024 14:34:17 -0400 Subject: [PATCH 18/19] Update zebra-state/src/service/check/difficulty.rs --- zebra-state/src/service/check/difficulty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-state/src/service/check/difficulty.rs b/zebra-state/src/service/check/difficulty.rs index 21c13160329..c4724e1ccda 100644 --- a/zebra-state/src/service/check/difficulty.rs +++ b/zebra-state/src/service/check/difficulty.rs @@ -338,7 +338,7 @@ impl AdjustedDifficulty { // > median(𝑆) := sorted(𝑆)_{ceiling((length(𝑆)+1)/2)} // , section 7.7.3, Difficulty Adjustment (p. 132) - let median_idx = (POW_MEDIAN_BLOCK_SPAN / 2).min(median_block_span_times.len() / 2); + let median_idx = median_block_span_times.len() / 2; median_block_span_times[median_idx] } } From 2ad072a19f1713090bbbaccabb5d3a7d381a55f1 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 1 May 2024 19:43:20 -0400 Subject: [PATCH 19/19] Updates docs/comments, renames an argument --- zebra-chain/src/block/commitment.rs | 4 ++++ .../get_block_template_rpcs/get_block_template.rs | 6 +++--- zebra-state/src/service/check.rs | 14 ++++++++++---- zebra-state/src/service/check/difficulty.rs | 3 ++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/zebra-chain/src/block/commitment.rs b/zebra-chain/src/block/commitment.rs index e36fffb7d11..03acf350349 100644 --- a/zebra-chain/src/block/commitment.rs +++ b/zebra-chain/src/block/commitment.rs @@ -115,6 +115,10 @@ impl Commitment { Ok(root) => Ok(FinalSaplingRoot(root)), _ => Err(InvalidSapingRootBytes), }, + // NetworkUpgrade::current() returns the latest network upgrade that's activated at the provided height, so + // on Regtest for heights above height 0, it returns NU5, and it's possible for the current network upgrade + // to be NU5 (or Canopy, or any network upgrade above Heartwood) at the Heartwood activation height. + // TODO: Check Canopy too once Zebra can construct Canopy block templates. Heartwood | Nu5 if Some(height) == Heartwood.activation_height(network) => { if bytes == CHAIN_HISTORY_ACTIVATION_RESERVED { Ok(ChainHistoryActivationReserved) 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 ce623e35ba3..62026b1bfad 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 @@ -284,7 +284,7 @@ where /// in the `getblocktemplate` RPC. pub fn generate_coinbase_and_roots( network: &Network, - height: Height, + block_template_height: Height, miner_address: &transparent::Address, mempool_txs: &[VerifiedUnminedTx], history_tree: Arc, @@ -295,7 +295,7 @@ pub fn generate_coinbase_and_roots( let miner_fee = calculate_miner_fee(mempool_txs); let coinbase_txn = generate_coinbase_transaction( network, - height, + block_template_height, miner_address, miner_fee, like_zcashd, @@ -308,7 +308,7 @@ pub fn generate_coinbase_and_roots( let chain_history_root = history_tree .hash() .or_else(|| { - (NetworkUpgrade::Heartwood.activation_height(network) == Some(height)) + (NetworkUpgrade::Heartwood.activation_height(network) == Some(block_template_height)) .then_some([0; 32].into()) }) .expect("history tree can't be empty"); diff --git a/zebra-state/src/service/check.rs b/zebra-state/src/service/check.rs index 8d9b4e1b264..b20ca0fd4c5 100644 --- a/zebra-state/src/service/check.rs +++ b/zebra-state/src/service/check.rs @@ -46,10 +46,6 @@ pub(crate) use difficulty::AdjustedDifficulty; /// /// The relevant chain is an iterator over the ancestors of `block`, starting /// with its parent block. -/// -/// # Panics -/// -/// If the state contains less than 28 ([`POW_ADJUSTMENT_BLOCK_SPAN`]) blocks. #[tracing::instrument(skip(semantically_verified, finalized_tip_height, relevant_chain))] pub(crate) fn block_is_valid_for_recent_chain( semantically_verified: &SemanticallyVerifiedBlock, @@ -97,6 +93,16 @@ where // committed in strict height order. But this function is only called on semantically // verified blocks, so there will be at least 1 million blocks in the state when it is // called. So this error should never happen on Mainnet or the default Testnet. + // + // It's okay to use a relevant chain of fewer than `POW_ADJUSTMENT_BLOCK_SPAN` blocks, because + // the MedianTime function uses height 0 if passed a negative height by the ActualTimespan function: + // > ActualTimespan(height : N) := MedianTime(height) βˆ’ MedianTime(height βˆ’ PoWAveragingWindow) + // > MedianTime(height : N) := median([[ nTime(𝑖) for 𝑖 from max(0, height βˆ’ PoWMedianBlockSpan) up to height βˆ’ 1 ]]) + // and the MeanTarget function only requires the past `PoWAveragingWindow` (17) blocks for heights above 17, + // > PoWLimit, if height ≀ PoWAveragingWindow + // > ([ToTarget(nBits(𝑖)) for 𝑖 from heightβˆ’PoWAveragingWindow up to heightβˆ’1]) otherwise + // + // See the 'Difficulty Adjustment' section (page 132) in the Zcash specification. #[cfg(not(test))] if relevant_chain.is_empty() { return Err(ValidateContextError::NotReadyToBeCommitted); diff --git a/zebra-state/src/service/check/difficulty.rs b/zebra-state/src/service/check/difficulty.rs index c4724e1ccda..44069685a51 100644 --- a/zebra-state/src/service/check/difficulty.rs +++ b/zebra-state/src/service/check/difficulty.rs @@ -26,7 +26,8 @@ pub const POW_MEDIAN_BLOCK_SPAN: usize = 11; /// The overall block span used for adjusting Zcash block difficulty. /// -/// `PoWAveragingWindow + PoWMedianBlockSpan` in the Zcash specification. +/// `PoWAveragingWindow + PoWMedianBlockSpan` in the Zcash specification based on +/// > ActualTimespan(height : N) := MedianTime(height) βˆ’ MedianTime(height βˆ’ PoWAveragingWindow) pub const POW_ADJUSTMENT_BLOCK_SPAN: usize = POW_AVERAGING_WINDOW + POW_MEDIAN_BLOCK_SPAN; /// The damping factor for median timespan variance.