diff --git a/CHANGELOG.md b/CHANGELOG.md index 6231ccbf..401fb9ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ +## 0.13.1 (2024-12-26) + +- Generate reverse mutations set on applying of mutations set, implemented serialization of `MutationsSet` (#355). + ## 0.13.0 (2024-11-24) - Fixed a bug in the implementation of `draw_integers` for `RpoRandomCoin` (#343). - [BREAKING] Refactor error messages and use `thiserror` to derive errors (#344). - [BREAKING] Updated Winterfell dependency to v0.11 (#346). + ## 0.12.0 (2024-10-30) - [BREAKING] Updated Winterfell dependency to v0.10 (#338). diff --git a/Cargo.lock b/Cargo.lock index 160a2e7a..253f0d39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,18 +92,18 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -113,9 +113,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake3" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" dependencies = [ "arrayref", "arrayvec", @@ -153,9 +153,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.1" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -197,9 +197,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -231,9 +231,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -351,19 +351,19 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" @@ -456,9 +456,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -471,10 +471,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -495,9 +496,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" @@ -525,7 +526,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miden-crypto" -version = "0.13.0" +version = "0.13.1" dependencies = [ "assert_matches", "blake3", @@ -685,9 +686,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", @@ -711,9 +712,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -808,15 +809,15 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -854,18 +855,18 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -874,9 +875,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -908,9 +909,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.89" +version = "2.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" dependencies = [ "proc-macro2", "quote", @@ -932,18 +933,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", @@ -1017,9 +1018,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -1028,13 +1029,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1043,9 +1043,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1053,9 +1053,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -1066,15 +1066,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index b9a05ef5..87bf6821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "miden-crypto" -version = "0.13.0" +version = "0.13.1" description = "Miden Cryptographic primitives" authors = ["miden contributors"] readme = "README.md" license = "MIT" repository = "https://github.com/0xPolygonMiden/crypto" -documentation = "https://docs.rs/miden-crypto/0.13.0" +documentation = "https://docs.rs/miden-crypto/0.13.1" categories = ["cryptography", "no-std"] keywords = ["miden", "crypto", "hash", "merkle"] edition = "2021" @@ -65,7 +65,7 @@ assert_matches = { version = "1.5", default-features = false } criterion = { version = "0.5", features = ["html_reports"] } getrandom = { version = "0.2", features = ["js"] } hex = { version = "0.4", default-features = false, features = ["alloc"] } -proptest = "1.5" +proptest = "1.6" rand_chacha = { version = "0.3", default-features = false } rand-utils = { version = "0.11", package = "winter-rand-utils" } seq-macro = { version = "0.3" } diff --git a/Makefile b/Makefile index 31894c9a..c84805bb 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,10 @@ build-sve: ## Build with sve support # --- benchmarking -------------------------------------------------------------------------------- -.PHONY: bench-tx -bench-tx: ## Run crypto benchmarks +.PHONY: bench +bench: ## Run crypto benchmarks cargo bench + +.PHONY: bench-smt-concurrent +bench-smt-concurrent: ## Run SMT benchmarks with concurrent feature + cargo run --release --features executable -- --size 1000000 diff --git a/src/main.rs b/src/main.rs index 776ccc21..2cc11ac2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,9 @@ use clap::Parser; use miden_crypto::{ hash::rpo::{Rpo256, RpoDigest}, merkle::{MerkleError, Smt}, - Felt, Word, ONE, + Felt, Word, EMPTY_WORD, ONE, }; +use rand::{prelude::IteratorRandom, thread_rng, Rng}; use rand_utils::rand_value; #[derive(Parser, Debug)] @@ -13,7 +14,7 @@ use rand_utils::rand_value; pub struct BenchmarkCmd { /// Size of the tree #[clap(short = 's', long = "size")] - size: u64, + size: usize, } fn main() { @@ -29,101 +30,153 @@ pub fn benchmark_smt() { let mut entries = Vec::new(); for i in 0..tree_size { let key = rand_value::(); - let value = [ONE, ONE, ONE, Felt::new(i)]; + let value = [ONE, ONE, ONE, Felt::new(i as u64)]; entries.push((key, value)); } - let mut tree = construction(entries, tree_size).unwrap(); - insertion(&mut tree, tree_size).unwrap(); - batched_insertion(&mut tree, tree_size).unwrap(); - proof_generation(&mut tree, tree_size).unwrap(); + let mut tree = construction(entries.clone(), tree_size).unwrap(); + insertion(&mut tree).unwrap(); + batched_insertion(&mut tree).unwrap(); + batched_update(&mut tree, entries).unwrap(); + proof_generation(&mut tree).unwrap(); } /// Runs the construction benchmark for [`Smt`], returning the constructed tree. -pub fn construction(entries: Vec<(RpoDigest, Word)>, size: u64) -> Result { +pub fn construction(entries: Vec<(RpoDigest, Word)>, size: usize) -> Result { println!("Running a construction benchmark:"); let now = Instant::now(); let tree = Smt::with_entries(entries)?; - let elapsed = now.elapsed(); - println!( - "Constructed a SMT with {} key-value pairs in {:.3} seconds", - size, - elapsed.as_secs_f32(), - ); + let elapsed = now.elapsed().as_secs_f32(); + println!("Constructed a SMT with {size} key-value pairs in {elapsed:.1} seconds"); println!("Number of leaf nodes: {}\n", tree.leaves().count()); Ok(tree) } /// Runs the insertion benchmark for the [`Smt`]. -pub fn insertion(tree: &mut Smt, size: u64) -> Result<(), MerkleError> { +pub fn insertion(tree: &mut Smt) -> Result<(), MerkleError> { + const NUM_INSERTIONS: usize = 1_000; + println!("Running an insertion benchmark:"); + let size = tree.num_leaves(); + let mut insertion_times = Vec::new(); - for i in 0..20 { + for i in 0..NUM_INSERTIONS { let test_key = Rpo256::hash(&rand_value::().to_be_bytes()); - let test_value = [ONE, ONE, ONE, Felt::new(size + i)]; + let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)]; let now = Instant::now(); tree.insert(test_key, test_value); let elapsed = now.elapsed(); - insertion_times.push(elapsed.as_secs_f32()); + insertion_times.push(elapsed.as_micros()); } println!( - "An average insertion time measured by 20 inserts into a SMT with {} key-value pairs is {:.3} milliseconds\n", - size, - // calculate the average by dividing by 20 and convert to milliseconds by multiplying by - // 1000. As a result, we can only multiply by 50 - insertion_times.iter().sum::() * 50f32, + "An average insertion time measured by {NUM_INSERTIONS} inserts into an SMT with {size} leaves is {:.0} μs\n", + // calculate the average + insertion_times.iter().sum::() as f64 / (NUM_INSERTIONS as f64), ); Ok(()) } -pub fn batched_insertion(tree: &mut Smt, size: u64) -> Result<(), MerkleError> { +pub fn batched_insertion(tree: &mut Smt) -> Result<(), MerkleError> { + const NUM_INSERTIONS: usize = 1_000; + println!("Running a batched insertion benchmark:"); - let new_pairs: Vec<(RpoDigest, Word)> = (0..1000) + let size = tree.num_leaves(); + + let new_pairs: Vec<(RpoDigest, Word)> = (0..NUM_INSERTIONS) .map(|i| { let key = Rpo256::hash(&rand_value::().to_be_bytes()); - let value = [ONE, ONE, ONE, Felt::new(size + i)]; + let value = [ONE, ONE, ONE, Felt::new((size + i) as u64)]; (key, value) }) .collect(); let now = Instant::now(); let mutations = tree.compute_mutations(new_pairs); - let compute_elapsed = now.elapsed(); + let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms + + let now = Instant::now(); + tree.apply_mutations(mutations)?; + let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms + + println!( + "An average insert-batch computation time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs", + compute_elapsed, + compute_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs + ); + + println!( + "An average insert-batch application time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs", + apply_elapsed, + apply_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs + ); + + println!( + "An average batch insertion time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms", + (compute_elapsed + apply_elapsed), + ); + + println!(); + + Ok(()) +} + +pub fn batched_update(tree: &mut Smt, entries: Vec<(RpoDigest, Word)>) -> Result<(), MerkleError> { + const NUM_UPDATES: usize = 1_000; + const REMOVAL_PROBABILITY: f64 = 0.2; + + println!("Running a batched update benchmark:"); + + let size = tree.num_leaves(); + let mut rng = thread_rng(); + + let new_pairs = + entries + .into_iter() + .choose_multiple(&mut rng, NUM_UPDATES) + .into_iter() + .map(|(key, _)| { + let value = if rng.gen_bool(REMOVAL_PROBABILITY) { + EMPTY_WORD + } else { + [ONE, ONE, ONE, Felt::new(rng.gen())] + }; + + (key, value) + }); + + assert_eq!(new_pairs.len(), NUM_UPDATES); + + let now = Instant::now(); + let mutations = tree.compute_mutations(new_pairs); + let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms let now = Instant::now(); - tree.apply_mutations(mutations).unwrap(); - let apply_elapsed = now.elapsed(); + tree.apply_mutations(mutations)?; + let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms println!( - "An average batch computation time measured by a 1k-batch into an SMT with {} key-value pairs over {:.3} milliseconds is {:.3} milliseconds", - size, - compute_elapsed.as_secs_f32() * 1000f32, - // Dividing by the number of iterations, 1000, and then multiplying by 1000 to get - // milliseconds, cancels out. - compute_elapsed.as_secs_f32(), + "An average update-batch computation time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs", + compute_elapsed, + compute_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs ); println!( - "An average batch application time measured by a 1k-batch into an SMT with {} key-value pairs over {:.3} milliseconds is {:.3} milliseconds", - size, - apply_elapsed.as_secs_f32() * 1000f32, - // Dividing by the number of iterations, 1000, and then multiplying by 1000 to get - // milliseconds, cancels out. - apply_elapsed.as_secs_f32(), + "An average update-batch application time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs", + apply_elapsed, + apply_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs ); println!( - "An average batch insertion time measured by a 1k-batch into an SMT with {} key-value pairs totals to {:.3} milliseconds", - size, - (compute_elapsed + apply_elapsed).as_secs_f32() * 1000f32, + "An average batch update time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms", + (compute_elapsed + apply_elapsed), ); println!(); @@ -132,28 +185,29 @@ pub fn batched_insertion(tree: &mut Smt, size: u64) -> Result<(), MerkleError> { } /// Runs the proof generation benchmark for the [`Smt`]. -pub fn proof_generation(tree: &mut Smt, size: u64) -> Result<(), MerkleError> { +pub fn proof_generation(tree: &mut Smt) -> Result<(), MerkleError> { + const NUM_PROOFS: usize = 100; + println!("Running a proof generation benchmark:"); let mut insertion_times = Vec::new(); - for i in 0..20 { + let size = tree.num_leaves(); + + for i in 0..NUM_PROOFS { let test_key = Rpo256::hash(&rand_value::().to_be_bytes()); - let test_value = [ONE, ONE, ONE, Felt::new(size + i)]; + let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)]; tree.insert(test_key, test_value); let now = Instant::now(); let _proof = tree.open(&test_key); - let elapsed = now.elapsed(); - insertion_times.push(elapsed.as_secs_f32()); + insertion_times.push(now.elapsed().as_micros()); } println!( - "An average proving time measured by 20 value proofs in a SMT with {} key-value pairs in {:.3} microseconds", - size, - // calculate the average by dividing by 20 and convert to microseconds by multiplying by - // 1000000. As a result, we can only multiply by 50000 - insertion_times.iter().sum::() * 50000f32, + "An average proving time measured by {NUM_PROOFS} value proofs in an SMT with {size} leaves in {:.0} μs", + // calculate the average + insertion_times.iter().sum::() as f64 / (NUM_PROOFS as f64), ); Ok(()) diff --git a/src/merkle/index.rs b/src/merkle/index.rs index d4887b7d..31a4bd28 100644 --- a/src/merkle/index.rs +++ b/src/merkle/index.rs @@ -128,7 +128,7 @@ impl NodeIndex { self.value } - /// Returns true if the current instance points to a right sibling node. + /// Returns `true` if the current instance points to a right sibling node. pub const fn is_value_odd(&self) -> bool { (self.value & 1) == 1 } diff --git a/src/merkle/mmr/partial.rs b/src/merkle/mmr/partial.rs index 5327548d..b29c4f52 100644 --- a/src/merkle/mmr/partial.rs +++ b/src/merkle/mmr/partial.rs @@ -303,7 +303,7 @@ impl PartialMmr { if leaf_pos + 1 == self.forest && path.depth() == 0 - && self.peaks.last().map_or(false, |v| *v == leaf) + && self.peaks.last().is_some_and(|v| *v == leaf) { self.track_latest = true; return Ok(()); diff --git a/src/merkle/smt/full/leaf.rs b/src/merkle/smt/full/leaf.rs index ed3e6b7d..04867911 100644 --- a/src/merkle/smt/full/leaf.rs +++ b/src/merkle/smt/full/leaf.rs @@ -70,7 +70,7 @@ impl SmtLeaf { Self::Single((key, value)) } - /// Returns a new single leaf with the specified entry. The leaf index is derived from the + /// Returns a new multiple leaf with the specified entries. The leaf index is derived from the /// entries' keys. /// /// # Errors diff --git a/src/merkle/smt/full/mod.rs b/src/merkle/smt/full/mod.rs index 226a8b18..d800185d 100644 --- a/src/merkle/smt/full/mod.rs +++ b/src/merkle/smt/full/mod.rs @@ -114,6 +114,11 @@ impl Smt { >::root(self) } + /// Returns the number of non-empty leaves in this tree. + pub fn num_leaves(&self) -> usize { + self.leaves.len() + } + /// Returns the leaf to which `key` maps pub fn get_leaf(&self, key: &RpoDigest) -> SmtLeaf { >::get_leaf(self, key) @@ -200,7 +205,7 @@ impl Smt { >::compute_mutations(self, kv_pairs) } - /// Apply the prospective mutations computed with [`Smt::compute_mutations()`] to this tree. + /// Applies the prospective mutations computed with [`Smt::compute_mutations()`] to this tree. /// /// # Errors /// If `mutations` was computed on a tree with a different root than this one, returns @@ -214,6 +219,23 @@ impl Smt { >::apply_mutations(self, mutations) } + /// Applies the prospective mutations computed with [`Smt::compute_mutations()`] to this tree + /// and returns the reverse mutation set. + /// + /// Applying the reverse mutation sets to the updated tree will revert the changes. + /// + /// # Errors + /// If `mutations` was computed on a tree with a different root than this one, returns + /// [`MerkleError::ConflictingRoots`] with a two-item [`Vec`]. The first item is the root hash + /// the `mutations` were computed against, and the second item is the actual current root of + /// this tree. + pub fn apply_mutations_with_reversion( + &mut self, + mutations: MutationSet, + ) -> Result, MerkleError> { + >::apply_mutations_with_reversion(self, mutations) + } + // HELPERS // -------------------------------------------------------------------------------------------- @@ -275,12 +297,12 @@ impl SparseMerkleTree for Smt { .unwrap_or_else(|| EmptySubtreeRoots::get_inner_node(SMT_DEPTH, index.depth())) } - fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) { - self.inner_nodes.insert(index, inner_node); + fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) -> Option { + self.inner_nodes.insert(index, inner_node) } - fn remove_inner_node(&mut self, index: NodeIndex) { - let _ = self.inner_nodes.remove(&index); + fn remove_inner_node(&mut self, index: NodeIndex) -> Option { + self.inner_nodes.remove(&index) } fn insert_value(&mut self, key: Self::Key, value: Self::Value) -> Option { diff --git a/src/merkle/smt/full/tests.rs b/src/merkle/smt/full/tests.rs index 03eb3829..6404f294 100644 --- a/src/merkle/smt/full/tests.rs +++ b/src/merkle/smt/full/tests.rs @@ -1,12 +1,14 @@ -use alloc::vec::Vec; +use alloc::{collections::BTreeMap, vec::Vec}; use super::{Felt, LeafIndex, NodeIndex, Rpo256, RpoDigest, Smt, SmtLeaf, EMPTY_WORD, SMT_DEPTH}; use crate::{ - merkle::{smt::SparseMerkleTree, EmptySubtreeRoots, MerkleStore}, + merkle::{ + smt::{NodeMutation, SparseMerkleTree}, + EmptySubtreeRoots, MerkleStore, MutationSet, + }, utils::{Deserializable, Serializable}, Word, ONE, WORD_SIZE, }; - // SMT // -------------------------------------------------------------------------------------------- @@ -412,21 +414,49 @@ fn test_prospective_insertion() { let mutations = smt.compute_mutations(vec![(key_1, value_1)]); assert_eq!(mutations.root(), root_1, "prospective root 1 did not match actual root 1"); - smt.apply_mutations(mutations).unwrap(); + let revert = apply_mutations(&mut smt, mutations); assert_eq!(smt.root(), root_1, "mutations before and after apply did not match"); + assert_eq!(revert.old_root, smt.root(), "reverse mutations old root did not match"); + assert_eq!(revert.root(), root_empty, "reverse mutations new root did not match"); + assert_eq!( + revert.new_pairs, + BTreeMap::from_iter([(key_1, EMPTY_WORD)]), + "reverse mutations pairs did not match" + ); + assert_eq!( + revert.node_mutations, + smt.inner_nodes.keys().map(|key| (*key, NodeMutation::Removal)).collect(), + "reverse mutations inner nodes did not match" + ); let mutations = smt.compute_mutations(vec![(key_2, value_2)]); assert_eq!(mutations.root(), root_2, "prospective root 2 did not match actual root 2"); let mutations = smt.compute_mutations(vec![(key_3, EMPTY_WORD), (key_2, value_2), (key_3, value_3)]); assert_eq!(mutations.root(), root_3, "mutations before and after apply did not match"); - smt.apply_mutations(mutations).unwrap(); + let old_root = smt.root(); + let revert = apply_mutations(&mut smt, mutations); + assert_eq!(revert.old_root, smt.root(), "reverse mutations old root did not match"); + assert_eq!(revert.root(), old_root, "reverse mutations new root did not match"); + assert_eq!( + revert.new_pairs, + BTreeMap::from_iter([(key_2, EMPTY_WORD), (key_3, EMPTY_WORD)]), + "reverse mutations pairs did not match" + ); // Edge case: multiple values at the same key, where a later pair restores the original value. let mutations = smt.compute_mutations(vec![(key_3, EMPTY_WORD), (key_3, value_3)]); assert_eq!(mutations.root(), root_3); - smt.apply_mutations(mutations).unwrap(); + let old_root = smt.root(); + let revert = apply_mutations(&mut smt, mutations); assert_eq!(smt.root(), root_3); + assert_eq!(revert.old_root, smt.root(), "reverse mutations old root did not match"); + assert_eq!(revert.root(), old_root, "reverse mutations new root did not match"); + assert_eq!( + revert.new_pairs, + BTreeMap::from_iter([(key_3, value_3)]), + "reverse mutations pairs did not match" + ); // Test batch updates, and that the order doesn't matter. let pairs = @@ -437,8 +467,16 @@ fn test_prospective_insertion() { root_empty, "prospective root for batch removal did not match actual root", ); - smt.apply_mutations(mutations).unwrap(); + let old_root = smt.root(); + let revert = apply_mutations(&mut smt, mutations); assert_eq!(smt.root(), root_empty, "mutations before and after apply did not match"); + assert_eq!(revert.old_root, smt.root(), "reverse mutations old root did not match"); + assert_eq!(revert.root(), old_root, "reverse mutations new root did not match"); + assert_eq!( + revert.new_pairs, + BTreeMap::from_iter([(key_1, value_1), (key_2, value_2), (key_3, value_3)]), + "reverse mutations pairs did not match" + ); let pairs = vec![(key_3, value_3), (key_1, value_1), (key_2, value_2)]; let mutations = smt.compute_mutations(pairs); @@ -447,6 +485,72 @@ fn test_prospective_insertion() { assert_eq!(smt.root(), root_3); } +#[test] +fn test_mutations_revert() { + let mut smt = Smt::default(); + + let key_1: RpoDigest = RpoDigest::from([ONE, ONE, ONE, Felt::new(1)]); + let key_2: RpoDigest = + RpoDigest::from([2_u32.into(), 2_u32.into(), 2_u32.into(), Felt::new(2)]); + let key_3: RpoDigest = + RpoDigest::from([0_u32.into(), 0_u32.into(), 0_u32.into(), Felt::new(3)]); + + let value_1 = [ONE; WORD_SIZE]; + let value_2 = [2_u32.into(); WORD_SIZE]; + let value_3 = [3_u32.into(); WORD_SIZE]; + + smt.insert(key_1, value_1); + smt.insert(key_2, value_2); + + let mutations = + smt.compute_mutations(vec![(key_1, EMPTY_WORD), (key_2, value_1), (key_3, value_3)]); + + let original = smt.clone(); + + let revert = smt.apply_mutations_with_reversion(mutations).unwrap(); + assert_eq!(revert.old_root, smt.root(), "reverse mutations old root did not match"); + assert_eq!(revert.root(), original.root(), "reverse mutations new root did not match"); + + smt.apply_mutations(revert).unwrap(); + + assert_eq!(smt, original, "SMT with applied revert mutations did not match original SMT"); +} + +#[test] +fn test_mutation_set_serialization() { + let mut smt = Smt::default(); + + let key_1: RpoDigest = RpoDigest::from([ONE, ONE, ONE, Felt::new(1)]); + let key_2: RpoDigest = + RpoDigest::from([2_u32.into(), 2_u32.into(), 2_u32.into(), Felt::new(2)]); + let key_3: RpoDigest = + RpoDigest::from([0_u32.into(), 0_u32.into(), 0_u32.into(), Felt::new(3)]); + + let value_1 = [ONE; WORD_SIZE]; + let value_2 = [2_u32.into(); WORD_SIZE]; + let value_3 = [3_u32.into(); WORD_SIZE]; + + smt.insert(key_1, value_1); + smt.insert(key_2, value_2); + + let mutations = + smt.compute_mutations(vec![(key_1, EMPTY_WORD), (key_2, value_1), (key_3, value_3)]); + + let serialized = mutations.to_bytes(); + let deserialized = + MutationSet::::read_from_bytes(&serialized).unwrap(); + + assert_eq!(deserialized, mutations, "deserialized mutations did not match original"); + + let revert = smt.apply_mutations_with_reversion(mutations).unwrap(); + + let serialized = revert.to_bytes(); + let deserialized = + MutationSet::::read_from_bytes(&serialized).unwrap(); + + assert_eq!(deserialized, revert, "deserialized mutations did not match original"); +} + /// Tests that 2 key-value pairs stored in the same leaf have the same path #[test] fn test_smt_path_to_keys_in_same_leaf_are_equal() { @@ -602,3 +706,19 @@ fn build_multiple_leaf_node(kv_pairs: &[(RpoDigest, Word)]) -> RpoDigest { Rpo256::hash_elements(&elements) } + +/// Applies mutations with and without reversion to the given SMT, comparing resulting SMTs, +/// returning mutation set for reversion. +fn apply_mutations( + smt: &mut Smt, + mutation_set: MutationSet, +) -> MutationSet { + let mut smt2 = smt.clone(); + + let reversion = smt.apply_mutations_with_reversion(mutation_set.clone()).unwrap(); + smt2.apply_mutations(mutation_set).unwrap(); + + assert_eq!(&smt2, smt); + + reversion +} diff --git a/src/merkle/smt/mod.rs b/src/merkle/smt/mod.rs index 02eb9b36..6543fb30 100644 --- a/src/merkle/smt/mod.rs +++ b/src/merkle/smt/mod.rs @@ -1,5 +1,7 @@ use alloc::{collections::BTreeMap, vec::Vec}; +use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; + use super::{EmptySubtreeRoots, InnerNodeInfo, MerkleError, MerklePath, NodeIndex}; use crate::{ hash::rpo::{Rpo256, RpoDigest}, @@ -135,7 +137,7 @@ pub(crate) trait SparseMerkleTree { if node_hash == *EmptySubtreeRoots::entry(DEPTH, node_depth) { // If a subtree is empty, then can remove the inner node, since it's equal to the // default value - self.remove_inner_node(index) + self.remove_inner_node(index); } else { self.insert_inner_node(index, InnerNode { left, right }); } @@ -241,7 +243,7 @@ pub(crate) trait SparseMerkleTree { } } - /// Apply the prospective mutations computed with [`SparseMerkleTree::compute_mutations()`] to + /// Applies the prospective mutations computed with [`SparseMerkleTree::compute_mutations()`] to /// this tree. /// /// # Errors @@ -275,8 +277,12 @@ pub(crate) trait SparseMerkleTree { for (index, mutation) in node_mutations { match mutation { - Removal => self.remove_inner_node(index), - Addition(node) => self.insert_inner_node(index, node), + Removal => { + self.remove_inner_node(index); + }, + Addition(node) => { + self.insert_inner_node(index, node); + }, } } @@ -289,6 +295,76 @@ pub(crate) trait SparseMerkleTree { Ok(()) } + /// Applies the prospective mutations computed with [`SparseMerkleTree::compute_mutations()`] to + /// this tree and returns the reverse mutation set. Applying the reverse mutation sets to the + /// updated tree will revert the changes. + /// + /// # Errors + /// If `mutations` was computed on a tree with a different root than this one, returns + /// [`MerkleError::ConflictingRoots`] with a two-item [`Vec`]. The first item is the root hash + /// the `mutations` were computed against, and the second item is the actual current root of + /// this tree. + fn apply_mutations_with_reversion( + &mut self, + mutations: MutationSet, + ) -> Result, MerkleError> + where + Self: Sized, + { + use NodeMutation::*; + let MutationSet { + old_root, + node_mutations, + new_pairs, + new_root, + } = mutations; + + // Guard against accidentally trying to apply mutations that were computed against a + // different tree, including a stale version of this tree. + if old_root != self.root() { + return Err(MerkleError::ConflictingRoots { + expected_root: self.root(), + actual_root: old_root, + }); + } + + let mut reverse_mutations = BTreeMap::new(); + for (index, mutation) in node_mutations { + match mutation { + Removal => { + if let Some(node) = self.remove_inner_node(index) { + reverse_mutations.insert(index, Addition(node)); + } + }, + Addition(node) => { + if let Some(old_node) = self.insert_inner_node(index, node) { + reverse_mutations.insert(index, Addition(old_node)); + } else { + reverse_mutations.insert(index, Removal); + } + }, + } + } + + let mut reverse_pairs = BTreeMap::new(); + for (key, value) in new_pairs { + if let Some(old_value) = self.insert_value(key.clone(), value) { + reverse_pairs.insert(key, old_value); + } else { + reverse_pairs.insert(key, Self::EMPTY_VALUE); + } + } + + self.set_root(new_root); + + Ok(MutationSet { + old_root: new_root, + node_mutations: reverse_mutations, + new_pairs: reverse_pairs, + new_root: old_root, + }) + } + // REQUIRED METHODS // --------------------------------------------------------------------------------------------- @@ -302,10 +378,10 @@ pub(crate) trait SparseMerkleTree { fn get_inner_node(&self, index: NodeIndex) -> InnerNode; /// Inserts an inner node at the given index - fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode); + fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) -> Option; /// Removes an inner node at the given index - fn remove_inner_node(&mut self, index: NodeIndex); + fn remove_inner_node(&mut self, index: NodeIndex) -> Option; /// Inserts a leaf node, and returns the value at the key if already exists fn insert_value(&mut self, key: Self::Key, value: Self::Value) -> Option; @@ -352,7 +428,7 @@ pub(crate) trait SparseMerkleTree { #[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub(crate) struct InnerNode { +pub struct InnerNode { pub left: RpoDigest, pub right: RpoDigest, } @@ -423,7 +499,7 @@ impl TryFrom for LeafIndex { /// [`MutationSet`] stores this type in relation to a [`NodeIndex`] to keep track of what changes /// need to occur at which node indices. #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum NodeMutation { +pub enum NodeMutation { /// Corresponds to [`SparseMerkleTree::remove_inner_node()`]. Removal, /// Corresponds to [`SparseMerkleTree::insert_inner_node()`]. @@ -456,9 +532,94 @@ pub struct MutationSet { } impl MutationSet { - /// Queries the root that was calculated during `SparseMerkleTree::compute_mutations()`. See + /// Returns the SMT root that was calculated during `SparseMerkleTree::compute_mutations()`. See /// that method for more information. pub fn root(&self) -> RpoDigest { self.new_root } + + /// Returns the SMT root before the mutations were applied. + pub fn old_root(&self) -> RpoDigest { + self.old_root + } + + /// Returns the set of inner nodes that need to be removed or added. + pub fn node_mutations(&self) -> &BTreeMap { + &self.node_mutations + } + + /// Returns the set of top-level key-value pairs that need to be added, updated or deleted + /// (i.e. set to `EMPTY_WORD`). + pub fn new_pairs(&self) -> &BTreeMap { + &self.new_pairs + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for InnerNode { + fn write_into(&self, target: &mut W) { + self.left.write_into(target); + self.right.write_into(target); + } +} + +impl Deserializable for InnerNode { + fn read_from(source: &mut R) -> Result { + let left = source.read()?; + let right = source.read()?; + + Ok(Self { left, right }) + } +} + +impl Serializable for NodeMutation { + fn write_into(&self, target: &mut W) { + match self { + NodeMutation::Removal => target.write_bool(false), + NodeMutation::Addition(inner_node) => { + target.write_bool(true); + inner_node.write_into(target); + }, + } + } +} + +impl Deserializable for NodeMutation { + fn read_from(source: &mut R) -> Result { + if source.read_bool()? { + let inner_node = source.read()?; + return Ok(NodeMutation::Addition(inner_node)); + } + + Ok(NodeMutation::Removal) + } +} + +impl Serializable for MutationSet { + fn write_into(&self, target: &mut W) { + target.write(self.old_root); + target.write(self.new_root); + self.node_mutations.write_into(target); + self.new_pairs.write_into(target); + } +} + +impl Deserializable + for MutationSet +{ + fn read_from(source: &mut R) -> Result { + let old_root = source.read()?; + let new_root = source.read()?; + let node_mutations = source.read()?; + let new_pairs = source.read()?; + + Ok(Self { + old_root, + node_mutations, + new_pairs, + new_root, + }) + } } diff --git a/src/merkle/smt/simple/mod.rs b/src/merkle/smt/simple/mod.rs index b52aac4b..2a25706e 100644 --- a/src/merkle/smt/simple/mod.rs +++ b/src/merkle/smt/simple/mod.rs @@ -221,7 +221,7 @@ impl SimpleSmt { >::compute_mutations(self, kv_pairs) } - /// Apply the prospective mutations computed with [`SimpleSmt::compute_mutations()`] to this + /// Applies the prospective mutations computed with [`SimpleSmt::compute_mutations()`] to this /// tree. /// /// # Errors @@ -236,6 +236,23 @@ impl SimpleSmt { >::apply_mutations(self, mutations) } + /// Applies the prospective mutations computed with [`SimpleSmt::compute_mutations()`] to + /// this tree and returns the reverse mutation set. + /// + /// Applying the reverse mutation sets to the updated tree will revert the changes. + /// + /// # Errors + /// If `mutations` was computed on a tree with a different root than this one, returns + /// [`MerkleError::ConflictingRoots`] with a two-item [`alloc::vec::Vec`]. The first item is the + /// root hash the `mutations` were computed against, and the second item is the actual + /// current root of this tree. + pub fn apply_mutations_with_reversion( + &mut self, + mutations: MutationSet, Word>, + ) -> Result, Word>, MerkleError> { + >::apply_mutations_with_reversion(self, mutations) + } + /// Inserts a subtree at the specified index. The depth at which the subtree is inserted is /// computed as `DEPTH - SUBTREE_DEPTH`. /// @@ -321,12 +338,12 @@ impl SparseMerkleTree for SimpleSmt { .unwrap_or_else(|| EmptySubtreeRoots::get_inner_node(DEPTH, index.depth())) } - fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) { - self.inner_nodes.insert(index, inner_node); + fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) -> Option { + self.inner_nodes.insert(index, inner_node) } - fn remove_inner_node(&mut self, index: NodeIndex) { - let _ = self.inner_nodes.remove(&index); + fn remove_inner_node(&mut self, index: NodeIndex) -> Option { + self.inner_nodes.remove(&index) } fn insert_value(&mut self, key: LeafIndex, value: Word) -> Option {