From 1165a7f3fc90e02fb1da529843aa035d462fd923 Mon Sep 17 00:00:00 2001 From: steviez Date: Tue, 21 Jun 2022 22:49:30 -0500 Subject: [PATCH] Make clear_unconfirmed_slot() update parent's next_slots list (#26085) A slot may be purged from the blockstore with clear_unconfirmed_slot(). If the slot is added back, the slot should only exist once in its' parent SlotMeta::next_slots Vec. Prior to this change, repeated clearing and re-adding of a slot could result in the slot existing in parent's next_slots multiple times. The result is that if the next time the parent slot is processed (node restart or ledger-tool-replay), slot could be added to the queue of slots to play multiple times. Added test that failed before change and works now as well --- ledger/src/blockstore.rs | 61 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index d324815b15..62ef2db81c 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -994,8 +994,9 @@ impl Blockstore { self.completed_slots_senders.lock().unwrap().clear(); } - /// Range-delete all entries which prefix matches the specified `slot` and - /// clear all the related `SlotMeta` except its next_slots. + /// Range-delete all entries which prefix matches the specified `slot`, + /// remove `slot` its' parents SlotMeta next_slots list, and + /// clear `slot`'s SlotMeta (except for next_slots). /// /// This function currently requires `insert_shreds_lock`, as both /// `clear_unconfirmed_slot()` and `insert_shreds_handle_duplicate()` @@ -1011,6 +1012,21 @@ impl Blockstore { self.run_purge(slot, slot, PurgeType::PrimaryIndex) .expect("Purge database operations failed"); + // Clear this slot as a next slot from parent + if let Some(parent_slot) = slot_meta.parent_slot { + let mut parent_slot_meta = self + .meta(parent_slot) + .expect("Couldn't fetch from SlotMeta column family") + .expect("Unconfirmed slot should have had parent slot set"); + // .retain() is a linear scan; however, next_slots should + // only contain several elements so this isn't so bad + parent_slot_meta + .next_slots + .retain(|&next_slot| next_slot != slot); + self.meta_cf + .put(parent_slot, &parent_slot_meta) + .expect("Couldn't insert into SlotMeta column family"); + } // Reinsert parts of `slot_meta` that are important to retain, like the `next_slots` // field. slot_meta.clear_unconfirmed_slot(); @@ -8582,6 +8598,47 @@ pub mod tests { .is_none()); } + #[test] + fn test_clear_unconfirmed_slot_and_insert_again() { + let ledger_path = get_tmp_ledger_path_auto_delete!(); + let blockstore = Blockstore::open(ledger_path.path()).unwrap(); + + let confirmed_slot = 7; + let unconfirmed_slot = 8; + let slots = vec![confirmed_slot, unconfirmed_slot]; + + let shreds: Vec<_> = make_chaining_slot_entries(&slots, 1) + .into_iter() + .flat_map(|x| x.0) + .collect(); + assert_eq!(shreds.len(), 2); + + // Save off unconfirmed_slot for later, just one shred at shreds[1] + let unconfirmed_slot_shreds = vec![shreds[1].clone()]; + assert_eq!(unconfirmed_slot_shreds[0].slot(), unconfirmed_slot); + + // Insert into slot 9 + blockstore.insert_shreds(shreds, None, false).unwrap(); + + // Purge the slot + blockstore.clear_unconfirmed_slot(unconfirmed_slot); + assert!(!blockstore.is_dead(unconfirmed_slot)); + assert!(blockstore + .get_data_shred(unconfirmed_slot, 0) + .unwrap() + .is_none()); + + // Re-add unconfirmed_slot and confirm that confirmed_slot only has + // unconfirmed_slot in next_slots once + blockstore + .insert_shreds(unconfirmed_slot_shreds, None, false) + .unwrap(); + assert_eq!( + blockstore.meta(confirmed_slot).unwrap().unwrap().next_slots, + vec![unconfirmed_slot] + ); + } + #[test] fn test_update_completed_data_indexes() { let mut completed_data_indexes = BTreeSet::default();