2022-10-05 16:29:22 -07:00
|
|
|
//! Utility functions and types for Epoch Accounts Hash
|
|
|
|
|
|
|
|
use {
|
|
|
|
crate::bank::Bank,
|
2022-11-15 10:26:19 -08:00
|
|
|
solana_sdk::{
|
|
|
|
clock::{Epoch, Slot},
|
|
|
|
vote::state::MAX_LOCKOUT_HISTORY,
|
|
|
|
},
|
2022-10-05 16:29:22 -07:00
|
|
|
};
|
|
|
|
|
2022-11-15 10:26:19 -08:00
|
|
|
/// Is the EAH enabled this Epoch?
|
|
|
|
#[must_use]
|
|
|
|
pub fn is_enabled_this_epoch(bank: &Bank) -> bool {
|
|
|
|
// The EAH calculation "start" is based on when a bank is *rooted*, and "stop" is based on when a
|
|
|
|
// bank is *frozen*. Banks are rooted after exceeding the maximum lockout, so there is a delay
|
|
|
|
// of at least `maximum lockout` number of slots the EAH calculation must take into
|
|
|
|
// consideration. To ensure an EAH calculation has started by the time that calculation is
|
|
|
|
// needed, the calculation interval must be at least `maximum lockout` plus some buffer to
|
|
|
|
// handle when banks are not rooted every single slot.
|
|
|
|
const MINIMUM_CALCULATION_INTERVAL: u64 =
|
|
|
|
(MAX_LOCKOUT_HISTORY as u64).saturating_add(CALCULATION_INTERVAL_BUFFER);
|
|
|
|
// The calculation buffer is a best-attempt at median worst-case for how many bank ancestors can
|
|
|
|
// accumulate before the bank is rooted.
|
|
|
|
// [brooks] On Wed Oct 26 12:15:21 2022, over the previous 6 hour period against mainnet-beta,
|
|
|
|
// I saw multiple validators reporting metrics in the 120s for `total_parent_banks`. The mean
|
|
|
|
// is 2 to 3, but a number of nodes also reported values in the low 20s. A value of 150 should
|
|
|
|
// capture the majority of validators, and will not be an issue for clusters running with
|
|
|
|
// normal slots-per-epoch; this really will only affect tests and epoch schedule warmup.
|
|
|
|
const CALCULATION_INTERVAL_BUFFER: u64 = 150;
|
|
|
|
|
|
|
|
let calculation_interval = calculation_interval(bank);
|
|
|
|
calculation_interval >= MINIMUM_CALCULATION_INTERVAL
|
|
|
|
}
|
|
|
|
|
2022-10-05 16:29:22 -07:00
|
|
|
/// Calculation of the EAH occurs once per epoch. All nodes in the cluster must agree on which
|
|
|
|
/// slot the EAH is based on. This slot will be at an offset into the epoch, and referred to as
|
|
|
|
/// the "start" slot for the EAH calculation.
|
|
|
|
#[must_use]
|
|
|
|
#[inline]
|
|
|
|
pub fn calculation_offset_start(bank: &Bank) -> Slot {
|
|
|
|
calculation_info(bank).calculation_offset_start
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Calculation of the EAH occurs once per epoch. All nodes in the cluster must agree on which
|
|
|
|
/// bank will hash the EAH into its `Bank::hash`. This slot will be at an offset into the epoch,
|
|
|
|
/// and referred to as the "stop" slot for the EAH calculation. All nodes must complete the EAH
|
|
|
|
/// calculation before this slot!
|
|
|
|
#[must_use]
|
|
|
|
#[inline]
|
|
|
|
pub fn calculation_offset_stop(bank: &Bank) -> Slot {
|
|
|
|
calculation_info(bank).calculation_offset_stop
|
|
|
|
}
|
|
|
|
|
|
|
|
/// For the epoch that `bank` is in, get the slot that the EAH calculation starts
|
|
|
|
#[must_use]
|
|
|
|
#[inline]
|
|
|
|
pub fn calculation_start(bank: &Bank) -> Slot {
|
|
|
|
calculation_info(bank).calculation_start
|
|
|
|
}
|
|
|
|
|
|
|
|
/// For the epoch that `bank` is in, get the slot that the EAH calculation stops
|
|
|
|
#[must_use]
|
|
|
|
#[inline]
|
|
|
|
pub fn calculation_stop(bank: &Bank) -> Slot {
|
|
|
|
calculation_info(bank).calculation_stop
|
|
|
|
}
|
|
|
|
|
2022-11-15 10:26:19 -08:00
|
|
|
/// Get the number of slots from EAH calculation start to stop; known as the calculation interval
|
2022-11-14 09:34:44 -08:00
|
|
|
#[must_use]
|
|
|
|
#[inline]
|
2022-11-15 10:26:19 -08:00
|
|
|
pub fn calculation_interval(bank: &Bank) -> u64 {
|
|
|
|
calculation_info(bank).calculation_interval
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Is this bank in the calculation window?
|
|
|
|
#[must_use]
|
2022-11-14 09:34:44 -08:00
|
|
|
pub fn is_in_calculation_window(bank: &Bank) -> bool {
|
|
|
|
let info = calculation_info(bank);
|
2022-11-15 10:26:19 -08:00
|
|
|
let range = info.calculation_start..info.calculation_stop;
|
|
|
|
range.contains(&bank.slot())
|
2022-11-14 09:34:44 -08:00
|
|
|
}
|
|
|
|
|
2022-10-05 16:29:22 -07:00
|
|
|
/// For the epoch that `bank` is in, get all the EAH calculation information
|
|
|
|
pub fn calculation_info(bank: &Bank) -> CalculationInfo {
|
|
|
|
let epoch = bank.epoch();
|
|
|
|
let epoch_schedule = bank.epoch_schedule();
|
|
|
|
|
|
|
|
let slots_per_epoch = epoch_schedule.get_slots_in_epoch(epoch);
|
|
|
|
let calculation_offset_start = slots_per_epoch / 4;
|
|
|
|
let calculation_offset_stop = slots_per_epoch / 4 * 3;
|
|
|
|
|
|
|
|
let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch);
|
|
|
|
let last_slot_in_epoch = epoch_schedule.get_last_slot_in_epoch(epoch);
|
|
|
|
let calculation_start = first_slot_in_epoch.saturating_add(calculation_offset_start);
|
|
|
|
let calculation_stop = first_slot_in_epoch.saturating_add(calculation_offset_stop);
|
2022-11-15 10:26:19 -08:00
|
|
|
let calculation_interval = calculation_offset_stop.saturating_sub(calculation_offset_start);
|
2022-10-05 16:29:22 -07:00
|
|
|
|
|
|
|
CalculationInfo {
|
|
|
|
epoch,
|
|
|
|
slots_per_epoch,
|
|
|
|
first_slot_in_epoch,
|
|
|
|
last_slot_in_epoch,
|
|
|
|
calculation_offset_start,
|
|
|
|
calculation_offset_stop,
|
|
|
|
calculation_start,
|
|
|
|
calculation_stop,
|
2022-11-15 10:26:19 -08:00
|
|
|
calculation_interval,
|
2022-10-05 16:29:22 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// All the EAH calculation information for a specific epoch
|
|
|
|
///
|
|
|
|
/// Computing the EAH calculation information looks up a bunch of values. Instead of throwing
|
|
|
|
/// those values away, they are kept in here as well. This may aid in future debugging, and the
|
|
|
|
/// additional fields are trivial in size.
|
|
|
|
#[derive(Debug, Default, Copy, Clone)]
|
|
|
|
pub struct CalculationInfo {
|
|
|
|
/*
|
|
|
|
* The values that were looked up, which were needed to get the calculation info
|
|
|
|
*/
|
|
|
|
/// The epoch this information applies to
|
|
|
|
pub epoch: Epoch,
|
|
|
|
/// Number of slots in this epoch
|
|
|
|
pub slots_per_epoch: u64,
|
|
|
|
/// First slot in this epoch
|
|
|
|
pub first_slot_in_epoch: Slot,
|
|
|
|
/// Last slot in this epoch
|
|
|
|
pub last_slot_in_epoch: Slot,
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The computed values for the calculation info
|
|
|
|
*/
|
|
|
|
/// Offset into the epoch when the EAH calculation starts
|
|
|
|
pub calculation_offset_start: Slot,
|
|
|
|
/// Offset into the epoch when the EAH calculation stops
|
|
|
|
pub calculation_offset_stop: Slot,
|
|
|
|
/// Absolute slot where the EAH calculation starts
|
|
|
|
pub calculation_start: Slot,
|
|
|
|
/// Absolute slot where the EAH calculation stops
|
|
|
|
pub calculation_stop: Slot,
|
2022-11-15 10:26:19 -08:00
|
|
|
/// Number of slots from EAH calculation start to stop
|
|
|
|
pub calculation_interval: u64,
|
2022-10-05 16:29:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use {
|
|
|
|
super::*,
|
|
|
|
solana_sdk::{epoch_schedule::EpochSchedule, genesis_config::GenesisConfig},
|
2022-11-15 10:26:19 -08:00
|
|
|
test_case::test_case,
|
2022-10-05 16:29:22 -07:00
|
|
|
};
|
|
|
|
|
2022-11-15 10:26:19 -08:00
|
|
|
#[test_case( 32 => false)] // minimum slots per epoch
|
|
|
|
#[test_case( 361 => false)] // below minimum slots per epoch *for EAH*
|
|
|
|
#[test_case( 362 => false)] // minimum slots per epoch *for EAH*
|
|
|
|
#[test_case( 8_192 => true)] // default dev slots per epoch
|
|
|
|
#[test_case(432_000 => true)] // default slots per epoch
|
|
|
|
fn test_is_enabled_this_epoch(slots_per_epoch: u64) -> bool {
|
|
|
|
let genesis_config = GenesisConfig {
|
|
|
|
epoch_schedule: EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false),
|
|
|
|
..GenesisConfig::default()
|
|
|
|
};
|
|
|
|
let bank = Bank::new_for_tests(&genesis_config);
|
|
|
|
is_enabled_this_epoch(&bank)
|
|
|
|
}
|
|
|
|
|
2022-10-05 16:29:22 -07:00
|
|
|
#[test]
|
|
|
|
fn test_calculation_offset_bounds() {
|
|
|
|
let bank = Bank::default_for_tests();
|
|
|
|
let offset_start = calculation_offset_start(&bank);
|
|
|
|
let offset_stop = calculation_offset_stop(&bank);
|
|
|
|
assert!(offset_start < offset_stop);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_calculation_bounds() {
|
|
|
|
let bank = Bank::default_for_tests();
|
|
|
|
let start = calculation_start(&bank);
|
|
|
|
let stop = calculation_stop(&bank);
|
|
|
|
assert!(start < stop);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_calculation_info() {
|
2022-11-15 10:26:19 -08:00
|
|
|
for slots_per_epoch in [32, 361, 362, 8_192, 65_536, 432_000, 123_456_789] {
|
2022-10-05 16:29:22 -07:00
|
|
|
for warmup in [false, true] {
|
|
|
|
let genesis_config = GenesisConfig {
|
|
|
|
epoch_schedule: EpochSchedule::custom(slots_per_epoch, slots_per_epoch, warmup),
|
|
|
|
..GenesisConfig::default()
|
|
|
|
};
|
|
|
|
let info = calculation_info(&Bank::new_for_tests(&genesis_config));
|
|
|
|
assert!(info.calculation_offset_start < info.calculation_offset_stop);
|
|
|
|
assert!(info.calculation_offset_start < info.slots_per_epoch);
|
|
|
|
assert!(info.calculation_offset_stop < info.slots_per_epoch);
|
2022-11-15 10:26:19 -08:00
|
|
|
assert!(info.calculation_start < info.calculation_stop);
|
|
|
|
assert!(info.calculation_start > info.first_slot_in_epoch);
|
|
|
|
assert!(info.calculation_stop < info.last_slot_in_epoch);
|
|
|
|
assert!(info.calculation_interval > 0);
|
2022-10-05 16:29:22 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|