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