From affe06519b7587358332423966179a2f055ba185 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 21 Nov 2023 10:22:35 +0100 Subject: [PATCH 1/4] prune merkle trees of bridge pool --- apps/src/lib/node/ledger/storage/mod.rs | 9 +++ core/src/ledger/storage/mod.rs | 97 ++++++++++++++++++++----- 2 files changed, 86 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 52c10d4a17..e6208fc800 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -55,6 +55,7 @@ mod tests { use itertools::Itertools; use namada::core::ledger::masp_conversions::update_allowed_conversions; + use namada::ledger::eth_bridge::storage::bridge_pool; use namada::ledger::gas::STORAGE_ACCESS_GAS_PER_BYTE; use namada::ledger::ibc::storage::ibc_key; use namada::ledger::parameters::{EpochDuration, Parameters}; @@ -62,6 +63,7 @@ mod tests { use namada::ledger::storage::{types, StoreType, WlStorage}; use namada::ledger::storage_api::{self, StorageWrite}; use namada::types::chain::ChainId; + use namada::types::ethereum_events::Uint; use namada::types::hash::Hash; use namada::types::storage::{BlockHash, BlockHeight, Key}; use namada::types::time::DurationSecs; @@ -487,6 +489,9 @@ mod tests { let value_bytes = types::encode(&storage.block.height); storage.write(&key, value_bytes)?; } + let key = bridge_pool::get_nonce_key(); + let bytes = types::encode(&Uint::default()); + storage.write(&key, bytes)?; // Update and commit let hash = BlockHash::default(); @@ -581,6 +586,10 @@ mod tests { None, Some(5), ); + let key = bridge_pool::get_nonce_key(); + let bytes = types::encode(&Uint::default()); + storage.write(&key, bytes).unwrap(); + storage .begin_block(BlockHash::default(), BlockHeight(1)) .expect("begin_block failed"); diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index b338b3518b..f888482fd6 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -25,7 +25,9 @@ pub use wl_storage::{ }; use super::gas::MEMORY_ACCESS_GAS_PER_BYTE; -use crate::ledger::eth_bridge::storage::bridge_pool::is_pending_transfer_key; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + self, is_pending_transfer_key, +}; use crate::ledger::gas::{ STORAGE_ACCESS_GAS_PER_BYTE, STORAGE_WRITE_GAS_PER_BYTE, }; @@ -39,6 +41,7 @@ use crate::ledger::storage::merkle_tree::{ use crate::tendermint::merkle::proof::ProofOps; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; +use crate::types::ethereum_events::Uint; use crate::types::ethereum_structs; use crate::types::hash::{Error as HashError, Hash}; use crate::types::internal::{ExpiredTxsQueue, TxQueue}; @@ -1144,25 +1147,7 @@ where if self.block.epoch.0 == 0 { return Ok(()); } - if let Some(limit) = self.storage_read_past_height_limit { - if self.get_last_block_height().0 <= limit { - return Ok(()); - } - - let min_height = (self.get_last_block_height().0 - limit).into(); - if let Some(epoch) = self.block.pred_epochs.get_epoch(min_height) { - if epoch.0 == 0 { - return Ok(()); - } - // Remove stores at the previous epoch because the Merkle tree - // stores at the starting height of the epoch would be used to - // restore stores at a height (> min_height) in the epoch - for st in StoreType::iter_provable() { - self.db.prune_merkle_tree_store(batch, st, epoch.prev())?; - } - } - } - // remove non-provable stores at the previous epoch + // Prune non-provable stores at the previous epoch for st in StoreType::iter_non_provable() { self.db.prune_merkle_tree_store( batch, @@ -1170,6 +1155,32 @@ where self.block.epoch.prev(), )?; } + // Prune provable stores + let oldest_epoch = self.get_oldest_epoch(); + if oldest_epoch.0 > 0 { + // Remove stores at the previous epoch because the Merkle tree + // stores at the starting height of the epoch would be used to + // restore stores at a height (> oldest_height) in the epoch + for st in StoreType::iter_provable() { + self.db.prune_merkle_tree_store( + batch, + st, + oldest_epoch.prev(), + )?; + } + + // Prune the BridgePool subtree stores with invalid nonce + let mut epoch = self.get_oldest_epoch_with_valid_nonce()?; + while oldest_epoch < epoch { + epoch = epoch.prev(); + self.db.prune_merkle_tree_store( + batch, + &StoreType::BridgePool, + epoch, + )?; + } + } + Ok(()) } @@ -1182,6 +1193,52 @@ where .unwrap_or_default() } + /// Get the oldest epoch where we can read a value + pub fn get_oldest_epoch(&self) -> Epoch { + let oldest_height = match self.storage_read_past_height_limit { + Some(limit) if limit < self.get_last_block_height().0 => { + (self.get_last_block_height().0 - limit).into() + } + _ => BlockHeight(1), + }; + self.block + .pred_epochs + .get_epoch(oldest_height) + .unwrap_or_default() + } + + /// Get oldest epoch which has the valid signed nonce of the bridge pool + pub fn get_oldest_epoch_with_valid_nonce(&self) -> Result { + let nonce_key = bridge_pool::get_nonce_key(); + let (bytes, _) = self.read(&nonce_key)?; + let bytes = bytes.expect("Bridge pool nonce should exits"); + let current_nonce = + Uint::try_from_slice(&bytes).map_err(Error::BorshCodingError)?; + let (mut epoch, _) = self.get_last_epoch(); + // We don't need to check the older epochs because their Merkle tree + // snapshots have been already removed + let oldest_epoch = self.get_oldest_epoch(); + // Look up the last valid epoch which has the previous nonce of the + // current one. It has the previous nonce, but it was + // incremented during the epoch. + while 0 < epoch.0 && oldest_epoch <= epoch { + epoch = epoch.prev(); + let height = + match self.block.pred_epochs.get_start_height_of_epoch(epoch) { + Some(h) => h, + None => continue, + }; + let (bytes, _) = self.read_with_height(&nonce_key, height)?; + let bytes = bytes.expect("Bridge pool nonce should exits"); + let nonce = Uint::try_from_slice(&bytes) + .map_err(Error::BorshCodingError)?; + if nonce < current_nonce { + break; + } + } + Ok(epoch) + } + /// Check it the given transaction's hash is already present in storage pub fn has_replay_protection_entry(&self, hash: &Hash) -> Result { self.db.has_replay_protection_entry(hash) From cb85b5cb6e4f7c32ccec693e80cd48c127b8db3e Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 21 Nov 2023 21:29:14 +0100 Subject: [PATCH 2/4] add test and changelog --- .../improvements/2110-prune-bp-merkle-tree.md | 2 + apps/src/lib/node/ledger/storage/mod.rs | 44 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 .changelog/unreleased/improvements/2110-prune-bp-merkle-tree.md diff --git a/.changelog/unreleased/improvements/2110-prune-bp-merkle-tree.md b/.changelog/unreleased/improvements/2110-prune-bp-merkle-tree.md new file mode 100644 index 0000000000..4abce31ec0 --- /dev/null +++ b/.changelog/unreleased/improvements/2110-prune-bp-merkle-tree.md @@ -0,0 +1,2 @@ +- Prune merkle tree of bridge pool + ([\#2110](https://github.com/anoma/namada/issues/2110)) \ No newline at end of file diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index e6208fc800..2fd1fc5aa8 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -586,9 +586,10 @@ mod tests { None, Some(5), ); - let key = bridge_pool::get_nonce_key(); - let bytes = types::encode(&Uint::default()); - storage.write(&key, bytes).unwrap(); + let bp_nonce_key = bridge_pool::get_nonce_key(); + let nonce = Uint::default(); + let bytes = types::encode(&nonce); + storage.write(&bp_nonce_key, bytes).unwrap(); storage .begin_block(BlockHash::default(), BlockHeight(1)) @@ -615,6 +616,13 @@ mod tests { .write(&key, types::encode(&value)) .expect("write failed"); + let nonce = nonce + 1; + let bytes = types::encode(&nonce); + storage.write(&bp_nonce_key, bytes).unwrap(); + + let bytes = types::encode(&Uint::default()); + storage.write(&bp_nonce_key, bytes).unwrap(); + storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(6)); let batch = PersistentStorage::batch(); @@ -626,6 +634,11 @@ mod tests { storage .begin_block(BlockHash::default(), BlockHeight(11)) .expect("begin_block failed"); + + let nonce = nonce + 1; + let bytes = types::encode(&nonce); + storage.write(&bp_nonce_key, bytes).unwrap(); + storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(11)); let batch = PersistentStorage::batch(); @@ -639,7 +652,30 @@ mod tests { "The tree at Height 5 shouldn't be able to be restored" ); let result = storage.get_merkle_tree(6.into(), Some(StoreType::Ibc)); - assert!(result.is_ok(), "The tree should be restored"); + assert!(result.is_ok(), "The ibc tree should be restored"); + let result = + storage.get_merkle_tree(6.into(), Some(StoreType::BridgePool)); + assert!(result.is_ok(), "The bridge pool tree should be restored"); + + storage + .begin_block(BlockHash::default(), BlockHeight(12)) + .expect("begin_block failed"); + + let nonce = nonce + 1; + let bytes = types::encode(&nonce); + storage.write(&bp_nonce_key, bytes).unwrap(); + storage.block.epoch = storage.block.epoch.next(); + storage.block.pred_epochs.new_epoch(BlockHeight(12)); + let batch = PersistentStorage::batch(); + storage.commit_block(batch).expect("commit failed"); + + // ibc tree should be able to be restored + let result = storage.get_merkle_tree(6.into(), Some(StoreType::Ibc)); + assert!(result.is_ok(), "The ibc tree should be restored"); + // bridge pool tree should be pruned because of the nonce + let result = + storage.get_merkle_tree(6.into(), Some(StoreType::BridgePool)); + assert!(result.is_err(), "The bridge pool tree should be pruned"); } /// Test the prefix iterator with RocksDB. From ff68f7b85d84671b59d0e7e767dc8ff3bc986469 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 23 Nov 2023 22:42:43 +0100 Subject: [PATCH 3/4] remove unneeded write --- apps/src/lib/node/ledger/storage/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 2fd1fc5aa8..0a433fd8f6 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -620,9 +620,6 @@ mod tests { let bytes = types::encode(&nonce); storage.write(&bp_nonce_key, bytes).unwrap(); - let bytes = types::encode(&Uint::default()); - storage.write(&bp_nonce_key, bytes).unwrap(); - storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(6)); let batch = PersistentStorage::batch(); From a912cc79ff8315e9d3cd78abd6b0091776955979 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 6 Dec 2023 17:34:09 +0100 Subject: [PATCH 4/4] fix to read signed root --- apps/src/lib/node/ledger/storage/mod.rs | 34 ++++++++++++++------- apps/src/lib/node/ledger/storage/rocksdb.rs | 20 ++++++++++++ core/src/ledger/storage/mockdb.rs | 9 ++++++ core/src/ledger/storage/mod.rs | 26 ++++++++-------- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 0a433fd8f6..d93955517b 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -55,6 +55,7 @@ mod tests { use itertools::Itertools; use namada::core::ledger::masp_conversions::update_allowed_conversions; + use namada::eth_bridge::storage::proof::BridgePoolRootProof; use namada::ledger::eth_bridge::storage::bridge_pool; use namada::ledger::gas::STORAGE_ACCESS_GAS_PER_BYTE; use namada::ledger::ibc::storage::ibc_key; @@ -65,6 +66,7 @@ mod tests { use namada::types::chain::ChainId; use namada::types::ethereum_events::Uint; use namada::types::hash::Hash; + use namada::types::keccak::KeccakHash; use namada::types::storage::{BlockHash, BlockHeight, Key}; use namada::types::time::DurationSecs; use namada::types::{address, storage, token}; @@ -489,8 +491,10 @@ mod tests { let value_bytes = types::encode(&storage.block.height); storage.write(&key, value_bytes)?; } - let key = bridge_pool::get_nonce_key(); - let bytes = types::encode(&Uint::default()); + let key = bridge_pool::get_signed_root_key(); + let root_proof = + BridgePoolRootProof::new((KeccakHash::default(), Uint::default())); + let bytes = types::encode(&root_proof); storage.write(&key, bytes)?; // Update and commit @@ -586,10 +590,12 @@ mod tests { None, Some(5), ); - let bp_nonce_key = bridge_pool::get_nonce_key(); + let signed_root_key = bridge_pool::get_signed_root_key(); let nonce = Uint::default(); - let bytes = types::encode(&nonce); - storage.write(&bp_nonce_key, bytes).unwrap(); + let root_proof = + BridgePoolRootProof::new((KeccakHash::default(), nonce)); + let bytes = types::encode(&root_proof); + storage.write(&signed_root_key, bytes).unwrap(); storage .begin_block(BlockHash::default(), BlockHeight(1)) @@ -617,8 +623,10 @@ mod tests { .expect("write failed"); let nonce = nonce + 1; - let bytes = types::encode(&nonce); - storage.write(&bp_nonce_key, bytes).unwrap(); + let root_proof = + BridgePoolRootProof::new((KeccakHash::default(), nonce)); + let bytes = types::encode(&root_proof); + storage.write(&signed_root_key, bytes).unwrap(); storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(6)); @@ -633,8 +641,10 @@ mod tests { .expect("begin_block failed"); let nonce = nonce + 1; - let bytes = types::encode(&nonce); - storage.write(&bp_nonce_key, bytes).unwrap(); + let root_proof = + BridgePoolRootProof::new((KeccakHash::default(), nonce)); + let bytes = types::encode(&root_proof); + storage.write(&signed_root_key, bytes).unwrap(); storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(11)); @@ -659,8 +669,10 @@ mod tests { .expect("begin_block failed"); let nonce = nonce + 1; - let bytes = types::encode(&nonce); - storage.write(&bp_nonce_key, bytes).unwrap(); + let root_proof = + BridgePoolRootProof::new((KeccakHash::default(), nonce)); + let bytes = types::encode(&root_proof); + storage.write(&signed_root_key, bytes).unwrap(); storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(12)); let batch = PersistentStorage::batch(); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index cc55641f08..1fc2858de4 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -50,6 +50,8 @@ use data_encoding::HEXLOWER; use itertools::Either; use namada::core::ledger::masp_conversions::ConversionState; use namada::core::types::ethereum_structs; +use namada::eth_bridge::storage::proof::BridgePoolRootProof; +use namada::ledger::eth_bridge::storage::bridge_pool; use namada::ledger::storage::merkle_tree::{ base_tree_key_prefix, subtree_key_prefix, }; @@ -58,6 +60,7 @@ use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; +use namada::types::ethereum_events::Uint; use namada::types::internal::TxQueue; use namada::types::storage::{ BlockHeight, BlockResults, Epoch, EthEventsQueue, Header, Key, KeySeg, @@ -1446,6 +1449,23 @@ impl DB for RocksDB { Ok(()) } + fn read_bridge_pool_signed_nonce( + &self, + height: BlockHeight, + last_height: BlockHeight, + ) -> Result { + let nonce_key = bridge_pool::get_signed_root_key(); + let bytes = if height == BlockHeight(0) || height >= last_height { + self.read_subspace_val(&nonce_key)? + } else { + self.read_subspace_val_with_height(&nonce_key, height, last_height)? + }; + let bytes = bytes.expect("Signed root should exist"); + let bp_root_proof = BridgePoolRootProof::try_from_slice(&bytes) + .map_err(Error::BorshCodingError)?; + Ok(bp_root_proof.data.1) + } + fn write_replay_protection_entry( &mut self, batch: &mut Self::WriteBatch, diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index c96c92f3bb..a60c6ccd2e 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -18,6 +18,7 @@ use super::{ }; use crate::ledger::masp_conversions::ConversionState; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; +use crate::types::ethereum_events::Uint; use crate::types::ethereum_structs; use crate::types::hash::Hash; use crate::types::internal::TxQueue; @@ -588,6 +589,14 @@ impl DB for MockDB { Ok(()) } + fn read_bridge_pool_signed_nonce( + &self, + _height: BlockHeight, + _last_height: BlockHeight, + ) -> Result { + Ok(Uint::default()) + } + fn write_replay_protection_entry( &mut self, _batch: &mut Self::WriteBatch, diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index f888482fd6..c8630d3351 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -25,9 +25,7 @@ pub use wl_storage::{ }; use super::gas::MEMORY_ACCESS_GAS_PER_BYTE; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - self, is_pending_transfer_key, -}; +use crate::ledger::eth_bridge::storage::bridge_pool::is_pending_transfer_key; use crate::ledger::gas::{ STORAGE_ACCESS_GAS_PER_BYTE, STORAGE_WRITE_GAS_PER_BYTE, }; @@ -360,6 +358,13 @@ pub trait DB: std::fmt::Debug { pruned_epoch: Epoch, ) -> Result<()>; + /// Read the signed nonce of Bridge Pool + fn read_bridge_pool_signed_nonce( + &self, + height: BlockHeight, + last_height: BlockHeight, + ) -> Result; + /// Write a replay protection entry fn write_replay_protection_entry( &mut self, @@ -1209,11 +1214,10 @@ where /// Get oldest epoch which has the valid signed nonce of the bridge pool pub fn get_oldest_epoch_with_valid_nonce(&self) -> Result { - let nonce_key = bridge_pool::get_nonce_key(); - let (bytes, _) = self.read(&nonce_key)?; - let bytes = bytes.expect("Bridge pool nonce should exits"); - let current_nonce = - Uint::try_from_slice(&bytes).map_err(Error::BorshCodingError)?; + let last_height = self.get_last_block_height(); + let current_nonce = self + .db + .read_bridge_pool_signed_nonce(last_height, last_height)?; let (mut epoch, _) = self.get_last_epoch(); // We don't need to check the older epochs because their Merkle tree // snapshots have been already removed @@ -1228,10 +1232,8 @@ where Some(h) => h, None => continue, }; - let (bytes, _) = self.read_with_height(&nonce_key, height)?; - let bytes = bytes.expect("Bridge pool nonce should exits"); - let nonce = Uint::try_from_slice(&bytes) - .map_err(Error::BorshCodingError)?; + let nonce = + self.db.read_bridge_pool_signed_nonce(height, last_height)?; if nonce < current_nonce { break; }