change(consensus): Refactor production code for network consensus rules to `Network` methods (#8340)

* begin refactor suggested as "step 2": https://github.com/ZcashFoundation/zebra/issues/7968#issue-2003245309
Squashed from multiple commits to enable partial rebase

* break out more little traits

* add activation implementation leveraging From<Network> for lrz::cons::

* for transfer of ownership I cannot return a type that's owned by the method

* hrp_sapling_extended_full_viewing_key

* complete implementation of interface of Parameters on Network reuse Parameters on zcash Network where possible

* move doc-comments to trait declarations (from impls)

* Simplify/complete Parameters impl for Network

* Add checkpoint_list method, move documentation, etc

* move last match network to inside network method

* add back comment

* use zcash_address for parameter types in zebra-chain

* use inherent methods instead of big parameters passthrough

* revert to implementation of From on zcash_primitives::..::Network vs &zcash_prim...

* move match

* add test to block maximum time rule

* update changelog

* finish porting target_difficutly_limit

* remove obscelete code comment

* revert non-functional change

* finish migrating target_difficulty_limit, checkpoint_list

* update changelog

---------

Co-authored-by: Hazel OHearn <gygaxis@zingolabs.org>
This commit is contained in:
Za Wilcox 2024-03-12 15:41:44 -06:00 committed by GitHub
parent 9b91d4bc0e
commit 3bef54b764
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 284 additions and 243 deletions

View File

@ -5,6 +5,20 @@ All notable changes to Zebra are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org).
## Unreleased
### Added
- `zebra_chain::Network` methods:
- `b58_pubkey_address_prefix`, `b58_script_address_prefix`, `num_funding_streams`
### Changed
- Functions that take a `zebra_chain::Network` as an argument have been moved to be methods of `Network`, including
- `zebra_chain::parameters`:
- `genesis::genesis_hash`, `NetworkUpgrade::activation_list`, `NetworkUpgrade::is_max_block_time_enforced`,
- `zebra_chain::work::difficulty::ExpandedDifficulty::target_difficutly_limit`
- `zebra_consensus::height_for_first_halving`
- `zebra_consensus::checkpoint::CheckpointList::new` (now `Network::checkpoint_list`)
## [Zebra 1.6.0](https://github.com/ZcashFoundation/zebra/releases/tag/v1.6.0) - 2024-02-23
This release exposes the shielded scanning functionality through an initial

View File

@ -31,7 +31,6 @@ async-error = [
# Mining RPC support
getblocktemplate-rpcs = [
"zcash_address",
]
# Experimental shielded scanning support
@ -132,8 +131,7 @@ serde_json = { version = "1.0.113", optional = true }
# Production feature async-error and testing feature proptest-impl
tokio = { version = "1.36.0", optional = true }
# Production feature getblocktemplate-rpcs
zcash_address = { version = "0.3.1", optional = true }
zcash_address = { version = "0.3.1" }
# Experimental feature shielded-scan
zcash_client_backend = { version = "0.10.0-rc.1", optional = true }

View File

@ -1,21 +1,7 @@
//! Genesis consensus parameters for each Zcash network.
use crate::{block, parameters::Network};
/// The previous block hash for the genesis block.
///
/// All known networks use the Bitcoin `null` value for the parent of the
/// genesis block. (In Bitcoin, `null` is `[0; 32]`.)
pub const GENESIS_PREVIOUS_BLOCK_HASH: block::Hash = block::Hash([0; 32]);
/// Returns the hash for the genesis block in `network`.
pub fn genesis_hash(network: Network) -> block::Hash {
match network {
// zcash-cli getblockhash 0
Network::Mainnet => "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08",
// zcash-cli -testnet getblockhash 0
Network::Testnet => "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38",
}
.parse()
.expect("hard-coded hash parses")
}
pub const GENESIS_PREVIOUS_BLOCK_HASH: crate::block::Hash = crate::block::Hash([0; 32]);

View File

@ -5,7 +5,7 @@ use std::{fmt, str::FromStr};
use thiserror::Error;
use crate::{
block::{Height, HeightDiff},
block::{self, Height, HeightDiff},
parameters::NetworkUpgrade::Canopy,
};
@ -63,6 +63,35 @@ pub enum Network {
Testnet,
}
use zcash_primitives::consensus::{Network as ZcashPrimitivesNetwork, Parameters as _};
impl Network {
/// 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] {
<ZcashPrimitivesNetwork>::from(*self).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] {
<ZcashPrimitivesNetwork>::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 <https://zips.z.cash/protocol/protocol.pdf#blockheader>
pub fn is_max_block_time_enforced(self, height: block::Height) -> bool {
match self {
Network::Mainnet => true,
Network::Testnet => height >= super::TESTNET_MAX_TIME_START_HEIGHT,
}
}
}
impl From<Network> for &'static str {
fn from(network: Network) -> &'static str {
match network {

View File

@ -1,7 +1,10 @@
use proptest::prelude::*;
use super::super::{Network, ZIP_212_GRACE_PERIOD_DURATION};
use crate::parameters::NetworkUpgrade;
use crate::{
block::Height,
parameters::{NetworkUpgrade, TESTNET_MAX_TIME_START_HEIGHT},
};
proptest! {
/// Check that the mandatory checkpoint is after the ZIP-212 grace period.
@ -23,4 +26,16 @@ proptest! {
assert!(network.mandatory_checkpoint_height() >= grace_period_end_height);
}
#[test]
/// Asserts that the activation height is correct for the block
/// maximum time rule on Testnet is correct.
fn max_block_times_correct_enforcement(height in any::<Height>()) {
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);
}
}

View File

@ -229,7 +229,7 @@ const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299
/// <https://zips.z.cash/protocol/protocol.pdf#blockheader>
pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
impl NetworkUpgrade {
impl Network {
/// Returns a map between activation heights and network upgrades for `network`,
/// in ascending height order.
///
@ -241,7 +241,7 @@ impl NetworkUpgrade {
/// When the environment variable TEST_FAKE_ACTIVATION_HEIGHTS is set
/// and it's a test build, this returns a list of fake activation heights
/// used by some tests.
pub fn activation_list(network: Network) -> BTreeMap<block::Height, NetworkUpgrade> {
pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
let (mainnet_heights, testnet_heights) = {
#[cfg(not(feature = "zebra-test"))]
{
@ -269,7 +269,7 @@ impl NetworkUpgrade {
(MAINNET_ACTIVATION_HEIGHTS, TESTNET_ACTIVATION_HEIGHTS)
}
};
match network {
match self {
Mainnet => mainnet_heights,
Testnet => testnet_heights,
}
@ -277,10 +277,12 @@ impl NetworkUpgrade {
.cloned()
.collect()
}
}
impl NetworkUpgrade {
/// Returns the current network upgrade for `network` and `height`.
pub fn current(network: Network, height: block::Height) -> NetworkUpgrade {
NetworkUpgrade::activation_list(network)
network
.activation_list()
.range(..=height)
.map(|(_, nu)| *nu)
.next_back()
@ -292,7 +294,8 @@ impl NetworkUpgrade {
/// Returns None if the next upgrade has not been implemented in Zebra
/// yet.
pub fn next(network: Network, height: block::Height) -> Option<NetworkUpgrade> {
NetworkUpgrade::activation_list(network)
network
.activation_list()
.range((Excluded(height), Unbounded))
.map(|(_, nu)| *nu)
.next()
@ -303,7 +306,8 @@ impl NetworkUpgrade {
/// Returns None if this network upgrade is a future upgrade, and its
/// activation height has not been set yet.
pub fn activation_height(&self, network: Network) -> Option<block::Height> {
NetworkUpgrade::activation_list(network)
network
.activation_list()
.iter()
.filter(|(_, nu)| nu == &self)
.map(|(height, _)| *height)
@ -316,7 +320,7 @@ impl NetworkUpgrade {
/// Use [`NetworkUpgrade::activation_height`] to get the specific network
/// upgrade.
pub fn is_activation_height(network: Network, height: block::Height) -> bool {
NetworkUpgrade::activation_list(network).contains_key(&height)
network.activation_list().contains_key(&height)
}
/// Returns an unordered mapping between NetworkUpgrades and their ConsensusBranchIds.
@ -445,20 +449,6 @@ impl NetworkUpgrade {
NetworkUpgrade::current(network, height).averaging_window_timespan()
}
/// 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 <https://zips.z.cash/protocol/protocol.pdf#blockheader>
pub fn is_max_block_time_enforced(network: Network, height: block::Height) -> bool {
match network {
Network::Mainnet => true,
Network::Testnet => height >= TESTNET_MAX_TIME_START_HEIGHT,
}
}
/// Returns the NetworkUpgrade given an u32 as ConsensusBranchId
pub fn from_branch_id(branch_id: u32) -> Option<NetworkUpgrade> {
CONSENSUS_BRANCH_IDS

View File

@ -14,14 +14,14 @@ use NetworkUpgrade::*;
fn activation_bijective() {
let _init_guard = zebra_test::init();
let mainnet_activations = NetworkUpgrade::activation_list(Mainnet);
let mainnet_activations = Mainnet.activation_list();
let mainnet_heights: HashSet<&block::Height> = mainnet_activations.keys().collect();
assert_eq!(MAINNET_ACTIVATION_HEIGHTS.len(), mainnet_heights.len());
let mainnet_nus: HashSet<&NetworkUpgrade> = mainnet_activations.values().collect();
assert_eq!(MAINNET_ACTIVATION_HEIGHTS.len(), mainnet_nus.len());
let testnet_activations = NetworkUpgrade::activation_list(Testnet);
let testnet_activations = Testnet.activation_list();
let testnet_heights: HashSet<&block::Height> = testnet_activations.keys().collect();
assert_eq!(TESTNET_ACTIVATION_HEIGHTS.len(), testnet_heights.len());
@ -46,7 +46,7 @@ fn activation_extremes_testnet() {
fn activation_extremes(network: Network) {
// The first three upgrades are Genesis, BeforeOverwinter, and Overwinter
assert_eq!(
NetworkUpgrade::activation_list(network).get(&block::Height(0)),
network.activation_list().get(&block::Height(0)),
Some(&Genesis)
);
assert_eq!(Genesis.activation_height(network), Some(block::Height(0)));
@ -62,7 +62,7 @@ fn activation_extremes(network: Network) {
);
assert_eq!(
NetworkUpgrade::activation_list(network).get(&block::Height(1)),
network.activation_list().get(&block::Height(1)),
Some(&BeforeOverwinter)
);
assert_eq!(
@ -91,7 +91,7 @@ fn activation_extremes(network: Network) {
// We assume that the last upgrade we know about continues forever
// (even if we suspect that won't be true)
assert_ne!(
NetworkUpgrade::activation_list(network).get(&block::Height::MAX),
network.activation_list().get(&block::Height::MAX),
Some(&Genesis)
);
assert!(!NetworkUpgrade::is_activation_height(
@ -121,7 +121,7 @@ fn activation_consistent_testnet() {
/// Check that the `activation_height`, `is_activation_height`,
/// `current`, and `next` functions are consistent for `network`.
fn activation_consistent(network: Network) {
let activation_list = NetworkUpgrade::activation_list(network);
let activation_list = network.activation_list();
let network_upgrades: HashSet<&NetworkUpgrade> = activation_list.values().collect();
for &network_upgrade in network_upgrades {

View File

@ -25,24 +25,12 @@ pub fn decrypts_successfully(transaction: &Transaction, network: Network, height
if let Some(bundle) = alt_tx.sapling_bundle() {
for output in bundle.shielded_outputs().iter() {
let recovery = match network {
Network::Mainnet => {
zcash_primitives::sapling::note_encryption::try_sapling_output_recovery(
&zcash_primitives::consensus::MAIN_NETWORK,
alt_height,
&null_sapling_ovk,
output,
)
}
Network::Testnet => {
zcash_primitives::sapling::note_encryption::try_sapling_output_recovery(
&zcash_primitives::consensus::TEST_NETWORK,
alt_height,
&null_sapling_ovk,
output,
)
}
};
let recovery = zcash_primitives::sapling::note_encryption::try_sapling_output_recovery(
&<zcash_primitives::consensus::Network>::from(network),
alt_height,
&null_sapling_ovk,
output,
);
if recovery.is_none() {
return false;
}

View File

@ -15,20 +15,6 @@ use crate::{
#[cfg(test)]
use proptest::prelude::*;
/// Magic numbers used to identify what networks Transparent Addresses
/// are associated with.
mod magics {
pub mod p2sh {
pub const MAINNET: [u8; 2] = [0x1C, 0xBD];
pub const TESTNET: [u8; 2] = [0x1C, 0xBA];
}
pub mod p2pkh {
pub const MAINNET: [u8; 2] = [0x1C, 0xB8];
pub const TESTNET: [u8; 2] = [0x1D, 0x25];
}
}
/// Transparent Zcash Addresses
///
/// In Bitcoin a single byte is used for the version field identifying
@ -118,24 +104,14 @@ impl ZcashSerialize for Address {
network,
script_hash,
} => {
// Dev network doesn't have a recommendation so we
// default to testnet bytes if it's not mainnet.
match *network {
Network::Mainnet => writer.write_all(&magics::p2sh::MAINNET[..])?,
_ => writer.write_all(&magics::p2sh::TESTNET[..])?,
}
writer.write_all(&network.b58_script_address_prefix())?;
writer.write_all(script_hash)?
}
Address::PayToPublicKeyHash {
network,
pub_key_hash,
} => {
// Dev network doesn't have a recommendation so we
// default to testnet bytes if it's not mainnet.
match *network {
Network::Mainnet => writer.write_all(&magics::p2pkh::MAINNET[..])?,
_ => writer.write_all(&magics::p2pkh::TESTNET[..])?,
}
writer.write_all(&network.b58_pubkey_address_prefix())?;
writer.write_all(pub_key_hash)?
}
}
@ -153,22 +129,30 @@ impl ZcashDeserialize for Address {
reader.read_exact(&mut hash_bytes)?;
match version_bytes {
magics::p2sh::MAINNET => Ok(Address::PayToScriptHash {
network: Network::Mainnet,
script_hash: hash_bytes,
}),
magics::p2sh::TESTNET => Ok(Address::PayToScriptHash {
network: Network::Testnet,
script_hash: hash_bytes,
}),
magics::p2pkh::MAINNET => Ok(Address::PayToPublicKeyHash {
network: Network::Mainnet,
pub_key_hash: hash_bytes,
}),
magics::p2pkh::TESTNET => Ok(Address::PayToPublicKeyHash {
network: Network::Testnet,
pub_key_hash: hash_bytes,
}),
zcash_primitives::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX => {
Ok(Address::PayToScriptHash {
network: Network::Mainnet,
script_hash: hash_bytes,
})
}
zcash_primitives::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX => {
Ok(Address::PayToScriptHash {
network: Network::Testnet,
script_hash: hash_bytes,
})
}
zcash_primitives::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX => {
Ok(Address::PayToPublicKeyHash {
network: Network::Mainnet,
pub_key_hash: hash_bytes,
})
}
zcash_primitives::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX => {
Ok(Address::PayToPublicKeyHash {
network: Network::Testnet,
pub_key_hash: hash_bytes,
})
}
_ => Err(SerializationError::Parse("bad t-addr version/type")),
}
}

View File

@ -384,32 +384,6 @@ impl ExpandedDifficulty {
U256::from_little_endian(&hash.0).into()
}
/// Returns the easiest target difficulty allowed on `network`.
///
/// # Consensus
///
/// See `PoWLimit` in the Zcash specification:
/// <https://zips.z.cash/protocol/protocol.pdf#constants>
pub fn target_difficulty_limit(network: Network) -> ExpandedDifficulty {
let limit: U256 = match network {
/* 2^243 - 1 */
Network::Mainnet => (U256::one() << 243) - 1,
/* 2^251 - 1 */
Network::Testnet => (U256::one() << 251) - 1,
};
// `zcashd` converts the PoWLimit into a compact representation before
// using it to perform difficulty filter checks.
//
// The Zcash specification converts to compact for the default difficulty
// filter, but not for testnet minimum difficulty blocks. (ZIP 205 and
// ZIP 208 don't specify this conversion either.) See #1277 for details.
ExpandedDifficulty(limit)
.to_compact()
.to_expanded()
.expect("difficulty limits are valid expanded values")
}
/// Calculate the CompactDifficulty for an expanded difficulty.
///
/// # Consensus
@ -681,7 +655,8 @@ impl PartialCumulativeWork {
pub fn difficulty_multiplier_for_display(&self, network: Network) -> f64 {
// This calculation is similar to the `getdifficulty` RPC, see that code for details.
let pow_limit = ExpandedDifficulty::target_difficulty_limit(network)
let pow_limit = network
.target_difficulty_limit()
.to_compact()
.to_work()
.expect("target difficult limit is valid work");
@ -705,6 +680,41 @@ impl PartialCumulativeWork {
}
}
/// Network methods related to Difficulty
pub trait ParameterDifficulty {
/// Returns the easiest target difficulty allowed on `network`.
///
/// # Consensus
///
/// See `PoWLimit` in the Zcash specification:
/// <https://zips.z.cash/protocol/protocol.pdf#constants>
fn target_difficulty_limit(&self) -> ExpandedDifficulty;
}
impl ParameterDifficulty for Network {
/// Returns the easiest target difficulty allowed on `network`.
/// See [`ParameterDifficulty::target_difficulty_limit`]
fn target_difficulty_limit(&self) -> ExpandedDifficulty {
let limit: U256 = match self {
/* 2^243 - 1 */
Network::Mainnet => (U256::one() << 243) - 1,
/* 2^251 - 1 */
Network::Testnet => (U256::one() << 251) - 1,
};
// `zcashd` converts the PoWLimit into a compact representation before
// using it to perform difficulty filter checks.
//
// The Zcash specification converts to compact for the default difficulty
// filter, but not for testnet minimum difficulty blocks. (ZIP 205 and
// ZIP 208 don't specify this conversion either.) See #1277 for details.
ExpandedDifficulty(limit)
.to_compact()
.to_expanded()
.expect("difficulty limits are valid expanded values")
}
}
impl From<Work> for PartialCumulativeWork {
fn from(work: Work) -> Self {
PartialCumulativeWork(work.0)

View File

@ -309,9 +309,9 @@ fn block_difficulty_for_network(network: Network) -> Result<(), Report> {
/// SPANDOC: Check the PoWLimit for block {?height, ?network, ?threshold, ?hash}
{
// the consensus rule
assert!(threshold <= ExpandedDifficulty::target_difficulty_limit(network));
assert!(threshold <= network.target_difficulty_limit());
// check that ordering is transitive, we checked `hash <= threshold` above
assert!(hash <= ExpandedDifficulty::target_difficulty_limit(network));
assert!(hash <= network.target_difficulty_limit());
}
/// SPANDOC: Check compact round-trip for block {?height, ?network}
@ -376,7 +376,7 @@ fn genesis_block_difficulty_for_network(network: Network) -> Result<(), Report>
{
assert_eq!(
threshold,
ExpandedDifficulty::target_difficulty_limit(network),
network.target_difficulty_limit(),
"genesis block difficulty thresholds must be equal to the PoWLimit"
);
}
@ -477,12 +477,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, ExpandedDifficulty::target_difficulty_limit(Network::Testnet),
assert_eq!(threshold, Network::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 <= ExpandedDifficulty::target_difficulty_limit(Network::Testnet),
hash <= Network::Testnet.target_difficulty_limit(),
"testnet minimum difficulty hashes must be less than the PoWLimit"
);
}

View File

@ -9,7 +9,10 @@ use zebra_chain::{
block::{Block, Hash, Header, Height},
parameters::{Network, NetworkUpgrade},
transaction,
work::{difficulty::ExpandedDifficulty, equihash},
work::{
difficulty::{ExpandedDifficulty, ParameterDifficulty as _},
equihash,
},
};
use crate::{error::*, parameters::SLOW_START_INTERVAL};
@ -78,13 +81,13 @@ pub fn difficulty_threshold_is_valid(
// The PowLimit check is part of `Threshold()` in the spec, but it doesn't
// actually depend on any previous blocks.
if difficulty_threshold > ExpandedDifficulty::target_difficulty_limit(network) {
if difficulty_threshold > network.target_difficulty_limit() {
Err(BlockError::TargetDifficultyLimit(
*height,
*hash,
difficulty_threshold,
network,
ExpandedDifficulty::target_difficulty_limit(network),
network.target_difficulty_limit(),
))?;
}

View File

@ -48,22 +48,6 @@ pub fn funding_stream_values(
Ok(results)
}
/// Returns the minimum height after the first halving
/// as described in [protocol specification §7.10][7.10]
///
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
pub fn height_for_first_halving(network: Network) -> Height {
// First halving on Mainnet is at Canopy
// while in Testnet is at block constant height of `1_116_000`
// https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
match network {
Network::Mainnet => Canopy
.activation_height(network)
.expect("canopy activation height should be available"),
Network::Testnet => FIRST_HALVING_TESTNET,
}
}
/// Returns the address change period
/// as described in [protocol specification §7.10][7.10]
///
@ -78,7 +62,7 @@ fn funding_stream_address_period(height: Height, network: Network) -> u32 {
// <https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators>
// This is the same as `floor()`, because these numbers are all positive.
let height_after_first_halving = height - height_for_first_halving(network);
let height_after_first_halving = height - network.height_for_first_halving();
let address_period = (height_after_first_halving + POST_BLOSSOM_HALVING_INTERVAL)
/ FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL;
@ -93,10 +77,7 @@ fn funding_stream_address_period(height: Height, network: Network) -> u32 {
///
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
fn funding_stream_address_index(height: Height, network: Network) -> usize {
let num_addresses = match network {
Network::Mainnet => FUNDING_STREAMS_NUM_ADDRESSES_MAINNET,
Network::Testnet => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET,
};
let num_addresses = network.num_funding_streams();
let index = 1u32
.checked_add(funding_stream_address_period(height, network))

View File

@ -131,11 +131,7 @@ mod test {
fn halving_for_network(network: Network) -> Result<(), Report> {
let blossom_height = Blossom.activation_height(network).unwrap();
let first_halving_height = match network {
Network::Mainnet => Canopy.activation_height(network).unwrap(),
// Based on "7.8 Calculation of Block Subsidy and Founders' Reward"
Network::Testnet => Height(1_116_000),
};
let first_halving_height = network.height_for_first_halving();
assert_eq!(
1,
@ -261,11 +257,7 @@ mod test {
fn block_subsidy_for_network(network: Network) -> Result<(), Report> {
let blossom_height = Blossom.activation_height(network).unwrap();
let first_halving_height = match network {
Network::Mainnet => Canopy.activation_height(network).unwrap(),
// Based on "7.8 Calculation of Block Subsidy and Founders' Reward"
Network::Testnet => Height(1_116_000),
};
let first_halving_height = network.height_for_first_halving();
// After slow-start mining and before Blossom the block subsidy is 12.5 ZEC
// https://z.cash/support/faq/#what-is-slow-start-mining

View File

@ -19,7 +19,7 @@ use zebra_chain::{
parameters::{Network, NetworkUpgrade},
serialization::{ZcashDeserialize, ZcashDeserializeInto},
transaction::{arbitrary::transaction_to_fake_v5, LockTime, Transaction},
work::difficulty::{ExpandedDifficulty, INVALID_COMPACT_DIFFICULTY},
work::difficulty::{ParameterDifficulty as _, INVALID_COMPACT_DIFFICULTY},
};
use zebra_script::CachedFfiTransaction;
use zebra_test::transcript::{ExpectedTranscriptError, Transcript};
@ -241,7 +241,7 @@ fn difficulty_validation_failure() -> Result<(), Report> {
hash,
difficulty_threshold,
Network::Mainnet,
ExpandedDifficulty::target_difficulty_limit(Network::Mainnet),
Network::Mainnet.target_difficulty_limit(),
);
assert_eq!(expected, result);

View File

@ -42,7 +42,7 @@ use crate::{
TargetHeight::{self, *},
},
error::BlockError,
BoxError,
BoxError, ParameterCheckpoint as _,
};
pub(crate) mod list;
@ -207,7 +207,7 @@ where
initial_tip: Option<(block::Height, block::Hash)>,
state_service: S,
) -> Self {
let checkpoint_list = CheckpointList::new(network);
let checkpoint_list = network.checkpoint_list();
let max_height = checkpoint_list.max_height();
tracing::info!(
?max_height,

View File

@ -17,7 +17,7 @@ use std::{
};
use zebra_chain::block;
use zebra_chain::parameters::{genesis_hash, Network};
use zebra_chain::parameters::Network;
/// The hard-coded checkpoints for mainnet, generated using the
/// `zebra-checkpoints` tool.
@ -43,6 +43,47 @@ const MAINNET_CHECKPOINTS: &str = include_str!("main-checkpoints.txt");
/// information.
const TESTNET_CHECKPOINTS: &str = include_str!("test-checkpoints.txt");
/// Network methods related to checkpoints
pub trait ParameterCheckpoint {
/// Returns the hash for the genesis block in `network`.
fn genesis_hash(&self) -> zebra_chain::block::Hash;
/// Returns the hard-coded checkpoint list for `network`.
fn checkpoint_list(&self) -> CheckpointList;
}
impl ParameterCheckpoint for Network {
fn genesis_hash(&self) -> zebra_chain::block::Hash {
match self {
// zcash-cli getblockhash 0
Network::Mainnet => "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08",
// zcash-cli -testnet getblockhash 0
Network::Testnet => "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38",
}
.parse()
.expect("hard-coded hash parses")
}
fn checkpoint_list(&self) -> CheckpointList {
// parse calls CheckpointList::from_list
let checkpoint_list: CheckpointList = match self {
Network::Mainnet => MAINNET_CHECKPOINTS
.parse()
.expect("Hard-coded Mainnet checkpoint list parses and validates"),
Network::Testnet => TESTNET_CHECKPOINTS
.parse()
.expect("Hard-coded Testnet checkpoint list parses and validates"),
};
match checkpoint_list.hash(block::Height(0)) {
Some(hash) if hash == self.genesis_hash() => checkpoint_list,
Some(_) => {
panic!("The hard-coded genesis checkpoint does not match the network genesis hash")
}
None => unreachable!("Parser should have checked for a missing genesis checkpoint"),
}
}
}
/// A list of block height and hash checkpoints.
///
/// Checkpoints should be chosen to avoid forks or chain reorganizations,
@ -81,27 +122,6 @@ impl FromStr for CheckpointList {
}
impl CheckpointList {
/// Returns the hard-coded checkpoint list for `network`.
pub fn new(network: Network) -> Self {
// parse calls CheckpointList::from_list
let checkpoint_list: CheckpointList = match network {
Network::Mainnet => MAINNET_CHECKPOINTS
.parse()
.expect("Hard-coded Mainnet checkpoint list parses and validates"),
Network::Testnet => TESTNET_CHECKPOINTS
.parse()
.expect("Hard-coded Testnet checkpoint list parses and validates"),
};
match checkpoint_list.hash(block::Height(0)) {
Some(hash) if hash == genesis_hash(network) => checkpoint_list,
Some(_) => {
panic!("The hard-coded genesis checkpoint does not match the network genesis hash")
}
None => unreachable!("Parser should have checked for a missing genesis checkpoint"),
}
}
/// Create a new checkpoint list for `network` from `checkpoint_list`.
///
/// Assumes that the provided genesis checkpoint is correct.
@ -123,8 +143,8 @@ impl CheckpointList {
// Check that the list starts with the correct genesis block
match checkpoints.iter().next() {
Some((block::Height(0), hash))
if (hash == &genesis_hash(Network::Mainnet)
|| hash == &genesis_hash(Network::Testnet)) => {}
if (hash == &Network::Mainnet.genesis_hash()
|| hash == &Network::Testnet.genesis_hash()) => {}
Some((block::Height(0), _)) => {
Err("the genesis checkpoint does not match the Mainnet or Testnet genesis hash")?
}

View File

@ -235,8 +235,8 @@ fn checkpoint_list_load_hard_coded() -> Result<(), BoxError> {
.parse()
.expect("hard-coded Testnet checkpoint list should parse");
let _ = CheckpointList::new(Mainnet);
let _ = CheckpointList::new(Testnet);
let _ = Mainnet.checkpoint_list();
let _ = Testnet.checkpoint_list();
Ok(())
}
@ -257,7 +257,7 @@ fn checkpoint_list_hard_coded_mandatory(network: Network) -> Result<(), BoxError
let mandatory_checkpoint = network.mandatory_checkpoint_height();
let list = CheckpointList::new(network);
let list = network.checkpoint_list();
assert!(
list.max_height() >= mandatory_checkpoint,
@ -292,7 +292,7 @@ fn checkpoint_list_hard_coded_max_gap(network: Network) -> Result<(), BoxError>
HeightDiff::try_from(div_ceil(MAX_CHECKPOINT_BYTE_COUNT, MAX_BLOCK_BYTES))
.expect("constant fits in HeightDiff");
let list = CheckpointList::new(network);
let list = network.checkpoint_list();
let mut heights = list.0.keys();
// Check that we start at the genesis height

View File

@ -49,18 +49,19 @@ pub use block::{
subsidy::{
funding_streams::{
funding_stream_address, funding_stream_recipient_info, funding_stream_values,
height_for_first_halving, new_coinbase_script,
new_coinbase_script,
},
general::miner_subsidy,
},
Request, VerifyBlockError, MAX_BLOCK_SIGOPS,
};
pub use checkpoint::{
CheckpointList, VerifyCheckpointError, MAX_CHECKPOINT_BYTE_COUNT, MAX_CHECKPOINT_HEIGHT_GAP,
list::ParameterCheckpoint, CheckpointList, VerifyCheckpointError, MAX_CHECKPOINT_BYTE_COUNT,
MAX_CHECKPOINT_HEIGHT_GAP,
};
pub use config::Config;
pub use error::BlockError;
pub use parameters::FundingStreamReceiver;
pub use parameters::{FundingStreamReceiver, ParameterSubsidy};
pub use primitives::{ed25519, groth16, halo2, redjubjub, redpallas};
pub use router::RouterError;

View File

@ -7,7 +7,7 @@ use lazy_static::lazy_static;
use zebra_chain::{
amount::COIN,
block::{Height, HeightDiff},
parameters::Network,
parameters::{Network, NetworkUpgrade},
};
/// An initial period from Genesis to this Height where the block subsidy is gradually incremented. [What is slow-start mining][slow-mining]
@ -198,6 +198,38 @@ pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRE
"t3XHAGxRP2FNfhAjxGjxbrQPYtQQjc3RCQD",
];
/// Functionality specific to block subsidy-related consensus rules
pub trait ParameterSubsidy {
/// Number of addresses for each funding stream in the Network.
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
fn num_funding_streams(&self) -> usize;
/// Returns the minimum height after the first halving
/// as described in [protocol specification §7.10][7.10]
///
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
fn height_for_first_halving(&self) -> Height;
}
/// Network methods related to Block Subsidy and Funding Streams
impl ParameterSubsidy for Network {
fn num_funding_streams(&self) -> usize {
match self {
Network::Mainnet => FUNDING_STREAMS_NUM_ADDRESSES_MAINNET,
Network::Testnet => 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`
// https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
match self {
Network::Mainnet => NetworkUpgrade::Canopy
.activation_height(*self)
.expect("canopy activation height should be available"),
Network::Testnet => FIRST_HALVING_TESTNET,
}
}
}
/// List of addresses for the Zcash Foundation funding stream in the Mainnet.
pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];

View File

@ -36,7 +36,7 @@ use crate::{
block::{Request, SemanticBlockVerifier, VerifyBlockError},
checkpoint::{CheckpointList, CheckpointVerifier, VerifyCheckpointError},
error::TransactionError,
transaction, BoxError, Config,
transaction, BoxError, Config, ParameterCheckpoint as _,
};
#[cfg(test)]
@ -263,7 +263,7 @@ where
// > activation block hashes given in § 3.12 Mainnet and Testnet on p. 20.
//
// <https://zips.z.cash/protocol/protocol.pdf#blockchain>
let full_checkpoints = CheckpointList::new(network);
let full_checkpoints = network.checkpoint_list();
let mut already_warned = false;
for (height, checkpoint_hash) in full_checkpoints.iter() {
@ -363,7 +363,7 @@ where
pub fn init_checkpoint_list(config: Config, network: Network) -> (CheckpointList, Height) {
// TODO: Zebra parses the checkpoint list three times at startup.
// Instead, cache the checkpoint list for each `network`.
let list = CheckpointList::new(network);
let list = network.checkpoint_list();
let max_checkpoint_height = if config.checkpoint_sync {
list.max_height()

View File

@ -163,7 +163,7 @@ impl Encoder<Message> for Codec {
let start_len = dst.len();
{
let dst = &mut dst.writer();
dst.write_all(&Magic::from(self.builder.network).0[..])?;
dst.write_all(&self.builder.network.magic_value().0[..])?;
dst.write_all(command)?;
dst.write_u32::<LittleEndian>(body_length as u32)?;
@ -389,7 +389,7 @@ impl Decoder for Codec {
"read header from src buffer"
);
if magic != Magic::from(self.builder.network) {
if magic != self.builder.network.magic_value() {
return Err(Parse("supplied magic did not meet expectations"));
}
if body_len > self.builder.max_len {

View File

@ -23,11 +23,13 @@ impl fmt::Debug for Magic {
f.debug_tuple("Magic").field(&hex::encode(self.0)).finish()
}
}
impl From<Network> for Magic {
pub(crate) trait ParameterMagic {
fn magic_value(&self) -> Magic;
}
impl ParameterMagic for Network {
/// Get the magic value associated to this `Network`.
fn from(network: Network) -> Self {
match network {
fn magic_value(&self) -> Magic {
match self {
Network::Mainnet => magics::MAINNET,
Network::Testnet => magics::TESTNET,
}

View File

@ -519,7 +519,7 @@ where
//
// Get the network upgrades in height order, like `zcashd`.
let mut upgrades = IndexMap::new();
for (activation_height, network_upgrade) in NetworkUpgrade::activation_list(network) {
for (activation_height, network_upgrade) in network.activation_list() {
// Zebra defines network upgrades based on incompatible consensus rule changes,
// but zcashd defines them based on ZIPs.
//

View File

@ -20,10 +20,10 @@ use zebra_chain::{
transparent::{
self, EXTRA_ZEBRA_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN,
},
work::difficulty::{ExpandedDifficulty, U256},
work::difficulty::{ParameterDifficulty as _, U256},
};
use zebra_consensus::{
funding_stream_address, funding_stream_values, height_for_first_halving, miner_subsidy,
funding_stream_address, funding_stream_values, miner_subsidy, ParameterSubsidy as _,
RouterError,
};
use zebra_network::AddressBookPeers;
@ -1098,7 +1098,7 @@ where
best_chain_tip_height(&latest_chain_tip)?
};
if height < height_for_first_halving(network) {
if height < network.height_for_first_halving() {
return Err(Error {
code: ErrorCode::ServerError(0),
message: "Zebra does not support founders' reward subsidies, \
@ -1197,7 +1197,7 @@ where
// using this calculation.)
// Get expanded difficulties (256 bits), these are the inverse of the work
let pow_limit: U256 = ExpandedDifficulty::target_difficulty_limit(network).into();
let pow_limit: U256 = network.target_difficulty_limit().into();
let difficulty: U256 = chain_info
.expected_difficulty
.to_expanded()

View File

@ -23,7 +23,7 @@ use zebra_chain::{
serialization::{DateTime32, ZcashDeserializeInto},
transaction::Transaction,
transparent,
work::difficulty::{CompactDifficulty, ExpandedDifficulty},
work::difficulty::{CompactDifficulty, ParameterDifficulty as _},
};
use zebra_network::{address_book_peers::MockAddressBookPeers, types::MetaAddr};
use zebra_node_services::mempool;
@ -115,7 +115,7 @@ pub async fn test_responses<State, ReadState>(
let fake_max_time = DateTime32::from(1654008728);
// Use a valid fractional difficulty for snapshots
let pow_limit = ExpandedDifficulty::target_difficulty_limit(network);
let pow_limit = network.target_difficulty_limit();
let fake_difficulty = pow_limit * 2 / 3;
let fake_difficulty = CompactDifficulty::from(fake_difficulty);

View File

@ -1658,7 +1658,7 @@ async fn rpc_getdifficulty() {
chain_sync_status::MockSyncStatus,
chain_tip::mock::MockChainTip,
serialization::DateTime32,
work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256},
work::difficulty::{CompactDifficulty, ExpandedDifficulty, ParameterDifficulty as _, U256},
};
use zebra_network::address_book_peers::MockAddressBookPeers;
@ -1742,7 +1742,7 @@ async fn rpc_getdifficulty() {
assert_eq!(format!("{:.9}", get_difficulty.unwrap()), "0.000122072");
// Fake the ChainInfo response: difficulty limit - smallest valid difficulty
let pow_limit = ExpandedDifficulty::target_difficulty_limit(Mainnet);
let pow_limit = Mainnet.target_difficulty_limit();
let fake_difficulty = pow_limit.into();
let mut read_state2 = read_state.clone();
let mock_read_state_request_handler = async move {

View File

@ -277,9 +277,7 @@ fn difficulty_threshold_and_time_are_valid(
// of that block plus 90*60 seconds.
//
// https://zips.z.cash/protocol/protocol.pdf#blockheader
if NetworkUpgrade::is_max_block_time_enforced(network, candidate_height)
&& candidate_time > block_time_max
{
if network.is_max_block_time_enforced(candidate_height) && candidate_time > block_time_max {
Err(ValidateContextError::TimeTooLate {
candidate_time,
block_time_max,

View File

@ -15,8 +15,8 @@ use zebra_chain::{
parameters::Network,
parameters::NetworkUpgrade,
parameters::POW_AVERAGING_WINDOW,
work::difficulty::ExpandedDifficulty,
work::difficulty::{CompactDifficulty, U256},
work::difficulty::{ExpandedDifficulty, ParameterDifficulty as _},
};
/// The median block span for time median calculations.
@ -188,7 +188,7 @@ impl AdjustedDifficulty {
Network::Testnet,
"invalid network: the minimum difficulty rule only applies on testnet"
);
ExpandedDifficulty::target_difficulty_limit(self.network).to_compact()
self.network.target_difficulty_limit().to_compact()
} else {
self.threshold_bits()
}
@ -210,10 +210,7 @@ impl AdjustedDifficulty {
let threshold = (self.mean_target_difficulty() / averaging_window_timespan.num_seconds())
* self.median_timespan_bounded().num_seconds();
let threshold = min(
ExpandedDifficulty::target_difficulty_limit(self.network),
threshold,
);
let threshold = min(self.network.target_difficulty_limit(), threshold);
threshold.to_compact()
}

View File

@ -21,8 +21,8 @@ use tower::{
use zebra_chain::{
block::{self, Height, HeightDiff},
chain_tip::ChainTip,
parameters::genesis_hash,
};
use zebra_consensus::ParameterCheckpoint as _;
use zebra_network as zn;
use zebra_state as zs;
@ -500,7 +500,7 @@ where
));
let new_syncer = Self {
genesis_hash: genesis_hash(config.network.network),
genesis_hash: config.network.network.genesis_hash(),
max_checkpoint_height,
checkpoint_verify_concurrency_limit,
full_verify_concurrency_limit,

View File

@ -16,7 +16,7 @@ use zebra_chain::{
fmt::humantime_seconds,
parameters::{Network, NetworkUpgrade, POST_BLOSSOM_POW_TARGET_SPACING},
};
use zebra_consensus::CheckpointList;
use zebra_consensus::ParameterCheckpoint as _;
use zebra_state::MAX_BLOCK_REORG_HEIGHT;
use crate::components::sync::SyncStatus;
@ -82,7 +82,8 @@ pub async fn show_block_chain_progress(
// The minimum height of the valid best chain, based on:
// - the hard-coded checkpoint height,
// - the minimum number of blocks after the highest checkpoint.
let after_checkpoint_height = CheckpointList::new(network)
let after_checkpoint_height = network
.checkpoint_list()
.max_height()
.add(min_after_checkpoint_blocks)
.expect("hard-coded checkpoint height is far below Height::MAX");

View File

@ -5,7 +5,7 @@ use std::time::Duration;
use color_eyre::eyre::Result;
use zebra_chain::{block::Height, chain_tip::mock::MockChainTip, parameters::Network};
use zebra_consensus::CheckpointList;
use zebra_consensus::ParameterCheckpoint as _;
use zebrad::components::sync::end_of_support::{self, EOS_PANIC_AFTER, ESTIMATED_RELEASE_HEIGHT};
// Estimated blocks per day with the current 75 seconds block spacing.
@ -54,7 +54,7 @@ fn end_of_support_function() {
#[tracing_test::traced_test]
fn end_of_support_date() {
// Get the list of checkpoints.
let list = CheckpointList::new(Network::Mainnet);
let list = Network::Mainnet.checkpoint_list();
// Get the last one we have and use it as tip.
let higher_checkpoint = list.max_height();