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 pub struct ConnectedFlags:u8 {
22 const CONNECTED = 0b0000_0001;
44 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)]
56pub struct SlotMeta {
58 pub slot: Slot,
61 pub consumed: u64,
65 pub received: u64,
69 pub first_shred_timestamp: u64,
71 #[serde(with = "serde_compat")]
74 pub last_index: Option<u64>,
75 #[serde(with = "serde_compat")]
78 pub parent_slot: Option<Slot>,
79 pub next_slots: Vec<Slot>,
82 pub connected_flags: ConnectedFlags,
84 pub completed_data_indexes: BTreeSet<u32>,
87}
88
89mod 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;
112pub type IndexFallback = IndexV1;
116pub type ShredIndexFallback = ShredIndexV1;
117
118#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
119pub 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 index: BTreeSet<u64>,
157}
158
159#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
160pub struct ErasureMeta {
162 #[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: u64,
170 first_received_coding_index: u64,
172 config: ErasureConfig,
174}
175
176mod serde_compat_cast {
179 use super::*;
180
181 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 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 merkle_root: Option<Hash>,
219 first_received_shred_index: u32,
221 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#[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#[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 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 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 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 pub(crate) fn is_orphan(&self) -> bool {
590 self.parent_slot.is_none()
591 }
592
593 pub fn is_connected(&self) -> bool {
595 self.connected_flags.contains(ConnectedFlags::CONNECTED)
596 }
597
598 pub fn set_connected(&mut self) {
600 assert!(self.is_parent_connected());
601 self.connected_flags.set(ConnectedFlags::CONNECTED, true);
602 }
603
604 pub fn is_parent_connected(&self) -> bool {
606 self.connected_flags
607 .contains(ConnectedFlags::PARENT_CONNECTED)
608 }
609
610 pub fn set_parent_connected(&mut self) -> bool {
614 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 #[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 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, 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 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 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 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; }
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 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#[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#[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#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
831pub struct PerfSampleV2 {
832 pub num_transactions: u64,
834 pub num_slots: u64,
835 pub sample_period_secs: u16,
836
837 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 , None );
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 fn rand_range(range: Range<u64>) -> impl Strategy<Value = Range<u64>> {
948 (range.clone(), range).prop_map(
949 |(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 #[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 #[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 #[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 let test_cases = [
1070 (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, Included(20), vec![10, 20]),
1077 (Unbounded, Excluded(20), vec![10]),
1078 (Included(30), Unbounded, vec![30, 40]),
1080 (Excluded(30), Unbounded, vec![40]),
1081 (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 index.insert(0);
1101 index.insert(7);
1103 index.insert(8);
1105 index.insert(15);
1107 index.insert(MAX_DATA_SHREDS_PER_SLOT as u64 - 1);
1109 index.insert(MAX_DATA_SHREDS_PER_SLOT as u64);
1111
1112 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 assert_eq!(index.range(6..10).collect::<Vec<_>>(), vec![7, 8]);
1122 assert_eq!(index.range(0..8).collect::<Vec<_>>(), vec![0, 7]);
1124 assert_eq!(index.range(8..16).collect::<Vec<_>>(), vec![8, 15]);
1126
1127 assert_eq!(index.range(0..0).count(), 0);
1129 assert_eq!(index.range(1..1).count(), 0);
1130
1131 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 #[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 assert_eq!(
1178 bincode::serialized_size(&with_bool).unwrap(),
1179 bincode::serialized_size(&with_flags).unwrap()
1180 );
1181
1182 assert_eq!(
1184 bincode::serialize(&with_bool).unwrap(),
1185 bincode::serialize(&with_flags).unwrap()
1186 );
1187
1188 with_bool.connected = true;
1190 assert_ne!(
1191 bincode::serialize(&with_bool).unwrap(),
1192 bincode::serialize(&with_flags).unwrap()
1193 );
1194
1195 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 assert_eq!(
1204 with_flags,
1205 bincode::deserialize::<WithFlags>(&bincode::serialize(&with_bool).unwrap()).unwrap()
1206 );
1207
1208 assert_eq!(
1210 with_bool,
1211 bincode::deserialize::<WithBool>(&bincode::serialize(&with_flags).unwrap()).unwrap()
1212 );
1213
1214 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 #[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}