Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [BREAKING] `PartialMmr::open()` now returns `Option<MmrProof>` instead of `Option<MmrPath>` ([#787](https://github.com/0xMiden/crypto/pull/787)).
- [BREAKING] Refactored BLAKE3 to use `Digest<N>` struct, added `Digest192` type alias ([#811](https://github.com/0xMiden/crypto/pull/811)).
- [BREAKING] Removed `hashbrown` dependency and `hashmaps` feature; `Map`/`Set` type aliases are now tied to the `std` feature ([#813](https://github.com/0xMiden/crypto/pull/813)).
- Fixed `LargeSmtForest::truncate` to remove emptied lineages from `non_empty_histories` ([#818](https://github.com/0xMiden/crypto/pull/818)).
- [BREAKING] Renamed `NodeIndex::value()` to `NodeIndex::position()`, `NodeIndex::is_value_odd()` to `NodeIndex::is_position_odd()`, and `LeafIndex::value()` to `LeafIndex::position()` ([#814](https://github.com/0xMiden/crypto/pull/814)).
- [BREAKING] Fix OOMs in Merkle/SMT deserialization ([#820](https://github.com/0xMiden/crypto/pull/820)).
- Fixed `SmtForest` to remove nodes with zero reference count from store ([#821](https://github.com/0xMiden/crypto/pull/821)).
Expand Down
4 changes: 3 additions & 1 deletion miden-crypto/src/merkle/smt/large_forest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,9 @@ impl<B: Backend> LargeSmtForest<B> {
}
});

self.non_empty_histories.extend(newly_empty);
for l in &newly_empty {
self.non_empty_histories.remove(l);
}
}
}

Expand Down
100 changes: 97 additions & 3 deletions miden-crypto/src/merkle/smt/large_forest/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ use itertools::Itertools;

use super::{Config, Result};
use crate::{
EMPTY_WORD, Word,
EMPTY_WORD, Map, Set, Word,
merkle::{
EmptySubtreeRoots,
smt::{
Backend, ForestInMemoryBackend, ForestOperation, LargeSmtForest, LargeSmtForestError,
LeafIndex, RootInfo, Smt, SmtForestUpdateBatch, SmtUpdateBatch, TreeId, VersionId,
large_forest::root::{LineageId, TreeEntry, TreeWithRoot},
large_forest::{
LineageData,
history::{History, LeafChanges, NodeChanges},
root::{LineageId, TreeEntry, TreeWithRoot},
},
},
},
rand::test_utils::ContinuousRng,
rand::test_utils::{ContinuousRng, rand_value},
};

// TYPE ALIASES
Expand Down Expand Up @@ -1112,3 +1116,93 @@ fn update_forest() -> Result<()> {

Ok(())
}

// TRUNCATION
// ================================================================================================

#[test]
fn truncate_removes_emptied_lineages_from_non_empty_histories() {
let lineage: LineageId = rand_value();
let root: Word = rand_value();

// Build a lineage with one historical version at version 5, and a latest version of 10.
let mut history = History::empty(4);
let nodes = NodeChanges::default();
let leaves = LeafChanges::default();
history.add_version(rand_value(), 5, nodes, leaves).unwrap();
assert_eq!(history.num_versions(), 1);

let lineage_data = LineageData {
history,
latest_version: 10,
latest_root: root,
};

let mut lineage_map = Map::default();
lineage_map.insert(lineage, lineage_data);

let mut non_empty = Set::default();
non_empty.insert(lineage);

let mut forest = LargeSmtForest {
config: Config::default(),
backend: ForestInMemoryBackend::new(),
lineage_data: lineage_map,
non_empty_histories: non_empty,
};

// Sanity: the lineage is tracked as having a non-empty history.
assert!(forest.non_empty_histories.contains(&lineage));

// Truncate to a version >= latest_version, which clears the history entirely.
forest.truncate(10);

// The lineage's history should now be empty, and it must have been removed from the set.
assert!(
!forest.non_empty_histories.contains(&lineage),
"emptied lineage must be removed from non_empty_histories after truncation"
);
}

#[test]
fn truncate_retains_non_empty_lineages_in_non_empty_histories() {
let lineage: LineageId = rand_value();
let root: Word = rand_value();

// Build a lineage with two historical versions (5 and 8), latest version 15.
let mut history = History::empty(4);
let nodes = NodeChanges::default();
let leaves = LeafChanges::default();
history.add_version(rand_value(), 5, nodes.clone(), leaves.clone()).unwrap();
history.add_version(rand_value(), 8, nodes, leaves).unwrap();
assert_eq!(history.num_versions(), 2);

let lineage_data = LineageData {
history,
latest_version: 15,
latest_root: root,
};

let mut lineage_map = Map::new();
lineage_map.insert(lineage, lineage_data);

let mut non_empty = Set::default();
non_empty.insert(lineage);

let mut forest = LargeSmtForest {
config: Config::default(),
backend: ForestInMemoryBackend::new(),
lineage_data: lineage_map,
non_empty_histories: non_empty,
};

// Truncate to version 7: removes versions older than 7, but version 8 should remain.
// Since version < latest_version (15), LineageData::truncate returns false.
forest.truncate(7);

// The history still has data, so the lineage must stay in non_empty_histories.
assert!(
forest.non_empty_histories.contains(&lineage),
"lineage with remaining history must stay in non_empty_histories"
);
}
1 change: 0 additions & 1 deletion miden-crypto/src/rand/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ fn rng_value<T: Randomizable>(rng: &mut impl Rng) -> T {
/// let x: u64 = rand_value();
/// let y: u128 = rand_value();
/// ```
#[cfg(feature = "std")]
pub fn rand_value<T: Randomizable>() -> T {
rng_value(&mut rand::rng())
}
Expand Down