forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
blockstore: always send a coding shred for chained merkle root conflicts #1353
Merged
AshwinSekar
merged 3 commits into
anza-xyz:master
from
AshwinSekar:chain-dup-proof-coding
May 28, 2024
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -498,11 +498,11 @@ impl Blockstore { | |
/// | ||
/// Checks the map `erasure_metas`, if not present scans blockstore. Returns None | ||
/// if the previous consecutive erasure set is not present in either. | ||
fn previous_erasure_set( | ||
&self, | ||
fn previous_erasure_set<'a>( | ||
&'a self, | ||
erasure_set: ErasureSetId, | ||
erasure_metas: &BTreeMap<ErasureSetId, WorkingEntry<ErasureMeta>>, | ||
) -> Result<Option<ErasureSetId>> { | ||
erasure_metas: &'a BTreeMap<ErasureSetId, WorkingEntry<ErasureMeta>>, | ||
) -> Result<Option<(ErasureSetId, Cow<ErasureMeta>)>> { | ||
let (slot, fec_set_index) = erasure_set.store_key(); | ||
|
||
// Check the previous entry from the in memory map to see if it is the consecutive | ||
|
@@ -513,13 +513,15 @@ impl Blockstore { | |
Bound::Excluded(erasure_set), | ||
)) | ||
.next_back(); | ||
let candidate_erasure_set = candidate_erasure_entry | ||
let candidate_erasure_set_and_meta = candidate_erasure_entry | ||
.filter(|(_, candidate_erasure_meta)| { | ||
candidate_erasure_meta.as_ref().next_fec_set_index() == Some(fec_set_index) | ||
}) | ||
.map(|(candidate_erasure_set, _)| *candidate_erasure_set); | ||
if candidate_erasure_set.is_some() { | ||
return Ok(candidate_erasure_set); | ||
.map(|(erasure_set, erasure_meta)| { | ||
(*erasure_set, Cow::Borrowed(erasure_meta.as_ref())) | ||
}); | ||
if candidate_erasure_set_and_meta.is_some() { | ||
return Ok(candidate_erasure_set_and_meta); | ||
} | ||
|
||
// Consecutive set was not found in memory, scan blockstore for a potential candidate | ||
|
@@ -549,7 +551,10 @@ impl Blockstore { | |
return Err(BlockstoreError::InvalidErasureConfig); | ||
}; | ||
if next_fec_set_index == fec_set_index { | ||
return Ok(Some(candidate_erasure_set)); | ||
return Ok(Some(( | ||
candidate_erasure_set, | ||
Cow::Owned(candidate_erasure_meta), | ||
))); | ||
} | ||
Ok(None) | ||
} | ||
|
@@ -1168,7 +1173,6 @@ impl Blockstore { | |
shred, | ||
&just_inserted_shreds, | ||
&erasure_metas, | ||
&merkle_root_metas, | ||
&mut duplicate_shreds, | ||
); | ||
} | ||
|
@@ -1887,7 +1891,6 @@ impl Blockstore { | |
shred: &Shred, | ||
just_inserted_shreds: &HashMap<ShredId, Shred>, | ||
erasure_metas: &BTreeMap<ErasureSetId, WorkingEntry<ErasureMeta>>, | ||
merkle_root_metas: &HashMap<ErasureSetId, WorkingEntry<MerkleRootMeta>>, | ||
duplicate_shreds: &mut Vec<PossibleDuplicateShred>, | ||
) -> bool { | ||
let slot = shred.slot(); | ||
|
@@ -1905,7 +1908,7 @@ impl Blockstore { | |
// If a shred from the previous fec set has already been inserted, check the chaining. | ||
// Since we cannot compute the previous fec set index, we check the in memory map, otherwise | ||
// check the previous key from blockstore to see if it is consecutive with our current set. | ||
let Some(prev_erasure_set) = self | ||
let Some((prev_erasure_set, prev_erasure_meta)) = self | ||
.previous_erasure_set(erasure_set, erasure_metas) | ||
.expect("Expect database operations to succeed") | ||
else { | ||
|
@@ -1915,37 +1918,22 @@ impl Blockstore { | |
return true; | ||
}; | ||
|
||
let Some(prev_merkle_root_meta) = merkle_root_metas | ||
.get(&prev_erasure_set) | ||
.map(WorkingEntry::as_ref) | ||
.map(Cow::Borrowed) | ||
.or_else(|| { | ||
self.merkle_root_meta(prev_erasure_set) | ||
.unwrap() | ||
.map(Cow::Owned) | ||
}) | ||
else { | ||
warn!( | ||
"The merkle root meta for the previous erasure set {prev_erasure_set:?} does not \ | ||
exist. This should only happen if you have recently upgraded from a version < \ | ||
v1.18.13. Skipping the backwards chained merkle root for {erasure_set:?}" | ||
); | ||
return true; | ||
}; | ||
let prev_shred_id = ShredId::new( | ||
slot, | ||
prev_merkle_root_meta.first_received_shred_index(), | ||
prev_merkle_root_meta.first_received_shred_type(), | ||
prev_erasure_meta | ||
.first_received_coding_shred_index() | ||
.expect("First received coding index must fit in u32"), | ||
ShredType::Code, | ||
); | ||
let Some(prev_shred) = | ||
Self::get_shred_from_just_inserted_or_db(self, just_inserted_shreds, prev_shred_id) | ||
.map(Cow::into_owned) | ||
else { | ||
error!( | ||
"Shred {prev_shred_id:?} indicated by merkle root meta {prev_merkle_root_meta:?} \ | ||
is missing from blockstore. This should only happen in extreme cases where \ | ||
blockstore cleanup has caught up to the root. Skipping the backwards chained \ | ||
merkle root consistency check" | ||
warn!( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We enter this if either:
|
||
"Shred {prev_shred_id:?} indicated by the erasure meta {prev_erasure_meta:?} \ | ||
is missing from blockstore. This can happen if you have recently upgraded \ | ||
from a version < v1.18.13, or if blockstore cleanup has caught up to the root. \ | ||
Skipping the backwards chained merkle root consistency check" | ||
); | ||
return true; | ||
}; | ||
|
@@ -1955,12 +1943,10 @@ impl Blockstore { | |
if !self.check_chaining(merkle_root, chained_merkle_root) { | ||
warn!( | ||
"Received conflicting chained merkle roots for slot: {slot}, shred {:?} type {:?} \ | ||
chains to merkle root {chained_merkle_root:?}, however previous fec set shred \ | ||
{prev_erasure_set:?} type {:?} has merkle root {merkle_root:?}. Reporting as \ | ||
duplicate", | ||
chains to merkle root {chained_merkle_root:?}, however previous fec set coding \ | ||
shred {prev_erasure_set:?} has merkle root {merkle_root:?}. Reporting as duplicate", | ||
shred.erasure_set(), | ||
shred.shred_type(), | ||
prev_merkle_root_meta.first_received_shred_type(), | ||
); | ||
|
||
if !self.has_duplicate_shreds_in_slot(shred.slot()) { | ||
|
@@ -11322,8 +11308,9 @@ pub mod tests { | |
assert_eq!( | ||
blockstore | ||
.previous_erasure_set(erasure_set, &erasure_metas) | ||
.unwrap(), | ||
Some(erasure_set_prev) | ||
.unwrap() | ||
.map(|(erasure_set, erasure_meta)| (erasure_set, erasure_meta.into_owned())), | ||
Some((erasure_set_prev, erasure_meta_prev)) | ||
); | ||
|
||
// Erasure meta only contains the older, blockstore has the previous fec set | ||
|
@@ -11334,32 +11321,36 @@ pub mod tests { | |
assert_eq!( | ||
blockstore | ||
.previous_erasure_set(erasure_set, &erasure_metas) | ||
.unwrap(), | ||
Some(erasure_set_prev) | ||
.unwrap() | ||
.map(|(erasure_set, erasure_meta)| (erasure_set, erasure_meta.into_owned())), | ||
Some((erasure_set_prev, erasure_meta_prev)) | ||
); | ||
|
||
// Both contain the previous fec set | ||
erasure_metas.insert(erasure_set_prev, WorkingEntry::Clean(erasure_meta_prev)); | ||
assert_eq!( | ||
blockstore | ||
.previous_erasure_set(erasure_set, &erasure_metas) | ||
.unwrap(), | ||
Some(erasure_set_prev) | ||
.unwrap() | ||
.map(|(erasure_set, erasure_meta)| (erasure_set, erasure_meta.into_owned())), | ||
Some((erasure_set_prev, erasure_meta_prev)) | ||
); | ||
|
||
// Works even if the previous fec set has index 0 | ||
assert_eq!( | ||
blockstore | ||
.previous_erasure_set(erasure_set_prev, &erasure_metas) | ||
.unwrap(), | ||
Some(erasure_set_0) | ||
.unwrap() | ||
.map(|(erasure_set, erasure_meta)| (erasure_set, erasure_meta.into_owned())), | ||
Some((erasure_set_0, erasure_meta_0)) | ||
); | ||
erasure_metas.remove(&erasure_set_0); | ||
assert_eq!( | ||
blockstore | ||
.previous_erasure_set(erasure_set_prev, &erasure_metas) | ||
.unwrap(), | ||
Some(erasure_set_0) | ||
.unwrap() | ||
.map(|(erasure_set, erasure_meta)| (erasure_set, erasure_meta.into_owned())), | ||
Some((erasure_set_0, erasure_meta_0)) | ||
); | ||
|
||
// Does not cross slot boundary | ||
|
@@ -11764,7 +11755,7 @@ pub mod tests { | |
|
||
#[test] | ||
fn test_chained_merkle_root_upgrade_inconsistency_backwards() { | ||
// Insert a coding shred (without a merkle meta) then inconsistent shreds from the next FEC set | ||
// Insert a coding shred (with an old erasure meta and no merkle root meta) then inconsistent shreds from the next FEC set | ||
let ledger_path = get_tmp_ledger_path_auto_delete!(); | ||
let blockstore = Blockstore::open(ledger_path.path()).unwrap(); | ||
|
||
|
@@ -11773,15 +11764,23 @@ pub mod tests { | |
let fec_set_index = 0; | ||
let (data_shreds, coding_shreds, leader_schedule) = | ||
setup_erasure_shreds_with_index(slot, parent_slot, 10, fec_set_index); | ||
let coding_shred_previous = coding_shreds[0].clone(); | ||
let coding_shred_previous = coding_shreds[1].clone(); | ||
let next_fec_set_index = fec_set_index + data_shreds.len() as u32; | ||
|
||
assert!(blockstore | ||
.insert_shred_return_duplicate(coding_shred_previous.clone(), &leader_schedule,) | ||
.is_empty()); | ||
|
||
// Remove the merkle root meta in order to simulate this blockstore originating from | ||
// an older version. | ||
// Set the first received coding shred index to 0 and remove merkle root meta to simulate this insertion coming from an | ||
// older version. | ||
let mut erasure_meta = blockstore | ||
.erasure_meta(coding_shred_previous.erasure_set()) | ||
.unwrap() | ||
.unwrap(); | ||
erasure_meta.clear_first_received_coding_shred_index(); | ||
blockstore | ||
.put_erasure_meta(coding_shred_previous.erasure_set(), &erasure_meta) | ||
.unwrap(); | ||
let mut write_batch = blockstore.db.batch().unwrap(); | ||
blockstore | ||
.db | ||
|
@@ -11794,7 +11793,7 @@ pub mod tests { | |
.is_none()); | ||
|
||
// Add an incorrectly chained merkle from the next set. Although incorrectly chained | ||
// we skip the duplicate check as the merkle root meta is missing. | ||
// we skip the duplicate check as the first received coding shred index shred is missing | ||
let merkle_root = Hash::new_unique(); | ||
assert!(merkle_root != coding_shred_previous.merkle_root().unwrap()); | ||
let (data_shreds, coding_shreds, leader_schedule) = | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is safe because
self.previous_erasure_set()
on line 1896 must have found anErasureMeta
forprev_erasure_set
in order to reach here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't
fn previous_erasure_set
actually return thatErasureMeta
instead of looking it up again here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah good point, I didn't do this last time because we only needed the merkle root.
Updated it to return the meta as well.