Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore!: remove mutable mmr #5954

Merged
merged 2 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 0 additions & 20 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion base_layer/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ tari_comms_dht = { path = "../../comms/dht" }
tari_comms_rpc_macros = { path = "../../comms/rpc_macros" }
tari_crypto = { version = "0.19", features = ["borsh"] }
tari_metrics = { path = "../../infrastructure/metrics", optional = true }
tari_mmr = { path = "../../base_layer/mmr", optional = true, features = ["native_bitmap"] }
tari_mmr = { path = "../../base_layer/mmr", optional = true}
tari_p2p = { path = "../../base_layer/p2p" }
tari_script = { path = "../../infrastructure/tari_script" }
tari_service_framework = { path = "../service_framework" }
Expand Down
6 changes: 0 additions & 6 deletions base_layer/mmr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ edition = "2018"

[features]
default = []
native_bitmap = ["croaring"]

[dependencies]
tari_utilities = { version = "0.6" }
Expand All @@ -20,7 +19,6 @@ borsh = "0.10"
digest = "0.10"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
croaring = { version = "0.9", optional = true }

[dev-dependencies]
rand = "0.8"
Expand All @@ -41,11 +39,7 @@ harness = false
name = "smt"
harness = false

[[bench]]
name = "smt_vs_mmr"
harness = false

[[test]]
name = "tari_mmr_integration_tests"
path = "tests/mmr_integration_tests.rs"
required-features = ["native_bitmap"]
5 changes: 0 additions & 5 deletions base_layer/mmr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,3 @@ at the bottom of the MMR is the hashes of the data. The MMR allows easy to add a
tree. MMR always tries to have the largest possible single binary tree, so in effect it is possible to have more than
one binary tree. Every time you have to get the merkle root (the single merkle proof of the whole MMR) you have the bag
the peaks of the individual trees, or mountain peaks.

### Features

* `native_bitmap` - default feature, provides implementation for `MerkleCheckPoint`, `MmrCache`, `MutableMmr` via
using linked C library `croaring` (make sure to disable this feature when compiling to WASM).
56 changes: 54 additions & 2 deletions base_layer/mmr/benches/smt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn create_smt() -> SparseMerkleTree<Blake2b<U32>> {
SparseMerkleTree::<Blake2b<U32>>::new()
}

pub fn benchmark_sparse_merkle_trees(c: &mut Criterion) {
pub fn benchmark_smt_insert(c: &mut Criterion) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this more closely, I think there's a subtle error in this benchmark. A fresh tree is created for each sample, but then each iteration of that sample applies the upsert operations to the same tree. This means the first iteration will perform inserts, and subsequent iterations will perform updates. The timing data will only be accurate if an insert and update take the same amount of time.

let sizes = [100, 10_000];
for size in sizes {
c.bench_function(&format!("SMT: Insert {size} keys"), move |b| {
Expand All @@ -38,5 +38,57 @@ pub fn benchmark_sparse_merkle_trees(c: &mut Criterion) {
}
}

criterion_group!(smt, benchmark_sparse_merkle_trees);
fn insert_into_smt(keys: &[NodeKey], tree: &mut SparseMerkleTree<Blake2b<U32>>) {
keys.iter().for_each(|key| {
tree.upsert(key.clone(), ValueHash::default()).unwrap();
});
}

fn delete_from_smt(keys: &[NodeKey], tree: &mut SparseMerkleTree<Blake2b<U32>>) {
keys.iter().for_each(|key| {
tree.delete(key).unwrap();
});
}

fn time_function(header: &str, f: impl FnOnce()) -> std::time::Duration {
println!("Starting: {header}");
let now = std::time::Instant::now();
f();
let t = now.elapsed();
println!("Finished: {header} - {t:?}");
t
}

pub fn root_hash(_c: &mut Criterion) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these operations be refactored to use Criterion's bench_function design? It might get a little tricky because of how its iterations work, since you want to work on a fresh tree for each of its iterations to ensure the operations are being run as expected. But at least then you can probably get better timing data.

let size = 1_000_000;
let half_size = size / 2;
let keys = get_keys(size);
let mut tree = create_smt();
time_function(&format!("SMT: Inserting {size} keys"), || {
insert_into_smt(&keys, &mut tree);
});
time_function("SMT: Calculating root hash", || {
let size = tree.size();
let hash = tree.hash();
println!("Tree size: {size}. Root hash: {hash:x}");
});
time_function(&format!("SMT: Deleting {half_size} keys"), || {
delete_from_smt(&keys[0..half_size], &mut tree);
});
time_function("SMT: Calculating root hash", || {
let size = tree.size();
let hash = tree.hash();
println!("Tree size: {size}. Root hash: {hash:x}");
});
time_function(&format!("SMT: Deleting another {half_size} keys"), || {
delete_from_smt(&keys[half_size..], &mut tree);
});
time_function("SMT: Calculating root hash", || {
let size = tree.size();
let hash = tree.hash();
println!("Tree size: {size}. Root hash: {hash:x}");
});
}

criterion_group!(smt, benchmark_smt_insert, root_hash);
criterion_main!(smt);
137 changes: 0 additions & 137 deletions base_layer/mmr/benches/smt_vs_mmr.rs

This file was deleted.

60 changes: 4 additions & 56 deletions base_layer/mmr/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,14 @@
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use crate::{
error::MerkleMountainRangeError,
pruned_hashset::PrunedHashSet,
ArrayLike,
Hash,
MerkleMountainRange,
MutableMmr,
};
use digest::Digest;
use std::{convert::TryFrom, marker::PhantomData};

use digest::Digest;
use tari_common::DomainDigest;

use crate::{error::MerkleMountainRangeError, pruned_hashset::PrunedHashSet, ArrayLike, Hash, MerkleMountainRange};

pub type PrunedMmr<D> = MerkleMountainRange<D, PrunedHashSet>;
pub type PrunedMutableMmr<D> = MutableMmr<D, PrunedHashSet>;

/// Create a pruned Merkle Mountain Range from the provided MMR. Pruning entails throwing all the hashes of the
/// pruned MMR away, except for the current peaks. A new MMR instance is returned that allows you to continue
Expand All @@ -52,52 +46,6 @@ where
})
}

