solana_runtime/bank/epoch_accounts_hash_utils.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
//! Utility functions and types for Epoch Accounts Hash
use {
crate::bank::Bank,
solana_sdk::{
clock::{Epoch, Slot},
vote::state::MAX_LOCKOUT_HISTORY,
},
};
/// 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
}
/// 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
}
/// Get the number of slots from EAH calculation start to stop; known as the calculation interval
#[must_use]
#[inline]
pub fn calculation_interval(bank: &Bank) -> u64 {
calculation_info(bank).calculation_interval
}
/// Is this bank in the calculation window?
#[must_use]
pub fn is_in_calculation_window(bank: &Bank) -> bool {
let info = calculation_info(bank);
let range = info.calculation_start..info.calculation_stop;
range.contains(&bank.slot())
}
/// 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);
let calculation_interval = calculation_offset_stop.saturating_sub(calculation_offset_start);
CalculationInfo {
epoch,
slots_per_epoch,
first_slot_in_epoch,
last_slot_in_epoch,
calculation_offset_start,
calculation_offset_stop,
calculation_start,
calculation_stop,
calculation_interval,
}
}
/// 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,
/// Number of slots from EAH calculation start to stop
pub calculation_interval: u64,
}
#[cfg(test)]
mod tests {
use {
super::*,
solana_sdk::{epoch_schedule::EpochSchedule, genesis_config::GenesisConfig},
test_case::test_case,
};
#[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)
}
#[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() {
for slots_per_epoch in [32, 361, 362, 8_192, 65_536, 432_000, 123_456_789] {
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);
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);
}
}
}
}