From a645d07e06a5b2da8076539d5799a5087c5cff52 Mon Sep 17 00:00:00 2001 From: Ashwin Sekar Date: Sun, 5 May 2024 19:20:32 -0700 Subject: [PATCH] blockstore: relax backwards chained merkle root check for upgrades (#1163) * blockstore: relax backwards chained merkle root check for upgrades * s/v1.18.12/v1.18.13 Co-authored-by: Trent Nelson <490004+t-nelson@users.noreply.github.com> --------- Co-authored-by: Trent Nelson <490004+t-nelson@users.noreply.github.com> --- ledger/src/blockstore.rs | 115 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index e83b2ee4d43a8c..bd961f59d12be7 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -1883,7 +1883,7 @@ impl Blockstore { return true; }; - let prev_merkle_root_meta = merkle_root_metas + let Some(prev_merkle_root_meta) = merkle_root_metas .get(&prev_erasure_set) .map(WorkingEntry::as_ref) .map(Cow::Borrowed) @@ -1892,7 +1892,14 @@ impl Blockstore { .unwrap() .map(Cow::Owned) }) - .expect("merkle root meta must exist for erasure meta"); + 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(), @@ -11588,4 +11595,108 @@ 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 + let ledger_path = get_tmp_ledger_path_auto_delete!(); + let blockstore = Blockstore::open(ledger_path.path()).unwrap(); + + let parent_slot = 0; + let slot = 1; + 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 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. + let mut write_batch = blockstore.db.batch().unwrap(); + blockstore + .db + .delete_range_cf::(&mut write_batch, slot, slot) + .unwrap(); + blockstore.db.write(write_batch).unwrap(); + assert!(blockstore + .merkle_root_meta(coding_shred_previous.erasure_set()) + .unwrap() + .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. + let merkle_root = Hash::new_unique(); + assert!(merkle_root != coding_shred_previous.merkle_root().unwrap()); + let (data_shreds, coding_shreds, leader_schedule) = + setup_erasure_shreds_with_index_and_chained_merkle( + slot, + parent_slot, + 10, + next_fec_set_index, + Some(merkle_root), + ); + let data_shred = data_shreds[0].clone(); + let coding_shred = coding_shreds[0].clone(); + assert!(blockstore + .insert_shred_return_duplicate(coding_shred, &leader_schedule) + .is_empty()); + assert!(blockstore + .insert_shred_return_duplicate(data_shred, &leader_schedule,) + .is_empty()); + } + + #[test] + fn test_chained_merkle_root_upgrade_inconsistency_forwards() { + // Insert a data shred (without a merkle root), then an inconsistent coding shred from the previous FEC set. + let ledger_path = get_tmp_ledger_path_auto_delete!(); + let blockstore = Blockstore::open(ledger_path.path()).unwrap(); + + let parent_slot = 0; + let slot = 1; + 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 = coding_shreds[0].clone(); + let next_fec_set_index = fec_set_index + data_shreds.len() as u32; + + // Incorrectly chained merkle + let merkle_root = Hash::new_unique(); + assert!(merkle_root != coding_shred.merkle_root().unwrap()); + let (next_data_shreds, next_coding_shreds, leader_schedule_next) = + setup_erasure_shreds_with_index_and_chained_merkle( + slot, + parent_slot, + 10, + next_fec_set_index, + Some(merkle_root), + ); + let next_data_shred = next_data_shreds[0].clone(); + + assert!(blockstore + .insert_shred_return_duplicate(next_data_shred, &leader_schedule_next,) + .is_empty()); + + // Remove the merkle root meta in order to simulate this blockstore originating from + // an older version. + let mut write_batch = blockstore.db.batch().unwrap(); + blockstore + .db + .delete_range_cf::(&mut write_batch, slot, slot) + .unwrap(); + blockstore.db.write(write_batch).unwrap(); + assert!(blockstore + .merkle_root_meta(next_coding_shreds[0].erasure_set()) + .unwrap() + .is_none()); + + // Insert previous FEC set, although incorrectly chained we skip the duplicate check + // as the merkle root meta is missing. + assert!(blockstore + .insert_shred_return_duplicate(coding_shred, &leader_schedule) + .is_empty()); + } }