2019-02-07 15:10:54 -08:00
|
|
|
//! The `repair_service` module implements the tools necessary to generate a thread which
|
2019-11-14 11:49:31 -08:00
|
|
|
//! regularly finds missing shreds in the ledger and sends repair requests for those shreds
|
2020-01-31 14:23:51 -08:00
|
|
|
use crate::{
|
|
|
|
cluster_info::ClusterInfo,
|
|
|
|
result::Result,
|
|
|
|
serve_repair::{RepairType, ServeRepair},
|
|
|
|
};
|
2019-11-02 00:38:30 -07:00
|
|
|
use solana_ledger::{
|
|
|
|
bank_forks::BankForks,
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore::{Blockstore, CompletedSlotsReceiver, SlotMeta},
|
2019-11-02 00:38:30 -07:00
|
|
|
};
|
2020-02-17 12:39:30 -08:00
|
|
|
use solana_sdk::clock::DEFAULT_SLOTS_PER_EPOCH;
|
2019-11-02 00:38:30 -07:00
|
|
|
use solana_sdk::{clock::Slot, epoch_schedule::EpochSchedule, pubkey::Pubkey};
|
2019-10-08 22:34:26 -07:00
|
|
|
use std::{
|
2020-03-05 10:58:00 -08:00
|
|
|
collections::{BTreeSet, HashSet},
|
2020-03-04 18:10:30 -08:00
|
|
|
iter::Iterator,
|
2019-10-08 22:34:26 -07:00
|
|
|
net::UdpSocket,
|
2020-02-17 12:39:30 -08:00
|
|
|
ops::Bound::{Included, Unbounded},
|
2019-10-08 22:34:26 -07:00
|
|
|
sync::atomic::{AtomicBool, Ordering},
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
thread::sleep,
|
|
|
|
thread::{self, Builder, JoinHandle},
|
|
|
|
time::Duration,
|
|
|
|
};
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2019-11-11 13:12:22 -08:00
|
|
|
pub const MAX_REPAIR_LENGTH: usize = 512;
|
|
|
|
pub const REPAIR_MS: u64 = 100;
|
2019-04-06 19:41:22 -07:00
|
|
|
pub const MAX_ORPHANS: usize = 5;
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2020-02-17 12:39:30 -08:00
|
|
|
const MAX_COMPLETED_SLOT_CACHE_LEN: usize = 256;
|
|
|
|
const COMPLETED_SLOT_CACHE_FLUSH_TRIGGER: usize = 512;
|
|
|
|
|
2019-05-09 14:10:04 -07:00
|
|
|
pub enum RepairStrategy {
|
|
|
|
RepairRange(RepairSlotRange),
|
|
|
|
RepairAll {
|
|
|
|
bank_forks: Arc<RwLock<BankForks>>,
|
|
|
|
completed_slots_receiver: CompletedSlotsReceiver,
|
2019-05-13 15:37:50 -07:00
|
|
|
epoch_schedule: EpochSchedule,
|
2019-05-09 14:10:04 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-02-12 17:43:45 -08:00
|
|
|
pub struct RepairSlotRange {
|
2019-12-05 11:25:13 -08:00
|
|
|
pub start: Slot,
|
|
|
|
pub end: Slot,
|
2019-02-12 17:43:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for RepairSlotRange {
|
|
|
|
fn default() -> Self {
|
|
|
|
RepairSlotRange {
|
|
|
|
start: 0,
|
|
|
|
end: std::u64::MAX,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-07 15:10:54 -08:00
|
|
|
pub struct RepairService {
|
|
|
|
t_repair: JoinHandle<()>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RepairService {
|
2019-05-08 13:50:32 -07:00
|
|
|
pub fn new(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: Arc<Blockstore>,
|
2019-05-24 19:20:09 -07:00
|
|
|
exit: Arc<AtomicBool>,
|
2019-05-08 13:50:32 -07:00
|
|
|
repair_socket: Arc<UdpSocket>,
|
|
|
|
cluster_info: Arc<RwLock<ClusterInfo>>,
|
2019-05-09 14:10:04 -07:00
|
|
|
repair_strategy: RepairStrategy,
|
2019-05-08 13:50:32 -07:00
|
|
|
) -> Self {
|
|
|
|
let t_repair = Builder::new()
|
|
|
|
.name("solana-repair-service".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
Self::run(
|
2020-01-13 14:13:52 -07:00
|
|
|
&blockstore,
|
2019-05-24 19:20:09 -07:00
|
|
|
&exit,
|
2019-05-08 13:50:32 -07:00
|
|
|
&repair_socket,
|
|
|
|
&cluster_info,
|
2019-05-09 14:10:04 -07:00
|
|
|
repair_strategy,
|
2019-05-08 13:50:32 -07:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
2020-02-10 17:00:00 -08:00
|
|
|
RepairService { t_repair }
|
2019-05-08 13:50:32 -07:00
|
|
|
}
|
|
|
|
|
2019-02-07 15:10:54 -08:00
|
|
|
fn run(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: &Arc<Blockstore>,
|
2019-05-24 19:20:09 -07:00
|
|
|
exit: &Arc<AtomicBool>,
|
2019-02-07 15:10:54 -08:00
|
|
|
repair_socket: &Arc<UdpSocket>,
|
|
|
|
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
2019-05-09 14:10:04 -07:00
|
|
|
repair_strategy: RepairStrategy,
|
2019-02-07 15:10:54 -08:00
|
|
|
) {
|
2020-01-31 14:23:51 -08:00
|
|
|
let serve_repair = ServeRepair::new(cluster_info.clone());
|
2019-12-05 11:25:13 -08:00
|
|
|
let mut epoch_slots: BTreeSet<Slot> = BTreeSet::new();
|
2020-02-17 12:39:30 -08:00
|
|
|
let mut old_incomplete_slots: BTreeSet<Slot> = BTreeSet::new();
|
2019-02-07 15:10:54 -08:00
|
|
|
let id = cluster_info.read().unwrap().id();
|
2019-05-13 15:37:50 -07:00
|
|
|
if let RepairStrategy::RepairAll {
|
2019-08-27 15:09:41 -07:00
|
|
|
ref epoch_schedule, ..
|
2019-05-13 15:37:50 -07:00
|
|
|
} = repair_strategy
|
|
|
|
{
|
2020-02-17 12:39:30 -08:00
|
|
|
let current_root = blockstore.last_root();
|
2019-05-13 15:37:50 -07:00
|
|
|
Self::initialize_epoch_slots(
|
|
|
|
id,
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore,
|
2019-05-13 15:37:50 -07:00
|
|
|
&mut epoch_slots,
|
2020-02-17 12:39:30 -08:00
|
|
|
&old_incomplete_slots,
|
2019-05-23 03:50:41 -07:00
|
|
|
current_root,
|
2019-05-13 15:37:50 -07:00
|
|
|
epoch_schedule,
|
|
|
|
cluster_info,
|
|
|
|
);
|
|
|
|
}
|
2019-02-07 15:10:54 -08:00
|
|
|
loop {
|
|
|
|
if exit.load(Ordering::Relaxed) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-04-08 12:46:23 -07:00
|
|
|
let repairs = {
|
2019-05-09 14:10:04 -07:00
|
|
|
match repair_strategy {
|
|
|
|
RepairStrategy::RepairRange(ref repair_slot_range) => {
|
2019-10-21 11:29:37 -06:00
|
|
|
// Strategy used by archivers
|
2019-05-09 14:10:04 -07:00
|
|
|
Self::generate_repairs_in_range(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore,
|
2019-05-09 14:10:04 -07:00
|
|
|
MAX_REPAIR_LENGTH,
|
|
|
|
repair_slot_range,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
RepairStrategy::RepairAll {
|
|
|
|
ref completed_slots_receiver,
|
2019-05-13 15:37:50 -07:00
|
|
|
..
|
2019-05-09 14:10:04 -07:00
|
|
|
} => {
|
2020-01-13 14:13:52 -07:00
|
|
|
let new_root = blockstore.last_root();
|
|
|
|
let lowest_slot = blockstore.lowest_slot();
|
2019-05-09 14:10:04 -07:00
|
|
|
Self::update_epoch_slots(
|
|
|
|
id,
|
2019-05-23 03:50:41 -07:00
|
|
|
new_root,
|
2019-12-05 11:25:13 -08:00
|
|
|
lowest_slot,
|
2019-05-13 15:37:50 -07:00
|
|
|
&mut epoch_slots,
|
2020-02-17 12:39:30 -08:00
|
|
|
&mut old_incomplete_slots,
|
2019-05-09 14:10:04 -07:00
|
|
|
&cluster_info,
|
|
|
|
completed_slots_receiver,
|
|
|
|
);
|
2020-01-13 14:13:52 -07:00
|
|
|
Self::generate_repairs(blockstore, new_root, MAX_REPAIR_LENGTH)
|
2019-05-09 14:10:04 -07:00
|
|
|
}
|
2019-04-08 12:46:23 -07:00
|
|
|
}
|
|
|
|
};
|
2019-02-07 15:10:54 -08:00
|
|
|
|
|
|
|
if let Ok(repairs) = repairs {
|
|
|
|
let reqs: Vec<_> = repairs
|
|
|
|
.into_iter()
|
2019-02-14 00:14:23 -08:00
|
|
|
.filter_map(|repair_request| {
|
2020-01-31 14:23:51 -08:00
|
|
|
serve_repair
|
2019-04-06 19:41:22 -07:00
|
|
|
.repair_request(&repair_request)
|
|
|
|
.map(|result| (result, repair_request))
|
2019-02-07 15:10:54 -08:00
|
|
|
.ok()
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2019-11-11 14:25:25 -08:00
|
|
|
for ((to, req), _) in reqs {
|
2019-02-07 15:10:54 -08:00
|
|
|
repair_socket.send_to(&req, to).unwrap_or_else(|e| {
|
|
|
|
info!("{} repair req send_to({}) error {:?}", id, to, e);
|
|
|
|
0
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sleep(Duration::from_millis(REPAIR_MS));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-09 19:57:51 -07:00
|
|
|
// Generate repairs for all slots `x` in the repair_range.start <= x <= repair_range.end
|
2019-06-17 18:12:13 -07:00
|
|
|
pub fn generate_repairs_in_range(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: &Blockstore,
|
2019-04-08 12:46:23 -07:00
|
|
|
max_repairs: usize,
|
|
|
|
repair_range: &RepairSlotRange,
|
2019-11-19 20:15:37 -08:00
|
|
|
) -> Result<Vec<RepairType>> {
|
2019-11-14 11:49:31 -08:00
|
|
|
// Slot height and shred indexes for shreds we want to repair
|
2019-04-08 12:46:23 -07:00
|
|
|
let mut repairs: Vec<RepairType> = vec![];
|
2019-05-15 11:37:20 -07:00
|
|
|
for slot in repair_range.start..=repair_range.end {
|
|
|
|
if repairs.len() >= max_repairs {
|
2019-04-08 12:46:23 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
let meta = blockstore
|
2019-05-15 11:37:20 -07:00
|
|
|
.meta(slot)
|
|
|
|
.expect("Unable to lookup slot meta")
|
|
|
|
.unwrap_or(SlotMeta {
|
|
|
|
slot,
|
|
|
|
..SlotMeta::default()
|
|
|
|
});
|
2019-04-08 12:46:23 -07:00
|
|
|
|
2019-05-15 11:37:20 -07:00
|
|
|
let new_repairs = Self::generate_repairs_for_slot(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore,
|
2019-05-15 11:37:20 -07:00
|
|
|
slot,
|
|
|
|
&meta,
|
|
|
|
max_repairs - repairs.len(),
|
|
|
|
);
|
|
|
|
repairs.extend(new_repairs);
|
2019-04-08 12:46:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(repairs)
|
|
|
|
}
|
|
|
|
|
2019-05-20 19:04:18 -07:00
|
|
|
fn generate_repairs(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: &Blockstore,
|
2019-12-05 11:25:13 -08:00
|
|
|
root: Slot,
|
2019-05-20 19:04:18 -07:00
|
|
|
max_repairs: usize,
|
2019-11-19 20:15:37 -08:00
|
|
|
) -> Result<Vec<RepairType>> {
|
2019-11-14 11:49:31 -08:00
|
|
|
// Slot height and shred indexes for shreds we want to repair
|
2019-04-08 12:46:23 -07:00
|
|
|
let mut repairs: Vec<RepairType> = vec![];
|
2020-01-13 14:13:52 -07:00
|
|
|
Self::generate_repairs_for_fork(blockstore, &mut repairs, max_repairs, root);
|
2019-04-08 12:46:23 -07:00
|
|
|
|
|
|
|
// TODO: Incorporate gossip to determine priorities for repair?
|
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
// Try to resolve orphans in blockstore
|
2020-03-04 18:10:30 -08:00
|
|
|
let orphans = blockstore.orphans_iterator(root + 1).unwrap();
|
|
|
|
Self::generate_repairs_for_orphans(orphans, &mut repairs);
|
2019-04-08 12:46:23 -07:00
|
|
|
Ok(repairs)
|
|
|
|
}
|
|
|
|
|
2019-04-06 19:41:22 -07:00
|
|
|
fn generate_repairs_for_slot(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: &Blockstore,
|
2019-11-02 00:38:30 -07:00
|
|
|
slot: Slot,
|
2019-03-05 14:18:29 -08:00
|
|
|
slot_meta: &SlotMeta,
|
2019-02-07 15:10:54 -08:00
|
|
|
max_repairs: usize,
|
2019-02-14 00:14:23 -08:00
|
|
|
) -> Vec<RepairType> {
|
2019-03-05 14:18:29 -08:00
|
|
|
if slot_meta.is_full() {
|
2019-02-14 00:14:23 -08:00
|
|
|
vec![]
|
2019-03-05 14:18:29 -08:00
|
|
|
} else if slot_meta.consumed == slot_meta.received {
|
2019-11-14 11:49:31 -08:00
|
|
|
vec![RepairType::HighestShred(slot, slot_meta.received)]
|
2019-02-07 15:10:54 -08:00
|
|
|
} else {
|
2020-01-13 14:13:52 -07:00
|
|
|
let reqs = blockstore.find_missing_data_indexes(
|
2019-03-05 14:18:29 -08:00
|
|
|
slot,
|
2019-11-07 11:08:09 -08:00
|
|
|
slot_meta.first_shred_timestamp,
|
2019-03-05 14:18:29 -08:00
|
|
|
slot_meta.consumed,
|
|
|
|
slot_meta.received,
|
2019-02-13 13:49:54 -08:00
|
|
|
max_repairs,
|
|
|
|
);
|
2019-02-14 00:14:23 -08:00
|
|
|
reqs.into_iter()
|
2019-11-07 11:08:09 -08:00
|
|
|
.map(|i| RepairType::Shred(slot, i))
|
2019-02-14 00:14:23 -08:00
|
|
|
.collect()
|
2019-02-07 15:10:54 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-04 18:10:30 -08:00
|
|
|
fn generate_repairs_for_orphans(
|
|
|
|
orphans: impl Iterator<Item = u64>,
|
|
|
|
repairs: &mut Vec<RepairType>,
|
|
|
|
) {
|
|
|
|
repairs.extend(orphans.take(MAX_ORPHANS).map(RepairType::Orphan));
|
2019-04-06 19:41:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Repairs any fork starting at the input slot
|
|
|
|
fn generate_repairs_for_fork(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: &Blockstore,
|
2019-04-06 19:41:22 -07:00
|
|
|
repairs: &mut Vec<RepairType>,
|
|
|
|
max_repairs: usize,
|
2019-11-02 00:38:30 -07:00
|
|
|
slot: Slot,
|
2019-04-06 19:41:22 -07:00
|
|
|
) {
|
|
|
|
let mut pending_slots = vec![slot];
|
|
|
|
while repairs.len() < max_repairs && !pending_slots.is_empty() {
|
|
|
|
let slot = pending_slots.pop().unwrap();
|
2020-01-13 14:13:52 -07:00
|
|
|
if let Some(slot_meta) = blockstore.meta(slot).unwrap() {
|
2019-04-06 19:41:22 -07:00
|
|
|
let new_repairs = Self::generate_repairs_for_slot(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore,
|
2019-04-06 19:41:22 -07:00
|
|
|
slot,
|
|
|
|
&slot_meta,
|
2019-02-13 13:49:54 -08:00
|
|
|
max_repairs - repairs.len(),
|
2019-02-14 00:14:23 -08:00
|
|
|
);
|
2019-02-13 13:49:54 -08:00
|
|
|
repairs.extend(new_repairs);
|
2019-04-06 19:41:22 -07:00
|
|
|
let next_slots = slot_meta.next_slots;
|
|
|
|
pending_slots.extend(next_slots);
|
|
|
|
} else {
|
|
|
|
break;
|
2019-02-07 15:10:54 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-08 13:50:32 -07:00
|
|
|
|
2019-05-13 15:37:50 -07:00
|
|
|
fn get_completed_slots_past_root(
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: &Blockstore,
|
2019-12-05 11:25:13 -08:00
|
|
|
slots_in_gossip: &mut BTreeSet<Slot>,
|
|
|
|
root: Slot,
|
2019-05-13 15:37:50 -07:00
|
|
|
epoch_schedule: &EpochSchedule,
|
|
|
|
) {
|
2019-10-08 22:34:26 -07:00
|
|
|
let last_confirmed_epoch = epoch_schedule.get_leader_schedule_epoch(root);
|
2019-05-13 15:37:50 -07:00
|
|
|
let last_epoch_slot = epoch_schedule.get_last_slot_in_epoch(last_confirmed_epoch);
|
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
let meta_iter = blockstore
|
2019-05-13 15:37:50 -07:00
|
|
|
.slot_meta_iterator(root + 1)
|
|
|
|
.expect("Couldn't get db iterator");
|
|
|
|
|
2019-05-15 18:28:23 -07:00
|
|
|
for (current_slot, meta) in meta_iter {
|
|
|
|
if current_slot > last_epoch_slot {
|
|
|
|
break;
|
|
|
|
}
|
2019-05-13 15:37:50 -07:00
|
|
|
if meta.is_full() {
|
|
|
|
slots_in_gossip.insert(current_slot);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn initialize_epoch_slots(
|
2019-05-08 18:51:43 -07:00
|
|
|
id: Pubkey,
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore: &Blockstore,
|
2019-12-05 11:25:13 -08:00
|
|
|
slots_in_gossip: &mut BTreeSet<Slot>,
|
2020-02-17 12:39:30 -08:00
|
|
|
old_incomplete_slots: &BTreeSet<Slot>,
|
2019-12-05 11:25:13 -08:00
|
|
|
root: Slot,
|
2019-05-13 15:37:50 -07:00
|
|
|
epoch_schedule: &EpochSchedule,
|
2019-05-08 18:51:43 -07:00
|
|
|
cluster_info: &RwLock<ClusterInfo>,
|
|
|
|
) {
|
2020-01-13 14:13:52 -07:00
|
|
|
Self::get_completed_slots_past_root(blockstore, slots_in_gossip, root, epoch_schedule);
|
2019-05-13 15:37:50 -07:00
|
|
|
|
|
|
|
// Safe to set into gossip because by this time, the leader schedule cache should
|
2020-01-13 14:13:52 -07:00
|
|
|
// also be updated with the latest root (done in blockstore_processor) and thus
|
2019-11-14 11:49:31 -08:00
|
|
|
// will provide a schedule to window_service for any incoming shreds up to the
|
2019-05-13 15:37:50 -07:00
|
|
|
// last_confirmed_epoch.
|
2019-12-05 11:25:13 -08:00
|
|
|
cluster_info.write().unwrap().push_epoch_slots(
|
|
|
|
id,
|
|
|
|
root,
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.lowest_slot(),
|
2019-12-05 11:25:13 -08:00
|
|
|
slots_in_gossip.clone(),
|
2020-02-17 12:39:30 -08:00
|
|
|
old_incomplete_slots,
|
2019-12-05 11:25:13 -08:00
|
|
|
);
|
2019-05-13 15:37:50 -07:00
|
|
|
}
|
|
|
|
|
2020-02-26 23:11:38 +08:00
|
|
|
// Update the gossiped structure used for the "Repairmen" repair protocol. See docs
|
2019-05-13 15:37:50 -07:00
|
|
|
// for details.
|
|
|
|
fn update_epoch_slots(
|
|
|
|
id: Pubkey,
|
2019-12-05 11:25:13 -08:00
|
|
|
latest_known_root: Slot,
|
|
|
|
lowest_slot: Slot,
|
2020-02-17 12:39:30 -08:00
|
|
|
completed_slot_cache: &mut BTreeSet<Slot>,
|
|
|
|
incomplete_slot_stash: &mut BTreeSet<Slot>,
|
2019-05-13 15:37:50 -07:00
|
|
|
cluster_info: &RwLock<ClusterInfo>,
|
|
|
|
completed_slots_receiver: &CompletedSlotsReceiver,
|
|
|
|
) {
|
2020-02-17 12:39:30 -08:00
|
|
|
let mut should_update = false;
|
2019-05-13 15:37:50 -07:00
|
|
|
while let Ok(completed_slots) = completed_slots_receiver.try_recv() {
|
|
|
|
for slot in completed_slots {
|
2020-02-17 12:39:30 -08:00
|
|
|
let last_slot_in_stash = *incomplete_slot_stash.iter().next_back().unwrap_or(&0);
|
|
|
|
let removed_from_stash = incomplete_slot_stash.remove(&slot);
|
|
|
|
// If the newly completed slot was not being tracked in stash, and is > last
|
|
|
|
// slot being tracked in stash, add it to cache. Also, update gossip
|
|
|
|
if !removed_from_stash && slot >= last_slot_in_stash {
|
|
|
|
should_update |= completed_slot_cache.insert(slot);
|
2019-05-13 15:37:50 -07:00
|
|
|
}
|
2020-02-17 12:39:30 -08:00
|
|
|
// If the slot was removed from stash, update gossip
|
|
|
|
should_update |= removed_from_stash;
|
2019-05-13 15:37:50 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if should_update {
|
2020-02-17 12:39:30 -08:00
|
|
|
if completed_slot_cache.len() >= COMPLETED_SLOT_CACHE_FLUSH_TRIGGER {
|
|
|
|
Self::stash_old_incomplete_slots(completed_slot_cache, incomplete_slot_stash);
|
|
|
|
let lowest_completed_slot_in_cache =
|
|
|
|
*completed_slot_cache.iter().next().unwrap_or(&0);
|
|
|
|
Self::prune_incomplete_slot_stash(
|
|
|
|
incomplete_slot_stash,
|
|
|
|
lowest_completed_slot_in_cache,
|
|
|
|
);
|
2019-05-23 03:50:41 -07:00
|
|
|
}
|
2019-05-25 06:44:40 -07:00
|
|
|
|
2019-05-23 03:50:41 -07:00
|
|
|
cluster_info.write().unwrap().push_epoch_slots(
|
|
|
|
id,
|
|
|
|
latest_known_root,
|
2019-12-05 11:25:13 -08:00
|
|
|
lowest_slot,
|
2020-02-17 12:39:30 -08:00
|
|
|
completed_slot_cache.clone(),
|
|
|
|
incomplete_slot_stash,
|
2019-05-23 03:50:41 -07:00
|
|
|
);
|
2019-05-13 15:37:50 -07:00
|
|
|
}
|
2019-05-08 13:50:32 -07:00
|
|
|
}
|
2019-05-23 03:50:41 -07:00
|
|
|
|
2020-02-17 12:39:30 -08:00
|
|
|
fn stash_old_incomplete_slots(cache: &mut BTreeSet<Slot>, stash: &mut BTreeSet<Slot>) {
|
|
|
|
if cache.len() > MAX_COMPLETED_SLOT_CACHE_LEN {
|
|
|
|
let mut prev = *cache.iter().next().expect("Expected to find some slot");
|
|
|
|
cache.remove(&prev);
|
|
|
|
while cache.len() >= MAX_COMPLETED_SLOT_CACHE_LEN {
|
|
|
|
let next = *cache.iter().next().expect("Expected to find some slot");
|
|
|
|
cache.remove(&next);
|
|
|
|
// Prev slot and next slot are not included in incomplete slot list.
|
|
|
|
(prev + 1..next).for_each(|slot| {
|
|
|
|
stash.insert(slot);
|
|
|
|
});
|
|
|
|
prev = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn prune_incomplete_slot_stash(
|
|
|
|
stash: &mut BTreeSet<Slot>,
|
|
|
|
lowest_completed_slot_in_cache: Slot,
|
|
|
|
) {
|
|
|
|
if let Some(oldest_incomplete_slot) = stash.iter().next() {
|
|
|
|
// Prune old slots
|
|
|
|
// Prune in batches to reduce overhead. Pruning starts when oldest slot is 1.5 epochs
|
|
|
|
// earlier than the new root. But, we prune all the slots that are older than 1 epoch.
|
|
|
|
// So slots in a batch of half epoch are getting pruned
|
|
|
|
if oldest_incomplete_slot + DEFAULT_SLOTS_PER_EPOCH + DEFAULT_SLOTS_PER_EPOCH / 2
|
|
|
|
< lowest_completed_slot_in_cache
|
|
|
|
{
|
|
|
|
let oldest_slot_to_retain =
|
|
|
|
lowest_completed_slot_in_cache.saturating_sub(DEFAULT_SLOTS_PER_EPOCH);
|
|
|
|
*stash = stash
|
|
|
|
.range((Included(&oldest_slot_to_retain), Unbounded))
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
}
|
2019-05-23 03:50:41 -07:00
|
|
|
}
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2020-03-05 10:58:00 -08:00
|
|
|
#[allow(dead_code)]
|
|
|
|
fn find_incomplete_slots(blockstore: &Blockstore, root: Slot) -> HashSet<Slot> {
|
|
|
|
blockstore
|
|
|
|
.live_slots_iterator(root)
|
|
|
|
.filter_map(|(slot, slot_meta)| {
|
|
|
|
if !slot_meta.is_full() {
|
|
|
|
Some(slot)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2019-11-13 11:12:09 -07:00
|
|
|
pub fn join(self) -> thread::Result<()> {
|
2020-02-10 17:00:00 -08:00
|
|
|
self.t_repair.join()
|
2019-02-07 15:10:54 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2019-05-13 15:37:50 -07:00
|
|
|
use crate::cluster_info::Node;
|
2019-09-03 21:32:51 -07:00
|
|
|
use itertools::Itertools;
|
2019-05-13 15:37:50 -07:00
|
|
|
use rand::seq::SliceRandom;
|
|
|
|
use rand::{thread_rng, Rng};
|
2020-01-13 14:13:52 -07:00
|
|
|
use solana_ledger::blockstore::{
|
2019-10-18 10:28:51 -06:00
|
|
|
make_chaining_slot_entries, make_many_slot_entries, make_slot_entries,
|
|
|
|
};
|
|
|
|
use solana_ledger::shred::max_ticks_per_n_shreds;
|
2020-01-13 14:13:52 -07:00
|
|
|
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
2019-05-13 15:37:50 -07:00
|
|
|
use std::thread::Builder;
|
2019-02-07 15:10:54 -08:00
|
|
|
|
|
|
|
#[test]
|
2019-04-06 19:41:22 -07:00
|
|
|
pub fn test_repair_orphan() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-02-07 15:10:54 -08:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2019-04-06 19:41:22 -07:00
|
|
|
// Create some orphan slots
|
2019-09-03 21:32:51 -07:00
|
|
|
let (mut shreds, _) = make_slot_entries(1, 0, 1);
|
|
|
|
let (shreds2, _) = make_slot_entries(5, 2, 1);
|
|
|
|
shreds.extend(shreds2);
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(shreds, None, false).unwrap();
|
2019-02-07 15:10:54 -08:00
|
|
|
assert_eq!(
|
2020-01-13 14:13:52 -07:00
|
|
|
RepairService::generate_repairs(&blockstore, 0, 2).unwrap(),
|
2019-11-14 11:49:31 -08:00
|
|
|
vec![RepairType::HighestShred(0, 0), RepairType::Orphan(2)]
|
2019-02-07 15:10:54 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-02-07 15:10:54 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn test_repair_empty_slot() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-02-07 15:10:54 -08:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2019-09-03 21:32:51 -07:00
|
|
|
let (shreds, _) = make_slot_entries(2, 0, 1);
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2019-11-14 11:49:31 -08:00
|
|
|
// Write this shred to slot 2, should chain to slot 0, which we haven't received
|
|
|
|
// any shreds for
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(shreds, None, false).unwrap();
|
2019-04-06 19:41:22 -07:00
|
|
|
|
2019-02-07 15:10:54 -08:00
|
|
|
// Check that repair tries to patch the empty slot
|
|
|
|
assert_eq!(
|
2020-01-13 14:13:52 -07:00
|
|
|
RepairService::generate_repairs(&blockstore, 0, 2).unwrap(),
|
2019-11-14 11:49:31 -08:00
|
|
|
vec![RepairType::HighestShred(0, 0)]
|
2019-02-07 15:10:54 -08:00
|
|
|
);
|
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-02-07 15:10:54 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn test_generate_repairs() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-02-07 15:10:54 -08:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2019-02-18 18:41:31 -08:00
|
|
|
let nth = 3;
|
2019-02-07 15:10:54 -08:00
|
|
|
let num_slots = 2;
|
|
|
|
|
2019-11-14 11:49:31 -08:00
|
|
|
// Create some shreds
|
2019-09-17 15:11:29 -07:00
|
|
|
let (mut shreds, _) = make_many_slot_entries(0, num_slots as u64, 150 as u64);
|
2019-08-21 15:27:42 -07:00
|
|
|
let num_shreds = shreds.len() as u64;
|
|
|
|
let num_shreds_per_slot = num_shreds / num_slots;
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2019-11-14 11:49:31 -08:00
|
|
|
// write every nth shred
|
2019-08-21 15:27:42 -07:00
|
|
|
let mut shreds_to_write = vec![];
|
|
|
|
let mut missing_indexes_per_slot = vec![];
|
|
|
|
for i in (0..num_shreds).rev() {
|
|
|
|
let index = i % num_shreds_per_slot;
|
|
|
|
if index % nth == 0 {
|
|
|
|
shreds_to_write.insert(0, shreds.remove(i as usize));
|
|
|
|
} else if i < num_shreds_per_slot {
|
|
|
|
missing_indexes_per_slot.insert(0, index);
|
|
|
|
}
|
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore
|
2019-11-14 00:32:07 -08:00
|
|
|
.insert_shreds(shreds_to_write, None, false)
|
|
|
|
.unwrap();
|
2019-11-07 11:08:09 -08:00
|
|
|
// sleep so that the holes are ready for repair
|
|
|
|
sleep(Duration::from_secs(1));
|
2019-02-14 00:14:23 -08:00
|
|
|
let expected: Vec<RepairType> = (0..num_slots)
|
2019-03-05 14:18:29 -08:00
|
|
|
.flat_map(|slot| {
|
2019-02-07 15:10:54 -08:00
|
|
|
missing_indexes_per_slot
|
|
|
|
.iter()
|
2019-11-14 11:49:31 -08:00
|
|
|
.map(move |shred_index| RepairType::Shred(slot as u64, *shred_index))
|
2019-02-07 15:10:54 -08:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
assert_eq!(
|
2020-01-13 14:13:52 -07:00
|
|
|
RepairService::generate_repairs(&blockstore, 0, std::usize::MAX).unwrap(),
|
2019-02-07 15:10:54 -08:00
|
|
|
expected
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
2020-01-13 14:13:52 -07:00
|
|
|
RepairService::generate_repairs(&blockstore, 0, expected.len() - 2).unwrap()[..],
|
2019-02-07 15:10:54 -08:00
|
|
|
expected[0..expected.len() - 2]
|
|
|
|
);
|
2019-02-18 18:41:31 -08:00
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-02-18 18:41:31 -08:00
|
|
|
}
|
2019-02-07 15:10:54 -08:00
|
|
|
|
2019-02-18 18:41:31 -08:00
|
|
|
#[test]
|
|
|
|
pub fn test_generate_highest_repair() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-02-18 18:41:31 -08:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
2019-02-14 00:14:23 -08:00
|
|
|
|
2019-09-03 21:32:51 -07:00
|
|
|
let num_entries_per_slot = 100;
|
2019-02-18 18:41:31 -08:00
|
|
|
|
2019-11-14 11:49:31 -08:00
|
|
|
// Create some shreds
|
2019-09-03 21:32:51 -07:00
|
|
|
let (mut shreds, _) = make_slot_entries(0, 0, num_entries_per_slot as u64);
|
|
|
|
let num_shreds_per_slot = shreds.len() as u64;
|
2019-02-18 18:41:31 -08:00
|
|
|
|
2019-09-03 21:32:51 -07:00
|
|
|
// Remove last shred (which is also last in slot) so that slot is not complete
|
|
|
|
shreds.pop();
|
2019-02-18 18:41:31 -08:00
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(shreds, None, false).unwrap();
|
2019-02-18 18:41:31 -08:00
|
|
|
|
2019-11-14 11:49:31 -08:00
|
|
|
// We didn't get the last shred for this slot, so ask for the highest shred for that slot
|
2019-09-03 21:32:51 -07:00
|
|
|
let expected: Vec<RepairType> =
|
2019-11-14 11:49:31 -08:00
|
|
|
vec![RepairType::HighestShred(0, num_shreds_per_slot - 1)];
|
2019-02-07 15:10:54 -08:00
|
|
|
|
|
|
|
assert_eq!(
|
2020-01-13 14:13:52 -07:00
|
|
|
RepairService::generate_repairs(&blockstore, 0, std::usize::MAX).unwrap(),
|
2019-02-07 15:10:54 -08:00
|
|
|
expected
|
|
|
|
);
|
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-02-07 15:10:54 -08:00
|
|
|
}
|
2019-02-12 17:43:45 -08:00
|
|
|
|
2019-04-08 12:46:23 -07:00
|
|
|
#[test]
|
2019-02-12 17:43:45 -08:00
|
|
|
pub fn test_repair_range() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-05-09 19:57:51 -07:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
2019-05-09 19:57:51 -07:00
|
|
|
|
|
|
|
let slots: Vec<u64> = vec![1, 3, 5, 7, 8];
|
2019-10-09 16:07:18 -07:00
|
|
|
let num_entries_per_slot = max_ticks_per_n_shreds(1) + 1;
|
2019-05-09 19:57:51 -07:00
|
|
|
|
2019-09-03 21:32:51 -07:00
|
|
|
let shreds = make_chaining_slot_entries(&slots, num_entries_per_slot);
|
2019-08-26 18:27:45 -07:00
|
|
|
for (mut slot_shreds, _) in shreds.into_iter() {
|
|
|
|
slot_shreds.remove(0);
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(slot_shreds, None, false).unwrap();
|
2019-05-09 19:57:51 -07:00
|
|
|
}
|
2019-11-07 11:08:09 -08:00
|
|
|
// sleep to make slot eligible for repair
|
|
|
|
sleep(Duration::from_secs(1));
|
2019-05-09 19:57:51 -07:00
|
|
|
// Iterate through all possible combinations of start..end (inclusive on both
|
|
|
|
// sides of the range)
|
|
|
|
for start in 0..slots.len() {
|
|
|
|
for end in start..slots.len() {
|
|
|
|
let mut repair_slot_range = RepairSlotRange::default();
|
|
|
|
repair_slot_range.start = slots[start];
|
|
|
|
repair_slot_range.end = slots[end];
|
2019-05-15 11:37:20 -07:00
|
|
|
let expected: Vec<RepairType> = (repair_slot_range.start
|
|
|
|
..=repair_slot_range.end)
|
|
|
|
.map(|slot_index| {
|
|
|
|
if slots.contains(&(slot_index as u64)) {
|
2019-11-07 11:08:09 -08:00
|
|
|
RepairType::Shred(slot_index as u64, 0)
|
2019-05-15 11:37:20 -07:00
|
|
|
} else {
|
2019-11-14 11:49:31 -08:00
|
|
|
RepairType::HighestShred(slot_index as u64, 0)
|
2019-05-15 11:37:20 -07:00
|
|
|
}
|
|
|
|
})
|
2019-05-09 19:57:51 -07:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
RepairService::generate_repairs_in_range(
|
2020-01-13 14:13:52 -07:00
|
|
|
&blockstore,
|
2019-05-09 19:57:51 -07:00
|
|
|
std::usize::MAX,
|
|
|
|
&repair_slot_range
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
expected
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-05-09 19:57:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn test_repair_range_highest() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-02-12 17:43:45 -08:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
2019-02-12 17:43:45 -08:00
|
|
|
|
|
|
|
let num_entries_per_slot = 10;
|
|
|
|
|
|
|
|
let num_slots = 1;
|
|
|
|
let start = 5;
|
2019-05-09 19:57:51 -07:00
|
|
|
|
2019-11-14 11:49:31 -08:00
|
|
|
// Create some shreds in slots 0..num_slots
|
2019-02-12 17:43:45 -08:00
|
|
|
for i in start..start + num_slots {
|
|
|
|
let parent = if i > 0 { i - 1 } else { 0 };
|
2019-09-03 21:32:51 -07:00
|
|
|
let (shreds, _) = make_slot_entries(i, parent, num_entries_per_slot as u64);
|
2019-02-12 17:43:45 -08:00
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(shreds, None, false).unwrap();
|
2019-02-12 17:43:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let end = 4;
|
2019-05-15 11:37:20 -07:00
|
|
|
let expected: Vec<RepairType> = vec![
|
2019-11-14 11:49:31 -08:00
|
|
|
RepairType::HighestShred(end - 2, 0),
|
|
|
|
RepairType::HighestShred(end - 1, 0),
|
|
|
|
RepairType::HighestShred(end, 0),
|
2019-05-15 11:37:20 -07:00
|
|
|
];
|
2019-02-12 17:43:45 -08:00
|
|
|
|
|
|
|
let mut repair_slot_range = RepairSlotRange::default();
|
|
|
|
repair_slot_range.start = 2;
|
|
|
|
repair_slot_range.end = end;
|
|
|
|
|
|
|
|
assert_eq!(
|
2019-04-08 12:46:23 -07:00
|
|
|
RepairService::generate_repairs_in_range(
|
2020-01-13 14:13:52 -07:00
|
|
|
&blockstore,
|
2019-04-08 12:46:23 -07:00
|
|
|
std::usize::MAX,
|
|
|
|
&repair_slot_range
|
|
|
|
)
|
|
|
|
.unwrap(),
|
2019-02-12 17:43:45 -08:00
|
|
|
expected
|
|
|
|
);
|
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-04-08 12:46:23 -07:00
|
|
|
}
|
2019-05-13 15:37:50 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn test_get_completed_slots_past_root() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-05-13 15:37:50 -07:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
2019-05-13 15:37:50 -07:00
|
|
|
let num_entries_per_slot = 10;
|
|
|
|
let root = 10;
|
|
|
|
|
|
|
|
let fork1 = vec![5, 7, root, 15, 20, 21];
|
2019-09-03 21:32:51 -07:00
|
|
|
let fork1_shreds: Vec<_> = make_chaining_slot_entries(&fork1, num_entries_per_slot)
|
2019-05-13 15:37:50 -07:00
|
|
|
.into_iter()
|
2019-09-03 21:32:51 -07:00
|
|
|
.flat_map(|(shreds, _)| shreds)
|
2019-05-13 15:37:50 -07:00
|
|
|
.collect();
|
|
|
|
let fork2 = vec![8, 12];
|
2019-09-03 21:32:51 -07:00
|
|
|
let fork2_shreds = make_chaining_slot_entries(&fork2, num_entries_per_slot);
|
2019-05-13 15:37:50 -07:00
|
|
|
|
2019-11-14 11:49:31 -08:00
|
|
|
// Remove the last shred from each slot to make an incomplete slot
|
2019-09-03 21:32:51 -07:00
|
|
|
let fork2_incomplete_shreds: Vec<_> = fork2_shreds
|
2019-05-13 15:37:50 -07:00
|
|
|
.into_iter()
|
2019-09-03 21:32:51 -07:00
|
|
|
.flat_map(|(mut shreds, _)| {
|
|
|
|
shreds.pop();
|
|
|
|
shreds
|
2019-05-13 15:37:50 -07:00
|
|
|
})
|
|
|
|
.collect();
|
2019-05-23 03:50:41 -07:00
|
|
|
let mut full_slots = BTreeSet::new();
|
2019-05-13 15:37:50 -07:00
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(fork1_shreds, None, false).unwrap();
|
|
|
|
blockstore
|
2019-11-14 00:32:07 -08:00
|
|
|
.insert_shreds(fork2_incomplete_shreds, None, false)
|
2019-09-05 18:20:30 -07:00
|
|
|
.unwrap();
|
2019-05-13 15:37:50 -07:00
|
|
|
|
|
|
|
// Test that only slots > root from fork1 were included
|
2019-10-08 22:34:26 -07:00
|
|
|
let epoch_schedule = EpochSchedule::custom(32, 32, false);
|
2019-05-13 15:37:50 -07:00
|
|
|
|
|
|
|
RepairService::get_completed_slots_past_root(
|
2020-01-13 14:13:52 -07:00
|
|
|
&blockstore,
|
2019-05-13 15:37:50 -07:00
|
|
|
&mut full_slots,
|
|
|
|
root,
|
|
|
|
&epoch_schedule,
|
|
|
|
);
|
|
|
|
|
2019-05-23 03:50:41 -07:00
|
|
|
let mut expected: BTreeSet<_> = fork1.into_iter().filter(|x| *x > root).collect();
|
2019-05-13 15:37:50 -07:00
|
|
|
assert_eq!(full_slots, expected);
|
|
|
|
|
|
|
|
// Test that slots past the last confirmed epoch boundary don't get included
|
2019-10-08 22:34:26 -07:00
|
|
|
let last_epoch = epoch_schedule.get_leader_schedule_epoch(root);
|
2019-05-13 15:37:50 -07:00
|
|
|
let last_slot = epoch_schedule.get_last_slot_in_epoch(last_epoch);
|
|
|
|
let fork3 = vec![last_slot, last_slot + 1];
|
2019-09-03 21:32:51 -07:00
|
|
|
let fork3_shreds: Vec<_> = make_chaining_slot_entries(&fork3, num_entries_per_slot)
|
2019-05-13 15:37:50 -07:00
|
|
|
.into_iter()
|
2019-09-03 21:32:51 -07:00
|
|
|
.flat_map(|(shreds, _)| shreds)
|
2019-05-13 15:37:50 -07:00
|
|
|
.collect();
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(fork3_shreds, None, false).unwrap();
|
2019-05-13 15:37:50 -07:00
|
|
|
RepairService::get_completed_slots_past_root(
|
2020-01-13 14:13:52 -07:00
|
|
|
&blockstore,
|
2019-05-13 15:37:50 -07:00
|
|
|
&mut full_slots,
|
|
|
|
root,
|
|
|
|
&epoch_schedule,
|
|
|
|
);
|
|
|
|
expected.insert(last_slot);
|
|
|
|
assert_eq!(full_slots, expected);
|
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-05-13 15:37:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn test_update_epoch_slots() {
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
2019-05-13 15:37:50 -07:00
|
|
|
{
|
2020-01-13 14:13:52 -07:00
|
|
|
// Create blockstore
|
|
|
|
let (blockstore, _, completed_slots_receiver) =
|
|
|
|
Blockstore::open_with_signal(&blockstore_path).unwrap();
|
2019-05-13 15:37:50 -07:00
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore = Arc::new(blockstore);
|
2019-05-13 15:37:50 -07:00
|
|
|
|
|
|
|
let mut root = 0;
|
|
|
|
let num_slots = 100;
|
|
|
|
let entries_per_slot = 5;
|
2020-01-13 14:13:52 -07:00
|
|
|
let blockstore_ = blockstore.clone();
|
2019-05-13 15:37:50 -07:00
|
|
|
|
2020-01-13 14:13:52 -07:00
|
|
|
// Spin up thread to write to blockstore
|
2019-05-13 15:37:50 -07:00
|
|
|
let writer = Builder::new()
|
|
|
|
.name("writer".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
let slots: Vec<_> = (1..num_slots + 1).collect();
|
2019-09-03 21:32:51 -07:00
|
|
|
let mut shreds: Vec<_> = make_chaining_slot_entries(&slots, entries_per_slot)
|
2019-05-13 15:37:50 -07:00
|
|
|
.into_iter()
|
2019-09-03 21:32:51 -07:00
|
|
|
.flat_map(|(shreds, _)| shreds)
|
2019-05-13 15:37:50 -07:00
|
|
|
.collect();
|
2019-09-03 21:32:51 -07:00
|
|
|
shreds.shuffle(&mut thread_rng());
|
2019-05-13 15:37:50 -07:00
|
|
|
let mut i = 0;
|
|
|
|
let max_step = entries_per_slot * 4;
|
|
|
|
let repair_interval_ms = 10;
|
|
|
|
let mut rng = rand::thread_rng();
|
2019-09-03 21:32:51 -07:00
|
|
|
let num_shreds = shreds.len();
|
|
|
|
while i < num_shreds {
|
|
|
|
let step = rng.gen_range(1, max_step + 1) as usize;
|
|
|
|
let step = std::cmp::min(step, num_shreds - i);
|
|
|
|
let shreds_to_insert = shreds.drain(..step).collect_vec();
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore_
|
2019-11-14 00:32:07 -08:00
|
|
|
.insert_shreds(shreds_to_insert, None, false)
|
|
|
|
.unwrap();
|
2019-05-13 15:37:50 -07:00
|
|
|
sleep(Duration::from_millis(repair_interval_ms));
|
2019-09-03 21:32:51 -07:00
|
|
|
i += step;
|
2019-05-13 15:37:50 -07:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
2019-05-23 03:50:41 -07:00
|
|
|
let mut completed_slots = BTreeSet::new();
|
2019-05-13 15:37:50 -07:00
|
|
|
let node_info = Node::new_localhost_with_pubkey(&Pubkey::default());
|
|
|
|
let cluster_info = RwLock::new(ClusterInfo::new_with_invalid_keypair(
|
|
|
|
node_info.info.clone(),
|
|
|
|
));
|
|
|
|
|
2020-02-17 12:39:30 -08:00
|
|
|
let mut old_incomplete_slots: BTreeSet<Slot> = BTreeSet::new();
|
2019-05-13 15:37:50 -07:00
|
|
|
while completed_slots.len() < num_slots as usize {
|
|
|
|
RepairService::update_epoch_slots(
|
|
|
|
Pubkey::default(),
|
|
|
|
root,
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.lowest_slot(),
|
2019-05-13 15:37:50 -07:00
|
|
|
&mut completed_slots,
|
2020-02-17 12:39:30 -08:00
|
|
|
&mut old_incomplete_slots,
|
2019-05-13 15:37:50 -07:00
|
|
|
&cluster_info,
|
|
|
|
&completed_slots_receiver,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-23 03:50:41 -07:00
|
|
|
let mut expected: BTreeSet<_> = (1..num_slots + 1).collect();
|
2019-05-13 15:37:50 -07:00
|
|
|
assert_eq!(completed_slots, expected);
|
|
|
|
|
|
|
|
// Update with new root, should filter out the slots <= root
|
|
|
|
root = num_slots / 2;
|
2019-09-03 21:32:51 -07:00
|
|
|
let (shreds, _) = make_slot_entries(num_slots + 2, num_slots + 1, entries_per_slot);
|
2020-01-13 14:13:52 -07:00
|
|
|
blockstore.insert_shreds(shreds, None, false).unwrap();
|
2019-05-13 15:37:50 -07:00
|
|
|
RepairService::update_epoch_slots(
|
|
|
|
Pubkey::default(),
|
|
|
|
root,
|
2019-12-05 11:25:13 -08:00
|
|
|
0,
|
2019-05-13 15:37:50 -07:00
|
|
|
&mut completed_slots,
|
2020-02-17 12:39:30 -08:00
|
|
|
&mut old_incomplete_slots,
|
2019-05-13 15:37:50 -07:00
|
|
|
&cluster_info,
|
|
|
|
&completed_slots_receiver,
|
|
|
|
);
|
|
|
|
expected.insert(num_slots + 2);
|
|
|
|
assert_eq!(completed_slots, expected);
|
|
|
|
writer.join().unwrap();
|
|
|
|
}
|
2020-01-13 14:13:52 -07:00
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
2019-05-13 15:37:50 -07:00
|
|
|
}
|
2019-05-25 06:44:40 -07:00
|
|
|
|
|
|
|
#[test]
|
2020-02-17 12:39:30 -08:00
|
|
|
fn test_stash_old_incomplete_slots() {
|
|
|
|
let mut cache: BTreeSet<Slot> = BTreeSet::new();
|
|
|
|
let mut stash: BTreeSet<Slot> = BTreeSet::new();
|
|
|
|
|
|
|
|
// When cache is empty.
|
|
|
|
RepairService::stash_old_incomplete_slots(&mut cache, &mut stash);
|
|
|
|
assert_eq!(stash.len(), 0);
|
|
|
|
|
|
|
|
// Insert some slots in cache ( < MAX_COMPLETED_SLOT_CACHE_LEN + 1)
|
|
|
|
cache.insert(101);
|
|
|
|
cache.insert(102);
|
|
|
|
cache.insert(104);
|
|
|
|
cache.insert(105);
|
|
|
|
|
|
|
|
// Not enough slots in cache. So stash should remain empty.
|
|
|
|
RepairService::stash_old_incomplete_slots(&mut cache, &mut stash);
|
|
|
|
assert_eq!(stash.len(), 0);
|
|
|
|
assert_eq!(cache.len(), 4);
|
|
|
|
|
|
|
|
// Insert slots in cache ( = MAX_COMPLETED_SLOT_CACHE_LEN)
|
|
|
|
let mut cache: BTreeSet<Slot> = BTreeSet::new();
|
|
|
|
(0..MAX_COMPLETED_SLOT_CACHE_LEN as u64)
|
|
|
|
.into_iter()
|
|
|
|
.for_each(|slot| {
|
|
|
|
cache.insert(slot);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Not enough slots in cache. So stash should remain empty.
|
|
|
|
RepairService::stash_old_incomplete_slots(&mut cache, &mut stash);
|
|
|
|
assert_eq!(stash.len(), 0);
|
|
|
|
assert_eq!(cache.len(), MAX_COMPLETED_SLOT_CACHE_LEN);
|
|
|
|
|
|
|
|
// Insert 1 more to cross the threshold
|
|
|
|
cache.insert(MAX_COMPLETED_SLOT_CACHE_LEN as u64);
|
|
|
|
RepairService::stash_old_incomplete_slots(&mut cache, &mut stash);
|
|
|
|
// Stash is still empty, as no missing slots
|
|
|
|
assert_eq!(stash.len(), 0);
|
|
|
|
// It removed some entries from cache
|
|
|
|
assert_eq!(cache.len(), MAX_COMPLETED_SLOT_CACHE_LEN - 1);
|
|
|
|
|
|
|
|
// Insert more slots to create a missing slot
|
|
|
|
let mut cache: BTreeSet<Slot> = BTreeSet::new();
|
|
|
|
cache.insert(0);
|
|
|
|
(2..=MAX_COMPLETED_SLOT_CACHE_LEN as u64 + 2)
|
|
|
|
.into_iter()
|
|
|
|
.for_each(|slot| {
|
|
|
|
cache.insert(slot);
|
|
|
|
});
|
|
|
|
RepairService::stash_old_incomplete_slots(&mut cache, &mut stash);
|
|
|
|
|
|
|
|
// Stash is not empty
|
|
|
|
assert!(stash.contains(&1));
|
|
|
|
// It removed some entries from cache
|
|
|
|
assert_eq!(cache.len(), MAX_COMPLETED_SLOT_CACHE_LEN - 1);
|
|
|
|
|
|
|
|
// Test multiple missing slots at dispersed locations
|
|
|
|
let mut cache: BTreeSet<Slot> = BTreeSet::new();
|
|
|
|
(0..MAX_COMPLETED_SLOT_CACHE_LEN as u64 * 2)
|
|
|
|
.into_iter()
|
|
|
|
.for_each(|slot| {
|
|
|
|
cache.insert(slot);
|
|
|
|
});
|
|
|
|
|
|
|
|
cache.remove(&10);
|
|
|
|
cache.remove(&11);
|
|
|
|
|
|
|
|
cache.remove(&28);
|
|
|
|
cache.remove(&29);
|
|
|
|
|
|
|
|
cache.remove(&148);
|
|
|
|
cache.remove(&149);
|
|
|
|
cache.remove(&150);
|
|
|
|
cache.remove(&151);
|
|
|
|
|
|
|
|
RepairService::stash_old_incomplete_slots(&mut cache, &mut stash);
|
|
|
|
|
|
|
|
// Stash is not empty
|
|
|
|
assert!(stash.contains(&10));
|
|
|
|
assert!(stash.contains(&11));
|
|
|
|
assert!(stash.contains(&28));
|
|
|
|
assert!(stash.contains(&29));
|
|
|
|
assert!(stash.contains(&148));
|
|
|
|
assert!(stash.contains(&149));
|
|
|
|
assert!(stash.contains(&150));
|
|
|
|
assert!(stash.contains(&151));
|
|
|
|
|
|
|
|
assert!(!stash.contains(&147));
|
|
|
|
assert!(!stash.contains(&152));
|
|
|
|
// It removed some entries from cache
|
|
|
|
assert_eq!(cache.len(), MAX_COMPLETED_SLOT_CACHE_LEN - 1);
|
|
|
|
(MAX_COMPLETED_SLOT_CACHE_LEN + 1..MAX_COMPLETED_SLOT_CACHE_LEN * 2)
|
|
|
|
.into_iter()
|
|
|
|
.for_each(|slot| {
|
|
|
|
let slot: u64 = slot as u64;
|
|
|
|
assert!(cache.contains(&slot));
|
|
|
|
});
|
|
|
|
}
|
2019-05-25 06:44:40 -07:00
|
|
|
|
2020-02-17 12:39:30 -08:00
|
|
|
#[test]
|
|
|
|
fn test_prune_incomplete_slot_stash() {
|
|
|
|
// Prune empty stash
|
|
|
|
let mut stash: BTreeSet<Slot> = BTreeSet::new();
|
|
|
|
RepairService::prune_incomplete_slot_stash(&mut stash, 0);
|
|
|
|
assert!(stash.is_empty());
|
|
|
|
|
|
|
|
// Prune stash with slots < DEFAULT_SLOTS_PER_EPOCH
|
|
|
|
stash.insert(0);
|
|
|
|
stash.insert(10);
|
|
|
|
stash.insert(11);
|
|
|
|
stash.insert(50);
|
|
|
|
assert_eq!(stash.len(), 4);
|
|
|
|
RepairService::prune_incomplete_slot_stash(&mut stash, 100);
|
|
|
|
assert_eq!(stash.len(), 4);
|
|
|
|
|
|
|
|
// Prune stash with slots > DEFAULT_SLOTS_PER_EPOCH, but < 1.5 * DEFAULT_SLOTS_PER_EPOCH
|
|
|
|
stash.insert(DEFAULT_SLOTS_PER_EPOCH + 50);
|
|
|
|
assert_eq!(stash.len(), 5);
|
|
|
|
RepairService::prune_incomplete_slot_stash(&mut stash, DEFAULT_SLOTS_PER_EPOCH + 100);
|
|
|
|
assert_eq!(stash.len(), 5);
|
|
|
|
|
|
|
|
// Prune stash with slots > 1.5 * DEFAULT_SLOTS_PER_EPOCH
|
|
|
|
stash.insert(DEFAULT_SLOTS_PER_EPOCH + DEFAULT_SLOTS_PER_EPOCH / 2);
|
|
|
|
assert_eq!(stash.len(), 6);
|
|
|
|
RepairService::prune_incomplete_slot_stash(
|
|
|
|
&mut stash,
|
|
|
|
DEFAULT_SLOTS_PER_EPOCH + DEFAULT_SLOTS_PER_EPOCH / 2 + 1,
|
2019-05-25 06:44:40 -07:00
|
|
|
);
|
2020-02-17 12:39:30 -08:00
|
|
|
assert_eq!(stash.len(), 2);
|
2019-05-25 06:44:40 -07:00
|
|
|
}
|
2020-03-05 10:58:00 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_incomplete_slots() {
|
|
|
|
let blockstore_path = get_tmp_ledger_path!();
|
|
|
|
{
|
|
|
|
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
|
|
|
let num_entries_per_slot = 100;
|
|
|
|
let (mut shreds, _) = make_slot_entries(0, 0, num_entries_per_slot);
|
|
|
|
assert!(shreds.len() > 1);
|
|
|
|
let (shreds4, _) = make_slot_entries(4, 0, num_entries_per_slot);
|
|
|
|
shreds.extend(shreds4);
|
|
|
|
blockstore.insert_shreds(shreds, None, false).unwrap();
|
|
|
|
|
|
|
|
// Nothing is incomplete
|
|
|
|
assert!(RepairService::find_incomplete_slots(&blockstore, 0).is_empty());
|
|
|
|
|
|
|
|
// Insert a slot 5 that chains to an incomplete orphan slot 3
|
|
|
|
let (shreds5, _) = make_slot_entries(5, 3, num_entries_per_slot);
|
|
|
|
blockstore.insert_shreds(shreds5, None, false).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
RepairService::find_incomplete_slots(&blockstore, 0),
|
|
|
|
vec![3].into_iter().collect()
|
|
|
|
);
|
|
|
|
|
|
|
|
// Insert another incomplete orphan slot 2 that is the parent of slot 3.
|
|
|
|
// Both should be incomplete
|
|
|
|
let (shreds3, _) = make_slot_entries(3, 2, num_entries_per_slot);
|
|
|
|
blockstore
|
|
|
|
.insert_shreds(shreds3[1..].to_vec(), None, false)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
RepairService::find_incomplete_slots(&blockstore, 0),
|
|
|
|
vec![2, 3].into_iter().collect()
|
|
|
|
);
|
|
|
|
|
|
|
|
// Insert a incomplete slot 6 that chains to the root 0,
|
|
|
|
// should also be incomplete
|
|
|
|
let (shreds6, _) = make_slot_entries(6, 0, num_entries_per_slot);
|
|
|
|
blockstore
|
|
|
|
.insert_shreds(shreds6[1..].to_vec(), None, false)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
RepairService::find_incomplete_slots(&blockstore, 0),
|
|
|
|
vec![2, 3, 6].into_iter().collect()
|
|
|
|
);
|
|
|
|
|
|
|
|
// Complete slot 3, should no longer be marked incomplete
|
|
|
|
blockstore
|
|
|
|
.insert_shreds(shreds3[..].to_vec(), None, false)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
RepairService::find_incomplete_slots(&blockstore, 0),
|
|
|
|
vec![2, 6].into_iter().collect()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
|
|
|
}
|
2019-02-07 15:10:54 -08:00
|
|
|
}
|