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 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 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 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 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 if expected_snapshot_slot == Some(num_banks) {
238 assert!(root_bank_squashed);
239 } else {
240 assert!(!root_bank_squashed);
241 }
242
243 assert_eq!(
245 snapshot_controller.latest_abs_request_slot(),
246 expected_snapshot_slot.unwrap_or(0)
247 );
248
249 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 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 assert!(root_bank_squashed);
331
332 assert_eq!(snapshot_controller.latest_abs_request_slot(), num_banks,);
334
335 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 assert_eq!(sent_request.snapshot_root_bank.slot(), num_banks);
346 }
347}