zebra/zebra-chain/src/chain_tip/tests/prop.rs

101 lines
3.7 KiB
Rust
Raw Normal View History

Estimate network chain tip height based on local node time and current best tip (#3492) * Remove redundant documentation The documentation was exactly the same as the documentation from the trait. * Calculate a mock time block delta for tests Simulate a block being added to the chain with a random block time based on the previous block time and the target spacing time. * Add a `time` field to `ChainTipBlock` Store the block time so that it's ready for a future chain that allows obtaining the chain tip's block time. * Add `ChainTip::best_tip_block_time` method Allow obtaining the bes chain tip's block time. * Add method to obtain both height and block time Prevent any data races by returning both values so that they refer to the same chain tip. * Add `NetworkUpgrade::all_target_spacings` method Returns all the target spacings defined for a network. * Create a `NetworkChainTipEstimator` helper type Isolate the code to calculate the height estimation in a new type, so that it's easier to understand and doesn't decrease the readability of the `chain_tip.rs` file. * Add `ChainTip::estimate_network_chain_tip_height` This is more of an extension method than a trait method. It uses the `NetworkChainTipHeightEstimator` to actually perform the estimation, but obtains the initial information from the current best chain tip. * Fix typo in documentation There was an extra closing bracket in the summary line. * Refactor `MockChainTipSender` into a separate type Prepare to allow mocking the block time of the best tip as well as the block height. * Allow sending mock best tip block times Add a separate `watch` channel to send the best tip block times from a `MockChainTipSender` to a `MockChainTip`. The `best_tip_height_and_block_time` implementation will only return a value if there's a height and a block time value for the best tip. * Fix off-by-one height estimation error Use Euclidean division to force the division result to round down instead of rounding towards zero. This fixes an off-by-one error when estimating a height that is lower than the current height, because the fractionary result was being discarded, and it should have forced the height to go one block back. * Fix panics on local times very far in the past Detect situations that might cause the block height estimate to underflow, and return the genesis height instead. * Fix another off-by-one height estimation error The implementation of `chrono::Duration::num_seconds` adds one to the number of seconds if it's negative. This breaks the division calculation, so it has to be compensated for. * Test network chain tip height estimation 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.
2022-02-10 17:27:02 -08:00
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
change(chain): Remove `Copy` trait impl from `Network` (#8354) * removed derive copy from network, address and ledgerstate * changed is_max_block_time_enforced to accept ref * changed NetworkUpgrade::Current to accept ref * changed NetworkUpgrade::Next to accept ref * changed NetworkUpgrade::IsActivationHeight to accept ref * changed NetworkUpgrade::TargetSpacingForHeight to accept ref * changed NetworkUpgrade::TargetSpacings to accept ref * changed NetworkUpgrade::MinimumDifficultySpacing_forHeight to accept ref * changed NetworkUpgrade::IsTestnetMinDifficultyBlock to accept ref * changed NetworkUpgrade::AveragingWindowTimespanForHeight to accept ref * changed NetworkUpgrade::ActivationHeight to accept ref * changed sapling_activation_height to accept ref * fixed lifetime for target_spacings * fixed sapling_activation_height * changed transaction_to_fake_v5 and fake_v5_transactions_for_network to accept ref to network * changed Input::vec_strategy to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_history.rs to accept ref to network * changed functions in zebra-chain/src/history_tree.rs to accept ref to network * changed functions in zebra-chain/src/history_tree.rs to accept ref to network * changed functions in zebra-chain/src/primitives/address.rs to accept ref to network * changed functions in zebra-chain/src/primitives/viewing_key* to accept ref to network * changed functions in zebra-chain/src/transparent/address.rs to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_primitives.rs to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_note_encryption.rs to accept ref to network * changed functions in zebra-chain/src/primitives/history_tree* to accept ref to network * changed functions in zebra-chain/src/block* to accept ref to network * fixed errors in zebra-chain::parameters::network * fixed errors in zebra-chain::parameters::network * fixed errors in zebra-chain * changed NonEmptyHistoryTree and InnerHistoryTree to hold value instead of ref * changed NonEmptyHistoryTree and InnerHistoryTree to hold value instead of ref * fixed errors in zebra-chain/src/block/arbitrary.rs * finished fixing errors in zebra-chain - all crate tests pass * changed functions in zebra-state::service::finalized_state to accept &Network * changed functions in zebra-state::service::non_finalized_state to accept &Network * zebra-state tests run but fail with overflow error * zebra-state tests all pass * converted zebra-network -- all crate tests pass * applied all requested changes from review * converted zebra-consensus -- all crate tests pass * converted zebra-scan -- all crate tests pass * converted zebra-rpc -- all crate tests pass * converted zebra-grpc -- all crate tests pass * converted zebrad -- all crate tests pass * applied all requested changes from review * fixed all clippy errors * fixed build error in zebrad/src/components/mempool/crawler.rs
2024-03-19 13:45:27 -07:00
.activation_height(&network)
Estimate network chain tip height based on local node time and current best tip (#3492) * Remove redundant documentation The documentation was exactly the same as the documentation from the trait. * Calculate a mock time block delta for tests Simulate a block being added to the chain with a random block time based on the previous block time and the target spacing time. * Add a `time` field to `ChainTipBlock` Store the block time so that it's ready for a future chain that allows obtaining the chain tip's block time. * Add `ChainTip::best_tip_block_time` method Allow obtaining the bes chain tip's block time. * Add method to obtain both height and block time Prevent any data races by returning both values so that they refer to the same chain tip. * Add `NetworkUpgrade::all_target_spacings` method Returns all the target spacings defined for a network. * Create a `NetworkChainTipEstimator` helper type Isolate the code to calculate the height estimation in a new type, so that it's easier to understand and doesn't decrease the readability of the `chain_tip.rs` file. * Add `ChainTip::estimate_network_chain_tip_height` This is more of an extension method than a trait method. It uses the `NetworkChainTipHeightEstimator` to actually perform the estimation, but obtains the initial information from the current best chain tip. * Fix typo in documentation There was an extra closing bracket in the summary line. * Refactor `MockChainTipSender` into a separate type Prepare to allow mocking the block time of the best tip as well as the block height. * Allow sending mock best tip block times Add a separate `watch` channel to send the best tip block times from a `MockChainTipSender` to a `MockChainTip`. The `best_tip_height_and_block_time` implementation will only return a value if there's a height and a block time value for the best tip. * Fix off-by-one height estimation error Use Euclidean division to force the division result to round down instead of rounding towards zero. This fixes an off-by-one error when estimating a height that is lower than the current height, because the fractionary result was being discarded, and it should have forced the height to go one block back. * Fix panics on local times very far in the past Detect situations that might cause the block height estimate to underflow, and return the genesis height instead. * Fix another off-by-one height estimation error The implementation of `chrono::Duration::num_seconds` adds one to the number of seconds if it's negative. This breaks the division calculation, so it has to be compensated for. * Test network chain tip height estimation 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.
2022-02-10 17:27:02 -08:00
.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,
change(chain): Remove `Copy` trait impl from `Network` (#8354) * removed derive copy from network, address and ledgerstate * changed is_max_block_time_enforced to accept ref * changed NetworkUpgrade::Current to accept ref * changed NetworkUpgrade::Next to accept ref * changed NetworkUpgrade::IsActivationHeight to accept ref * changed NetworkUpgrade::TargetSpacingForHeight to accept ref * changed NetworkUpgrade::TargetSpacings to accept ref * changed NetworkUpgrade::MinimumDifficultySpacing_forHeight to accept ref * changed NetworkUpgrade::IsTestnetMinDifficultyBlock to accept ref * changed NetworkUpgrade::AveragingWindowTimespanForHeight to accept ref * changed NetworkUpgrade::ActivationHeight to accept ref * changed sapling_activation_height to accept ref * fixed lifetime for target_spacings * fixed sapling_activation_height * changed transaction_to_fake_v5 and fake_v5_transactions_for_network to accept ref to network * changed Input::vec_strategy to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_history.rs to accept ref to network * changed functions in zebra-chain/src/history_tree.rs to accept ref to network * changed functions in zebra-chain/src/history_tree.rs to accept ref to network * changed functions in zebra-chain/src/primitives/address.rs to accept ref to network * changed functions in zebra-chain/src/primitives/viewing_key* to accept ref to network * changed functions in zebra-chain/src/transparent/address.rs to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_primitives.rs to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_note_encryption.rs to accept ref to network * changed functions in zebra-chain/src/primitives/history_tree* to accept ref to network * changed functions in zebra-chain/src/block* to accept ref to network * fixed errors in zebra-chain::parameters::network * fixed errors in zebra-chain::parameters::network * fixed errors in zebra-chain * changed NonEmptyHistoryTree and InnerHistoryTree to hold value instead of ref * changed NonEmptyHistoryTree and InnerHistoryTree to hold value instead of ref * fixed errors in zebra-chain/src/block/arbitrary.rs * finished fixing errors in zebra-chain - all crate tests pass * changed functions in zebra-state::service::finalized_state to accept &Network * changed functions in zebra-state::service::non_finalized_state to accept &Network * zebra-state tests run but fail with overflow error * zebra-state tests all pass * converted zebra-network -- all crate tests pass * applied all requested changes from review * converted zebra-consensus -- all crate tests pass * converted zebra-scan -- all crate tests pass * converted zebra-rpc -- all crate tests pass * converted zebra-grpc -- all crate tests pass * converted zebrad -- all crate tests pass * applied all requested changes from review * fixed all clippy errors * fixed build error in zebrad/src/components/mempool/crawler.rs
2024-03-19 13:45:27 -07:00
NetworkUpgrade::current(&network, network_height),
Estimate network chain tip height based on local node time and current best tip (#3492) * Remove redundant documentation The documentation was exactly the same as the documentation from the trait. * Calculate a mock time block delta for tests Simulate a block being added to the chain with a random block time based on the previous block time and the target spacing time. * Add a `time` field to `ChainTipBlock` Store the block time so that it's ready for a future chain that allows obtaining the chain tip's block time. * Add `ChainTip::best_tip_block_time` method Allow obtaining the bes chain tip's block time. * Add method to obtain both height and block time Prevent any data races by returning both values so that they refer to the same chain tip. * Add `NetworkUpgrade::all_target_spacings` method Returns all the target spacings defined for a network. * Create a `NetworkChainTipEstimator` helper type Isolate the code to calculate the height estimation in a new type, so that it's easier to understand and doesn't decrease the readability of the `chain_tip.rs` file. * Add `ChainTip::estimate_network_chain_tip_height` This is more of an extension method than a trait method. It uses the `NetworkChainTipHeightEstimator` to actually perform the estimation, but obtains the initial information from the current best chain tip. * Fix typo in documentation There was an extra closing bracket in the summary line. * Refactor `MockChainTipSender` into a separate type Prepare to allow mocking the block time of the best tip as well as the block height. * Allow sending mock best tip block times Add a separate `watch` channel to send the best tip block times from a `MockChainTipSender` to a `MockChainTip`. The `best_tip_height_and_block_time` implementation will only return a value if there's a height and a block time value for the best tip. * Fix off-by-one height estimation error Use Euclidean division to force the division result to round down instead of rounding towards zero. This fixes an off-by-one error when estimating a height that is lower than the current height, because the fractionary result was being discarded, and it should have forced the height to go one block back. * Fix panics on local times very far in the past Detect situations that might cause the block height estimate to underflow, and return the genesis height instead. * Fix another off-by-one height estimation error The implementation of `chrono::Duration::num_seconds` adds one to the number of seconds if it's negative. This breaks the division calculation, so it has to be compensated for. * Test network chain tip height estimation 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.
2022-02-10 17:27:02 -08:00
);
let mock_local_time = current_block_time + estimated_time_difference + time_displacement;
assert_eq!(
change(chain): Remove `Copy` trait impl from `Network` (#8354) * removed derive copy from network, address and ledgerstate * changed is_max_block_time_enforced to accept ref * changed NetworkUpgrade::Current to accept ref * changed NetworkUpgrade::Next to accept ref * changed NetworkUpgrade::IsActivationHeight to accept ref * changed NetworkUpgrade::TargetSpacingForHeight to accept ref * changed NetworkUpgrade::TargetSpacings to accept ref * changed NetworkUpgrade::MinimumDifficultySpacing_forHeight to accept ref * changed NetworkUpgrade::IsTestnetMinDifficultyBlock to accept ref * changed NetworkUpgrade::AveragingWindowTimespanForHeight to accept ref * changed NetworkUpgrade::ActivationHeight to accept ref * changed sapling_activation_height to accept ref * fixed lifetime for target_spacings * fixed sapling_activation_height * changed transaction_to_fake_v5 and fake_v5_transactions_for_network to accept ref to network * changed Input::vec_strategy to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_history.rs to accept ref to network * changed functions in zebra-chain/src/history_tree.rs to accept ref to network * changed functions in zebra-chain/src/history_tree.rs to accept ref to network * changed functions in zebra-chain/src/primitives/address.rs to accept ref to network * changed functions in zebra-chain/src/primitives/viewing_key* to accept ref to network * changed functions in zebra-chain/src/transparent/address.rs to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_primitives.rs to accept ref to network * changed functions in zebra-chain/src/primitives/zcash_note_encryption.rs to accept ref to network * changed functions in zebra-chain/src/primitives/history_tree* to accept ref to network * changed functions in zebra-chain/src/block* to accept ref to network * fixed errors in zebra-chain::parameters::network * fixed errors in zebra-chain::parameters::network * fixed errors in zebra-chain * changed NonEmptyHistoryTree and InnerHistoryTree to hold value instead of ref * changed NonEmptyHistoryTree and InnerHistoryTree to hold value instead of ref * fixed errors in zebra-chain/src/block/arbitrary.rs * finished fixing errors in zebra-chain - all crate tests pass * changed functions in zebra-state::service::finalized_state to accept &Network * changed functions in zebra-state::service::non_finalized_state to accept &Network * zebra-state tests run but fail with overflow error * zebra-state tests all pass * converted zebra-network -- all crate tests pass * applied all requested changes from review * converted zebra-consensus -- all crate tests pass * converted zebra-scan -- all crate tests pass * converted zebra-rpc -- all crate tests pass * converted zebra-grpc -- all crate tests pass * converted zebrad -- all crate tests pass * applied all requested changes from review * fixed all clippy errors * fixed build error in zebrad/src/components/mempool/crawler.rs
2024-03-19 13:45:27 -07:00
chain_tip.estimate_network_chain_tip_height(&network, mock_local_time),
Estimate network chain tip height based on local node time and current best tip (#3492) * Remove redundant documentation The documentation was exactly the same as the documentation from the trait. * Calculate a mock time block delta for tests Simulate a block being added to the chain with a random block time based on the previous block time and the target spacing time. * Add a `time` field to `ChainTipBlock` Store the block time so that it's ready for a future chain that allows obtaining the chain tip's block time. * Add `ChainTip::best_tip_block_time` method Allow obtaining the bes chain tip's block time. * Add method to obtain both height and block time Prevent any data races by returning both values so that they refer to the same chain tip. * Add `NetworkUpgrade::all_target_spacings` method Returns all the target spacings defined for a network. * Create a `NetworkChainTipEstimator` helper type Isolate the code to calculate the height estimation in a new type, so that it's easier to understand and doesn't decrease the readability of the `chain_tip.rs` file. * Add `ChainTip::estimate_network_chain_tip_height` This is more of an extension method than a trait method. It uses the `NetworkChainTipHeightEstimator` to actually perform the estimation, but obtains the initial information from the current best chain tip. * Fix typo in documentation There was an extra closing bracket in the summary line. * Refactor `MockChainTipSender` into a separate type Prepare to allow mocking the block time of the best tip as well as the block height. * Allow sending mock best tip block times Add a separate `watch` channel to send the best tip block times from a `MockChainTipSender` to a `MockChainTip`. The `best_tip_height_and_block_time` implementation will only return a value if there's a height and a block time value for the best tip. * Fix off-by-one height estimation error Use Euclidean division to force the division result to round down instead of rounding towards zero. This fixes an off-by-one error when estimating a height that is lower than the current height, because the fractionary result was being discarded, and it should have forced the height to go one block back. * Fix panics on local times very far in the past Detect situations that might cause the block height estimate to underflow, and return the genesis height instead. * Fix another off-by-one height estimation error The implementation of `chrono::Duration::num_seconds` adds one to the number of seconds if it's negative. This breaks the division calculation, so it has to be compensated for. * Test network chain tip height estimation 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.
2022-02-10 17:27:02 -08:00
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 = active_network_upgrade.target_spacing().num_seconds();
let height_difference = end_height - start_height;
Estimate network chain tip height based on local node time and current best tip (#3492) * Remove redundant documentation The documentation was exactly the same as the documentation from the trait. * Calculate a mock time block delta for tests Simulate a block being added to the chain with a random block time based on the previous block time and the target spacing time. * Add a `time` field to `ChainTipBlock` Store the block time so that it's ready for a future chain that allows obtaining the chain tip's block time. * Add `ChainTip::best_tip_block_time` method Allow obtaining the bes chain tip's block time. * Add method to obtain both height and block time Prevent any data races by returning both values so that they refer to the same chain tip. * Add `NetworkUpgrade::all_target_spacings` method Returns all the target spacings defined for a network. * Create a `NetworkChainTipEstimator` helper type Isolate the code to calculate the height estimation in a new type, so that it's easier to understand and doesn't decrease the readability of the `chain_tip.rs` file. * Add `ChainTip::estimate_network_chain_tip_height` This is more of an extension method than a trait method. It uses the `NetworkChainTipHeightEstimator` to actually perform the estimation, but obtains the initial information from the current best chain tip. * Fix typo in documentation There was an extra closing bracket in the summary line. * Refactor `MockChainTipSender` into a separate type Prepare to allow mocking the block time of the best tip as well as the block height. * Allow sending mock best tip block times Add a separate `watch` channel to send the best tip block times from a `MockChainTipSender` to a `MockChainTip`. The `best_tip_height_and_block_time` implementation will only return a value if there's a height and a block time value for the best tip. * Fix off-by-one height estimation error Use Euclidean division to force the division result to round down instead of rounding towards zero. This fixes an off-by-one error when estimating a height that is lower than the current height, because the fractionary result was being discarded, and it should have forced the height to go one block back. * Fix panics on local times very far in the past Detect situations that might cause the block height estimate to underflow, and return the genesis height instead. * Fix another off-by-one height estimation error The implementation of `chrono::Duration::num_seconds` adds one to the number of seconds if it's negative. This breaks the division calculation, so it has to be compensated for. * Test network chain tip height estimation 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.
2022-02-10 17:27:02 -08:00
if height_difference > 0 {
Duration::seconds(height_difference * spacing_seconds)
} else {
Duration::zero()
}
Estimate network chain tip height based on local node time and current best tip (#3492) * Remove redundant documentation The documentation was exactly the same as the documentation from the trait. * Calculate a mock time block delta for tests Simulate a block being added to the chain with a random block time based on the previous block time and the target spacing time. * Add a `time` field to `ChainTipBlock` Store the block time so that it's ready for a future chain that allows obtaining the chain tip's block time. * Add `ChainTip::best_tip_block_time` method Allow obtaining the bes chain tip's block time. * Add method to obtain both height and block time Prevent any data races by returning both values so that they refer to the same chain tip. * Add `NetworkUpgrade::all_target_spacings` method Returns all the target spacings defined for a network. * Create a `NetworkChainTipEstimator` helper type Isolate the code to calculate the height estimation in a new type, so that it's easier to understand and doesn't decrease the readability of the `chain_tip.rs` file. * Add `ChainTip::estimate_network_chain_tip_height` This is more of an extension method than a trait method. It uses the `NetworkChainTipHeightEstimator` to actually perform the estimation, but obtains the initial information from the current best chain tip. * Fix typo in documentation There was an extra closing bracket in the summary line. * Refactor `MockChainTipSender` into a separate type Prepare to allow mocking the block time of the best tip as well as the block height. * Allow sending mock best tip block times Add a separate `watch` channel to send the best tip block times from a `MockChainTipSender` to a `MockChainTip`. The `best_tip_height_and_block_time` implementation will only return a value if there's a height and a block time value for the best tip. * Fix off-by-one height estimation error Use Euclidean division to force the division result to round down instead of rounding towards zero. This fixes an off-by-one error when estimating a height that is lower than the current height, because the fractionary result was being discarded, and it should have forced the height to go one block back. * Fix panics on local times very far in the past Detect situations that might cause the block height estimate to underflow, and return the genesis height instead. * Fix another off-by-one height estimation error The implementation of `chrono::Duration::num_seconds` adds one to the number of seconds if it's negative. This breaks the division calculation, so it has to be compensated for. * Test network chain tip height estimation 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.
2022-02-10 17:27:02 -08:00
}
/// 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)
}