solana_accounts_db/epoch_accounts_hash/
manager.rs

1use {
2    super::EpochAccountsHash,
3    solana_clock::Slot,
4    std::sync::{Condvar, Mutex},
5};
6
7/// Manage the epoch accounts hash
8///
9/// Handles setting when an EAH calculation is requested and when it completes.  Also handles
10/// waiting for in-flight calculations to complete when the "stop" Bank must include it.
11#[derive(Debug)]
12pub struct Manager {
13    /// Current state of the epoch accounts hash
14    state: Mutex<State>,
15    /// This condition variable is used to wait for an in-flight EAH calculation to complete
16    cvar: Condvar,
17}
18
19impl Manager {
20    #[must_use]
21    fn _new(state: State) -> Self {
22        Self {
23            state: Mutex::new(state),
24            cvar: Condvar::new(),
25        }
26    }
27
28    /// Create a new epoch accounts hash manager, with the initial state set to Invalid
29    #[must_use]
30    pub fn new_invalid() -> Self {
31        Self::_new(State::Invalid)
32    }
33
34    /// Create a new epoch accounts hash manager, with the initial state set to Valid
35    #[must_use]
36    pub fn new_valid(epoch_accounts_hash: EpochAccountsHash, slot: Slot) -> Self {
37        Self::_new(State::Valid(epoch_accounts_hash, slot))
38    }
39
40    /// An epoch accounts hash calculation has been requested; update our state
41    pub fn set_in_flight(&self, slot: Slot) {
42        let mut state = self.state.lock().unwrap();
43        if let State::InFlight(old_slot) = &*state {
44            panic!("An epoch accounts hash calculation is already in-flight from slot {old_slot}!");
45        }
46        *state = State::InFlight(slot);
47    }
48
49    /// An epoch accounts hash calculation has completed; update our state
50    pub fn set_valid(&self, epoch_accounts_hash: EpochAccountsHash, slot: Slot) {
51        let mut state = self.state.lock().unwrap();
52        if let State::Valid(old_epoch_accounts_hash, old_slot) = &*state {
53            panic!(
54                "The epoch accounts hash is already valid! \
55                \nold slot: {old_slot}, epoch accounts hash: {old_epoch_accounts_hash:?} \
56                \nnew slot: {slot}, epoch accounts hash: {epoch_accounts_hash:?}"
57            );
58        }
59        *state = State::Valid(epoch_accounts_hash, slot);
60        self.cvar.notify_all();
61    }
62
63    /// Get the epoch accounts hash
64    ///
65    /// If an EAH calculation is in-flight, then this call will block until it completes.
66    pub fn wait_get_epoch_accounts_hash(&self) -> EpochAccountsHash {
67        let mut state = self.state.lock().unwrap();
68        loop {
69            match &*state {
70                State::Valid(epoch_accounts_hash, _slot) => break *epoch_accounts_hash,
71                State::InFlight(_slot) => state = self.cvar.wait(state).unwrap(),
72                State::Invalid => panic!("The epoch accounts hash cannot be awaited when Invalid!"),
73            }
74        }
75    }
76
77    /// Get the epoch accounts hash
78    ///
79    /// This fn does not block, and will only yield an EAH if the state is `Valid`
80    pub fn try_get_epoch_accounts_hash(&self) -> Option<EpochAccountsHash> {
81        let state = self.state.lock().unwrap();
82        match &*state {
83            State::Valid(epoch_accounts_hash, _slot) => Some(*epoch_accounts_hash),
84            _ => None,
85        }
86    }
87}
88
89/// The EpochAccountsHash is calculated in the background via AccountsBackgroundService.  This enum
90/// is used to track the state of that calculation, and queried when saving the EAH into a Bank or
91/// Snapshot.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93enum State {
94    /// The initial state of the EAH.  This can occur when loading from a snapshot that does not
95    /// include an EAH, or when starting from genesis (before an EAH calculation is requested).
96    Invalid,
97    /// An EAH calculation has been requested (for `Slot`) and is in flight.  The Bank that should
98    /// save the EAH must wait until the calculation has completed.
99    InFlight(Slot),
100    /// The EAH calculation is complete (for `Slot`) and the EAH value is valid to read/use.
101    Valid(EpochAccountsHash, Slot),
102}
103
104#[cfg(test)]
105mod tests {
106    use {super::*, solana_hash::Hash, std::time::Duration};
107
108    #[test]
109    fn test_new_valid() {
110        let epoch_accounts_hash = EpochAccountsHash::new(Hash::new_unique());
111        let manager = Manager::new_valid(epoch_accounts_hash, 5678);
112        assert_eq!(
113            manager.try_get_epoch_accounts_hash(),
114            Some(epoch_accounts_hash),
115        );
116        assert_eq!(manager.wait_get_epoch_accounts_hash(), epoch_accounts_hash);
117    }
118
119    #[test]
120    fn test_new_invalid() {
121        let manager = Manager::new_invalid();
122        assert!(manager.try_get_epoch_accounts_hash().is_none());
123    }
124
125    #[test]
126    fn test_try_get_epoch_accounts_hash() {
127        let epoch_accounts_hash = EpochAccountsHash::new(Hash::new_unique());
128        for (state, expected) in [
129            (State::Invalid, None),
130            (State::InFlight(123), None),
131            (
132                State::Valid(epoch_accounts_hash, 5678),
133                Some(epoch_accounts_hash),
134            ),
135        ] {
136            let manager = Manager::_new(state);
137            let actual = manager.try_get_epoch_accounts_hash();
138            assert_eq!(actual, expected);
139        }
140    }
141
142    #[test]
143    fn test_wait_epoch_accounts_hash() {
144        // Test: State is Valid, no need to wait
145        {
146            let epoch_accounts_hash = EpochAccountsHash::new(Hash::new_unique());
147            let manager = Manager::new_valid(epoch_accounts_hash, 5678);
148            assert_eq!(manager.wait_get_epoch_accounts_hash(), epoch_accounts_hash);
149        }
150
151        // Test: State is InFlight, must wait
152        {
153            let epoch_accounts_hash = EpochAccountsHash::new(Hash::new_unique());
154            let manager = Manager::new_invalid();
155            manager.set_in_flight(123);
156
157            std::thread::scope(|s| {
158                s.spawn(|| {
159                    std::thread::sleep(Duration::from_secs(1));
160                    manager.set_valid(epoch_accounts_hash, 5678)
161                });
162                assert!(manager.try_get_epoch_accounts_hash().is_none());
163                assert_eq!(manager.wait_get_epoch_accounts_hash(), epoch_accounts_hash);
164                assert!(manager.try_get_epoch_accounts_hash().is_some());
165            });
166        }
167    }
168
169    #[test]
170    #[should_panic]
171    fn test_wait_epoch_accounts_hash_invalid() {
172        // Test: State is Invalid, should panic
173        let manager = Manager::new_invalid();
174        let _epoch_accounts_hash = manager.wait_get_epoch_accounts_hash();
175    }
176}