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:
parent
027ebb6670
commit
6ad9dc18d8
|
@ -2684,6 +2684,7 @@ name = "solana-ledger-tool"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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 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)",
|
"serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -193,6 +193,70 @@ impl Blocktree {
|
||||||
false
|
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>> {
|
pub fn erasure_meta(&self, slot: u64, set_index: u64) -> Result<Option<ErasureMeta>> {
|
||||||
self.erasure_meta_cf.get((slot, set_index))
|
self.erasure_meta_cf.get((slot, set_index))
|
||||||
}
|
}
|
||||||
|
@ -201,7 +265,7 @@ impl Blocktree {
|
||||||
self.orphans_cf.get(slot)
|
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)
|
RootedSlotIterator::new(slot, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,6 +576,13 @@ impl Blocktree {
|
||||||
self.data_cf.get_bytes((slot, index))
|
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.
|
/// For benchmarks, testing, and setup.
|
||||||
/// Does no metadata tracking. Use with care.
|
/// Does no metadata tracking. Use with care.
|
||||||
pub fn put_data_blob_bytes(&self, slot: u64, index: u64, bytes: &[u8]) -> Result<()> {
|
pub fn put_data_blob_bytes(&self, slot: u64, index: u64, bytes: &[u8]) -> Result<()> {
|
||||||
|
@ -921,6 +992,39 @@ impl Blocktree {
|
||||||
batch_processor.write(batch)?;
|
batch_processor.write(batch)?;
|
||||||
Ok(())
|
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>(
|
fn insert_data_blob_batch<'a, I>(
|
||||||
|
@ -3336,6 +3440,66 @@ pub mod tests {
|
||||||
Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction");
|
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 {
|
mod erasure {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::blocktree::meta::ErasureMetaStatus;
|
use crate::blocktree::meta::ErasureMetaStatus;
|
||||||
|
|
|
@ -409,6 +409,16 @@ where
|
||||||
Ok(iter.map(|(key, value)| (C::index(&key), value)))
|
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]
|
#[inline]
|
||||||
pub fn handle(&self) -> B::ColumnFamily {
|
pub fn handle(&self) -> B::ColumnFamily {
|
||||||
self.backend.cf_handle(C::NAME).clone()
|
self.backend.cf_handle(C::NAME).clone()
|
||||||
|
|
|
@ -9,6 +9,7 @@ license = "Apache-2.0"
|
||||||
homepage = "https://solana.com/"
|
homepage = "https://solana.com/"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bincode = "1.1.4"
|
||||||
clap = "2.33.0"
|
clap = "2.33.0"
|
||||||
serde = "1.0.94"
|
serde = "1.0.94"
|
||||||
serde_derive = "1.0.94"
|
serde_derive = "1.0.94"
|
||||||
|
|
|
@ -91,6 +91,7 @@ fn main() {
|
||||||
.long("slot-list")
|
.long("slot-list")
|
||||||
.value_name("FILENAME")
|
.value_name("FILENAME")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
.help("The location of the YAML file with a list of rollback slot heights and hashes"),
|
.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(
|
.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()
|
.last()
|
||||||
.expect("Failed to find a valid slot");
|
.expect("Failed to find a valid slot");
|
||||||
println!("Prune at slot {:?} hash {:?}", target_slot, target_hash);
|
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)) => {
|
("list-roots", Some(args_matches)) => {
|
||||||
|
|
Loading…
Reference in New Issue