Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 859b748

Browse files
committed
use data shreds instead of erasure batch, use state machine
1 parent 58ee77d commit 859b748

File tree

5 files changed

+79
-30
lines changed

5 files changed

+79
-30
lines changed

core/src/repair/cluster_slot_state_verifier.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ impl BankFrozenState {
159159
is_slot_duplicate,
160160
}
161161
}
162+
163+
pub fn mark_duplicate(&mut self) {
164+
self.is_slot_duplicate = true;
165+
}
162166
}
163167

164168
#[derive(PartialEq, Eq, Debug)]

core/src/replay_stage.rs

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use {
4848
entry_notifier_service::EntryNotifierSender,
4949
leader_schedule_cache::LeaderScheduleCache,
5050
leader_schedule_utils::first_of_consecutive_leader_slots,
51-
shred::{ErasureSetId, DATA_SHREDS_PER_FEC_BLOCK},
51+
shred::DATA_SHREDS_PER_FEC_BLOCK,
5252
},
5353
solana_measure::measure::Measure,
5454
solana_poh::poh_recorder::{PohLeaderStatus, PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS},
@@ -2898,43 +2898,60 @@ impl ReplayStage {
28982898
(bank.slot(), bank.hash()),
28992899
Some((bank.parent_slot(), bank.parent_hash())),
29002900
);
2901-
// If the block does not have at least 64 shreds in the last FEC set, mark
2902-
// it as invalid, effectively removing it from fork choice.
2903-
let mut last_fec_set_too_small = true;
2904-
let slot_meta = blockstore
2905-
.meta(bank.slot())
2906-
.expect("Slot meta get must succeed on frozen banks")
2907-
.expect("Slot meta must exist during freeze");
2908-
if let Some(last_shred_index) = slot_meta.last_index {
2909-
if let Ok(Some(erasure_meta)) = blockstore.erasure_meta(ErasureSetId::new(
2910-
bank.slot(),
2911-
u32::try_from(last_shred_index).expect("LAST_SHRED_IN_SLOT should be u32"),
2912-
)) {
2913-
if erasure_meta.total_shreds() >= 2 * DATA_SHREDS_PER_FEC_BLOCK {
2914-
last_fec_set_too_small = false;
2915-
}
2916-
}
2917-
}
2918-
// If there is no erasure meta then we have not received a coding shred for this
2919-
// fec set. If there is no `slot_meta.last_index` then we should not be freezing
2920-
// the bank. There is already a duplicate check ensuring `LAST_SHRED_IN_SLOT` is
2921-
// consistent. At this point if `incomplete_last_fec_set` is `false`, then the
2922-
// leader has sent less than 2 * DATA_SHREDS_PER_FEC_BLOCK shreds in the last fec set,
2923-
// meaning we can disregard this slot
2924-
if last_fec_set_too_small {
2925-
heaviest_subtree_fork_choice
2926-
.mark_fork_invalid_candidate(&(bank.slot(), bank.hash()));
2927-
}
29282901

