Skip to content

Commit

Permalink
Merge pull request #4043 from anoma/yuji/prune-more-subtree-snapshots
Browse files Browse the repository at this point in the history
Prune more Merkle tree snapshots
  • Loading branch information
mergify[bot] authored Nov 19, 2024
2 parents 47fc912 + ddecdfe commit fb3a7cd
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Prune old Merkle tree snapshots which are saved every block
([\#4043](https://github.com/anoma/namada/issues/4043))
48 changes: 37 additions & 11 deletions crates/node/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ mod tests {
use namada_sdk::{
address, decode, encode, parameters, storage, token, validation,
};
use namada_vp::state::MerkleTree;
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::test_runner::Config;
Expand Down Expand Up @@ -533,7 +534,8 @@ mod tests {
}
}
// save the last root
roots.insert(state.in_mem().block.height, state.in_mem().merkle_root());
let last_height = state.in_mem().block.height;
roots.insert(last_height, state.in_mem().merkle_root());
state.commit_block_from_batch(batch)?;

let mut current_state = HashMap::new();
Expand Down Expand Up @@ -577,34 +579,53 @@ mod tests {
let key = make_key(i);
current_state.insert(key, true);
}
// NoDiff subtree can be restored for the last 2 blocks
let prev_last_height = last_height.prev_height().unwrap();
for (height, key, write_type) in blocks_write_type {
if key != client_counter_key() {
continue;
}
let merkle_key =
Key::from(NO_DIFF_KEY_PREFIX.to_string().to_db_key())
.join(&key);
let tree =
state.get_merkle_tree(height, Some(StoreType::NoDiff))?;
// Check if the rebuilt tree's root is the same as the saved one
assert_eq!(tree.root().0, roots.get(&height).unwrap().0);
let tree = match state
.get_merkle_tree(height, Some(StoreType::NoDiff))
{
Ok(tree) if height >= prev_last_height => {
// Check if the rebuilt tree's root is the same as the saved
// one
assert_eq!(tree.root().0, roots.get(&height).unwrap().0);
tree
}
Ok(_) => panic!("The tree at the height should be pruned"),
// expected error because of no merkle subtree store
// set an empty merkle tree as a dummy
Err(_) => MerkleTree::<PersistentStorageHasher>::default(),
};
// Check the NoDiff subtree at the last height
match write_type {
0 => {
// data was not updated
if *current_state.get(&key).unwrap() {
assert!(tree.has_key(&merkle_key)?);
} else {
assert!(!tree.has_key(&merkle_key)?);
if height >= prev_last_height {
if *current_state.get(&key).unwrap() {
assert!(tree.has_key(&merkle_key)?);
} else {
assert!(!tree.has_key(&merkle_key)?);
}
}
}
1 | 3 => {
// data was deleted
assert!(!tree.has_key(&merkle_key)?);
if height >= prev_last_height {
assert!(!tree.has_key(&merkle_key)?);
}
current_state.insert(key, false);
}
_ => {
// data was updated
assert!(tree.has_key(&merkle_key)?);
if height >= prev_last_height {
assert!(tree.has_key(&merkle_key)?);
}
current_state.insert(key, true);
}
}
Expand Down Expand Up @@ -733,6 +754,11 @@ mod tests {
let result =
state.get_merkle_tree(6.into(), Some(StoreType::BridgePool));
assert!(result.is_err(), "The bridge pool tree should be pruned");

let result = state.get_merkle_tree(10.into(), Some(StoreType::NoDiff));
assert!(result.is_err(), "The tree at Height 10 should be pruned");
let result = state.get_merkle_tree(11.into(), Some(StoreType::NoDiff));
assert!(result.is_ok(), "The tree at Height 11 should be restored");
}

/// Test the prefix iterator with RocksDB.
Expand Down
17 changes: 13 additions & 4 deletions crates/node/src/storage/rocksdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1410,7 +1410,8 @@ impl DB for RocksDB {
format!("{key_prefix}/{MERKLE_TREE_ROOT_KEY_SEGMENT}");
match self.read_value(block_cf, root_key)? {
Some(root) => merkle_tree_stores.set_root(st, root),
None => return Ok(None),
None if store_type.is_some() => return Ok(None),
_ => continue,
}

let store_key =
Expand All @@ -1419,7 +1420,8 @@ impl DB for RocksDB {
Some(bytes) => {
merkle_tree_stores.set_store(st.decode_store(bytes)?)
}
None => return Ok(None),
None if store_type.is_some() => return Ok(None),
_ => continue,
}
}
Ok(Some(merkle_tree_stores))
Expand Down Expand Up @@ -1659,10 +1661,17 @@ impl DB for RocksDB {
&mut self,
batch: &mut Self::WriteBatch,
store_type: &StoreType,
epoch: Epoch,
pruned_target: Either<BlockHeight, Epoch>,
) -> Result<()> {
let block_cf = self.get_column_family(BLOCK_CF)?;
let key_prefix = tree_key_prefix_with_epoch(store_type, epoch);
let key_prefix = match pruned_target {
Either::Left(height) => {
tree_key_prefix_with_height(store_type, height)
}
Either::Right(epoch) => {
tree_key_prefix_with_epoch(store_type, epoch)
}
};
let root_key = format!("{key_prefix}/{MERKLE_TREE_ROOT_KEY_SEGMENT}");
batch.0.delete_cf(block_cf, root_key);
let store_key = format!("{key_prefix}/{MERKLE_TREE_STORE_KEY_SEGMENT}");
Expand Down
53 changes: 42 additions & 11 deletions crates/state/src/wl_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::cmp::Ordering;
use std::ops::{Deref, DerefMut};

use itertools::Either;
use namada_core::address::Address;
use namada_core::arith::checked;
use namada_core::borsh::BorshSerializeExt;
Expand Down Expand Up @@ -393,12 +394,41 @@ where
// commit.
fn prune_merkle_tree_stores(
&mut self,
is_full_commit: bool,
batch: &mut D::WriteBatch,
) -> Result<()> {
if let Some(prev_height) = self
.in_mem
.block
.height
.prev_height()
.and_then(|h| h.prev_height())
{
for st in StoreType::iter().filter(|st| st.is_stored_every_block())
{
match st {
StoreType::Base => continue,
_ => self.0.db.prune_merkle_tree_store(
batch,
st,
Either::Left(prev_height),
)?,
}
}
}

if !is_full_commit {
return Ok(());
}

// Prune non-provable stores at the previous epoch
if let Some(prev_epoch) = self.in_mem.block.epoch.prev() {
for st in StoreType::iter_non_provable() {
self.0.db.prune_merkle_tree_store(batch, st, prev_epoch)?;
self.0.db.prune_merkle_tree_store(
batch,
st,
Either::Right(prev_epoch),
)?;
}
}
// Prune provable stores
Expand All @@ -411,7 +441,11 @@ where
self.db.prune_merkle_tree_store(
batch,
st,
oldest_epoch.prev().unwrap(),
Either::Right(
oldest_epoch
.prev()
.expect("the previous epoch should exist"),
),
)?;
}

Expand All @@ -425,7 +459,7 @@ where
self.db.prune_merkle_tree_store(
batch,
&StoreType::BridgePool,
epoch,
Either::Right(epoch),
)?;
}
}
Expand Down Expand Up @@ -637,10 +671,8 @@ where
time: header.time,
});
self.in_mem.last_epoch = self.in_mem.block.epoch;
if is_full_commit {
// prune old merkle tree stores
self.prune_merkle_tree_stores(&mut batch)?;
}
// prune old merkle tree stores
self.prune_merkle_tree_stores(is_full_commit, &mut batch)?;
// If there's a previous block, prune non-persisted diffs from it
if let Some(height) = self.in_mem.block.height.prev_height() {
self.db.prune_non_persisted_diffs(&mut batch, height)?;
Expand Down Expand Up @@ -996,15 +1028,14 @@ where
None => BlockHeight(1),
},
};
// Try to read all subtrees, but some subtrees would be stale or empty.
// They will be rebuild later. That's why the tree isn't validated here.
let stores = self
.db
.read_merkle_tree_stores(epoch, start_height, store_type)?
.ok_or(StateError::NoMerkleTree { height })?;
let mut tree = MerkleTree::<H>::new_partial(stores);
let prefix = store_type.and_then(|st| st.provable_prefix());
let mut tree = match store_type {
Some(_) => MerkleTree::<H>::new_partial(stores),
None => MerkleTree::<H>::new(stores).expect("invalid stores"),
};
// Restore the tree state with diffs
let mut target_height = start_height;
while target_height < height {
Expand Down
5 changes: 3 additions & 2 deletions crates/storage/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::fmt::Debug;
use std::num::TryFromIntError;

use itertools::Either;
use namada_core::address::EstablishedAddressGen;
use namada_core::chain::{BlockHeader, BlockHeight, Epoch, Epochs};
use namada_core::hash::{Error as HashError, Hash};
Expand Down Expand Up @@ -169,7 +170,7 @@ pub trait DB: Debug {

/// Read the merkle tree stores with the given epoch. If a store_type is
/// given, it reads only the specified tree. Otherwise, it reads all
/// trees.
/// trees, but some subtrees could be empty if the stores aren't saved.
fn read_merkle_tree_stores(
&self,
epoch: Epoch,
Expand Down Expand Up @@ -258,7 +259,7 @@ pub trait DB: Debug {
&mut self,
batch: &mut Self::WriteBatch,
store_type: &StoreType,
pruned_epoch: Epoch,
pruned_target: Either<BlockHeight, Epoch>,
) -> Result<()>;

/// Read the signed nonce of Bridge Pool
Expand Down
21 changes: 14 additions & 7 deletions crates/storage/src/mockdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,16 +322,16 @@ impl DB for MockDB {
format!("{key_prefix}/{MERKLE_TREE_ROOT_KEY_SEGMENT}");
match self.read_value(root_key)? {
Some(root) => merkle_tree_stores.set_root(st, root),
None => return Ok(None),
None if store_type.is_some() => return Ok(None),
_ => continue,
}
let store_key =
format!("{key_prefix}/{MERKLE_TREE_STORE_KEY_SEGMENT}");
let bytes = self.0.borrow().get(&store_key.to_string()).cloned();
match bytes {
Some(b) => {
merkle_tree_stores.set_store(st.decode_store(b)?);
}
None => return Ok(None),
Some(b) => merkle_tree_stores.set_store(st.decode_store(b)?),
None if store_type.is_some() => return Ok(None),
_ => continue,
}
}
Ok(Some(merkle_tree_stores))
Expand Down Expand Up @@ -547,9 +547,16 @@ impl DB for MockDB {
&mut self,
_batch: &mut Self::WriteBatch,
store_type: &StoreType,
epoch: Epoch,
pruned_target: Either<BlockHeight, Epoch>,
) -> Result<()> {
let key_prefix = tree_key_prefix_with_epoch(store_type, epoch);
let key_prefix = match pruned_target {
Either::Left(height) => {
tree_key_prefix_with_height(store_type, height)
}
Either::Right(epoch) => {
tree_key_prefix_with_epoch(store_type, epoch)
}
};
let root_key = format!("{key_prefix}/{MERKLE_TREE_ROOT_KEY_SEGMENT}");
self.0.borrow_mut().remove(&root_key);
let store_key = format!("{key_prefix}/{MERKLE_TREE_STORE_KEY_SEGMENT}");
Expand Down
2 changes: 1 addition & 1 deletion wasm_for_tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fb3a7cd

Please sign in to comment.