solana_runtime/bank/
epoch_accounts_hash_utils.rs

1//! Utility functions and types for Epoch Accounts Hash
2
3use {
4    crate::bank::Bank,
5    solana_sdk::{
6        clock::{Epoch, Slot},
7        vote::state::MAX_LOCKOUT_HISTORY,
8    },
9};
10
11/// Is the EAH enabled this Epoch?
12#[must_use]
13pub fn is_enabled_this_epoch(bank: &Bank) -> bool {
14    // The EAH calculation "start" is based on when a bank is *rooted*, and "stop" is based on when a
15    // bank is *frozen*.  Banks are rooted after exceeding the maximum lockout, so there is a delay
16    // of at least `maximum lockout` number of slots the EAH calculation must take into
17    // consideration.  To ensure an EAH calculation has started by the time that calculation is
18    // needed, the calculation interval must be at least `maximum lockout` plus some buffer to
19    // handle when banks are not rooted every single slot.
20    const MINIMUM_CALCULATION_INTERVAL: u64 =
21        (MAX_LOCKOUT_HISTORY as u64).saturating_add(CALCULATION_INTERVAL_BUFFER);
22    // The calculation buffer is a best-attempt at median worst-case for how many bank ancestors can
23    // accumulate before the bank is rooted.
24    // [brooks] On Wed Oct 26 12:15:21 2022, over the previous 6 hour period against mainnet-beta,
25    // I saw multiple validators reporting metrics in the 120s for `total_parent_banks`.  The mean
26    // is 2 to 3, but a number of nodes also reported values in the low 20s.  A value of 150 should
27    // capture the majority of validators, and will not be an issue for clusters running with
28    // normal slots-per-epoch; this really will only affect tests and epoch schedule warmup.
29    const CALCULATION_INTERVAL_BUFFER: u64 = 150;
30
31    let calculation_interval = calculation_interval(bank);
32    calculation_interval >= MINIMUM_CALCULATION_INTERVAL
33}
34
35/// Calculation of the EAH occurs once per epoch.  All nodes in the cluster must agree on which
36/// slot the EAH is based on.  This slot will be at an offset into the epoch, and referred to as
37/// the "start" slot for the EAH calculation.
38#[must_use]
39#[inline]
40pub fn calculation_offset_start(bank: &Bank) -> Slot {
41    calculation_info(bank).calculation_offset_start
42}
43
44/// Calculation of the EAH occurs once per epoch.  All nodes in the cluster must agree on which
45/// bank will hash the EAH into its `Bank::hash`.  This slot will be at an offset into the epoch,
46/// and referred to as the "stop" slot for the EAH calculation.  All nodes must complete the EAH
47/// calculation before this slot!
48#[must_use]
49#[inline]
50pub fn calculation_offset_stop(bank: &Bank) -> Slot {
51    calculation_info(bank).calculation_offset_stop
52}
53
54/// For the epoch that `bank` is in, get the slot that the EAH calculation starts
55#[must_use]
56#[inline]
57pub fn calculation_start(bank: &Bank) -> Slot {
58    calculation_info(bank).calculation_start
59}
60
61/// For the epoch that `bank` is in, get the slot that the EAH calculation stops
62#[must_use]
63#[inline]
64pub fn calculation_stop(bank: &Bank) -> Slot {
65    calculation_info(bank).calculation_stop
66}
67
68/// Get the number of slots from EAH calculation start to stop; known as the calculation interval
69#[must_use]
70#[inline]
71pub fn calculation_interval(bank: &Bank) -> u64 {
72    calculation_info(bank).calculation_interval
73}
74
75/// Is this bank in the calculation window?
76#[must_use]
77pub fn is_in_calculation_window(bank: &Bank) -> bool {
78    let info = calculation_info(bank);
79    let range = info.calculation_start..info.calculation_stop;
80    range.contains(&bank.slot())
81}
82
83/// For the epoch that `bank` is in, get all the EAH calculation information
84pub fn calculation_info(bank: &Bank) -> CalculationInfo {
85    let epoch = bank.epoch();
86    let epoch_schedule = bank.epoch_schedule();
87
88    let slots_per_epoch = epoch_schedule.get_slots_in_epoch(epoch);
89    let calculation_offset_start = slots_per_epoch / 4;
90    let calculation_offset_stop = slots_per_epoch / 4 * 3;
91
92    let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch);
93    let last_slot_in_epoch = epoch_schedule.get_last_slot_in_epoch(epoch);
94    let calculation_start = first_slot_in_epoch.saturating_add(calculation_offset_start);
95    let calculation_stop = first_slot_in_epoch.saturating_add(calculation_offset_stop);
96    let calculation_interval = calculation_offset_stop.saturating_sub(calculation_offset_start);
97
98    CalculationInfo {
99        epoch,
100        slots_per_epoch,
101        first_slot_in_epoch,
102        last_slot_in_epoch,
103        calculation_offset_start,
104        calculation_offset_stop,
105        calculation_start,
106        calculation_stop,
107        calculation_interval,
108    }
109}
110
111/// All the EAH calculation information for a specific epoch
112///
113/// Computing the EAH calculation information looks up a bunch of values.  Instead of throwing
114/// those values away, they are kept in here as well.  This may aid in future debugging, and the
115/// additional fields are trivial in size.
116#[derive(Debug, Default, Copy, Clone)]
117pub struct CalculationInfo {
118    /*
119     * The values that were looked up, which were needed to get the calculation info
120     */
121    /// The epoch this information applies to
122    pub epoch: Epoch,
123    /// Number of slots in this epoch
124    pub slots_per_epoch: u64,
125    /// First slot in this epoch
126    pub first_slot_in_epoch: Slot,
127    /// Last slot in this epoch
128    pub last_slot_in_epoch: Slot,
129
130    /*
131     * The computed values for the calculation info
132     */
133    /// Offset into the epoch when the EAH calculation starts
134    pub calculation_offset_start: Slot,
135    /// Offset into the epoch when the EAH calculation stops
136    pub calculation_offset_stop: Slot,
137    /// Absolute slot where the EAH calculation starts
138    pub calculation_start: Slot,
139    /// Absolute slot where the EAH calculation stops
140    pub calculation_stop: Slot,
141    /// Number of slots from EAH calculation start to stop
142    pub calculation_interval: u64,
143}
144
145#[cfg(test)]
146mod tests {
147    use {
148        super::*,
149        solana_sdk::{epoch_schedule::EpochSchedule, genesis_config::GenesisConfig},
150        test_case::test_case,
151    };
152
153    #[test_case(     32 => false)] // minimum slots per epoch
154    #[test_case(    361 => false)] // below minimum slots per epoch *for EAH*
155    #[test_case(    362 => false)] // minimum slots per epoch *for EAH*
156    #[test_case(  8_192 => true)] // default dev slots per epoch
157    #[test_case(432_000 => true)] // default slots per epoch
158    fn test_is_enabled_this_epoch(slots_per_epoch: u64) -> bool {
159        let genesis_config = GenesisConfig {
160            epoch_schedule: EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false),
161            ..GenesisConfig::default()
162        };
163        let bank = Bank::new_for_tests(&genesis_config);
164        is_enabled_this_epoch(&bank)
165    }
166
167    #[test]
168    fn test_calculation_offset_bounds() {
169        let bank = Bank::default_for_tests();
170        let offset_start = calculation_offset_start(&bank);
171        let offset_stop = calculation_offset_stop(&bank);
172        assert!(offset_start < offset_stop);
173    }
174
175    #[test]
176    fn test_calculation_bounds() {
177        let bank = Bank::default_for_tests();
178        let start = calculation_start(&bank);
179        let stop = calculation_stop(&bank);
180        assert!(start < stop);
181    }
182
183    #[test]
184    fn test_calculation_info() {
185        for slots_per_epoch in [32, 361, 362, 8_192, 65_536, 432_000, 123_456_789] {
186            for warmup in [false, true] {
187                let genesis_config = GenesisConfig {
188                    epoch_schedule: EpochSchedule::custom(slots_per_epoch, slots_per_epoch, warmup),
189                    ..GenesisConfig::default()
190                };
191                let info = calculation_info(&Bank::new_for_tests(&genesis_config));
192                assert!(info.calculation_offset_start < info.calculation_offset_stop);
193                assert!(info.calculation_offset_start < info.slots_per_epoch);
194                assert!(info.calculation_offset_stop < info.slots_per_epoch);
195                assert!(info.calculation_start < info.calculation_stop);
196                assert!(info.calculation_start > info.first_slot_in_epoch);
197                assert!(info.calculation_stop < info.last_slot_in_epoch);
198                assert!(info.calculation_interval > 0);
199            }
200        }
201    }
202}