Skip to content

fix: decouple proof generation from persistence flags - #18

Closed
mwaddip wants to merge 1 commit into
ergoplatform:mainfrom
mwaddip:fix/proof-persistence-decouple
Closed

fix: decouple proof generation from persistence flags#18
mwaddip wants to merge 1 commit into
ergoplatform:mainfrom
mwaddip:fix/proof-persistence-decouple

Conversation

@mwaddip

@mwaddip mwaddip commented Jul 27, 2026

Copy link
Copy Markdown

generate_proof() called tree.reset(), clearing flags that a downstream
storage.update() needs for collect_changed_nodes. A proof-first caller
(required for correct Lookup proofs) found only the root — internal nodes from
rebalancing were never persisted.

Decouple proof generation from persistence:

  • New modified_nodes vec, populated by on_node_visit, queried by pack_tree
    via pointer-identity lookup. pack_tree no longer touches visited.
  • Persistence flags now reset by perform_one_operation() at the cycle
    boundary, not by generate_proof(). storage.update() keeps its reset.
  • Relaxes check_tree_helper's post-proof assertion.

All 22 tests pass. test_successful_modifications 20/20 consecutive.

Found via the ergo Rust full-node persistence backend.

@mwaddip
mwaddip force-pushed the fix/proof-persistence-decouple branch from 6e1a617 to a96bba5 Compare August 2, 2026 12:19
@mwaddip

mwaddip commented Aug 2, 2026

Copy link
Copy Markdown
Author

Force-pushed: rustfmt applied, folded into the existing commit so this stays a single commit.

cargo fmt --check was failing on files this PR modifies. Formatting only — no behavioural change, tests unchanged and passing. New head is below; the diff against the previous head is purely whitespace and line-wrapping.

Unrelated: tests/avl_batch_specification.rs also fails cargo fmt --check, but that predates this branch and is left alone here — #23 covers it.

@mwaddip

mwaddip commented Aug 2, 2026

Copy link
Copy Markdown
Author

Note for whenever this lands — no action needed here.

#22 (restore_root) is written against current main, so it omits the needs_cycle_reset = false line: the field only exists once this PR merges. Once it does, #22 needs that one-line addition, since restore_root calls tree.reset() inline and should suppress the implicit reset in the next perform_one_operation.

Flagging it here because that's where the trigger is — #22's description already records the dependency from its own side. Tracking it; nothing required of reviewers.

Proof generation (pack_tree) and persistence (collect_changed_nodes)
both relied on the tree's visited/is_new flags, creating a conflict:
generate_proof() called tree.reset(), clearing flags that a subsequent
storage.update() needed to find changed nodes. This caused internal
nodes created during AVL rebalancing to never be persisted.

Decouple them:

1. Add modified_nodes tracking (AuthenticatedTreeOpsBase) — populated
   unconditionally in on_node_visit, cleared by generate_proof().
   was_modified() keys on Rc::as_ptr, independent of tree flags.

2. Switch pack_tree from tree.visited() to was_modified() — proof
   generation no longer touches visited/is_new.

3. Replace tree.reset() in generate_proof() with modified_nodes.clear()
   + a needs_cycle_reset flag. The next perform_one_operation() resets
   visited/is_new (persistence flags) at the cycle boundary, after
   update() had a chance to collect them.

4. Relax check_tree_helper's post_proof assertion — flags are now
   cleared by update() or perform_one_operation(), not generate_proof().

5. Reset the cloned tree in generate_proof_for_operations() so the
   clone's on_node_visit starts with fresh flags.

modified_nodes is a BTreeMap keyed on the node's address rather than a
list, because pack_tree asks was_modified() once per node it walks: a
linear scan there makes proof generation quadratic in the number of
visits, which on a remove-heavy benchmark costs minutes rather than
seconds. The map's NodeId values keep each node alive, so an address
cannot be recycled while it is still a key, and the set stays per-prover
— a tree clone shares its nodes, so per-node state would leak proof
bookkeeping between the original and the clone.
@mwaddip

mwaddip commented Aug 12, 2026

Copy link
Copy Markdown
Author

Superseded by #27, which carries this commit patch-identical, alongside #11 and #22, rebased onto current main — plus a fix for a defect that only appears once they are combined.

@mwaddip mwaddip closed this Aug 12, 2026
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.

1 participant