29292902
bank_progress.fork_stats.bank_hash = Some(bank.hash());
2930-
let bank_frozen_state = BankFrozenState::new_from_state(
2903+
let mut bank_frozen_state = BankFrozenState::new_from_state(
29312904
bank.slot(),
29322905
bank.hash(),
29332906
duplicate_slots_tracker,
29342907
duplicate_confirmed_slots,
29352908
heaviest_subtree_fork_choice,
29362909
epoch_slots_frozen_slots,
29372910
);
2911+
2912+
if bank
2913+
.feature_set
2914+
.is_active(&solana_sdk::feature_set::vote_only_full_fec_sets::id())
2915+
{
2916+
let mut last_fec_set_too_small = true;
2917+
if let Some((fec_set_index, shred_index)) =
2918+
blockstore.get_last_shred_indices(bank.slot())
2919+
{
2920+
// We need to check if the last FEC set index contains at least `DATA_SHREDS_PER_FEC_BLOCK` data shreds.
2921+
// Since we froze the slot we know that the data shreds are connected. We can offset from the
2922+
// last data shred index to compare the size of the last FEC set.
2923+
// offset_index = shred_index - (DATA_SHREDS_PER_FEC_BLOCK - 1)
2924+
let offset_index = u32::try_from(DATA_SHREDS_PER_FEC_BLOCK)
2925+
.unwrap()
2926+
.checked_sub(1)
2927+
.and_then(|offset| shred_index.checked_sub(offset));
2928+
if let Some(offset_index) = offset_index {
2929+
if let Some(data_shred_fec_set_index) =
2930+
blockstore.get_data_shred_fec_set_index(bank.slot(), offset_index)
2931+
{
2932+
if fec_set_index == data_shred_fec_set_index {
2933+
// This implies that the last fec set contains at least `DATA_SHREDS_PER_FEC_BLOCK`.
2934+
// Since we have reached the max tick height if there are more data shreds not yet received
2935+
// this block will be marked dead/invalid once they are received.
2936+
//
2937+
// Under this assumption, we must have recovered the remaining `DATA_SHREDS_PER_FEC_BLOCK+` coding
2938+
// shreds as well, and the FEC set contains at least `DATA_SHREDS_PER_FEC_BLOCK`.
2939+
// TODO: Recovery is only possible if we receive at least 1 coding shred. for the 32 data 0 coding
2940+
// case we need to wait to continue until 1 coding shred has been received. We can add a separate
2941+
// check here or address this as part of the IP verification which also needs a wait for 33/64.
2942+
last_fec_set_too_small = false;
2943+
}
2944+
}
2945+
}
2946+
}
2947+
2948+
if last_fec_set_too_small {
2949+
// If the block does not have at least 2 * DATA_SHREDS_PER_FEC_BLOCK shreds in the last FEC set, treat it
2950+
// as duplicate, effectively removing it from fork choice.
2951+
bank_frozen_state.mark_duplicate();
2952+
}
2953+
}
2954+
29382955
check_slot_agrees_with_cluster(
29392956
bank.slot(),
29402957
bank_forks.read().unwrap().root(),

ledger/src/blockstore.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,29 @@ impl Blockstore {
14651465
shred.is_code() && shred.slot() > max_root
14661466
}
14671467

1468+
/// If slot is present in blockstore, get the fec_set_index and shred index
1469+
/// of the last shred.
1470+
///
1471+
/// Returns `None` if
1472+
/// - No slot meta
1473+
/// - No LAST_SHRED_IN_SLOT flag (incomplete block)
1474+
pub fn get_last_shred_indices(&self, slot: Slot) -> Option<(u32, u32)> {
1475+
let slot_meta = self.meta(slot).ok()??;
1476+
let shred_index = u32::try_from(slot_meta.last_index?)
1477+
.expect("last_index should be a shred index of size u32");
1478+
Some((
1479+
self.get_data_shred_fec_set_index(slot, shred_index)?,
1480+
shred_index,
1481+
))
1482+
}
1483+
1484+
/// If the shred is present in blockstore, get the fec_set_index
1485+
pub fn get_data_shred_fec_set_index(&self, slot: Slot, shred_index: u32) -> Option<u32> {
1486+
let raw_shred = self.get_data_shred(slot, u64::from(shred_index)).ok()??;
1487+
let shred = Shred::new_from_serialized_shred(raw_shred).ok()?;
1488+
Some(shred.fec_set_index())
1489+
}
1490+
14681491
fn insert_coding_shred(
14691492
&self,
14701493
index_meta: &mut Index,

ledger/src/shred.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ impl Shred {
472472
}
473473

474474
// Identifier for the erasure coding set that the shred belongs to.
475-
pub(crate) fn erasure_set(&self) -> ErasureSetId {
475+
pub fn erasure_set(&self) -> ErasureSetId {
476476
ErasureSetId(self.slot(), self.fec_set_index())
477477
}
478478

sdk/src/feature_set.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,10 @@ pub mod drop_legacy_shreds {
736736
solana_sdk::declare_id!("GV49KKQdBNaiv2pgqhS2Dy3GWYJGXMTVYbYkdk91orRy");
737737
}
738738

739+
pub mod vote_only_full_fec_sets {
740+
solana_sdk::declare_id!("ffecLRhhakKSGhMuc6Fz2Lnfq4uT9q3iu9ZsNaPLxPc");
741+
}
742+
739743
lazy_static! {
740744
/// Map of feature identifiers to user-visible description
741745
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@@ -915,6 +919,7 @@ lazy_static! {
915919
(disable_rent_fees_collection::id(), "Disable rent fees collection #33945"),
916920
(enable_zk_transfer_with_fee::id(), "enable Zk Token proof program transfer with fee"),
917921
(drop_legacy_shreds::id(), "drops legacy shreds #34328"),
922+
(vote_only_full_fec_sets::id(), "vote only full fec sets #34294"),
918923
/*************** ADD NEW FEATURES HERE ***************/
919924
]
920925
.iter()

0 commit comments

Comments
 (0)