/// A convenience function in the same vein as [prune_mmr], but applied to `MutableMmr` instances.
pub fn prune_mutable_mmr<D, B>(mmr: &MutableMmr<D, B>) -> Result<PrunedMutableMmr<D>, MerkleMountainRangeError>
where
D: Digest + DomainDigest,
B: ArrayLike<Value = Hash>,
{
let backend = PrunedHashSet::try_from(&mmr.mmr)?;
Ok(MutableMmr {
mmr: MerkleMountainRange::new(backend),
deleted: mmr.deleted.clone(),
size: mmr.size,
})
}

/// `calculate_mmr_root`` takes an MMR instance and efficiently calculates the new MMR root by applying the given
/// additions to calculate a new MMR root without changing the original MMR.
///
/// This is done by creating a memory-backed sparse (pruned) copy of the original MMR, applying the changes and then
/// calculating a new root.
///
/// # Parameters
/// * `src`: A reference to the original MMR
/// * `additions`: A vector of leaf node hashes to append to the MMR
/// * `deletions`: A vector of leaf node _indices_ that will be marked as deleted.
///
/// # Returns
/// The new MMR root as a result of applying the given changes
pub fn calculate_pruned_mmr_root<D, B>(
src: &MutableMmr<D, B>,
additions: Vec<Hash>,
deletions: Vec<u32>,
) -> Result<Hash, MerkleMountainRangeError>
where
D: Digest + DomainDigest,
B: ArrayLike<Value = Hash>,
{
let mut pruned_mmr = prune_mutable_mmr(src)?;
for hash in additions {
pruned_mmr.push(hash)?;
}
for index in deletions {
pruned_mmr.delete(index);
}
pruned_mmr.get_merkle_root()
}

pub fn calculate_mmr_root<D, B>(
src: &MerkleMountainRange<D, B>,
additions: Vec<Hash>,
Expand Down
27 changes: 1 addition & 26 deletions base_layer/mmr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,29 +165,4 @@ pub use merkle_mountain_range::MerkleMountainRange;
/// A data structure for proving a hash inclusion in an MMR
pub use merkle_proof::{MerkleProof, MerkleProofError};

macro_rules! if_native_bitmap {
($($item:item)*) => {
$(
#[cfg(feature = "native_bitmap")]
$item
)*
}
}

if_native_bitmap! {
mod merkle_checkpoint;
mod mmr_cache;
mod mutable_mmr;
mod mutable_mmr_leaf_nodes;
pub mod functions;

/// A Merkle checkpoint contains the set of hash additions and deletion indices.
pub use merkle_checkpoint::MerkleCheckPoint;
/// The MMR cache is used to calculate Merkle and Merklish roots based on the state of the set of shared
/// checkpoints.
pub use mmr_cache::{MmrCache, MmrCacheConfig};
/// An append-only Merkle Mountain range (MMR) data structure that allows deletion of existing leaf nodes.
pub use mutable_mmr::MutableMmr;
/// A data structure for storing all the data required to restore the state of an MMR.
pub use mutable_mmr_leaf_nodes::MutableMmrLeafNodes;
}
pub mod functions;