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