diff --git a/zebra-chain/src/parameters/arbitrary.rs b/zebra-chain/src/parameters/arbitrary.rs index 12440946d62..63e678b3dc1 100644 --- a/zebra-chain/src/parameters/arbitrary.rs +++ b/zebra-chain/src/parameters/arbitrary.rs @@ -2,7 +2,7 @@ use proptest::prelude::*; -use super::NetworkUpgrade; +use super::{Network, NetworkUpgrade}; impl NetworkUpgrade { /// Generates network upgrades. @@ -32,3 +32,13 @@ impl NetworkUpgrade { .boxed() } } + +impl Arbitrary for Network { + type Parameters = (); + + fn arbitrary_with(_args: ()) -> Self::Strategy { + prop_oneof![Just(Self::Mainnet), Just(Self::new_default_testnet())].boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index 8c5ecaf9ee5..47bcf68e88c 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -4,18 +4,15 @@ use std::{fmt, str::FromStr, sync::Arc}; use thiserror::Error; -use zcash_primitives::constants; +use zcash_primitives::{consensus as zp_consensus, constants as zp_constants}; use crate::{ block::{self, Height, HeightDiff}, - parameters::NetworkUpgrade::Canopy, + parameters::NetworkUpgrade, }; pub mod testnet; -#[cfg(any(test, feature = "proptest-impl"))] -use proptest_derive::Arbitrary; - #[cfg(test)] mod tests; @@ -81,7 +78,6 @@ impl From for NetworkKind { /// An enum describing the possible network choices. #[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] #[serde(into = "NetworkKind")] pub enum Network { /// The production mainnet. @@ -98,8 +94,8 @@ impl NetworkKind { /// pay-to-public-key-hash payment addresses for the network. pub fn b58_pubkey_address_prefix(self) -> [u8; 2] { match self { - Self::Mainnet => constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX, - Self::Testnet | Self::Regtest => constants::testnet::B58_PUBKEY_ADDRESS_PREFIX, + Self::Mainnet => zp_constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX, + Self::Testnet | Self::Regtest => zp_constants::testnet::B58_PUBKEY_ADDRESS_PREFIX, } } @@ -107,8 +103,8 @@ impl NetworkKind { /// payment addresses for the network. pub fn b58_script_address_prefix(self) -> [u8; 2] { match self { - Self::Mainnet => constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX, - Self::Testnet | Self::Regtest => constants::testnet::B58_SCRIPT_ADDRESS_PREFIX, + Self::Mainnet => zp_constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX, + Self::Testnet | Self::Regtest => zp_constants::testnet::B58_SCRIPT_ADDRESS_PREFIX, } } @@ -231,7 +227,7 @@ impl Network { // // See the `ZIP_212_GRACE_PERIOD_DURATION` documentation for more information. - let canopy_activation = Canopy + let canopy_activation = NetworkUpgrade::Canopy .activation_height(self) .expect("Canopy activation height must be present for both networks"); @@ -279,3 +275,70 @@ impl FromStr for Network { #[derive(Clone, Debug, Error)] #[error("Invalid network: {0}")] pub struct InvalidNetworkError(String); + +impl zp_consensus::Parameters for Network { + fn activation_height( + &self, + nu: zcash_primitives::consensus::NetworkUpgrade, + ) -> Option { + let target_nu = match nu { + zp_consensus::NetworkUpgrade::Overwinter => NetworkUpgrade::Overwinter, + zp_consensus::NetworkUpgrade::Sapling => NetworkUpgrade::Sapling, + zp_consensus::NetworkUpgrade::Blossom => NetworkUpgrade::Blossom, + zp_consensus::NetworkUpgrade::Heartwood => NetworkUpgrade::Heartwood, + zp_consensus::NetworkUpgrade::Canopy => NetworkUpgrade::Canopy, + zp_consensus::NetworkUpgrade::Nu5 => NetworkUpgrade::Nu5, + }; + + // Heights are hard-coded below Height::MAX or checked when the config is parsed. + target_nu + .activation_height(self) + .map(|Height(h)| zp_consensus::BlockHeight::from_u32(h)) + } + + fn coin_type(&self) -> u32 { + match self { + Network::Mainnet => zp_constants::mainnet::COIN_TYPE, + // The regtest cointype reuses the testnet cointype, + // See + Network::Testnet(_) => zp_constants::testnet::COIN_TYPE, + } + } + + fn address_network(&self) -> Option { + match self { + Network::Mainnet => Some(zcash_address::Network::Main), + // TODO: Check if network is `Regtest` first, and if it is, return `zcash_address::Network::Regtest` + Network::Testnet(_params) => Some(zcash_address::Network::Test), + } + } + + fn hrp_sapling_extended_spending_key(&self) -> &str { + match self { + Network::Mainnet => zp_constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + Network::Testnet(params) => params.hrp_sapling_extended_spending_key(), + } + } + + fn hrp_sapling_extended_full_viewing_key(&self) -> &str { + match self { + Network::Mainnet => zp_constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + Network::Testnet(params) => params.hrp_sapling_extended_full_viewing_key(), + } + } + + fn hrp_sapling_payment_address(&self) -> &str { + match self { + Network::Mainnet => zp_constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS, + Network::Testnet(params) => params.hrp_sapling_payment_address(), + } + } + + fn b58_pubkey_address_prefix(&self) -> [u8; 2] { + self.kind().b58_pubkey_address_prefix() + } + + fn b58_script_address_prefix(&self) -> [u8; 2] { + self.kind().b58_script_address_prefix() + } +} diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index d759be94b93..1a1b47001a0 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -1,16 +1,220 @@ //! Types and implementation for Testnet consensus parameters +use std::collections::BTreeMap; -#[cfg(any(test, feature = "proptest-impl"))] -use proptest_derive::Arbitrary; +use zcash_primitives::constants as zp_constants; + +use crate::{ + block::Height, + parameters::{ + network_upgrade::TESTNET_ACTIVATION_HEIGHTS, Network, NetworkUpgrade, + NETWORK_UPGRADES_IN_ORDER, + }, +}; + +/// Configurable activation heights for Regtest and configured Testnets. +#[derive(Deserialize, Default)] +#[serde(rename_all = "PascalCase")] +pub struct ConfiguredActivationHeights { + /// Activation height for `BeforeOverwinter` network upgrade. + pub before_overwinter: Option, + /// Activation height for `Overwinter` network upgrade. + pub overwinter: Option, + /// Activation height for `Sapling` network upgrade. + pub sapling: Option, + /// Activation height for `Blossom` network upgrade. + pub blossom: Option, + /// Activation height for `Heartwood` network upgrade. + pub heartwood: Option, + /// Activation height for `Canopy` network upgrade. + pub canopy: Option, + /// Activation height for `NU5` network upgrade. + #[serde(rename = "NU5")] + pub nu5: Option, +} + +/// Builder for the [`Parameters`] struct. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct ParametersBuilder { + /// The network upgrade activation heights for this network, see [`Parameters::activation_heights`] for more details. + activation_heights: BTreeMap, + /// Sapling extended spending key human-readable prefix for this network + hrp_sapling_extended_spending_key: String, + /// Sapling extended full viewing key human-readable prefix for this network + hrp_sapling_extended_full_viewing_key: String, + /// Sapling payment address human-readable prefix for this network + hrp_sapling_payment_address: String, +} + +impl Default for ParametersBuilder { + fn default() -> Self { + Self { + // # Correctness + // + // `Genesis` network upgrade activation height must always be 0 + activation_heights: [ + (Height(0), NetworkUpgrade::Genesis), + // TODO: Find out if `BeforeOverwinter` must always be active at Height(1), remove it here if it's not required. + (Height(1), NetworkUpgrade::BeforeOverwinter), + ] + .into_iter() + .collect(), + hrp_sapling_extended_spending_key: + zp_constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY.to_string(), + hrp_sapling_extended_full_viewing_key: + zp_constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY.to_string(), + hrp_sapling_payment_address: zp_constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS + .to_string(), + } + } +} + +impl ParametersBuilder { + /// Checks that the provided network upgrade activation heights are in the correct order, then + /// sets them as the new network upgrade activation heights. + pub fn activation_heights( + mut self, + ConfiguredActivationHeights { + // TODO: Find out if `BeforeOverwinter` is required at Height(1), allow for + // configuring its activation height if it's not required to be at Height(1) + before_overwinter: _, + overwinter, + sapling, + blossom, + heartwood, + canopy, + nu5, + }: ConfiguredActivationHeights, + ) -> Self { + use NetworkUpgrade::*; + + // # Correctness + // + // These must be in order so that later network upgrades overwrite prior ones + // if multiple network upgrades are configured with the same activation height. + let activation_heights: BTreeMap<_, _> = overwinter + .into_iter() + .map(|h| (h, Overwinter)) + .chain(sapling.into_iter().map(|h| (h, Sapling))) + .chain(blossom.into_iter().map(|h| (h, Blossom))) + .chain(heartwood.into_iter().map(|h| (h, Heartwood))) + .chain(canopy.into_iter().map(|h| (h, Canopy))) + .chain(nu5.into_iter().map(|h| (h, Nu5))) + .filter(|&(_, nu)| nu != NetworkUpgrade::BeforeOverwinter) + .map(|(h, nu)| (h.try_into().expect("activation height must be valid"), nu)) + .collect(); + + let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect(); + + // Check that the provided network upgrade activation heights are in the same order by height as the default testnet activation heights + let mut activation_heights_iter = activation_heights.iter(); + for expected_network_upgrade in NETWORK_UPGRADES_IN_ORDER { + if !network_upgrades.contains(&expected_network_upgrade) { + continue; + } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() { + assert_ne!( + height, + Height(0), + "Height(0) is reserved for the `Genesis` upgrade" + ); + + assert!( + network_upgrade == expected_network_upgrade, + "network upgrades must be activated in order, the correct order is {NETWORK_UPGRADES_IN_ORDER:?}" + ); + } + } + + // # Correctness + // + // Height(0) must be reserved for the `NetworkUpgrade::Genesis`. + self.activation_heights.split_off(&Height(2)); + self.activation_heights.extend(activation_heights); + + self + } + + /// Converts the builder to a [`Parameters`] struct + pub fn finish(self) -> Parameters { + let Self { + activation_heights, + hrp_sapling_extended_spending_key, + hrp_sapling_extended_full_viewing_key, + hrp_sapling_payment_address, + } = self; + Parameters { + activation_heights, + hrp_sapling_extended_spending_key, + hrp_sapling_extended_full_viewing_key, + hrp_sapling_payment_address, + } + } + + /// Converts the builder to a configured [`Network::Testnet`] + pub fn to_network(self) -> Network { + Network::new_configured_testnet(self.finish()) + } +} /// Network consensus parameters for test networks such as Regtest and the default Testnet. -#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] -pub struct Parameters {} +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct Parameters { + /// The network upgrade activation heights for this network. + /// + /// Note: This value is ignored by `Network::activation_list()` when `zebra-chain` is + /// compiled with the `zebra-test` feature flag AND the `TEST_FAKE_ACTIVATION_HEIGHTS` + /// environment variable is set. + activation_heights: BTreeMap, + /// Sapling extended spending key human-readable prefix for this network + hrp_sapling_extended_spending_key: String, + /// Sapling extended full viewing key human-readable prefix for this network + hrp_sapling_extended_full_viewing_key: String, + /// Sapling payment address human-readable prefix for this network + hrp_sapling_payment_address: String, +} + +impl Default for Parameters { + /// Returns an instance of the default public testnet [`Parameters`]. + fn default() -> Self { + Self { + activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(), + hrp_sapling_extended_spending_key: + zp_constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY.to_string(), + hrp_sapling_extended_full_viewing_key: + zp_constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY.to_string(), + hrp_sapling_payment_address: zp_constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS + .to_string(), + } + } +} impl Parameters { + /// Creates a new [`ParametersBuilder`]. + pub fn build() -> ParametersBuilder { + ParametersBuilder::default() + } + /// Returns true if the instance of [`Parameters`] represents the default public Testnet. pub fn is_default_testnet(&self) -> bool { self == &Self::default() } + + /// Returns the network upgrade activation heights + pub fn activation_heights(&self) -> &BTreeMap { + &self.activation_heights + } + + /// Returns the `hrp_sapling_extended_spending_key` field + pub fn hrp_sapling_extended_spending_key(&self) -> &str { + &self.hrp_sapling_extended_spending_key + } + + /// Returns the `hrp_sapling_extended_full_viewing_key` field + pub fn hrp_sapling_extended_full_viewing_key(&self) -> &str { + &self.hrp_sapling_extended_full_viewing_key + } + + /// Returns the `hrp_sapling_payment_address` field + pub fn hrp_sapling_payment_address(&self) -> &str { + &self.hrp_sapling_payment_address + } } diff --git a/zebra-chain/src/parameters/network/tests.rs b/zebra-chain/src/parameters/network/tests.rs index 2bf82ef4e54..cc95d9d451f 100644 --- a/zebra-chain/src/parameters/network/tests.rs +++ b/zebra-chain/src/parameters/network/tests.rs @@ -1 +1,2 @@ mod prop; +mod vectors; diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs new file mode 100644 index 00000000000..b6fca27c274 --- /dev/null +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -0,0 +1,127 @@ +//! Fixed test vectors for the network consensus parameters. + +use zcash_primitives::consensus::{self as zp_consensus, Parameters}; + +use crate::{ + block::Height, + parameters::{ + testnet::{self, ConfiguredActivationHeights}, + Network, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER, + }, +}; + +/// Checks that every method in the `Parameters` impl for `zebra_chain::Network` has the same output +/// as the Parameters impl for `zcash_primitives::consensus::Network` on Mainnet and the default Testnet. +#[test] +fn check_parameters_impl() { + let zp_network_upgrades = [ + zp_consensus::NetworkUpgrade::Overwinter, + zp_consensus::NetworkUpgrade::Sapling, + zp_consensus::NetworkUpgrade::Blossom, + zp_consensus::NetworkUpgrade::Heartwood, + zp_consensus::NetworkUpgrade::Canopy, + zp_consensus::NetworkUpgrade::Nu5, + ]; + + for (network, zp_network) in [ + (Network::Mainnet, zp_consensus::Network::MainNetwork), + ( + Network::new_default_testnet(), + zp_consensus::Network::TestNetwork, + ), + ] { + for nu in zp_network_upgrades { + let activation_height = network + .activation_height(nu) + .expect("must have activation height for past network upgrades"); + + assert_eq!( + activation_height, + zp_network + .activation_height(nu) + .expect("must have activation height for past network upgrades"), + "Parameters::activation_heights() outputs must match" + ); + + let activation_height: u32 = activation_height.into(); + + for height in (activation_height - 1)..=(activation_height + 1) { + for nu in zp_network_upgrades { + let height = zp_consensus::BlockHeight::from_u32(height); + assert_eq!( + network.is_nu_active(nu, height), + zp_network.is_nu_active(nu, height), + "Parameters::is_nu_active() outputs must match", + ); + } + } + } + + assert_eq!( + network.coin_type(), + zp_network.coin_type(), + "Parameters::coin_type() outputs must match" + ); + assert_eq!( + network.hrp_sapling_extended_spending_key(), + zp_network.hrp_sapling_extended_spending_key(), + "Parameters::hrp_sapling_extended_spending_key() outputs must match" + ); + assert_eq!( + network.hrp_sapling_extended_full_viewing_key(), + zp_network.hrp_sapling_extended_full_viewing_key(), + "Parameters::hrp_sapling_extended_full_viewing_key() outputs must match" + ); + assert_eq!( + network.hrp_sapling_payment_address(), + zp_network.hrp_sapling_payment_address(), + "Parameters::hrp_sapling_payment_address() outputs must match" + ); + assert_eq!( + network.b58_pubkey_address_prefix(), + zp_network.b58_pubkey_address_prefix(), + "Parameters::b58_pubkey_address_prefix() outputs must match" + ); + assert_eq!( + network.b58_script_address_prefix(), + zp_network.b58_script_address_prefix(), + "Parameters::b58_script_address_prefix() outputs must match" + ); + } +} + +/// Checks that `NetworkUpgrade::activation_height()` returns the activation height of the next +/// network upgrade if it doesn't find an activation height for a prior network upgrade, and that the +/// `Genesis` upgrade is always at `Height(0)`. +#[test] +fn activates_network_upgrades_correctly() { + let expected_activation_height = 1; + let network = testnet::Parameters::build() + .activation_heights(ConfiguredActivationHeights { + nu5: Some(expected_activation_height), + ..Default::default() + }) + .to_network(); + + let genesis_activation_height = NetworkUpgrade::Genesis + .activation_height(&network) + .expect("must return an activation height"); + + assert_eq!( + genesis_activation_height, + Height(0), + "activation height for all networks after Genesis and BeforeOverwinter should match NU5 activation height" + ); + + for nu in NETWORK_UPGRADES_IN_ORDER.into_iter().skip(1) { + let activation_height = nu + .activation_height(&network) + .expect("must return an activation height"); + + assert_eq!( + activation_height, Height(expected_activation_height), + "activation height for all networks after Genesis and BeforeOverwinter \ + should match NU5 activation height, network_upgrade: {nu}, activation_height: {activation_height:?}" + ); + } +} diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 6c114062b82..de546e3b422 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -7,7 +7,6 @@ use crate::parameters::{Network, Network::*}; use std::collections::{BTreeMap, HashMap}; use std::fmt; -use std::ops::Bound::*; use chrono::{DateTime, Duration, Utc}; use hex::{FromHex, ToHex}; @@ -15,6 +14,18 @@ use hex::{FromHex, ToHex}; #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; +/// A list of network upgrades in the order that they must be activated. +pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 8] = [ + Genesis, + BeforeOverwinter, + Overwinter, + Sapling, + Blossom, + Heartwood, + Canopy, + Nu5, +]; + /// A Zcash network upgrade. /// /// Network upgrades can change the Zcash network protocol or consensus rules in @@ -242,12 +253,7 @@ impl Network { /// and it's a test build, this returns a list of fake activation heights /// used by some tests. pub fn activation_list(&self) -> BTreeMap { - let (mainnet_heights, testnet_heights) = { - #[cfg(not(feature = "zebra-test"))] - { - (MAINNET_ACTIVATION_HEIGHTS, TESTNET_ACTIVATION_HEIGHTS) - } - + match self { // To prevent accidentally setting this somehow, only check the env var // when being compiled for tests. We can't use cfg(test) since the // test that uses this is in zebra-state, and cfg(test) is not @@ -260,25 +266,19 @@ impl Network { // feature should only be enabled for tests: // https://doc.rust-lang.org/cargo/reference/features.html#resolver-version-2-command-line-flags #[cfg(feature = "zebra-test")] - if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() { - ( - FAKE_MAINNET_ACTIVATION_HEIGHTS, - FAKE_TESTNET_ACTIVATION_HEIGHTS, - ) - } else { - (MAINNET_ACTIVATION_HEIGHTS, TESTNET_ACTIVATION_HEIGHTS) + Mainnet if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => { + FAKE_MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect() } - }; - match self { - Mainnet => mainnet_heights, - // TODO: Add an `activation_heights` field to `testnet::Parameters` to return here. (#7970) - Testnet(_params) => testnet_heights, + #[cfg(feature = "zebra-test")] + Testnet(_) if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => { + FAKE_TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect() + } + Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(), + Testnet(params) => params.activation_heights().clone(), } - .iter() - .cloned() - .collect() } } + impl NetworkUpgrade { /// Returns the current network upgrade for `network` and `height`. pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade { @@ -290,11 +290,28 @@ impl NetworkUpgrade { .expect("every height has a current network upgrade") } + /// Returns the next expected network upgrade after this network upgrade + pub fn next_upgrade(self) -> Option { + match self { + Genesis => Some(BeforeOverwinter), + BeforeOverwinter => Some(Overwinter), + Overwinter => Some(Sapling), + Sapling => Some(Blossom), + Blossom => Some(Heartwood), + Heartwood => Some(Canopy), + Canopy => Some(Nu5), + Nu5 => None, + } + } + /// Returns the next network upgrade for `network` and `height`. /// /// Returns None if the next upgrade has not been implemented in Zebra /// yet. + #[cfg(test)] pub fn next(network: &Network, height: block::Height) -> Option { + use std::ops::Bound::*; + network .activation_list() .range((Excluded(height), Unbounded)) @@ -302,17 +319,27 @@ impl NetworkUpgrade { .next() } - /// Returns the activation height for this network upgrade on `network`. + /// Returns the activation height for this network upgrade on `network`, or + /// + /// Returns the activation height of the first network upgrade that follows + /// this network upgrade if there is no activation height for this network upgrade + /// such as on Regtest or a configured Testnet where multiple network upgrades have the + /// same activation height, or if one is omitted when others that follow it are included. /// /// Returns None if this network upgrade is a future upgrade, and its /// activation height has not been set yet. + /// + /// Returns None if this network upgrade has not been configured on a Testnet or Regtest. pub fn activation_height(&self, network: &Network) -> Option { network .activation_list() .iter() - .filter(|(_, nu)| nu == &self) + .find(|(_, nu)| nu == &self) .map(|(height, _)| *height) - .next() + .or_else(|| { + self.next_upgrade() + .and_then(|next_nu| next_nu.activation_height(network)) + }) } /// Returns `true` if `height` is the activation height of any network upgrade diff --git a/zebra-chain/src/primitives/zcash_note_encryption.rs b/zebra-chain/src/primitives/zcash_note_encryption.rs index cde0879d8f2..f8e47fbad47 100644 --- a/zebra-chain/src/primitives/zcash_note_encryption.rs +++ b/zebra-chain/src/primitives/zcash_note_encryption.rs @@ -22,29 +22,11 @@ pub fn decrypts_successfully(transaction: &Transaction, network: &Network, heigh let alt_height = height.0.into(); let null_sapling_ovk = zcash_primitives::keys::OutgoingViewingKey([0u8; 32]); - let network = match network { - Network::Mainnet => zcash_primitives::consensus::Network::MainNetwork, - Network::Testnet(params) => { - // # Correctness: - // - // There are differences between the `TestNetwork` parameters and those returned by - // `CRegTestParams()` in zcashd, so this function can't return `TestNetwork` unless - // Zebra is using the default public Testnet. - // - // TODO: Remove this conversion by implementing `zcash_primitives::consensus::Parameters` - // for `Network` (#8365). - assert!( - params.is_default_testnet(), - "could not convert configured testnet to zcash_primitives::consensus::Network" - ); - zcash_primitives::consensus::Network::TestNetwork - } - }; if let Some(bundle) = alt_tx.sapling_bundle() { for output in bundle.shielded_outputs().iter() { let recovery = zcash_primitives::sapling::note_encryption::try_sapling_output_recovery( - &network, + network, alt_height, &null_sapling_ovk, output, diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 9e75a2967ab..fa1e2056869 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -14,7 +14,10 @@ use tempfile::NamedTempFile; use tokio::{fs, io::AsyncWriteExt}; use tracing::Span; -use zebra_chain::parameters::{testnet, Network, NetworkKind}; +use zebra_chain::parameters::{ + testnet::{self, ConfiguredActivationHeights}, + Network, NetworkKind, +}; use crate::{ constants::{ @@ -624,12 +627,18 @@ impl<'de> Deserialize<'de> for Config { where D: Deserializer<'de>, { + #[derive(Deserialize)] + struct DTestnetParameters { + #[serde(default)] + pub(super) activation_heights: ConfiguredActivationHeights, + } + #[derive(Deserialize)] #[serde(deny_unknown_fields, default)] struct DConfig { listen_addr: String, network: NetworkKind, - testnet_parameters: Option, + testnet_parameters: Option, initial_mainnet_peers: IndexSet, initial_testnet_peers: IndexSet, cache_dir: CacheDir, @@ -668,15 +677,17 @@ impl<'de> Deserialize<'de> for Config { max_connections_per_ip, } = DConfig::deserialize(deserializer)?; - let network = if let Some(network_params) = testnet_parameters { - // TODO: Panic here if the initial testnet peers are the default initial testnet peers. + // TODO: Panic here if the initial testnet peers are the default initial testnet peers. + let network = if let Some(DTestnetParameters { activation_heights }) = testnet_parameters { assert_eq!( network_kind, NetworkKind::Testnet, "set network to 'Testnet' to use configured testnet parameters" ); - Network::new_configured_testnet(network_params) + testnet::Parameters::build() + .activation_heights(activation_heights) + .to_network() } else { // Convert to default `Network` for a `NetworkKind` if there are no testnet parameters. match network_kind { diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index f35aa9082da..9ef9c581e0a 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, POW_AVERAGING_WINDOW}, + parameters::{Network, NetworkKind, POW_AVERAGING_WINDOW}, primitives, serialization::ZcashDeserializeInto, transparent::{ @@ -449,13 +449,25 @@ where ) -> Self { // Prevent loss of miner funds due to an unsupported or incorrect address type. if let Some(miner_address) = mining_config.miner_address.clone() { - assert_eq!( - miner_address.network_kind(), - network.kind(), - "incorrect miner address config: {miner_address} \ - network.network {network} and miner address network {} must match", - miner_address.network_kind(), - ); + match network.kind() { + NetworkKind::Mainnet => assert_eq!( + miner_address.network_kind(), + NetworkKind::Mainnet, + "Incorrect config: Zebra is configured to run on a Mainnet network, \ + which implies the configured mining address needs to be for Mainnet, \ + but the provided address is for {}.", + miner_address.network_kind(), + ), + // `Regtest` uses `Testnet` transparent addresses. + network_kind @ (NetworkKind::Testnet | NetworkKind::Regtest) => assert_eq!( + miner_address.network_kind(), + NetworkKind::Testnet, + "Incorrect config: Zebra is configured to run on a {network_kind} network, \ + which implies the configured mining address needs to be for Testnet, \ + but the provided address is for {}.", + miner_address.network_kind(), + ), + } } // A limit on the configured extra coinbase data, regardless of the current block height. diff --git a/zebra-scan/src/service/scan_task/scan.rs b/zebra-scan/src/service/scan_task/scan.rs index c078e3260cc..12bbb86a187 100644 --- a/zebra-scan/src/service/scan_task/scan.rs +++ b/zebra-scan/src/service/scan_task/scan.rs @@ -384,25 +384,6 @@ pub fn scan_block( // TODO: Implement a check that returns early when the block height is below the Sapling // activation height. - let network = match network { - Network::Mainnet => zcash_primitives::consensus::Network::MainNetwork, - Network::Testnet(params) => { - // # Correctness: - // - // There are differences between the `TestNetwork` parameters and those returned by - // `CRegTestParams()` in zcashd, so this function can't return `TestNetwork` unless - // Zebra is using the default public Testnet. - // - // TODO: Remove this conversion by implementing `zcash_primitives::consensus::Parameters` - // for `Network` (#8365). - assert!( - params.is_default_testnet(), - "could not convert configured testnet to zcash_primitives::consensus::Network" - ); - zcash_primitives::consensus::Network::TestNetwork - } - }; - let chain_metadata = ChainMetadata { sapling_commitment_tree_size: sapling_tree_size, // Orchard is not supported at the moment so the tree size can be 0. @@ -417,7 +398,7 @@ pub fn scan_block( .collect(); zcash_client_backend::scanning::scan_block( - &network, + network, block_to_compact(block, chain_metadata), scanning_keys.as_slice(), // Ignore whether notes are change from a viewer's own spends for now. diff --git a/zebra-utils/src/bin/scanning-results-reader/main.rs b/zebra-utils/src/bin/scanning-results-reader/main.rs index 66b48e0ac6b..1bfa15d3683 100644 --- a/zebra-utils/src/bin/scanning-results-reader/main.rs +++ b/zebra-utils/src/bin/scanning-results-reader/main.rs @@ -41,15 +41,13 @@ use zebra_scan::{storage::Storage, Config}; /// - The transaction fetched via RPC cannot be deserialized from raw bytes. #[allow(clippy::print_stdout)] pub fn main() { - // TODO: Implement `zcash_primitives::consensus::Parameters` for `Network` and remove this variable (#8365). - let network = zcash_primitives::consensus::Network::MainNetwork; - let zebra_network = zebra_chain::parameters::Network::Mainnet; - let storage = Storage::new(&Config::default(), &zebra_network, true); + let network = zebra_chain::parameters::Network::Mainnet; + let storage = Storage::new(&Config::default(), &network, true); // If the first memo is empty, it doesn't get printed. But we never print empty memos anyway. let mut prev_memo = "".to_owned(); for (key, _) in storage.sapling_keys_last_heights().iter() { - let dfvk = sapling_key_to_scan_block_keys(key, &zebra_network) + let dfvk = sapling_key_to_scan_block_keys(key, &network) .expect("Scanning key from the storage should be valid") .0 .into_iter() diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 166c7183eae..eb6a2791738 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -922,6 +922,69 @@ fn invalid_generated_config() -> Result<()> { Ok(()) } +/// Test all versions of `zebrad.toml` we have stored can be parsed by the latest `zebrad`. +#[tracing::instrument] +#[test] +fn stored_configs_parsed_correctly() -> Result<()> { + let old_configs_dir = configs_dir(); + use abscissa_core::Application; + use zebrad::application::ZebradApp; + + tracing::info!(?old_configs_dir, "testing older config parsing"); + + for config_file in old_configs_dir + .read_dir() + .expect("read_dir call failed") + .flatten() + { + let config_file_path = config_file.path(); + let config_file_name = config_file_path + .file_name() + .expect("config files must have a file name") + .to_str() + .expect("config file names are valid unicode"); + + if config_file_name.starts_with('.') || config_file_name.starts_with('#') { + // Skip editor files and other invalid config paths + tracing::info!( + ?config_file_path, + "skipping hidden/temporary config file path" + ); + continue; + } + + // ignore files starting with getblocktemplate prefix + // if we were not built with the getblocktemplate-rpcs feature. + #[cfg(not(feature = "getblocktemplate-rpcs"))] + if config_file_name.starts_with(GET_BLOCK_TEMPLATE_CONFIG_PREFIX) { + tracing::info!( + ?config_file_path, + "skipping getblocktemplate-rpcs config file path" + ); + 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; + } + + tracing::info!( + ?config_file_path, + "testing old config can be parsed by current zebrad" + ); + + ZebradApp::default() + .load_config(&config_file_path) + .expect("config should parse"); + } + + Ok(()) +} + /// Test all versions of `zebrad.toml` we have stored can be parsed by the latest `zebrad`. #[tracing::instrument] fn stored_configs_work() -> Result<()> { diff --git a/zebrad/tests/common/configs/v1.7.0.toml b/zebrad/tests/common/configs/v1.7.0.toml index cbb3a0200a3..58aed232f4e 100644 --- a/zebrad/tests/common/configs/v1.7.0.toml +++ b/zebrad/tests/common/configs/v1.7.0.toml @@ -60,7 +60,14 @@ max_connections_per_ip = 1 network = "Testnet" peerset_initial_target_size = 25 -[network.testnet_parameters] +[network.testnet_parameters.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 @@ -81,4 +88,4 @@ parallel_cpu_threads = 0 buffer_limit = 128000 force_use_color = false use_color = true -use_journald = false \ No newline at end of file +use_journald = false