From c9ee85c3b5d31d533a67d4b90e40eb93c83c8ee7 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 22 Jul 2020 22:10:02 +1000 Subject: [PATCH] feature: Add network upgrade activation heights --- zebra-consensus/src/parameters.rs | 46 +------- zebra-consensus/src/parameters/genesis.rs | 22 ++++ .../src/parameters/network_upgrade.rs | 109 ++++++++++++++++++ zebra-consensus/src/parameters/tests.rs | 105 +++++++++++++++++ zebra-network/src/constants.rs | 2 +- zebra-network/src/peer/handshake.rs | 11 +- 6 files changed, 252 insertions(+), 43 deletions(-) create mode 100644 zebra-consensus/src/parameters/genesis.rs create mode 100644 zebra-consensus/src/parameters/network_upgrade.rs create mode 100644 zebra-consensus/src/parameters/tests.rs diff --git a/zebra-consensus/src/parameters.rs b/zebra-consensus/src/parameters.rs index 88b61bf6..141fbf95 100644 --- a/zebra-consensus/src/parameters.rs +++ b/zebra-consensus/src/parameters.rs @@ -9,45 +9,11 @@ //! Typically, consensus parameters are accessed via a function that takes a //! `Network` and `BlockHeight`. -use zebra_chain::block::BlockHeaderHash; -use zebra_chain::{Network, Network::*}; +pub mod genesis; +pub mod network_upgrade; -/// A Zcash network protocol upgrade. -// -// TODO: are new network upgrades a breaking change, or should we make this -// enum non-exhaustive? -pub enum NetworkUpgrade { - /// The Zcash protocol before the Overwinter upgrade. - /// - /// We avoid using `Sprout`, because the specification says that Sprout - /// is the name of the pre-Sapling protocol, before and after Overwinter. - BeforeOverwinter, - /// The Zcash protocol after the Overwinter upgrade. - Overwinter, - /// The Zcash protocol after the Sapling upgrade. - Sapling, - /// The Zcash protocol after the Blossom upgrade. - Blossom, - /// The Zcash protocol after the Heartwood upgrade. - Heartwood, - /// The Zcash protocol after the Canopy upgrade. - Canopy, -} +pub use genesis::*; +pub use network_upgrade::*; -/// 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: BlockHeaderHash = BlockHeaderHash([0; 32]); - -/// Returns the hash for the genesis block in `network`. -pub fn genesis_hash(network: Network) -> BlockHeaderHash { - match network { - // zcash-cli getblockhash 0 | zebrad revhex - Mainnet => "08ce3d9731b000c08338455c8a4a6bd05da16e26b11daa1b917184ece80f0400", - // zcash-cli -testnet getblockhash 0 | zebrad revhex - Testnet => "382c4a332661c7ed0671f32a34d724619f086c61873bce7c99859dd9920aa605", - } - .parse() - .expect("hard-coded hash parses") -} +#[cfg(test)] +mod tests; diff --git a/zebra-consensus/src/parameters/genesis.rs b/zebra-consensus/src/parameters/genesis.rs new file mode 100644 index 00000000..e3023d3f --- /dev/null +++ b/zebra-consensus/src/parameters/genesis.rs @@ -0,0 +1,22 @@ +//! Genesis consensus parameters for each Zcash network. + +use zebra_chain::block::BlockHeaderHash; +use zebra_chain::{Network, 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: BlockHeaderHash = BlockHeaderHash([0; 32]); + +/// Returns the hash for the genesis block in `network`. +pub fn genesis_hash(network: Network) -> BlockHeaderHash { + match network { + // zcash-cli getblockhash 0 | zebrad revhex + Mainnet => "08ce3d9731b000c08338455c8a4a6bd05da16e26b11daa1b917184ece80f0400", + // zcash-cli -testnet getblockhash 0 | zebrad revhex + Testnet => "382c4a332661c7ed0671f32a34d724619f086c61873bce7c99859dd9920aa605", + } + .parse() + .expect("hard-coded hash parses") +} diff --git a/zebra-consensus/src/parameters/network_upgrade.rs b/zebra-consensus/src/parameters/network_upgrade.rs new file mode 100644 index 00000000..ff449666 --- /dev/null +++ b/zebra-consensus/src/parameters/network_upgrade.rs @@ -0,0 +1,109 @@ +//! Network upgrade consensus parameters for Zcash. + +use NetworkUpgrade::*; + +use std::collections::BTreeMap; +use std::ops::Bound::*; + +use zebra_chain::types::BlockHeight; +use zebra_chain::{Network, Network::*}; + +/// A Zcash network protocol upgrade. +// +// TODO: are new network upgrades a breaking change, or should we make this +// enum non-exhaustive? +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum NetworkUpgrade { + /// The Zcash protocol before the Overwinter upgrade. + /// + /// We avoid using `Sprout`, because the specification says that Sprout + /// is the name of the pre-Sapling protocol, before and after Overwinter. + BeforeOverwinter, + /// The Zcash protocol after the Overwinter upgrade. + Overwinter, + /// The Zcash protocol after the Sapling upgrade. + Sapling, + /// The Zcash protocol after the Blossom upgrade. + Blossom, + /// The Zcash protocol after the Heartwood upgrade. + Heartwood, + /// The Zcash protocol after the Canopy upgrade. + Canopy, +} + +/// Mainnet network upgrade activation heights. +/// +/// 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 MAINNET_ACTIVATION_HEIGHTS: &[(BlockHeight, NetworkUpgrade)] = &[ + (BlockHeight(0), BeforeOverwinter), + (BlockHeight(347_500), Overwinter), + (BlockHeight(419_200), Sapling), + (BlockHeight(653_600), Blossom), + (BlockHeight(903_000), Heartwood), + (BlockHeight(1_046_400), Canopy), +]; + +/// Testnet network upgrade activation heights. +/// +/// 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 TESTNET_ACTIVATION_HEIGHTS: &[(BlockHeight, NetworkUpgrade)] = &[ + (BlockHeight(0), BeforeOverwinter), + (BlockHeight(207_500), Overwinter), + (BlockHeight(280_000), Sapling), + (BlockHeight(584_000), Blossom), + (BlockHeight(903_800), Heartwood), + // As of 21 July 2020, the Canopy testnet height has not been decided. + // See ZIP 251 for updates. +]; + +impl NetworkUpgrade { + /// Returns a BTreeMap of activation heights and network upgrades for + /// `network`. + /// + /// If the activation height of a future upgrade is not known, that + /// network upgrade does not appear in the list. + /// + /// This is actually a bijective map. + pub(crate) fn activation_list(network: Network) -> BTreeMap { + match network { + Mainnet => MAINNET_ACTIVATION_HEIGHTS, + Testnet => TESTNET_ACTIVATION_HEIGHTS, + } + .iter() + .cloned() + .collect() + } + + /// Returns the current network upgrade for `network` and `height`. + pub fn current(network: Network, height: BlockHeight) -> NetworkUpgrade { + NetworkUpgrade::activation_list(network) + .range(..=height) + .map(|(_, nu)| *nu) + .next_back() + .expect("every height has a current network upgrade") + } + + /// Returns the next network upgrade for `network` and `height`. + /// + /// Returns None if the name of the next upgrade has not been decided yet. + pub fn next(network: Network, height: BlockHeight) -> Option { + NetworkUpgrade::activation_list(network) + .range((Excluded(height), Unbounded)) + .map(|(_, nu)| *nu) + .next() + } + + /// Returns the activation height for this network upgrade on `network`. + /// + /// 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 { + NetworkUpgrade::activation_list(network) + .iter() + .filter(|(_, nu)| nu == &self) + .map(|(height, _)| *height) + .next() + } +} diff --git a/zebra-consensus/src/parameters/tests.rs b/zebra-consensus/src/parameters/tests.rs new file mode 100644 index 00000000..beafcab5 --- /dev/null +++ b/zebra-consensus/src/parameters/tests.rs @@ -0,0 +1,105 @@ +//! Consensus parameter tests for Zebra. + +use super::*; +use NetworkUpgrade::*; + +use std::collections::HashSet; + +use zebra_chain::types::BlockHeight; +use zebra_chain::{Network, Network::*}; + +/// Check that the activation heights and network upgrades are unique. +#[test] +fn activation_bijective() { + let mainnet_activations = NetworkUpgrade::activation_list(Mainnet); + let mainnet_heights: HashSet<&BlockHeight> = 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_heights: HashSet<&BlockHeight> = testnet_activations.keys().collect(); + assert_eq!(TESTNET_ACTIVATION_HEIGHTS.len(), testnet_heights.len()); + + let testnet_nus: HashSet<&NetworkUpgrade> = testnet_activations.values().collect(); + assert_eq!(TESTNET_ACTIVATION_HEIGHTS.len(), testnet_nus.len()); +} + +#[test] +fn activation_extremes_mainnet() { + activation_extremes(Mainnet) +} + +#[test] +fn activation_extremes_testnet() { + activation_extremes(Testnet) +} + +/// Test the activation_list, activation_height, current, and next functions +/// for `network` with extreme values. +fn activation_extremes(network: Network) { + // The first two upgrades are BeforeOverwinter and Overwinter + assert_eq!( + NetworkUpgrade::activation_list(network).get(&BlockHeight(0)), + Some(&BeforeOverwinter) + ); + assert_eq!( + BeforeOverwinter.activation_height(network), + Some(BlockHeight(0)) + ); + assert_eq!( + NetworkUpgrade::current(network, BlockHeight(0)), + BeforeOverwinter + ); + assert_eq!( + NetworkUpgrade::next(network, BlockHeight(0)), + Some(Overwinter) + ); + + // 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(&BlockHeight::MAX), + Some(&BeforeOverwinter) + ); + assert_ne!( + NetworkUpgrade::current(network, BlockHeight::MAX), + BeforeOverwinter + ); + assert_eq!(NetworkUpgrade::next(network, BlockHeight::MAX), None); +} + +#[test] +fn activation_consistent_mainnet() { + activation_consistent(Mainnet) +} + +#[test] +fn activation_consistent_testnet() { + activation_consistent(Testnet) +} + +/// Check that the activation_height, current, and next functions are +/// consistent for `network`. +fn activation_consistent(network: Network) { + let activation_list = NetworkUpgrade::activation_list(network); + let network_upgrades: HashSet<&NetworkUpgrade> = activation_list.values().collect(); + + for &network_upgrade in network_upgrades { + let height = network_upgrade + .activation_height(network) + .expect("activations must have a height"); + assert_eq!(NetworkUpgrade::current(network, height), network_upgrade); + // Network upgrades don't repeat + assert_ne!(NetworkUpgrade::next(network, height), Some(network_upgrade)); + assert_ne!( + NetworkUpgrade::next(network, BlockHeight(height.0 + 1)), + Some(network_upgrade) + ); + assert_ne!( + NetworkUpgrade::next(network, BlockHeight::MAX), + Some(network_upgrade) + ); + } +} diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 355bacca..5a805aed 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -46,7 +46,7 @@ pub const CURRENT_VERSION: Version = Version(170_011); /// /// Used to select the minimum supported version for peer connections. // -// TODO: dynamically choose the minimum network upgrade based on block height. +// TODO: replace with NetworkUpgrade::current(network, height). // See the detailed comment in handshake.rs, where this constant is used. pub const MIN_NETWORK_UPGRADE: NetworkUpgrade = Heartwood; diff --git a/zebra-network/src/peer/handshake.rs b/zebra-network/src/peer/handshake.rs index 5e73f2eb..34e30f4d 100644 --- a/zebra-network/src/peer/handshake.rs +++ b/zebra-network/src/peer/handshake.rs @@ -194,8 +194,15 @@ where // if (pfrom->nVersion < consensusParams.vUpgrades[currentEpoch].nProtocolVersion) // // For approximately 1.5 days before a network upgrade, we also need to: - // - prefer evicting pre-upgrade peers from the peer set, and - // - prefer choosing post-upgrade ready peers for queries + // - avoid old peers, and + // - prefer updated peers. + // For example, we could reject old peers with probability 0.5. + // + // At the network upgrade, we also need to disconnect from old peers. + // TODO: replace MIN_NETWORK_UPGRADE with + // NetworkUpgrade::current(network, height) where network is + // the configured network, and height is the best tip's block + // height. if remote_version < Version::min_version(network, constants::MIN_NETWORK_UPGRADE) { // Disconnect if peer is using an obsolete version.