| use { |
| crate::shred::{Shred, ShredType}, |
| bitflags::bitflags, |
| serde::{Deserialize, Deserializer, Serialize, Serializer}, |
| solana_sdk::{ |
| clock::{Slot, UnixTimestamp}, |
| hash::Hash, |
| }, |
| std::{ |
| collections::BTreeSet, |
| ops::{Range, RangeBounds}, |
| }, |
| }; |
|
|
| bitflags! { |
| #[derive(Deserialize, Serialize)] |
| |
| pub struct ConnectedFlags:u8 { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const CONNECTED = 0b0000_0001; |
| |
| const PARENT_CONNECTED = 0b1000_0000; |
| } |
| } |
|
|
| impl Default for ConnectedFlags { |
| fn default() -> Self { |
| ConnectedFlags::empty() |
| } |
| } |
|
|
| #[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] |
| |
| pub struct SlotMeta { |
| |
| |
| pub slot: Slot, |
| |
| |
| pub consumed: u64, |
| |
| |
| |
| pub received: u64, |
| |
| pub first_shred_timestamp: u64, |
| |
| |
| #[serde(with = "serde_compat")] |
| pub last_index: Option<u64>, |
| |
| |
| #[serde(with = "serde_compat")] |
| pub parent_slot: Option<Slot>, |
| |
| |
| pub next_slots: Vec<Slot>, |
| |
| pub connected_flags: ConnectedFlags, |
| |
| pub completed_data_indexes: BTreeSet<u32>, |
| } |
|
|
| |
| |
| mod serde_compat { |
| use super::*; |
|
|
| pub(super) fn serialize<S>(val: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: Serializer, |
| { |
| val.unwrap_or(u64::MAX).serialize(serializer) |
| } |
|
|
| pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error> |
| where |
| D: Deserializer<'de>, |
| { |
| let val = u64::deserialize(deserializer)?; |
| Ok((val != u64::MAX).then(|| val)) |
| } |
| } |
|
|
| #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] |
| |
| pub struct Index { |
| pub slot: Slot, |
| data: ShredIndex, |
| coding: ShredIndex, |
| } |
|
|
| #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] |
| pub struct ShredIndex { |
| |
| index: BTreeSet<u64>, |
| } |
|
|
| #[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)] |
| |
| pub struct ErasureMeta { |
| |
| set_index: u64, |
| |
| first_coding_index: u64, |
| |
| #[serde(rename = "size")] |
| __unused_size: usize, |
| |
| config: ErasureConfig, |
| } |
|
|
| #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] |
| pub(crate) struct ErasureConfig { |
| num_data: usize, |
| num_coding: usize, |
| } |
|
|
| #[derive(Deserialize, Serialize)] |
| pub struct DuplicateSlotProof { |
| #[serde(with = "serde_bytes")] |
| pub shred1: Vec<u8>, |
| #[serde(with = "serde_bytes")] |
| pub shred2: Vec<u8>, |
| } |
|
|
| #[derive(Debug, PartialEq, Eq)] |
| pub enum ErasureMetaStatus { |
| CanRecover, |
| DataFull, |
| StillNeed(usize), |
| } |
|
|
| #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] |
| pub enum FrozenHashVersioned { |
| Current(FrozenHashStatus), |
| } |
|
|
| impl FrozenHashVersioned { |
| pub fn frozen_hash(&self) -> Hash { |
| match self { |
| FrozenHashVersioned::Current(frozen_hash_status) => frozen_hash_status.frozen_hash, |
| } |
| } |
|
|
| pub fn is_duplicate_confirmed(&self) -> bool { |
| match self { |
| FrozenHashVersioned::Current(frozen_hash_status) => { |
| frozen_hash_status.is_duplicate_confirmed |
| } |
| } |
| } |
| } |
|
|
| #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] |
| pub struct FrozenHashStatus { |
| pub frozen_hash: Hash, |
| pub is_duplicate_confirmed: bool, |
| } |
|
|
| impl Index { |
| pub(crate) fn new(slot: Slot) -> Self { |
| Index { |
| slot, |
| data: ShredIndex::default(), |
| coding: ShredIndex::default(), |
| } |
| } |
|
|
| pub fn data(&self) -> &ShredIndex { |
| &self.data |
| } |
| pub fn coding(&self) -> &ShredIndex { |
| &self.coding |
| } |
|
|
| pub(crate) fn data_mut(&mut self) -> &mut ShredIndex { |
| &mut self.data |
| } |
| pub(crate) fn coding_mut(&mut self) -> &mut ShredIndex { |
| &mut self.coding |
| } |
| } |
|
|
| impl ShredIndex { |
| pub fn num_shreds(&self) -> usize { |
| self.index.len() |
| } |
|
|
| pub(crate) fn range<R>(&self, bounds: R) -> impl Iterator<Item = &u64> |
| where |
| R: RangeBounds<u64>, |
| { |
| self.index.range(bounds) |
| } |
|
|
| pub(crate) fn contains(&self, index: u64) -> bool { |
| self.index.contains(&index) |
| } |
|
|
| pub(crate) fn insert(&mut self, index: u64) { |
| self.index.insert(index); |
| } |
| } |
|
|
| impl SlotMeta { |
| pub fn is_full(&self) -> bool { |
| |
| |
| |
| |
| if self |
| .last_index |
| .map(|ix| self.consumed > ix + 1) |
| .unwrap_or_default() |
| { |
| datapoint_error!( |
| "blockstore_error", |
| ( |
| "error", |
| format!( |
| "Observed a slot meta with consumed: {} > meta.last_index + 1: {:?}", |
| self.consumed, |
| self.last_index.map(|ix| ix + 1), |
| ), |
| String |
| ) |
| ); |
| } |
|
|
| Some(self.consumed) == self.last_index.map(|ix| ix + 1) |
| } |
|
|
| pub fn is_connected(&self) -> bool { |
| self.connected_flags.contains(ConnectedFlags::CONNECTED) |
| } |
|
|
| pub fn set_connected(&mut self) { |
| self.connected_flags.set(ConnectedFlags::CONNECTED, true); |
| } |
|
|
| |
| pub fn unset_parent(&mut self) { |
| self.parent_slot = None; |
| } |
|
|
| pub fn clear_unconfirmed_slot(&mut self) { |
| let old = std::mem::replace(self, SlotMeta::new_orphan(self.slot)); |
| self.next_slots = old.next_slots; |
| } |
|
|
| pub(crate) fn new(slot: Slot, parent_slot: Option<Slot>) -> Self { |
| let connected_flags = if slot == 0 { |
| |
| |
| ConnectedFlags::CONNECTED |
| } else { |
| ConnectedFlags::default() |
| }; |
| SlotMeta { |
| slot, |
| parent_slot, |
| connected_flags, |
| ..SlotMeta::default() |
| } |
| } |
|
|
| pub(crate) fn new_orphan(slot: Slot) -> Self { |
| Self::new(slot, None) |
| } |
| } |
|
|
| impl ErasureMeta { |
| pub(crate) fn from_coding_shred(shred: &Shred) -> Option<Self> { |
| match shred.shred_type() { |
| ShredType::Data => None, |
| ShredType::Code => { |
| let config = ErasureConfig { |
| num_data: usize::from(shred.num_data_shreds().ok()?), |
| num_coding: usize::from(shred.num_coding_shreds().ok()?), |
| }; |
| let first_coding_index = u64::from(shred.first_coding_index()?); |
| let erasure_meta = ErasureMeta { |
| set_index: u64::from(shred.fec_set_index()), |
| config, |
| first_coding_index, |
| __unused_size: 0, |
| }; |
| Some(erasure_meta) |
| } |
| } |
| } |
|
|
| |
| |
| pub(crate) fn check_coding_shred(&self, shred: &Shred) -> bool { |
| let mut other = match Self::from_coding_shred(shred) { |
| Some(erasure_meta) => erasure_meta, |
| None => return false, |
| }; |
| other.__unused_size = self.__unused_size; |
| self == &other |
| } |
|
|
| pub(crate) fn config(&self) -> ErasureConfig { |
| self.config |
| } |
|
|
| pub(crate) fn data_shreds_indices(&self) -> Range<u64> { |
| let num_data = self.config.num_data as u64; |
| self.set_index..self.set_index + num_data |
| } |
|
|
| pub(crate) fn coding_shreds_indices(&self) -> Range<u64> { |
| let num_coding = self.config.num_coding as u64; |
| self.first_coding_index..self.first_coding_index + num_coding |
| } |
|
|
| pub(crate) fn status(&self, index: &Index) -> ErasureMetaStatus { |
| use ErasureMetaStatus::*; |
|
|
| let num_coding = index.coding().range(self.coding_shreds_indices()).count(); |
| let num_data = index.data().range(self.data_shreds_indices()).count(); |
|
|
| let (data_missing, num_needed) = ( |
| self.config.num_data.saturating_sub(num_data), |
| self.config.num_data.saturating_sub(num_data + num_coding), |
| ); |
|
|
| if data_missing == 0 { |
| DataFull |
| } else if num_needed == 0 { |
| CanRecover |
| } else { |
| StillNeed(num_needed) |
| } |
| } |
| } |
|
|
| impl DuplicateSlotProof { |
| pub(crate) fn new(shred1: Vec<u8>, shred2: Vec<u8>) -> Self { |
| DuplicateSlotProof { shred1, shred2 } |
| } |
| } |
|
|
| #[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)] |
| pub struct TransactionStatusIndexMeta { |
| pub max_slot: Slot, |
| pub frozen: bool, |
| } |
|
|
| #[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)] |
| pub struct AddressSignatureMeta { |
| pub writeable: bool, |
| } |
|
|
| #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] |
| pub struct PerfSample { |
| pub num_transactions: u64, |
| pub num_slots: u64, |
| pub sample_period_secs: u16, |
| } |
|
|
| #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] |
| pub struct ProgramCost { |
| pub cost: u64, |
| } |
|
|
| #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] |
| pub struct OptimisticSlotMetaV0 { |
| pub hash: Hash, |
| pub timestamp: UnixTimestamp, |
| } |
|
|
| #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] |
| pub enum OptimisticSlotMetaVersioned { |
| V0(OptimisticSlotMetaV0), |
| } |
|
|
| impl OptimisticSlotMetaVersioned { |
| pub fn new(hash: Hash, timestamp: UnixTimestamp) -> Self { |
| OptimisticSlotMetaVersioned::V0(OptimisticSlotMetaV0 { hash, timestamp }) |
| } |
|
|
| pub fn hash(&self) -> Hash { |
| match self { |
| OptimisticSlotMetaVersioned::V0(meta) => meta.hash, |
| } |
| } |
|
|
| pub fn timestamp(&self) -> UnixTimestamp { |
| match self { |
| OptimisticSlotMetaVersioned::V0(meta) => meta.timestamp, |
| } |
| } |
| } |
| #[cfg(test)] |
| mod test { |
| use { |
| super::*, |
| rand::{seq::SliceRandom, thread_rng}, |
| }; |
|
|
| #[test] |
| fn test_erasure_meta_status() { |
| use ErasureMetaStatus::*; |
|
|
| let set_index = 0; |
| let erasure_config = ErasureConfig { |
| num_data: 8, |
| num_coding: 16, |
| }; |
| let e_meta = ErasureMeta { |
| set_index, |
| first_coding_index: set_index, |
| config: erasure_config, |
| __unused_size: 0, |
| }; |
| let mut rng = thread_rng(); |
| let mut index = Index::new(0); |
|
|
| let data_indexes = 0..erasure_config.num_data as u64; |
| let coding_indexes = 0..erasure_config.num_coding as u64; |
|
|
| assert_eq!(e_meta.status(&index), StillNeed(erasure_config.num_data)); |
|
|
| for ix in data_indexes.clone() { |
| index.data_mut().insert(ix); |
| } |
|
|
| assert_eq!(e_meta.status(&index), DataFull); |
|
|
| for ix in coding_indexes.clone() { |
| index.coding_mut().insert(ix); |
| } |
|
|
| for &idx in data_indexes |
| .clone() |
| .collect::<Vec<_>>() |
| .choose_multiple(&mut rng, erasure_config.num_data) |
| { |
| index.data_mut().index.remove(&idx); |
|
|
| assert_eq!(e_meta.status(&index), CanRecover); |
| } |
|
|
| for ix in data_indexes { |
| index.data_mut().insert(ix); |
| } |
|
|
| for &idx in coding_indexes |
| .collect::<Vec<_>>() |
| .choose_multiple(&mut rng, erasure_config.num_coding) |
| { |
| index.coding_mut().index.remove(&idx); |
|
|
| assert_eq!(e_meta.status(&index), DataFull); |
| } |
| } |
|
|
| #[test] |
| fn test_connected_flags_compatibility() { |
| |
| |
| |
| #[derive(Debug, Deserialize, PartialEq, Serialize)] |
| struct WithBool { |
| slot: Slot, |
| connected: bool, |
| } |
| #[derive(Debug, Deserialize, PartialEq, Serialize)] |
| struct WithFlags { |
| slot: Slot, |
| connected: ConnectedFlags, |
| } |
|
|
| let slot = 3; |
| let mut with_bool = WithBool { |
| slot, |
| connected: false, |
| }; |
| let mut with_flags = WithFlags { |
| slot, |
| connected: ConnectedFlags::default(), |
| }; |
|
|
| |
| assert_eq!( |
| bincode::serialized_size(&with_bool).unwrap(), |
| bincode::serialized_size(&with_flags).unwrap() |
| ); |
|
|
| |
| assert_eq!( |
| bincode::serialize(&with_bool).unwrap(), |
| bincode::serialize(&with_flags).unwrap() |
| ); |
|
|
| |
| with_bool.connected = true; |
| assert_ne!( |
| bincode::serialize(&with_bool).unwrap(), |
| bincode::serialize(&with_flags).unwrap() |
| ); |
|
|
| |
| with_flags.connected.set(ConnectedFlags::CONNECTED, true); |
| assert_eq!( |
| bincode::serialize(&with_bool).unwrap(), |
| bincode::serialize(&with_flags).unwrap() |
| ); |
|
|
| |
| assert_eq!( |
| with_flags, |
| bincode::deserialize::<WithFlags>(&bincode::serialize(&with_bool).unwrap()).unwrap() |
| ); |
|
|
| |
| assert_eq!( |
| with_bool, |
| bincode::deserialize::<WithBool>(&bincode::serialize(&with_flags).unwrap()).unwrap() |
| ); |
|
|
| |
| with_flags |
| .connected |
| .set(ConnectedFlags::PARENT_CONNECTED, true); |
| assert!( |
| bincode::deserialize::<WithBool>(&bincode::serialize(&with_flags).unwrap()).is_err() |
| ); |
| } |
|
|
| #[test] |
| fn test_clear_unconfirmed_slot() { |
| let mut slot_meta = SlotMeta::new_orphan(5); |
| slot_meta.consumed = 5; |
| slot_meta.received = 5; |
| slot_meta.next_slots = vec![6, 7]; |
| slot_meta.clear_unconfirmed_slot(); |
|
|
| let mut expected = SlotMeta::new_orphan(5); |
| expected.next_slots = vec![6, 7]; |
| assert_eq!(slot_meta, expected); |
| } |
| } |
|
|