2019-06-20 18:27:41 -07:00
|
|
|
use crate::erasure::CodingHeader;
|
2019-05-10 08:33:58 -07:00
|
|
|
use solana_metrics::datapoint;
|
2019-06-20 18:27:41 -07:00
|
|
|
use std::{collections::BTreeMap, ops::RangeBounds};
|
2019-04-11 14:14:57 -07:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
|
|
|
|
// The Meta column family
|
|
|
|
pub struct SlotMeta {
|
|
|
|
// The number of slots above the root (the genesis block). The first
|
|
|
|
// slot has slot 0.
|
|
|
|
pub slot: u64,
|
|
|
|
// The total number of consecutive blobs starting from index 0
|
|
|
|
// we have received for this slot.
|
|
|
|
pub consumed: u64,
|
|
|
|
// The index *plus one* of the highest blob received for this slot. Useful
|
|
|
|
// for checking if the slot has received any blobs yet, and to calculate the
|
|
|
|
// range where there is one or more holes: `(consumed..received)`.
|
|
|
|
pub received: u64,
|
|
|
|
// The index of the blob that is flagged as the last blob for this slot.
|
|
|
|
pub last_index: u64,
|
|
|
|
// The slot height of the block this one derives from.
|
|
|
|
pub parent_slot: u64,
|
|
|
|
// The list of slot heights, each of which contains a block that derives
|
|
|
|
// from this one.
|
|
|
|
pub next_slots: Vec<u64>,
|
|
|
|
// True if this slot is full (consumed == last_index + 1) and if every
|
|
|
|
// slot that is a parent of this slot is also connected.
|
|
|
|
pub is_connected: bool,
|
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
|
|
|
|
/// Index recording presence/absence of blobs
|
|
|
|
pub struct Index {
|
|
|
|
pub slot: u64,
|
|
|
|
data: DataIndex,
|
|
|
|
coding: CodingIndex,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
|
|
|
|
pub struct DataIndex {
|
|
|
|
slot: u64,
|
|
|
|
/// Map representing presence/absence of data blobs
|
|
|
|
index: BTreeMap<u64, bool>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
|
|
|
|
/// Erasure coding information
|
|
|
|
pub struct CodingIndex {
|
|
|
|
slot: u64,
|
|
|
|
/// Map from set index, to hashmap from blob index to presence bool
|
|
|
|
index: BTreeMap<u64, BTreeMap<u64, bool>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq)]
|
|
|
|
/// Erasure coding information
|
|
|
|
pub struct ErasureMeta {
|
|
|
|
header: CodingHeader,
|
|
|
|
set_index: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub enum ErasureMetaStatus {
|
|
|
|
CanRecover,
|
|
|
|
DataFull,
|
|
|
|
StillNeed(usize),
|
|
|
|
}
|
|
|
|
|
2019-04-11 14:14:57 -07:00
|
|
|
impl SlotMeta {
|
|
|
|
pub fn is_full(&self) -> bool {
|
|
|
|
// last_index is std::u64::MAX when it has no information about how
|
|
|
|
// many blobs will fill this slot.
|
|
|
|
// Note: A full slot with zero blobs is not possible.
|
|
|
|
if self.last_index == std::u64::MAX {
|
|
|
|
return false;
|
|
|
|
}
|
2019-04-25 00:04:49 -07:00
|
|
|
|
|
|
|
// Should never happen
|
|
|
|
if self.consumed > self.last_index + 1 {
|
2019-05-10 08:33:58 -07:00
|
|
|
datapoint!(
|
|
|
|
"blocktree_error",
|
|
|
|
(
|
|
|
|
"error",
|
|
|
|
format!(
|
|
|
|
"Observed a slot meta with consumed: {} > meta.last_index + 1: {}",
|
|
|
|
self.consumed,
|
|
|
|
self.last_index + 1
|
|
|
|
),
|
|
|
|
String
|
|
|
|
)
|
2019-04-25 00:04:49 -07:00
|
|
|
);
|
|
|
|
}
|
2019-04-11 14:14:57 -07:00
|
|
|
|
|
|
|
self.consumed == self.last_index + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_parent_set(&self) -> bool {
|
|
|
|
self.parent_slot != std::u64::MAX
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(in crate::blocktree) fn new(slot: u64, parent_slot: u64) -> Self {
|
|
|
|
SlotMeta {
|
|
|
|
slot,
|
|
|
|
consumed: 0,
|
|
|
|
received: 0,
|
|
|
|
parent_slot,
|
|
|
|
next_slots: vec![],
|
|
|
|
is_connected: slot == 0,
|
|
|
|
last_index: std::u64::MAX,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
impl Index {
|
|
|
|
pub(in crate::blocktree) fn new(slot: u64) -> Self {
|
|
|
|
Index {
|
|
|
|
slot,
|
|
|
|
data: DataIndex::default(),
|
|
|
|
coding: CodingIndex::default(),
|
2019-04-18 21:56:43 -07:00
|
|
|
}
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn data(&self) -> &DataIndex {
|
|
|
|
&self.data
|
2019-05-02 17:04:40 -07:00
|
|
|
}
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn coding(&self) -> &CodingIndex {
|
|
|
|
&self.coding
|
2019-05-02 17:04:40 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn data_mut(&mut self) -> &mut DataIndex {
|
|
|
|
&mut self.data
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn coding_mut(&mut self) -> &mut CodingIndex {
|
|
|
|
&mut self.coding
|
2019-04-19 20:22:51 -07:00
|
|
|
}
|
2019-06-20 18:27:41 -07:00
|
|
|
}
|
2019-04-19 20:22:51 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
impl CodingIndex {
|
|
|
|
pub fn is_set_present(&self, set_index: u64) -> bool {
|
|
|
|
self.index.contains_key(&set_index)
|
2019-04-19 20:22:51 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn present_in_set(&self, set_index: u64) -> usize {
|
|
|
|
match self.index.get(&set_index) {
|
|
|
|
Some(map) => map.values().filter(|presence| **presence).count(),
|
|
|
|
None => 0,
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn is_present(&self, set_index: u64, blob_index: u64) -> bool {
|
|
|
|
match self.index.get(&set_index) {
|
|
|
|
Some(map) => *map.get(&blob_index).unwrap_or(&false),
|
|
|
|
None => false,
|
2019-04-18 21:56:43 -07:00
|
|
|
}
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn set_present(&mut self, set_index: u64, blob_index: u64, present: bool) {
|
|
|
|
let set_map = self
|
|
|
|
.index
|
|
|
|
.entry(set_index)
|
|
|
|
.or_insert_with(BTreeMap::default);
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
set_map.insert(blob_index, present);
|
2019-05-02 17:04:40 -07:00
|
|
|
}
|
2019-06-20 18:27:41 -07:00
|
|
|
}
|
2019-05-02 17:04:40 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
impl DataIndex {
|
|
|
|
pub fn present_in_bounds(&self, bounds: impl RangeBounds<u64>) -> usize {
|
|
|
|
self.index
|
|
|
|
.range(bounds)
|
|
|
|
.filter(|(_, presence)| **presence)
|
|
|
|
.count()
|
2019-05-02 17:04:40 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn is_present(&self, index: u64) -> bool {
|
|
|
|
*self.index.get(&index).unwrap_or(&false)
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn set_present(&mut self, index: u64, presence: bool) {
|
|
|
|
self.index.insert(index, presence);
|
2019-04-24 15:53:01 -07:00
|
|
|
}
|
2019-06-20 18:27:41 -07:00
|
|
|
}
|
2019-04-24 15:53:01 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
impl ErasureMeta {
|
|
|
|
pub(in crate::blocktree) fn new(set_index: u64) -> ErasureMeta {
|
|
|
|
ErasureMeta {
|
|
|
|
header: CodingHeader::default(),
|
|
|
|
set_index,
|
|
|
|
}
|
2019-04-24 15:53:01 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn session_info(&self) -> CodingHeader {
|
|
|
|
self.header
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn set_session_info(&mut self, header: CodingHeader) {
|
|
|
|
self.header = header;
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
pub fn status(&self, index: &Index) -> ErasureMetaStatus {
|
|
|
|
use ErasureMetaStatus::*;
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
let start_idx = self.header.start_index;
|
|
|
|
let end_idx = start_idx + self.header.data_count as u64;
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
let num_coding = index.coding().present_in_set(self.header.set_index);
|
|
|
|
let num_data = index.data().present_in_bounds(start_idx..end_idx);
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
assert!(self.header.shard_size != 0);
|
2019-04-24 15:53:01 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
let (data_missing, coding_missing) = (
|
|
|
|
self.header.data_count - num_data,
|
|
|
|
self.header.parity_count - num_coding,
|
|
|
|
);
|
2019-04-24 15:53:01 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
let total_missing = data_missing + coding_missing;
|
2019-04-24 15:53:01 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
if data_missing > 0 && total_missing <= self.header.parity_count {
|
|
|
|
CanRecover
|
|
|
|
} else if data_missing == 0 {
|
|
|
|
DataFull
|
|
|
|
} else {
|
|
|
|
StillNeed(total_missing - self.header.parity_count)
|
|
|
|
}
|
|
|
|
}
|
2019-04-18 21:56:43 -07:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2019-04-14 17:10:30 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
const NUM_DATA: u64 = 7;
|
|
|
|
const NUM_CODING: u64 = 8;
|
2019-04-14 17:10:30 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
fn sample_header() -> CodingHeader {
|
|
|
|
CodingHeader {
|
|
|
|
shard_size: 1,
|
|
|
|
data_count: NUM_DATA as usize,
|
|
|
|
parity_count: NUM_CODING as usize,
|
|
|
|
..CodingHeader::default()
|
|
|
|
}
|
2019-04-14 17:10:30 -07:00
|
|
|
}
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
#[test]
|
|
|
|
fn test_erasure_meta_status() {
|
|
|
|
let set_index = 0;
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
let header = sample_header();
|
|
|
|
let mut e_meta = ErasureMeta::new(set_index);
|
|
|
|
e_meta.set_session_info(header);
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
let mut index = Index::new(0);
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
assert_eq!(e_meta.status(&index), ErasureMetaStatus::StillNeed(7));
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
for i in 0..NUM_DATA {
|
|
|
|
index.data_mut().set_present(i, true);
|
|
|
|
}
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
assert_eq!(e_meta.status(&index), ErasureMetaStatus::DataFull);
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
index.data_mut().set_present(NUM_DATA - 1, false);
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
assert_eq!(e_meta.status(&index), ErasureMetaStatus::StillNeed(1));
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
for i in 0..NUM_DATA - 2 {
|
|
|
|
index.data_mut().set_present(i, false);
|
|
|
|
}
|
2019-04-11 14:14:57 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
assert_eq!(e_meta.status(&index), ErasureMetaStatus::StillNeed(6));
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
for i in 0..NUM_CODING {
|
|
|
|
index.coding_mut().set_present(set_index, i, true);
|
|
|
|
}
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
index.data_mut().set_present(NUM_DATA - 1, false);
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
for i in 0..NUM_DATA - 1 {
|
|
|
|
index.data_mut().set_present(i, true);
|
2019-04-18 21:56:43 -07:00
|
|
|
|
2019-06-20 18:27:41 -07:00
|
|
|
assert_eq!(e_meta.status(&index), ErasureMetaStatus::CanRecover);
|
|
|
|
}
|
2019-04-18 21:56:43 -07:00
|
|
|
}
|
2019-04-11 14:14:57 -07:00
|
|
|
}
|