Skip to main content

clone_solana_ledger/
blockstore_meta.rs

1use {
2    crate::{
3        blockstore::MAX_DATA_SHREDS_PER_SLOT,
4        shred::{self, Shred, ShredType},
5    },
6    bitflags::bitflags,
7    serde::{Deserialize, Deserializer, Serialize, Serializer},
8    clone_solana_sdk::{
9        clock::{Slot, UnixTimestamp},
10        hash::Hash,
11    },
12    std::{
13        collections::BTreeSet,
14        ops::{Bound, Range, RangeBounds},
15    },
16};
17
18bitflags! {
19    #[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
20    /// Flags to indicate whether a slot is a descendant of a slot on the main fork
21    pub struct ConnectedFlags:u8 {
22        // A slot S should be considered to be connected if:
23        // 1) S is a rooted slot itself OR
24        // 2) S's parent is connected AND S is full (S's complete block present)
25        //
26        // 1) is a straightfoward case, roots are finalized blocks on the main fork
27        // so by definition, they are connected. All roots are connected, but not
28        // all connected slots are (or will become) roots.
29        //
30        // Based on the criteria stated in 2), S is connected iff it has a series
31        // of ancestors (that are each connected) that form a chain back to
32        // some root slot.
33        //
34        // A ledger that is updating with a cluster will have either begun at
35        // genesis or at some snapshot slot.
36        // - Genesis is obviously a special case, and slot 0's parent is deemed
37        //   to be connected in order to kick off the induction
38        // - Snapshots are taken at rooted slots, and as such, the snapshot slot
39        //   should be marked as connected so that a connected chain can start
40        //
41        // CONNECTED is explicitly the first bit to ensure backwards compatibility
42        // with the boolean field that ConnectedFlags replaced in SlotMeta.
43        const CONNECTED        = 0b0000_0001;
44        // PARENT_CONNECTED IS INTENTIIONALLY UNUSED FOR NOW
45        const PARENT_CONNECTED = 0b1000_0000;
46    }
47}
48
49impl Default for ConnectedFlags {
50    fn default() -> Self {
51        ConnectedFlags::empty()
52    }
53}
54
55#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
56/// The Meta column family
57pub struct SlotMeta {
58    /// The number of slots above the root (the genesis block). The first
59    /// slot has slot 0.
60    pub slot: Slot,
61    /// The total number of consecutive shreds starting from index 0 we have received for this slot.
62    /// At the same time, it is also an index of the first missing shred for this slot, while the
63    /// slot is incomplete.
64    pub consumed: u64,
65    /// The index *plus one* of the highest shred received for this slot.  Useful
66    /// for checking if the slot has received any shreds yet, and to calculate the
67    /// range where there is one or more holes: `(consumed..received)`.
68    pub received: u64,
69    /// The timestamp of the first time a shred was added for this slot
70    pub first_shred_timestamp: u64,
71    /// The index of the shred that is flagged as the last shred for this slot.
72    /// None until the shred with LAST_SHRED_IN_SLOT flag is received.
73    #[serde(with = "serde_compat")]
74    pub last_index: Option<u64>,
75    /// The slot height of the block this one derives from.
76    /// The parent slot of the head of a detached chain of slots is None.
77    #[serde(with = "serde_compat")]
78    pub parent_slot: Option<Slot>,
79    /// The list of slots, each of which contains a block that derives
80    /// from this one.
81    pub next_slots: Vec<Slot>,
82    /// Connected status flags of this slot
83    pub connected_flags: ConnectedFlags,
84    /// Shreds indices which are marked data complete.  That is, those that have the
85    /// [`ShredFlags::DATA_COMPLETE_SHRED`][`crate::shred::ShredFlags::DATA_COMPLETE_SHRED`] set.
86    pub completed_data_indexes: BTreeSet<u32>,
87}
88
89// Serde implementation of serialize and deserialize for Option<u64>
90// where None is represented as u64::MAX; for backward compatibility.
91mod serde_compat {
92    use super::*;
93
94    pub(super) fn serialize<S>(val: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
95    where
96        S: Serializer,
97    {
98        val.unwrap_or(u64::MAX).serialize(serializer)
99    }
100
101    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
102    where
103        D: Deserializer<'de>,
104    {
105        let val = u64::deserialize(deserializer)?;
106        Ok((val != u64::MAX).then_some(val))
107    }
108}
109
110pub type Index = IndexV2;
111pub type ShredIndex = ShredIndexV2;
112/// We currently support falling back to the previous format for migration purposes.
113///
114/// See https://github.com/anza-xyz/agave/issues/3570.
115pub type IndexFallback = IndexV1;
116pub type ShredIndexFallback = ShredIndexV1;
117
118#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
119/// Index recording presence/absence of shreds
120pub struct IndexV1 {
121    pub slot: Slot,
122    data: ShredIndexV1,
123    coding: ShredIndexV1,
124}
125
126#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
127pub struct IndexV2 {
128    pub slot: Slot,
129    data: ShredIndexV2,
130    coding: ShredIndexV2,
131}
132
133impl From<IndexV2> for IndexV1 {
134    fn from(index: IndexV2) -> Self {
135        IndexV1 {
136            slot: index.slot,
137            data: index.data.into(),
138            coding: index.coding.into(),
139        }
140    }
141}
142
143impl From<IndexV1> for IndexV2 {
144    fn from(index: IndexV1) -> Self {
145        IndexV2 {
146            slot: index.slot,
147            data: index.data.into(),
148            coding: index.coding.into(),
149        }
150    }
151}
152
153#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
154pub struct ShredIndexV1 {
155    /// Map representing presence/absence of shreds
156    index: BTreeSet<u64>,
157}
158
159#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
160/// Erasure coding information
161pub struct ErasureMeta {
162    /// Which erasure set in the slot this is
163    #[serde(
164        serialize_with = "serde_compat_cast::serialize::<_, u64, _>",
165        deserialize_with = "serde_compat_cast::deserialize::<_, u64, _>"
166    )]
167    fec_set_index: u32,
168    /// First coding index in the FEC set
169    first_coding_index: u64,
170    /// Index of the first received coding shred in the FEC set
171    first_received_coding_index: u64,
172    /// Erasure configuration for this erasure set
173    config: ErasureConfig,
174}
175
176// Helper module to serde values by type-casting to an intermediate
177// type for backward compatibility.
178mod serde_compat_cast {
179    use super::*;
180
181    // Serializes a value of type T by first type-casting to type R.
182    pub(super) fn serialize<S: Serializer, R, T: Copy>(
183        &val: &T,
184        serializer: S,
185    ) -> Result<S::Ok, S::Error>
186    where
187        R: TryFrom<T> + Serialize,
188        <R as TryFrom<T>>::Error: std::fmt::Display,
189    {
190        R::try_from(val)
191            .map_err(serde::ser::Error::custom)?
192            .serialize(serializer)
193    }
194
195    // Deserializes a value of type R and type-casts it to type T.
196    pub(super) fn deserialize<'de, D, R, T>(deserializer: D) -> Result<T, D::Error>
197    where
198        D: Deserializer<'de>,
199        R: Deserialize<'de>,
200        T: TryFrom<R>,
201        <T as TryFrom<R>>::Error: std::fmt::Display,
202    {
203        R::deserialize(deserializer)
204            .map(T::try_from)?
205            .map_err(serde::de::Error::custom)
206    }
207}
208
209#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
210pub(crate) struct ErasureConfig {
211    num_data: usize,
212    num_coding: usize,
213}
214
215#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
216pub struct MerkleRootMeta {
217    /// The merkle root, `None` for legacy shreds
218    merkle_root: Option<Hash>,
219    /// The first received shred index
220    first_received_shred_index: u32,
221    /// The shred type of the first received shred
222    first_received_shred_type: ShredType,
223}
224
225#[derive(Deserialize, Serialize)]
226pub struct DuplicateSlotProof {
227    #[serde(with = "shred::serde_bytes_payload")]
228    pub shred1: shred::Payload,
229    #[serde(with = "shred::serde_bytes_payload")]
230    pub shred2: shred::Payload,
231}
232
233#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
234pub enum FrozenHashVersioned {
235    Current(FrozenHashStatus),
236}
237
238impl FrozenHashVersioned {
239    pub fn frozen_hash(&self) -> Hash {
240        match self {
241            FrozenHashVersioned::Current(frozen_hash_status) => frozen_hash_status.frozen_hash,
242        }
243    }
244
245    pub fn is_duplicate_confirmed(&self) -> bool {
246        match self {
247            FrozenHashVersioned::Current(frozen_hash_status) => {
248                frozen_hash_status.is_duplicate_confirmed
249            }
250        }
251    }
252}
253
254#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
255pub struct FrozenHashStatus {
256    pub frozen_hash: Hash,
257    pub is_duplicate_confirmed: bool,
258}
259
260impl Index {
261    pub(crate) fn new(slot: Slot) -> Self {
262        Self {
263            slot,
264            data: ShredIndex::default(),
265            coding: ShredIndex::default(),
266        }
267    }
268
269    pub fn data(&self) -> &ShredIndex {
270        &self.data
271    }
272    pub fn coding(&self) -> &ShredIndex {
273        &self.coding
274    }
275
276    pub(crate) fn data_mut(&mut self) -> &mut ShredIndex {
277        &mut self.data
278    }
279    pub(crate) fn coding_mut(&mut self) -> &mut ShredIndex {
280        &mut self.coding
281    }
282}
283
284#[cfg(test)]
285#[allow(unused)]
286impl IndexFallback {
287    pub(crate) fn new(slot: Slot) -> Self {
288        Self {
289            slot,
290            data: ShredIndexFallback::default(),
291            coding: ShredIndexFallback::default(),
292        }
293    }
294
295    pub fn data(&self) -> &ShredIndexFallback {
296        &self.data
297    }
298    pub fn coding(&self) -> &ShredIndexFallback {
299        &self.coding
300    }
301
302    pub(crate) fn data_mut(&mut self) -> &mut ShredIndexFallback {
303        &mut self.data
304    }
305    pub(crate) fn coding_mut(&mut self) -> &mut ShredIndexFallback {
306        &mut self.coding
307    }
308}
309
310/// Superseded by [`ShredIndexV2`].
311///
312/// TODO: Remove this once new [`ShredIndexV2`] is fully rolled out
313/// and no longer relies on it for fallback.
314#[cfg(test)]
315#[allow(unused)]
316impl ShredIndexV1 {
317    pub fn num_shreds(&self) -> usize {
318        self.index.len()
319    }
320
321    pub(crate) fn range<R>(&self, bounds: R) -> impl Iterator<Item = &u64>
322    where
323        R: RangeBounds<u64>,
324    {
325        self.index.range(bounds)
326    }
327
328    pub(crate) fn contains(&self, index: u64) -> bool {
329        self.index.contains(&index)
330    }
331
332    pub(crate) fn insert(&mut self, index: u64) {
333        self.index.insert(index);
334    }
335
336    fn remove(&mut self, index: u64) {
337        self.index.remove(&index);
338    }
339}
340
341/// A bitvec (`Vec<u8>`) of shred indices, where each u8 represents 8 shred indices.
342///
343/// The current implementation of [`ShredIndex`] utilizes a [`BTreeSet`] to store
344/// shred indices. While [`BTreeSet`] remains efficient as operations are amortized
345/// over time, the overhead of the B-tree structure becomes significant when frequently
346/// serialized and deserialized. In particular:
347/// - **Tree Traversal**: Serialization requires walking the non-contiguous tree structure.
348/// - **Reconstruction**: Deserialization involves rebuilding the tree in bulk,
349///   including dynamic memory allocations and re-balancing nodes.
350///
351/// In contrast, our bit vec implementation provides:
352/// - **Contiguous Memory**: All bits are stored in a contiguous array of u64 words,
353///   allowing direct indexing and efficient memory access patterns.
354/// - **Direct Range Access**: Can load only the specific words that overlap with a
355///   requested range, avoiding unnecessary traversal.
356/// - **Simplified Serialization**: The contiguous memory layout allows for efficient
357///   serialization/deserialization without tree reconstruction.
358#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
359pub struct ShredIndexV2 {
360    #[serde(with = "serde_bytes")]
361    index: Vec<u8>,
362    num_shreds: usize,
363}
364
365impl Default for ShredIndexV2 {
366    fn default() -> Self {
367        Self {
368            index: vec![0; Self::MAX_WORDS_PER_SLOT],
369            num_shreds: 0,
370        }
371    }
372}
373
374type ShredIndexV2Word = u8;
375impl ShredIndexV2 {
376    const SIZE_OF_WORD: usize = std::mem::size_of::<ShredIndexV2Word>();
377    const BITS_PER_WORD: usize = Self::SIZE_OF_WORD * 8;
378    const MAX_WORDS_PER_SLOT: usize = MAX_DATA_SHREDS_PER_SLOT.div_ceil(Self::BITS_PER_WORD);
379
380    pub fn num_shreds(&self) -> usize {
381        self.num_shreds
382    }
383
384    fn index_and_mask(index: u64) -> (usize, ShredIndexV2Word) {
385        let word_idx = index as usize / Self::BITS_PER_WORD;
386        let bit_idx = index as usize % Self::BITS_PER_WORD;
387        let mask = 1 << bit_idx;
388        (word_idx, mask as ShredIndexV2Word)
389    }
390
391    #[cfg(test)]
392    fn remove(&mut self, index: u64) {
393        assert!(
394            index < MAX_DATA_SHREDS_PER_SLOT as u64,
395            "index out of bounds. {index} >= {MAX_DATA_SHREDS_PER_SLOT}"
396        );
397
398        let (word_idx, mask) = Self::index_and_mask(index);
399
400        if self.index[word_idx] & mask != 0 {
401            self.index[word_idx] ^= mask;
402            self.num_shreds -= 1;
403        }
404    }
405
406    #[allow(unused)]
407    pub(crate) fn contains(&self, idx: u64) -> bool {
408        if idx >= MAX_DATA_SHREDS_PER_SLOT as u64 {
409            return false;
410        }
411        let (word_idx, mask) = Self::index_and_mask(idx);
412        (self.index[word_idx] & mask) != 0
413    }
414
415    pub(crate) fn insert(&mut self, idx: u64) {
416        if idx >= MAX_DATA_SHREDS_PER_SLOT as u64 {
417            return;
418        }
419        let (word_idx, mask) = Self::index_and_mask(idx);
420        if self.index[word_idx] & mask == 0 {
421            self.index[word_idx] |= mask;
422            self.num_shreds += 1;
423        }
424    }
425
426    /// Provides an iterator over the set shred indices within a specified range.
427    ///
428    /// # Algorithm
429    /// 1. Divide the specified range into 8-bit words (u8).
430    /// 2. For each word:HH
431    ///    - Calculate the base index (position of the word * 8).
432    ///    - Process all set bits in the word.
433    ///    - For words overlapping the range boundaries:
434    ///      - Determine the relevant bit range using boundaries.
435    ///      - Mask out bits outside the range.
436    ///    - Use bit manipulation to iterate over set bits efficiently.
437    ///
438    /// ## Explanation
439    /// Given range `[75..205]`:
440    ///
441    /// Word layout (each word is 8 bits), where each X represents a bit candidate:
442    /// ```text
443    /// Word 9  (72-79):   [..XXXXXX] ← Partial word (start)
444    /// Word 10 (80-87):   [XXXXXXXX] ← Full word (entirely in range)
445    /// ...
446    /// Word 25 (200-207): [XXXXXX..] ← Partial word (end)
447    /// ```
448    ///
449    /// Partial Word 9 (contains start boundary 75):
450    /// - Base index = 72
451    /// - Lower boundary = 75 - 72 = 3
452    /// - Lower mask = `11111000` (right-shift)
453    ///
454    /// Partial Word 25 (contains end boundary 205):
455    /// - Base index = 200
456    /// - Upper boundary = 205 - 200 = 5
457    /// - Upper mask = `00111111` (left-shift)
458    ///
459    /// Final mask = `word & lower_mask & upper_mask`
460    ///
461    /// Bit iteration:
462    /// 1. Apply masks to restrict the bits to the range.
463    /// 2. While bits remain in the masked word:
464    ///    a. Find the lowest set bit (`trailing_zeros`).
465    ///    b. Add the bit's position to the base index.
466    ///    c. Clear the lowest set bit (`n & (n - 1)`).
467    /// ```
468    pub(crate) fn range<R>(&self, bounds: R) -> impl Iterator<Item = u64> + '_
469    where
470        R: RangeBounds<u64>,
471    {
472        let start = match bounds.start_bound() {
473            Bound::Included(&n) => n as usize,
474            Bound::Excluded(&n) => n as usize + 1,
475            Bound::Unbounded => 0,
476        };
477        let end = match bounds.end_bound() {
478            Bound::Included(&n) => n as usize + 1,
479            Bound::Excluded(&n) => n as usize,
480            Bound::Unbounded => MAX_DATA_SHREDS_PER_SLOT,
481        };
482
483        let end_word = end
484            .div_ceil(Self::BITS_PER_WORD)
485            .min(Self::MAX_WORDS_PER_SLOT);
486        let start_word = (start / Self::BITS_PER_WORD).min(end_word);
487
488        self.index[start_word..end_word]
489            .iter()
490            .enumerate()
491            .flat_map(move |(word_offset, &word)| {
492                let base_idx = (start_word + word_offset) * Self::BITS_PER_WORD;
493
494                let lower_bound = start.saturating_sub(base_idx);
495                let upper_bound = if base_idx + Self::BITS_PER_WORD > end {
496                    end - base_idx
497                } else {
498                    Self::BITS_PER_WORD
499                };
500
501                let lower_mask = !0 << lower_bound;
502                let upper_mask = !0 >> (Self::BITS_PER_WORD - upper_bound);
503                let mask = word & lower_mask & upper_mask;
504
505                std::iter::from_fn({
506                    let mut remaining = mask;
507                    move || {
508                        if remaining == 0 {
509                            None
510                        } else {
511                            let bit_idx = remaining.trailing_zeros();
512                            // Clear the lowest set bit
513                            remaining &= remaining - 1;
514                            Some(base_idx as u64 + bit_idx as u64)
515                        }
516                    }
517                })
518            })
519    }
520
521    fn iter(&self) -> impl Iterator<Item = u64> + '_ {
522        self.range(0..MAX_DATA_SHREDS_PER_SLOT as u64)
523    }
524}
525
526impl FromIterator<u64> for ShredIndexV2 {
527    fn from_iter<T: IntoIterator<Item = u64>>(iter: T) -> Self {
528        let mut index = ShredIndexV2::default();
529        for idx in iter {
530            index.insert(idx);
531        }
532        index
533    }
534}
535
536impl FromIterator<u64> for ShredIndexV1 {
537    fn from_iter<T: IntoIterator<Item = u64>>(iter: T) -> Self {
538        ShredIndexV1 {
539            index: iter.into_iter().collect(),
540        }
541    }
542}
543
544impl From<ShredIndexV1> for ShredIndexV2 {
545    fn from(value: ShredIndexV1) -> Self {
546        value.index.into_iter().collect()
547    }
548}
549
550impl From<ShredIndexV2> for ShredIndexV1 {
551    fn from(value: ShredIndexV2) -> Self {
552        ShredIndexV1 {
553            index: value.iter().collect(),
554        }
555    }
556}
557
558impl SlotMeta {
559    pub fn is_full(&self) -> bool {
560        // last_index is None when it has no information about how
561        // many shreds will fill this slot.
562        // Note: A full slot with zero shreds is not possible.
563        // Should never happen
564        if self
565            .last_index
566            .map(|ix| self.consumed > ix + 1)
567            .unwrap_or_default()
568        {
569            datapoint_error!(
570                "blockstore_error",
571                (
572                    "error",
573                    format!(
574                        "Observed a slot meta with consumed: {} > meta.last_index + 1: {:?}",
575                        self.consumed,
576                        self.last_index.map(|ix| ix + 1),
577                    ),
578                    String
579                )
580            );
581        }
582
583        Some(self.consumed) == self.last_index.map(|ix| ix + 1)
584    }
585
586    /// Returns a boolean indicating whether this meta's parent slot is known.
587    /// This value being true indicates that this meta's slot is the head of a
588    /// detached chain of slots.
589    pub(crate) fn is_orphan(&self) -> bool {
590        self.parent_slot.is_none()
591    }
592
593    /// Returns a boolean indicating whether the meta is connected.
594    pub fn is_connected(&self) -> bool {
595        self.connected_flags.contains(ConnectedFlags::CONNECTED)
596    }
597
598    /// Mark the meta as connected.
599    pub fn set_connected(&mut self) {
600        assert!(self.is_parent_connected());
601        self.connected_flags.set(ConnectedFlags::CONNECTED, true);
602    }
603
604    /// Returns a boolean indicating whether the meta's parent is connected.
605    pub fn is_parent_connected(&self) -> bool {
606        self.connected_flags
607            .contains(ConnectedFlags::PARENT_CONNECTED)
608    }
609
610    /// Mark the meta's parent as connected.
611    /// If the meta is also full, the meta is now connected as well. Return a
612    /// boolean indicating whether the meta becamed connected from this call.
613    pub fn set_parent_connected(&mut self) -> bool {
614        // Already connected so nothing to do, bail early
615        if self.is_connected() {
616            return false;
617        }
618
619        self.connected_flags
620            .set(ConnectedFlags::PARENT_CONNECTED, true);
621
622        if self.is_full() {
623            self.set_connected();
624        }
625
626        self.is_connected()
627    }
628
629    /// Dangerous.
630    #[cfg(feature = "dev-context-only-utils")]
631    pub fn unset_parent(&mut self) {
632        self.parent_slot = None;
633    }
634
635    pub fn clear_unconfirmed_slot(&mut self) {
636        let old = std::mem::replace(self, SlotMeta::new_orphan(self.slot));
637        self.next_slots = old.next_slots;
638    }
639
640    pub(crate) fn new(slot: Slot, parent_slot: Option<Slot>) -> Self {
641        let connected_flags = if slot == 0 {
642            // Slot 0 is the start, mark it as having its' parent connected
643            // such that slot 0 becoming full will be updated as connected
644            ConnectedFlags::PARENT_CONNECTED
645        } else {
646            ConnectedFlags::default()
647        };
648        SlotMeta {
649            slot,
650            parent_slot,
651            connected_flags,
652            ..SlotMeta::default()
653        }
654    }
655
656    pub(crate) fn new_orphan(slot: Slot) -> Self {
657        Self::new(slot, /*parent_slot:*/ None)
658    }
659}
660
661impl ErasureMeta {
662    pub(crate) fn from_coding_shred(shred: &Shred) -> Option<Self> {
663        match shred.shred_type() {
664            ShredType::Data => None,
665            ShredType::Code => {
666                let config = ErasureConfig {
667                    num_data: usize::from(shred.num_data_shreds().ok()?),
668                    num_coding: usize::from(shred.num_coding_shreds().ok()?),
669                };
670                let first_coding_index = u64::from(shred.first_coding_index()?);
671                let first_received_coding_index = u64::from(shred.index());
672                let erasure_meta = ErasureMeta {
673                    fec_set_index: shred.fec_set_index(),
674                    config,
675                    first_coding_index,
676                    first_received_coding_index,
677                };
678                Some(erasure_meta)
679            }
680        }
681    }
682
683    // Returns true if the erasure fields on the shred
684    // are consistent with the erasure-meta.
685    pub(crate) fn check_coding_shred(&self, shred: &Shred) -> bool {
686        let Some(mut other) = Self::from_coding_shred(shred) else {
687            return false;
688        };
689        other.first_received_coding_index = self.first_received_coding_index;
690        self == &other
691    }
692
693    /// Returns true if both shreds are coding shreds and have a
694    /// consistent erasure config
695    pub fn check_erasure_consistency(shred1: &Shred, shred2: &Shred) -> bool {
696        let Some(coding_shred) = Self::from_coding_shred(shred1) else {
697            return false;
698        };
699        coding_shred.check_coding_shred(shred2)
700    }
701
702    pub(crate) fn config(&self) -> ErasureConfig {
703        self.config
704    }
705
706    pub(crate) fn data_shreds_indices(&self) -> Range<u64> {
707        let num_data = self.config.num_data as u64;
708        let fec_set_index = u64::from(self.fec_set_index);
709        fec_set_index..fec_set_index + num_data
710    }
711
712    pub(crate) fn coding_shreds_indices(&self) -> Range<u64> {
713        let num_coding = self.config.num_coding as u64;
714        self.first_coding_index..self.first_coding_index + num_coding
715    }
716
717    pub(crate) fn first_received_coding_shred_index(&self) -> Option<u32> {
718        u32::try_from(self.first_received_coding_index).ok()
719    }
720
721    pub(crate) fn next_fec_set_index(&self) -> Option<u32> {
722        let num_data = u32::try_from(self.config.num_data).ok()?;
723        self.fec_set_index.checked_add(num_data)
724    }
725
726    // Returns true if some data shreds are missing, but there are enough data
727    // and coding shreds to recover the erasure batch.
728    // TODO: In order to retransmit all shreds from the erasure batch, we need
729    // to always recover the batch as soon as possible, even if no data shreds
730    // are missing. But because we currently do not store recovered coding
731    // shreds into the blockstore we cannot identify if the batch was already
732    // recovered (and retransmitted) or not.
733    pub(crate) fn should_recover_shreds(&self, index: &Index) -> bool {
734        let num_data = index.data().range(self.data_shreds_indices()).count();
735        if num_data >= self.config.num_data {
736            return false; // No data shreds is missing.
737        }
738        let num_coding = index.coding().range(self.coding_shreds_indices()).count();
739        self.config.num_data <= num_data + num_coding
740    }
741
742    #[cfg(test)]
743    pub(crate) fn clear_first_received_coding_shred_index(&mut self) {
744        self.first_received_coding_index = 0;
745    }
746}
747
748impl MerkleRootMeta {
749    pub(crate) fn from_shred(shred: &Shred) -> Self {
750        Self {
751            // An error here after the shred has already sigverified
752            // can only indicate that the leader is sending
753            // legacy or malformed shreds. We should still store
754            // `None` for those cases in blockstore, as a later
755            // shred that contains a proper merkle root would constitute
756            // a valid duplicate shred proof.
757            merkle_root: shred.merkle_root().ok(),
758            first_received_shred_index: shred.index(),
759            first_received_shred_type: shred.shred_type(),
760        }
761    }
762
763    pub(crate) fn merkle_root(&self) -> Option<Hash> {
764        self.merkle_root
765    }
766
767    pub(crate) fn first_received_shred_index(&self) -> u32 {
768        self.first_received_shred_index
769    }
770
771    pub(crate) fn first_received_shred_type(&self) -> ShredType {
772        self.first_received_shred_type
773    }
774}
775
776impl DuplicateSlotProof {
777    pub(crate) fn new<S, T>(shred1: S, shred2: T) -> Self
778    where
779        shred::Payload: From<S> + From<T>,
780    {
781        DuplicateSlotProof {
782            shred1: shred::Payload::from(shred1),
783            shred2: shred::Payload::from(shred2),
784        }
785    }
786}
787
788#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
789pub struct TransactionStatusIndexMeta {
790    pub max_slot: Slot,
791    pub frozen: bool,
792}
793
794#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
795pub struct AddressSignatureMeta {
796    pub writeable: bool,
797}
798
799/// Performance information about validator execution during a time slice.
800///
801/// Older versions should only arise as a result of deserialization of entries stored by a previous
802/// version of the validator.  Current version should only produce [`PerfSampleV2`].
803#[derive(Clone, Debug, PartialEq, Eq)]
804pub enum PerfSample {
805    V1(PerfSampleV1),
806    V2(PerfSampleV2),
807}
808
809impl From<PerfSampleV1> for PerfSample {
810    fn from(value: PerfSampleV1) -> PerfSample {
811        PerfSample::V1(value)
812    }
813}
814
815impl From<PerfSampleV2> for PerfSample {
816    fn from(value: PerfSampleV2) -> PerfSample {
817        PerfSample::V2(value)
818    }
819}
820
821/// Version of [`PerfSample`] used before 1.15.x.
822#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
823pub struct PerfSampleV1 {
824    pub num_transactions: u64,
825    pub num_slots: u64,
826    pub sample_period_secs: u16,
827}
828
829/// Version of the [`PerfSample`] introduced in 1.15.x.
830#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
831pub struct PerfSampleV2 {
832    // `PerfSampleV1` part
833    pub num_transactions: u64,
834    pub num_slots: u64,
835    pub sample_period_secs: u16,
836
837    // New fields.
838    pub num_non_vote_transactions: u64,
839}
840
841#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
842pub struct ProgramCost {
843    pub cost: u64,
844}
845
846#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
847pub struct OptimisticSlotMetaV0 {
848    pub hash: Hash,
849    pub timestamp: UnixTimestamp,
850}
851
852#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
853pub enum OptimisticSlotMetaVersioned {
854    V0(OptimisticSlotMetaV0),
855}
856
857impl OptimisticSlotMetaVersioned {
858    pub fn new(hash: Hash, timestamp: UnixTimestamp) -> Self {
859        OptimisticSlotMetaVersioned::V0(OptimisticSlotMetaV0 { hash, timestamp })
860    }
861
862    pub fn hash(&self) -> Hash {
863        match self {
864            OptimisticSlotMetaVersioned::V0(meta) => meta.hash,
865        }
866    }
867
868    pub fn timestamp(&self) -> UnixTimestamp {
869        match self {
870            OptimisticSlotMetaVersioned::V0(meta) => meta.timestamp,
871        }
872    }
873}
874
875#[cfg(test)]
876mod test {
877    use {
878        super::*,
879        bincode::Options,
880        proptest::prelude::*,
881        rand::{seq::SliceRandom, thread_rng},
882    };
883
884    #[test]
885    fn test_slot_meta_slot_zero_connected() {
886        let meta = SlotMeta::new(0 /* slot */, None /* parent */);
887        assert!(meta.is_parent_connected());
888        assert!(!meta.is_connected());
889    }
890
891    #[test]
892    fn test_should_recover_shreds() {
893        let fec_set_index = 0;
894        let erasure_config = ErasureConfig {
895            num_data: 8,
896            num_coding: 16,
897        };
898        let e_meta = ErasureMeta {
899            fec_set_index,
900            first_coding_index: u64::from(fec_set_index),
901            config: erasure_config,
902            first_received_coding_index: 0,
903        };
904        let mut rng = thread_rng();
905        let mut index = Index::new(0);
906
907        let data_indexes = 0..erasure_config.num_data as u64;
908        let coding_indexes = 0..erasure_config.num_coding as u64;
909
910        assert!(!e_meta.should_recover_shreds(&index));
911
912        for ix in data_indexes.clone() {
913            index.data_mut().insert(ix);
914        }
915
916        assert!(!e_meta.should_recover_shreds(&index));
917
918        for ix in coding_indexes.clone() {
919            index.coding_mut().insert(ix);
920        }
921
922        for &idx in data_indexes
923            .clone()
924            .collect::<Vec<_>>()
925            .choose_multiple(&mut rng, erasure_config.num_data)
926        {
927            index.data_mut().remove(idx);
928
929            assert!(e_meta.should_recover_shreds(&index));
930        }
931
932        for ix in data_indexes {
933            index.data_mut().insert(ix);
934        }
935
936        for &idx in coding_indexes
937            .collect::<Vec<_>>()
938            .choose_multiple(&mut rng, erasure_config.num_coding)
939        {
940            index.coding_mut().remove(idx);
941
942            assert!(!e_meta.should_recover_shreds(&index));
943        }
944    }
945
946    /// Generate a random Range<u64>.
947    fn rand_range(range: Range<u64>) -> impl Strategy<Value = Range<u64>> {
948        (range.clone(), range).prop_map(
949            // Avoid descending (empty) ranges
950            |(start, end)| {
951                if start > end {
952                    end..start
953                } else {
954                    start..end
955                }
956            },
957        )
958    }
959
960    proptest! {
961        #[test]
962        fn shred_index_legacy_compat(
963            shreds in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64),
964            range in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64)
965        ) {
966            let mut legacy = ShredIndexV1::default();
967            let mut v2 = ShredIndexV2::default();
968
969            for i in shreds {
970                v2.insert(i);
971                legacy.insert(i);
972            }
973
974            for &i in legacy.index.iter() {
975                assert!(v2.contains(i));
976            }
977
978            assert_eq!(v2.num_shreds(), legacy.num_shreds());
979
980            assert_eq!(
981                v2.range(range.clone()).sum::<u64>(),
982                legacy.range(range).sum::<u64>()
983            );
984
985            assert_eq!(ShredIndexV2::from(legacy.clone()), v2.clone());
986            assert_eq!(ShredIndexV1::from(v2), legacy);
987        }
988
989        /// Property: [`Index`] cannot be deserialized from [`IndexV2`].
990        ///
991        /// # Failure cases
992        /// 1. Empty [`IndexV2`]
993        ///     - [`ShredIndex`] deserialization should fail due to trailing bytes of `num_shreds`.
994        /// 2. Non-empty [`IndexV2`]
995        ///     - Encoded length of [`ShredIndexV2::index`] (`Vec<u8>`) will be relative to a sequence of `u8`,
996        ///       resulting in not enough bytes when deserialized into sequence of `u64`.
997        #[test]
998        fn test_legacy_collision(
999            coding_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64),
1000            data_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64),
1001            slot in 0..u64::MAX
1002        ) {
1003            let index = IndexV2 {
1004                coding: coding_indices.into_iter().collect(),
1005                data: data_indices.into_iter().collect(),
1006                slot,
1007            };
1008            let config = bincode::DefaultOptions::new().with_fixint_encoding().reject_trailing_bytes();
1009            let legacy = config.deserialize::<IndexV1>(&config.serialize(&index).unwrap());
1010            prop_assert!(legacy.is_err());
1011        }
1012
1013        /// Property: [`IndexV2`] cannot be deserialized from [`Index`].
1014        ///
1015        /// # Failure cases
1016        /// 1. Empty [`Index`]
1017        ///     - [`ShredIndexV2`] deserialization should fail due to missing `num_shreds` (not enough bytes).
1018        /// 2. Non-empty [`Index`]
1019        ///     - Encoded length of [`ShredIndex::index`] (`BTreeSet<u64>`) will be relative to a sequence of `u64`,
1020        ///       resulting in trailing bytes when deserialized into sequence of `u8`.
1021        #[test]
1022        fn test_legacy_collision_inverse(
1023            coding_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64),
1024            data_indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64),
1025            slot in 0..u64::MAX
1026        ) {
1027            let index = IndexV1 {
1028                coding: coding_indices.into_iter().collect(),
1029                data: data_indices.into_iter().collect(),
1030                slot,
1031            };
1032            let config = bincode::DefaultOptions::new()
1033                .with_fixint_encoding()
1034                .reject_trailing_bytes();
1035            let v2 = config.deserialize::<IndexV2>(&config.serialize(&index).unwrap());
1036            prop_assert!(v2.is_err());
1037        }
1038
1039        // Property: range queries should return correct indices
1040        #[test]
1041        fn range_query_correctness(
1042            indices in rand_range(0..MAX_DATA_SHREDS_PER_SLOT as u64),
1043        ) {
1044            let mut index = ShredIndexV2::default();
1045
1046            for idx in indices.clone() {
1047                index.insert(idx);
1048            }
1049
1050            assert_eq!(
1051                index.range(indices.clone()).collect::<Vec<_>>(),
1052                indices.into_iter().collect::<Vec<_>>()
1053            );
1054        }
1055    }
1056
1057    #[test]
1058    fn test_shred_index_v2_range_bounds() {
1059        let mut index = ShredIndexV2::default();
1060
1061        index.insert(10);
1062        index.insert(20);
1063        index.insert(30);
1064        index.insert(40);
1065
1066        use std::ops::Bound::*;
1067
1068        // Test all combinations of bounds
1069        let test_cases = [
1070            // (start_bound, end_bound, expected_result)
1071            (Included(10), Included(30), vec![10, 20, 30]),
1072            (Included(10), Excluded(30), vec![10, 20]),
1073            (Excluded(10), Included(30), vec![20, 30]),
1074            (Excluded(10), Excluded(30), vec![20]),
1075            // Unbounded start
1076            (Unbounded, Included(20), vec![10, 20]),
1077            (Unbounded, Excluded(20), vec![10]),
1078            // Unbounded end
1079            (Included(30), Unbounded, vec![30, 40]),
1080            (Excluded(30), Unbounded, vec![40]),
1081            // Both Unbounded
1082            (Unbounded, Unbounded, vec![10, 20, 30, 40]),
1083        ];
1084
1085        for (start_bound, end_bound, expected) in test_cases {
1086            let result: Vec<_> = index.range((start_bound, end_bound)).collect();
1087            assert_eq!(
1088                result, expected,
1089                "Failed for bounds: start={:?}, end={:?}",
1090                start_bound, end_bound
1091            );
1092        }
1093    }
1094
1095    #[test]
1096    fn test_shred_index_v2_boundary_conditions() {
1097        let mut index = ShredIndexV2::default();
1098
1099        // First possible index
1100        index.insert(0);
1101        // Last index in first word (bits 0-7)
1102        index.insert(7);
1103        // First index in second word (bits 8-15)
1104        index.insert(8);
1105        // Last index in second word
1106        index.insert(15);
1107        // Last valid index
1108        index.insert(MAX_DATA_SHREDS_PER_SLOT as u64 - 1);
1109        // Should be ignored (too large)
1110        index.insert(MAX_DATA_SHREDS_PER_SLOT as u64);
1111
1112        // Verify contents
1113        assert!(index.contains(0));
1114        assert!(index.contains(7));
1115        assert!(index.contains(8));
1116        assert!(index.contains(15));
1117        assert!(index.contains(MAX_DATA_SHREDS_PER_SLOT as u64 - 1));
1118        assert!(!index.contains(MAX_DATA_SHREDS_PER_SLOT as u64));
1119
1120        // Cross-word boundary
1121        assert_eq!(index.range(6..10).collect::<Vec<_>>(), vec![7, 8]);
1122        // Full first word
1123        assert_eq!(index.range(0..8).collect::<Vec<_>>(), vec![0, 7]);
1124        // Full second word
1125        assert_eq!(index.range(8..16).collect::<Vec<_>>(), vec![8, 15]);
1126
1127        // Empty ranges
1128        assert_eq!(index.range(0..0).count(), 0);
1129        assert_eq!(index.range(1..1).count(), 0);
1130
1131        // Test range that exceeds max
1132        let oversized_range = index.range(0..MAX_DATA_SHREDS_PER_SLOT as u64 + 1);
1133        assert_eq!(oversized_range.count(), 5);
1134        assert_eq!(index.num_shreds(), 5);
1135
1136        index.remove(0);
1137        assert!(!index.contains(0));
1138        index.remove(7);
1139        assert!(!index.contains(7));
1140        index.remove(8);
1141        assert!(!index.contains(8));
1142        index.remove(15);
1143        assert!(!index.contains(15));
1144        index.remove(MAX_DATA_SHREDS_PER_SLOT as u64 - 1);
1145        assert!(!index.contains(MAX_DATA_SHREDS_PER_SLOT as u64 - 1));
1146
1147        assert_eq!(index.num_shreds(), 0);
1148    }
1149
1150    #[test]
1151    fn test_connected_flags_compatibility() {
1152        // Define a couple structs with bool and ConnectedFlags to illustrate
1153        // that that ConnectedFlags can be deserialized into a bool if the
1154        // PARENT_CONNECTED bit is NOT set
1155        #[derive(Debug, Deserialize, PartialEq, Serialize)]
1156        struct WithBool {
1157            slot: Slot,
1158            connected: bool,
1159        }
1160        #[derive(Debug, Deserialize, PartialEq, Serialize)]
1161        struct WithFlags {
1162            slot: Slot,
1163            connected: ConnectedFlags,
1164        }
1165
1166        let slot = 3;
1167        let mut with_bool = WithBool {
1168            slot,
1169            connected: false,
1170        };
1171        let mut with_flags = WithFlags {
1172            slot,
1173            connected: ConnectedFlags::default(),
1174        };
1175
1176        // Confirm that serialized byte arrays are same length
1177        assert_eq!(
1178            bincode::serialized_size(&with_bool).unwrap(),
1179            bincode::serialized_size(&with_flags).unwrap()
1180        );
1181
1182        // Confirm that connected=false equivalent to ConnectedFlags::default()
1183        assert_eq!(
1184            bincode::serialize(&with_bool).unwrap(),
1185            bincode::serialize(&with_flags).unwrap()
1186        );
1187
1188        // Set connected in WithBool and confirm inequality
1189        with_bool.connected = true;
1190        assert_ne!(
1191            bincode::serialize(&with_bool).unwrap(),
1192            bincode::serialize(&with_flags).unwrap()
1193        );
1194
1195        // Set connected in WithFlags and confirm equality regained
1196        with_flags.connected.set(ConnectedFlags::CONNECTED, true);
1197        assert_eq!(
1198            bincode::serialize(&with_bool).unwrap(),
1199            bincode::serialize(&with_flags).unwrap()
1200        );
1201
1202        // Dserializing WithBool into WithFlags succeeds
1203        assert_eq!(
1204            with_flags,
1205            bincode::deserialize::<WithFlags>(&bincode::serialize(&with_bool).unwrap()).unwrap()
1206        );
1207
1208        // Deserializing WithFlags into WithBool succeeds
1209        assert_eq!(
1210            with_bool,
1211            bincode::deserialize::<WithBool>(&bincode::serialize(&with_flags).unwrap()).unwrap()
1212        );
1213
1214        // Deserializing WithFlags with extra bit set into WithBool fails
1215        with_flags
1216            .connected
1217            .set(ConnectedFlags::PARENT_CONNECTED, true);
1218        assert!(
1219            bincode::deserialize::<WithBool>(&bincode::serialize(&with_flags).unwrap()).is_err()
1220        );
1221    }
1222
1223    #[test]
1224    fn test_clear_unconfirmed_slot() {
1225        let mut slot_meta = SlotMeta::new_orphan(5);
1226        slot_meta.consumed = 5;
1227        slot_meta.received = 5;
1228        slot_meta.next_slots = vec![6, 7];
1229        slot_meta.clear_unconfirmed_slot();
1230
1231        let mut expected = SlotMeta::new_orphan(5);
1232        expected.next_slots = vec![6, 7];
1233        assert_eq!(slot_meta, expected);
1234    }
1235
1236    // `PerfSampleV2` should contain `PerfSampleV1` as a prefix, in order for the column to be
1237    // backward and forward compatible.
1238    #[test]
1239    fn perf_sample_v1_is_prefix_of_perf_sample_v2() {
1240        let v2 = PerfSampleV2 {
1241            num_transactions: 4190143848,
1242            num_slots: 3607325588,
1243            sample_period_secs: 31263,
1244            num_non_vote_transactions: 4056116066,
1245        };
1246
1247        let v2_bytes = bincode::serialize(&v2).expect("`PerfSampleV2` can be serialized");
1248
1249        let actual: PerfSampleV1 = bincode::deserialize(&v2_bytes)
1250            .expect("Bytes encoded as `PerfSampleV2` can be decoded as `PerfSampleV1`");
1251        let expected = PerfSampleV1 {
1252            num_transactions: v2.num_transactions,
1253            num_slots: v2.num_slots,
1254            sample_period_secs: v2.sample_period_secs,
1255        };
1256
1257        assert_eq!(actual, expected);
1258    }
1259
1260    #[test]
1261    fn test_erasure_meta_transition() {
1262        #[derive(Debug, Deserialize, PartialEq, Serialize)]
1263        struct OldErasureMeta {
1264            set_index: u64,
1265            first_coding_index: u64,
1266            #[serde(rename = "size")]
1267            __unused_size: usize,
1268            config: ErasureConfig,
1269        }
1270
1271        let set_index = 64;
1272        let erasure_config = ErasureConfig {
1273            num_data: 8,
1274            num_coding: 16,
1275        };
1276        let mut old_erasure_meta = OldErasureMeta {
1277            set_index,
1278            first_coding_index: set_index,
1279            __unused_size: 0,
1280            config: erasure_config,
1281        };
1282        let mut new_erasure_meta = ErasureMeta {
1283            fec_set_index: u32::try_from(set_index).unwrap(),
1284            first_coding_index: set_index,
1285            first_received_coding_index: 0,
1286            config: erasure_config,
1287        };
1288
1289        assert_eq!(
1290            bincode::serialized_size(&old_erasure_meta).unwrap(),
1291            bincode::serialized_size(&new_erasure_meta).unwrap(),
1292        );
1293
1294        assert_eq!(
1295            bincode::deserialize::<ErasureMeta>(&bincode::serialize(&old_erasure_meta).unwrap())
1296                .unwrap(),
1297            new_erasure_meta
1298        );
1299
1300        new_erasure_meta.first_received_coding_index = u64::from(u32::MAX);
1301        old_erasure_meta.__unused_size = usize::try_from(u32::MAX).unwrap();
1302
1303        assert_eq!(
1304            bincode::deserialize::<OldErasureMeta>(&bincode::serialize(&new_erasure_meta).unwrap())
1305                .unwrap(),
1306            old_erasure_meta
1307        );
1308    }
1309}