From 7586699f860d954765a5e2fb4bc6f7f79212be6c Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 29 Jun 2021 10:49:03 +1000 Subject: [PATCH] Support a minimum protocol version during initial block download (#2395) * Support a min protocol version during initial block download But don't actually use the state height yet. Also rename some functions and constants. Co-authored-by: Janito Vaqueiro Ferreira Filho --- zebra-network/src/constants.rs | 22 +++--- zebra-network/src/peer/handshake.rs | 39 ++++------- zebra-network/src/protocol/external/codec.rs | 4 +- zebra-network/src/protocol/external/types.rs | 73 ++++++++++++++------ 4 files changed, 82 insertions(+), 56 deletions(-) diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 2bcd93b73..361f021bb 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -100,19 +100,25 @@ pub const USER_AGENT: &str = "/Zebra:1.0.0-alpha.11/"; /// during connection setup. /// /// The current protocol version is checked by our peers. If it is too old, -/// newer peers will refuse to connect to us. +/// newer peers will disconnect from us. /// /// The current protocol version typically changes before Mainnet and Testnet /// network upgrades. -pub const CURRENT_VERSION: Version = Version(170_013); +pub const CURRENT_NETWORK_PROTOCOL_VERSION: Version = Version(170_013); -/// The most recent bilateral consensus upgrade implemented by this crate. +/// The minimum network protocol version accepted by this crate for each network, +/// represented as a network upgrade. /// -/// The minimum network upgrade is used to check the protocol versions of our -/// peers. If their versions are too old, we will disconnect from them. -// -// TODO: replace with NetworkUpgrade::current(network, height). (#1334) -pub const MIN_NETWORK_UPGRADE: NetworkUpgrade = NetworkUpgrade::Canopy; +/// The minimum protocol version is used to check the protocol versions of our +/// peers during the initial block download. After the intial block download, +/// we use the current block height to select the minimum network protocol +/// version. +/// +/// If peer versions are too old, we will disconnect from them. +/// +/// The minimum network protocol version typically changes after Mainnet network +/// upgrades. +pub const INITIAL_MIN_NETWORK_PROTOCOL_VERSION: NetworkUpgrade = NetworkUpgrade::Canopy; /// The default RTT estimate for peer responses. /// diff --git a/zebra-network/src/peer/handshake.rs b/zebra-network/src/peer/handshake.rs index 235992931..1b1e9440d 100644 --- a/zebra-network/src/peer/handshake.rs +++ b/zebra-network/src/peer/handshake.rs @@ -487,7 +487,7 @@ pub async fn negotiate_version( }; let our_version = Message::Version { - version: constants::CURRENT_VERSION, + version: constants::CURRENT_NETWORK_PROTOCOL_VERSION, services: our_services, timestamp, address_recv: (PeerServices::NODE_NETWORK, their_addr), @@ -552,29 +552,17 @@ pub async fn negotiate_version( Err(HandshakeError::NonceReuse)?; } - // XXX in zcashd remote peer can only send one version message and - // we would disconnect here if it received a second one. Is it even possible - // for that to happen to us here? - - // TODO: Reject incoming connections from nodes that don't know about the current epoch. - // zcashd does this: - // const Consensus::Params& consensusParams = chainparams.GetConsensus(); - // auto currentEpoch = CurrentEpoch(GetHeight(), consensusParams); - // if (pfrom->nVersion < consensusParams.vUpgrades[currentEpoch].nProtocolVersion) - // - // For approximately 1.5 days before a network upgrade, zcashd also: - // - avoids old peers, and - // - prefers updated peers. - // We haven't decided if we need this behaviour in Zebra yet (see #706). - // - // At the network upgrade, we also need to disconnect from old peers (see #1334). - // - // TODO: replace min_for_upgrade(network, MIN_NETWORK_UPGRADE) with - // current_min(network, height) where network is the - // configured network, and height is the best tip's block - // height. - - if remote_version < Version::min_for_upgrade(config.network, constants::MIN_NETWORK_UPGRADE) { + // TODO: Reject connections with nodes that don't know about the current network upgrade (#1334) + // Use the latest non-finalized block height, rather than the minimum + if remote_version + < Version::min_remote_for_height( + config.network, + // This code will be replaced in #1334 + constants::INITIAL_MIN_NETWORK_PROTOCOL_VERSION + .activation_height(config.network) + .expect("minimum network protocol network upgrade has an activation height"), + ) + { // Disconnect if peer is using an obsolete version. Err(HandshakeError::ObsoleteVersion(remote_version))?; } @@ -680,7 +668,8 @@ where } // Set the connection's version to the minimum of the received version or our own. - let negotiated_version = std::cmp::min(remote_version, constants::CURRENT_VERSION); + let negotiated_version = + std::cmp::min(remote_version, constants::CURRENT_NETWORK_PROTOCOL_VERSION); // Reconfigure the codec to use the negotiated version. // diff --git a/zebra-network/src/protocol/external/codec.rs b/zebra-network/src/protocol/external/codec.rs index 695527a52..070d70b4d 100644 --- a/zebra-network/src/protocol/external/codec.rs +++ b/zebra-network/src/protocol/external/codec.rs @@ -54,7 +54,7 @@ impl Codec { pub fn builder() -> Builder { Builder { network: Network::Mainnet, - version: constants::CURRENT_VERSION, + version: constants::CURRENT_NETWORK_PROTOCOL_VERSION, max_len: MAX_PROTOCOL_MESSAGE_LEN, metrics_addr_label: None, } @@ -650,7 +650,7 @@ mod tests { let services = PeerServices::NODE_NETWORK; let timestamp = Utc.timestamp(1_568_000_000, 0); Message::Version { - version: crate::constants::CURRENT_VERSION, + version: crate::constants::CURRENT_NETWORK_PROTOCOL_VERSION, services, timestamp, address_recv: ( diff --git a/zebra-network/src/protocol/external/types.rs b/zebra-network/src/protocol/external/types.rs index 3f9ecb5ec..6eef69873 100644 --- a/zebra-network/src/protocol/external/types.rs +++ b/zebra-network/src/protocol/external/types.rs @@ -1,8 +1,8 @@ #![allow(clippy::unit_arg)] -use crate::constants::magics; +use crate::constants::{self, magics}; -use std::fmt; +use std::{cmp::max, fmt}; use zebra_chain::{ block, @@ -42,9 +42,49 @@ impl From for Magic { pub struct Version(pub u32); impl Version { - /// Returns the minimum network protocol version for `network` and + /// Returns the minimum remote node network protocol version for `network` and + /// `height`. Zebra disconnects from peers with lower versions. + /// + /// # Panics + /// + /// If we are incompatible with our own minimum remote protocol version. + pub fn min_remote_for_height(network: Network, height: block::Height) -> Version { + let min_spec = Version::min_specified_for_height(network, height); + + // shut down if our own version is too old + assert!( + constants::CURRENT_NETWORK_PROTOCOL_VERSION >= min_spec, + "Zebra does not implement the minimum specified {:?} protocol version for {:?} at {:?}", + NetworkUpgrade::current(network, height), + network, + height, + ); + + max(min_spec, Version::initial_min_for_network(network)) + } + + /// Returns the minimum supported network protocol version for `network`. + /// + /// This is the minimum peer version when Zebra is significantly behind current tip: + /// - during the initial block download, + /// - after Zebra restarts, and + /// - after Zebra's local network is slow or shut down. + fn initial_min_for_network(network: Network) -> Version { + Version::min_specified_for_upgrade(network, constants::INITIAL_MIN_NETWORK_PROTOCOL_VERSION) + } + + /// Returns the minimum specified network protocol version for `network` and + /// `height`. + /// + /// This is the minimum peer version when Zebra is close to the current tip. + fn min_specified_for_height(network: Network, height: block::Height) -> Version { + let network_upgrade = NetworkUpgrade::current(network, height); + Version::min_specified_for_upgrade(network, network_upgrade) + } + + /// Returns the minimum specified network protocol version for `network` and /// `network_upgrade`. - pub fn min_for_upgrade(network: Network, network_upgrade: NetworkUpgrade) -> Self { + fn min_specified_for_upgrade(network: Network, network_upgrade: NetworkUpgrade) -> Version { // TODO: Should we reject earlier protocol versions during our initial // sync? zcashd accepts 170_002 or later during its initial sync. Version(match (network, network_upgrade) { @@ -62,15 +102,6 @@ impl Version { (Mainnet, Nu5) => 170_015, }) } - - /// Returns the current minimum protocol version for `network` and `height`. - /// - /// Returns None if the network has no branch id at this height. - #[allow(dead_code)] - pub fn current_min(network: Network, height: block::Height) -> Version { - let network_upgrade = NetworkUpgrade::current(network, height); - Version::min_for_upgrade(network, network_upgrade) - } } bitflags! { @@ -159,21 +190,21 @@ mod test { version_extremes(Testnet) } - /// Test the min_for_upgrade and current_min functions for `network` with + /// Test the min_specified_for_upgrade and min_specified_for_height functions for `network` with /// extreme values. fn version_extremes(network: Network) { zebra_test::init(); assert_eq!( - Version::current_min(network, block::Height(0)), - Version::min_for_upgrade(network, BeforeOverwinter), + Version::min_specified_for_height(network, block::Height(0)), + Version::min_specified_for_upgrade(network, BeforeOverwinter), ); // We assume that the last version we know about continues forever // (even if we suspect that won't be true) assert_ne!( - Version::current_min(network, block::Height::MAX), - Version::min_for_upgrade(network, BeforeOverwinter), + Version::min_specified_for_height(network, block::Height::MAX), + Version::min_specified_for_upgrade(network, BeforeOverwinter), ); } @@ -187,7 +218,7 @@ mod test { version_consistent(Testnet) } - /// Check that the min_for_upgrade and current_min functions + /// Check that the min_specified_for_upgrade and min_specified_for_height functions /// are consistent for `network`. fn version_consistent(network: Network) { zebra_test::init(); @@ -208,8 +239,8 @@ mod test { let height = network_upgrade.activation_height(network); if let Some(height) = height { assert_eq!( - Version::min_for_upgrade(network, network_upgrade), - Version::current_min(network, height) + Version::min_specified_for_upgrade(network, network_upgrade), + Version::min_specified_for_height(network, height) ); } }