Refactor block subsidy to handle Height::MAX without panicking (#5787)
This commit is contained in:
parent
019ae25847
commit
ea64585c62
|
|
@ -1,8 +1,9 @@
|
||||||
//! Consensus check functions
|
//! Consensus check functions
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::HashSet, sync::Arc};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::{Amount, Error as AmountError, NonNegative},
|
amount::{Amount, Error as AmountError, NonNegative},
|
||||||
block::{Block, Hash, Header, Height},
|
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)?;
|
let coinbase = block.transactions.get(0).ok_or(SubsidyError::NoCoinbase)?;
|
||||||
|
|
||||||
// Validate funding streams
|
// 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
|
let canopy_activation_height = NetworkUpgrade::Canopy
|
||||||
.activation_height(network)
|
.activation_height(network)
|
||||||
.expect("Canopy activation height is known");
|
.expect("Canopy activation height is known");
|
||||||
|
|
|
||||||
|
|
@ -18,27 +18,44 @@ use crate::{funding_stream_values, parameters::subsidy::*};
|
||||||
/// `1 << Halving(height)`, as described in [protocol specification §7.8][7.8]
|
/// `1 << Halving(height)`, as described in [protocol specification §7.8][7.8]
|
||||||
///
|
///
|
||||||
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
|
/// [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<u64> {
|
||||||
let blossom_height = Blossom
|
let blossom_height = Blossom
|
||||||
.activation_height(network)
|
.activation_height(network)
|
||||||
.expect("blossom activation height should be available");
|
.expect("blossom activation height should be available");
|
||||||
|
|
||||||
if height < SLOW_START_SHIFT {
|
if height < SLOW_START_SHIFT {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
"unsupported block height: callers should handle blocks below {:?}",
|
"unsupported block height: checkpoints should handle blocks below {:?}",
|
||||||
SLOW_START_SHIFT
|
SLOW_START_SHIFT
|
||||||
)
|
)
|
||||||
} else if height < blossom_height {
|
} else if height < blossom_height {
|
||||||
let scaled_pre_blossom_height = (height - SLOW_START_SHIFT) as u64;
|
let pre_blossom_height: u32 = (height - SLOW_START_SHIFT)
|
||||||
let halving_shift = scaled_pre_blossom_height / (PRE_BLOSSOM_HALVING_INTERVAL.0 as u64);
|
.try_into()
|
||||||
1 << halving_shift
|
.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 {
|
} else {
|
||||||
let scaled_pre_blossom_height =
|
let pre_blossom_height: u32 = (blossom_height - SLOW_START_SHIFT)
|
||||||
(blossom_height - SLOW_START_SHIFT) as u64 * BLOSSOM_POW_TARGET_SPACING_RATIO;
|
.try_into()
|
||||||
let post_blossom_height = (height - blossom_height) as u64;
|
.expect("blossom height is above slow start, and the difference fits in u32");
|
||||||
let halving_shift = (scaled_pre_blossom_height + post_blossom_height)
|
let scaled_pre_blossom_height = pre_blossom_height * BLOSSOM_POW_TARGET_SPACING_RATIO;
|
||||||
/ (POST_BLOSSOM_HALVING_INTERVAL.0 as u64);
|
|
||||||
1 << halving_shift
|
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<Amount<NonNegat
|
||||||
let blossom_height = Blossom
|
let blossom_height = Blossom
|
||||||
.activation_height(network)
|
.activation_height(network)
|
||||||
.expect("blossom activation height should be available");
|
.expect("blossom activation height should be available");
|
||||||
let halving_div = halving_divisor(height, network);
|
|
||||||
|
// If the halving divisor is larger than u64::MAX, the block subsidy is zero,
|
||||||
|
// because amounts fit in an i64.
|
||||||
|
//
|
||||||
|
// Note: bitcoind incorrectly wraps here, which restarts large block rewards.
|
||||||
|
let Some(halving_div) = halving_divisor(height, network) else {
|
||||||
|
return Ok(Amount::zero());
|
||||||
|
};
|
||||||
|
|
||||||
if height < SLOW_START_INTERVAL {
|
if height < SLOW_START_INTERVAL {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
|
|
@ -60,7 +84,8 @@ pub fn block_subsidy(height: Height, network: Network) -> Result<Amount<NonNegat
|
||||||
// this calculation is exact, because the halving divisor is 1 here
|
// this calculation is exact, because the halving divisor is 1 here
|
||||||
Amount::try_from(MAX_BLOCK_SUBSIDY / halving_div)
|
Amount::try_from(MAX_BLOCK_SUBSIDY / halving_div)
|
||||||
} else {
|
} else {
|
||||||
let scaled_max_block_subsidy = MAX_BLOCK_SUBSIDY / BLOSSOM_POW_TARGET_SPACING_RATIO;
|
let scaled_max_block_subsidy =
|
||||||
|
MAX_BLOCK_SUBSIDY / u64::from(BLOSSOM_POW_TARGET_SPACING_RATIO);
|
||||||
// in future halvings, this calculation might not be exact
|
// in future halvings, this calculation might not be exact
|
||||||
// Amount division is implemented using integer division,
|
// Amount division is implemented using integer division,
|
||||||
// which truncates (rounds down) the result, as specified
|
// which truncates (rounds down) the result, as specified
|
||||||
|
|
@ -111,17 +136,20 @@ mod test {
|
||||||
Network::Testnet => Height(1_116_000),
|
Network::Testnet => Height(1_116_000),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(1, halving_divisor((blossom_height - 1).unwrap(), network));
|
|
||||||
assert_eq!(1, halving_divisor(blossom_height, network));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1,
|
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!(
|
assert_eq!(
|
||||||
2,
|
2,
|
||||||
halving_divisor((first_halving_height + 1).unwrap(), network)
|
halving_divisor((first_halving_height + 1).unwrap(), network).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -130,6 +158,7 @@ mod test {
|
||||||
(first_halving_height + POST_BLOSSOM_HALVING_INTERVAL).unwrap(),
|
(first_halving_height + POST_BLOSSOM_HALVING_INTERVAL).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
8,
|
8,
|
||||||
|
|
@ -137,6 +166,7 @@ mod test {
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 2)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 2)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -145,6 +175,7 @@ mod test {
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 9)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 9)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1024 * 1024,
|
1024 * 1024,
|
||||||
|
|
@ -152,6 +183,7 @@ mod test {
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 19)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 19)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1024 * 1024 * 1024,
|
1024 * 1024 * 1024,
|
||||||
|
|
@ -159,6 +191,7 @@ mod test {
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 29)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 29)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1024 * 1024 * 1024 * 1024,
|
1024 * 1024 * 1024 * 1024,
|
||||||
|
|
@ -166,17 +199,48 @@ mod test {
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 39)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 39)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
// The largest possible divisor
|
// The largest possible integer divisor
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1 << 63,
|
(i64::MAX as u64 + 1),
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 62)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 62)).unwrap(),
|
||||||
network
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
//! Constants for Block Subsidy and Funding Streams
|
//! Constants for Block Subsidy and Funding Streams
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use zebra_chain::{amount::COIN, block::Height, parameters::Network};
|
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]
|
/// 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`
|
/// Calculated as `PRE_BLOSSOM_POW_TARGET_SPACING / POST_BLOSSOM_POW_TARGET_SPACING`
|
||||||
/// in the Zcash specification.
|
/// 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.
|
/// 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.
|
/// 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 =
|
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`
|
/// 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]
|
/// as specified in [protocol specification §7.10.1][7.10.1]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue