diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index ce17abed..094adf47 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -1,8 +1,9 @@ //! Consensus check functions -use chrono::{DateTime, Utc}; use std::{collections::HashSet, sync::Arc}; +use chrono::{DateTime, Utc}; + use zebra_chain::{ amount::{Amount, Error as AmountError, NonNegative}, block::{Block, Hash, Header, Height}, @@ -126,7 +127,11 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro let coinbase = block.transactions.get(0).ok_or(SubsidyError::NoCoinbase)?; // Validate funding streams - let halving_div = subsidy::general::halving_divisor(height, network); + let Some(halving_div) = subsidy::general::halving_divisor(height, network) else { + // Far future halving, with no founders reward or funding streams + return Ok(()); + }; + let canopy_activation_height = NetworkUpgrade::Canopy .activation_height(network) .expect("Canopy activation height is known"); diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 9b2e69de..e959a792 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -18,27 +18,44 @@ use crate::{funding_stream_values, parameters::subsidy::*}; /// `1 << Halving(height)`, as described in [protocol specification §7.8][7.8] /// /// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies -pub fn halving_divisor(height: Height, network: Network) -> u64 { +/// +/// Returns `None` if the divisor would overflow a `u64`. +pub fn halving_divisor(height: Height, network: Network) -> Option { let blossom_height = Blossom .activation_height(network) .expect("blossom activation height should be available"); if height < SLOW_START_SHIFT { unreachable!( - "unsupported block height: callers should handle blocks below {:?}", + "unsupported block height: checkpoints should handle blocks below {:?}", SLOW_START_SHIFT ) } else if height < blossom_height { - let scaled_pre_blossom_height = (height - SLOW_START_SHIFT) as u64; - let halving_shift = scaled_pre_blossom_height / (PRE_BLOSSOM_HALVING_INTERVAL.0 as u64); - 1 << halving_shift + let pre_blossom_height: u32 = (height - SLOW_START_SHIFT) + .try_into() + .expect("height is above slow start, and the difference fits in u32"); + let halving_shift = pre_blossom_height / PRE_BLOSSOM_HALVING_INTERVAL.0; + + let halving_div = 1u64 + .checked_shl(halving_shift) + .expect("pre-blossom heights produce small shifts"); + + Some(halving_div) } else { - let scaled_pre_blossom_height = - (blossom_height - SLOW_START_SHIFT) as u64 * BLOSSOM_POW_TARGET_SPACING_RATIO; - let post_blossom_height = (height - blossom_height) as u64; - let halving_shift = (scaled_pre_blossom_height + post_blossom_height) - / (POST_BLOSSOM_HALVING_INTERVAL.0 as u64); - 1 << halving_shift + let pre_blossom_height: u32 = (blossom_height - SLOW_START_SHIFT) + .try_into() + .expect("blossom height is above slow start, and the difference fits in u32"); + let scaled_pre_blossom_height = pre_blossom_height * BLOSSOM_POW_TARGET_SPACING_RATIO; + + let post_blossom_height: u32 = (height - blossom_height) + .try_into() + .expect("height is above blossom, and the difference fits in u32"); + + let halving_shift = + (scaled_pre_blossom_height + post_blossom_height) / POST_BLOSSOM_HALVING_INTERVAL.0; + + // Some far-future shifts can be more than 63 bits + 1u64.checked_shl(halving_shift) } } @@ -49,7 +66,14 @@ pub fn block_subsidy(height: Height, network: Network) -> Result Result Height(1_116_000), }; - assert_eq!(1, halving_divisor((blossom_height - 1).unwrap(), network)); - assert_eq!(1, halving_divisor(blossom_height, network)); assert_eq!( 1, - halving_divisor((first_halving_height - 1).unwrap(), network) + halving_divisor((blossom_height - 1).unwrap(), network).unwrap() + ); + assert_eq!(1, halving_divisor(blossom_height, network).unwrap()); + assert_eq!( + 1, + halving_divisor((first_halving_height - 1).unwrap(), network).unwrap() ); - assert_eq!(2, halving_divisor(first_halving_height, network)); + assert_eq!(2, halving_divisor(first_halving_height, network).unwrap()); assert_eq!( 2, - halving_divisor((first_halving_height + 1).unwrap(), network) + halving_divisor((first_halving_height + 1).unwrap(), network).unwrap() ); assert_eq!( @@ -130,6 +158,7 @@ mod test { (first_halving_height + POST_BLOSSOM_HALVING_INTERVAL).unwrap(), network ) + .unwrap() ); assert_eq!( 8, @@ -137,6 +166,7 @@ mod test { (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 2)).unwrap(), network ) + .unwrap() ); assert_eq!( @@ -145,6 +175,7 @@ mod test { (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 9)).unwrap(), network ) + .unwrap() ); assert_eq!( 1024 * 1024, @@ -152,6 +183,7 @@ mod test { (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 19)).unwrap(), network ) + .unwrap() ); assert_eq!( 1024 * 1024 * 1024, @@ -159,6 +191,7 @@ mod test { (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 29)).unwrap(), network ) + .unwrap() ); assert_eq!( 1024 * 1024 * 1024 * 1024, @@ -166,17 +199,48 @@ mod test { (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 39)).unwrap(), network ) + .unwrap() ); - // The largest possible divisor + // The largest possible integer divisor assert_eq!( - 1 << 63, + (i64::MAX as u64 + 1), halving_divisor( (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 62)).unwrap(), network ) + .unwrap(), ); + // Very large divisors which should also result in zero amounts + assert_eq!( + None, + halving_divisor( + (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 63)).unwrap(), + network, + ), + ); + + assert_eq!( + None, + halving_divisor( + (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 64)).unwrap(), + network, + ), + ); + + assert_eq!( + None, + halving_divisor(Height(Height::MAX_AS_U32 / 4), network), + ); + + assert_eq!( + None, + halving_divisor(Height(Height::MAX_AS_U32 / 2), network), + ); + + assert_eq!(None, halving_divisor(Height::MAX, network)); + Ok(()) } diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index d05845d9..8456fdc3 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -1,8 +1,9 @@ //! Constants for Block Subsidy and Funding Streams -use lazy_static::lazy_static; use std::collections::HashMap; +use lazy_static::lazy_static; + use zebra_chain::{amount::COIN, block::Height, parameters::Network}; /// An initial period from Genesis to this Height where the block subsidy is gradually incremented. [What is slow-start mining][slow-mining] @@ -27,7 +28,7 @@ pub const MAX_BLOCK_SUBSIDY: u64 = ((25 * COIN) / 2) as u64; /// /// Calculated as `PRE_BLOSSOM_POW_TARGET_SPACING / POST_BLOSSOM_POW_TARGET_SPACING` /// in the Zcash specification. -pub const BLOSSOM_POW_TARGET_SPACING_RATIO: u64 = 2; +pub const BLOSSOM_POW_TARGET_SPACING_RATIO: u32 = 2; /// Halving is at about every 4 years, before Blossom block time is 150 seconds. /// @@ -36,7 +37,7 @@ pub const PRE_BLOSSOM_HALVING_INTERVAL: Height = Height(840_000); /// After Blossom the block time is reduced to 75 seconds but halving period should remain around 4 years. pub const POST_BLOSSOM_HALVING_INTERVAL: Height = - Height((PRE_BLOSSOM_HALVING_INTERVAL.0 as u64 * BLOSSOM_POW_TARGET_SPACING_RATIO) as u32); + Height(PRE_BLOSSOM_HALVING_INTERVAL.0 * BLOSSOM_POW_TARGET_SPACING_RATIO); /// The first halving height in the testnet is at block height `1_116_000` /// as specified in [protocol specification §7.10.1][7.10.1]