solana_ledger/
rooted_slot_iterator.rs

1use {
2    crate::{blockstore::*, blockstore_meta::SlotMeta},
3    log::*,
4    solana_clock::Slot,
5};
6
7pub struct RootedSlotIterator<'a> {
8    next_slots: Vec<Slot>,
9    prev_root: Slot,
10    blockstore: &'a Blockstore,
11}
12
13impl<'a> RootedSlotIterator<'a> {
14    pub fn new(start_slot: Slot, blockstore: &'a Blockstore) -> Result<Self> {
15        if blockstore.is_root(start_slot) {
16            Ok(Self {
17                next_slots: vec![start_slot],
18                prev_root: start_slot,
19                blockstore,
20            })
21        } else {
22            Err(BlockstoreError::SlotNotRooted)
23        }
24    }
25}
26impl Iterator for RootedSlotIterator<'_> {
27    type Item = (Slot, Option<SlotMeta>);
28
29    fn next(&mut self) -> Option<Self::Item> {
30        // Clone b/c passing the closure to the map below requires exclusive access to
31        // `self`, which is borrowed here if we don't clone.
32        let (rooted_slot, slot_skipped) = self
33            .next_slots
34            .iter()
35            .find(|x| self.blockstore.is_root(**x))
36            .map(|x| (Some(*x), false))
37            .unwrap_or_else(|| {
38                let mut iter = self
39                    .blockstore
40                    .rooted_slot_iterator(
41                        // First iteration the root always exists as guaranteed by the constructor,
42                        // so this unwrap_or_else cases won't be hit. Every subsequent iteration
43                        // of this iterator must thereafter have a valid `prev_root`
44                        self.prev_root,
45                    )
46                    .expect("Database failure, couldn't fetch rooted slots iterator");
47                iter.next();
48                (iter.next(), true)
49            });
50
51        let slot_meta = rooted_slot
52            .map(|r| {
53                self.blockstore
54                    .meta(r)
55                    .expect("Database failure, couldn't fetch SlotMeta")
56            })
57            .unwrap_or(None);
58
59        if let Some(ref slot_meta) = slot_meta {
60            self.next_slots.clone_from(&slot_meta.next_slots);
61        }
62
63        if slot_meta.is_none() && slot_skipped {
64            warn!("Rooted SlotMeta was deleted in between checking is_root and fetch");
65        }
66
67        rooted_slot.map(|r| {
68            self.prev_root = r;
69            if slot_skipped {
70                (r, None)
71            } else {
72                (r, slot_meta)
73            }
74        })
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use {
81        super::*, crate::blockstore_processor::fill_blockstore_slot_with_ticks, solana_hash::Hash,
82    };
83
84    #[test]
85    fn test_rooted_slot_iterator() {
86        let ledger_path = get_tmp_ledger_path_auto_delete!();
87        let blockstore = Blockstore::open(ledger_path.path()).unwrap();
88        blockstore.set_roots(std::iter::once(&0)).unwrap();
89        let ticks_per_slot = 5;
90        /*
91            Build a blockstore in the ledger with the following fork structure:
92
93                 slot 0
94                   |
95                 slot 1  <-- set_root
96                 /   \
97            slot 2   |
98               /     |
99            slot 3   |
100                     |
101                   slot 4
102
103        */
104
105        // Fork 1, ending at slot 3
106        let last_entry_hash = Hash::default();
107        let fork_point = 1;
108        let mut fork_hash = Hash::default();
109        for slot in 0..=3 {
110            let parent = {
111                if slot == 0 {
112                    0
113                } else {
114                    slot - 1
115                }
116            };
117            let last_entry_hash = fill_blockstore_slot_with_ticks(
118                &blockstore,
119                ticks_per_slot,
120                slot,
121                parent,
122                last_entry_hash,
123            );
124
125            if slot == fork_point {
126                fork_hash = last_entry_hash;
127            }
128        }
129
130        // Fork 2, ending at slot 4
131        let _ =
132            fill_blockstore_slot_with_ticks(&blockstore, ticks_per_slot, 4, fork_point, fork_hash);
133
134        // Set a root
135        blockstore.set_roots([1, 2, 3].iter()).unwrap();
136
137        // Trying to get an iterator on a different fork will error
138        assert!(RootedSlotIterator::new(4, &blockstore).is_err());
139
140        // Trying to get an iterator on any slot on the root fork should succeed
141        let result: Vec<_> = RootedSlotIterator::new(3, &blockstore)
142            .unwrap()
143            .map(|(slot, _)| slot)
144            .collect();
145        let expected = vec![3];
146        assert_eq!(result, expected);
147
148        let result: Vec<_> = RootedSlotIterator::new(0, &blockstore)
149            .unwrap()
150            .map(|(slot, _)| slot)
151            .collect();
152        let expected = vec![0, 1, 2, 3];
153        assert_eq!(result, expected);
154    }
155
156    #[test]
157    fn test_skipping_rooted_slot_iterator() {
158        let ledger_path = get_tmp_ledger_path_auto_delete!();
159        let blockstore = Blockstore::open(ledger_path.path()).unwrap();
160        let ticks_per_slot = 5;
161        /*
162            Build a blockstore in the ledger with the following fork structure:
163                 slot 0
164                   |
165                 slot 1
166                   |
167                 slot 2
168                   |
169                 slot 3 <-- set_root
170                   |
171                 SKIP (caused by a snapshot)
172                   |
173                 slot 10 <-- set_root
174                   |
175                 slot 11 <-- set_root
176        */
177
178        // Create pre-skip slots
179        for slot in 0..=3 {
180            let parent = {
181                if slot == 0 {
182                    0
183                } else {
184                    slot - 1
185                }
186            };
187            fill_blockstore_slot_with_ticks(
188                &blockstore,
189                ticks_per_slot,
190                slot,
191                parent,
192                Hash::default(),
193            );
194        }
195
196        // Set roots
197        blockstore.set_roots([0, 1, 2, 3].iter()).unwrap();
198
199        // Create one post-skip slot at 10, simulating starting from a snapshot
200        // at 10
201        blockstore.set_roots(std::iter::once(&10)).unwrap();
202        // Try to get an iterator from before the skip. The post-skip slot
203        // should not return a SlotMeta
204        let result: Vec<_> = RootedSlotIterator::new(3, &blockstore)
205            .unwrap()
206            .map(|(slot, meta)| (slot, meta.is_some()))
207            .collect();
208        let expected = vec![(3, true), (10, false)];
209        assert_eq!(result, expected);
210
211        // Create one more post-skip slot at 11 with parent equal to 10
212        fill_blockstore_slot_with_ticks(&blockstore, ticks_per_slot, 11, 10, Hash::default());
213
214        // Set roots
215        blockstore.set_roots(std::iter::once(&11)).unwrap();
216
217        let result: Vec<_> = RootedSlotIterator::new(0, &blockstore)
218            .unwrap()
219            .map(|(slot, meta)| (slot, meta.is_some()))
220            .collect();
221        let expected = vec![
222            (0, true),
223            (1, true),
224            (2, true),
225            (3, true),
226            (10, false),
227            (11, true),
228        ];
229        assert_eq!(result, expected);
230    }
231}