From c361c47634ea71e821801b7e9b5d92dab56196c2 Mon Sep 17 00:00:00 2001 From: tingyanmac Date: Mon, 9 Mar 2026 18:08:45 +0800 Subject: [PATCH 1/5] first commit --- .../smt/large_forest/property_tests.txt | 2 + .../merkle/smt/large_forest/property_tests.rs | 507 ++++++++++++++++-- 2 files changed, 454 insertions(+), 55 deletions(-) diff --git a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt b/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt index 7b3cd11859..6e95f88287 100644 --- a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt +++ b/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt @@ -1 +1,3 @@ cc abf493f7aece0d6db6c370e3c95651effc9f46f7a2f97e2dccddacfce2afd16d +cc d31635099aadf49dc7a4921c4753faafec3fba5d6cb282e6a0a19fa1ef6168bd # shrinks to lineage = LineageId([0, 0, 0, 0, 0, 0, 6, 134, 180, 253, 79, 1, 19, 71, 69, 134, 139, 188, 152, 143, 123, 5, 7, 41, 240, 8, 241, 230, 199, 31, 14, 89]), version = 2855610169485101264, key_1 = Word([15027228326572826394, 2449773096073471939, 1, 1]), key_2 = Word([0, 0, 7179825491337404832, 15166335543510979961]), key_3 = Word([0, 0, 0, 0]), key_4 = Word([1, 0, 1, 0]), value_1 = Word([0, 0, 4166498242271630998, 2112440444805112128]), value_2 = Word([14977426122414734217, 1, 11007100616583172448, 10694363109802328710]), value_3 = Word([4675660120051400524, 1, 0, 0]), value_4 = Word([0, 1, 1, 1]) +cc 3f3f39b97d8ceaf30003998cca4fbfe28169f62d760609b0d81ebfe8ff656e30 # shrinks to (lineage_1, lineage_2) = (LineageId([0, 0, 0, 0, 0, 0, 232, 122, 238, 30, 89, 44, 49, 28, 77, 219, 179, 112, 36, 72, 232, 250, 231, 93, 143, 215, 215, 159, 158, 226, 173, 97]), LineageId([198, 161, 102, 94, 148, 84, 129, 138, 193, 213, 139, 236, 8, 233, 37, 118, 159, 41, 91, 166, 31, 54, 75, 153, 8, 28, 29, 243, 37, 215, 31, 62])), version = 17312513013672310475, entries_1 = SmtUpdateBatch { operations: [Insert { key: Word([1, 1, 1, 8883089326044805234]), value: Word([6767366290328836935, 15554513986002896567, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 2176165409416381652, 1, 0]) }, Remove { key: Word([1259804000796618038, 0, 1, 3843141957296688256]) }, Remove { key: Word([16811392870151446286, 0, 11271959507560345653, 1]) }, Remove { key: Word([0, 0, 1, 0]) }, Remove { key: Word([11674674013029065268, 13349289720418832766, 0, 12703396317320847219]) }, Insert { key: Word([8740297440801378999, 5755130643906320059, 1940551248160640265, 1]), value: Word([1, 0, 16092175185287067279, 1]) }, Insert { key: Word([8426047218490858998, 0, 0, 0]), value: Word([0, 1, 1, 0]) }, Insert { key: Word([16782533609864651261, 1, 1, 1]), value: Word([9170079226015306968, 0, 11096257312260037529, 16937533143372446371]) }, Insert { key: Word([1, 6631202946458827552, 1, 342418232418702084]), value: Word([1, 10592302896319821639, 0, 1]) }, Insert { key: Word([0, 0, 17738165335782745808, 1]), value: Word([1, 13553872823046713887, 0, 426173808032763737]) }, Insert { key: Word([3056798850323584971, 0, 1, 0]), value: Word([1, 8974977867514097449, 1, 6398916685474410396]) }, Remove { key: Word([14771151508908011907, 9336954503104093067, 0, 1]) }, Insert { key: Word([0, 3152482219228125965, 18428152949576908923, 1]), value: Word([0, 1, 16328853536060354335, 5516886098361198582]) }, Remove { key: Word([11680993339473885522, 1, 0, 14983331548913249549]) }, Insert { key: Word([0, 12838858272800215179, 1, 3582745931826420989]), value: Word([1, 1, 7754227162734386628, 1]) }, Insert { key: Word([0, 14172366391665068676, 18325769936104074904, 0]), value: Word([13994202171586590279, 13610922986535222514, 0, 5310562433892520383]) }, Remove { key: Word([2878166138061031844, 10175019298922501635, 5901076228003682298, 17864652145186231593]) }, Remove { key: Word([0, 1, 10186185712864847348, 1]) }, Insert { key: Word([1, 0, 0, 1]), value: Word([6110178256982486739, 4633776758970289828, 1, 1]) }, Insert { key: Word([9227232819924409125, 0, 1, 0]), value: Word([0, 16494296012793388244, 0, 0]) }, Insert { key: Word([8756749806951214092, 1, 1, 0]), value: Word([6470559871559089613, 1, 16676586964611877144, 1]) }, Remove { key: Word([6763675011502344316, 7828531377988895909, 1, 0]) }, Insert { key: Word([0, 1, 17147933904208871429, 12156551885477264305]), value: Word([1, 0, 15927208852860788851, 1937034459769738155]) }, Remove { key: Word([11959833773583617419, 6550527153120532766, 1, 6409652662909982193]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([1, 11025252442381051939, 17996801927902025684, 0]) }, Insert { key: Word([0, 0, 0, 1]), value: Word([10829882057129557398, 0, 1, 0]) }, Remove { key: Word([0, 17224596873724788161, 1, 1]) }, Insert { key: Word([2517251090534613623, 1032838647773247847, 1, 0]), value: Word([8234826568210921271, 0, 0, 0]) }, Remove { key: Word([1, 17615748174619551130, 11315123232658366474, 1]) }, Remove { key: Word([1, 0, 5170534923570279187, 1]) }, Remove { key: Word([1, 1, 1, 1]) }, Insert { key: Word([9246629584295322947, 18267217368261909149, 0, 16547364337860238646]), value: Word([1, 1, 0, 12267082558708905705]) }, Remove { key: Word([1946282825081997120, 1, 0, 0]) }, Remove { key: Word([0, 1, 532733513929130186, 1]) }, Remove { key: Word([9857553359089925209, 3670284283835616883, 0, 0]) }, Remove { key: Word([1, 0, 1, 0]) }, Remove { key: Word([1, 1, 0, 6361012482410246433]) }, Insert { key: Word([9532617996200090147, 1, 0, 0]), value: Word([918302852707658488, 1, 1, 0]) }, Remove { key: Word([0, 12698500416087613665, 0, 0]) }, Insert { key: Word([10725903488572237539, 3986405299360553828, 18317390841946459680, 0]), value: Word([18324362828682629559, 1, 3791755819948731068, 0]) }, Remove { key: Word([7690046314558172722, 1, 0, 8976555652346565482]) }, Remove { key: Word([0, 3694848647706326005, 6907275645428848210, 2114365079230366038]) }, Remove { key: Word([1, 16624248387237044463, 0, 1]) }, Insert { key: Word([9858747110922163293, 1, 1, 0]), value: Word([7429215223544290804, 7203172924309807717, 8832347899817466171, 12227862553840304739]) }, Remove { key: Word([0, 15353586358356994393, 0, 1]) }, Insert { key: Word([16733654783878794994, 0, 1, 8817490243098122548]), value: Word([3302755326419096766, 1, 14456271924136641443, 624541902483893325]) }, Remove { key: Word([1, 0, 4322565726635173000, 0]) }, Remove { key: Word([1, 0, 1, 1]) }, Insert { key: Word([0, 11620518700581604434, 0, 3936702472300723942]), value: Word([10271301149461858574, 0, 0, 6167856395631110295]) }, Remove { key: Word([17745500194736444176, 1, 0, 1]) }, Remove { key: Word([1, 0, 1, 169046990262281750]) }, Remove { key: Word([12901896786967992602, 1, 8888704404116905844, 0]) }, Insert { key: Word([0, 0, 0, 5620820944710529380]), value: Word([13542152279682288187, 1, 0, 0]) }, Remove { key: Word([1, 13308017647193830867, 0, 0]) }, Remove { key: Word([0, 1, 0, 1]) }, Remove { key: Word([0, 15831058220127321905, 0, 6298834374391047111]) }, Insert { key: Word([1, 2989516875646118441, 15309294462176965199, 1]), value: Word([7100156772121765464, 7828570767521684837, 14054559306509700500, 0]) }, Insert { key: Word([1, 13178968465938844430, 0, 16740112912536554666]), value: Word([0, 0, 1, 13773162374166198758]) }, Remove { key: Word([13819415592460350916, 4110991220466578453, 13935769169910259208, 1247968354138240939]) }, Remove { key: Word([1, 1077820108018403536, 1, 7218107272674068759]) }, Insert { key: Word([8209322678624179904, 13312769554643468881, 0, 8065909178849987803]), value: Word([0, 0, 11106085756378135749, 0]) }, Remove { key: Word([2203060784316042879, 210631703595938611, 1, 15849550179177738248]) }, Remove { key: Word([1, 17877652313103439658, 1, 17906593519713382833]) }, Insert { key: Word([1, 1, 0, 1]), value: Word([0, 1050654849010352575, 1, 1]) }, Remove { key: Word([1, 0, 5789444504166640682, 1]) }, Remove { key: Word([2631979723406516104, 0, 1, 1]) }, Insert { key: Word([5074801108563332821, 0, 0, 2127856619604761360]), value: Word([1, 1, 14333194213371802331, 1]) }, Insert { key: Word([1, 0, 4123142830716620201, 1]), value: Word([1, 1, 12251416771322562311, 0]) }, Remove { key: Word([7422961120865521295, 0, 0, 1]) }, Insert { key: Word([0, 1, 15526885976431560668, 0]), value: Word([0, 0, 1103652432140073876, 1]) }, Remove { key: Word([0, 1, 0, 18057084434711087441]) }, Insert { key: Word([18090932232754919671, 1, 1, 2182033769059749819]), value: Word([14041409624716996402, 272032649099079167, 1, 1]) }, Insert { key: Word([0, 6530696107938298265, 1, 0]), value: Word([0, 1883754267540247886, 1438376661217377470, 1]) }, Remove { key: Word([0, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 8967200633631313717]) }, Insert { key: Word([1, 1, 17629172687690340677, 1]), value: Word([1, 1, 1, 1]) }, Remove { key: Word([1, 0, 9843055088344094980, 6184899735878474978]) }, Insert { key: Word([15205567444176662515, 11999795296703645169, 1, 1]), value: Word([6253828661616804009, 0, 1, 2690950586007670584]) }, Insert { key: Word([0, 0, 13212332576159737726, 1]), value: Word([5726548914108765535, 0, 12608015729409713237, 12133271336125274669]) }, Insert { key: Word([0, 10308125941342538868, 1, 12319592991551008321]), value: Word([1, 1, 1, 1]) }, Insert { key: Word([0, 0, 1, 1]), value: Word([6523003581400786739, 0, 1, 1]) }, Remove { key: Word([0, 1, 0, 9165150698919234700]) }, Remove { key: Word([2954595062061317082, 0, 8266074120551467604, 0]) }, Remove { key: Word([1, 16246360505526734245, 1088274137070565268, 1]) }, Remove { key: Word([1, 0, 0, 1612093581827769339]) }, Remove { key: Word([1, 7054147393396314739, 1, 1]) }, Insert { key: Word([14682601064246064708, 0, 1, 1]), value: Word([172873241072493585, 0, 7196593873867300827, 1]) }, Remove { key: Word([0, 1, 2415535828392031969, 1]) }, Insert { key: Word([0, 16195449028922367243, 1, 4663126652982682757]), value: Word([1, 1, 0, 0]) }, Insert { key: Word([15303551377489680285, 1, 3093673840854542579, 0]), value: Word([15436508119690333242, 0, 8570202100476749444, 1]) }, Insert { key: Word([0, 6358967043899839930, 1, 87985668382071701]), value: Word([1, 0, 0, 0]) }, Insert { key: Word([6973989483418510917, 0, 1, 15556954857246159095]), value: Word([1, 0, 1, 0]) }, Remove { key: Word([1, 8581172697127573282, 15679760397543993618, 4997708835887850835]) }, Insert { key: Word([1, 10127634745462951534, 14615711362014788974, 13625891244404001059]), value: Word([1, 1, 0, 1]) }, Remove { key: Word([119231955628608878, 1, 0, 0]) }, Remove { key: Word([0, 8395840794016908810, 0, 2393267629686164853]) }, Remove { key: Word([2715757173348252000, 14368032285363844983, 1, 0]) }, Remove { key: Word([0, 10837942435588081955, 1, 0]) }, Insert { key: Word([1, 0, 1, 9858489670168513556]), value: Word([1238801263357276659, 1, 1, 0]) }, Insert { key: Word([0, 0, 9096137242973502968, 3900733749693158622]), value: Word([1, 0, 0, 0]) }, Insert { key: Word([11962760985571401119, 1, 1, 0]), value: Word([0, 14034774889593425859, 0, 0]) }, Remove { key: Word([17785663483271411926, 3268327260889550807, 0, 14941297619696404060]) }] }, entries_2 = SmtUpdateBatch { operations: [Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([6030709746294843161, 1, 0, 9249190027185780944]) }, Remove { key: Word([1, 1, 0, 1]) }, Insert { key: Word([0, 14940699019530352844, 17244401288210311232, 1]), value: Word([1, 1, 4728536063683959304, 1]) }, Insert { key: Word([1, 0, 0, 1]), value: Word([1, 11237477384232652031, 0, 0]) }, Insert { key: Word([544277310183424411, 9780745374291801883, 461169023505808419, 0]), value: Word([0, 0, 1, 0]) }, Insert { key: Word([6985371516834926661, 1, 1, 8804912808114894470]), value: Word([1, 1, 1, 0]) }, Remove { key: Word([13906844383324634079, 1, 1, 1]) }, Insert { key: Word([0, 1, 16841499546813194809, 7679475560636344893]), value: Word([7904534373882342030, 0, 1, 15605546222765851284]) }, Remove { key: Word([6863742302631917843, 0, 1, 7664600298055605925]) }, Insert { key: Word([1, 2225565692356237783, 0, 5968355586438186696]), value: Word([5430892199895203567, 1, 0, 5349296607935364797]) }, Insert { key: Word([1, 1, 0, 0]), value: Word([1, 6091339048773328409, 662634167734567575, 2608861177032479353]) }, Remove { key: Word([9008888161938403017, 549409518434638957, 0, 0]) }, Remove { key: Word([1, 0, 1, 2355327944789950163]) }, Insert { key: Word([10500509532569639493, 1539801006067081576, 0, 15606942622413012853]), value: Word([1, 1684927388973166747, 0, 202216658505000562]) }, Remove { key: Word([0, 1, 0, 1]) }, Insert { key: Word([9585176846977709987, 1, 0, 0]), value: Word([10013981214750014425, 18104125189892071887, 0, 7098258435062455964]) }, Insert { key: Word([0, 9819380558124886789, 1, 0]), value: Word([1, 5854559179845332386, 1, 1]) }, Remove { key: Word([3495622059221320688, 1, 11036394830354471358, 1]) }, Remove { key: Word([0, 1, 864762228438406920, 0]) }, Remove { key: Word([0, 339969621938238828, 1, 0]) }, Remove { key: Word([3491755806906704453, 0, 0, 4237109598714448427]) }, Remove { key: Word([0, 1, 0, 0]) }] }, updates_1 = SmtUpdateBatch { operations: [Remove { key: Word([0, 15807772610888402188, 1, 1]) }] }, query_key = Word([0, 0, 0, 0]) diff --git a/miden-crypto/src/merkle/smt/large_forest/property_tests.rs b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs index aa776a9d32..533522f923 100644 --- a/miden-crypto/src/merkle/smt/large_forest/property_tests.rs +++ b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs @@ -10,8 +10,9 @@ use proptest::prelude::*; use crate::{ EMPTY_WORD, Felt, Map, ONE, Word, ZERO, merkle::smt::{ - ForestInMemoryBackend, ForestOperation, LargeSmtForest, LeafIndex, LineageId, - MAX_LEAF_ENTRIES, SMT_DEPTH, Smt, SmtUpdateBatch, TreeEntry, TreeId, VersionId, + Backend, ForestConfig, ForestInMemoryBackend, ForestOperation, LargeSmtForest, LeafIndex, + LineageId, MAX_LEAF_ENTRIES, RootInfo, SMT_DEPTH, Smt, SmtUpdateBatch, TreeEntry, TreeId, + VersionId, }, }; @@ -22,7 +23,10 @@ use crate::{ const MIN_BATCH_ENTRIES: usize = 0; /// The maximum number of entries that can be included in a batch. -const MAX_BATCH_ENTRIES: usize = 10_000; +const MAX_BATCH_ENTRIES: usize = 256; + +/// We reserve some headroom so property tests can safely add a few versions. +const MAX_START_VERSION: VersionId = u64::MAX - 8; // GENERATORS // ================================================================================================ @@ -32,9 +36,14 @@ fn arbitrary_lineage() -> impl Strategy { prop::array::uniform32(any::()).prop_map(LineageId::new) } -/// Generates an arbitrary version identifier. +/// Generates two distinct lineage identifiers. +fn arbitrary_distinct_lineages() -> impl Strategy { + (arbitrary_lineage(), arbitrary_lineage()).prop_filter("lineages must be distinct", |(a, b)| a != b) +} + +/// Generates an arbitrary version identifier that leaves room for increments. fn arbitrary_version() -> impl Strategy { - any::() + 0..=MAX_START_VERSION } /// Generates an arbitrary valid felt value. @@ -47,6 +56,11 @@ fn arbitrary_word() -> impl Strategy { prop_oneof![prop::array::uniform4(arbitrary_felt()).prop_map(Word::new), Just(Word::empty()),] } +/// Generates a non-empty word value. +fn arbitrary_non_empty_word() -> impl Strategy { + arbitrary_word().prop_filter("word must be non-empty", |word| *word != EMPTY_WORD) +} + /// Generates a random number of unique (non-overlapping) key-value pairs. /// /// Note that the generated pairs may well have the same leaf index. @@ -56,25 +70,24 @@ fn arbitrary_entries() -> impl Strategy> { MIN_BATCH_ENTRIES..=MAX_BATCH_ENTRIES, ) .prop_map(move |entries| { - // We want to avoid duplicate entries. It is well-defined, but it helps with test simplicity - // to avoid it here. let mut used_keys = BTreeSet::new(); let mut keys_in_leaf: Map, usize> = Map::default(); entries .into_iter() .flat_map(|(k, v)| { + if !used_keys.insert(k) { + return None; + } + let leaf_index = LeafIndex::from(k); let count = keys_in_leaf.entry(leaf_index).or_default(); - // We don't want to overfill a leaf. if *count >= MAX_LEAF_ENTRIES { return None; - } else { - *count += 1; } - used_keys.insert(k); + *count += 1; Some((k, v)) }) .collect() @@ -83,8 +96,8 @@ fn arbitrary_entries() -> impl Strategy> { /// Generates an arbitrary batch of updates to be performed on an arbitrary tree. fn arbitrary_batch() -> impl Strategy { - arbitrary_entries().prop_map(|e| { - SmtUpdateBatch::new(e.into_iter().map(|(k, v)| { + arbitrary_entries().prop_map(|entries| { + SmtUpdateBatch::new(entries.into_iter().map(|(k, v)| { if v == EMPTY_WORD { ForestOperation::remove(k) } else { @@ -94,11 +107,103 @@ fn arbitrary_batch() -> impl Strategy { }) } -// ENTRIES +// HELPERS +// ================================================================================================ + +fn build_tree(initial: SmtUpdateBatch) -> core::result::Result { + let mut tree = Smt::new(); + apply_batch(&mut tree, initial)?; + Ok(tree) +} + +fn apply_batch(tree: &mut Smt, batch: SmtUpdateBatch) -> core::result::Result<(), TestCaseError> { + let mutations = tree.compute_mutations(Vec::<(Word, Word)>::from(batch).into_iter()).map_err(to_fail)?; + tree.apply_mutations(mutations).map_err(to_fail) +} + +fn word_to_option(value: Word) -> Option { + if value == EMPTY_WORD { None } else { Some(value) } +} + +fn sorted_tree_entries(tree: &Smt) -> Vec { + tree.entries() + .map(|(key, value)| TreeEntry { + key: *key, + value: *value, + }) + .sorted() + .collect_vec() +} + +fn sorted_forest_entries( + forest: &LargeSmtForest, + tree: TreeId, +) -> core::result::Result, TestCaseError> { + Ok(forest.entries(tree).map_err(to_fail)?.sorted().collect_vec()) +} + +fn batch_keys(batch: &SmtUpdateBatch) -> Vec { + Vec::::from(batch.clone()).into_iter().map(|operation| operation.key()).collect() +} + +fn assert_tree_queries_match( + forest: &LargeSmtForest, + tree_id: TreeId, + reference: &Smt, + sample_keys: &[Word], + assert_openings: bool, +) -> core::result::Result<(), TestCaseError> { + let forest_entries = sorted_forest_entries(forest, tree_id)?; + let reference_entries = sorted_tree_entries(reference); + let reference_entry_count = reference_entries.len(); + prop_assert_eq!(forest_entries, reference_entries); + prop_assert_eq!(forest.entry_count(tree_id).map_err(to_fail)?, reference_entry_count); + + for key in sample_keys { + prop_assert_eq!( + forest.get(tree_id, *key).map_err(to_fail)?, + word_to_option(reference.get_value(key)) + ); + if assert_openings { + prop_assert_eq!(forest.open(tree_id, *key).map_err(to_fail)?, reference.open(key)); + } + } + + Ok(()) +} + +fn assert_lineage_metadata( + forest: &LargeSmtForest, + lineage: LineageId, + versions: &[(VersionId, Word)], +) -> core::result::Result<(), TestCaseError> { + let (latest_version, latest_root) = versions.last().copied().expect("lineage must be non-empty"); + + prop_assert_eq!(forest.latest_version(lineage), Some(latest_version)); + prop_assert_eq!(forest.latest_root(lineage), Some(latest_root)); + prop_assert_eq!( + forest.lineage_roots(lineage).expect("lineage must be present").collect_vec(), + versions.iter().rev().map(|(_, root)| *root).collect_vec() + ); + + for (idx, (version, root)) in versions.iter().enumerate() { + let tree = TreeId::new(lineage, *version); + let expected = if idx + 1 == versions.len() { + RootInfo::LatestVersion(*root) + } else { + RootInfo::HistoricalVersion(*root) + }; + prop_assert_eq!(forest.root_info(tree), expected); + } + + Ok(()) +} + +// PROPERTY TESTS // ================================================================================================ proptest! { - #![proptest_config(ProptestConfig::with_cases(20))] + #![proptest_config(ProptestConfig::with_cases(12))] /// This test ensures that the `entries` iterator for the forest always returns the exact same /// values as the `entries` iterator over a basic SMT with the same state. @@ -109,44 +214,19 @@ proptest! { entries_v1 in arbitrary_batch(), entries_v2 in arbitrary_batch(), ) { - // We now create a forest and add the lineage to it using the first set of entries. let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; forest.add_lineage(lineage, version, entries_v1.clone()).map_err(to_fail)?; - let tree_info = - forest.update_tree(lineage, version + 1, entries_v2.clone()).map_err(to_fail)?; - - // We then create two auxiliary trees to work with, to compare our results against. - let mut tree_v1 = Smt::new(); - let tree_v1_mutations = - tree_v1.compute_mutations(Vec::from(entries_v1).into_iter()).map_err(to_fail)?; - tree_v1.apply_mutations(tree_v1_mutations).map_err(to_fail)?; + let tree_info = forest.update_tree(lineage, version + 1, entries_v2.clone()).map_err(to_fail)?; + let tree_v1 = build_tree(entries_v1.clone())?; let mut tree_v2 = tree_v1.clone(); - let tree_v2_mutations = - tree_v2.compute_mutations(Vec::from(entries_v2).into_iter()).map_err(to_fail)?; - tree_v2.apply_mutations(tree_v2_mutations).map_err(to_fail)?; + apply_batch(&mut tree_v2, entries_v2)?; - // Iterating over the historical version of the lineage in the forest should produce exactly - // the same entries as iterating over V1 of our test tree. let old_version = TreeId::new(lineage, version); - let forest_entries = forest.entries(old_version).map_err(to_fail)?.sorted().collect_vec(); - let tree_entries = tree_v1 - .entries() - .map(|(k, v)| TreeEntry { key: *k, value: *v }) - .sorted() - .collect_vec(); - assert_eq!(forest_entries, tree_entries); - - // Iterating over the newest version of the lineage in the forest should provide exactly the - // same entries as iterating over V2 of our test tree. + prop_assert_eq!(sorted_forest_entries(&forest, old_version)?, sorted_tree_entries(&tree_v1)); + let current_version = TreeId::new(lineage, tree_info.version()); - let forest_entries = forest.entries(current_version).map_err(to_fail)?.sorted().collect_vec(); - let tree_entries = tree_v2 - .entries() - .map(|(k, v)| TreeEntry { key: *k, value: *v }) - .sorted() - .collect_vec(); - assert_eq!(forest_entries, tree_entries); + prop_assert_eq!(sorted_forest_entries(&forest, current_version)?, sorted_tree_entries(&tree_v2)); } /// This test ensures that the `entries` iterator for the forest will never return entries where @@ -158,21 +238,338 @@ proptest! { entries_v1 in arbitrary_batch(), entries_v2 in arbitrary_batch(), ) { - // We now create a forest and add the lineage to it using the first set of entries. let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; forest.add_lineage(lineage, version, entries_v1.clone()).map_err(to_fail)?; - let tree_info = - forest.update_tree(lineage, version + 1, entries_v2.clone()).map_err(to_fail)?; + let tree_info = forest.update_tree(lineage, version + 1, entries_v2).map_err(to_fail)?; - // Iterating over the historical version of the lineage in the forest should produce exactly - // the same entries as iterating over V1 of our test tree. let old_version = TreeId::new(lineage, version); - assert!(forest.entries(old_version).map_err(to_fail)?.all(|e| e.value != EMPTY_WORD)); + prop_assert!(forest.entries(old_version).map_err(to_fail)?.all(|entry| entry.value != EMPTY_WORD)); - // Iterating over the newest version of the lineage in the forest should provide exactly the - // same entries as iterating over V2 of our test tree. let current_version = TreeId::new(lineage, tree_info.version()); - assert!(forest.entries(current_version).map_err(to_fail)?.all(|e| e.value != EMPTY_WORD)); + prop_assert!(forest.entries(current_version).map_err(to_fail)?.all(|entry| entry.value != EMPTY_WORD)); + } + + /// This test cross-checks the core query APIs (`get`, `open`, `entries`, `entry_count`) and the + /// associated metadata APIs against a reference SMT model across current and historical versions. + #[test] + fn queries_and_metadata_match_reference_model( + lineage in arbitrary_lineage(), + version in arbitrary_version(), + entries_v1 in arbitrary_batch(), + entries_v2 in arbitrary_batch(), + random_key in arbitrary_word(), + ) { + let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; + let add_result = forest.add_lineage(lineage, version, entries_v1.clone()).map_err(to_fail)?; + let update_result = forest.update_tree(lineage, version + 1, entries_v2.clone()).map_err(to_fail)?; + + let tree_v1 = build_tree(entries_v1.clone())?; + let mut tree_current = tree_v1.clone(); + apply_batch(&mut tree_current, entries_v2.clone())?; + + let mut sample_keys = batch_keys(&entries_v1); + sample_keys.extend(batch_keys(&entries_v2)); + sample_keys.push(random_key); + sample_keys.sort(); + sample_keys.dedup(); + + assert_tree_queries_match(&forest, TreeId::new(lineage, version), &tree_v1, &sample_keys, false)?; + assert_tree_queries_match( + &forest, + TreeId::new(lineage, update_result.version()), + &tree_current, + &sample_keys, + true, + )?; + + let expected_versions = if tree_current.root() == tree_v1.root() { + vec![(version, tree_v1.root())] + } else { + vec![(version, add_result.root()), (version + 1, tree_current.root())] + }; + + assert_lineage_metadata(&forest, lineage, &expected_versions)?; + prop_assert_eq!(forest.lineage_count(), 1); + prop_assert_eq!(forest.tree_count(), expected_versions.len()); + prop_assert_eq!( + forest.roots().map(|root| (root.lineage(), root.value())).sorted().collect_vec(), + expected_versions.iter().map(|(_, root)| (lineage, *root)).sorted().collect_vec() + ); + + let unknown_lineage = LineageId::new([0xAA; 32]); + prop_assume!(unknown_lineage != lineage); + prop_assert_eq!(forest.latest_version(unknown_lineage), None); + prop_assert_eq!(forest.latest_root(unknown_lineage), None); + prop_assert!(forest.lineage_roots(unknown_lineage).is_none()); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::Missing); + prop_assert_eq!(forest.root_info(TreeId::new(unknown_lineage, version)), RootInfo::Missing); + } + + /// This test validates single-lineage mutation semantics, including duplicate additions, bad + /// version updates, and no-op updates preserving the observable forest state. + #[test] + fn add_lineage_and_update_tree_preserve_state_on_failures( + lineage in arbitrary_lineage(), + version in arbitrary_version(), + initial_entries in arbitrary_batch(), + extra_entries in arbitrary_batch(), + random_key in arbitrary_word(), + ) { + let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; + forest.add_lineage(lineage, version, initial_entries.clone()).map_err(to_fail)?; + let reference = build_tree(initial_entries.clone())?; + + let mut sample_keys = batch_keys(&initial_entries); + sample_keys.extend(batch_keys(&extra_entries)); + sample_keys.push(random_key); + sample_keys.sort(); + sample_keys.dedup(); + + let duplicate = forest.add_lineage(lineage, version + 1, extra_entries.clone()); + prop_assert!(duplicate.is_err()); + prop_assert_eq!(duplicate.unwrap_err().to_string(), format!("Duplicate lineage ID {lineage} provided")); + assert_tree_queries_match(&forest, TreeId::new(lineage, version), &reference, &sample_keys, true)?; + prop_assert_eq!(forest.lineage_count(), 1); + prop_assert_eq!(forest.tree_count(), 1); + + let bad_version = forest.update_tree(lineage, version, extra_entries); + prop_assert!(bad_version.is_err()); + prop_assert_eq!( + bad_version.unwrap_err().to_string(), + format!("Version {version} is not newer than latest-known {version}") + ); + assert_tree_queries_match(&forest, TreeId::new(lineage, version), &reference, &sample_keys, true)?; + + let no_op = forest.update_tree(lineage, version + 1, SmtUpdateBatch::empty()).map_err(to_fail)?; + prop_assert_eq!(no_op.version(), version); + prop_assert_eq!(no_op.root(), reference.root()); + prop_assert_eq!(forest.latest_version(lineage), Some(version)); + prop_assert_eq!(forest.tree_count(), 1); + } + + /// This test validates batch updates across multiple lineages and ensures invalid batches do + /// not partially modify forest state. + #[test] + fn update_forest_matches_reference_model_and_preserves_state_on_error( + (lineage_1, lineage_2) in arbitrary_distinct_lineages(), + version in arbitrary_version(), + entries_1 in arbitrary_batch(), + entries_2 in arbitrary_batch(), + updates_1 in arbitrary_batch(), + updates_2 in arbitrary_batch(), + query_key in arbitrary_word(), + ) { + let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; + forest.add_lineage(lineage_1, version, entries_1.clone()).map_err(to_fail)?; + forest.add_lineage(lineage_2, version, entries_2.clone()).map_err(to_fail)?; + + let tree_1_v1 = build_tree(entries_1.clone())?; + let tree_2_v1 = build_tree(entries_2.clone())?; + + let mut expected_tree_1 = tree_1_v1.clone(); + let mut expected_tree_2 = tree_2_v1.clone(); + apply_batch(&mut expected_tree_1, updates_1.clone())?; + apply_batch(&mut expected_tree_2, updates_2.clone())?; + + let mut forest_updates = crate::merkle::smt::SmtForestUpdateBatch::empty(); + forest_updates.add_operations(lineage_1, Vec::::from(updates_1.clone()).into_iter()); + forest_updates.add_operations(lineage_2, Vec::::from(updates_2.clone()).into_iter()); + let results = forest.update_forest(version + 1, forest_updates).map_err(to_fail)?; + prop_assert_eq!(results.len(), 2); + + let mut sample_keys = batch_keys(&entries_1); + sample_keys.extend(batch_keys(&entries_2)); + sample_keys.extend(batch_keys(&updates_1)); + sample_keys.extend(batch_keys(&updates_2)); + sample_keys.push(query_key); + sample_keys.sort(); + sample_keys.dedup(); + + let versions_1 = if expected_tree_1.root() == tree_1_v1.root() { + vec![(version, tree_1_v1.root())] + } else { + vec![(version, tree_1_v1.root()), (version + 1, expected_tree_1.root())] + }; + let versions_2 = if expected_tree_2.root() == tree_2_v1.root() { + vec![(version, tree_2_v1.root())] + } else { + vec![(version, tree_2_v1.root()), (version + 1, expected_tree_2.root())] + }; + + assert_tree_queries_match( + &forest, + TreeId::new(lineage_1, versions_1.last().expect("non-empty").0), + &expected_tree_1, + &sample_keys, + true, + )?; + assert_tree_queries_match( + &forest, + TreeId::new(lineage_2, versions_2.last().expect("non-empty").0), + &expected_tree_2, + &sample_keys, + true, + )?; + assert_lineage_metadata(&forest, lineage_1, &versions_1)?; + assert_lineage_metadata(&forest, lineage_2, &versions_2)?; + + let roots = forest.roots().map(|root| (root.lineage(), root.value())).sorted().collect_vec(); + let mut expected_roots = versions_1.iter().map(|(_, root)| (lineage_1, *root)).collect_vec(); + expected_roots.extend(versions_2.iter().map(|(_, root)| (lineage_2, *root))); + expected_roots.sort(); + prop_assert_eq!(roots, expected_roots); + prop_assert_eq!(forest.lineage_count(), 2); + prop_assert_eq!(forest.tree_count(), versions_1.len() + versions_2.len()); + + let unknown_lineage = LineageId::new([0x55; 32]); + prop_assume!(unknown_lineage != lineage_1 && unknown_lineage != lineage_2); + let mut invalid_updates = crate::merkle::smt::SmtForestUpdateBatch::empty(); + invalid_updates.add_operations( + lineage_1, + Vec::::from(SmtUpdateBatch::new([ForestOperation::insert(query_key, Word::new([ONE; 4]))].into_iter())).into_iter(), + ); + invalid_updates.operations(unknown_lineage).add_insert(query_key, Word::new([ONE; 4])); + let invalid_result = forest.update_forest(version + 2, invalid_updates); + prop_assert!(invalid_result.is_err()); + + assert_tree_queries_match( + &forest, + TreeId::new(lineage_1, versions_1.last().expect("non-empty").0), + &expected_tree_1, + &sample_keys, + true, + )?; + assert_tree_queries_match( + &forest, + TreeId::new(lineage_2, versions_2.last().expect("non-empty").0), + &expected_tree_2, + &sample_keys, + true, + )?; + } + + /// This test validates constructor behavior when loading from a pre-populated backend. The + /// forest should load the latest tree state, but not reconstruct historical versions. + #[test] + fn new_loads_latest_backend_state_without_history( + (lineage_1, lineage_2) in arbitrary_distinct_lineages(), + version in arbitrary_version(), + entries_1 in arbitrary_batch(), + entries_2 in arbitrary_batch(), + updates_1 in arbitrary_batch(), + query_key in arbitrary_word(), + ) { + let mut backend = ForestInMemoryBackend::new(); + backend.add_lineage(lineage_1, version, entries_1.clone()).map_err(to_fail)?; + backend.add_lineage(lineage_2, version, entries_2.clone()).map_err(to_fail)?; + backend.update_tree(lineage_1, version + 1, updates_1.clone()).map_err(to_fail)?; + + let forest = LargeSmtForest::new(backend).map_err(to_fail)?; + + let mut expected_tree_1 = build_tree(entries_1.clone())?; + apply_batch(&mut expected_tree_1, updates_1.clone())?; + let expected_tree_2 = build_tree(entries_2.clone())?; + let latest_version_1 = if expected_tree_1.root() == build_tree(entries_1.clone())?.root() { + version + } else { + version + 1 + }; + + let mut sample_keys = batch_keys(&entries_1); + sample_keys.extend(batch_keys(&entries_2)); + sample_keys.extend(batch_keys(&updates_1)); + sample_keys.push(query_key); + sample_keys.sort(); + sample_keys.dedup(); + + assert_tree_queries_match(&forest, TreeId::new(lineage_1, latest_version_1), &expected_tree_1, &sample_keys, true)?; + assert_tree_queries_match(&forest, TreeId::new(lineage_2, version), &expected_tree_2, &sample_keys, true)?; + prop_assert_eq!(forest.lineage_count(), 2); + prop_assert_eq!(forest.tree_count(), 2); + prop_assert_eq!(forest.latest_version(lineage_1), Some(latest_version_1)); + prop_assert_eq!(forest.latest_root(lineage_1), Some(expected_tree_1.root())); + let expected_root_info = if latest_version_1 == version { + RootInfo::Missing + } else { + RootInfo::LatestVersion(expected_tree_1.root()) + }; + prop_assert_eq!(forest.root_info(TreeId::new(lineage_1, version + 1)), expected_root_info); + } + + /// This test validates history retention under custom configuration and the semantics of + /// explicit truncation. + #[test] + fn with_config_and_truncate_limit_retained_versions( + lineage in arbitrary_lineage(), + version in arbitrary_version(), + key_1 in arbitrary_word(), + key_2 in arbitrary_word(), + key_3 in arbitrary_word(), + key_4 in arbitrary_word(), + value_1 in arbitrary_non_empty_word(), + value_2 in arbitrary_non_empty_word(), + value_3 in arbitrary_non_empty_word(), + value_4 in arbitrary_non_empty_word(), + ) { + prop_assume!(key_1 != key_2 && key_1 != key_3 && key_1 != key_4); + prop_assume!(key_2 != key_3 && key_2 != key_4); + prop_assume!(key_3 != key_4); + + let config = ForestConfig::default().with_max_history_versions(2); + let mut forest = LargeSmtForest::with_config(ForestInMemoryBackend::new(), config).map_err(to_fail)?; + forest.add_lineage( + lineage, + version, + SmtUpdateBatch::new([ForestOperation::insert(key_1, value_1)].into_iter()), + ).map_err(to_fail)?; + forest.update_tree( + lineage, + version + 1, + SmtUpdateBatch::new([ForestOperation::insert(key_2, value_2)].into_iter()), + ).map_err(to_fail)?; + forest.update_tree( + lineage, + version + 2, + SmtUpdateBatch::new([ForestOperation::insert(key_3, value_3)].into_iter()), + ).map_err(to_fail)?; + forest.update_tree( + lineage, + version + 3, + SmtUpdateBatch::new([ForestOperation::insert(key_4, value_4)].into_iter()), + ).map_err(to_fail)?; + + let mut tree_v1 = Smt::new(); + apply_batch(&mut tree_v1, SmtUpdateBatch::new([ForestOperation::insert(key_1, value_1)].into_iter()))?; + let mut tree_v2 = tree_v1.clone(); + apply_batch(&mut tree_v2, SmtUpdateBatch::new([ForestOperation::insert(key_2, value_2)].into_iter()))?; + let mut tree_v3 = tree_v2.clone(); + apply_batch(&mut tree_v3, SmtUpdateBatch::new([ForestOperation::insert(key_3, value_3)].into_iter()))?; + let mut tree_v4 = tree_v3.clone(); + apply_batch(&mut tree_v4, SmtUpdateBatch::new([ForestOperation::insert(key_4, value_4)].into_iter()))?; + + let sample_keys = vec![key_1, key_2, key_3, key_4]; + assert_tree_queries_match(&forest, TreeId::new(lineage, version + 2), &tree_v3, &sample_keys, false)?; + assert_tree_queries_match(&forest, TreeId::new(lineage, version + 3), &tree_v4, &sample_keys, true)?; + prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); + prop_assert_eq!(forest.latest_root(lineage), Some(tree_v4.root())); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 3)), RootInfo::LatestVersion(tree_v4.root())); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::HistoricalVersion(tree_v3.root())); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version)), RootInfo::Missing); + + forest.truncate(version + 2); + assert_tree_queries_match(&forest, TreeId::new(lineage, version + 2), &tree_v3, &sample_keys, false)?; + assert_tree_queries_match(&forest, TreeId::new(lineage, version + 3), &tree_v4, &sample_keys, true)?; + prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 3)), RootInfo::LatestVersion(tree_v4.root())); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::HistoricalVersion(tree_v3.root())); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 1)), RootInfo::Missing); + + forest.truncate(version + 3); + assert_tree_queries_match(&forest, TreeId::new(lineage, version + 3), &tree_v4, &sample_keys, true)?; + prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); + prop_assert_eq!(forest.latest_root(lineage), Some(tree_v4.root())); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 3)), RootInfo::LatestVersion(tree_v4.root())); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::Missing); } } From da7ee9b56763802b8ec92242f457c584fbd086c4 Mon Sep 17 00:00:00 2001 From: tingyanmac Date: Tue, 10 Mar 2026 15:34:47 +0800 Subject: [PATCH 2/5] delete unnecessary contents --- .../merkle/smt/large_forest/property_tests.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt b/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt index 6e95f88287..72708fc0ca 100644 --- a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt +++ b/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt @@ -1,3 +1 @@ -cc abf493f7aece0d6db6c370e3c95651effc9f46f7a2f97e2dccddacfce2afd16d -cc d31635099aadf49dc7a4921c4753faafec3fba5d6cb282e6a0a19fa1ef6168bd # shrinks to lineage = LineageId([0, 0, 0, 0, 0, 0, 6, 134, 180, 253, 79, 1, 19, 71, 69, 134, 139, 188, 152, 143, 123, 5, 7, 41, 240, 8, 241, 230, 199, 31, 14, 89]), version = 2855610169485101264, key_1 = Word([15027228326572826394, 2449773096073471939, 1, 1]), key_2 = Word([0, 0, 7179825491337404832, 15166335543510979961]), key_3 = Word([0, 0, 0, 0]), key_4 = Word([1, 0, 1, 0]), value_1 = Word([0, 0, 4166498242271630998, 2112440444805112128]), value_2 = Word([14977426122414734217, 1, 11007100616583172448, 10694363109802328710]), value_3 = Word([4675660120051400524, 1, 0, 0]), value_4 = Word([0, 1, 1, 1]) -cc 3f3f39b97d8ceaf30003998cca4fbfe28169f62d760609b0d81ebfe8ff656e30 # shrinks to (lineage_1, lineage_2) = (LineageId([0, 0, 0, 0, 0, 0, 232, 122, 238, 30, 89, 44, 49, 28, 77, 219, 179, 112, 36, 72, 232, 250, 231, 93, 143, 215, 215, 159, 158, 226, 173, 97]), LineageId([198, 161, 102, 94, 148, 84, 129, 138, 193, 213, 139, 236, 8, 233, 37, 118, 159, 41, 91, 166, 31, 54, 75, 153, 8, 28, 29, 243, 37, 215, 31, 62])), version = 17312513013672310475, entries_1 = SmtUpdateBatch { operations: [Insert { key: Word([1, 1, 1, 8883089326044805234]), value: Word([6767366290328836935, 15554513986002896567, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 2176165409416381652, 1, 0]) }, Remove { key: Word([1259804000796618038, 0, 1, 3843141957296688256]) }, Remove { key: Word([16811392870151446286, 0, 11271959507560345653, 1]) }, Remove { key: Word([0, 0, 1, 0]) }, Remove { key: Word([11674674013029065268, 13349289720418832766, 0, 12703396317320847219]) }, Insert { key: Word([8740297440801378999, 5755130643906320059, 1940551248160640265, 1]), value: Word([1, 0, 16092175185287067279, 1]) }, Insert { key: Word([8426047218490858998, 0, 0, 0]), value: Word([0, 1, 1, 0]) }, Insert { key: Word([16782533609864651261, 1, 1, 1]), value: Word([9170079226015306968, 0, 11096257312260037529, 16937533143372446371]) }, Insert { key: Word([1, 6631202946458827552, 1, 342418232418702084]), value: Word([1, 10592302896319821639, 0, 1]) }, Insert { key: Word([0, 0, 17738165335782745808, 1]), value: Word([1, 13553872823046713887, 0, 426173808032763737]) }, Insert { key: Word([3056798850323584971, 0, 1, 0]), value: Word([1, 8974977867514097449, 1, 6398916685474410396]) }, Remove { key: Word([14771151508908011907, 9336954503104093067, 0, 1]) }, Insert { key: Word([0, 3152482219228125965, 18428152949576908923, 1]), value: Word([0, 1, 16328853536060354335, 5516886098361198582]) }, Remove { key: Word([11680993339473885522, 1, 0, 14983331548913249549]) }, Insert { key: Word([0, 12838858272800215179, 1, 3582745931826420989]), value: Word([1, 1, 7754227162734386628, 1]) }, Insert { key: Word([0, 14172366391665068676, 18325769936104074904, 0]), value: Word([13994202171586590279, 13610922986535222514, 0, 5310562433892520383]) }, Remove { key: Word([2878166138061031844, 10175019298922501635, 5901076228003682298, 17864652145186231593]) }, Remove { key: Word([0, 1, 10186185712864847348, 1]) }, Insert { key: Word([1, 0, 0, 1]), value: Word([6110178256982486739, 4633776758970289828, 1, 1]) }, Insert { key: Word([9227232819924409125, 0, 1, 0]), value: Word([0, 16494296012793388244, 0, 0]) }, Insert { key: Word([8756749806951214092, 1, 1, 0]), value: Word([6470559871559089613, 1, 16676586964611877144, 1]) }, Remove { key: Word([6763675011502344316, 7828531377988895909, 1, 0]) }, Insert { key: Word([0, 1, 17147933904208871429, 12156551885477264305]), value: Word([1, 0, 15927208852860788851, 1937034459769738155]) }, Remove { key: Word([11959833773583617419, 6550527153120532766, 1, 6409652662909982193]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([1, 11025252442381051939, 17996801927902025684, 0]) }, Insert { key: Word([0, 0, 0, 1]), value: Word([10829882057129557398, 0, 1, 0]) }, Remove { key: Word([0, 17224596873724788161, 1, 1]) }, Insert { key: Word([2517251090534613623, 1032838647773247847, 1, 0]), value: Word([8234826568210921271, 0, 0, 0]) }, Remove { key: Word([1, 17615748174619551130, 11315123232658366474, 1]) }, Remove { key: Word([1, 0, 5170534923570279187, 1]) }, Remove { key: Word([1, 1, 1, 1]) }, Insert { key: Word([9246629584295322947, 18267217368261909149, 0, 16547364337860238646]), value: Word([1, 1, 0, 12267082558708905705]) }, Remove { key: Word([1946282825081997120, 1, 0, 0]) }, Remove { key: Word([0, 1, 532733513929130186, 1]) }, Remove { key: Word([9857553359089925209, 3670284283835616883, 0, 0]) }, Remove { key: Word([1, 0, 1, 0]) }, Remove { key: Word([1, 1, 0, 6361012482410246433]) }, Insert { key: Word([9532617996200090147, 1, 0, 0]), value: Word([918302852707658488, 1, 1, 0]) }, Remove { key: Word([0, 12698500416087613665, 0, 0]) }, Insert { key: Word([10725903488572237539, 3986405299360553828, 18317390841946459680, 0]), value: Word([18324362828682629559, 1, 3791755819948731068, 0]) }, Remove { key: Word([7690046314558172722, 1, 0, 8976555652346565482]) }, Remove { key: Word([0, 3694848647706326005, 6907275645428848210, 2114365079230366038]) }, Remove { key: Word([1, 16624248387237044463, 0, 1]) }, Insert { key: Word([9858747110922163293, 1, 1, 0]), value: Word([7429215223544290804, 7203172924309807717, 8832347899817466171, 12227862553840304739]) }, Remove { key: Word([0, 15353586358356994393, 0, 1]) }, Insert { key: Word([16733654783878794994, 0, 1, 8817490243098122548]), value: Word([3302755326419096766, 1, 14456271924136641443, 624541902483893325]) }, Remove { key: Word([1, 0, 4322565726635173000, 0]) }, Remove { key: Word([1, 0, 1, 1]) }, Insert { key: Word([0, 11620518700581604434, 0, 3936702472300723942]), value: Word([10271301149461858574, 0, 0, 6167856395631110295]) }, Remove { key: Word([17745500194736444176, 1, 0, 1]) }, Remove { key: Word([1, 0, 1, 169046990262281750]) }, Remove { key: Word([12901896786967992602, 1, 8888704404116905844, 0]) }, Insert { key: Word([0, 0, 0, 5620820944710529380]), value: Word([13542152279682288187, 1, 0, 0]) }, Remove { key: Word([1, 13308017647193830867, 0, 0]) }, Remove { key: Word([0, 1, 0, 1]) }, Remove { key: Word([0, 15831058220127321905, 0, 6298834374391047111]) }, Insert { key: Word([1, 2989516875646118441, 15309294462176965199, 1]), value: Word([7100156772121765464, 7828570767521684837, 14054559306509700500, 0]) }, Insert { key: Word([1, 13178968465938844430, 0, 16740112912536554666]), value: Word([0, 0, 1, 13773162374166198758]) }, Remove { key: Word([13819415592460350916, 4110991220466578453, 13935769169910259208, 1247968354138240939]) }, Remove { key: Word([1, 1077820108018403536, 1, 7218107272674068759]) }, Insert { key: Word([8209322678624179904, 13312769554643468881, 0, 8065909178849987803]), value: Word([0, 0, 11106085756378135749, 0]) }, Remove { key: Word([2203060784316042879, 210631703595938611, 1, 15849550179177738248]) }, Remove { key: Word([1, 17877652313103439658, 1, 17906593519713382833]) }, Insert { key: Word([1, 1, 0, 1]), value: Word([0, 1050654849010352575, 1, 1]) }, Remove { key: Word([1, 0, 5789444504166640682, 1]) }, Remove { key: Word([2631979723406516104, 0, 1, 1]) }, Insert { key: Word([5074801108563332821, 0, 0, 2127856619604761360]), value: Word([1, 1, 14333194213371802331, 1]) }, Insert { key: Word([1, 0, 4123142830716620201, 1]), value: Word([1, 1, 12251416771322562311, 0]) }, Remove { key: Word([7422961120865521295, 0, 0, 1]) }, Insert { key: Word([0, 1, 15526885976431560668, 0]), value: Word([0, 0, 1103652432140073876, 1]) }, Remove { key: Word([0, 1, 0, 18057084434711087441]) }, Insert { key: Word([18090932232754919671, 1, 1, 2182033769059749819]), value: Word([14041409624716996402, 272032649099079167, 1, 1]) }, Insert { key: Word([0, 6530696107938298265, 1, 0]), value: Word([0, 1883754267540247886, 1438376661217377470, 1]) }, Remove { key: Word([0, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 8967200633631313717]) }, Insert { key: Word([1, 1, 17629172687690340677, 1]), value: Word([1, 1, 1, 1]) }, Remove { key: Word([1, 0, 9843055088344094980, 6184899735878474978]) }, Insert { key: Word([15205567444176662515, 11999795296703645169, 1, 1]), value: Word([6253828661616804009, 0, 1, 2690950586007670584]) }, Insert { key: Word([0, 0, 13212332576159737726, 1]), value: Word([5726548914108765535, 0, 12608015729409713237, 12133271336125274669]) }, Insert { key: Word([0, 10308125941342538868, 1, 12319592991551008321]), value: Word([1, 1, 1, 1]) }, Insert { key: Word([0, 0, 1, 1]), value: Word([6523003581400786739, 0, 1, 1]) }, Remove { key: Word([0, 1, 0, 9165150698919234700]) }, Remove { key: Word([2954595062061317082, 0, 8266074120551467604, 0]) }, Remove { key: Word([1, 16246360505526734245, 1088274137070565268, 1]) }, Remove { key: Word([1, 0, 0, 1612093581827769339]) }, Remove { key: Word([1, 7054147393396314739, 1, 1]) }, Insert { key: Word([14682601064246064708, 0, 1, 1]), value: Word([172873241072493585, 0, 7196593873867300827, 1]) }, Remove { key: Word([0, 1, 2415535828392031969, 1]) }, Insert { key: Word([0, 16195449028922367243, 1, 4663126652982682757]), value: Word([1, 1, 0, 0]) }, Insert { key: Word([15303551377489680285, 1, 3093673840854542579, 0]), value: Word([15436508119690333242, 0, 8570202100476749444, 1]) }, Insert { key: Word([0, 6358967043899839930, 1, 87985668382071701]), value: Word([1, 0, 0, 0]) }, Insert { key: Word([6973989483418510917, 0, 1, 15556954857246159095]), value: Word([1, 0, 1, 0]) }, Remove { key: Word([1, 8581172697127573282, 15679760397543993618, 4997708835887850835]) }, Insert { key: Word([1, 10127634745462951534, 14615711362014788974, 13625891244404001059]), value: Word([1, 1, 0, 1]) }, Remove { key: Word([119231955628608878, 1, 0, 0]) }, Remove { key: Word([0, 8395840794016908810, 0, 2393267629686164853]) }, Remove { key: Word([2715757173348252000, 14368032285363844983, 1, 0]) }, Remove { key: Word([0, 10837942435588081955, 1, 0]) }, Insert { key: Word([1, 0, 1, 9858489670168513556]), value: Word([1238801263357276659, 1, 1, 0]) }, Insert { key: Word([0, 0, 9096137242973502968, 3900733749693158622]), value: Word([1, 0, 0, 0]) }, Insert { key: Word([11962760985571401119, 1, 1, 0]), value: Word([0, 14034774889593425859, 0, 0]) }, Remove { key: Word([17785663483271411926, 3268327260889550807, 0, 14941297619696404060]) }] }, entries_2 = SmtUpdateBatch { operations: [Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([6030709746294843161, 1, 0, 9249190027185780944]) }, Remove { key: Word([1, 1, 0, 1]) }, Insert { key: Word([0, 14940699019530352844, 17244401288210311232, 1]), value: Word([1, 1, 4728536063683959304, 1]) }, Insert { key: Word([1, 0, 0, 1]), value: Word([1, 11237477384232652031, 0, 0]) }, Insert { key: Word([544277310183424411, 9780745374291801883, 461169023505808419, 0]), value: Word([0, 0, 1, 0]) }, Insert { key: Word([6985371516834926661, 1, 1, 8804912808114894470]), value: Word([1, 1, 1, 0]) }, Remove { key: Word([13906844383324634079, 1, 1, 1]) }, Insert { key: Word([0, 1, 16841499546813194809, 7679475560636344893]), value: Word([7904534373882342030, 0, 1, 15605546222765851284]) }, Remove { key: Word([6863742302631917843, 0, 1, 7664600298055605925]) }, Insert { key: Word([1, 2225565692356237783, 0, 5968355586438186696]), value: Word([5430892199895203567, 1, 0, 5349296607935364797]) }, Insert { key: Word([1, 1, 0, 0]), value: Word([1, 6091339048773328409, 662634167734567575, 2608861177032479353]) }, Remove { key: Word([9008888161938403017, 549409518434638957, 0, 0]) }, Remove { key: Word([1, 0, 1, 2355327944789950163]) }, Insert { key: Word([10500509532569639493, 1539801006067081576, 0, 15606942622413012853]), value: Word([1, 1684927388973166747, 0, 202216658505000562]) }, Remove { key: Word([0, 1, 0, 1]) }, Insert { key: Word([9585176846977709987, 1, 0, 0]), value: Word([10013981214750014425, 18104125189892071887, 0, 7098258435062455964]) }, Insert { key: Word([0, 9819380558124886789, 1, 0]), value: Word([1, 5854559179845332386, 1, 1]) }, Remove { key: Word([3495622059221320688, 1, 11036394830354471358, 1]) }, Remove { key: Word([0, 1, 864762228438406920, 0]) }, Remove { key: Word([0, 339969621938238828, 1, 0]) }, Remove { key: Word([3491755806906704453, 0, 0, 4237109598714448427]) }, Remove { key: Word([0, 1, 0, 0]) }] }, updates_1 = SmtUpdateBatch { operations: [Remove { key: Word([0, 15807772610888402188, 1, 1]) }] }, query_key = Word([0, 0, 0, 0]) +cc abf493f7aece0d6db6c370e3c95651effc9f46f7a2f97e2dccddacfce2afd16d \ No newline at end of file From a4835889986c488c9c1866b2f823346b2c32bfda Mon Sep 17 00:00:00 2001 From: AlexWaker <0xlunasterling@proton.me> Date: Tue, 24 Mar 2026 11:51:38 +0800 Subject: [PATCH 3/5] a huge adjustment --- .../smt/large_forest/property_tests.txt | 9 +- .../src/merkle/smt/large_forest/mod.rs | 29 +- .../merkle/smt/large_forest/property_tests.rs | 658 ++++++++---------- .../src/merkle/smt/large_forest/test_utils.rs | 123 +++- .../src/merkle/smt/large_forest/tests.rs | 4 +- 5 files changed, 443 insertions(+), 380 deletions(-) diff --git a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt b/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt index 72708fc0ca..a89baa179c 100644 --- a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt +++ b/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt @@ -1 +1,8 @@ -cc abf493f7aece0d6db6c370e3c95651effc9f46f7a2f97e2dccddacfce2afd16d \ No newline at end of file +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 4b7894584e6fed73ffc34213caa4ac8d90270c5e0a0cfeb0f62f154db1ebe44a # shrinks to lineage = LineageId([0, 0, 0, 0, 3, 12, 220, 141, 40, 211, 122, 87, 131, 76, 153, 223, 199, 1, 239, 233, 232, 138, 6, 56, 101, 239, 74, 14, 66, 235, 2, 58]), version = 6375444638265261073, key_1 = Word([1, 1, 1, 0]), key_2 = Word([17809828939116071465, 1, 4238248194923747156, 2627680782911860790]), key_3 = Word([1, 0, 17443273806143551851, 0]), key_4 = Word([15936525627748290972, 275898757835181111, 1, 0]), value_1 = Word([1, 11143404628732975980, 1, 1]), value_2 = Word([0, 930814439367801590, 14399587934779981401, 1]), value_3 = Word([17890142681827673407, 573402031812891261, 0, 13368909212370054120]), value_4 = Word([196994630634329390, 1, 6270331038003912332, 12792655455856645741]) +cc da7e7d0ada4185620bc2ae942f6b82b87c5ede94e72f07d77b0ad0b4693c7915 # shrinks to lineage = LineageId([0, 0, 0, 0, 0, 0, 19, 207, 66, 110, 140, 52, 164, 109, 32, 128, 183, 137, 217, 233, 90, 52, 103, 122, 244, 156, 24, 145, 131, 213, 72, 114]), version = 14378414525250753652, entries_v1 = SmtUpdateBatch { operations: [Insert { key: Word([4715263322365442641, 4841246979176943439, 0, 13348185607200837129]), value: Word([1, 5568866945875495768, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 15613517144968074488, 0, 1]) }, Remove { key: Word([6784195750361934512, 1, 4880529614489168186, 16893881042139654907]) }, Insert { key: Word([0, 6466511433887533777, 0, 1]), value: Word([17878133050888212350, 1, 15393790679626110881, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 0, 7797119101044420374]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 14155528324853465935, 12235058683017086980]) }, Insert { key: Word([0, 1, 0, 0]), value: Word([17492047924635082928, 1, 0, 8993653636289850364]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 1, 5818418131541643395]), value: Word([1, 1, 18284251167398315778, 1]) }, Remove { key: Word([1, 17359342700057268629, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([8592837165946161407, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 1, 0, 1]) }, Remove { key: Word([1, 16380040976954053337, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 2080851406930176171, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 0, 17517724945192259983, 1]) }, Remove { key: Word([0, 16242203334174013302, 1756530009948396796, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([16108619294158194701, 1784819492617802517, 1, 0]) }, Insert { key: Word([1, 1, 14054389673399095056, 0]), value: Word([1, 1, 1727710126018317349, 14846722981467667084]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 0, 11763672703183244684]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([14417587859422312233, 0, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 16736242669094307415, 14416751148720608515]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 14974858188074865258, 0, 0]), value: Word([5420890705143951672, 0, 13063801731867560085, 0]) }, Remove { key: Word([1, 9399736085123660465, 286315038662195539, 1]) }, Insert { key: Word([0, 0, 7695936059184519594, 8540535462340968011]), value: Word([0, 1920389745064574245, 0, 1]) }, Remove { key: Word([16118396807909684551, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 0, 0]) }, Remove { key: Word([10203034533161946108, 1, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([10879162206579897656, 0, 0, 1]) }, Remove { key: Word([1, 0, 623716606286544957, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 5347737516742995769, 0]) }, Insert { key: Word([2534353175334127491, 1, 1, 1]), value: Word([8250724196395461455, 9355749963543018864, 9428953335003919114, 2446150955899649643]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 1, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 8845688326510475834, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([9483271017630993280, 1, 12128089580524682601, 1]) }, Remove { key: Word([15133786835186669016, 1, 1, 9141360008066921158]) }, Insert { key: Word([0, 1, 6034403946245642539, 12759208261588747989]), value: Word([1, 11183608821684979529, 0, 8419951467689494709]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 14930758959435094009, 15435804836674243848, 9780799933936898434]) }, Insert { key: Word([15955352879420194064, 0, 1, 0]), value: Word([1, 16968342451115143108, 1, 1]) }, Insert { key: Word([0, 10623551768161530180, 985721976669860576, 1]), value: Word([13090362831404762652, 12986816529963769346, 1, 11199290601714394278]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1, 0, 9499451410496508218, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 8816586531185414554, 1, 0]) }, Insert { key: Word([1, 5255482087259444081, 2169237974033036249, 14801118069429349049]), value: Word([1, 0, 18275997481822985485, 16456532796430420392]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([7578679113505053091, 12348855039133126620, 0, 3772227775799021454]) }, Insert { key: Word([0, 0, 1, 0]), value: Word([1, 0, 11664317378431862633, 9836843618832932413]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 3946762668109591974, 1, 599545304698496531]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([10700183636552547742, 1, 0, 1]) }, Insert { key: Word([0, 17546231225743711151, 0, 1]), value: Word([1, 7565207980961864337, 1, 2395036494917290299]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([4617743312688166538, 1, 1, 5777272497844076859]) }, Remove { key: Word([7451811908981275514, 5551806376086585374, 0, 0]) }, Insert { key: Word([0, 0, 1, 1]), value: Word([1, 10891893702695903520, 2699854409412634711, 0]) }, Remove { key: Word([1, 1, 8965341523656334880, 1]) }, Remove { key: Word([1, 1, 1, 14974041991530408605]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([4905703101863438197, 0, 1, 1]) }, Remove { key: Word([0, 1, 0, 0]) }, Remove { key: Word([1, 1, 6580548392849240511, 423957863778862200]) }, Remove { key: Word([0, 15146204795695467943, 13550506586618352499, 0]) }, Remove { key: Word([1, 0, 13835538468799972163, 4214645277282946139]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1588412766835784998, 1, 12982803496921393259, 2074847052564173435]) }, Insert { key: Word([3199829192986355867, 0, 0, 0]), value: Word([6671148298710261256, 0, 15755236232554191055, 8025642313709472676]) }, Remove { key: Word([1, 0, 15412117974050591368, 10347831653105819699]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 1, 0, 1]), value: Word([7372854460875585977, 16086438733249589539, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 5554808828071876536, 1, 1]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([1, 18204887486473566505, 1, 16377926231648314218]) }, Remove { key: Word([11540412879012764862, 0, 0, 1]) }, Remove { key: Word([1, 0, 6514524391152019104, 18118806069779959543]) }, Insert { key: Word([1, 1, 15055514482527026051, 1]), value: Word([4648223986840732704, 3569739232840310777, 0, 1]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([0, 5542969459758511979, 16682858730518930377, 2910815683514337258]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 2087821471747433455, 1, 0]) }, Insert { key: Word([0, 0, 2860490218596303863, 15888741258585145042]), value: Word([1, 1, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 0, 0, 1]) }, Remove { key: Word([1, 7360996536975129650, 0, 0]) }, Remove { key: Word([0, 0, 0, 8890886382688162039]) }, Remove { key: Word([1, 17449711356883577557, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 9027282040533900585, 0, 0]) }, Remove { key: Word([0, 5954516411873846089, 3359854980925249277, 17476760461068760315]) }, Insert { key: Word([5282361522484264121, 1, 0, 0]), value: Word([1, 17568752897574512740, 17531278156256391534, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([10115099205580265432, 1, 1, 7143071174086017069]) }, Insert { key: Word([7149270656809029628, 6794270497261542375, 0, 0]), value: Word([1, 1, 1, 6217679770684509513]) }, Insert { key: Word([8187402099352561135, 0, 1, 7970209752017100117]), value: Word([2943917165102628533, 0, 1, 3284476271619099284]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([1, 1, 10037703311177033596, 846661881122981572]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 1, 2007256076077404014]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 8664480436385648093, 0]) }, Insert { key: Word([0, 0, 1, 14602422081042044909]), value: Word([11476774637830631792, 7420219053118809811, 10031325639504350980, 7411888692432835382]) }, Remove { key: Word([1, 0, 1, 4530593761340126717]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([79023662508776939, 16983937845552071205, 0, 8876741277042651923]), value: Word([1, 1, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([2312126386353390709, 5054447914725148197, 1, 216081414777227431]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 12867642070796805043, 0]) }, Remove { key: Word([0, 1, 1, 4641783074465389612]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([15995512101060171146, 0, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 5317777551529895, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 14244901757456224170, 0, 17826699297702846720]) }, Remove { key: Word([17455838129484622230, 0, 15357367460782955055, 395945022856420185]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1975876493361877352, 3740003930233290837, 5812449452985664043, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([6407986762022463692, 1, 15654402736953619656, 1]) }, Remove { key: Word([13599177958961154988, 0, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 0, 0, 3361259231170337184]), value: Word([5775145840382474735, 1, 0, 1]) }, Insert { key: Word([0, 0, 0, 8714773746010194488]), value: Word([1, 6868227923334612836, 1, 2552465560106869889]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 13083054148557890003, 1, 0]), value: Word([1, 0, 8958200922778054055, 1]) }, Remove { key: Word([12909419010340823623, 0, 9511793988410931362, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 1, 1]) }, Insert { key: Word([1, 11939841116458585496, 5252451298814573952, 1928197503387806608]), value: Word([1, 762920920770422436, 14860682348660100088, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 13180808326107638627, 0, 0]), value: Word([0, 1, 1, 10631678530453352674]) }, Remove { key: Word([0, 0, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 10326153129179073670, 6792997568552657542]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 7894976204057781596, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 14758316206389932443, 0, 1]), value: Word([0, 1, 1, 2941518481653533627]) }, Insert { key: Word([0, 8774693584700768110, 16360300427338571912, 15322864309907762649]), value: Word([11982941647898410180, 1, 1, 1386528248779926529]) }, Remove { key: Word([1, 0, 12615213812497121945, 0]) }, Insert { key: Word([0, 13687345636211620643, 1, 1]), value: Word([0, 15338645266308888470, 1, 0]) }, Remove { key: Word([1, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 1, 0, 0]), value: Word([3155128086588310054, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([3628059670242864462, 0, 14803462570730943148, 7661027732520056092]), value: Word([1, 8651191585692664897, 2552679505911886381, 1]) }, Insert { key: Word([1, 1, 16827335939319472459, 9261369697388433021]), value: Word([1, 204373952672213217, 1, 9745946111673251089]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 1, 0]) }, Insert { key: Word([12024798715915071567, 1983883130368086218, 417433103337935958, 5650950723731425983]), value: Word([12122558640554734720, 0, 1, 16895158236963652742]) }, Insert { key: Word([1, 1, 8733301627474754875, 11976552330908318698]), value: Word([1, 1, 1503146448857043088, 0]) }, Remove { key: Word([0, 1, 0, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 1, 0, 2073573561362233363]), value: Word([0, 9198232018703017413, 1, 0]) }, Remove { key: Word([0, 3393120253725548875, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 11270524037504036460, 1, 9415899648920862086]), value: Word([1, 12865093919488147242, 1, 0]) }, Remove { key: Word([1, 1, 1860633986519085183, 1]) }, Insert { key: Word([12844757103765701837, 15464333193451077969, 1, 10679199020359865059]), value: Word([16329022990122342840, 1, 1, 16651586460628861108]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([11876742985186359465, 0, 1, 489924416806554387]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 1, 0]) }, Remove { key: Word([0, 0, 0, 1]) }, Remove { key: Word([0, 17340343089548661158, 1736568239065064584, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 0, 1, 8740670642349073662]) }, Remove { key: Word([10485280542043754300, 1, 0, 414544917731547023]) }, Insert { key: Word([1, 932295115101442175, 0, 1]), value: Word([0, 0, 12180430153013489892, 1]) }, Remove { key: Word([13236088366182083669, 18026265043841859645, 0, 0]) }, Remove { key: Word([0, 0, 13244010672048553378, 0]) }] }, entries_v2 = SmtUpdateBatch { operations: [Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 0, 130630469781385363, 2918612881082043543]), value: Word([0, 1, 12683451587785776512, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 5857059368556401714, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 967733294636046527, 4703772855648996154, 16357961517182088807]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([7671380349212115108, 3232623150437798774, 1, 1]) }, Insert { key: Word([1, 9395255692305235741, 1, 0]), value: Word([2468461131572902796, 5539652486494237729, 0, 3583158755848592036]) }, Remove { key: Word([0, 3133064241058569931, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([2198723372894752938, 1, 7766860407492519429, 16353168434517105610]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([15576036095111912527, 1, 15133903514472688154, 5087521421828266809]) }, Insert { key: Word([1, 12428969342704184935, 0, 2261153628469780607]), value: Word([0, 7928430322549734970, 1, 18336068418833454237]) }, Insert { key: Word([0, 13099950430763069862, 0, 9930194365803599023]), value: Word([6788715641981323007, 13585923073706478028, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 0, 16622901136535004036]) }, Insert { key: Word([0, 9639404142061142784, 1, 0]), value: Word([1, 0, 0, 0]) }] }, random_key = Word([0, 0, 0, 0]) diff --git a/miden-crypto/src/merkle/smt/large_forest/mod.rs b/miden-crypto/src/merkle/smt/large_forest/mod.rs index ffb0e2ea96..9ff0a5e3fc 100644 --- a/miden-crypto/src/merkle/smt/large_forest/mod.rs +++ b/miden-crypto/src/merkle/smt/large_forest/mod.rs @@ -325,7 +325,7 @@ pub use operation::{ForestOperation, SmtForestUpdateBatch, SmtUpdateBatch}; pub use root::{LineageId, RootInfo, TreeEntry, TreeId, TreeWithRoot, VersionId}; use crate::{ - EMPTY_WORD, Map, Set, Word, + EMPTY_WORD, Felt, Map, Set, Word, merkle::{ NodeIndex, SparseMerklePath, smt::{ @@ -617,7 +617,7 @@ impl LargeSmtForest { // We compute the new leaf and new path by applying any reversions from the history on // top of the current state. let new_leaf = self.merge_leaves(opening.leaf(), view)?; - let new_path = self.merge_paths(leaf_index, opening.path(), view)?; + let new_path = self.merge_paths(tree.lineage(), leaf_index, opening.path(), view)?; // Finally we can compose our combined opening. Ok(SmtProof::new(new_path, new_leaf)?) @@ -1052,13 +1052,16 @@ impl LargeSmtForest { ); // Any entries that are still empty at this point should be removed. - Ok(SmtLeaf::new(leaf_entries.into_iter().collect(), full_tree_leaf.index())?) + let mut entries = leaf_entries.into_iter().collect::>(); + entries.sort_by_key(|(key, value)| (*key, *value)); + Ok(SmtLeaf::new(entries, full_tree_leaf.index())?) } /// Applies any historical changes contained in `history_view` on top of the merkle path /// obtained from the full tree to produce the correct path for a historical opening. fn merge_paths( &self, + lineage: LineageId, leaf_index: LeafIndex, full_tree_path: &SparseMerklePath, history_view: HistoryView, @@ -1074,6 +1077,26 @@ impl LargeSmtForest { // If there is a historical value we need to use it, and so we write it to the // correct slot in the path elements array. path_elems[depth as usize - 1] = *historical_value; + } else if path_node_ix.depth() == SMT_DEPTH { + let sibling_leaf_index = LeafIndex::new_max_depth(path_node_ix.position()); + let sibling_leaf_changed = history_view + .changed_keys() + .any(|(key, _)| LeafIndex::from(key) == sibling_leaf_index); + + if sibling_leaf_changed { + let sibling_key = Word::new([ + Felt::new(0), + Felt::new(0), + Felt::new(0), + Felt::new(sibling_leaf_index.position()), + ]); + let sibling_opening = self.backend.open(lineage, sibling_key)?; + let sibling_leaf = self.merge_leaves(sibling_opening.leaf(), history_view)?; + path_elems[depth as usize - 1] = sibling_leaf.hash(); + } else { + let bounded_depth = NonZeroU8::new(depth).expect("depth ∈ 1 ..= SMT_DEPTH]"); + path_elems[depth as usize - 1] = full_tree_path.at_depth(bounded_depth)?; + } } else { // If there isn't a historical value, we should delegate to the corresponding // element in the path from the full-tree opening. diff --git a/miden-crypto/src/merkle/smt/large_forest/property_tests.rs b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs index 666335eb5b..88136da1a3 100644 --- a/miden-crypto/src/merkle/smt/large_forest/property_tests.rs +++ b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs @@ -11,188 +11,226 @@ use crate::{ merkle::smt::{ Backend, ForestConfig, ForestInMemoryBackend, ForestOperation, LargeSmtForest, LargeSmtForestError, LineageId, RootInfo, Smt, SmtForestUpdateBatch, SmtUpdateBatch, - TreeEntry, TreeId, VersionId, + TreeId, large_forest::test_utils::{ - arbitrary_batch, arbitrary_lineage, arbitrary_version, arbitrary_word, to_fail, + apply_batch, arbitrary_batch, arbitrary_distinct_lineages, arbitrary_lineage, + arbitrary_non_empty_word, arbitrary_version, arbitrary_word, + assert_lineage_metadata, assert_tree_queries_match, batch_keys, build_tree, + sorted_forest_entries, sorted_tree_entries, to_fail, }, }, }; -// HELPERS +// PROPERTY TESTS // ================================================================================================ -/// Generates two distinct lineage identifiers. -fn arbitrary_distinct_lineages() -> impl Strategy { - (arbitrary_lineage(), arbitrary_lineage()) - .prop_filter("lineages must be distinct", |(a, b)| a != b) -} - -/// Generates a non-empty word value. -fn arbitrary_non_empty_word() -> impl Strategy { - arbitrary_word().prop_filter("word must be non-empty", |word| *word != EMPTY_WORD) -} - -fn build_tree(initial: SmtUpdateBatch) -> core::result::Result { - let mut tree = Smt::new(); - apply_batch(&mut tree, initial)?; - Ok(tree) -} - -fn apply_batch(tree: &mut Smt, batch: SmtUpdateBatch) -> core::result::Result<(), TestCaseError> { - let mutations = tree - .compute_mutations(Vec::<(Word, Word)>::from(batch).into_iter()) - .map_err(to_fail)?; - tree.apply_mutations(mutations).map_err(to_fail) -} - -fn word_to_option(value: Word) -> Option { - if value == EMPTY_WORD { None } else { Some(value) } -} - -fn sorted_tree_entries(tree: &Smt) -> Vec { - tree.entries() - .map(|(key, value)| TreeEntry { key: *key, value: *value }) - .sorted() - .collect_vec() -} +proptest! { + #![proptest_config(ProptestConfig::with_cases(10))] -fn sorted_forest_entries( - forest: &LargeSmtForest, - tree: TreeId, -) -> core::result::Result, TestCaseError> { - Ok(forest - .entries(tree) - .map_err(to_fail)? - .collect::>>() - .map_err(to_fail)? - .into_iter() - .sorted() - .collect_vec()) -} + /// This test validates constructor behavior when loading from a pre-populated backend. The + /// forest should load the latest tree state, but not reconstruct historical versions. + #[test] + fn new_loads_latest_backend_state_without_history( + (lineage_1, lineage_2) in arbitrary_distinct_lineages(), + version in arbitrary_version(), + entries_1 in arbitrary_batch(), + entries_2 in arbitrary_batch(), + updates_1 in arbitrary_batch(), + query_key in arbitrary_word(), + ) { + let mut backend = ForestInMemoryBackend::new(); + backend.add_lineage(lineage_1, version, entries_1.clone()).map_err(to_fail)?; + backend.add_lineage(lineage_2, version, entries_2.clone()).map_err(to_fail)?; + backend.update_tree(lineage_1, version + 1, updates_1.clone()).map_err(to_fail)?; -fn batch_keys(batch: &SmtUpdateBatch) -> Vec { - batch.clone().into_iter().map(|operation| operation.key()).collect() -} + let forest = LargeSmtForest::new(backend).map_err(to_fail)?; -fn assert_tree_queries_match( - forest: &LargeSmtForest, - tree_id: TreeId, - reference: &Smt, - sample_keys: &[Word], - assert_openings: bool, -) -> core::result::Result<(), TestCaseError> { - let forest_entries = sorted_forest_entries(forest, tree_id)?; - let reference_entries = sorted_tree_entries(reference); - let reference_entry_count = reference_entries.len(); - prop_assert_eq!(forest_entries, reference_entries); - prop_assert_eq!(forest.entry_count(tree_id).map_err(to_fail)?, reference_entry_count); - - for key in sample_keys { - prop_assert_eq!( - forest.get(tree_id, *key).map_err(to_fail)?, - word_to_option(reference.get_value(key)) - ); - if assert_openings { - prop_assert_eq!(forest.open(tree_id, *key).map_err(to_fail)?, reference.open(key)); - } - } + let tree_1_v1 = build_tree(entries_1.clone())?; + let mut expected_tree_1 = tree_1_v1.clone(); + apply_batch(&mut expected_tree_1, updates_1.clone())?; + let expected_tree_2 = build_tree(entries_2.clone())?; + let latest_version_1 = if expected_tree_1.root() == tree_1_v1.root() { + version + } else { + version + 1 + }; - Ok(()) -} + let mut sample_keys = batch_keys(&entries_1); + sample_keys.extend(batch_keys(&entries_2)); + sample_keys.extend(batch_keys(&updates_1)); + sample_keys.push(query_key); + sample_keys.sort(); + sample_keys.dedup(); -fn assert_lineage_metadata( - forest: &LargeSmtForest, - lineage: LineageId, - versions: &[(VersionId, Word)], -) -> core::result::Result<(), TestCaseError> { - let (latest_version, latest_root) = - versions.last().copied().expect("lineage must be non-empty"); - - prop_assert_eq!(forest.latest_version(lineage), Some(latest_version)); - prop_assert_eq!(forest.latest_root(lineage), Some(latest_root)); - prop_assert_eq!( - forest.lineage_roots(lineage).expect("lineage must be present").collect_vec(), - versions.iter().rev().map(|(_, root)| *root).collect_vec() - ); - - for (idx, (version, root)) in versions.iter().enumerate() { - let tree = TreeId::new(lineage, *version); - let expected = if idx + 1 == versions.len() { - RootInfo::LatestVersion(*root) + assert_tree_queries_match( + &forest, + TreeId::new(lineage_1, latest_version_1), + &expected_tree_1, + &sample_keys, + true, + )?; + assert_tree_queries_match( + &forest, + TreeId::new(lineage_2, version), + &expected_tree_2, + &sample_keys, + true, + )?; + prop_assert_eq!(forest.lineage_count(), 2); + prop_assert_eq!(forest.tree_count(), 2); + prop_assert_eq!(forest.latest_version(lineage_1), Some(latest_version_1)); + prop_assert_eq!(forest.latest_root(lineage_1), Some(expected_tree_1.root())); + let expected_root_info = if latest_version_1 == version { + RootInfo::Missing } else { - RootInfo::HistoricalVersion(*root) + RootInfo::LatestVersion(expected_tree_1.root()) }; - prop_assert_eq!(forest.root_info(tree), expected); + prop_assert_eq!( + forest.root_info(TreeId::new(lineage_1, version + 1)), + expected_root_info + ); } - Ok(()) -} - -// PROPERTY TESTS -// ================================================================================================ - -proptest! { - #![proptest_config(ProptestConfig::with_cases(10))] - - /// This test ensures that the `entries` iterator for the forest always returns the exact same - /// values as the `entries` iterator over a basic SMT with the same state. + /// This test validates history retention under custom configuration and the semantics of + /// explicit truncation. #[test] - fn entries_correct( + fn with_config_and_truncate_limit_retained_versions( lineage in arbitrary_lineage(), version in arbitrary_version(), - entries_v1 in arbitrary_batch(), - entries_v2 in arbitrary_batch(), + key_1 in arbitrary_word(), + key_2 in arbitrary_word(), + key_3 in arbitrary_word(), + key_4 in arbitrary_word(), + value_1 in arbitrary_non_empty_word(), + value_2 in arbitrary_non_empty_word(), + value_3 in arbitrary_non_empty_word(), + value_4 in arbitrary_non_empty_word(), ) { - let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; - forest.add_lineage(lineage, version, entries_v1.clone()).map_err(to_fail)?; - let tree_info = - forest.update_tree(lineage, version + 1, entries_v2.clone()).map_err(to_fail)?; + prop_assume!(key_1 != key_2 && key_1 != key_3 && key_1 != key_4); + prop_assume!(key_2 != key_3 && key_2 != key_4); + prop_assume!(key_3 != key_4); - let tree_v1 = build_tree(entries_v1.clone())?; + let config = ForestConfig::default().with_max_history_versions(2); + let mut forest = + LargeSmtForest::with_config(ForestInMemoryBackend::new(), config).map_err(to_fail)?; + forest + .add_lineage( + lineage, + version, + SmtUpdateBatch::new([ForestOperation::insert(key_1, value_1)].into_iter()), + ) + .map_err(to_fail)?; + forest + .update_tree( + lineage, + version + 1, + SmtUpdateBatch::new([ForestOperation::insert(key_2, value_2)].into_iter()), + ) + .map_err(to_fail)?; + forest + .update_tree( + lineage, + version + 2, + SmtUpdateBatch::new([ForestOperation::insert(key_3, value_3)].into_iter()), + ) + .map_err(to_fail)?; + forest + .update_tree( + lineage, + version + 3, + SmtUpdateBatch::new([ForestOperation::insert(key_4, value_4)].into_iter()), + ) + .map_err(to_fail)?; + + let mut tree_v1 = Smt::new(); + apply_batch( + &mut tree_v1, + SmtUpdateBatch::new([ForestOperation::insert(key_1, value_1)].into_iter()), + )?; let mut tree_v2 = tree_v1.clone(); - apply_batch(&mut tree_v2, entries_v2)?; + apply_batch( + &mut tree_v2, + SmtUpdateBatch::new([ForestOperation::insert(key_2, value_2)].into_iter()), + )?; + let mut tree_v3 = tree_v2.clone(); + apply_batch( + &mut tree_v3, + SmtUpdateBatch::new([ForestOperation::insert(key_3, value_3)].into_iter()), + )?; + let mut tree_v4 = tree_v3.clone(); + apply_batch( + &mut tree_v4, + SmtUpdateBatch::new([ForestOperation::insert(key_4, value_4)].into_iter()), + )?; - let old_version = TreeId::new(lineage, version); + let sample_keys = vec![key_1, key_2, key_3, key_4]; + assert_tree_queries_match( + &forest, + TreeId::new(lineage, version + 2), + &tree_v3, + &sample_keys, + true, + )?; + assert_tree_queries_match( + &forest, + TreeId::new(lineage, version + 3), + &tree_v4, + &sample_keys, + true, + )?; + prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); + prop_assert_eq!(forest.latest_root(lineage), Some(tree_v4.root())); prop_assert_eq!( - sorted_forest_entries(&forest, old_version)?, - sorted_tree_entries(&tree_v1) + forest.root_info(TreeId::new(lineage, version + 3)), + RootInfo::LatestVersion(tree_v4.root()) ); - - let current_version = TreeId::new(lineage, tree_info.version()); prop_assert_eq!( - sorted_forest_entries(&forest, current_version)?, - sorted_tree_entries(&tree_v2) + forest.root_info(TreeId::new(lineage, version + 2)), + RootInfo::HistoricalVersion(tree_v3.root()) ); - } - - /// This test ensures that the `entries` iterator for the forest will never return entries where - /// the value is the empty word. - #[test] - fn entries_never_yields_empty_values( - lineage in arbitrary_lineage(), - version in arbitrary_version(), - entries_v1 in arbitrary_batch(), - entries_v2 in arbitrary_batch(), - ) { - let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; - forest.add_lineage(lineage, version, entries_v1.clone()).map_err(to_fail)?; - let tree_info = forest.update_tree(lineage, version + 1, entries_v2).map_err(to_fail)?; + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version)), RootInfo::Missing); - let old_version = TreeId::new(lineage, version); - let old_entries = forest - .entries(old_version) - .map_err(to_fail)? - .collect::>>() - .map_err(to_fail)?; - prop_assert!(old_entries.iter().all(|entry| entry.value != EMPTY_WORD)); + forest.truncate(version + 2); + assert_tree_queries_match( + &forest, + TreeId::new(lineage, version + 2), + &tree_v3, + &sample_keys, + true, + )?; + assert_tree_queries_match( + &forest, + TreeId::new(lineage, version + 3), + &tree_v4, + &sample_keys, + true, + )?; + prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); + prop_assert_eq!( + forest.root_info(TreeId::new(lineage, version + 3)), + RootInfo::LatestVersion(tree_v4.root()) + ); + prop_assert_eq!( + forest.root_info(TreeId::new(lineage, version + 2)), + RootInfo::HistoricalVersion(tree_v3.root()) + ); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 1)), RootInfo::Missing); - let current_version = TreeId::new(lineage, tree_info.version()); - let current_entries = forest - .entries(current_version) - .map_err(to_fail)? - .collect::>>() - .map_err(to_fail)?; - prop_assert!(current_entries.iter().all(|entry| entry.value != EMPTY_WORD)); + forest.truncate(version + 3); + assert_tree_queries_match( + &forest, + TreeId::new(lineage, version + 3), + &tree_v4, + &sample_keys, + true, + )?; + prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); + prop_assert_eq!(forest.latest_root(lineage), Some(tree_v4.root())); + prop_assert_eq!( + forest.root_info(TreeId::new(lineage, version + 3)), + RootInfo::LatestVersion(tree_v4.root()) + ); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::Missing); } /// This test cross-checks the core query APIs (`get`, `open`, `entries`, `entry_count`) and the @@ -226,7 +264,7 @@ proptest! { TreeId::new(lineage, version), &tree_v1, &sample_keys, - false, + true, )?; assert_tree_queries_match( &forest, @@ -246,17 +284,78 @@ proptest! { prop_assert_eq!(forest.lineage_count(), 1); prop_assert_eq!(forest.tree_count(), expected_versions.len()); prop_assert_eq!( - forest.roots().map(|root| (root.lineage(), root.value())).sorted().collect_vec(), - expected_versions.iter().map(|(_, root)| (lineage, *root)).sorted().collect_vec() + forest.roots().map(|root| (root.lineage(), root.value())).sorted().collect_vec(), + expected_versions.iter().map(|(_, root)| (lineage, *root)).sorted().collect_vec() + ); + + let unknown_lineage = LineageId::new([0xAA; 32]); + prop_assume!(unknown_lineage != lineage); + prop_assert_eq!(forest.latest_version(unknown_lineage), None); + prop_assert_eq!(forest.latest_root(unknown_lineage), None); + prop_assert!(forest.lineage_roots(unknown_lineage).is_none()); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::Missing); + prop_assert_eq!(forest.root_info(TreeId::new(unknown_lineage, version)), RootInfo::Missing); + } + + /// This test ensures that the `entries` iterator for the forest always returns the exact same + /// values as the `entries` iterator over a basic SMT with the same state. + #[test] + fn entries_correct( + lineage in arbitrary_lineage(), + version in arbitrary_version(), + entries_v1 in arbitrary_batch(), + entries_v2 in arbitrary_batch(), + ) { + let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; + forest.add_lineage(lineage, version, entries_v1.clone()).map_err(to_fail)?; + let tree_info = + forest.update_tree(lineage, version + 1, entries_v2.clone()).map_err(to_fail)?; + + let tree_v1 = build_tree(entries_v1.clone())?; + let mut tree_v2 = tree_v1.clone(); + apply_batch(&mut tree_v2, entries_v2)?; + + let old_version = TreeId::new(lineage, version); + prop_assert_eq!( + sorted_forest_entries(&forest, old_version)?, + sorted_tree_entries(&tree_v1) + ); + + let current_version = TreeId::new(lineage, tree_info.version()); + prop_assert_eq!( + sorted_forest_entries(&forest, current_version)?, + sorted_tree_entries(&tree_v2) ); + } - let unknown_lineage = LineageId::new([0xAA; 32]); - prop_assume!(unknown_lineage != lineage); - prop_assert_eq!(forest.latest_version(unknown_lineage), None); - prop_assert_eq!(forest.latest_root(unknown_lineage), None); - prop_assert!(forest.lineage_roots(unknown_lineage).is_none()); - prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::Missing); - prop_assert_eq!(forest.root_info(TreeId::new(unknown_lineage, version)), RootInfo::Missing); + /// This test ensures that the `entries` iterator for the forest will never return entries where + /// the value is the empty word. + #[test] + fn entries_never_yields_empty_values( + lineage in arbitrary_lineage(), + version in arbitrary_version(), + entries_v1 in arbitrary_batch(), + entries_v2 in arbitrary_batch(), + ) { + let mut forest = LargeSmtForest::new(ForestInMemoryBackend::new()).map_err(to_fail)?; + forest.add_lineage(lineage, version, entries_v1.clone()).map_err(to_fail)?; + let tree_info = forest.update_tree(lineage, version + 1, entries_v2).map_err(to_fail)?; + + let old_version = TreeId::new(lineage, version); + let old_entries = forest + .entries(old_version) + .map_err(to_fail)? + .collect::>>() + .map_err(to_fail)?; + prop_assert!(old_entries.iter().all(|entry| entry.value != EMPTY_WORD)); + + let current_version = TreeId::new(lineage, tree_info.version()); + let current_entries = forest + .entries(current_version) + .map_err(to_fail)? + .collect::>>() + .map_err(to_fail)?; + prop_assert!(current_entries.iter().all(|entry| entry.value != EMPTY_WORD)); } /// This test validates single-lineage mutation semantics, including duplicate additions, bad @@ -285,6 +384,7 @@ proptest! { Err(LargeSmtForestError::DuplicateLineage(l)) if l == lineage ); prop_assert!(is_duplicate); + assert_lineage_metadata(&forest, lineage, &[(version, reference.root())])?; assert_tree_queries_match( &forest, TreeId::new(lineage, version), @@ -294,6 +394,11 @@ proptest! { )?; prop_assert_eq!(forest.lineage_count(), 1); prop_assert_eq!(forest.tree_count(), 1); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 1)), RootInfo::Missing); + prop_assert_eq!( + forest.roots().map(|root| (root.lineage(), root.value())).collect_vec(), + vec![(lineage, reference.root())] + ); let bad_version = forest.update_tree(lineage, version, extra_entries); let is_bad_version = matches!( @@ -301,6 +406,7 @@ proptest! { Err(LargeSmtForestError::BadVersion { provided, latest }) if provided == version && latest == version ); prop_assert!(is_bad_version); + assert_lineage_metadata(&forest, lineage, &[(version, reference.root())])?; assert_tree_queries_match( &forest, TreeId::new(lineage, version), @@ -308,14 +414,20 @@ proptest! { &sample_keys, true, )?; + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 1)), RootInfo::Missing); + prop_assert_eq!( + forest.roots().map(|root| (root.lineage(), root.value())).collect_vec(), + vec![(lineage, reference.root())] + ); let no_op = forest .update_tree(lineage, version + 1, SmtUpdateBatch::empty()) .map_err(to_fail)?; prop_assert_eq!(no_op.version(), version); prop_assert_eq!(no_op.root(), reference.root()); - prop_assert_eq!(forest.latest_version(lineage), Some(version)); + assert_lineage_metadata(&forest, lineage, &[(version, reference.root())])?; prop_assert_eq!(forest.tree_count(), 1); + prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 1)), RootInfo::Missing); } /// This test validates batch updates across multiple lineages and ensures invalid batches do @@ -345,11 +457,11 @@ proptest! { let mut forest_updates = SmtForestUpdateBatch::empty(); forest_updates.add_operations( lineage_1, - updates_1.clone().into_iter(), + updates_1.clone().consume().into_iter(), ); forest_updates.add_operations( lineage_2, - updates_2.clone().into_iter(), + updates_2.clone().consume().into_iter(), ); let results = forest.update_forest(version + 1, forest_updates).map_err(to_fail)?; prop_assert_eq!(results.len(), 2); @@ -399,7 +511,7 @@ proptest! { versions_1.iter().map(|(_, root)| (lineage_1, *root)).collect_vec(); expected_roots.extend(versions_2.iter().map(|(_, root)| (lineage_2, *root))); expected_roots.sort(); - prop_assert_eq!(roots, expected_roots); + prop_assert_eq!(roots, expected_roots.clone()); prop_assert_eq!(forest.lineage_count(), 2); prop_assert_eq!(forest.tree_count(), versions_1.len() + versions_2.len()); @@ -410,6 +522,7 @@ proptest! { invalid_updates.add_operations( lineage_1, SmtUpdateBatch::new([ForestOperation::insert(query_key, invalid_value)].into_iter()) + .consume() .into_iter(), ); invalid_updates @@ -436,6 +549,12 @@ proptest! { assert_lineage_metadata(&forest, lineage_2, &versions_2)?; prop_assert_eq!(forest.lineage_count(), 2); prop_assert_eq!(forest.tree_count(), versions_1.len() + versions_2.len()); + let roots_after_error = forest + .roots() + .map(|root| (root.lineage(), root.value())) + .sorted() + .collect_vec(); + prop_assert_eq!(roots_after_error, expected_roots.clone()); prop_assert_eq!( forest.root_info(TreeId::new(lineage_1, version + 2)), RootInfo::Missing @@ -446,209 +565,4 @@ proptest! { ); } - /// This test validates constructor behavior when loading from a pre-populated backend. The - /// forest should load the latest tree state, but not reconstruct historical versions. - #[test] - fn new_loads_latest_backend_state_without_history( - (lineage_1, lineage_2) in arbitrary_distinct_lineages(), - version in arbitrary_version(), - entries_1 in arbitrary_batch(), - entries_2 in arbitrary_batch(), - updates_1 in arbitrary_batch(), - query_key in arbitrary_word(), - ) { - let mut backend = ForestInMemoryBackend::new(); - backend.add_lineage(lineage_1, version, entries_1.clone()).map_err(to_fail)?; - backend.add_lineage(lineage_2, version, entries_2.clone()).map_err(to_fail)?; - backend.update_tree(lineage_1, version + 1, updates_1.clone()).map_err(to_fail)?; - - let forest = LargeSmtForest::new(backend).map_err(to_fail)?; - - let tree_1_v1 = build_tree(entries_1.clone())?; - let mut expected_tree_1 = tree_1_v1.clone(); - apply_batch(&mut expected_tree_1, updates_1.clone())?; - let expected_tree_2 = build_tree(entries_2.clone())?; - let latest_version_1 = if expected_tree_1.root() == tree_1_v1.root() { - version - } else { - version + 1 - }; - - let mut sample_keys = batch_keys(&entries_1); - sample_keys.extend(batch_keys(&entries_2)); - sample_keys.extend(batch_keys(&updates_1)); - sample_keys.push(query_key); - sample_keys.sort(); - sample_keys.dedup(); - - assert_tree_queries_match( - &forest, - TreeId::new(lineage_1, latest_version_1), - &expected_tree_1, - &sample_keys, - true, - )?; - assert_tree_queries_match( - &forest, - TreeId::new(lineage_2, version), - &expected_tree_2, - &sample_keys, - true, - )?; - prop_assert_eq!(forest.lineage_count(), 2); - prop_assert_eq!(forest.tree_count(), 2); - prop_assert_eq!(forest.latest_version(lineage_1), Some(latest_version_1)); - prop_assert_eq!(forest.latest_root(lineage_1), Some(expected_tree_1.root())); - let expected_root_info = if latest_version_1 == version { - RootInfo::Missing - } else { - RootInfo::LatestVersion(expected_tree_1.root()) - }; - prop_assert_eq!( - forest.root_info(TreeId::new(lineage_1, version + 1)), - expected_root_info - ); - } - - /// This test validates history retention under custom configuration and the semantics of - /// explicit truncation. - #[test] - fn with_config_and_truncate_limit_retained_versions( - lineage in arbitrary_lineage(), - version in arbitrary_version(), - key_1 in arbitrary_word(), - key_2 in arbitrary_word(), - key_3 in arbitrary_word(), - key_4 in arbitrary_word(), - value_1 in arbitrary_non_empty_word(), - value_2 in arbitrary_non_empty_word(), - value_3 in arbitrary_non_empty_word(), - value_4 in arbitrary_non_empty_word(), - ) { - prop_assume!(key_1 != key_2 && key_1 != key_3 && key_1 != key_4); - prop_assume!(key_2 != key_3 && key_2 != key_4); - prop_assume!(key_3 != key_4); - - let config = ForestConfig::default().with_max_history_versions(2); - let mut forest = - LargeSmtForest::with_config(ForestInMemoryBackend::new(), config).map_err(to_fail)?; - forest - .add_lineage( - lineage, - version, - SmtUpdateBatch::new([ForestOperation::insert(key_1, value_1)].into_iter()), - ) - .map_err(to_fail)?; - forest - .update_tree( - lineage, - version + 1, - SmtUpdateBatch::new([ForestOperation::insert(key_2, value_2)].into_iter()), - ) - .map_err(to_fail)?; - forest - .update_tree( - lineage, - version + 2, - SmtUpdateBatch::new([ForestOperation::insert(key_3, value_3)].into_iter()), - ) - .map_err(to_fail)?; - forest - .update_tree( - lineage, - version + 3, - SmtUpdateBatch::new([ForestOperation::insert(key_4, value_4)].into_iter()), - ) - .map_err(to_fail)?; - - let mut tree_v1 = Smt::new(); - apply_batch( - &mut tree_v1, - SmtUpdateBatch::new([ForestOperation::insert(key_1, value_1)].into_iter()), - )?; - let mut tree_v2 = tree_v1.clone(); - apply_batch( - &mut tree_v2, - SmtUpdateBatch::new([ForestOperation::insert(key_2, value_2)].into_iter()), - )?; - let mut tree_v3 = tree_v2.clone(); - apply_batch( - &mut tree_v3, - SmtUpdateBatch::new([ForestOperation::insert(key_3, value_3)].into_iter()), - )?; - let mut tree_v4 = tree_v3.clone(); - apply_batch( - &mut tree_v4, - SmtUpdateBatch::new([ForestOperation::insert(key_4, value_4)].into_iter()), - )?; - - let sample_keys = vec![key_1, key_2, key_3, key_4]; - assert_tree_queries_match( - &forest, - TreeId::new(lineage, version + 2), - &tree_v3, - &sample_keys, - false, - )?; - assert_tree_queries_match( - &forest, - TreeId::new(lineage, version + 3), - &tree_v4, - &sample_keys, - true, - )?; - prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); - prop_assert_eq!(forest.latest_root(lineage), Some(tree_v4.root())); - prop_assert_eq!( - forest.root_info(TreeId::new(lineage, version + 3)), - RootInfo::LatestVersion(tree_v4.root()) - ); - prop_assert_eq!( - forest.root_info(TreeId::new(lineage, version + 2)), - RootInfo::HistoricalVersion(tree_v3.root()) - ); - prop_assert_eq!(forest.root_info(TreeId::new(lineage, version)), RootInfo::Missing); - - forest.truncate(version + 2); - assert_tree_queries_match( - &forest, - TreeId::new(lineage, version + 2), - &tree_v3, - &sample_keys, - false, - )?; - assert_tree_queries_match( - &forest, - TreeId::new(lineage, version + 3), - &tree_v4, - &sample_keys, - true, - )?; - prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); - prop_assert_eq!( - forest.root_info(TreeId::new(lineage, version + 3)), - RootInfo::LatestVersion(tree_v4.root()) - ); - prop_assert_eq!( - forest.root_info(TreeId::new(lineage, version + 2)), - RootInfo::HistoricalVersion(tree_v3.root()) - ); - prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 1)), RootInfo::Missing); - - forest.truncate(version + 3); - assert_tree_queries_match( - &forest, - TreeId::new(lineage, version + 3), - &tree_v4, - &sample_keys, - true, - )?; - prop_assert_eq!(forest.latest_version(lineage), Some(version + 3)); - prop_assert_eq!(forest.latest_root(lineage), Some(tree_v4.root())); - prop_assert_eq!( - forest.root_info(TreeId::new(lineage, version + 3)), - RootInfo::LatestVersion(tree_v4.root()) - ); - prop_assert_eq!(forest.root_info(TreeId::new(lineage, version + 2)), RootInfo::Missing); - } } diff --git a/miden-crypto/src/merkle/smt/large_forest/test_utils.rs b/miden-crypto/src/merkle/smt/large_forest/test_utils.rs index 026a491d13..911359ff58 100644 --- a/miden-crypto/src/merkle/smt/large_forest/test_utils.rs +++ b/miden-crypto/src/merkle/smt/large_forest/test_utils.rs @@ -4,14 +4,16 @@ use alloc::{string::ToString, vec::Vec}; use core::error::Error; +use itertools::Itertools; use miden_field::{Felt, Word}; use proptest::prelude::*; use crate::{ EMPTY_WORD, Map, ONE, ZERO, merkle::smt::{ - Backend, ForestInMemoryBackend, ForestOperation, LeafIndex, LineageId, MAX_LEAF_ENTRIES, - SMT_DEPTH, SmtForestUpdateBatch, SmtProof, SmtUpdateBatch, VersionId, + Backend, ForestInMemoryBackend, ForestOperation, LeafIndex, LineageId, LargeSmtForest, + MAX_LEAF_ENTRIES, RootInfo, SMT_DEPTH, Smt, SmtForestUpdateBatch, SmtProof, + SmtUpdateBatch, TreeId, VersionId, large_forest::{ backend::{BackendError, Result as BackendResult}, root::{TreeEntry, TreeWithRoot}, @@ -48,6 +50,12 @@ pub fn arbitrary_lineage() -> impl Strategy { prop::array::uniform32(any::()).prop_map(LineageId::new) } +/// Generates two distinct lineage identifiers. +pub fn arbitrary_distinct_lineages() -> impl Strategy { + (arbitrary_lineage(), arbitrary_lineage()) + .prop_filter("lineages must be distinct", |(a, b)| a != b) +} + /// Generates an arbitrary version identifier. pub fn arbitrary_version() -> impl Strategy { // As the proptests occasionally increment the version they are given, we exclude u64::MAX just @@ -65,6 +73,11 @@ pub fn arbitrary_word() -> impl Strategy { prop_oneof![prop::array::uniform4(arbitrary_felt()).prop_map(Word::new), Just(Word::empty()),] } +/// Generates a non-empty word value. +pub fn arbitrary_non_empty_word() -> impl Strategy { + arbitrary_word().prop_filter("word must be non-empty", |word| *word != EMPTY_WORD) +} + /// Generates a random number of unique (non-overlapping) key-value pairs. /// /// Note that the generated pairs may well have the same leaf index. @@ -110,6 +123,112 @@ pub fn arbitrary_batch() -> impl Strategy { }) } +/// Builds a reference [`Smt`] by applying `initial` to an empty tree. +pub fn build_tree(initial: SmtUpdateBatch) -> core::result::Result { + let mut tree = Smt::new(); + apply_batch(&mut tree, initial)?; + Ok(tree) +} + +/// Applies a batch to the provided reference [`Smt`]. +pub fn apply_batch(tree: &mut Smt, batch: SmtUpdateBatch) -> core::result::Result<(), TestCaseError> { + let mutations = tree + .compute_mutations(batch.consume().into_iter().map(Into::<(Word, Word)>::into)) + .map_err(to_fail)?; + tree.apply_mutations(mutations).map_err(to_fail) +} + +/// Collects the keys affected by a batch using the batch's canonicalized ordering and deduping. +pub fn batch_keys(batch: &SmtUpdateBatch) -> Vec { + batch.clone().consume().into_iter().map(|operation| operation.key()).collect() +} + +/// Sorts tree entries explicitly by `(key, value)` so tests compare sets without constraining +/// backend iteration order. +pub fn sorted_tree_entries(tree: &Smt) -> Vec { + let mut entries = tree + .entries() + .map(|(key, value)| TreeEntry { key: *key, value: *value }) + .collect_vec(); + entries.sort_by_key(|entry| (entry.key, entry.value)); + entries +} + +/// Sorts forest entries explicitly by `(key, value)` so tests compare observable contents rather +/// than relying on unspecified iterator ordering. +pub fn sorted_forest_entries( + forest: &LargeSmtForest, + tree: TreeId, +) -> core::result::Result, TestCaseError> { + let mut entries = forest + .entries(tree) + .map_err(to_fail)? + .collect::>>() + .map_err(to_fail)?; + entries.sort_by_key(|entry| (entry.key, entry.value)); + Ok(entries) +} + +fn word_to_option(value: Word) -> Option { + if value == EMPTY_WORD { None } else { Some(value) } +} + +/// Asserts that the forest and reference tree agree on entries, counts, key lookups, and openings. +pub fn assert_tree_queries_match( + forest: &LargeSmtForest, + tree_id: TreeId, + reference: &Smt, + sample_keys: &[Word], + assert_openings: bool, +) -> core::result::Result<(), TestCaseError> { + let forest_entries = sorted_forest_entries(forest, tree_id)?; + let reference_entries = sorted_tree_entries(reference); + let reference_entry_count = reference_entries.len(); + prop_assert_eq!(forest_entries, reference_entries); + prop_assert_eq!(forest.entry_count(tree_id).map_err(to_fail)?, reference_entry_count); + + for key in sample_keys { + prop_assert_eq!( + forest.get(tree_id, *key).map_err(to_fail)?, + word_to_option(reference.get_value(key)) + ); + if assert_openings { + prop_assert_eq!(forest.open(tree_id, *key).map_err(to_fail)?, reference.open(key)); + } + } + + Ok(()) +} + +/// Asserts that the forest metadata for `lineage` matches the provided sequence of versions. +pub fn assert_lineage_metadata( + forest: &LargeSmtForest, + lineage: LineageId, + versions: &[(VersionId, Word)], +) -> core::result::Result<(), TestCaseError> { + let (latest_version, latest_root) = + versions.last().copied().expect("lineage must be non-empty"); + + prop_assert_eq!(forest.latest_version(lineage), Some(latest_version)); + prop_assert_eq!(forest.latest_root(lineage), Some(latest_root)); + prop_assert_eq!( + forest.lineage_roots(lineage).expect("lineage must be present").collect_vec(), + versions.iter().rev().map(|(_, root)| *root).collect_vec() + ); + + for (idx, (version, root)) in versions.iter().enumerate() { + let tree = TreeId::new(lineage, *version); + let expected = if idx + 1 == versions.len() { + RootInfo::LatestVersion(*root) + } else { + RootInfo::HistoricalVersion(*root) + }; + prop_assert_eq!(forest.root_info(tree), expected); + } + + Ok(()) +} + // FALLIBLE ENTRIES BACKEND // ================================================================================================ diff --git a/miden-crypto/src/merkle/smt/large_forest/tests.rs b/miden-crypto/src/merkle/smt/large_forest/tests.rs index 60a6b93f2b..9859bed663 100644 --- a/miden-crypto/src/merkle/smt/large_forest/tests.rs +++ b/miden-crypto/src/merkle/smt/large_forest/tests.rs @@ -1347,7 +1347,7 @@ fn entries_with_fallible_backend() -> Result<()> { // Third item should be the simulated error. let third = iter.next(); - assert_matches!(&third, Some(Err(LargeSmtForestError::Unspecified(msg))) if msg == "simulated read failure"); + assert_matches!(&third, Some(Err(LargeSmtForestError::Unspecified(_)))); // After faulting, the iterator must yield None — the remaining entries (4th, 5th) are never // returned. @@ -1398,7 +1398,7 @@ fn entry_count_with_fallible_backend() -> Result<()> { // Query entry_count for the historical version V1. // This takes the WithHistory iteration path, which will hit our fallible iterator. let result = forest.entry_count(TreeId::new(lineage, version_1)); - assert_matches!(result, Err(LargeSmtForestError::Unspecified(msg)) if msg == "simulated read failure"); + assert_matches!(result, Err(LargeSmtForestError::Unspecified(_))); Ok(()) } From c66e1e24a1043c67a39a7465e1999f23acfedb2d Mon Sep 17 00:00:00 2001 From: AlexWaker <0xlunasterling@proton.me> Date: Tue, 24 Mar 2026 16:19:01 +0800 Subject: [PATCH 4/5] delete property_tests --- .../merkle/smt/large_forest/property_tests.txt | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt diff --git a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt b/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt deleted file mode 100644 index a89baa179c..0000000000 --- a/miden-crypto/proptest-regressions/merkle/smt/large_forest/property_tests.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc 4b7894584e6fed73ffc34213caa4ac8d90270c5e0a0cfeb0f62f154db1ebe44a # shrinks to lineage = LineageId([0, 0, 0, 0, 3, 12, 220, 141, 40, 211, 122, 87, 131, 76, 153, 223, 199, 1, 239, 233, 232, 138, 6, 56, 101, 239, 74, 14, 66, 235, 2, 58]), version = 6375444638265261073, key_1 = Word([1, 1, 1, 0]), key_2 = Word([17809828939116071465, 1, 4238248194923747156, 2627680782911860790]), key_3 = Word([1, 0, 17443273806143551851, 0]), key_4 = Word([15936525627748290972, 275898757835181111, 1, 0]), value_1 = Word([1, 11143404628732975980, 1, 1]), value_2 = Word([0, 930814439367801590, 14399587934779981401, 1]), value_3 = Word([17890142681827673407, 573402031812891261, 0, 13368909212370054120]), value_4 = Word([196994630634329390, 1, 6270331038003912332, 12792655455856645741]) -cc da7e7d0ada4185620bc2ae942f6b82b87c5ede94e72f07d77b0ad0b4693c7915 # shrinks to lineage = LineageId([0, 0, 0, 0, 0, 0, 19, 207, 66, 110, 140, 52, 164, 109, 32, 128, 183, 137, 217, 233, 90, 52, 103, 122, 244, 156, 24, 145, 131, 213, 72, 114]), version = 14378414525250753652, entries_v1 = SmtUpdateBatch { operations: [Insert { key: Word([4715263322365442641, 4841246979176943439, 0, 13348185607200837129]), value: Word([1, 5568866945875495768, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 15613517144968074488, 0, 1]) }, Remove { key: Word([6784195750361934512, 1, 4880529614489168186, 16893881042139654907]) }, Insert { key: Word([0, 6466511433887533777, 0, 1]), value: Word([17878133050888212350, 1, 15393790679626110881, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 0, 7797119101044420374]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 14155528324853465935, 12235058683017086980]) }, Insert { key: Word([0, 1, 0, 0]), value: Word([17492047924635082928, 1, 0, 8993653636289850364]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 1, 5818418131541643395]), value: Word([1, 1, 18284251167398315778, 1]) }, Remove { key: Word([1, 17359342700057268629, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([8592837165946161407, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 1, 0, 1]) }, Remove { key: Word([1, 16380040976954053337, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 2080851406930176171, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 0, 17517724945192259983, 1]) }, Remove { key: Word([0, 16242203334174013302, 1756530009948396796, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([16108619294158194701, 1784819492617802517, 1, 0]) }, Insert { key: Word([1, 1, 14054389673399095056, 0]), value: Word([1, 1, 1727710126018317349, 14846722981467667084]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 0, 11763672703183244684]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([14417587859422312233, 0, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 16736242669094307415, 14416751148720608515]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 14974858188074865258, 0, 0]), value: Word([5420890705143951672, 0, 13063801731867560085, 0]) }, Remove { key: Word([1, 9399736085123660465, 286315038662195539, 1]) }, Insert { key: Word([0, 0, 7695936059184519594, 8540535462340968011]), value: Word([0, 1920389745064574245, 0, 1]) }, Remove { key: Word([16118396807909684551, 1, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 0, 0]) }, Remove { key: Word([10203034533161946108, 1, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([10879162206579897656, 0, 0, 1]) }, Remove { key: Word([1, 0, 623716606286544957, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 5347737516742995769, 0]) }, Insert { key: Word([2534353175334127491, 1, 1, 1]), value: Word([8250724196395461455, 9355749963543018864, 9428953335003919114, 2446150955899649643]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 1, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 8845688326510475834, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([9483271017630993280, 1, 12128089580524682601, 1]) }, Remove { key: Word([15133786835186669016, 1, 1, 9141360008066921158]) }, Insert { key: Word([0, 1, 6034403946245642539, 12759208261588747989]), value: Word([1, 11183608821684979529, 0, 8419951467689494709]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 14930758959435094009, 15435804836674243848, 9780799933936898434]) }, Insert { key: Word([15955352879420194064, 0, 1, 0]), value: Word([1, 16968342451115143108, 1, 1]) }, Insert { key: Word([0, 10623551768161530180, 985721976669860576, 1]), value: Word([13090362831404762652, 12986816529963769346, 1, 11199290601714394278]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1, 0, 9499451410496508218, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 8816586531185414554, 1, 0]) }, Insert { key: Word([1, 5255482087259444081, 2169237974033036249, 14801118069429349049]), value: Word([1, 0, 18275997481822985485, 16456532796430420392]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([7578679113505053091, 12348855039133126620, 0, 3772227775799021454]) }, Insert { key: Word([0, 0, 1, 0]), value: Word([1, 0, 11664317378431862633, 9836843618832932413]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 3946762668109591974, 1, 599545304698496531]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([10700183636552547742, 1, 0, 1]) }, Insert { key: Word([0, 17546231225743711151, 0, 1]), value: Word([1, 7565207980961864337, 1, 2395036494917290299]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([4617743312688166538, 1, 1, 5777272497844076859]) }, Remove { key: Word([7451811908981275514, 5551806376086585374, 0, 0]) }, Insert { key: Word([0, 0, 1, 1]), value: Word([1, 10891893702695903520, 2699854409412634711, 0]) }, Remove { key: Word([1, 1, 8965341523656334880, 1]) }, Remove { key: Word([1, 1, 1, 14974041991530408605]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([4905703101863438197, 0, 1, 1]) }, Remove { key: Word([0, 1, 0, 0]) }, Remove { key: Word([1, 1, 6580548392849240511, 423957863778862200]) }, Remove { key: Word([0, 15146204795695467943, 13550506586618352499, 0]) }, Remove { key: Word([1, 0, 13835538468799972163, 4214645277282946139]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1588412766835784998, 1, 12982803496921393259, 2074847052564173435]) }, Insert { key: Word([3199829192986355867, 0, 0, 0]), value: Word([6671148298710261256, 0, 15755236232554191055, 8025642313709472676]) }, Remove { key: Word([1, 0, 15412117974050591368, 10347831653105819699]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 1, 0, 1]), value: Word([7372854460875585977, 16086438733249589539, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 5554808828071876536, 1, 1]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([1, 18204887486473566505, 1, 16377926231648314218]) }, Remove { key: Word([11540412879012764862, 0, 0, 1]) }, Remove { key: Word([1, 0, 6514524391152019104, 18118806069779959543]) }, Insert { key: Word([1, 1, 15055514482527026051, 1]), value: Word([4648223986840732704, 3569739232840310777, 0, 1]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([0, 5542969459758511979, 16682858730518930377, 2910815683514337258]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 2087821471747433455, 1, 0]) }, Insert { key: Word([0, 0, 2860490218596303863, 15888741258585145042]), value: Word([1, 1, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 0, 0, 1]) }, Remove { key: Word([1, 7360996536975129650, 0, 0]) }, Remove { key: Word([0, 0, 0, 8890886382688162039]) }, Remove { key: Word([1, 17449711356883577557, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 9027282040533900585, 0, 0]) }, Remove { key: Word([0, 5954516411873846089, 3359854980925249277, 17476760461068760315]) }, Insert { key: Word([5282361522484264121, 1, 0, 0]), value: Word([1, 17568752897574512740, 17531278156256391534, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([10115099205580265432, 1, 1, 7143071174086017069]) }, Insert { key: Word([7149270656809029628, 6794270497261542375, 0, 0]), value: Word([1, 1, 1, 6217679770684509513]) }, Insert { key: Word([8187402099352561135, 0, 1, 7970209752017100117]), value: Word([2943917165102628533, 0, 1, 3284476271619099284]) }, Insert { key: Word([1, 0, 0, 0]), value: Word([1, 1, 10037703311177033596, 846661881122981572]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 1, 2007256076077404014]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 8664480436385648093, 0]) }, Insert { key: Word([0, 0, 1, 14602422081042044909]), value: Word([11476774637830631792, 7420219053118809811, 10031325639504350980, 7411888692432835382]) }, Remove { key: Word([1, 0, 1, 4530593761340126717]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([79023662508776939, 16983937845552071205, 0, 8876741277042651923]), value: Word([1, 1, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([2312126386353390709, 5054447914725148197, 1, 216081414777227431]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 12867642070796805043, 0]) }, Remove { key: Word([0, 1, 1, 4641783074465389612]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([15995512101060171146, 0, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 5317777551529895, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 14244901757456224170, 0, 17826699297702846720]) }, Remove { key: Word([17455838129484622230, 0, 15357367460782955055, 395945022856420185]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([1975876493361877352, 3740003930233290837, 5812449452985664043, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([6407986762022463692, 1, 15654402736953619656, 1]) }, Remove { key: Word([13599177958961154988, 0, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 0, 0, 3361259231170337184]), value: Word([5775145840382474735, 1, 0, 1]) }, Insert { key: Word([0, 0, 0, 8714773746010194488]), value: Word([1, 6868227923334612836, 1, 2552465560106869889]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 13083054148557890003, 1, 0]), value: Word([1, 0, 8958200922778054055, 1]) }, Remove { key: Word([12909419010340823623, 0, 9511793988410931362, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 1, 1]) }, Insert { key: Word([1, 11939841116458585496, 5252451298814573952, 1928197503387806608]), value: Word([1, 762920920770422436, 14860682348660100088, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 13180808326107638627, 0, 0]), value: Word([0, 1, 1, 10631678530453352674]) }, Remove { key: Word([0, 0, 1, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 10326153129179073670, 6792997568552657542]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 7894976204057781596, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 14758316206389932443, 0, 1]), value: Word([0, 1, 1, 2941518481653533627]) }, Insert { key: Word([0, 8774693584700768110, 16360300427338571912, 15322864309907762649]), value: Word([11982941647898410180, 1, 1, 1386528248779926529]) }, Remove { key: Word([1, 0, 12615213812497121945, 0]) }, Insert { key: Word([0, 13687345636211620643, 1, 1]), value: Word([0, 15338645266308888470, 1, 0]) }, Remove { key: Word([1, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 1, 0, 0]), value: Word([3155128086588310054, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([3628059670242864462, 0, 14803462570730943148, 7661027732520056092]), value: Word([1, 8651191585692664897, 2552679505911886381, 1]) }, Insert { key: Word([1, 1, 16827335939319472459, 9261369697388433021]), value: Word([1, 204373952672213217, 1, 9745946111673251089]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 1, 0]) }, Insert { key: Word([12024798715915071567, 1983883130368086218, 417433103337935958, 5650950723731425983]), value: Word([12122558640554734720, 0, 1, 16895158236963652742]) }, Insert { key: Word([1, 1, 8733301627474754875, 11976552330908318698]), value: Word([1, 1, 1503146448857043088, 0]) }, Remove { key: Word([0, 1, 0, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 1, 0, 2073573561362233363]), value: Word([0, 9198232018703017413, 1, 0]) }, Remove { key: Word([0, 3393120253725548875, 1, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 11270524037504036460, 1, 9415899648920862086]), value: Word([1, 12865093919488147242, 1, 0]) }, Remove { key: Word([1, 1, 1860633986519085183, 1]) }, Insert { key: Word([12844757103765701837, 15464333193451077969, 1, 10679199020359865059]), value: Word([16329022990122342840, 1, 1, 16651586460628861108]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([11876742985186359465, 0, 1, 489924416806554387]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 1, 1, 0]) }, Remove { key: Word([0, 0, 0, 1]) }, Remove { key: Word([0, 17340343089548661158, 1736568239065064584, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 0, 1, 8740670642349073662]) }, Remove { key: Word([10485280542043754300, 1, 0, 414544917731547023]) }, Insert { key: Word([1, 932295115101442175, 0, 1]), value: Word([0, 0, 12180430153013489892, 1]) }, Remove { key: Word([13236088366182083669, 18026265043841859645, 0, 0]) }, Remove { key: Word([0, 0, 13244010672048553378, 0]) }] }, entries_v2 = SmtUpdateBatch { operations: [Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([1, 0, 130630469781385363, 2918612881082043543]), value: Word([0, 1, 12683451587785776512, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 0, 5857059368556401714, 1]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([0, 967733294636046527, 4703772855648996154, 16357961517182088807]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([7671380349212115108, 3232623150437798774, 1, 1]) }, Insert { key: Word([1, 9395255692305235741, 1, 0]), value: Word([2468461131572902796, 5539652486494237729, 0, 3583158755848592036]) }, Remove { key: Word([0, 3133064241058569931, 0, 1]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([2198723372894752938, 1, 7766860407492519429, 16353168434517105610]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([15576036095111912527, 1, 15133903514472688154, 5087521421828266809]) }, Insert { key: Word([1, 12428969342704184935, 0, 2261153628469780607]), value: Word([0, 7928430322549734970, 1, 18336068418833454237]) }, Insert { key: Word([0, 13099950430763069862, 0, 9930194365803599023]), value: Word([6788715641981323007, 13585923073706478028, 1, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Remove { key: Word([0, 0, 0, 0]) }, Insert { key: Word([0, 0, 0, 0]), value: Word([1, 1, 0, 16622901136535004036]) }, Insert { key: Word([0, 9639404142061142784, 1, 0]), value: Word([1, 0, 0, 0]) }] }, random_key = Word([0, 0, 0, 0]) From eef611175f1fdc575639a079574b0d67c8ccddb7 Mon Sep 17 00:00:00 2001 From: AlexWaker <0xlunasterling@proton.me> Date: Tue, 24 Mar 2026 18:39:15 +0800 Subject: [PATCH 5/5] fix format --- .../src/merkle/smt/large_forest/property_tests.rs | 6 +++--- .../src/merkle/smt/large_forest/test_utils.rs | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/miden-crypto/src/merkle/smt/large_forest/property_tests.rs b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs index 88136da1a3..2da969de91 100644 --- a/miden-crypto/src/merkle/smt/large_forest/property_tests.rs +++ b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs @@ -14,9 +14,9 @@ use crate::{ TreeId, large_forest::test_utils::{ apply_batch, arbitrary_batch, arbitrary_distinct_lineages, arbitrary_lineage, - arbitrary_non_empty_word, arbitrary_version, arbitrary_word, - assert_lineage_metadata, assert_tree_queries_match, batch_keys, build_tree, - sorted_forest_entries, sorted_tree_entries, to_fail, + arbitrary_non_empty_word, arbitrary_version, arbitrary_word, assert_lineage_metadata, + assert_tree_queries_match, batch_keys, build_tree, sorted_forest_entries, + sorted_tree_entries, to_fail, }, }, }; diff --git a/miden-crypto/src/merkle/smt/large_forest/test_utils.rs b/miden-crypto/src/merkle/smt/large_forest/test_utils.rs index 911359ff58..89cf06531e 100644 --- a/miden-crypto/src/merkle/smt/large_forest/test_utils.rs +++ b/miden-crypto/src/merkle/smt/large_forest/test_utils.rs @@ -11,9 +11,9 @@ use proptest::prelude::*; use crate::{ EMPTY_WORD, Map, ONE, ZERO, merkle::smt::{ - Backend, ForestInMemoryBackend, ForestOperation, LeafIndex, LineageId, LargeSmtForest, - MAX_LEAF_ENTRIES, RootInfo, SMT_DEPTH, Smt, SmtForestUpdateBatch, SmtProof, - SmtUpdateBatch, TreeId, VersionId, + Backend, ForestInMemoryBackend, ForestOperation, LargeSmtForest, LeafIndex, LineageId, + MAX_LEAF_ENTRIES, RootInfo, SMT_DEPTH, Smt, SmtForestUpdateBatch, SmtProof, SmtUpdateBatch, + TreeId, VersionId, large_forest::{ backend::{BackendError, Result as BackendResult}, root::{TreeEntry, TreeWithRoot}, @@ -131,7 +131,10 @@ pub fn build_tree(initial: SmtUpdateBatch) -> core::result::Result core::result::Result<(), TestCaseError> { +pub fn apply_batch( + tree: &mut Smt, + batch: SmtUpdateBatch, +) -> core::result::Result<(), TestCaseError> { let mutations = tree .compute_mutations(batch.consume().into_iter().map(Into::<(Word, Word)>::into)) .map_err(to_fail)?;