Add ability to prune ledger (#5128)
* Add utility to prune the ledger * Add tests * Fix clippy * Fix off by one * Rework to force delete every column * Minor fixup
This commit is contained in:
		
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -2684,6 +2684,7 @@ name = "solana-ledger-tool" | ||||
| version = "0.17.0" | ||||
| dependencies = [ | ||||
|  "assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|   | ||||
| @@ -193,6 +193,70 @@ impl Blocktree { | ||||
|         false | ||||
|     } | ||||
|  | ||||
|     // silently deletes all blocktree column families starting at the given slot | ||||
|     fn delete_all_columns(&self, starting_slot: u64) { | ||||
|         match self.meta_cf.force_delete_all(Some(starting_slot)) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting meta_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|         match self.data_cf.force_delete_all(Some((starting_slot, 0))) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting data_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|         match self | ||||
|             .erasure_meta_cf | ||||
|             .force_delete_all(Some((starting_slot, 0))) | ||||
|         { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting erasure_meta_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|         match self.erasure_cf.force_delete_all(Some((starting_slot, 0))) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting erasure_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|         match self.orphans_cf.force_delete_all(Some(starting_slot)) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting orphans_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|         match self.index_cf.force_delete_all(Some(starting_slot)) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting index_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|         match self.dead_slots_cf.force_delete_all(Some(starting_slot)) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting dead_slots_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|         let roots_cf = self.db.column::<cf::Root>(); | ||||
|         match roots_cf.force_delete_all(Some(starting_slot)) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!( | ||||
|                 "Error: {:?} while deleting roots_cf for slot {:?}", | ||||
|                 e, starting_slot | ||||
|             ), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn erasure_meta(&self, slot: u64, set_index: u64) -> Result<Option<ErasureMeta>> { | ||||
|         self.erasure_meta_cf.get((slot, set_index)) | ||||
|     } | ||||
| @@ -201,7 +265,7 @@ impl Blocktree { | ||||
|         self.orphans_cf.get(slot) | ||||
|     } | ||||
|  | ||||
|     pub fn rooted_slot_iterator<'a>(&'a self, slot: u64) -> Result<RootedSlotIterator<'a>> { | ||||
|     pub fn rooted_slot_iterator(&self, slot: u64) -> Result<RootedSlotIterator> { | ||||
|         RootedSlotIterator::new(slot, self) | ||||
|     } | ||||
|  | ||||
| @@ -512,6 +576,13 @@ impl Blocktree { | ||||
|         self.data_cf.get_bytes((slot, index)) | ||||
|     } | ||||
|  | ||||
|     /// Manually update the meta for a slot. | ||||
|     /// Can interfere with automatic meta update and potentially break chaining. | ||||
|     /// Dangerous. Use with care. | ||||
|     pub fn put_meta_bytes(&self, slot: u64, bytes: &[u8]) -> Result<()> { | ||||
|         self.meta_cf.put_bytes(slot, bytes) | ||||
|     } | ||||
|  | ||||
|     /// For benchmarks, testing, and setup. | ||||
|     /// Does no metadata tracking. Use with care. | ||||
|     pub fn put_data_blob_bytes(&self, slot: u64, index: u64, bytes: &[u8]) -> Result<()> { | ||||
| @@ -921,6 +992,39 @@ impl Blocktree { | ||||
|         batch_processor.write(batch)?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Prune blocktree such that slots higher than `target_slot` are deleted and all references to | ||||
|     /// higher slots are removed | ||||
|     pub fn prune(&self, target_slot: u64) { | ||||
|         let mut meta = self | ||||
|             .meta(target_slot) | ||||
|             .expect("couldn't read slot meta") | ||||
|             .expect("no meta for target slot"); | ||||
|         meta.next_slots.clear(); | ||||
|         self.put_meta_bytes( | ||||
|             target_slot, | ||||
|             &bincode::serialize(&meta).expect("couldn't get meta bytes"), | ||||
|         ) | ||||
|         .expect("unable to update meta for target slot"); | ||||
|  | ||||
|         self.delete_all_columns(target_slot + 1); | ||||
|  | ||||
|         // fixup anything that refers to non-root slots and delete the rest | ||||
|         for (slot, mut meta) in self | ||||
|             .slot_meta_iterator(0) | ||||
|             .expect("unable to iterate over meta") | ||||
|         { | ||||
|             if slot > target_slot { | ||||
|                 break; | ||||
|             } | ||||
|             meta.next_slots.retain(|slot| *slot <= target_slot); | ||||
|             self.put_meta_bytes( | ||||
|                 slot, | ||||
|                 &bincode::serialize(&meta).expect("couldn't update meta"), | ||||
|             ) | ||||
|             .expect("couldn't update meta"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn insert_data_blob_batch<'a, I>( | ||||
| @@ -3336,6 +3440,66 @@ pub mod tests { | ||||
|         Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_prune() { | ||||
|         let blocktree_path = get_tmp_ledger_path!(); | ||||
|         let blocktree = Blocktree::open(&blocktree_path).unwrap(); | ||||
|         let (blobs, _) = make_many_slot_entries(0, 50, 6); | ||||
|         blocktree.write_blobs(blobs).unwrap(); | ||||
|         blocktree | ||||
|             .slot_meta_iterator(0) | ||||
|             .unwrap() | ||||
|             .for_each(|(_, meta)| assert_eq!(meta.last_index, 5)); | ||||
|  | ||||
|         blocktree.prune(5); | ||||
|  | ||||
|         blocktree | ||||
|             .slot_meta_iterator(0) | ||||
|             .unwrap() | ||||
|             .for_each(|(slot, meta)| { | ||||
|                 assert!(slot <= 5); | ||||
|                 assert_eq!(meta.last_index, 5) | ||||
|             }); | ||||
|  | ||||
|         let data_iter = blocktree.data_cf.iter(Some((0, 0))).unwrap(); | ||||
|         for ((slot, _), _) in data_iter { | ||||
|             if slot > 5 { | ||||
|                 assert!(false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         drop(blocktree); | ||||
|         Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction"); | ||||
|     } | ||||
|  | ||||
|     #[should_panic] | ||||
|     #[test] | ||||
|     fn test_prune_out_of_bounds() { | ||||
|         let blocktree_path = get_tmp_ledger_path!(); | ||||
|         let blocktree = Blocktree::open(&blocktree_path).unwrap(); | ||||
|  | ||||
|         // slot 5 does not exist, prune should panic | ||||
|         blocktree.prune(5); | ||||
|  | ||||
|         drop(blocktree); | ||||
|         Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_iter_bounds() { | ||||
|         let blocktree_path = get_tmp_ledger_path!(); | ||||
|         let blocktree = Blocktree::open(&blocktree_path).unwrap(); | ||||
|  | ||||
|         // slot 5 does not exist, iter should be ok and should be a noop | ||||
|         blocktree | ||||
|             .slot_meta_iterator(5) | ||||
|             .unwrap() | ||||
|             .for_each(|_| assert!(false)); | ||||
|  | ||||
|         drop(blocktree); | ||||
|         Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction"); | ||||
|     } | ||||
|  | ||||
|     mod erasure { | ||||
|         use super::*; | ||||
|         use crate::blocktree::meta::ErasureMetaStatus; | ||||
|   | ||||
| @@ -409,6 +409,16 @@ where | ||||
|         Ok(iter.map(|(key, value)| (C::index(&key), value))) | ||||
|     } | ||||
|  | ||||
|     //TODO add a delete_until that goes the other way | ||||
|     pub fn force_delete_all(&self, start_from: Option<C::Index>) -> Result<()> { | ||||
|         let iter = self.iter(start_from)?; | ||||
|         iter.for_each(|(index, _)| match self.delete(index) { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => error!("Error: {:?} while deleting {:?}", e, C::NAME), | ||||
|         }); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     pub fn handle(&self) -> B::ColumnFamily { | ||||
|         self.backend.cf_handle(C::NAME).clone() | ||||
|   | ||||
| @@ -9,6 +9,7 @@ license = "Apache-2.0" | ||||
| homepage = "https://solana.com/" | ||||
|  | ||||
| [dependencies] | ||||
| bincode = "1.1.4" | ||||
| clap = "2.33.0" | ||||
| serde = "1.0.94" | ||||
| serde_derive = "1.0.94" | ||||
|   | ||||
| @@ -91,6 +91,7 @@ fn main() { | ||||
|                 .long("slot-list") | ||||
|                 .value_name("FILENAME") | ||||
|                 .takes_value(true) | ||||
|                 .required(true) | ||||
|                 .help("The location of the YAML file with a list of rollback slot heights and hashes"), | ||||
|         )) | ||||
|         .subcommand(SubCommand::with_name("list-roots").about("Output upto last <num-roots> root hashes and their heights starting at the given block height").arg( | ||||
| @@ -191,7 +192,7 @@ fn main() { | ||||
|                     .last() | ||||
|                     .expect("Failed to find a valid slot"); | ||||
|                 println!("Prune at slot {:?} hash {:?}", target_slot, target_hash); | ||||
|                 // ToDo: Do the actual pruning of the database | ||||
|                 blocktree.prune(*target_slot); | ||||
|             } | ||||
|         } | ||||
|         ("list-roots", Some(args_matches)) => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user