change(chain): Refactor the handling of height differences (#6330)
* Unify the `impl`s of `Sub` and `Add` for `Height` * Adjust tests for `Height` subtraction * Use `Height` instead of `i32` * Use `block:Height` in RPC tests * Use `let .. else` statement Co-authored-by: Arya <aryasolhi@gmail.com> * Update zebra-consensus/src/block/subsidy/general.rs * Refactor the handling of height differences * Remove a redundant comment * Update zebrad/src/components/sync/progress.rs Co-authored-by: Arya <aryasolhi@gmail.com> * Update progress.rs * impl TryFrom<u32> for Height * Make some test assertions clearer * Refactor estimate_up_to() * Restore a comment that was accidentally removed * Document when estimate_distance_to_network_chain_tip() returns None * Change HeightDiff to i64 and make Height.sub(Height) return HeightDiff (no Option) * Update chain tip estimates for HeightDiff i64 * Update subsidy for HeightDiff i64 * Fix some height calculation test edge cases * Fix the funding stream interval calculation --------- Co-authored-by: Arya <aryasolhi@gmail.com> Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
6a65df1554
commit
2a48d4cf25
|
|
@ -35,7 +35,7 @@ pub use commitment::{
|
||||||
};
|
};
|
||||||
pub use hash::Hash;
|
pub use hash::Hash;
|
||||||
pub use header::{BlockTimeError, CountedHeader, Header, ZCASH_BLOCK_VERSION};
|
pub use header::{BlockTimeError, CountedHeader, Header, ZCASH_BLOCK_VERSION};
|
||||||
pub use height::Height;
|
pub use height::{Height, HeightDiff};
|
||||||
pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES};
|
pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES};
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ use crate::serialization::SerializationError;
|
||||||
|
|
||||||
/// The length of the chain back to the genesis block.
|
/// The length of the chain back to the genesis block.
|
||||||
///
|
///
|
||||||
/// Block heights can't be added, but they can be *subtracted*,
|
/// Two [`Height`]s can't be added, but they can be *subtracted* to get their difference,
|
||||||
/// to get a difference of block heights, represented as an `i32`,
|
/// represented as an [`HeightDiff`]. This difference can then be added to or subtracted from a
|
||||||
/// and height differences can be added to block heights to get new heights.
|
/// [`Height`]. Note the similarity with `chrono::DateTime` and `chrono::Duration`.
|
||||||
///
|
///
|
||||||
/// # Invariants
|
/// # Invariants
|
||||||
///
|
///
|
||||||
|
|
@ -64,70 +64,71 @@ impl Height {
|
||||||
pub const MAX_EXPIRY_HEIGHT: Height = Height(499_999_999);
|
pub const MAX_EXPIRY_HEIGHT: Height = Height(499_999_999);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add<Height> for Height {
|
/// A difference between two [`Height`]s, possibly negative.
|
||||||
type Output = Option<Height>;
|
///
|
||||||
|
/// This can represent the difference between any height values,
|
||||||
|
/// even if they are outside the valid height range (for example, in buggy RPC code).
|
||||||
|
pub type HeightDiff = i64;
|
||||||
|
|
||||||
fn add(self, rhs: Height) -> Option<Height> {
|
impl TryFrom<u32> for Height {
|
||||||
// We know that both values are positive integers. Therefore, the result is
|
type Error = &'static str;
|
||||||
// positive, and we can skip the conversions. The checked_add is required,
|
|
||||||
// because the result may overflow.
|
|
||||||
let height = self.0.checked_add(rhs.0)?;
|
|
||||||
let height = Height(height);
|
|
||||||
|
|
||||||
if height <= Height::MAX && height >= Height::MIN {
|
/// Checks that the `height` is within the valid [`Height`] range.
|
||||||
Some(height)
|
fn try_from(height: u32) -> Result<Self, Self::Error> {
|
||||||
|
// Check the bounds.
|
||||||
|
if Height::MIN.0 <= height && height <= Height::MAX.0 {
|
||||||
|
Ok(Height(height))
|
||||||
} else {
|
} else {
|
||||||
None
|
Err("heights must be less than or equal to Height::MAX")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sub<Height> for Height {
|
|
||||||
type Output = i32;
|
|
||||||
|
|
||||||
/// Panics if the inputs or result are outside the valid i32 range.
|
|
||||||
fn sub(self, rhs: Height) -> i32 {
|
|
||||||
// We construct heights from integers without any checks,
|
|
||||||
// so the inputs or result could be out of range.
|
|
||||||
let lhs = i32::try_from(self.0)
|
|
||||||
.expect("out of range input `self`: inputs should be valid Heights");
|
|
||||||
let rhs =
|
|
||||||
i32::try_from(rhs.0).expect("out of range input `rhs`: inputs should be valid Heights");
|
|
||||||
lhs.checked_sub(rhs)
|
|
||||||
.expect("out of range result: valid input heights should yield a valid result")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't implement Add<u32> or Sub<u32>, because they cause type inference issues for integer constants.
|
// We don't implement Add<u32> or Sub<u32>, because they cause type inference issues for integer constants.
|
||||||
|
|
||||||
impl Add<i32> for Height {
|
impl Sub<Height> for Height {
|
||||||
|
type Output = HeightDiff;
|
||||||
|
|
||||||
|
/// Subtract two heights, returning the result, which can be negative.
|
||||||
|
/// Since [`HeightDiff`] is `i64` and [`Height`] is `u32`, the result is always correct.
|
||||||
|
fn sub(self, rhs: Height) -> Self::Output {
|
||||||
|
// All these conversions are exact, and the subtraction can't overflow or underflow.
|
||||||
|
let lhs = HeightDiff::from(self.0);
|
||||||
|
let rhs = HeightDiff::from(rhs.0);
|
||||||
|
|
||||||
|
lhs - rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<HeightDiff> for Height {
|
||||||
|
type Output = Option<Self>;
|
||||||
|
|
||||||
|
/// Subtract a height difference from a height, returning `None` if the resulting height is
|
||||||
|
/// outside the valid `Height` range (this also checks the result is non-negative).
|
||||||
|
fn sub(self, rhs: HeightDiff) -> Option<Self> {
|
||||||
|
// We need to convert the height to [`i64`] so we can subtract negative [`HeightDiff`]s.
|
||||||
|
let lhs = HeightDiff::from(self.0);
|
||||||
|
let res = lhs - rhs;
|
||||||
|
|
||||||
|
// Check the bounds.
|
||||||
|
let res = u32::try_from(res).ok()?;
|
||||||
|
Height::try_from(res).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<HeightDiff> for Height {
|
||||||
type Output = Option<Height>;
|
type Output = Option<Height>;
|
||||||
|
|
||||||
fn add(self, rhs: i32) -> Option<Height> {
|
/// Add a height difference to a height, returning `None` if the resulting height is outside
|
||||||
// Because we construct heights from integers without any checks,
|
/// the valid `Height` range (this also checks the result is non-negative).
|
||||||
// the input values could be outside the valid range for i32.
|
fn add(self, rhs: HeightDiff) -> Option<Height> {
|
||||||
let lhs = i32::try_from(self.0).ok()?;
|
// We need to convert the height to [`i64`] so we can add negative [`HeightDiff`]s.
|
||||||
let result = lhs.checked_add(rhs)?;
|
let lhs = i64::from(self.0);
|
||||||
let result = u32::try_from(result).ok()?;
|
let res = lhs + rhs;
|
||||||
match result {
|
|
||||||
h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sub<i32> for Height {
|
// Check the bounds.
|
||||||
type Output = Option<Height>;
|
let res = u32::try_from(res).ok()?;
|
||||||
|
Height::try_from(res).ok()
|
||||||
fn sub(self, rhs: i32) -> Option<Height> {
|
|
||||||
// These checks are required, see above for details.
|
|
||||||
let lhs = i32::try_from(self.0).ok()?;
|
|
||||||
let result = lhs.checked_sub(rhs)?;
|
|
||||||
let result = u32::try_from(result).ok()?;
|
|
||||||
match result {
|
|
||||||
h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,22 +137,22 @@ fn operator_tests() {
|
||||||
let _init_guard = zebra_test::init();
|
let _init_guard = zebra_test::init();
|
||||||
|
|
||||||
// Elementary checks.
|
// Elementary checks.
|
||||||
assert_eq!(Some(Height(2)), Height(1) + Height(1));
|
assert_eq!(Some(Height(2)), Height(1) + 1);
|
||||||
assert_eq!(None, Height::MAX + Height(1));
|
assert_eq!(None, Height::MAX + 1);
|
||||||
|
|
||||||
let height = Height(u32::pow(2, 31) - 2);
|
let height = Height(u32::pow(2, 31) - 2);
|
||||||
assert!(height < Height::MAX);
|
assert!(height < Height::MAX);
|
||||||
|
|
||||||
let max_height = (height + Height(1)).expect("this addition should produce the max height");
|
let max_height = (height + 1).expect("this addition should produce the max height");
|
||||||
assert!(height < max_height);
|
assert!(height < max_height);
|
||||||
assert!(max_height <= Height::MAX);
|
assert!(max_height <= Height::MAX);
|
||||||
assert_eq!(Height::MAX, max_height);
|
assert_eq!(Height::MAX, max_height);
|
||||||
assert_eq!(None, max_height + Height(1));
|
assert_eq!(None, max_height + 1);
|
||||||
|
|
||||||
// Bad heights aren't caught at compile-time or runtime, until we add or subtract
|
// Bad heights aren't caught at compile-time or runtime, until we add or subtract
|
||||||
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + Height(0));
|
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 0);
|
||||||
assert_eq!(None, Height(i32::MAX as u32) + Height(1));
|
assert_eq!(None, Height(i32::MAX as u32) + 1);
|
||||||
assert_eq!(None, Height(u32::MAX) + Height(0));
|
assert_eq!(None, Height(u32::MAX) + 0);
|
||||||
|
|
||||||
assert_eq!(Some(Height(2)), Height(1) + 1);
|
assert_eq!(Some(Height(2)), Height(1) + 1);
|
||||||
assert_eq!(None, Height::MAX + 1);
|
assert_eq!(None, Height::MAX + 1);
|
||||||
|
|
@ -162,14 +163,14 @@ fn operator_tests() {
|
||||||
assert_eq!(None, Height(0) + -1);
|
assert_eq!(None, Height(0) + -1);
|
||||||
assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX + -1);
|
assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX + -1);
|
||||||
|
|
||||||
// Bad heights aren't caught at compile-time or runtime, until we add or subtract
|
// Bad heights aren't caught at compile-time or runtime, until we add or subtract,
|
||||||
// `+ 0` would also cause an error here, but it triggers a spurious clippy lint
|
// and the result is invalid
|
||||||
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 1);
|
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 1);
|
||||||
assert_eq!(None, Height(i32::MAX as u32) + 1);
|
assert_eq!(None, Height(i32::MAX as u32) + 1);
|
||||||
assert_eq!(None, Height(u32::MAX) + 1);
|
assert_eq!(None, Height(u32::MAX) + 1);
|
||||||
|
|
||||||
// Adding negative numbers
|
// Adding negative numbers
|
||||||
assert_eq!(None, Height(i32::MAX as u32 + 1) + -1);
|
assert_eq!(Some(Height::MAX), Height(i32::MAX as u32 + 1) + -1);
|
||||||
assert_eq!(None, Height(u32::MAX) + -1);
|
assert_eq!(None, Height(u32::MAX) + -1);
|
||||||
|
|
||||||
assert_eq!(Some(Height(1)), Height(2) - 1);
|
assert_eq!(Some(Height(1)), Height(2) - 1);
|
||||||
|
|
@ -182,8 +183,9 @@ fn operator_tests() {
|
||||||
assert_eq!(Some(Height::MAX), Height(Height::MAX_AS_U32 - 1) - -1);
|
assert_eq!(Some(Height::MAX), Height(Height::MAX_AS_U32 - 1) - -1);
|
||||||
assert_eq!(None, Height::MAX - -1);
|
assert_eq!(None, Height::MAX - -1);
|
||||||
|
|
||||||
// Bad heights aren't caught at compile-time or runtime, until we add or subtract
|
// Bad heights aren't caught at compile-time or runtime, until we add or subtract,
|
||||||
assert_eq!(None, Height(i32::MAX as u32 + 1) - 1);
|
// and the result is invalid
|
||||||
|
assert_eq!(Some(Height::MAX), Height(i32::MAX as u32 + 1) - 1);
|
||||||
assert_eq!(None, Height(u32::MAX) - 1);
|
assert_eq!(None, Height(u32::MAX) - 1);
|
||||||
|
|
||||||
// Subtracting negative numbers
|
// Subtracting negative numbers
|
||||||
|
|
@ -191,13 +193,12 @@ fn operator_tests() {
|
||||||
assert_eq!(None, Height(i32::MAX as u32) - -1);
|
assert_eq!(None, Height(i32::MAX as u32) - -1);
|
||||||
assert_eq!(None, Height(u32::MAX) - -1);
|
assert_eq!(None, Height(u32::MAX) - -1);
|
||||||
|
|
||||||
// Sub<Height> panics on out of range errors
|
assert_eq!(1, (Height(2) - Height(1)));
|
||||||
assert_eq!(1, Height(2) - Height(1));
|
assert_eq!(0, (Height(1) - Height(1)));
|
||||||
assert_eq!(0, Height(1) - Height(1));
|
|
||||||
assert_eq!(-1, Height(0) - Height(1));
|
assert_eq!(-1, Height(0) - Height(1));
|
||||||
assert_eq!(-5, Height(2) - Height(7));
|
assert_eq!(-5, Height(2) - Height(7));
|
||||||
assert_eq!(Height::MAX_AS_U32 as i32, Height::MAX - Height(0));
|
assert_eq!(Height::MAX.0 as HeightDiff, (Height::MAX - Height(0)));
|
||||||
assert_eq!(1, Height::MAX - Height(Height::MAX_AS_U32 - 1));
|
assert_eq!(1, (Height::MAX - Height(Height::MAX_AS_U32 - 1)));
|
||||||
assert_eq!(-1, Height(Height::MAX_AS_U32 - 1) - Height::MAX);
|
assert_eq!(-1, Height(Height::MAX_AS_U32 - 1) - Height::MAX);
|
||||||
assert_eq!(-(Height::MAX_AS_U32 as i32), Height(0) - Height::MAX);
|
assert_eq!(-(Height::MAX_AS_U32 as HeightDiff), Height(0) - Height::MAX);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,26 +95,31 @@ pub trait ChainTip {
|
||||||
Some(estimator.estimate_height_at(now))
|
Some(estimator.estimate_height_at(now))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an estimate of how many blocks there are ahead of Zebra's best chain tip
|
/// Return an estimate of how many blocks there are ahead of Zebra's best chain tip until the
|
||||||
/// until the network chain tip, and Zebra's best chain tip height.
|
/// network chain tip, and Zebra's best chain tip height.
|
||||||
|
///
|
||||||
|
/// The first element in the returned tuple is the estimate.
|
||||||
|
/// The second element in the returned tuple is the current best chain tip.
|
||||||
///
|
///
|
||||||
/// The estimate is calculated based on the current local time, the block time of the best tip
|
/// The estimate is calculated based on the current local time, the block time of the best tip
|
||||||
/// and the height of the best tip.
|
/// and the height of the best tip.
|
||||||
///
|
///
|
||||||
/// This estimate may be negative if the current local time is behind the chain tip block's timestamp.
|
/// This estimate may be negative if the current local time is behind the chain tip block's
|
||||||
|
/// timestamp.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the state is empty.
|
||||||
fn estimate_distance_to_network_chain_tip(
|
fn estimate_distance_to_network_chain_tip(
|
||||||
&self,
|
&self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Option<(i32, block::Height)> {
|
) -> Option<(block::HeightDiff, block::Height)> {
|
||||||
let (current_height, current_block_time) = self.best_tip_height_and_block_time()?;
|
let (current_height, current_block_time) = self.best_tip_height_and_block_time()?;
|
||||||
|
|
||||||
let estimator =
|
let estimator =
|
||||||
NetworkChainTipHeightEstimator::new(current_block_time, current_height, network);
|
NetworkChainTipHeightEstimator::new(current_block_time, current_height, network);
|
||||||
|
|
||||||
Some((
|
let distance_to_tip = estimator.estimate_height_at(Utc::now()) - current_height;
|
||||||
estimator.estimate_height_at(Utc::now()) - current_height,
|
|
||||||
current_height,
|
Some((distance_to_tip, current_height))
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub struct MockChainTipSender {
|
||||||
best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,
|
best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,
|
||||||
|
|
||||||
/// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
|
/// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
|
||||||
estimated_distance_to_network_chain_tip: watch::Sender<Option<i32>>,
|
estimated_distance_to_network_chain_tip: watch::Sender<Option<block::HeightDiff>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mock [`ChainTip`] implementation that allows setting the `best_tip_height` externally.
|
/// A mock [`ChainTip`] implementation that allows setting the `best_tip_height` externally.
|
||||||
|
|
@ -43,7 +43,7 @@ pub struct MockChainTip {
|
||||||
best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,
|
best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,
|
||||||
|
|
||||||
/// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
|
/// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
|
||||||
estimated_distance_to_network_chain_tip: watch::Receiver<Option<i32>>,
|
estimated_distance_to_network_chain_tip: watch::Receiver<Option<block::HeightDiff>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockChainTip {
|
impl MockChainTip {
|
||||||
|
|
@ -112,7 +112,7 @@ impl ChainTip for MockChainTip {
|
||||||
fn estimate_distance_to_network_chain_tip(
|
fn estimate_distance_to_network_chain_tip(
|
||||||
&self,
|
&self,
|
||||||
_network: Network,
|
_network: Network,
|
||||||
) -> Option<(i32, block::Height)> {
|
) -> Option<(block::HeightDiff, block::Height)> {
|
||||||
self.estimated_distance_to_network_chain_tip
|
self.estimated_distance_to_network_chain_tip
|
||||||
.borrow()
|
.borrow()
|
||||||
.and_then(|estimated_distance| {
|
.and_then(|estimated_distance| {
|
||||||
|
|
@ -179,7 +179,10 @@ impl MockChainTipSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a new estimated distance to network chain tip to the [`MockChainTip`].
|
/// Send a new estimated distance to network chain tip to the [`MockChainTip`].
|
||||||
pub fn send_estimated_distance_to_network_chain_tip(&self, distance: impl Into<Option<i32>>) {
|
pub fn send_estimated_distance_to_network_chain_tip(
|
||||||
|
&self,
|
||||||
|
distance: impl Into<Option<block::HeightDiff>>,
|
||||||
|
) {
|
||||||
self.estimated_distance_to_network_chain_tip
|
self.estimated_distance_to_network_chain_tip
|
||||||
.send(distance.into())
|
.send(distance.into())
|
||||||
.expect("attempt to send a best tip height to a dropped `MockChainTip`");
|
.expect("attempt to send a best tip height to a dropped `MockChainTip`");
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::vec;
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block,
|
block::{self, HeightDiff},
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -87,12 +87,11 @@ impl NetworkChainTipHeightEstimator {
|
||||||
/// The amount of blocks advanced is then used to extrapolate the amount to advance the
|
/// The amount of blocks advanced is then used to extrapolate the amount to advance the
|
||||||
/// `current_block_time`.
|
/// `current_block_time`.
|
||||||
fn estimate_up_to(&mut self, max_height: block::Height) {
|
fn estimate_up_to(&mut self, max_height: block::Height) {
|
||||||
let remaining_blocks = i64::from(max_height - self.current_height);
|
let remaining_blocks = max_height - self.current_height;
|
||||||
|
|
||||||
if remaining_blocks > 0 {
|
if remaining_blocks > 0 {
|
||||||
let target_spacing_seconds = self.current_target_spacing.num_seconds();
|
let target_spacing_seconds = self.current_target_spacing.num_seconds();
|
||||||
let time_to_activation = Duration::seconds(remaining_blocks * target_spacing_seconds);
|
let time_to_activation = Duration::seconds(remaining_blocks * target_spacing_seconds);
|
||||||
|
|
||||||
self.current_block_time += time_to_activation;
|
self.current_block_time += time_to_activation;
|
||||||
self.current_height = max_height;
|
self.current_height = max_height;
|
||||||
}
|
}
|
||||||
|
|
@ -119,21 +118,25 @@ impl NetworkChainTipHeightEstimator {
|
||||||
time_difference_seconds -= 1;
|
time_difference_seconds -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let block_difference = i32::try_from(
|
|
||||||
// Euclidean division is used so that the number is rounded towards negative infinity,
|
// Euclidean division is used so that the number is rounded towards negative infinity,
|
||||||
// so that fractionary values always round down to the previous height when going back
|
// so that fractionary values always round down to the previous height when going back
|
||||||
// in time (i.e., when the dividend is negative). This works because the divisor (the
|
// in time (i.e., when the dividend is negative). This works because the divisor (the
|
||||||
// target spacing) is always positive.
|
// target spacing) is always positive.
|
||||||
time_difference_seconds.div_euclid(self.current_target_spacing.num_seconds()),
|
let block_difference: HeightDiff =
|
||||||
)
|
time_difference_seconds.div_euclid(self.current_target_spacing.num_seconds());
|
||||||
.expect("time difference is too large");
|
|
||||||
|
|
||||||
if -(block_difference as i64) > self.current_height.0 as i64 {
|
let current_height_as_diff = HeightDiff::from(self.current_height.0);
|
||||||
|
|
||||||
|
if let Some(height_estimate) = self.current_height + block_difference {
|
||||||
|
height_estimate
|
||||||
|
} else if current_height_as_diff + block_difference < 0 {
|
||||||
// Gracefully handle attempting to estimate a block before genesis. This can happen if
|
// Gracefully handle attempting to estimate a block before genesis. This can happen if
|
||||||
// the local time is set incorrectly to a time too far in the past.
|
// the local time is set incorrectly to a time too far in the past.
|
||||||
block::Height(0)
|
block::Height(0)
|
||||||
} else {
|
} else {
|
||||||
(self.current_height + block_difference).expect("block difference is too large")
|
// Gracefully handle attempting to estimate a block at a very large height. This can
|
||||||
|
// happen if the local time is set incorrectly to a time too far in the future.
|
||||||
|
block::Height::MAX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,10 +71,13 @@ fn estimate_time_difference(
|
||||||
active_network_upgrade: NetworkUpgrade,
|
active_network_upgrade: NetworkUpgrade,
|
||||||
) -> Duration {
|
) -> Duration {
|
||||||
let spacing_seconds = active_network_upgrade.target_spacing().num_seconds();
|
let spacing_seconds = active_network_upgrade.target_spacing().num_seconds();
|
||||||
|
let height_difference = end_height - start_height;
|
||||||
|
|
||||||
let height_difference = i64::from(end_height - start_height);
|
if height_difference > 0 {
|
||||||
|
|
||||||
Duration::seconds(height_difference * spacing_seconds)
|
Duration::seconds(height_difference * spacing_seconds)
|
||||||
|
} else {
|
||||||
|
Duration::zero()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use `displacement` to get a displacement duration between zero and the target spacing of the
|
/// Use `displacement` to get a displacement duration between zero and the target spacing of the
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{block::Height, parameters::NetworkUpgrade::Canopy};
|
use crate::{
|
||||||
|
block::{Height, HeightDiff},
|
||||||
|
parameters::NetworkUpgrade::Canopy,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
use proptest_derive::Arbitrary;
|
use proptest_derive::Arbitrary;
|
||||||
|
|
@ -46,7 +49,7 @@ mod tests;
|
||||||
/// period. Therefore Zebra must validate those blocks during the grace period using checkpoints.
|
/// period. Therefore Zebra must validate those blocks during the grace period using checkpoints.
|
||||||
/// Therefore the mandatory checkpoint height ([`Network::mandatory_checkpoint_height`]) must be
|
/// Therefore the mandatory checkpoint height ([`Network::mandatory_checkpoint_height`]) must be
|
||||||
/// after the grace period.
|
/// after the grace period.
|
||||||
const ZIP_212_GRACE_PERIOD_DURATION: i32 = 32_256;
|
const ZIP_212_GRACE_PERIOD_DURATION: HeightDiff = 32_256;
|
||||||
|
|
||||||
/// An enum describing the possible network choices.
|
/// An enum describing the possible network choices.
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,23 @@ pub fn height_for_first_halving(network: Network) -> Height {
|
||||||
///
|
///
|
||||||
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
||||||
fn funding_stream_address_period(height: Height, network: Network) -> u32 {
|
fn funding_stream_address_period(height: Height, network: Network) -> u32 {
|
||||||
// - Spec equation: `address_period = floor((height - height_for_halving(1) - post_blossom_halving_interval)/funding_stream_address_change_interval)`:
|
// Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`,
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
// <https://zips.z.cash/protocol/protocol.pdf#fundingstreams>
|
||||||
// - In Rust, "integer division rounds towards zero":
|
//
|
||||||
// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
|
// Note that the brackets make it so the post blossom halving interval is added to the total.
|
||||||
|
//
|
||||||
|
// In Rust, "integer division rounds towards zero":
|
||||||
|
// <https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators>
|
||||||
// This is the same as `floor()`, because these numbers are all positive.
|
// This is the same as `floor()`, because these numbers are all positive.
|
||||||
(height.0 + (POST_BLOSSOM_HALVING_INTERVAL.0) - (height_for_first_halving(network).0))
|
|
||||||
/ (FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL.0)
|
let height_after_first_halving = height - height_for_first_halving(network);
|
||||||
|
|
||||||
|
let address_period = (height_after_first_halving + POST_BLOSSOM_HALVING_INTERVAL)
|
||||||
|
/ FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL;
|
||||||
|
|
||||||
|
address_period
|
||||||
|
.try_into()
|
||||||
|
.expect("all values are positive and smaller than the input height")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the position in the address slice for each funding stream
|
/// Returns the position in the address slice for each funding stream
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::{collections::HashSet, convert::TryFrom};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::{Amount, Error, NonNegative},
|
amount::{Amount, Error, NonNegative},
|
||||||
block::Height,
|
block::{Height, HeightDiff},
|
||||||
parameters::{Network, NetworkUpgrade::*},
|
parameters::{Network, NetworkUpgrade::*},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
|
@ -27,35 +27,37 @@ pub fn halving_divisor(height: Height, network: Network) -> Option<u64> {
|
||||||
|
|
||||||
if height < SLOW_START_SHIFT {
|
if height < SLOW_START_SHIFT {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
"unsupported block height: checkpoints should handle blocks below {:?}",
|
"unsupported block height {height:?}: checkpoints should handle blocks below {SLOW_START_SHIFT:?}",
|
||||||
SLOW_START_SHIFT
|
|
||||||
)
|
)
|
||||||
} else if height < blossom_height {
|
} else if height < blossom_height {
|
||||||
let pre_blossom_height: u32 = (height - SLOW_START_SHIFT)
|
let pre_blossom_height = height - SLOW_START_SHIFT;
|
||||||
.try_into()
|
let halving_shift = pre_blossom_height / PRE_BLOSSOM_HALVING_INTERVAL;
|
||||||
.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
|
let halving_div = 1u64
|
||||||
.checked_shl(halving_shift)
|
.checked_shl(
|
||||||
|
halving_shift
|
||||||
|
.try_into()
|
||||||
|
.expect("already checked for negatives"),
|
||||||
|
)
|
||||||
.expect("pre-blossom heights produce small shifts");
|
.expect("pre-blossom heights produce small shifts");
|
||||||
|
|
||||||
Some(halving_div)
|
Some(halving_div)
|
||||||
} else {
|
} else {
|
||||||
let pre_blossom_height: u32 = (blossom_height - SLOW_START_SHIFT)
|
let pre_blossom_height = blossom_height - SLOW_START_SHIFT;
|
||||||
.try_into()
|
let scaled_pre_blossom_height = pre_blossom_height
|
||||||
.expect("blossom height is above slow start, and the difference fits in u32");
|
* HeightDiff::try_from(BLOSSOM_POW_TARGET_SPACING_RATIO).expect("constant is positive");
|
||||||
let scaled_pre_blossom_height = pre_blossom_height * BLOSSOM_POW_TARGET_SPACING_RATIO;
|
|
||||||
|
|
||||||
let post_blossom_height: u32 = (height - blossom_height)
|
let post_blossom_height = height - blossom_height;
|
||||||
.try_into()
|
|
||||||
.expect("height is above blossom, and the difference fits in u32");
|
|
||||||
|
|
||||||
let halving_shift =
|
let halving_shift =
|
||||||
(scaled_pre_blossom_height + post_blossom_height) / POST_BLOSSOM_HALVING_INTERVAL.0;
|
(scaled_pre_blossom_height + post_blossom_height) / POST_BLOSSOM_HALVING_INTERVAL;
|
||||||
|
|
||||||
// Some far-future shifts can be more than 63 bits
|
// Some far-future shifts can be more than 63 bits
|
||||||
1u64.checked_shl(halving_shift)
|
1u64.checked_shl(
|
||||||
|
halving_shift
|
||||||
|
.try_into()
|
||||||
|
.expect("already checked for negatives"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,8 +79,7 @@ pub fn block_subsidy(height: Height, network: Network) -> Result<Amount<NonNegat
|
||||||
|
|
||||||
if height < SLOW_START_INTERVAL {
|
if height < SLOW_START_INTERVAL {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
"unsupported block height: callers should handle blocks below {:?}",
|
"unsupported block height {height:?}: callers should handle blocks below {SLOW_START_INTERVAL:?}",
|
||||||
SLOW_START_INTERVAL
|
|
||||||
)
|
)
|
||||||
} else if height < blossom_height {
|
} else if height < blossom_height {
|
||||||
// this calculation is exact, because the halving divisor is 1 here
|
// this calculation is exact, because the halving divisor is 1 here
|
||||||
|
|
@ -136,6 +137,10 @@ mod test {
|
||||||
Network::Testnet => Height(1_116_000),
|
Network::Testnet => Height(1_116_000),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
1,
|
||||||
|
halving_divisor((SLOW_START_INTERVAL + 1).unwrap(), network).unwrap()
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1,
|
1,
|
||||||
halving_divisor((blossom_height - 1).unwrap(), network).unwrap()
|
halving_divisor((blossom_height - 1).unwrap(), network).unwrap()
|
||||||
|
|
@ -163,7 +168,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
8,
|
8,
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 2)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 2)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -172,7 +177,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1024,
|
1024,
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 9)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 9)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -180,7 +185,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1024 * 1024,
|
1024 * 1024,
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 19)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 19)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -188,7 +193,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1024 * 1024 * 1024,
|
1024 * 1024 * 1024,
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 29)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 29)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -196,7 +201,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1024 * 1024 * 1024 * 1024,
|
1024 * 1024 * 1024 * 1024,
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 39)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 39)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -206,7 +211,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(i64::MAX as u64 + 1),
|
(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 * 62)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
@ -216,7 +221,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
None,
|
None,
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 63)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 63)).unwrap(),
|
||||||
network,
|
network,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -224,7 +229,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
None,
|
None,
|
||||||
halving_divisor(
|
halving_divisor(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 64)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 64)).unwrap(),
|
||||||
network,
|
network,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -264,6 +269,10 @@ mod test {
|
||||||
|
|
||||||
// After slow-start mining and before Blossom the block subsidy is 12.5 ZEC
|
// After slow-start mining and before Blossom the block subsidy is 12.5 ZEC
|
||||||
// https://z.cash/support/faq/#what-is-slow-start-mining
|
// https://z.cash/support/faq/#what-is-slow-start-mining
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(1_250_000_000),
|
||||||
|
block_subsidy((SLOW_START_INTERVAL + 1).unwrap(), network)
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::try_from(1_250_000_000),
|
Amount::try_from(1_250_000_000),
|
||||||
block_subsidy((blossom_height - 1).unwrap(), network)
|
block_subsidy((blossom_height - 1).unwrap(), network)
|
||||||
|
|
@ -298,7 +307,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::try_from(4_882_812),
|
Amount::try_from(4_882_812),
|
||||||
block_subsidy(
|
block_subsidy(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 6)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 6)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -308,7 +317,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::try_from(1),
|
Amount::try_from(1),
|
||||||
block_subsidy(
|
block_subsidy(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 28)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 28)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -318,20 +327,71 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::try_from(0),
|
Amount::try_from(0),
|
||||||
block_subsidy(
|
block_subsidy(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 29)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 29)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// The largest possible divisor
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::try_from(0),
|
Amount::try_from(0),
|
||||||
block_subsidy(
|
block_subsidy(
|
||||||
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL.0 as i32 * 62)).unwrap(),
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 39)).unwrap(),
|
||||||
network
|
network
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(0),
|
||||||
|
block_subsidy(
|
||||||
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 49)).unwrap(),
|
||||||
|
network
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(0),
|
||||||
|
block_subsidy(
|
||||||
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 59)).unwrap(),
|
||||||
|
network
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// The largest possible integer divisor
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(0),
|
||||||
|
block_subsidy(
|
||||||
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 62)).unwrap(),
|
||||||
|
network
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Other large divisors which should also result in zero
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(0),
|
||||||
|
block_subsidy(
|
||||||
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 63)).unwrap(),
|
||||||
|
network
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(0),
|
||||||
|
block_subsidy(
|
||||||
|
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 64)).unwrap(),
|
||||||
|
network
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(0),
|
||||||
|
block_subsidy(Height(Height::MAX_AS_U32 / 4), network)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Amount::try_from(0),
|
||||||
|
block_subsidy(Height(Height::MAX_AS_U32 / 2), network)
|
||||||
|
);
|
||||||
|
assert_eq!(Amount::try_from(0), block_subsidy(Height::MAX, network));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
//! Tests for CheckpointList
|
//! Tests for CheckpointList
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use zebra_chain::parameters::{Network, Network::*};
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block},
|
block::{self, Block, HeightDiff},
|
||||||
|
parameters::{Network, Network::*},
|
||||||
serialization::ZcashDeserialize,
|
serialization::ZcashDeserialize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
/// Make a checkpoint list containing only the genesis block
|
/// Make a checkpoint list containing only the genesis block
|
||||||
#[test]
|
#[test]
|
||||||
fn checkpoint_list_genesis() -> Result<(), BoxError> {
|
fn checkpoint_list_genesis() -> Result<(), BoxError> {
|
||||||
|
|
@ -290,7 +290,8 @@ fn checkpoint_list_hard_coded_max_gap(network: Network) -> Result<(), BoxError>
|
||||||
assert_eq!(heights.next(), Some(&previous_height));
|
assert_eq!(heights.next(), Some(&previous_height));
|
||||||
|
|
||||||
for height in heights {
|
for height in heights {
|
||||||
let height_limit = (previous_height + (crate::MAX_CHECKPOINT_HEIGHT_GAP as i32)).unwrap();
|
let height_limit =
|
||||||
|
(previous_height + (crate::MAX_CHECKPOINT_HEIGHT_GAP as HeightDiff)).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
height <= &height_limit,
|
height <= &height_limit,
|
||||||
"Checkpoint gaps must be within MAX_CHECKPOINT_HEIGHT_GAP"
|
"Checkpoint gaps must be within MAX_CHECKPOINT_HEIGHT_GAP"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use zebra_chain::{amount::COIN, block::Height, parameters::Network};
|
use zebra_chain::{
|
||||||
|
amount::COIN,
|
||||||
|
block::{Height, HeightDiff},
|
||||||
|
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]
|
||||||
///
|
///
|
||||||
|
|
@ -33,11 +37,11 @@ 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.
|
||||||
///
|
///
|
||||||
/// `(60 * 60 * 24 * 365 * 4) / 150 = 840960`
|
/// `(60 * 60 * 24 * 365 * 4) / 150 = 840960`
|
||||||
pub const PRE_BLOSSOM_HALVING_INTERVAL: Height = Height(840_000);
|
pub const PRE_BLOSSOM_HALVING_INTERVAL: HeightDiff = 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: HeightDiff =
|
||||||
Height(PRE_BLOSSOM_HALVING_INTERVAL.0 * BLOSSOM_POW_TARGET_SPACING_RATIO);
|
PRE_BLOSSOM_HALVING_INTERVAL * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
|
||||||
|
|
||||||
/// 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]
|
||||||
|
|
@ -133,8 +137,7 @@ lazy_static! {
|
||||||
/// as described in [protocol specification §7.10.1][7.10.1].
|
/// as described in [protocol specification §7.10.1][7.10.1].
|
||||||
///
|
///
|
||||||
/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
|
/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
|
||||||
pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: Height =
|
pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: HeightDiff = POST_BLOSSOM_HALVING_INTERVAL / 48;
|
||||||
Height(POST_BLOSSOM_HALVING_INTERVAL.0 / 48);
|
|
||||||
|
|
||||||
/// Number of addresses for each funding stream in the Mainnet.
|
/// Number of addresses for each funding stream in the Mainnet.
|
||||||
/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
|
/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use jsonrpc_core::ErrorCode;
|
use jsonrpc_core::ErrorCode;
|
||||||
|
|
||||||
|
use zebra_chain::block;
|
||||||
use zebra_consensus::FundingStreamReceiver::{self, *};
|
use zebra_consensus::FundingStreamReceiver::{self, *};
|
||||||
|
|
||||||
/// When long polling, the amount of time we wait between mempool queries.
|
/// When long polling, the amount of time we wait between mempool queries.
|
||||||
|
|
@ -42,7 +43,7 @@ pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &["proposal"];
|
||||||
/// > and clock time varies between nodes.
|
/// > and clock time varies between nodes.
|
||||||
/// >
|
/// >
|
||||||
/// > <https://zips.z.cash/protocol/protocol.pdf#blockheader>
|
/// > <https://zips.z.cash/protocol/protocol.pdf#blockheader>
|
||||||
pub const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 100;
|
pub const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: block::HeightDiff = 100;
|
||||||
|
|
||||||
/// The RPC error code used by `zcashd` for when it's still downloading initial blocks.
|
/// The RPC error code used by `zcashd` for when it's still downloading initial blocks.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ where
|
||||||
|| estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP
|
|| estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP
|
||||||
{
|
{
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
estimated_distance_to_chain_tip,
|
?estimated_distance_to_chain_tip,
|
||||||
?local_tip_height,
|
?local_tip_height,
|
||||||
"Zebra has not synced to the chain tip. \
|
"Zebra has not synced to the chain tip. \
|
||||||
Hint: check your network connection, clock, and time zone settings."
|
Hint: check your network connection, clock, and time zone settings."
|
||||||
|
|
@ -183,7 +183,7 @@ where
|
||||||
code: NOT_SYNCED_ERROR_CODE,
|
code: NOT_SYNCED_ERROR_CODE,
|
||||||
message: format!(
|
message: format!(
|
||||||
"Zebra has not synced to the chain tip, \
|
"Zebra has not synced to the chain tip, \
|
||||||
estimated distance: {estimated_distance_to_chain_tip}, \
|
estimated distance: {estimated_distance_to_chain_tip:?}, \
|
||||||
local tip: {local_tip_height:?}. \
|
local tip: {local_tip_height:?}. \
|
||||||
Hint: check your network connection, clock, and time zone settings."
|
Hint: check your network connection, clock, and time zone settings."
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ pub use zebra_chain::transparent::MIN_TRANSPARENT_COINBASE_MATURITY;
|
||||||
/// early non-finalized blocks, or finalized blocks. But if that chain becomes
|
/// early non-finalized blocks, or finalized blocks. But if that chain becomes
|
||||||
/// the best chain, all non-finalized blocks past the [`MAX_BLOCK_REORG_HEIGHT`]
|
/// the best chain, all non-finalized blocks past the [`MAX_BLOCK_REORG_HEIGHT`]
|
||||||
/// will be finalized. This includes all mature coinbase outputs.
|
/// will be finalized. This includes all mature coinbase outputs.
|
||||||
|
//
|
||||||
|
// TODO: change to HeightDiff
|
||||||
pub const MAX_BLOCK_REORG_HEIGHT: u32 = MIN_TRANSPARENT_COINBASE_MATURITY - 1;
|
pub const MAX_BLOCK_REORG_HEIGHT: u32 = MIN_TRANSPARENT_COINBASE_MATURITY - 1;
|
||||||
|
|
||||||
/// The database format version, incremented each time the database format changes.
|
/// The database format version, incremented each time the database format changes.
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ use tracing::{instrument, Instrument, Span};
|
||||||
use tower::buffer::Buffer;
|
use tower::buffer::Buffer;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, CountedHeader},
|
block::{self, CountedHeader, HeightDiff},
|
||||||
diagnostic::CodeTimer,
|
diagnostic::CodeTimer,
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
};
|
};
|
||||||
|
|
@ -391,7 +391,8 @@ impl StateService {
|
||||||
);
|
);
|
||||||
|
|
||||||
let full_verifier_utxo_lookahead = max_checkpoint_height
|
let full_verifier_utxo_lookahead = max_checkpoint_height
|
||||||
- i32::try_from(checkpoint_verify_concurrency_limit).expect("fits in i32");
|
- HeightDiff::try_from(checkpoint_verify_concurrency_limit)
|
||||||
|
.expect("fits in HeightDiff");
|
||||||
let full_verifier_utxo_lookahead =
|
let full_verifier_utxo_lookahead =
|
||||||
full_verifier_utxo_lookahead.expect("unexpected negative height");
|
full_verifier_utxo_lookahead.expect("unexpected negative height");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount, block,
|
amount,
|
||||||
transparent::{self, utxos_from_ordered_utxos, CoinbaseSpendRestriction::*},
|
transparent::{self, utxos_from_ordered_utxos, CoinbaseSpendRestriction::*},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -200,7 +200,7 @@ pub fn transparent_coinbase_spend(
|
||||||
match spend_restriction {
|
match spend_restriction {
|
||||||
OnlyShieldedOutputs { spend_height } => {
|
OnlyShieldedOutputs { spend_height } => {
|
||||||
let min_spend_height =
|
let min_spend_height =
|
||||||
utxo.utxo.height + block::Height(MIN_TRANSPARENT_COINBASE_MATURITY);
|
utxo.utxo.height + MIN_TRANSPARENT_COINBASE_MATURITY.try_into().unwrap();
|
||||||
let min_spend_height =
|
let min_spend_height =
|
||||||
min_spend_height.expect("valid UTXOs have coinbase heights far below Height::MAX");
|
min_spend_height.expect("valid UTXOs have coinbase heights far below Height::MAX");
|
||||||
if spend_height >= min_spend_height {
|
if spend_height >= min_spend_height {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::TryFrom,
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
@ -17,7 +16,10 @@ use tokio::{sync::oneshot, task::JoinHandle};
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{Service, ServiceExt};
|
||||||
use tracing_futures::Instrument;
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
use zebra_chain::{block, chain_tip::ChainTip};
|
use zebra_chain::{
|
||||||
|
block::{self, HeightDiff},
|
||||||
|
chain_tip::ChainTip,
|
||||||
|
};
|
||||||
use zebra_network as zn;
|
use zebra_network as zn;
|
||||||
use zebra_state as zs;
|
use zebra_state as zs;
|
||||||
|
|
||||||
|
|
@ -281,7 +283,8 @@ where
|
||||||
let tip_height = latest_chain_tip.best_tip_height();
|
let tip_height = latest_chain_tip.best_tip_height();
|
||||||
|
|
||||||
let max_lookahead_height = if let Some(tip_height) = tip_height {
|
let max_lookahead_height = if let Some(tip_height) = tip_height {
|
||||||
let lookahead = i32::try_from(full_verify_concurrency_limit).expect("fits in i32");
|
let lookahead = HeightDiff::try_from(full_verify_concurrency_limit)
|
||||||
|
.expect("fits in HeightDiff");
|
||||||
(tip_height + lookahead).expect("tip is much lower than Height::MAX")
|
(tip_height + lookahead).expect("tip is much lower than Height::MAX")
|
||||||
} else {
|
} else {
|
||||||
let genesis_lookahead =
|
let genesis_lookahead =
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use tower::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Height},
|
block::{self, Height, HeightDiff},
|
||||||
chain_tip::ChainTip,
|
chain_tip::ChainTip,
|
||||||
parameters::genesis_hash,
|
parameters::genesis_hash,
|
||||||
};
|
};
|
||||||
|
|
@ -165,7 +165,7 @@ const FINAL_CHECKPOINT_BLOCK_VERIFY_TIMEOUT: Duration = Duration::from_secs(2 *
|
||||||
/// The number of blocks after the final checkpoint that get the shorter timeout.
|
/// The number of blocks after the final checkpoint that get the shorter timeout.
|
||||||
///
|
///
|
||||||
/// We've only seen this error on the first few blocks after the final checkpoint.
|
/// We've only seen this error on the first few blocks after the final checkpoint.
|
||||||
const FINAL_CHECKPOINT_BLOCK_VERIFY_TIMEOUT_LIMIT: i32 = 100;
|
const FINAL_CHECKPOINT_BLOCK_VERIFY_TIMEOUT_LIMIT: HeightDiff = 100;
|
||||||
|
|
||||||
/// Controls how long we wait to restart syncing after finishing a sync run.
|
/// Controls how long we wait to restart syncing after finishing a sync run.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use tower::{hedge, Service, ServiceExt};
|
||||||
use tracing_futures::Instrument;
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Height},
|
block::{self, Height, HeightDiff},
|
||||||
chain_tip::ChainTip,
|
chain_tip::ChainTip,
|
||||||
};
|
};
|
||||||
use zebra_network as zn;
|
use zebra_network as zn;
|
||||||
|
|
@ -56,7 +56,7 @@ pub const VERIFICATION_PIPELINE_SCALING_MULTIPLIER: usize = 2;
|
||||||
|
|
||||||
/// The maximum height difference between Zebra's state tip and a downloaded block.
|
/// The maximum height difference between Zebra's state tip and a downloaded block.
|
||||||
/// Blocks higher than this will get dropped and return an error.
|
/// Blocks higher than this will get dropped and return an error.
|
||||||
pub const VERIFICATION_PIPELINE_DROP_LIMIT: i32 = 50_000;
|
pub const VERIFICATION_PIPELINE_DROP_LIMIT: HeightDiff = 50_000;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub(super) struct AlwaysHedge;
|
pub(super) struct AlwaysHedge;
|
||||||
|
|
@ -388,10 +388,10 @@ where
|
||||||
let (lookahead_drop_height, lookahead_pause_height, lookahead_reset_height) = if let Some(tip_height) = tip_height {
|
let (lookahead_drop_height, lookahead_pause_height, lookahead_reset_height) = if let Some(tip_height) = tip_height {
|
||||||
// Scale the height limit with the lookahead limit,
|
// Scale the height limit with the lookahead limit,
|
||||||
// so users with low capacity or under DoS can reduce them both.
|
// so users with low capacity or under DoS can reduce them both.
|
||||||
let lookahead_pause = i32::try_from(
|
let lookahead_pause = HeightDiff::try_from(
|
||||||
lookahead_limit + lookahead_limit * VERIFICATION_PIPELINE_SCALING_MULTIPLIER,
|
lookahead_limit + lookahead_limit * VERIFICATION_PIPELINE_SCALING_MULTIPLIER,
|
||||||
)
|
)
|
||||||
.expect("fits in i32");
|
.expect("fits in HeightDiff");
|
||||||
|
|
||||||
|
|
||||||
((tip_height + VERIFICATION_PIPELINE_DROP_LIMIT).expect("tip is much lower than Height::MAX"),
|
((tip_height + VERIFICATION_PIPELINE_DROP_LIMIT).expect("tip is much lower than Height::MAX"),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use chrono::Utc;
|
||||||
use num_integer::div_ceil;
|
use num_integer::div_ceil;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::Height,
|
block::{Height, HeightDiff},
|
||||||
chain_sync_status::ChainSyncStatus,
|
chain_sync_status::ChainSyncStatus,
|
||||||
chain_tip::ChainTip,
|
chain_tip::ChainTip,
|
||||||
fmt::humantime_seconds,
|
fmt::humantime_seconds,
|
||||||
|
|
@ -23,14 +23,14 @@ const LOG_INTERVAL: Duration = Duration::from_secs(60);
|
||||||
/// The number of blocks we consider to be close to the tip.
|
/// The number of blocks we consider to be close to the tip.
|
||||||
///
|
///
|
||||||
/// Most chain forks are 1-7 blocks long.
|
/// Most chain forks are 1-7 blocks long.
|
||||||
const MAX_CLOSE_TO_TIP_BLOCKS: i32 = 1;
|
const MAX_CLOSE_TO_TIP_BLOCKS: HeightDiff = 1;
|
||||||
|
|
||||||
/// Skip slow sync warnings when we are this close to the tip.
|
/// Skip slow sync warnings when we are this close to the tip.
|
||||||
///
|
///
|
||||||
/// In testing, we've seen warnings around 30 blocks.
|
/// In testing, we've seen warnings around 30 blocks.
|
||||||
///
|
///
|
||||||
/// TODO: replace with `MAX_CLOSE_TO_TIP_BLOCKS` after fixing slow syncing near tip (#3375)
|
/// TODO: replace with `MAX_CLOSE_TO_TIP_BLOCKS` after fixing slow syncing near tip (#3375)
|
||||||
const MIN_SYNC_WARNING_BLOCKS: i32 = 60;
|
const MIN_SYNC_WARNING_BLOCKS: HeightDiff = 60;
|
||||||
|
|
||||||
/// The number of fractional digits in sync percentages.
|
/// The number of fractional digits in sync percentages.
|
||||||
const SYNC_PERCENT_FRAC_DIGITS: usize = 3;
|
const SYNC_PERCENT_FRAC_DIGITS: usize = 3;
|
||||||
|
|
@ -49,6 +49,8 @@ const SYNC_PERCENT_FRAC_DIGITS: usize = 3;
|
||||||
///
|
///
|
||||||
/// We might add tests that sync from a cached tip state,
|
/// We might add tests that sync from a cached tip state,
|
||||||
/// so we only allow a few extra blocks here.
|
/// so we only allow a few extra blocks here.
|
||||||
|
//
|
||||||
|
// TODO: change to HeightDiff?
|
||||||
const MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE: u32 = 10;
|
const MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE: u32 = 10;
|
||||||
|
|
||||||
/// Logs Zebra's estimated progress towards the chain tip every minute or so.
|
/// Logs Zebra's estimated progress towards the chain tip every minute or so.
|
||||||
|
|
@ -67,9 +69,7 @@ pub async fn show_block_chain_progress(
|
||||||
// and the automated tests for that update.
|
// and the automated tests for that update.
|
||||||
let min_after_checkpoint_blocks =
|
let min_after_checkpoint_blocks =
|
||||||
MAX_BLOCK_REORG_HEIGHT + MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE;
|
MAX_BLOCK_REORG_HEIGHT + MIN_BLOCKS_MINED_AFTER_CHECKPOINT_UPDATE;
|
||||||
let min_after_checkpoint_blocks: i32 = min_after_checkpoint_blocks
|
let min_after_checkpoint_blocks: HeightDiff = min_after_checkpoint_blocks.into();
|
||||||
.try_into()
|
|
||||||
.expect("constant fits in i32");
|
|
||||||
|
|
||||||
// The minimum height of the valid best chain, based on:
|
// The minimum height of the valid best chain, based on:
|
||||||
// - the hard-coded checkpoint height,
|
// - the hard-coded checkpoint height,
|
||||||
|
|
@ -128,7 +128,10 @@ pub async fn show_block_chain_progress(
|
||||||
frac = SYNC_PERCENT_FRAC_DIGITS,
|
frac = SYNC_PERCENT_FRAC_DIGITS,
|
||||||
);
|
);
|
||||||
|
|
||||||
let remaining_sync_blocks = estimated_height - current_height;
|
let mut remaining_sync_blocks = estimated_height - current_height;
|
||||||
|
if remaining_sync_blocks < 0 {
|
||||||
|
remaining_sync_blocks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Work out how long it has been since the state height has increased.
|
// Work out how long it has been since the state height has increased.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue