diff --git a/zebra-chain/src/block/tests/vectors.rs b/zebra-chain/src/block/tests/vectors.rs index 8aa81dbd..5ff19ca1 100644 --- a/zebra-chain/src/block/tests/vectors.rs +++ b/zebra-chain/src/block/tests/vectors.rs @@ -202,7 +202,7 @@ fn block_test_vectors_height_mainnet() { fn block_test_vectors_height_testnet() { let _init_guard = zebra_test::init(); - block_test_vectors_height(Testnet); + block_test_vectors_height(Network::new_default_testnet()); } /// Test that the block test vector indexes match the heights in the block data, @@ -245,7 +245,7 @@ fn block_commitment_mainnet() { fn block_commitment_testnet() { let _init_guard = zebra_test::init(); - block_commitment(Testnet); + block_commitment(Network::new_default_testnet()); } /// Check that the block commitment field parses without errors. diff --git a/zebra-chain/src/history_tree.rs b/zebra-chain/src/history_tree.rs index 8666ba49..ca19c589 100644 --- a/zebra-chain/src/history_tree.rs +++ b/zebra-chain/src/history_tree.rs @@ -372,8 +372,8 @@ impl NonEmptyHistoryTree { } /// Return the network where this tree is used. - pub fn network(&self) -> Network { - self.network.clone() + pub fn network(&self) -> &Network { + &self.network } } diff --git a/zebra-chain/src/history_tree/tests/vectors.rs b/zebra-chain/src/history_tree/tests/vectors.rs index 9ddb32bd..e65da99b 100644 --- a/zebra-chain/src/history_tree/tests/vectors.rs +++ b/zebra-chain/src/history_tree/tests/vectors.rs @@ -22,10 +22,10 @@ use eyre::Result; /// higher level API. #[test] fn push_and_prune() -> Result<()> { - push_and_prune_for_network_upgrade(Network::Mainnet, NetworkUpgrade::Heartwood)?; - push_and_prune_for_network_upgrade(Network::Testnet, NetworkUpgrade::Heartwood)?; - push_and_prune_for_network_upgrade(Network::Mainnet, NetworkUpgrade::Canopy)?; - push_and_prune_for_network_upgrade(Network::Testnet, NetworkUpgrade::Canopy)?; + for network in Network::iter() { + push_and_prune_for_network_upgrade(network.clone(), NetworkUpgrade::Heartwood)?; + push_and_prune_for_network_upgrade(network, NetworkUpgrade::Canopy)?; + } Ok(()) } @@ -109,8 +109,9 @@ fn push_and_prune_for_network_upgrade( fn upgrade() -> Result<()> { // The history tree only exists Hearwood-onward, and the only upgrade for which // we have vectors since then is Canopy. Therefore, only test the Heartwood->Canopy upgrade. - upgrade_for_network_upgrade(Network::Mainnet, NetworkUpgrade::Canopy)?; - upgrade_for_network_upgrade(Network::Testnet, NetworkUpgrade::Canopy)?; + for network in Network::iter() { + upgrade_for_network_upgrade(network, NetworkUpgrade::Canopy)?; + } Ok(()) } diff --git a/zebra-chain/src/parameters.rs b/zebra-chain/src/parameters.rs index fe62f7b6..2974bd78 100644 --- a/zebra-chain/src/parameters.rs +++ b/zebra-chain/src/parameters.rs @@ -21,7 +21,7 @@ mod transaction; pub mod arbitrary; pub use genesis::*; -pub use network::Network; +pub use network::{testnet, Network, NetworkKind}; pub use network_upgrade::*; pub use transaction::*; diff --git a/zebra-chain/src/parameters/network.rs b/zebra-chain/src/parameters/network.rs index 3ba5a090..8c5ecaf9 100644 --- a/zebra-chain/src/parameters/network.rs +++ b/zebra-chain/src/parameters/network.rs @@ -1,14 +1,18 @@ //! Consensus parameters for each Zcash network. -use std::{fmt, str::FromStr}; +use std::{fmt, str::FromStr, sync::Arc}; use thiserror::Error; +use zcash_primitives::constants; + use crate::{ block::{self, Height, HeightDiff}, parameters::NetworkUpgrade::Canopy, }; +pub mod testnet; + #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; @@ -51,52 +55,102 @@ mod tests; /// after the grace period. const ZIP_212_GRACE_PERIOD_DURATION: HeightDiff = 32_256; +/// An enum describing the kind of network, whether it's the production mainnet or a testnet. +// Note: The order of these variants is important for correct bincode (de)serialization +// of history trees in the db format. +// TODO: Replace bincode (de)serialization of `HistoryTreeParts` in a db format upgrade? +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum NetworkKind { + /// The production mainnet. + #[default] + Mainnet, + + /// A test network. + Testnet, + + /// Regtest mode, not yet implemented + // TODO: Add `new_regtest()` and `is_regtest` methods on `Network`. + Regtest, +} + +impl From for NetworkKind { + fn from(network: Network) -> Self { + network.kind() + } +} + /// An enum describing the possible network choices. -#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[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. #[default] Mainnet, - /// The oldest public test network. - Testnet, + /// A test network such as the default public testnet, + /// a configured testnet, or Regtest. + Testnet(Arc), } -use zcash_primitives::consensus::{Network as ZcashPrimitivesNetwork, Parameters as _}; -impl Network { +impl NetworkKind { /// Returns the human-readable prefix for Base58Check-encoded transparent /// pay-to-public-key-hash payment addresses for the network. - pub fn b58_pubkey_address_prefix(&self) -> [u8; 2] { - ::from(self).b58_pubkey_address_prefix() + 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, + } } /// Returns the human-readable prefix for Base58Check-encoded transparent pay-to-script-hash /// payment addresses for the network. - pub fn b58_script_address_prefix(&self) -> [u8; 2] { - ::from(self).b58_script_address_prefix() - } - /// Returns true if the maximum block time rule is active for `network` and `height`. - /// - /// Always returns true if `network` is the Mainnet. - /// If `network` is the Testnet, the `height` should be at least - /// TESTNET_MAX_TIME_START_HEIGHT to return true. - /// Returns false otherwise. - /// - /// Part of the consensus rules at - pub fn is_max_block_time_enforced(&self, height: block::Height) -> bool { + pub fn b58_script_address_prefix(self) -> [u8; 2] { match self { - Network::Mainnet => true, - Network::Testnet => height >= super::TESTNET_MAX_TIME_START_HEIGHT, + Self::Mainnet => constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX, + Self::Testnet | Self::Regtest => constants::testnet::B58_SCRIPT_ADDRESS_PREFIX, } } + + /// Return the network name as defined in + /// [BIP70](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#paymentdetailspaymentrequest) + pub fn bip70_network_name(&self) -> String { + if *self == Self::Mainnet { + "main".to_string() + } else { + "test".to_string() + } + } +} + +impl From for &'static str { + fn from(network: NetworkKind) -> &'static str { + // These should be different from the `Display` impl for `Network` so that its lowercase form + // can't be parsed as the default Testnet in the `Network` `FromStr` impl, it's easy to + // distinguish them in logs, and so it's generally harder to confuse the two. + match network { + NetworkKind::Mainnet => "MainnetKind", + NetworkKind::Testnet => "TestnetKind", + NetworkKind::Regtest => "RegtestKind", + } + } +} + +impl fmt::Display for NetworkKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str((*self).into()) + } } impl From<&Network> for &'static str { fn from(network: &Network) -> &'static str { match network { Network::Mainnet => "Mainnet", - Network::Testnet => "Testnet", + // TODO: + // - Add a `name` field to use here instead of checking `is_default_testnet()` + // - zcashd calls the Regtest cache dir 'regtest' (#8327). + Network::Testnet(params) if params.is_default_testnet() => "Testnet", + Network::Testnet(_params) => "UnknownTestnet", } } } @@ -108,17 +162,62 @@ impl fmt::Display for Network { } impl Network { + /// Creates a new [`Network::Testnet`] with the default Testnet [`testnet::Parameters`]. + pub fn new_default_testnet() -> Self { + Self::Testnet(Arc::new(testnet::Parameters::default())) + } + + /// Creates a new configured [`Network::Testnet`] with the provided Testnet [`testnet::Parameters`]. + pub fn new_configured_testnet(params: testnet::Parameters) -> Self { + Self::Testnet(Arc::new(params)) + } + + /// Returns true if the network is the default Testnet, or false otherwise. + pub fn is_default_testnet(&self) -> bool { + if let Self::Testnet(params) = self { + params.is_default_testnet() + } else { + false + } + } + + /// Returns the [`NetworkKind`] for this 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(_) => 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::Testnet].into_iter() + [Self::Mainnet, Self::new_default_testnet()].into_iter() + } + + /// Returns true if the maximum block time rule is active for `network` and `height`. + /// + /// Always returns true if `network` is the Mainnet. + /// If `network` is the Testnet, the `height` should be at least + /// TESTNET_MAX_TIME_START_HEIGHT to return true. + /// Returns false otherwise. + /// + /// Part of the consensus rules at + pub fn is_max_block_time_enforced(&self, height: block::Height) -> bool { + match self { + Network::Mainnet => true, + // TODO: Move `TESTNET_MAX_TIME_START_HEIGHT` to a field on testnet::Parameters (#8364) + Network::Testnet(_params) => height >= super::TESTNET_MAX_TIME_START_HEIGHT, + } } /// Get the default port associated to this network. pub fn default_port(&self) -> u16 { match self { Network::Mainnet => 8233, - Network::Testnet => 18233, + // TODO: Add a `default_port` field to `testnet::Parameters` to return here. (zcashd uses 18344 for Regtest) + Network::Testnet(_params) => 18233, } } @@ -143,10 +242,7 @@ impl Network { /// Return the network name as defined in /// [BIP70](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#paymentdetailspaymentrequest) pub fn bip70_network_name(&self) -> String { - match self { - Network::Mainnet => "main".to_string(), - Network::Testnet => "test".to_string(), - } + self.kind().bip70_network_name() } /// Return the lowercase network name. @@ -167,13 +263,14 @@ impl Network { } } +// This is used for parsing a command-line argument for the `TipHeight` command in zebrad. impl FromStr for Network { type Err = InvalidNetworkError; fn from_str(string: &str) -> Result { match string.to_lowercase().as_str() { "mainnet" => Ok(Network::Mainnet), - "testnet" => Ok(Network::Testnet), + "testnet" => Ok(Network::new_default_testnet()), _ => Err(InvalidNetworkError(string.to_owned())), } } diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs new file mode 100644 index 00000000..d759be94 --- /dev/null +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -0,0 +1,16 @@ +//! Types and implementation for Testnet consensus parameters + +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + +/// 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 {} + +impl Parameters { + /// Returns true if the instance of [`Parameters`] represents the default public Testnet. + pub fn is_default_testnet(&self) -> bool { + self == &Self::default() + } +} diff --git a/zebra-chain/src/parameters/network/tests/prop.rs b/zebra-chain/src/parameters/network/tests/prop.rs index cab04496..c2b1e31e 100644 --- a/zebra-chain/src/parameters/network/tests/prop.rs +++ b/zebra-chain/src/parameters/network/tests/prop.rs @@ -34,6 +34,6 @@ proptest! { let _init_guard = zebra_test::init(); assert!(Network::Mainnet.is_max_block_time_enforced(height)); - assert_eq!(Network::Testnet.is_max_block_time_enforced(height), TESTNET_MAX_TIME_START_HEIGHT <= height); + assert_eq!(Network::new_default_testnet().is_max_block_time_enforced(height), TESTNET_MAX_TIME_START_HEIGHT <= height); } } diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 4d88e96b..6c114062 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -271,7 +271,8 @@ impl Network { }; match self { Mainnet => mainnet_heights, - Testnet => testnet_heights, + // TODO: Add an `activation_heights` field to `testnet::Parameters` to return here. (#7970) + Testnet(_params) => testnet_heights, } .iter() .cloned() @@ -394,9 +395,14 @@ impl NetworkUpgrade { height: block::Height, ) -> Option { match (network, height) { - (Network::Testnet, height) if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT => None, + // TODO: Move `TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT` to a field on testnet::Parameters (#8364) + (Network::Testnet(_params), height) + if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT => + { + None + } (Network::Mainnet, _) => None, - (Network::Testnet, _) => { + (Network::Testnet(_params), _) => { let network_upgrade = NetworkUpgrade::current(network, height); Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER) } diff --git a/zebra-chain/src/parameters/tests.rs b/zebra-chain/src/parameters/tests.rs index 715ceb68..2b0f8b4a 100644 --- a/zebra-chain/src/parameters/tests.rs +++ b/zebra-chain/src/parameters/tests.rs @@ -21,7 +21,7 @@ fn activation_bijective() { let mainnet_nus: HashSet<&NetworkUpgrade> = mainnet_activations.values().collect(); assert_eq!(MAINNET_ACTIVATION_HEIGHTS.len(), mainnet_nus.len()); - let testnet_activations = Testnet.activation_list(); + let testnet_activations = Network::new_default_testnet().activation_list(); let testnet_heights: HashSet<&block::Height> = testnet_activations.keys().collect(); assert_eq!(TESTNET_ACTIVATION_HEIGHTS.len(), testnet_heights.len()); @@ -38,7 +38,7 @@ fn activation_extremes_mainnet() { #[test] fn activation_extremes_testnet() { let _init_guard = zebra_test::init(); - activation_extremes(Testnet) + activation_extremes(Network::new_default_testnet()) } /// Test the activation_list, activation_height, current, and next functions @@ -115,7 +115,7 @@ fn activation_consistent_mainnet() { #[test] fn activation_consistent_testnet() { let _init_guard = zebra_test::init(); - activation_consistent(Testnet) + activation_consistent(Network::new_default_testnet()) } /// Check that the `activation_height`, `is_activation_height`, @@ -178,7 +178,7 @@ fn branch_id_extremes_mainnet() { #[test] fn branch_id_extremes_testnet() { let _init_guard = zebra_test::init(); - branch_id_extremes(Testnet) + branch_id_extremes(Network::new_default_testnet()) } /// Test the branch_id_list, branch_id, and current functions for `network` with @@ -217,7 +217,7 @@ fn branch_id_consistent_mainnet() { #[test] fn branch_id_consistent_testnet() { let _init_guard = zebra_test::init(); - branch_id_consistent(Testnet) + branch_id_consistent(Network::new_default_testnet()) } /// Check that the branch_id and current functions are consistent for `network`. diff --git a/zebra-chain/src/primitives/address.rs b/zebra-chain/src/primitives/address.rs index 553e523b..7e5f7613 100644 --- a/zebra-chain/src/primitives/address.rs +++ b/zebra-chain/src/primitives/address.rs @@ -5,7 +5,7 @@ use zcash_address::unified::{self, Container}; use zcash_primitives::sapling; -use crate::{parameters::Network, transparent, BoxError}; +use crate::{parameters::NetworkKind, transparent, BoxError}; /// Zcash address variants pub enum Address { @@ -14,8 +14,8 @@ pub enum Address { /// Sapling address Sapling { - /// Address' network - network: Network, + /// Address' network kind + network: NetworkKind, /// Sapling address address: sapling::PaymentAddress, @@ -23,8 +23,8 @@ pub enum Address { /// Unified address Unified { - /// Address' network - network: Network, + /// Address' network kind + network: NetworkKind, /// Unified address unified_address: zcash_address::unified::Address, @@ -40,28 +40,6 @@ pub enum Address { }, } -impl TryFrom for Network { - // TODO: better error type - type Error = BoxError; - - fn try_from(network: zcash_address::Network) -> Result { - match network { - zcash_address::Network::Main => Ok(Network::Mainnet), - zcash_address::Network::Test => Ok(Network::Testnet), - zcash_address::Network::Regtest => Err("unsupported Zcash network parameters".into()), - } - } -} - -impl From<&Network> for zcash_address::Network { - fn from(network: &Network) -> Self { - match network { - Network::Mainnet => zcash_address::Network::Main, - Network::Testnet => zcash_address::Network::Test, - } - } -} - impl zcash_address::TryFromAddress for Address { // TODO: crate::serialization::SerializationError type Error = BoxError; @@ -71,7 +49,7 @@ impl zcash_address::TryFromAddress for Address { data: [u8; 20], ) -> Result> { Ok(Self::Transparent(transparent::Address::from_pub_key_hash( - &network.try_into()?, + NetworkKind::from_zcash_address(network), data, ))) } @@ -81,7 +59,7 @@ impl zcash_address::TryFromAddress for Address { data: [u8; 20], ) -> Result> { Ok(Self::Transparent(transparent::Address::from_script_hash( - &network.try_into()?, + NetworkKind::from_zcash_address(network), data, ))) } @@ -90,7 +68,7 @@ impl zcash_address::TryFromAddress for Address { network: zcash_address::Network, data: [u8; 43], ) -> Result> { - let network = network.try_into()?; + let network = NetworkKind::from_zcash_address(network); sapling::PaymentAddress::from_bytes(&data) .map(|address| Self::Sapling { address, network }) .ok_or_else(|| BoxError::from("not a valid sapling address").into()) @@ -100,7 +78,7 @@ impl zcash_address::TryFromAddress for Address { network: zcash_address::Network, unified_address: zcash_address::unified::Address, ) -> Result> { - let network = &network.try_into()?; + let network = NetworkKind::from_zcash_address(network); let mut orchard = None; let mut sapling = None; let mut transparent = None; @@ -144,7 +122,7 @@ impl zcash_address::TryFromAddress for Address { } Ok(Self::Unified { - network: network.clone(), + network, unified_address, orchard, sapling, @@ -155,10 +133,10 @@ impl zcash_address::TryFromAddress for Address { impl Address { /// Returns the network for the address. - pub fn network(&self) -> Network { + pub fn network(&self) -> NetworkKind { match &self { - Self::Transparent(address) => address.network(), - Self::Sapling { network, .. } | Self::Unified { network, .. } => network.clone(), + Self::Transparent(address) => address.network_kind(), + Self::Sapling { network, .. } | Self::Unified { network, .. } => *network, } } @@ -185,10 +163,34 @@ impl Address { Self::Transparent(address) => Some(address.to_string()), Self::Sapling { address, network } => { let data = address.to_bytes(); - let address = ZcashAddress::from_sapling(network.into(), data); + let address = ZcashAddress::from_sapling(network.to_zcash_address(), data); Some(address.encode()) } Self::Unified { .. } => None, } } } + +impl NetworkKind { + /// Converts a [`zcash_address::Network`] to a [`NetworkKind`]. + /// + /// This method is meant to be used for decoding Zcash addresses in zebra-rpc methods. + fn from_zcash_address(network: zcash_address::Network) -> Self { + match network { + zcash_address::Network::Main => NetworkKind::Mainnet, + zcash_address::Network::Test => NetworkKind::Testnet, + zcash_address::Network::Regtest => NetworkKind::Regtest, + } + } + + /// Converts a [`zcash_address::Network`] to a [`NetworkKind`]. + /// + /// This method is meant to be used for encoding Zcash addresses in zebra-rpc methods. + fn to_zcash_address(self) -> zcash_address::Network { + match self { + NetworkKind::Mainnet => zcash_address::Network::Main, + NetworkKind::Testnet => zcash_address::Network::Test, + NetworkKind::Regtest => zcash_address::Network::Regtest, + } + } +} diff --git a/zebra-chain/src/primitives/zcash_history/tests/vectors.rs b/zebra-chain/src/primitives/zcash_history/tests/vectors.rs index b5ead109..d23cb078 100644 --- a/zebra-chain/src/primitives/zcash_history/tests/vectors.rs +++ b/zebra-chain/src/primitives/zcash_history/tests/vectors.rs @@ -11,10 +11,10 @@ use eyre::Result; /// and its next block. #[test] fn tree() -> Result<()> { - tree_for_network_upgrade(&Network::Mainnet, NetworkUpgrade::Heartwood)?; - tree_for_network_upgrade(&Network::Testnet, NetworkUpgrade::Heartwood)?; - tree_for_network_upgrade(&Network::Mainnet, NetworkUpgrade::Canopy)?; - tree_for_network_upgrade(&Network::Testnet, NetworkUpgrade::Canopy)?; + for network in Network::iter() { + tree_for_network_upgrade(&network, NetworkUpgrade::Heartwood)?; + tree_for_network_upgrade(&network, NetworkUpgrade::Canopy)?; + } Ok(()) } diff --git a/zebra-chain/src/primitives/zcash_note_encryption.rs b/zebra-chain/src/primitives/zcash_note_encryption.rs index 0870dac5..cde0879d 100644 --- a/zebra-chain/src/primitives/zcash_note_encryption.rs +++ b/zebra-chain/src/primitives/zcash_note_encryption.rs @@ -22,11 +22,29 @@ 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( - &::from(network), + &network, alt_height, &null_sapling_ovk, output, diff --git a/zebra-chain/src/primitives/zcash_primitives.rs b/zebra-chain/src/primitives/zcash_primitives.rs index c4e43918..564afab0 100644 --- a/zebra-chain/src/primitives/zcash_primitives.rs +++ b/zebra-chain/src/primitives/zcash_primitives.rs @@ -328,29 +328,11 @@ pub(crate) fn transparent_output_address( match alt_addr { Some(zcash_primitives::legacy::TransparentAddress::PublicKey(pub_key_hash)) => Some( - transparent::Address::from_pub_key_hash(network, pub_key_hash), + transparent::Address::from_pub_key_hash(network.kind(), pub_key_hash), + ), + Some(zcash_primitives::legacy::TransparentAddress::Script(script_hash)) => Some( + transparent::Address::from_script_hash(network.kind(), script_hash), ), - Some(zcash_primitives::legacy::TransparentAddress::Script(script_hash)) => { - Some(transparent::Address::from_script_hash(network, script_hash)) - } None => None, } } - -impl From<&Network> for zcash_primitives::consensus::Network { - fn from(network: &Network) -> Self { - match network { - Network::Mainnet => zcash_primitives::consensus::Network::MainNetwork, - Network::Testnet => zcash_primitives::consensus::Network::TestNetwork, - } - } -} - -impl From for Network { - fn from(network: zcash_primitives::consensus::Network) -> Self { - match network { - zcash_primitives::consensus::Network::MainNetwork => Network::Mainnet, - zcash_primitives::consensus::Network::TestNetwork => Network::Testnet, - } - } -} diff --git a/zebra-chain/src/sapling/tests/tree.rs b/zebra-chain/src/sapling/tests/tree.rs index 17ee640f..37773e01 100644 --- a/zebra-chain/src/sapling/tests/tree.rs +++ b/zebra-chain/src/sapling/tests/tree.rs @@ -51,8 +51,9 @@ fn incremental_roots() { #[test] fn incremental_roots_with_blocks() -> Result<()> { - incremental_roots_with_blocks_for_network(Network::Mainnet)?; - incremental_roots_with_blocks_for_network(Network::Testnet)?; + for network in Network::iter() { + incremental_roots_with_blocks_for_network(network)?; + } Ok(()) } diff --git a/zebra-chain/src/sprout/tests/tree.rs b/zebra-chain/src/sprout/tests/tree.rs index f71c8c6a..f04ee9af 100644 --- a/zebra-chain/src/sprout/tests/tree.rs +++ b/zebra-chain/src/sprout/tests/tree.rs @@ -79,9 +79,9 @@ fn incremental_roots() { #[test] fn incremental_roots_with_blocks() -> Result<()> { - incremental_roots_with_blocks_for_network(Network::Mainnet)?; - incremental_roots_with_blocks_for_network(Network::Testnet)?; - + for network in Network::iter() { + incremental_roots_with_blocks_for_network(network)?; + } Ok(()) } diff --git a/zebra-chain/src/transaction/tests/vectors.rs b/zebra-chain/src/transaction/tests/vectors.rs index 129565a6..aa95a9c5 100644 --- a/zebra-chain/src/transaction/tests/vectors.rs +++ b/zebra-chain/src/transaction/tests/vectors.rs @@ -343,9 +343,9 @@ fn empty_v5_librustzcash_round_trip() { #[test] fn fake_v5_round_trip() { let _init_guard = zebra_test::init(); - - fake_v5_round_trip_for_network(Network::Mainnet); - fake_v5_round_trip_for_network(Network::Testnet); + for network in Network::iter() { + fake_v5_round_trip_for_network(network); + } } fn fake_v5_round_trip_for_network(network: Network) { @@ -491,9 +491,9 @@ fn invalid_orchard_nullifier() { #[test] fn fake_v5_librustzcash_round_trip() { let _init_guard = zebra_test::init(); - - fake_v5_librustzcash_round_trip_for_network(Network::Mainnet); - fake_v5_librustzcash_round_trip_for_network(Network::Testnet); + for network in Network::iter() { + fake_v5_librustzcash_round_trip_for_network(network); + } } fn fake_v5_librustzcash_round_trip_for_network(network: Network) { @@ -931,9 +931,9 @@ fn zip244_sighash() -> Result<()> { #[test] fn binding_signatures() { let _init_guard = zebra_test::init(); - - binding_signatures_for_network(Network::Mainnet); - binding_signatures_for_network(Network::Testnet); + for network in Network::iter() { + binding_signatures_for_network(network); + } } fn binding_signatures_for_network(network: Network) { diff --git a/zebra-chain/src/transparent/address.rs b/zebra-chain/src/transparent/address.rs index 0c7ab62d..47404128 100644 --- a/zebra-chain/src/transparent/address.rs +++ b/zebra-chain/src/transparent/address.rs @@ -7,7 +7,7 @@ use secp256k1::PublicKey; use sha2::Sha256; use crate::{ - parameters::Network, + parameters::NetworkKind, serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}, transparent::{opcodes::OpCode, Script}, }; @@ -29,15 +29,11 @@ use proptest::prelude::*; #[derive( Clone, Eq, PartialEq, Hash, serde_with::SerializeDisplay, serde_with::DeserializeFromStr, )] -#[cfg_attr( - any(test, feature = "proptest-impl"), - derive(proptest_derive::Arbitrary) -)] pub enum Address { /// P2SH (Pay to Script Hash) addresses PayToScriptHash { /// Production, test, or other network - network: Network, + network_kind: NetworkKind, /// 20 bytes specifying a script hash. script_hash: [u8; 20], }, @@ -45,7 +41,7 @@ pub enum Address { /// P2PKH (Pay to Public Key Hash) addresses PayToPublicKeyHash { /// Production, test, or other network - network: Network, + network_kind: NetworkKind, /// 20 bytes specifying a public key hash, which is a RIPEMD-160 /// hash of a SHA-256 hash of a compressed ECDSA key encoding. pub_key_hash: [u8; 20], @@ -58,17 +54,17 @@ impl fmt::Debug for Address { match self { Address::PayToScriptHash { - network, + network_kind, script_hash, } => debug_struct - .field("network", network) + .field("network_kind", network_kind) .field("script_hash", &hex::encode(script_hash)) .finish(), Address::PayToPublicKeyHash { - network, + network_kind, pub_key_hash, } => debug_struct - .field("network", network) + .field("network_kind", network_kind) .field("pub_key_hash", &hex::encode(pub_key_hash)) .finish(), } @@ -101,17 +97,17 @@ impl ZcashSerialize for Address { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { match self { Address::PayToScriptHash { - network, + network_kind, script_hash, } => { - writer.write_all(&network.b58_script_address_prefix())?; + writer.write_all(&network_kind.b58_script_address_prefix())?; writer.write_all(script_hash)? } Address::PayToPublicKeyHash { - network, + network_kind, pub_key_hash, } => { - writer.write_all(&network.b58_pubkey_address_prefix())?; + writer.write_all(&network_kind.b58_pubkey_address_prefix())?; writer.write_all(pub_key_hash)? } } @@ -131,25 +127,25 @@ impl ZcashDeserialize for Address { match version_bytes { zcash_primitives::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX => { Ok(Address::PayToScriptHash { - network: Network::Mainnet, + network_kind: NetworkKind::Mainnet, script_hash: hash_bytes, }) } zcash_primitives::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX => { Ok(Address::PayToScriptHash { - network: Network::Testnet, + network_kind: NetworkKind::Testnet, script_hash: hash_bytes, }) } zcash_primitives::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX => { Ok(Address::PayToPublicKeyHash { - network: Network::Mainnet, + network_kind: NetworkKind::Mainnet, pub_key_hash: hash_bytes, }) } zcash_primitives::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX => { Ok(Address::PayToPublicKeyHash { - network: Network::Testnet, + network_kind: NetworkKind::Testnet, pub_key_hash: hash_bytes, }) } @@ -160,22 +156,22 @@ impl ZcashDeserialize for Address { trait ToAddressWithNetwork { /// Convert `self` to an `Address`, given the current `network`. - fn to_address(&self, network: Network) -> Address; + fn to_address(&self, network: NetworkKind) -> Address; } impl ToAddressWithNetwork for Script { - fn to_address(&self, network: Network) -> Address { + fn to_address(&self, network_kind: NetworkKind) -> Address { Address::PayToScriptHash { - network, + network_kind, script_hash: Address::hash_payload(self.as_raw_bytes()), } } } impl ToAddressWithNetwork for PublicKey { - fn to_address(&self, network: Network) -> Address { + fn to_address(&self, network_kind: NetworkKind) -> Address { Address::PayToPublicKeyHash { - network, + network_kind, pub_key_hash: Address::hash_payload(&self.serialize()[..]), } } @@ -183,26 +179,26 @@ impl ToAddressWithNetwork for PublicKey { impl Address { /// Create an address for the given public key hash and network. - pub fn from_pub_key_hash(network: &Network, pub_key_hash: [u8; 20]) -> Self { + pub fn from_pub_key_hash(network_kind: NetworkKind, pub_key_hash: [u8; 20]) -> Self { Self::PayToPublicKeyHash { - network: network.clone(), + network_kind, pub_key_hash, } } /// Create an address for the given script hash and network. - pub fn from_script_hash(network: &Network, script_hash: [u8; 20]) -> Self { + pub fn from_script_hash(network_kind: NetworkKind, script_hash: [u8; 20]) -> Self { Self::PayToScriptHash { - network: network.clone(), + network_kind, script_hash, } } - /// Returns the network for this address. - pub fn network(&self) -> Network { + /// Returns the network kind for this address. + pub fn network_kind(&self) -> NetworkKind { match self { - Address::PayToScriptHash { network, .. } => network.clone(), - Address::PayToPublicKeyHash { network, .. } => network.clone(), + Address::PayToScriptHash { network_kind, .. } => *network_kind, + Address::PayToPublicKeyHash { network_kind, .. } => *network_kind, } } @@ -283,7 +279,7 @@ mod tests { ]) .expect("A PublicKey from slice"); - let t_addr = pub_key.to_address(Network::Mainnet); + let t_addr = pub_key.to_address(NetworkKind::Mainnet); assert_eq!(format!("{t_addr}"), "t1bmMa1wJDFdbc2TiURQP5BbBz6jHjUBuHq"); } @@ -298,7 +294,7 @@ mod tests { ]) .expect("A PublicKey from slice"); - let t_addr = pub_key.to_address(Network::Testnet); + let t_addr = pub_key.to_address(NetworkKind::Testnet); assert_eq!(format!("{t_addr}"), "tmTc6trRhbv96kGfA99i7vrFwb5p7BVFwc3"); } @@ -309,7 +305,7 @@ mod tests { let script = Script::new(&[0u8; 20]); - let t_addr = script.to_address(Network::Mainnet); + let t_addr = script.to_address(NetworkKind::Mainnet); assert_eq!(format!("{t_addr}"), "t3Y5pHwfgHbS6pDjj1HLuMFxhFFip1fcJ6g"); } @@ -320,7 +316,7 @@ mod tests { let script = Script::new(&[0; 20]); - let t_addr = script.to_address(Network::Testnet); + let t_addr = script.to_address(NetworkKind::Testnet); assert_eq!(format!("{t_addr}"), "t2L51LcmpA43UMvKTw2Lwtt9LMjwyqU2V1P"); } @@ -342,7 +338,7 @@ mod tests { assert_eq!( format!("{t_addr:?}"), - "TransparentAddress { network: Mainnet, script_hash: \"7d46a730d31f97b1930d3368a967c309bd4d136a\" }" + "TransparentAddress { network_kind: Mainnet, script_hash: \"7d46a730d31f97b1930d3368a967c309bd4d136a\" }" ); } } diff --git a/zebra-chain/src/transparent/arbitrary.rs b/zebra-chain/src/transparent/arbitrary.rs index f7678bdc..d275b8e9 100644 --- a/zebra-chain/src/transparent/arbitrary.rs +++ b/zebra-chain/src/transparent/arbitrary.rs @@ -1,8 +1,8 @@ use proptest::{arbitrary::any, collection::vec, prelude::*}; -use crate::{block, LedgerState}; +use crate::{block, parameters::NetworkKind, LedgerState}; -use super::{CoinbaseData, Input, OutPoint, Script, GENESIS_COINBASE_DATA}; +use super::{Address, CoinbaseData, Input, OutPoint, Script, GENESIS_COINBASE_DATA}; impl Input { /// Construct a strategy for creating valid-ish vecs of Inputs. @@ -46,3 +46,27 @@ impl Arbitrary for Input { type Strategy = BoxedStrategy; } + +impl Arbitrary for Address { + type Parameters = (); + + fn arbitrary_with(_args: ()) -> Self::Strategy { + any::<(bool, bool, [u8; 20])>() + .prop_map(|(is_mainnet, is_p2pkh, hash_bytes)| { + let network = if is_mainnet { + NetworkKind::Mainnet + } else { + NetworkKind::Testnet + }; + + if is_p2pkh { + Address::from_pub_key_hash(network, hash_bytes) + } else { + Address::from_script_hash(network, hash_bytes) + } + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/transparent/tests/vectors.rs b/zebra-chain/src/transparent/tests/vectors.rs index d3ceae41..f83dbf1c 100644 --- a/zebra-chain/src/transparent/tests/vectors.rs +++ b/zebra-chain/src/transparent/tests/vectors.rs @@ -67,15 +67,17 @@ fn get_transparent_output_address() -> Result<()> { let addr = transparent_output_address(&transaction.outputs()[0], &Network::Mainnet) .expect("should return address"); assert_eq!(addr.to_string(), "t3M5FDmPfWNRG3HRLddbicsuSCvKuk9hxzZ"); - let addr = transparent_output_address(&transaction.outputs()[0], &Network::Testnet) - .expect("should return address"); + let addr = + transparent_output_address(&transaction.outputs()[0], &Network::new_default_testnet()) + .expect("should return address"); assert_eq!(addr.to_string(), "t294SGSVoNq2daz15ZNbmAW65KQZ5e3nN5G"); // Public key hash e4ff5512ffafe9287992a1cd177ca6e408e03003 let addr = transparent_output_address(&transaction.outputs()[1], &Network::Mainnet) .expect("should return address"); assert_eq!(addr.to_string(), "t1ekRwsd4LaSsd6NXgsx66q2HxQWTLCF44y"); - let addr = transparent_output_address(&transaction.outputs()[1], &Network::Testnet) - .expect("should return address"); + let addr = + transparent_output_address(&transaction.outputs()[1], &Network::new_default_testnet()) + .expect("should return address"); assert_eq!(addr.to_string(), "tmWbBGi7TjExNmLZyMcFpxVh3ZPbGrpbX3H"); Ok(()) @@ -84,9 +86,9 @@ fn get_transparent_output_address() -> Result<()> { #[test] fn get_transparent_output_address_with_blocks() { let _init_guard = zebra_test::init(); - - get_transparent_output_address_with_blocks_for_network(Network::Mainnet); - get_transparent_output_address_with_blocks_for_network(Network::Testnet); + for network in Network::iter() { + get_transparent_output_address_with_blocks_for_network(network); + } } /// Test that the block test vector indexes match the heights in the block data, diff --git a/zebra-chain/src/work/difficulty.rs b/zebra-chain/src/work/difficulty.rs index 9c5252c2..c1321092 100644 --- a/zebra-chain/src/work/difficulty.rs +++ b/zebra-chain/src/work/difficulty.rs @@ -699,7 +699,8 @@ impl ParameterDifficulty for Network { /* 2^243 - 1 */ Network::Mainnet => (U256::one() << 243) - 1, /* 2^251 - 1 */ - Network::Testnet => (U256::one() << 251) - 1, + // TODO: Add a `target_difficulty_limit` field to `testnet::Parameters` to return here. + Network::Testnet(_params) => (U256::one() << 251) - 1, }; // `zcashd` converts the PoWLimit into a compact representation before diff --git a/zebra-chain/src/work/difficulty/tests/vectors.rs b/zebra-chain/src/work/difficulty/tests/vectors.rs index 9f9c0354..09250e6b 100644 --- a/zebra-chain/src/work/difficulty/tests/vectors.rs +++ b/zebra-chain/src/work/difficulty/tests/vectors.rs @@ -263,8 +263,9 @@ fn compact_bitcoin_test_vectors() { /// Test blocks using CompactDifficulty. #[test] fn block_difficulty() -> Result<(), Report> { - block_difficulty_for_network(Network::Mainnet)?; - block_difficulty_for_network(Network::Testnet)?; + for network in Network::iter() { + block_difficulty_for_network(network)?; + } Ok(()) } @@ -349,8 +350,9 @@ fn block_difficulty_for_network(network: Network) -> Result<(), Report> { /// Test that the genesis block threshold is PowLimit #[test] fn genesis_block_difficulty() -> Result<(), Report> { - genesis_block_difficulty_for_network(Network::Mainnet)?; - genesis_block_difficulty_for_network(Network::Testnet)?; + for network in Network::iter() { + genesis_block_difficulty_for_network(network)?; + } Ok(()) } @@ -454,7 +456,10 @@ fn check_testnet_minimum_difficulty_block(height: block::Height) -> Result<(), R // threshold, as documented in ZIP-205 and ZIP-208: // https://zips.z.cash/zip-0205#change-to-difficulty-adjustment-on-testnet // https://zips.z.cash/zip-0208#minimum-difficulty-blocks-on-testnet - match NetworkUpgrade::minimum_difficulty_spacing_for_height(&Network::Testnet, height) { + match NetworkUpgrade::minimum_difficulty_spacing_for_height( + &Network::new_default_testnet(), + height, + ) { None => Err(eyre!("the minimum difficulty rule is not active"))?, Some(spacing) if (time_gap <= spacing) => Err(eyre!( "minimum difficulty block times must be more than 6 target spacing intervals apart" @@ -477,12 +482,12 @@ fn check_testnet_minimum_difficulty_block(height: block::Height) -> Result<(), R /// SPANDOC: Check that the testnet minimum difficulty is the PoWLimit {?height, ?threshold, ?hash} { - assert_eq!(threshold, Network::Testnet.target_difficulty_limit(), + assert_eq!(threshold, Network::new_default_testnet().target_difficulty_limit(), "testnet minimum difficulty thresholds should be equal to the PoWLimit. Hint: Blocks with large gaps are allowed to have the minimum difficulty, but it's not required."); // all blocks pass the minimum difficulty threshold, even if they aren't minimum // difficulty blocks, because it's the lowest permitted difficulty assert!( - hash <= Network::Testnet.target_difficulty_limit(), + hash <= Network::new_default_testnet().target_difficulty_limit(), "testnet minimum difficulty hashes must be less than the PoWLimit" ); } diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 39382e7a..c3389782 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -29,7 +29,7 @@ pub fn funding_stream_values( let mut results = HashMap::new(); if height >= canopy_height { - let range = FUNDING_STREAM_HEIGHT_RANGES.get(network).unwrap(); + let range = FUNDING_STREAM_HEIGHT_RANGES.get(&network.kind()).unwrap(); if range.contains(&height) { let block_subsidy = block_subsidy(height, network)?; for (&receiver, &numerator) in FUNDING_STREAM_RECEIVER_NUMERATORS.iter() { @@ -83,7 +83,10 @@ fn funding_stream_address_index(height: Height, network: &Network) -> usize { .checked_add(funding_stream_address_period(height, network)) .expect("no overflow should happen in this sum") .checked_sub(funding_stream_address_period( - FUNDING_STREAM_HEIGHT_RANGES.get(network).unwrap().start, + FUNDING_STREAM_HEIGHT_RANGES + .get(&network.kind()) + .unwrap() + .start, network, )) .expect("no overflow should happen in this sub") as usize; @@ -105,7 +108,7 @@ pub fn funding_stream_address( ) -> transparent::Address { let index = funding_stream_address_index(height, network); let address = &FUNDING_STREAM_ADDRESSES - .get(network) + .get(&network.kind()) .expect("there is always another hash map as value for a given valid network") .get(&receiver) .expect("in the inner hash map there is always a vector of strings with addresses")[index]; diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 763fa172..7f6011a4 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -44,7 +44,7 @@ fn test_funding_stream_values() -> Result<(), Report> { ); // funding stream period is ending - let range = FUNDING_STREAM_HEIGHT_RANGES.get(network).unwrap(); + let range = FUNDING_STREAM_HEIGHT_RANGES.get(&network.kind()).unwrap(); let end = range.end; let last = end - 1; @@ -68,7 +68,7 @@ fn test_funding_stream_addresses() -> Result<(), Report> { let address = transparent::Address::from_str(address).expect("address should deserialize"); assert_eq!( - &address.network(), + &address.network_kind(), network, "incorrect network for {receiver:?} funding stream address constant: {address}", ); diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 37ba1cfe..d9fbe0a8 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -122,9 +122,9 @@ mod test { #[test] fn halving_test() -> Result<(), Report> { let _init_guard = zebra_test::init(); - - halving_for_network(&Network::Mainnet)?; - halving_for_network(&Network::Testnet)?; + for network in Network::iter() { + halving_for_network(&network)?; + } Ok(()) } @@ -249,8 +249,9 @@ mod test { fn block_subsidy_test() -> Result<(), Report> { let _init_guard = zebra_test::init(); - block_subsidy_for_network(&Network::Mainnet)?; - block_subsidy_for_network(&Network::Testnet)?; + for network in Network::iter() { + block_subsidy_for_network(&network)?; + } Ok(()) } diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index 44e577b1..6dec96df 100644 --- a/zebra-consensus/src/block/tests.rs +++ b/zebra-consensus/src/block/tests.rs @@ -181,9 +181,9 @@ fn coinbase_is_first_for_historical_blocks() -> Result<(), Report> { #[test] fn difficulty_is_valid_for_historical_blocks() -> Result<(), Report> { let _init_guard = zebra_test::init(); - - difficulty_is_valid_for_network(Network::Mainnet)?; - difficulty_is_valid_for_network(Network::Testnet)?; + for network in Network::iter() { + difficulty_is_valid_for_network(network)?; + } Ok(()) } @@ -285,9 +285,9 @@ fn equihash_is_valid_for_historical_blocks() -> Result<(), Report> { #[test] fn subsidy_is_valid_for_historical_blocks() -> Result<(), Report> { let _init_guard = zebra_test::init(); - - subsidy_is_valid_for_network(Network::Mainnet)?; - subsidy_is_valid_for_network(Network::Testnet)?; + for network in Network::iter() { + subsidy_is_valid_for_network(network)?; + } Ok(()) } @@ -388,9 +388,9 @@ fn coinbase_validation_failure() -> Result<(), Report> { #[test] fn funding_stream_validation() -> Result<(), Report> { let _init_guard = zebra_test::init(); - - funding_stream_validation_for_network(Network::Mainnet)?; - funding_stream_validation_for_network(Network::Testnet)?; + for network in Network::iter() { + funding_stream_validation_for_network(network)?; + } Ok(()) } @@ -463,9 +463,9 @@ fn funding_stream_validation_failure() -> Result<(), Report> { #[test] fn miner_fees_validation_success() -> Result<(), Report> { let _init_guard = zebra_test::init(); - - miner_fees_validation_for_network(Network::Mainnet)?; - miner_fees_validation_for_network(Network::Testnet)?; + for network in Network::iter() { + miner_fees_validation_for_network(network)?; + } Ok(()) } @@ -546,12 +546,14 @@ fn merkle_root_is_valid() -> Result<(), Report> { let _init_guard = zebra_test::init(); // test all original blocks available, all blocks validate - merkle_root_is_valid_for_network(Network::Mainnet)?; - merkle_root_is_valid_for_network(Network::Testnet)?; + for network in Network::iter() { + merkle_root_is_valid_for_network(network)?; + } // create and test fake blocks with v5 transactions, all blocks fail validation - merkle_root_fake_v5_for_network(Network::Mainnet)?; - merkle_root_fake_v5_for_network(Network::Testnet)?; + for network in Network::iter() { + merkle_root_fake_v5_for_network(network)?; + } Ok(()) } @@ -683,8 +685,9 @@ fn legacy_sigops_count_for_historic_blocks() { fn transaction_expiration_height_validation() -> Result<(), Report> { let _init_guard = zebra_test::init(); - transaction_expiration_height_for_network(&Network::Mainnet)?; - transaction_expiration_height_for_network(&Network::Testnet)?; + for network in Network::iter() { + transaction_expiration_height_for_network(&network)?; + } Ok(()) } diff --git a/zebra-consensus/src/checkpoint/list.rs b/zebra-consensus/src/checkpoint/list.rs index 2b45e436..88799848 100644 --- a/zebra-consensus/src/checkpoint/list.rs +++ b/zebra-consensus/src/checkpoint/list.rs @@ -57,7 +57,10 @@ impl ParameterCheckpoint for Network { // zcash-cli getblockhash 0 Network::Mainnet => "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08", // zcash-cli -testnet getblockhash 0 - Network::Testnet => "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38", + // TODO: Add a `genesis_hash` field to `testnet::Parameters` and return it here (#8366) + Network::Testnet(_params) => { + "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38" + } } .parse() .expect("hard-coded hash parses") @@ -65,11 +68,14 @@ impl ParameterCheckpoint for Network { fn checkpoint_list(&self) -> CheckpointList { // parse calls CheckpointList::from_list + // TODO: + // - Add a `genesis_hash` field to `testnet::Parameters` and return it here (#8366) + // - Try to disable checkpoints entirely for regtest and custom testnets let checkpoint_list: CheckpointList = match self { Network::Mainnet => MAINNET_CHECKPOINTS .parse() .expect("Hard-coded Mainnet checkpoint list parses and validates"), - Network::Testnet => TESTNET_CHECKPOINTS + Network::Testnet(_params) => TESTNET_CHECKPOINTS .parse() .expect("Hard-coded Testnet checkpoint list parses and validates"), }; @@ -142,9 +148,12 @@ impl CheckpointList { // Check that the list starts with the correct genesis block match checkpoints.iter().next() { + // TODO: If required (we may not need checkpoints at all in Regtest or custom testnets): + // move this check to `::checkpoint_list(&network)` method above (#8366), + // See Some((block::Height(0), hash)) if (hash == &Network::Mainnet.genesis_hash() - || hash == &Network::Testnet.genesis_hash()) => {} + || hash == &Network::new_default_testnet().genesis_hash()) => {} Some((block::Height(0), _)) => { Err("the genesis checkpoint does not match the Mainnet or Testnet genesis hash")? } diff --git a/zebra-consensus/src/checkpoint/list/tests.rs b/zebra-consensus/src/checkpoint/list/tests.rs index 0588a365..037dd027 100644 --- a/zebra-consensus/src/checkpoint/list/tests.rs +++ b/zebra-consensus/src/checkpoint/list/tests.rs @@ -236,7 +236,7 @@ fn checkpoint_list_load_hard_coded() -> Result<(), BoxError> { .expect("hard-coded Testnet checkpoint list should parse"); let _ = Mainnet.checkpoint_list(); - let _ = Testnet.checkpoint_list(); + let _ = Network::new_default_testnet().checkpoint_list(); Ok(()) } @@ -248,7 +248,7 @@ fn checkpoint_list_hard_coded_mandatory_mainnet() -> Result<(), BoxError> { #[test] fn checkpoint_list_hard_coded_mandatory_testnet() -> Result<(), BoxError> { - checkpoint_list_hard_coded_mandatory(Testnet) + checkpoint_list_hard_coded_mandatory(Network::new_default_testnet()) } /// Check that the hard-coded lists cover the mandatory checkpoint @@ -274,7 +274,7 @@ fn checkpoint_list_hard_coded_max_gap_mainnet() -> Result<(), BoxError> { #[test] fn checkpoint_list_hard_coded_max_gap_testnet() -> Result<(), BoxError> { - checkpoint_list_hard_coded_max_gap(Testnet) + checkpoint_list_hard_coded_max_gap(Network::new_default_testnet()) } /// Check that the hard-coded checkpoints are within [`MAX_CHECKPOINT_HEIGHT_GAP`], diff --git a/zebra-consensus/src/checkpoint/tests.rs b/zebra-consensus/src/checkpoint/tests.rs index b2493540..b60540d2 100644 --- a/zebra-consensus/src/checkpoint/tests.rs +++ b/zebra-consensus/src/checkpoint/tests.rs @@ -205,8 +205,9 @@ async fn multi_item_checkpoint_list() -> Result<(), Report> { #[tokio::test(flavor = "multi_thread")] async fn continuous_blockchain_no_restart() -> Result<(), Report> { - continuous_blockchain(None, Mainnet).await?; - continuous_blockchain(None, Testnet).await?; + for network in Network::iter() { + continuous_blockchain(None, network).await?; + } Ok(()) } @@ -216,7 +217,11 @@ async fn continuous_blockchain_restart() -> Result<(), Report> { continuous_blockchain(Some(block::Height(height.try_into().unwrap())), Mainnet).await?; } for height in 0..zebra_test::vectors::CONTINUOUS_TESTNET_BLOCKS.len() { - continuous_blockchain(Some(block::Height(height.try_into().unwrap())), Testnet).await?; + continuous_blockchain( + Some(block::Height(height.try_into().unwrap())), + Network::new_default_testnet(), + ) + .await?; } Ok(()) } diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 8dfd42bd..e7cf8f91 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -7,7 +7,7 @@ use lazy_static::lazy_static; use zebra_chain::{ amount::COIN, block::{Height, HeightDiff}, - parameters::{Network, NetworkUpgrade}, + parameters::{Network, NetworkKind, NetworkUpgrade}, }; /// An initial period from Genesis to this Height where the block subsidy is gradually incremented. [What is slow-start mining][slow-mining] @@ -104,15 +104,18 @@ lazy_static! { /// as described in [protocol specification §7.10.1][7.10.1]. /// /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams - pub static ref FUNDING_STREAM_HEIGHT_RANGES: HashMap> = { + // TODO: Move the value here to a field on `testnet::Parameters` (#8367) + pub static ref FUNDING_STREAM_HEIGHT_RANGES: HashMap> = { let mut hash_map = HashMap::new(); - hash_map.insert(Network::Mainnet, Height(1_046_400)..Height(2_726_400)); - hash_map.insert(Network::Testnet, Height(1_028_500)..Height(2_796_000)); + hash_map.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 }; /// Convenient storage for all addresses, for all receivers and networks - pub static ref FUNDING_STREAM_ADDRESSES: HashMap>> = { + // 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); // Mainnet addresses @@ -120,14 +123,14 @@ lazy_static! { mainnet_addresses.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); mainnet_addresses.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); mainnet_addresses.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_MAINNET.iter().map(|a| a.to_string()).collect()); - addresses_by_network.insert(Network::Mainnet, mainnet_addresses); + addresses_by_network.insert(NetworkKind::Mainnet, mainnet_addresses); // Testnet addresses let mut testnet_addresses = HashMap::with_capacity(3); testnet_addresses.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); testnet_addresses.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); testnet_addresses.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); - addresses_by_network.insert(Network::Testnet, testnet_addresses); + addresses_by_network.insert(NetworkKind::Testnet, testnet_addresses); addresses_by_network }; @@ -215,9 +218,11 @@ impl ParameterSubsidy for Network { fn num_funding_streams(&self) -> usize { match self { Network::Mainnet => FUNDING_STREAMS_NUM_ADDRESSES_MAINNET, - Network::Testnet => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET, + // TODO: Check what zcashd does here, consider adding a field to `NetworkParamters` to make this configurable. + Network::Testnet(_params) => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET, } } + fn height_for_first_halving(&self) -> Height { // First halving on Mainnet is at Canopy // while in Testnet is at block constant height of `1_116_000` @@ -226,7 +231,8 @@ impl ParameterSubsidy for Network { Network::Mainnet => NetworkUpgrade::Canopy .activation_height(self) .expect("canopy activation height should be available"), - Network::Testnet => FIRST_HALVING_TESTNET, + // TODO: Check what zcashd does here, consider adding a field to `testnet::Parameters` to make this configurable. + Network::Testnet(_params) => FIRST_HALVING_TESTNET, } } } diff --git a/zebra-consensus/src/router.rs b/zebra-consensus/src/router.rs index d8587309..d9545ff0 100644 --- a/zebra-consensus/src/router.rs +++ b/zebra-consensus/src/router.rs @@ -240,13 +240,9 @@ where // Make sure the state contains the known best chain checkpoints, in a separate thread. - let (checkpoint_state_service, checkpoint_sync, network_clone) = { - let checkpoint_state_service = state_service.clone(); - let checkpoint_sync = config.checkpoint_sync; - let network_clone = network.clone(); - - (checkpoint_state_service, checkpoint_sync, network_clone) - }; + let checkpoint_state_service = state_service.clone(); + let checkpoint_sync = config.checkpoint_sync; + let checkpoint_network = network.clone(); let state_checkpoint_verify_handle = tokio::task::spawn( // TODO: move this into an async function? @@ -269,7 +265,7 @@ where // > activation block hashes given in § 3.12 ‘Mainnet and Testnet’ on p. 20. // // - let full_checkpoints = network_clone.checkpoint_list(); + let full_checkpoints = checkpoint_network.checkpoint_list(); let mut already_warned = false; for (height, checkpoint_hash) in full_checkpoints.iter() { diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 758c13fa..6f5c3639 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -42,13 +42,8 @@ mod prop; fn v5_fake_transactions() -> Result<(), Report> { let _init_guard = zebra_test::init(); - let networks = vec![ - (Network::Mainnet, zebra_test::vectors::MAINNET_BLOCKS.iter()), - (Network::Testnet, zebra_test::vectors::TESTNET_BLOCKS.iter()), - ]; - - for (network, blocks) in networks { - for transaction in fake_v5_transactions_for_network(&network, blocks) { + for network in Network::iter() { + for transaction in fake_v5_transactions_for_network(&network, network.block_iter()) { match check::has_inputs_and_outputs(&transaction) { Ok(()) => (), Err(TransactionError::NoInputs) | Err(TransactionError::NoOutputs) => (), @@ -858,16 +853,12 @@ async fn v5_transaction_is_rejected_before_nu5_activation() { const V5_TRANSACTION_VERSION: u32 = 5; let canopy = NetworkUpgrade::Canopy; - let networks = vec![ - (Network::Mainnet, zebra_test::vectors::MAINNET_BLOCKS.iter()), - (Network::Testnet, zebra_test::vectors::TESTNET_BLOCKS.iter()), - ]; - for (network, blocks) in networks { + for network in Network::iter() { let state_service = service_fn(|_| async { unreachable!("Service should not be called") }); let verifier = Verifier::new(&network, state_service); - let transaction = fake_v5_transactions_for_network(&network, blocks) + let transaction = fake_v5_transactions_for_network(&network, network.block_iter()) .next_back() .expect("At least one fake V5 transaction in the test vectors"); @@ -899,7 +890,7 @@ fn v5_transaction_is_accepted_after_nu5_activation_mainnet() { #[test] fn v5_transaction_is_accepted_after_nu5_activation_testnet() { - v5_transaction_is_accepted_after_nu5_activation_for_network(Network::Testnet) + v5_transaction_is_accepted_after_nu5_activation_for_network(Network::new_default_testnet()) } fn v5_transaction_is_accepted_after_nu5_activation_for_network(network: Network) { @@ -1544,7 +1535,7 @@ fn v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejecte /// Test if V5 transaction with transparent funds is accepted. #[tokio::test] async fn v5_transaction_with_transparent_transfer_is_accepted() { - let network = Network::Testnet; + let network = Network::new_default_testnet(); let network_upgrade = NetworkUpgrade::Nu5; let nu5_activation_height = network_upgrade @@ -1601,12 +1592,13 @@ async fn v5_transaction_with_transparent_transfer_is_accepted() { /// accepted. #[tokio::test] async fn v5_transaction_with_last_valid_expiry_height() { + let network = Network::new_default_testnet(); let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let verifier = Verifier::new(&Network::Testnet, state_service); + let verifier = Verifier::new(&network, state_service); let block_height = NetworkUpgrade::Nu5 - .activation_height(&Network::Testnet) + .activation_height(&network) .expect("Nu5 activation height for testnet is specified"); let fund_height = (block_height - 1).expect("fake source fund block height is too small"); let (input, output, known_utxos) = mock_transparent_transfer( @@ -1646,12 +1638,13 @@ async fn v5_transaction_with_last_valid_expiry_height() { /// is equal to the height of the block the transaction belongs to. #[tokio::test] async fn v5_coinbase_transaction_expiry_height() { + let network = Network::new_default_testnet(); let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let verifier = Verifier::new(&Network::Testnet, state_service); + let verifier = Verifier::new(&network, state_service); let block_height = NetworkUpgrade::Nu5 - .activation_height(&Network::Testnet) + .activation_height(&network) .expect("Nu5 activation height for testnet is specified"); let (input, output) = mock_coinbase_transparent_output(block_height); @@ -1761,12 +1754,14 @@ async fn v5_coinbase_transaction_expiry_height() { /// Tests if an expired non-coinbase V5 transaction is rejected. #[tokio::test] async fn v5_transaction_with_too_low_expiry_height() { + let network = Network::new_default_testnet(); + let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let verifier = Verifier::new(&Network::Testnet, state_service); + let verifier = Verifier::new(&network, state_service); let block_height = NetworkUpgrade::Nu5 - .activation_height(&Network::Testnet) + .activation_height(&network) .expect("Nu5 activation height for testnet is specified"); let fund_height = (block_height - 1).expect("fake source fund block height is too small"); let (input, output, known_utxos) = mock_transparent_transfer( @@ -1864,7 +1859,7 @@ async fn v5_transaction_with_exceeding_expiry_height() { /// Test if V5 coinbase transaction is accepted. #[tokio::test] async fn v5_coinbase_transaction_is_accepted() { - let network = Network::Testnet; + let network = Network::new_default_testnet(); let network_upgrade = NetworkUpgrade::Nu5; let nu5_activation_height = network_upgrade @@ -1916,7 +1911,7 @@ async fn v5_coinbase_transaction_is_accepted() { /// script prevents spending the source UTXO. #[tokio::test] async fn v5_transaction_with_transparent_transfer_is_rejected_by_the_script() { - let network = Network::Testnet; + let network = Network::new_default_testnet(); let network_upgrade = NetworkUpgrade::Nu5; let nu5_activation_height = network_upgrade @@ -2759,8 +2754,9 @@ fn add_to_sprout_pool_after_nu() { fn coinbase_outputs_are_decryptable_for_historical_blocks() -> Result<(), Report> { let _init_guard = zebra_test::init(); - coinbase_outputs_are_decryptable_for_historical_blocks_for_network(Network::Mainnet)?; - coinbase_outputs_are_decryptable_for_historical_blocks_for_network(Network::Testnet)?; + for network in Network::iter() { + coinbase_outputs_are_decryptable_for_historical_blocks_for_network(network)?; + } Ok(()) } @@ -2844,7 +2840,7 @@ fn fill_action_with_note_encryption_test_vector( /// viewing key. #[test] fn coinbase_outputs_are_decryptable_for_fake_v5_blocks() { - let network = Network::Testnet; + let network = Network::new_default_testnet(); for v in zebra_test::vectors::ORCHARD_NOTE_ENCRYPTION_ZERO_VECTOR.iter() { // Find a transaction with no inputs or outputs to use as base @@ -2886,7 +2882,7 @@ fn coinbase_outputs_are_decryptable_for_fake_v5_blocks() { /// viewing key. #[test] fn shielded_outputs_are_not_decryptable_for_fake_v5_blocks() { - let network = Network::Testnet; + let network = Network::new_default_testnet(); for v in zebra_test::vectors::ORCHARD_NOTE_ENCRYPTION_VECTOR.iter() { // Find a transaction with no inputs or outputs to use as base diff --git a/zebra-grpc/src/tests/snapshot.rs b/zebra-grpc/src/tests/snapshot.rs index 37acb59f..6b81a3c7 100644 --- a/zebra-grpc/src/tests/snapshot.rs +++ b/zebra-grpc/src/tests/snapshot.rs @@ -38,7 +38,7 @@ async fn test_grpc_response_data() { zebra_test::net::random_known_port() ), test_mocked_rpc_response_data_for_network( - Network::Testnet, + Network::new_default_testnet(), zebra_test::net::random_known_port() ), ); diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index da989f99..2ee59c88 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -15,7 +15,7 @@ use tempfile::NamedTempFile; use tokio::{fs, io::AsyncWriteExt}; use tracing::Span; -use zebra_chain::parameters::Network; +use zebra_chain::parameters::{testnet, Network, NetworkKind}; use crate::{ constants::{ @@ -222,10 +222,16 @@ impl Config { } /// Returns the initial seed peer hostnames for the configured network. - pub fn initial_peer_hostnames(&self) -> &IndexSet { - match self.network { - Network::Mainnet => &self.initial_mainnet_peers, - Network::Testnet => &self.initial_testnet_peers, + pub fn initial_peer_hostnames(&self) -> IndexSet { + match &self.network { + Network::Mainnet => self.initial_mainnet_peers.clone(), + 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: + // + Network::Testnet(_params) => IndexSet::new(), } } @@ -623,7 +629,8 @@ impl<'de> Deserialize<'de> for Config { #[serde(deny_unknown_fields, default)] struct DConfig { listen_addr: String, - network: Network, + network: NetworkKind, + testnet_parameters: Option, initial_mainnet_peers: IndexSet, initial_testnet_peers: IndexSet, cache_dir: CacheDir, @@ -638,7 +645,8 @@ impl<'de> Deserialize<'de> for Config { let config = Config::default(); Self { listen_addr: "0.0.0.0".to_string(), - network: config.network, + network: Default::default(), + testnet_parameters: None, initial_mainnet_peers: config.initial_mainnet_peers, initial_testnet_peers: config.initial_testnet_peers, cache_dir: config.cache_dir, @@ -651,7 +659,8 @@ impl<'de> Deserialize<'de> for Config { let DConfig { listen_addr, - network, + network: network_kind, + testnet_parameters, initial_mainnet_peers, initial_testnet_peers, cache_dir, @@ -660,6 +669,24 @@ 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. + assert_eq!( + network_kind, + NetworkKind::Testnet, + "set network to 'Testnet' to use configured testnet parameters" + ); + + Network::new_configured_testnet(network_params) + } else { + // Convert to default `Network` for a `NetworkKind` if there are no testnet parameters. + match network_kind { + NetworkKind::Mainnet => Network::Mainnet, + NetworkKind::Testnet => Network::new_default_testnet(), + NetworkKind::Regtest => unimplemented!("Regtest is not yet implemented in Zebra"), + } + }; + let listen_addr = match listen_addr.parse::() { Ok(socket) => Ok(socket), Err(_) => match listen_addr.parse::() { diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 7d7543d8..363c0571 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -16,6 +16,7 @@ use crate::protocol::external::types::*; use zebra_chain::{ parameters::{ Network::{self, *}, + NetworkKind, NetworkUpgrade::*, }, serialization::Duration32, @@ -392,11 +393,12 @@ lazy_static! { /// /// The minimum network protocol version typically changes after Mainnet and /// Testnet network upgrades. - pub static ref INITIAL_MIN_NETWORK_PROTOCOL_VERSION: HashMap = { + // TODO: Move the value here to a field on `testnet::Parameters` (#8367) + pub static ref INITIAL_MIN_NETWORK_PROTOCOL_VERSION: HashMap = { let mut hash_map = HashMap::new(); - hash_map.insert(Mainnet, Version::min_specified_for_upgrade(&Mainnet, Nu5)); - hash_map.insert(Testnet, Version::min_specified_for_upgrade(&Testnet, Nu5)); + 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 }; diff --git a/zebra-network/src/isolated/tests/vectors.rs b/zebra-network/src/isolated/tests/vectors.rs index 94f75fe5..a037fcb5 100644 --- a/zebra-network/src/isolated/tests/vectors.rs +++ b/zebra-network/src/isolated/tests/vectors.rs @@ -14,8 +14,6 @@ use crate::{ use super::super::*; -use Network::*; - /// Test that `connect_isolated` sends a version message with minimal distinguishing features, /// when sent over TCP. #[tokio::test] @@ -26,8 +24,9 @@ async fn connect_isolated_sends_anonymised_version_message_tcp() { return; } - connect_isolated_sends_anonymised_version_message_tcp_net(Mainnet).await; - connect_isolated_sends_anonymised_version_message_tcp_net(Testnet).await; + for network in Network::iter() { + connect_isolated_sends_anonymised_version_message_tcp_net(network).await; + } } async fn connect_isolated_sends_anonymised_version_message_tcp_net(network: Network) { @@ -82,9 +81,9 @@ async fn connect_isolated_sends_anonymised_version_message_tcp_net(network: Netw #[tokio::test] async fn connect_isolated_sends_anonymised_version_message_mem() { let _init_guard = zebra_test::init(); - - connect_isolated_sends_anonymised_version_message_mem_net(Mainnet).await; - connect_isolated_sends_anonymised_version_message_mem_net(Testnet).await; + for network in Network::iter() { + connect_isolated_sends_anonymised_version_message_mem_net(network).await; + } } async fn connect_isolated_sends_anonymised_version_message_mem_net(network: Network) { diff --git a/zebra-network/src/peer_set/initialize/tests/vectors.rs b/zebra-network/src/peer_set/initialize/tests/vectors.rs index 54fd25a2..c9e0c26f 100644 --- a/zebra-network/src/peer_set/initialize/tests/vectors.rs +++ b/zebra-network/src/peer_set/initialize/tests/vectors.rs @@ -80,8 +80,9 @@ async fn local_listener_unspecified_port_unspecified_addr_v4() { // these tests might fail on machines with no configured IPv4 addresses // (localhost should be enough) - local_listener_port_with("0.0.0.0:0".parse().unwrap(), Mainnet).await; - local_listener_port_with("0.0.0.0:0".parse().unwrap(), Testnet).await; + for network in Network::iter() { + local_listener_port_with("0.0.0.0:0".parse().unwrap(), network).await; + } } /// Test that zebra-network discovers dynamic bind-to-all-interfaces listener ports, @@ -101,8 +102,9 @@ async fn local_listener_unspecified_port_unspecified_addr_v6() { } // these tests might fail on machines with no configured IPv6 addresses - local_listener_port_with("[::]:0".parse().unwrap(), Mainnet).await; - local_listener_port_with("[::]:0".parse().unwrap(), Testnet).await; + for network in Network::iter() { + local_listener_port_with("[::]:0".parse().unwrap(), network).await; + } } /// Test that zebra-network discovers dynamic localhost listener ports, @@ -116,8 +118,9 @@ async fn local_listener_unspecified_port_localhost_addr_v4() { } // these tests might fail on machines with unusual IPv4 localhost configs - local_listener_port_with("127.0.0.1:0".parse().unwrap(), Mainnet).await; - local_listener_port_with("127.0.0.1:0".parse().unwrap(), Testnet).await; + for network in Network::iter() { + local_listener_port_with("127.0.0.1:0".parse().unwrap(), network).await; + } } /// Test that zebra-network discovers dynamic localhost listener ports, @@ -135,8 +138,9 @@ async fn local_listener_unspecified_port_localhost_addr_v6() { } // these tests might fail on machines with no configured IPv6 addresses - local_listener_port_with("[::1]:0".parse().unwrap(), Mainnet).await; - local_listener_port_with("[::1]:0".parse().unwrap(), Testnet).await; + for network in Network::iter() { + local_listener_port_with("[::1]:0".parse().unwrap(), network).await; + } } /// Test that zebra-network propagates fixed localhost listener ports to the `AddressBook`. @@ -150,8 +154,9 @@ async fn local_listener_fixed_port_localhost_addr_v4() { return; } - local_listener_port_with(SocketAddr::new(localhost_v4, random_known_port()), Mainnet).await; - local_listener_port_with(SocketAddr::new(localhost_v4, random_known_port()), Testnet).await; + for network in Network::iter() { + local_listener_port_with(SocketAddr::new(localhost_v4, random_known_port()), network).await; + } } /// Test that zebra-network propagates fixed localhost listener ports to the `AddressBook`. @@ -169,8 +174,9 @@ async fn local_listener_fixed_port_localhost_addr_v6() { return; } - local_listener_port_with(SocketAddr::new(localhost_v6, random_known_port()), Mainnet).await; - local_listener_port_with(SocketAddr::new(localhost_v6, random_known_port()), Testnet).await; + for network in Network::iter() { + local_listener_port_with(SocketAddr::new(localhost_v6, random_known_port()), network).await; + } } /// Test zebra-network with a peer limit of zero peers on mainnet. @@ -207,8 +213,15 @@ async fn peer_limit_zero_testnet() { let unreachable_inbound_service = service_fn(|_| async { unreachable!("inbound service should never be called") }); - let address_book = - init_with_peer_limit(0, unreachable_inbound_service, Testnet, None, None).await; + let address_book = init_with_peer_limit( + 0, + unreachable_inbound_service, + Network::new_default_testnet(), + None, + None, + ) + .await; + assert_eq!( address_book.lock().unwrap().peers().count(), 0, @@ -247,7 +260,14 @@ async fn peer_limit_one_testnet() { let nil_inbound_service = service_fn(|_| async { Ok(Response::Nil) }); - let _ = init_with_peer_limit(1, nil_inbound_service, Testnet, None, None).await; + let _ = init_with_peer_limit( + 1, + nil_inbound_service, + Network::new_default_testnet(), + None, + None, + ) + .await; // Let the crawler run for a while. tokio::time::sleep(CRAWLER_TEST_DURATION).await; @@ -285,7 +305,14 @@ async fn peer_limit_two_testnet() { let nil_inbound_service = service_fn(|_| async { Ok(Response::Nil) }); - let _ = init_with_peer_limit(2, nil_inbound_service, Testnet, None, None).await; + let _ = init_with_peer_limit( + 2, + nil_inbound_service, + Network::new_default_testnet(), + None, + None, + ) + .await; // Let the crawler run for a while. tokio::time::sleep(CRAWLER_TEST_DURATION).await; diff --git a/zebra-network/src/protocol/external/types.rs b/zebra-network/src/protocol/external/types.rs index 08e2aa38..0e35698a 100644 --- a/zebra-network/src/protocol/external/types.rs +++ b/zebra-network/src/protocol/external/types.rs @@ -8,7 +8,7 @@ use zebra_chain::{ }, }; -use crate::constants::{self, magics}; +use crate::constants::{self, magics, CURRENT_NETWORK_PROTOCOL_VERSION}; #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; @@ -31,7 +31,8 @@ impl ParameterMagic for Network { fn magic_value(&self) -> Magic { match self { Network::Mainnet => magics::MAINNET, - Network::Testnet => magics::TESTNET, + // TODO: Move `Magic` struct definition to `zebra-chain`, add it as a field in `testnet::Parameters`, and return it here. + Network::Testnet(_params) => magics::TESTNET, } } } @@ -80,7 +81,7 @@ impl Version { /// - after Zebra's local network is slow or shut down. fn initial_min_for_network(network: &Network) -> Version { *constants::INITIAL_MIN_NETWORK_PROTOCOL_VERSION - .get(network) + .get(&network.kind()) .expect("We always have a value for testnet or mainnet") } @@ -103,17 +104,22 @@ impl Version { // sync? zcashd accepts 170_002 or later during its initial sync. Version(match (network, network_upgrade) { (_, Genesis) | (_, BeforeOverwinter) => 170_002, - (Testnet, Overwinter) => 170_003, + (Testnet(params), Overwinter) if params.is_default_testnet() => 170_003, (Mainnet, Overwinter) => 170_005, + // TODO: Use 170_006 for (Testnet(params), Sapling) if params.is_regtest() (`Regtest` in zcashd uses + // version 170_006 for Sapling, and the same values as Testnet for other network upgrades.) (_, Sapling) => 170_007, - (Testnet, Blossom) => 170_008, + (Testnet(params), Blossom) if params.is_default_testnet() => 170_008, (Mainnet, Blossom) => 170_009, - (Testnet, Heartwood) => 170_010, + (Testnet(params), Heartwood) if params.is_default_testnet() => 170_010, (Mainnet, Heartwood) => 170_011, - (Testnet, Canopy) => 170_012, + (Testnet(params), Canopy) if params.is_default_testnet() => 170_012, (Mainnet, Canopy) => 170_013, - (Testnet, Nu5) => 170_050, + (Testnet(params), Nu5) if params.is_default_testnet() => 170_050, (Mainnet, Nu5) => 170_100, + + // It should be fine to reject peers with earlier network protocol versions on custom testnets for now. + (Testnet(_params), _) => CURRENT_NETWORK_PROTOCOL_VERSION.0, }) } } @@ -201,7 +207,7 @@ mod test { #[test] fn version_extremes_testnet() { - version_extremes(&Testnet) + version_extremes(&Network::new_default_testnet()) } /// Test the min_specified_for_upgrade and min_specified_for_height functions for `network` with @@ -229,7 +235,7 @@ mod test { #[test] fn version_consistent_testnet() { - version_consistent(&Testnet) + version_consistent(&Network::new_default_testnet()) } /// Check that the min_specified_for_upgrade and min_specified_for_height functions diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 9dba00aa..de47bba3 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -450,11 +450,11 @@ where // 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(), - network.clone(), + miner_address.network_kind(), + network.kind(), "incorrect miner address config: {miner_address} \ network.network {network} and miner address network {} must match", - miner_address.network(), + miner_address.network_kind(), ); } @@ -1084,7 +1084,7 @@ where return Ok(validate_address::Response::invalid()); } - if address.network() == network { + if address.network() == network.kind() { Ok(validate_address::Response { address: Some(raw_address), is_valid: true, @@ -1124,7 +1124,7 @@ where } }; - if address.network() == network { + if address.network() == network.kind() { Ok(z_validate_address::Response { is_valid: true, address: Some(raw_address), diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index e1c84f4a..3dae9507 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -11,11 +11,8 @@ use insta::dynamic_redaction; use tower::buffer::Buffer; use zebra_chain::{ - block::Block, - chain_tip::mock::MockChainTip, - parameters::Network::{Mainnet, Testnet}, - serialization::ZcashDeserializeInto, - subtree::NoteCommitmentSubtreeData, + block::Block, chain_tip::mock::MockChainTip, parameters::Network::Mainnet, + serialization::ZcashDeserializeInto, subtree::NoteCommitmentSubtreeData, }; use zebra_state::{ReadRequest, ReadResponse, MAX_ON_DISK_HEIGHT}; use zebra_test::mock_service::MockService; @@ -33,12 +30,13 @@ pub const EXCESSIVE_BLOCK_HEIGHT: u32 = MAX_ON_DISK_HEIGHT.0 + 1; #[tokio::test(flavor = "multi_thread")] async fn test_rpc_response_data() { let _init_guard = zebra_test::init(); + let default_testnet = Network::new_default_testnet(); tokio::join!( test_rpc_response_data_for_network(&Mainnet), - test_rpc_response_data_for_network(&Testnet), + test_rpc_response_data_for_network(&default_testnet), test_mocked_rpc_response_data_for_network(&Mainnet), - test_mocked_rpc_response_data_for_network(&Testnet), + test_mocked_rpc_response_data_for_network(&default_testnet), ); } 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 3c2da657..cc7d0b90 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 @@ -94,7 +94,10 @@ pub async fn test_responses( #[allow(clippy::unnecessary_struct_initialization)] let mining_config = crate::config::mining::Config { - miner_address: Some(transparent::Address::from_script_hash(network, [0xad; 20])), + miner_address: Some(transparent::Address::from_script_hash( + network.kind(), + [0xad; 20], + )), extra_coinbase_data: None, debug_like_zcashd: true, // TODO: Use default field values when optional features are enabled in tests #8183 diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 1591ab69..118c2ee3 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -706,7 +706,7 @@ async fn rpc_getaddresstxids_invalid_arguments() { async fn rpc_getaddresstxids_response() { let _init_guard = zebra_test::init(); - for network in [Mainnet, Testnet] { + for network in Network::iter() { let blocks: Vec> = network .blockchain_map() .iter() @@ -1245,6 +1245,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { amount::NonNegative, block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, chain_sync_status::MockSyncStatus, + parameters::NetworkKind, serialization::DateTime32, transaction::{zip317, VerifiedUnminedTx}, work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256}, @@ -1275,11 +1276,10 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { let mut mock_sync_status = MockSyncStatus::default(); mock_sync_status.set_is_close_to_tip(true); + let network = NetworkKind::Mainnet; let miner_address = match use_p2pkh { - false => Some(transparent::Address::from_script_hash(&Mainnet, [0x7e; 20])), - true => Some(transparent::Address::from_pub_key_hash( - &Mainnet, [0x7e; 20], - )), + false => Some(transparent::Address::from_script_hash(network, [0x7e; 20])), + true => Some(transparent::Address::from_pub_key_hash(network, [0x7e; 20])), }; #[allow(clippy::unnecessary_struct_initialization)] diff --git a/zebra-scan/src/service/scan_task/scan.rs b/zebra-scan/src/service/scan_task/scan.rs index b1207ff0..c078e326 100644 --- a/zebra-scan/src/service/scan_task/scan.rs +++ b/zebra-scan/src/service/scan_task/scan.rs @@ -384,7 +384,24 @@ pub fn scan_block( // TODO: Implement a check that returns early when the block height is below the Sapling // activation height. - let network: zcash_primitives::consensus::Network = network.into(); + 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, diff --git a/zebra-scan/src/storage/db/tests/snapshot.rs b/zebra-scan/src/storage/db/tests/snapshot.rs index 81dc1a71..93f37678 100644 --- a/zebra-scan/src/storage/db/tests/snapshot.rs +++ b/zebra-scan/src/storage/db/tests/snapshot.rs @@ -28,10 +28,7 @@ use std::collections::BTreeMap; use itertools::Itertools; -use zebra_chain::{ - block::Height, - parameters::Network::{self, *}, -}; +use zebra_chain::{block::Height, parameters::Network}; use zebra_state::{RawBytes, ReadDisk, SaplingScannedDatabaseIndex, TransactionLocation, KV}; use crate::storage::{db::ScannerDb, Storage}; @@ -45,8 +42,9 @@ use crate::storage::{db::ScannerDb, Storage}; fn test_database_format() { let _init_guard = zebra_test::init(); - test_database_format_with_network(Mainnet); - test_database_format_with_network(Testnet); + for network in Network::iter() { + test_database_format_with_network(network); + } } /// Snapshot raw and typed database formats for `network`. diff --git a/zebra-state/src/service/arbitrary.rs b/zebra-state/src/service/arbitrary.rs index 08eb42a8..308953ff 100644 --- a/zebra-state/src/service/arbitrary.rs +++ b/zebra-state/src/service/arbitrary.rs @@ -89,13 +89,14 @@ impl PreparedChain { // The history tree only works with Heartwood onward. // Since the network will be chosen later, we pick the larger // between the mainnet and testnet Heartwood activation heights. - let main_height = NetworkUpgrade::Heartwood - .activation_height(&Network::Mainnet) - .expect("must have height"); - let test_height = NetworkUpgrade::Heartwood - .activation_height(&Network::Testnet) - .expect("must have height"); - let height = std::cmp::max(main_height, test_height); + let height = Network::iter() + .map(|network| { + NetworkUpgrade::Heartwood + .activation_height(&network) + .expect("must have height") + }) + .max() + .expect("Network::iter() must return non-empty iterator"); PreparedChain { ledger_strategy: Some(LedgerState::height_strategy( diff --git a/zebra-state/src/service/check/difficulty.rs b/zebra-state/src/service/check/difficulty.rs index 3f6cd9e9..ab583735 100644 --- a/zebra-state/src/service/check/difficulty.rs +++ b/zebra-state/src/service/check/difficulty.rs @@ -183,10 +183,9 @@ impl AdjustedDifficulty { self.candidate_time, self.relevant_times[0], ) { - assert_eq!( - self.network, - Network::Testnet, - "invalid network: the minimum difficulty rule only applies on testnet" + assert!( + self.network.is_a_test_network(), + "invalid network: the minimum difficulty rule only applies on test networks" ); self.network.target_difficulty_limit().to_compact() } else { diff --git a/zebra-state/src/service/finalized_state/disk_format/chain.rs b/zebra-state/src/service/finalized_state/disk_format/chain.rs index cf1cbe7d..2f04b536 100644 --- a/zebra-state/src/service/finalized_state/disk_format/chain.rs +++ b/zebra-state/src/service/finalized_state/disk_format/chain.rs @@ -10,8 +10,12 @@ use std::collections::BTreeMap; use bincode::Options; use zebra_chain::{ - amount::NonNegative, block::Height, history_tree::NonEmptyHistoryTree, parameters::Network, - primitives::zcash_history, value_balance::ValueBalance, + amount::NonNegative, + block::Height, + history_tree::{HistoryTreeError, NonEmptyHistoryTree}, + parameters::{Network, NetworkKind}, + primitives::zcash_history, + value_balance::ValueBalance, }; use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk}; @@ -39,42 +43,54 @@ impl FromDisk for ValueBalance { // https://docs.rs/bincode/1.3.3/bincode/config/index.html#options-struct-vs-bincode-functions #[derive(serde::Serialize, serde::Deserialize)] -struct HistoryTreeParts { - network: Network, +pub struct HistoryTreeParts { + network_kind: NetworkKind, size: u32, peaks: BTreeMap, current_height: Height, } -impl IntoDisk for NonEmptyHistoryTree { +impl HistoryTreeParts { + /// Converts [`HistoryTreeParts`] to a [`NonEmptyHistoryTree`]. + pub(crate) fn with_network( + self, + network: &Network, + ) -> Result { + assert_eq!( + self.network_kind, + network.kind(), + "history tree network kind should match current network" + ); + + NonEmptyHistoryTree::from_cache(network, self.size, self.peaks, self.current_height) + } +} + +impl From<&NonEmptyHistoryTree> for HistoryTreeParts { + fn from(history_tree: &NonEmptyHistoryTree) -> Self { + HistoryTreeParts { + network_kind: history_tree.network().kind(), + size: history_tree.size(), + peaks: history_tree.peaks().clone(), + current_height: history_tree.current_height(), + } + } +} + +impl IntoDisk for HistoryTreeParts { type Bytes = Vec; fn as_bytes(&self) -> Self::Bytes { - let data = HistoryTreeParts { - network: self.network(), - size: self.size(), - peaks: self.peaks().clone(), - current_height: self.current_height(), - }; bincode::DefaultOptions::new() - .serialize(&data) + .serialize(self) .expect("serialization to vec doesn't fail") } } -impl FromDisk for NonEmptyHistoryTree { +impl FromDisk for HistoryTreeParts { fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { - let parts: HistoryTreeParts = bincode::DefaultOptions::new() + bincode::DefaultOptions::new() .deserialize(bytes.as_ref()) - .expect( - "deserialization format should match the serialization format used by IntoDisk", - ); - NonEmptyHistoryTree::from_cache( - &parts.network, - parts.size, - parts.peaks, - parts.current_height, - ) - .expect("deserialization format should match the serialization format used by IntoDisk") + .expect("deserialization format should match the serialization format used by IntoDisk") } } diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs b/zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs index 4605faf7..14a8dd6c 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs @@ -29,11 +29,7 @@ use std::{collections::BTreeMap, sync::Arc}; -use zebra_chain::{ - block::Block, - parameters::Network::{self, *}, - serialization::ZcashDeserializeInto, -}; +use zebra_chain::{block::Block, parameters::Network, serialization::ZcashDeserializeInto}; use crate::{ service::finalized_state::{ @@ -50,9 +46,9 @@ use crate::{ #[test] fn test_raw_rocksdb_column_families() { let _init_guard = zebra_test::init(); - - test_raw_rocksdb_column_families_with_network(Mainnet); - test_raw_rocksdb_column_families_with_network(Testnet); + for network in Network::iter() { + test_raw_rocksdb_column_families_with_network(network); + } } /// Snapshot raw column families for `network`. diff --git a/zebra-state/src/service/finalized_state/disk_format/transparent.rs b/zebra-state/src/service/finalized_state/disk_format/transparent.rs index 89620cda..b45c211b 100644 --- a/zebra-state/src/service/finalized_state/disk_format/transparent.rs +++ b/zebra-state/src/service/finalized_state/disk_format/transparent.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use zebra_chain::{ amount::{self, Amount, NonNegative}, block::Height, - parameters::Network::*, + parameters::NetworkKind, serialization::{ZcashDeserializeInto, ZcashSerialize}, transparent::{self, Address::*}, }; @@ -498,14 +498,18 @@ impl AddressTransaction { /// Returns a byte representing the [`transparent::Address`] variant. fn address_variant(address: &transparent::Address) -> u8 { + use NetworkKind::*; // Return smaller values for more common variants. // // (This probably doesn't matter, but it might help slightly with data compression.) - match (address.network(), address) { + match (address.network_kind(), address) { (Mainnet, PayToPublicKeyHash { .. }) => 0, (Mainnet, PayToScriptHash { .. }) => 1, - (Testnet, PayToPublicKeyHash { .. }) => 2, - (Testnet, PayToScriptHash { .. }) => 3, + // There's no way to distinguish between Regtest and Testnet for encoded transparent addresses, + // we can consider `Regtest` to use `Testnet` transparent addresses, so it's okay to use the `Testnet` + // address variant for `Regtest` transparent addresses in the db format + (Testnet | Regtest, PayToPublicKeyHash { .. }) => 2, + (Testnet | Regtest, PayToScriptHash { .. }) => 3, } } @@ -529,15 +533,15 @@ impl FromDisk for transparent::Address { let hash_bytes = hash_bytes.try_into().unwrap(); let network = if address_variant < 2 { - Mainnet + NetworkKind::Mainnet } else { - Testnet + NetworkKind::Testnet }; if address_variant % 2 == 0 { - transparent::Address::from_pub_key_hash(&network, hash_bytes) + transparent::Address::from_pub_key_hash(network, hash_bytes) } else { - transparent::Address::from_script_hash(&network, hash_bytes) + transparent::Address::from_script_hash(network, hash_bytes) } } } diff --git a/zebra-state/src/service/finalized_state/zebra_db.rs b/zebra-state/src/service/finalized_state/zebra_db.rs index 71c619e4..17891e38 100644 --- a/zebra-state/src/service/finalized_state/zebra_db.rs +++ b/zebra-state/src/service/finalized_state/zebra_db.rs @@ -215,7 +215,7 @@ impl ZebraDb { /// Returns the configured network for this database. pub fn network(&self) -> Network { - self.db.network().clone() + self.db.network() } /// Returns the `Path` where the files used by this database are located. diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs index f91ea584..dbd0e7c1 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs @@ -37,7 +37,7 @@ use serde::Serialize; use zebra_chain::{ block::{self, Block, Height, SerializedBlock}, orchard, - parameters::Network::{self, *}, + parameters::Network, sapling, serialization::{ZcashDeserializeInto, ZcashSerialize}, transaction::{self, Transaction}, @@ -153,9 +153,9 @@ impl TransactionData { #[test] fn test_block_and_transaction_data() { let _init_guard = zebra_test::init(); - - test_block_and_transaction_data_with_network(Mainnet); - test_block_and_transaction_data_with_network(Testnet); + for network in Network::iter() { + test_block_and_transaction_data_with_network(network); + } } /// Snapshot finalized block and transaction data for `network`. @@ -318,7 +318,7 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) { // Skip these checks for empty history trees. if let Some(history_tree_at_tip) = history_tree_at_tip.as_ref().as_ref() { assert_eq!(history_tree_at_tip.current_height(), max_height); - assert_eq!(history_tree_at_tip.network(), state.network()); + assert_eq!(history_tree_at_tip.network(), &state.network()); } } diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index 136121db..1b4f7db9 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -43,7 +43,7 @@ fn test_block_db_round_trip() { .map(|(_height, block)| block.zcash_deserialize_into().unwrap()); test_block_db_round_trip_with(&Mainnet, mainnet_test_cases); - test_block_db_round_trip_with(&Testnet, testnet_test_cases); + test_block_db_round_trip_with(&Network::new_default_testnet(), testnet_test_cases); // It doesn't matter if these blocks are mainnet or testnet, // because there is no validation at this level of the database. diff --git a/zebra-state/src/service/finalized_state/zebra_db/chain.rs b/zebra-state/src/service/finalized_state/zebra_db/chain.rs index 842fc945..93c65322 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/chain.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/chain.rs @@ -18,17 +18,17 @@ use std::{ }; use zebra_chain::{ - amount::NonNegative, - block::Height, - history_tree::{HistoryTree, NonEmptyHistoryTree}, - transparent, + amount::NonNegative, block::Height, history_tree::HistoryTree, transparent, value_balance::ValueBalance, }; use crate::{ request::FinalizedBlock, service::finalized_state::{ - disk_db::DiskWriteBatch, disk_format::RawBytes, zebra_db::ZebraDb, TypedColumnFamily, + disk_db::DiskWriteBatch, + disk_format::{chain::HistoryTreeParts, RawBytes}, + zebra_db::ZebraDb, + TypedColumnFamily, }, BoxError, }; @@ -42,15 +42,15 @@ pub const HISTORY_TREE: &str = "history_tree"; /// /// This constant should be used so the compiler can detect incorrectly typed accesses to the /// column family. -pub type HistoryTreeCf<'cf> = TypedColumnFamily<'cf, (), NonEmptyHistoryTree>; +pub type HistoryTreePartsCf<'cf> = TypedColumnFamily<'cf, (), HistoryTreeParts>; /// The legacy (1.3.0 and earlier) type for reading history trees from the database. /// This type should not be used in new code. -pub type LegacyHistoryTreeCf<'cf> = TypedColumnFamily<'cf, Height, NonEmptyHistoryTree>; +pub type LegacyHistoryTreePartsCf<'cf> = TypedColumnFamily<'cf, Height, HistoryTreeParts>; /// A generic raw key type for reading history trees from the database, regardless of the database version. /// This type should not be used in new code. -pub type RawHistoryTreeCf<'cf> = TypedColumnFamily<'cf, RawBytes, NonEmptyHistoryTree>; +pub type RawHistoryTreePartsCf<'cf> = TypedColumnFamily<'cf, RawBytes, HistoryTreeParts>; /// The name of the chain value pools column family. /// @@ -67,22 +67,22 @@ impl ZebraDb { // Column family convenience methods /// Returns a typed handle to the `history_tree` column family. - pub(crate) fn history_tree_cf(&self) -> HistoryTreeCf { - HistoryTreeCf::new(&self.db, HISTORY_TREE) + pub(crate) fn history_tree_cf(&self) -> HistoryTreePartsCf { + HistoryTreePartsCf::new(&self.db, HISTORY_TREE) .expect("column family was created when database was created") } /// Returns a legacy typed handle to the `history_tree` column family. /// This should not be used in new code. - pub(crate) fn legacy_history_tree_cf(&self) -> LegacyHistoryTreeCf { - LegacyHistoryTreeCf::new(&self.db, HISTORY_TREE) + pub(crate) fn legacy_history_tree_cf(&self) -> LegacyHistoryTreePartsCf { + LegacyHistoryTreePartsCf::new(&self.db, HISTORY_TREE) .expect("column family was created when database was created") } /// Returns a generic raw key typed handle to the `history_tree` column family. /// This should not be used in new code. - pub(crate) fn raw_history_tree_cf(&self) -> RawHistoryTreeCf { - RawHistoryTreeCf::new(&self.db, HISTORY_TREE) + pub(crate) fn raw_history_tree_cf(&self) -> RawHistoryTreePartsCf { + RawHistoryTreePartsCf::new(&self.db, HISTORY_TREE) .expect("column family was created when database was created") } @@ -115,19 +115,24 @@ impl ZebraDb { // // So we use the empty key `()`. Since the key has a constant value, we will always read // the latest tree. - let mut history_tree = history_tree_cf.zs_get(&()); + let mut history_tree_parts = history_tree_cf.zs_get(&()); - if history_tree.is_none() { + if history_tree_parts.is_none() { let legacy_history_tree_cf = self.legacy_history_tree_cf(); // In Zebra 1.4.0 and later, we only update the history tip tree when it has changed (for every block after heartwood). // But we write with a `()` key, not a height key. // So we need to look for the most recent update height if the `()` key has never been written. - history_tree = legacy_history_tree_cf + history_tree_parts = legacy_history_tree_cf .zs_last_key_value() .map(|(_height_key, tree_value)| tree_value); } + let history_tree = history_tree_parts.map(|parts| { + parts.with_network(&self.db.network()).expect( + "deserialization format should match the serialization format used by IntoDisk", + ) + }); Arc::new(HistoryTree::from(history_tree)) } @@ -139,7 +144,12 @@ impl ZebraDb { raw_history_tree_cf .zs_forward_range_iter(..) - .map(|(raw_key, history_tree)| (raw_key, Arc::new(HistoryTree::from(history_tree)))) + .map(|(raw_key, history_tree_parts)| { + let history_tree = history_tree_parts.with_network(&self.db.network()).expect( + "deserialization format should match the serialization format used by IntoDisk", + ); + (raw_key, Arc::new(HistoryTree::from(history_tree))) + }) .collect() } @@ -164,9 +174,9 @@ impl DiskWriteBatch { pub fn update_history_tree(&mut self, db: &ZebraDb, tree: &HistoryTree) { let history_tree_cf = db.history_tree_cf().with_batch_for_writing(self); - if let Some(tree) = tree.as_ref().as_ref() { + if let Some(tree) = tree.as_ref() { // The batch is modified by this method and written by the caller. - let _ = history_tree_cf.zs_insert(&(), tree); + let _ = history_tree_cf.zs_insert(&(), &HistoryTreeParts::from(tree)); } } diff --git a/zebra-state/src/service/non_finalized_state/tests/vectors.rs b/zebra-state/src/service/non_finalized_state/tests/vectors.rs index 00159e4c..6f908f08 100644 --- a/zebra-state/src/service/non_finalized_state/tests/vectors.rs +++ b/zebra-state/src/service/non_finalized_state/tests/vectors.rs @@ -134,8 +134,9 @@ fn ord_matches_work() -> Result<()> { fn best_chain_wins() -> Result<()> { let _init_guard = zebra_test::init(); - best_chain_wins_for_network(Network::Mainnet)?; - best_chain_wins_for_network(Network::Testnet)?; + for network in Network::iter() { + best_chain_wins_for_network(network)?; + } Ok(()) } @@ -172,8 +173,9 @@ fn best_chain_wins_for_network(network: Network) -> Result<()> { fn finalize_pops_from_best_chain() -> Result<()> { let _init_guard = zebra_test::init(); - finalize_pops_from_best_chain_for_network(Network::Mainnet)?; - finalize_pops_from_best_chain_for_network(Network::Testnet)?; + for network in Network::iter() { + finalize_pops_from_best_chain_for_network(network)?; + } Ok(()) } @@ -219,8 +221,9 @@ fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> { fn commit_block_extending_best_chain_doesnt_drop_worst_chains() -> Result<()> { let _init_guard = zebra_test::init(); - commit_block_extending_best_chain_doesnt_drop_worst_chains_for_network(Network::Mainnet)?; - commit_block_extending_best_chain_doesnt_drop_worst_chains_for_network(Network::Testnet)?; + for network in Network::iter() { + commit_block_extending_best_chain_doesnt_drop_worst_chains_for_network(network)?; + } Ok(()) } @@ -264,10 +267,9 @@ fn commit_block_extending_best_chain_doesnt_drop_worst_chains_for_network( #[test] fn shorter_chain_can_be_best_chain() -> Result<()> { let _init_guard = zebra_test::init(); - - shorter_chain_can_be_best_chain_for_network(Network::Mainnet)?; - shorter_chain_can_be_best_chain_for_network(Network::Testnet)?; - + for network in Network::iter() { + shorter_chain_can_be_best_chain_for_network(network)?; + } Ok(()) } @@ -307,9 +309,9 @@ fn shorter_chain_can_be_best_chain_for_network(network: Network) -> Result<()> { #[test] fn longer_chain_with_more_work_wins() -> Result<()> { let _init_guard = zebra_test::init(); - - longer_chain_with_more_work_wins_for_network(Network::Mainnet)?; - longer_chain_with_more_work_wins_for_network(Network::Testnet)?; + for network in Network::iter() { + longer_chain_with_more_work_wins_for_network(network)?; + } Ok(()) } @@ -355,8 +357,9 @@ fn longer_chain_with_more_work_wins_for_network(network: Network) -> Result<()> fn equal_length_goes_to_more_work() -> Result<()> { let _init_guard = zebra_test::init(); - equal_length_goes_to_more_work_for_network(Network::Mainnet)?; - equal_length_goes_to_more_work_for_network(Network::Testnet)?; + for network in Network::iter() { + equal_length_goes_to_more_work_for_network(network)?; + } Ok(()) } @@ -394,8 +397,9 @@ fn equal_length_goes_to_more_work_for_network(network: Network) -> Result<()> { #[test] fn history_tree_is_updated() -> Result<()> { - history_tree_is_updated_for_network_upgrade(Network::Mainnet, NetworkUpgrade::Heartwood)?; - history_tree_is_updated_for_network_upgrade(Network::Testnet, NetworkUpgrade::Heartwood)?; + for network in Network::iter() { + history_tree_is_updated_for_network_upgrade(network, NetworkUpgrade::Heartwood)?; + } // TODO: we can't test other upgrades until we have a method for creating a FinalizedState // with a HistoryTree. Ok(()) @@ -497,8 +501,9 @@ fn history_tree_is_updated_for_network_upgrade( #[test] fn commitment_is_validated() { - commitment_is_validated_for_network_upgrade(Network::Mainnet, NetworkUpgrade::Heartwood); - commitment_is_validated_for_network_upgrade(Network::Testnet, NetworkUpgrade::Heartwood); + for network in Network::iter() { + commitment_is_validated_for_network_upgrade(network, NetworkUpgrade::Heartwood); + } // TODO: we can't test other upgrades until we have a method for creating a FinalizedState // with a HistoryTree. } diff --git a/zebra-state/tests/basic.rs b/zebra-state/tests/basic.rs index 49d3346f..01b90a11 100644 --- a/zebra-state/tests/basic.rs +++ b/zebra-state/tests/basic.rs @@ -63,7 +63,7 @@ async fn check_transcripts_mainnet() -> Result<(), Report> { #[tokio::test(flavor = "multi_thread")] async fn check_transcripts_testnet() -> Result<(), Report> { - check_transcripts(Network::Testnet).await + check_transcripts(Network::new_default_testnet()).await } #[spandoc::spandoc] diff --git a/zebra-utils/src/bin/scanning-results-reader/main.rs b/zebra-utils/src/bin/scanning-results-reader/main.rs index cd31f55c..66b48e0a 100644 --- a/zebra-utils/src/bin/scanning-results-reader/main.rs +++ b/zebra-utils/src/bin/scanning-results-reader/main.rs @@ -41,8 +41,9 @@ 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 = network.into(); + let zebra_network = zebra_chain::parameters::Network::Mainnet; let storage = Storage::new(&Config::default(), &zebra_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(); diff --git a/zebrad/src/components/mempool/storage/tests/vectors.rs b/zebrad/src/components/mempool/storage/tests/vectors.rs index 48bb3cb8..4dd12827 100644 --- a/zebrad/src/components/mempool/storage/tests/vectors.rs +++ b/zebrad/src/components/mempool/storage/tests/vectors.rs @@ -60,8 +60,9 @@ fn mempool_storage_basic() -> Result<()> { // Test multiple times to catch intermittent bugs since eviction is randomized for _ in 0..10 { - mempool_storage_basic_for_network(Network::Mainnet)?; - mempool_storage_basic_for_network(Network::Testnet)?; + for network in Network::iter() { + mempool_storage_basic_for_network(network)?; + } } Ok(()) @@ -236,10 +237,9 @@ fn mempool_storage_crud_same_effects_mainnet() { #[test] fn mempool_expired_basic() -> Result<()> { let _init_guard = zebra_test::init(); - - mempool_expired_basic_for_network(Network::Mainnet)?; - mempool_expired_basic_for_network(Network::Testnet)?; - + for network in Network::iter() { + mempool_expired_basic_for_network(network)?; + } Ok(()) } diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 6ae68ce4..656ef9a2 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -1051,7 +1051,7 @@ fn sync_one_checkpoint_mainnet() -> Result<()> { fn sync_one_checkpoint_testnet() -> Result<()> { sync_until( TINY_CHECKPOINT_TEST_HEIGHT, - &Testnet, + &Network::new_default_testnet(), STOP_AT_HEIGHT_REGEX, TINY_CHECKPOINT_TIMEOUT, None, @@ -1271,7 +1271,7 @@ fn sync_to_mandatory_checkpoint_mainnet() -> Result<()> { #[cfg_attr(feature = "test_sync_to_mandatory_checkpoint_testnet", test)] fn sync_to_mandatory_checkpoint_testnet() -> Result<()> { let _init_guard = zebra_test::init(); - let network = Testnet; + let network = Network::new_default_testnet(); create_cached_database(network) } @@ -1297,7 +1297,7 @@ fn sync_past_mandatory_checkpoint_mainnet() -> Result<()> { #[cfg_attr(feature = "test_sync_past_mandatory_checkpoint_testnet", test)] fn sync_past_mandatory_checkpoint_testnet() -> Result<()> { let _init_guard = zebra_test::init(); - let network = Testnet; + let network = Network::new_default_testnet(); sync_past_mandatory_checkpoint(network) } @@ -1322,7 +1322,10 @@ fn full_sync_mainnet() -> Result<()> { #[ignore] fn full_sync_testnet() -> Result<()> { // TODO: add "ZEBRA" at the start of this env var, to avoid clashes - full_sync_test(Testnet, "FULL_SYNC_TESTNET_TIMEOUT_MINUTES") + full_sync_test( + Network::new_default_testnet(), + "FULL_SYNC_TESTNET_TIMEOUT_MINUTES", + ) } #[cfg(feature = "prometheus")] @@ -2529,14 +2532,14 @@ async fn generate_checkpoints_mainnet() -> Result<()> { #[ignore] #[cfg(feature = "zebra-checkpoints")] async fn generate_checkpoints_testnet() -> Result<()> { - common::checkpoints::run(Testnet).await + common::checkpoints::run(Network::new_default_testnet()).await } /// Check that new states are created with the current state format version, /// and that restarting `zebrad` doesn't change the format version. #[tokio::test] async fn new_state_format() -> Result<()> { - for network in [Mainnet, Testnet] { + for network in Network::iter() { state_format_test("new_state_format_test", &network, 2, None).await?; } @@ -2554,7 +2557,7 @@ async fn update_state_format() -> Result<()> { fake_version.minor = 0; fake_version.patch = 0; - for network in [Mainnet, Testnet] { + for network in Network::iter() { state_format_test("update_state_format_test", &network, 3, Some(&fake_version)).await?; } @@ -2571,7 +2574,7 @@ async fn downgrade_state_format() -> Result<()> { fake_version.minor = u16::MAX.into(); fake_version.patch = 0; - for network in [Mainnet, Testnet] { + for network in Network::iter() { state_format_test( "downgrade_state_format_test", &network, diff --git a/zebrad/tests/common/checkpoints.rs b/zebrad/tests/common/checkpoints.rs index f59dd8fb..602525fd 100644 --- a/zebrad/tests/common/checkpoints.rs +++ b/zebrad/tests/common/checkpoints.rs @@ -15,7 +15,7 @@ use tempfile::TempDir; use zebra_chain::{ block::{Height, HeightDiff, TryIntoHeight}, - parameters::Network::{self, *}, + parameters::Network, transparent::MIN_TRANSPARENT_COINBASE_MATURITY, }; use zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP; @@ -82,7 +82,7 @@ pub async fn run(network: Network) -> Result<()> { // Wait for the upgrade if needed. // Currently we only write an image for testnet, which is quick. // (Mainnet would need to wait at the end of this function, if the upgrade is long.) - if network == Testnet { + if network.is_a_test_network() { let state_version_message = wait_for_state_version_message(&mut zebrad)?; // Before we write a cached state image, wait for a database upgrade. diff --git a/zebrad/tests/common/configs/v1.7.0.toml b/zebrad/tests/common/configs/v1.7.0.toml new file mode 100644 index 00000000..cbb3a020 --- /dev/null +++ b/zebrad/tests/common/configs/v1.7.0.toml @@ -0,0 +1,84 @@ +# Default configuration for zebrad. +# +# This file can be used as a skeleton for custom configs. +# +# Unspecified fields use default values. Optional fields are Some(field) if the +# field is present and None if it is absent. +# +# This file is generated as an example using zebrad's current defaults. +# You should set only the config options you want to keep, and delete the rest. +# Only a subset of fields are present in the skeleton, since optional values +# whose default is None are omitted. +# +# The config format (including a complete list of sections and fields) is +# documented here: +# https://docs.rs/zebrad/latest/zebrad/config/struct.ZebradConfig.html +# +# zebrad attempts to load configs in the following order: +# +# 1. The -c flag on the command line, e.g., `zebrad -c myconfig.toml start`; +# 2. The file `zebrad.toml` in the users's preference directory (platform-dependent); +# 3. The default config. +# +# The user's preference directory and the default path to the `zebrad` config are platform dependent, +# based on `dirs::preference_dir`, see https://docs.rs/dirs/latest/dirs/fn.preference_dir.html : +# +# | Platform | Value | Example | +# | -------- | ------------------------------------- | ---------------------------------------------- | +# | Linux | `$XDG_CONFIG_HOME` or `$HOME/.config` | `/home/alice/.config/zebrad.toml` | +# | macOS | `$HOME/Library/Preferences` | `/Users/Alice/Library/Preferences/zebrad.toml` | +# | Windows | `{FOLDERID_RoamingAppData}` | `C:\Users\Alice\AppData\Local\zebrad.toml` | + +[consensus] +checkpoint_sync = true + +[mempool] +eviction_memory_time = "1h" +tx_cost_limit = 80000000 + +[metrics] + +[mining] +debug_like_zcashd = true + +[network] +cache_dir = true +crawl_new_peer_interval = "1m 1s" +initial_mainnet_peers = [ + "dnsseed.z.cash:8233", + "dnsseed.str4d.xyz:8233", + "mainnet.seeder.zfnd.org:8233", + "mainnet.is.yolo.money:8233", +] +initial_testnet_peers = [ + "dnsseed.testnet.z.cash:18233", + "testnet.seeder.zfnd.org:18233", + "testnet.is.yolo.money:18233", +] +listen_addr = "0.0.0.0:8233" +max_connections_per_ip = 1 +network = "Testnet" +peerset_initial_target_size = 25 + +[network.testnet_parameters] + +[rpc] +debug_force_finished_sync = false +parallel_cpu_threads = 0 + +[state] +cache_dir = "cache_dir" +delete_old_database = true +ephemeral = false + +[sync] +checkpoint_verify_concurrency_limit = 1000 +download_concurrency_limit = 50 +full_verify_concurrency_limit = 20 +parallel_cpu_threads = 0 + +[tracing] +buffer_limit = 128000 +force_use_color = false +use_color = true +use_journald = false \ No newline at end of file