Skip to content

Commit

Permalink
Test network chain tip height estimation
Browse files Browse the repository at this point in the history
Generate pairs of block heights and check that it's possible to estimate
the larger height from the smaller height and a displaced time
difference.
  • Loading branch information
jvff committed Feb 8, 2022
1 parent 08b3c4b commit f604225
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 0 deletions.
2 changes: 2 additions & 0 deletions zebra-chain/src/chain_tip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::{block, parameters::Network, transaction};
#[cfg(any(test, feature = "proptest-impl"))]
pub mod mock;
mod network_chain_tip_height_estimator;
#[cfg(test)]
mod tests;

/// An interface for querying the chain tip.
///
Expand Down
1 change: 1 addition & 0 deletions zebra-chain/src/chain_tip/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod prop;
100 changes: 100 additions & 0 deletions zebra-chain/src/chain_tip/tests/prop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use chrono::Duration;
use proptest::prelude::*;

use crate::{
block,
chain_tip::{mock::MockChainTip, ChainTip},
parameters::{Network, NetworkUpgrade},
serialization::arbitrary::datetime_u32,
};

const NU_BEFORE_BLOSSOM: NetworkUpgrade = NetworkUpgrade::Sapling;

proptest! {
/// Test network chain tip height estimation.
///
/// Given a pair of block heights, estimate the time difference and use it with the lowest
/// height to check if the estimation of the height is correct.
#[test]
fn network_chain_tip_height_estimation_is_correct(
network in any::<Network>(),
mut block_heights in any::<[block::Height; 2]>(),
current_block_time in datetime_u32(),
time_displacement_factor in 0.0..1.0_f64,
) {
let (chain_tip, mock_chain_tip_sender) = MockChainTip::new();
let blossom_activation_height = NetworkUpgrade::Blossom
.activation_height(network)
.expect("Blossom activation height is missing");

block_heights.sort();
let current_height = block_heights[0];
let network_height = block_heights[1];

mock_chain_tip_sender.send_best_tip_height(current_height);
mock_chain_tip_sender.send_best_tip_block_time(current_block_time);

let estimated_time_difference =
// Estimate time difference for heights before Blossom activation.
estimate_time_difference(
current_height.min(blossom_activation_height),
network_height.min(blossom_activation_height),
NU_BEFORE_BLOSSOM,
)
// Estimate time difference for heights after Blossom activation.
+ estimate_time_difference(
current_height.max(blossom_activation_height),
network_height.max(blossom_activation_height),
NetworkUpgrade::Blossom,
);

let time_displacement = calculate_time_displacement(
time_displacement_factor,
NetworkUpgrade::current(network, network_height),
);

let mock_local_time = current_block_time + estimated_time_difference + time_displacement;

assert_eq!(
chain_tip.estimate_network_chain_tip_height(network, mock_local_time),
Some(network_height)
);
}
}

/// Estimate the time necessary for the chain to progress from `start_height` to `end_height`,
/// assuming each block is produced at exactly the number of seconds of the target spacing for the
/// `active_network_upgrade`.
fn estimate_time_difference(
start_height: block::Height,
end_height: block::Height,
active_network_upgrade: NetworkUpgrade,
) -> Duration {
let spacing_seconds: i64 = active_network_upgrade
.target_spacing()
.num_seconds()
.expect("All target spacings fit in an i32");

let height_difference = i64::from(end_height - start_height);

Duration::seconds(height_difference * spacing_seconds)
}

/// Use `displacement` to get a displacement duration between zero and the target spacing of the
/// specified `network_upgrade`.
///
/// This is used to "displace" the time used in the test so that the test inputs aren't exact
/// multiples of the target spacing.
fn calculate_time_displacement(displacement: f64, network_upgrade: NetworkUpgrade) -> Duration {
let target_spacing = network_upgrade.target_spacing();

let nanoseconds = target_spacing
.num_nanoseconds()
.expect("Target spacing nanoseconds fit in a i64");

let displaced_nanoseconds = (displacement * nanoseconds as f64)
.round()
.clamp(i64::MIN as f64, i64::MAX as f64) as i64;

Duration::nanoseconds(displaced_nanoseconds)
}

0 comments on commit f604225

Please sign in to comment.