diff --git a/zebra-consensus/src/parameters/network_upgrade.rs b/zebra-consensus/src/parameters/network_upgrade.rs index ff449666..76ea0627 100644 --- a/zebra-consensus/src/parameters/network_upgrade.rs +++ b/zebra-consensus/src/parameters/network_upgrade.rs @@ -2,7 +2,7 @@ use NetworkUpgrade::*; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::ops::Bound::*; use zebra_chain::types::BlockHeight; @@ -58,6 +58,32 @@ pub(crate) const TESTNET_ACTIVATION_HEIGHTS: &[(BlockHeight, NetworkUpgrade)] = // See ZIP 251 for updates. ]; +/// The Consensus Branch Id, used to bind transactions and blocks to a +/// particular network upgrade. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct ConsensusBranchId(u32); + +/// Network Upgrade Consensus Branch Ids. +/// +/// Branch ids are the same for mainnet and testnet. If there is a testnet +/// rollback after a bug, the branch id changes. +/// +/// Branch ids were introduced in the Overwinter upgrade, so there is no +/// BeforeOverwinter branch id. +/// +/// This is actually a bijective map, but it is const, so we use a vector, and +/// do the uniqueness check in the unit tests. +pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[ + // TODO(teor): byte order? + (Overwinter, ConsensusBranchId(0x5ba81b19)), + (Sapling, ConsensusBranchId(0x76b809bb)), + (Blossom, ConsensusBranchId(0x2bb40e60)), + (Heartwood, ConsensusBranchId(0xf5b9230b)), + // As of 21 July 2020. Could change before mainnet activation. + // See ZIP 251 for updates. + (Canopy, ConsensusBranchId(0xe9ff75a6)), +]; + impl NetworkUpgrade { /// Returns a BTreeMap of activation heights and network upgrades for /// `network`. @@ -106,4 +132,32 @@ impl NetworkUpgrade { .map(|(height, _)| *height) .next() } + + /// Returns a BTreeMap of NetworkUpgrades and their ConsensusBranchIds. + /// + /// Branch ids are the same for mainnet and testnet. + /// + /// If network upgrade does not have a branch id, that network upgrade does + /// not appear in the list. + /// + /// This is actually a bijective map. + pub(crate) fn branch_id_list() -> HashMap { + CONSENSUS_BRANCH_IDS.iter().cloned().collect() + } + + /// Returns the consensus branch id for this network upgrade. + /// + /// Returns None if this network upgrade has no consensus branch id. + pub fn branch_id(&self) -> Option { + NetworkUpgrade::branch_id_list().get(&self).cloned() + } +} + +impl ConsensusBranchId { + /// Returns the current consensus branch id for `network` and `height`. + /// + /// Returns None if the network has no branch id at this height. + pub fn current(network: Network, height: BlockHeight) -> Option { + NetworkUpgrade::current(network, height).branch_id() + } } diff --git a/zebra-consensus/src/parameters/tests.rs b/zebra-consensus/src/parameters/tests.rs index beafcab5..66b45ada 100644 --- a/zebra-consensus/src/parameters/tests.rs +++ b/zebra-consensus/src/parameters/tests.rs @@ -103,3 +103,75 @@ fn activation_consistent(network: Network) { ); } } + +/// Check that the network upgrades and branch ids are unique. +#[test] +fn branch_id_bijective() { + let branch_id_list = NetworkUpgrade::branch_id_list(); + let nus: HashSet<&NetworkUpgrade> = branch_id_list.keys().collect(); + assert_eq!(CONSENSUS_BRANCH_IDS.len(), nus.len()); + + let branch_ids: HashSet<&ConsensusBranchId> = branch_id_list.values().collect(); + assert_eq!(CONSENSUS_BRANCH_IDS.len(), branch_ids.len()); +} + +#[test] +fn branch_id_extremes_mainnet() { + branch_id_extremes(Mainnet) +} + +#[test] +fn branch_id_extremes_testnet() { + branch_id_extremes(Testnet) +} + +/// Test the branch_id_list, branch_id, and current functions for `network` with +/// extreme values. +fn branch_id_extremes(network: Network) { + // Branch ids were introduced in Overwinter + assert_eq!( + NetworkUpgrade::branch_id_list().get(&BeforeOverwinter), + None + ); + assert_eq!(ConsensusBranchId::current(network, BlockHeight(0)), None); + assert_eq!( + NetworkUpgrade::branch_id_list().get(&Overwinter).cloned(), + Overwinter.branch_id() + ); + + // We assume that the last upgrade we know about continues forever + // (even if we suspect that won't be true) + assert_ne!( + NetworkUpgrade::branch_id_list().get(&NetworkUpgrade::current(network, BlockHeight::MAX)), + None + ); + assert_ne!(ConsensusBranchId::current(network, BlockHeight::MAX), None); +} + +#[test] +fn branch_id_consistent_mainnet() { + branch_id_consistent(Mainnet) +} + +#[test] +fn branch_id_consistent_testnet() { + branch_id_consistent(Testnet) +} + +/// Check that the branch_id and current functions are consistent for `network`. +fn branch_id_consistent(network: Network) { + let branch_id_list = NetworkUpgrade::branch_id_list(); + let network_upgrades: HashSet<&NetworkUpgrade> = branch_id_list.keys().collect(); + + for &network_upgrade in network_upgrades { + let height = network_upgrade.activation_height(network); + + // Skip network upgrades that don't have activation heights yet + if let Some(height) = height { + assert_eq!( + ConsensusBranchId::current(network, height), + network_upgrade.branch_id() + ); + } + } +}