Skip to content

fix: clear stale leaves and inner nodes in set_subtree#840

Open
sashass1315 wants to merge 2 commits into0xMiden:nextfrom
sashass1315:fix/smt-set-subtree-stale-data
Open

fix: clear stale leaves and inner nodes in set_subtree#840
sashass1315 wants to merge 2 commits into0xMiden:nextfrom
sashass1315:fix/smt-set-subtree-stale-data

Conversation

@sashass1315
Copy link
Contributor

@sashass1315 sashass1315 commented Feb 22, 2026

What was wrong?

set_subtree()

pub fn set_subtree<const SUBTREE_DEPTH: u8>(
did not remove existing leaves and inner nodes in the insertion region before adding new subtree data. This caused stale data to remain in the tree, breaking the invariant leaves.is_empty() == (root == EMPTY_ROOT)
/// Returns a boolean value indicating whether the SMT is empty.
pub fn is_empty(&self) -> bool {
debug_assert_eq!(self.leaves.is_empty(), self.root == Self::EMPTY_ROOT);
self.root == Self::EMPTY_ROOT
}
and causing num_leaves()
/// Returns the number of non-empty leaves in this tree.
pub fn num_leaves(&self) -> usize {
self.leaves.len()
}
to return incorrect values. In debug mode, calling is_empty()
/// Returns a boolean value indicating whether the SMT is empty.
pub fn is_empty(&self) -> bool {
debug_assert_eq!(self.leaves.is_empty(), self.root == Self::EMPTY_ROOT);
self.root == Self::EMPTY_ROOT
}
after inserting an empty subtree would panic.

What changed?

Added cleanup logic to set_subtree() that removes all leaves and inner nodes within the insertion region before adding the new subtree's data. Also added tests to verify the fix.

@bobbinth bobbinth requested a review from huitseeker February 23, 2026 22:19
// Leaves 4 and 5 should be removed, leaves 0, 1, 7 should remain
assert_eq!(tree.num_leaves(), 3);
assert_eq!(tree.get_leaf(&LeafIndex::<3>::new(0).unwrap()), a);
assert_eq!(tree.get_leaf(&LeafIndex::<3>::new(1).unwrap()), b);
Copy link
Contributor

Choose a reason for hiding this comment

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

The new tests cover empty subtree insertion well, but don't verify behavior when inserting a non-empty subtree over existing data. This is the main scenario the stale data fix targets. Could we add a test that inserts a non-empty subtree at depth > 1 into a populated tree and verifies the root matches a fresh build?

let node_value_offset = subtree_insertion_index * num_nodes_at_depth;

for node_value in node_value_offset..(node_value_offset + num_nodes_at_depth) {
if let Ok(node_index) = NodeIndex::new(node_depth, node_value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The if let Ok(node_index) = NodeIndex::new(node_depth, node_value) pattern here silently skips any indices that fail validation. Is this hiding an error condition?

Looking at the loop logic:

  • node_depth ranges from subtree_root_insertion_depth + 1 to DEPTH
  • num_nodes_at_depth = 2^depth_offset where depth_offset = node_depth - subtree_root_insertion_depth
  • node_value ranges from node_value_offset to node_value_offset + num_nodes_at_depth - 1

For a valid NodeIndex, position must be less than 2^depth. Here that means node_value < 2^(subtree_root_insertion_depth + depth_offset) which equals subtree_insertion_index * 2^depth_offset + 2^depth_offset = (subtree_insertion_index + 1) * num_nodes_at_depth.

Since subtree_insertion_index is validated at line 307 to be < 2^subtree_root_insertion_depth, the math should always work out. So if NodeIndex::new returns an error, it would indicate a bug in the calculation logic that should probably not be silently ignored.

Would it make sense to use expect() here instead, similar to line 357? Or is there a case where this can legitimately fail?

NodeIndex::new(subtree_root_insertion_depth, subtree_insertion_index)?;

// add leaves
// remove existing leaves and inner nodes in the insertion region
Copy link
Contributor

Choose a reason for hiding this comment

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

The CI changelog check is failing. Could you add an entry for this PR under the ## 0.23.0 (TBD) section in CHANGELOG.md? Suggested entry:

  • Fixed SimpleSmt::set_subtree to clear stale leaves and inner nodes in the insertion region.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants