Skip to main content

solana_runtime/
snapshot_controller.rs

1use {
2    crate::{
3        accounts_background_service::{
4            SnapshotRequest, SnapshotRequestKind, SnapshotRequestSender,
5        },
6        bank::{Bank, SquashTiming},
7    },
8    agave_snapshots::{SnapshotInterval, snapshot_config::SnapshotConfig},
9    log::*,
10    solana_clock::Slot,
11    solana_measure::measure::Measure,
12    std::{
13        sync::{
14            Arc,
15            atomic::{AtomicBool, AtomicU64, Ordering},
16        },
17        time::Instant,
18    },
19};
20
21struct SnapshotGenerationIntervals {
22    full_snapshot_interval: SnapshotInterval,
23    incremental_snapshot_interval: SnapshotInterval,
24}
25
26pub struct SnapshotController {
27    abs_request_sender: SnapshotRequestSender,
28    snapshot_config: SnapshotConfig,
29    latest_abs_request_slot: AtomicU64,
30    request_fastboot_snapshot: AtomicBool,
31    latest_bank_snapshot_slot: AtomicU64,
32}
33
34impl SnapshotController {
35    pub fn new(
36        abs_request_sender: SnapshotRequestSender,
37        snapshot_config: SnapshotConfig,
38        root_slot: Slot,
39    ) -> Self {
40        Self {
41            abs_request_sender,
42            snapshot_config,
43            latest_abs_request_slot: AtomicU64::new(root_slot),
44            request_fastboot_snapshot: AtomicBool::new(false),
45            latest_bank_snapshot_slot: AtomicU64::new(root_slot),
46        }
47    }
48
49    pub fn snapshot_config(&self) -> &SnapshotConfig {
50        &self.snapshot_config
51    }
52
53    pub fn request_sender(&self) -> &SnapshotRequestSender {
54        &self.abs_request_sender
55    }
56
57    fn latest_abs_request_slot(&self) -> Slot {
58        self.latest_abs_request_slot.load(Ordering::Relaxed)
59    }
60
61    fn set_latest_abs_request_slot(&self, slot: Slot) {
62        self.latest_abs_request_slot.store(slot, Ordering::Relaxed);
63    }
64
65    /// Request that a fastboot snapshot is requested at the root bank the next time
66    /// handle_new_roots() is called without requesting a full or incremental snapshot
67    pub fn request_fastboot_snapshot(&self) {
68        self.request_fastboot_snapshot
69            .store(true, Ordering::Relaxed);
70    }
71
72    pub fn latest_bank_snapshot_slot(&self) -> Slot {
73        self.latest_bank_snapshot_slot.load(Ordering::Relaxed)
74    }
75
76    pub fn set_latest_bank_snapshot_slot(&self, slot: Slot) {
77        self.latest_bank_snapshot_slot
78            .store(slot, Ordering::Relaxed);
79    }
80
81    pub fn handle_new_roots(&self, root: Slot, banks: &[&Arc<Bank>]) -> (bool, SquashTiming, u64) {
82        let mut is_root_bank_squashed = false;
83        let mut squash_timing = SquashTiming::default();
84        let mut total_snapshot_ms = 0;
85        let request_fastboot_snapshot = self.request_fastboot_snapshot.load(Ordering::Relaxed);
86
87        let SnapshotGenerationIntervals {
88            full_snapshot_interval,
89            incremental_snapshot_interval,
90        } = self.snapshot_generation_intervals();
91
92        if let Some((bank, request_kind)) = banks.iter().find_map(|bank| {
93            let should_request_full_snapshot =
94                if let SnapshotInterval::Slots(snapshot_interval) = full_snapshot_interval {
95                    bank.block_height() % snapshot_interval == 0
96                } else {
97                    false
98                };
99            let should_request_incremental_snapshot =
100                if let SnapshotInterval::Slots(snapshot_interval) = incremental_snapshot_interval {
101                    bank.block_height() % snapshot_interval == 0
102                } else {
103                    false
104                };
105
106            if bank.slot() <= self.latest_abs_request_slot() {
107                None
108            } else if should_request_full_snapshot {
109                Some((bank, SnapshotRequestKind::FullSnapshot))
110            } else if should_request_incremental_snapshot {
111                Some((bank, SnapshotRequestKind::IncrementalSnapshot))
112            } else if request_fastboot_snapshot {
113                // Cleared only when sent; a full or incremental request is held behind the
114                // write cache lag, so fastboot must be requested on a later root instead.
115                self.request_fastboot_snapshot
116                    .store(false, Ordering::Relaxed);
117                Some((bank, SnapshotRequestKind::FastbootSnapshot))
118            } else {
119                None
120            }
121        }) {
122            let bank_slot = bank.slot();
123            self.set_latest_abs_request_slot(bank_slot);
124            squash_timing += bank.squash();
125
126            is_root_bank_squashed = bank_slot == root;
127
128            let mut snapshot_time = Measure::start("squash::snapshot_time");
129            // Save off the status cache because these may get pruned if another
130            // `set_root()` is called before the snapshots package can be generated
131            let status_cache_slot_deltas = bank.status_cache.read().unwrap().root_slot_deltas();
132            if let Err(e) = self.abs_request_sender.send(SnapshotRequest {
133                snapshot_root_bank: Arc::clone(bank),
134                status_cache_slot_deltas,
135                request_kind,
136                enqueued: Instant::now(),
137            }) {
138                warn!("Error sending snapshot request for bank: {bank_slot}, err: {e:?}");
139            }
140            snapshot_time.stop();
141            total_snapshot_ms += snapshot_time.as_ms();
142        }
143
144        (is_root_bank_squashed, squash_timing, total_snapshot_ms)
145    }
146
147    /// Returns the intervals, in slots, for sending snapshot requests
148    fn snapshot_generation_intervals(&self) -> SnapshotGenerationIntervals {
149        if self.snapshot_config.should_generate_snapshots() {
150            SnapshotGenerationIntervals {
151                full_snapshot_interval: self.snapshot_config.full_snapshot_archive_interval,
152                incremental_snapshot_interval: self
153                    .snapshot_config
154                    .incremental_snapshot_archive_interval,
155            }
156        } else {
157            SnapshotGenerationIntervals {
158                full_snapshot_interval: SnapshotInterval::Disabled,
159                incremental_snapshot_interval: SnapshotInterval::Disabled,
160            }
161        }
162    }
163
164    // Returns true if either snapshot interval is enabled, indicating that the controller will
165    // generate snapshots at some slot intervals
166    pub fn is_generating_snapshots(&self) -> bool {
167        let intervals = self.snapshot_generation_intervals();
168        !(intervals.full_snapshot_interval == SnapshotInterval::Disabled
169            && intervals.incremental_snapshot_interval == SnapshotInterval::Disabled)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use {
176        super::*, crate::accounts_background_service::SnapshotRequestKind,
177        agave_snapshots::snapshot_config::SnapshotConfig, crossbeam_channel::bounded,
178        solana_genesis_config::create_genesis_config, solana_leader_schedule::SlotLeader,
179        std::sync::Arc, test_case::test_case,
180    };
181
182    fn create_banks(num_banks: u64) -> Vec<Arc<Bank>> {
183        let mut banks = vec![];
184        let (genesis_config, _) = create_genesis_config(1_000_000);
185        let (bank0, bank_forks) =
186            Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
187        let mut parent_bank = bank0;
188        banks.push(parent_bank.clone());
189
190        for _ in 1..=num_banks {
191            let slot = parent_bank.slot() + 1;
192            let new_bank = Bank::new_from_parent_with_bank_forks(
193                bank_forks.as_ref(),
194                parent_bank.clone(),
195                SlotLeader::default(),
196                slot,
197            );
198            parent_bank = new_bank;
199            banks.push(parent_bank.clone());
200        }
201
202        banks
203    }
204    #[test_case(SnapshotInterval::Disabled, SnapshotInterval::Disabled,
205        50, None, None; "Snapshots Disabled")]
206    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Slots(5.try_into().unwrap()),
207        4, None, None; "No snapshot triggered")]
208    #[test_case(SnapshotInterval::Slots(5.try_into().unwrap()), SnapshotInterval::Disabled,
209        8, Some(5), Some(SnapshotRequestKind::FullSnapshot); "Full without Incremental")]
210    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Slots(10.try_into().unwrap()),
211        10, Some(10), Some(SnapshotRequestKind::FullSnapshot); "Full and Incremental on same slot, full is selected")]
212    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Slots(5.try_into().unwrap()),
213        15, Some(15), Some(SnapshotRequestKind::IncrementalSnapshot); "Newer incremental picked over older Full")]
214    #[test_case(SnapshotInterval::Disabled, SnapshotInterval::Slots(5.try_into().unwrap()),
215        5, Some(5), Some(SnapshotRequestKind::IncrementalSnapshot); "Incremental without Full")]
216    fn test_handle_new_roots(
217        full_snapshot_archive_interval: SnapshotInterval,
218        incremental_snapshot_archive_interval: SnapshotInterval,
219        num_banks: u64,
220        expected_snapshot_slot: Option<u64>,
221        expected_snapshot_type: Option<SnapshotRequestKind>,
222    ) {
223        let banks = create_banks(num_banks);
224        let banks = banks.iter().rev().collect::<Vec<_>>();
225
226        let snapshot_config = SnapshotConfig {
227            full_snapshot_archive_interval,
228            incremental_snapshot_archive_interval,
229            ..Default::default()
230        };
231
232        let (snapshot_request_sender, snapshot_request_receiver) = bounded(1024);
233        let snapshot_controller =
234            SnapshotController::new(snapshot_request_sender, snapshot_config, 0);
235
236        let (root_bank_squashed, _, _) = snapshot_controller.handle_new_roots(num_banks, &banks);
237
238        // Verify that the root bank was squashed if and only if a snapshot was requested at that slot
239        if expected_snapshot_slot == Some(num_banks) {
240            assert!(root_bank_squashed);
241        } else {
242            assert!(!root_bank_squashed);
243        }
244
245        // Verify the latest_abs_request_slot is updated
246        assert_eq!(
247            snapshot_controller.latest_abs_request_slot(),
248            expected_snapshot_slot.unwrap_or(0)
249        );
250
251        // Pull the snapshot request from the channel
252        let sent_request = snapshot_request_receiver.try_recv();
253
254        if let Some(expected_snapshot_type) = expected_snapshot_type {
255            assert!(
256                sent_request.is_ok(),
257                "Expected a snapshot request to be sent"
258            );
259            let sent_request = sent_request.unwrap();
260            assert_eq!(sent_request.request_kind, expected_snapshot_type);
261            // Verify that the bank was squashed up to the snapshot slot
262            assert_eq!(
263                sent_request.snapshot_root_bank.slot(),
264                expected_snapshot_slot.unwrap()
265            );
266        } else {
267            assert!(
268                sent_request.is_err(),
269                "Expected no snapshot request to be sent"
270            );
271        }
272    }
273
274    #[test_case(SnapshotInterval::Disabled, SnapshotInterval::Disabled, false;
275        "both disabled")]
276    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Disabled, true;
277        "full only")]
278    #[test_case(SnapshotInterval::Disabled, SnapshotInterval::Slots(5.try_into().unwrap()), true;
279        "incremental only")]
280    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Slots(5.try_into().unwrap()), true;
281        "both enabled")]
282    fn test_is_generating_snapshots(
283        full_snapshot_archive_interval: SnapshotInterval,
284        incremental_snapshot_archive_interval: SnapshotInterval,
285        expected: bool,
286    ) {
287        let snapshot_config = SnapshotConfig {
288            full_snapshot_archive_interval,
289            incremental_snapshot_archive_interval,
290            ..Default::default()
291        };
292        let (snapshot_request_sender, _snapshot_request_receiver) = bounded(1024);
293        let snapshot_controller =
294            SnapshotController::new(snapshot_request_sender, snapshot_config, 0);
295        assert_eq!(snapshot_controller.is_generating_snapshots(), expected);
296    }
297
298    #[test_case(SnapshotInterval::Disabled, SnapshotInterval::Disabled,
299        50, SnapshotRequestKind::FastbootSnapshot; "Fastboot triggered when snapshots are disabled")]
300    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Slots(5.try_into().unwrap()),
301        4, SnapshotRequestKind::FastbootSnapshot; "Fastboot triggered when no other snapshot conditions are met")]
302    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Disabled,
303        10, SnapshotRequestKind::FullSnapshot; "Full snapshot overrides fastboot on the same slot")]
304    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Slots(5.try_into().unwrap()),
305        5, SnapshotRequestKind::IncrementalSnapshot; "Incremental snapshot overrides fastboot on the same slot")]
306    #[test_case(SnapshotInterval::Slots(10.try_into().unwrap()), SnapshotInterval::Slots(5.try_into().unwrap()),
307        14, SnapshotRequestKind::FastbootSnapshot; "Fastboot triggered on a newer slot, overriding full and incremental")]
308    fn test_fastboot_snapshot(
309        full_snapshot_archive_interval: SnapshotInterval,
310        incremental_snapshot_archive_interval: SnapshotInterval,
311        num_banks: u64,
312        expected_snapshot_type: SnapshotRequestKind,
313    ) {
314        let banks = create_banks(num_banks);
315        let banks = banks.iter().rev().collect::<Vec<_>>();
316
317        let snapshot_config = SnapshotConfig {
318            full_snapshot_archive_interval,
319            incremental_snapshot_archive_interval,
320            ..Default::default()
321        };
322
323        let (snapshot_request_sender, snapshot_request_receiver) = bounded(1024);
324        let snapshot_controller =
325            SnapshotController::new(snapshot_request_sender, snapshot_config, 0);
326
327        snapshot_controller.request_fastboot_snapshot();
328
329        let (root_bank_squashed, _, _) = snapshot_controller.handle_new_roots(num_banks, &banks);
330
331        // Root bank should always be squashed when fastboot snapshot is requested
332        assert!(root_bank_squashed);
333
334        // Verify the latest_abs_request_slot is updated
335        assert_eq!(snapshot_controller.latest_abs_request_slot(), num_banks,);
336
337        // Pull the snapshot request from the channel
338        let sent_request = snapshot_request_receiver.try_recv();
339
340        assert!(
341            sent_request.is_ok(),
342            "Expected a snapshot request to be sent"
343        );
344        let sent_request = sent_request.unwrap();
345        assert_eq!(sent_request.request_kind, expected_snapshot_type);
346        // Verify that the bank was squashed up to the snapshot slot
347        assert_eq!(sent_request.snapshot_root_bank.slot(), num_banks);
348
349        // The fastboot request stays pending until a fastboot request is sent
350        assert_eq!(
351            snapshot_controller
352                .request_fastboot_snapshot
353                .load(Ordering::Relaxed),
354            expected_snapshot_type != SnapshotRequestKind::FastbootSnapshot,
355        );
356    }
357}