Skip to content

feat: Implement commit all and revert all for world state checkpoints#21532

Open
PhilWindle wants to merge 4 commits intomerge-train/spartanfrom
pw/revert-to
Open

feat: Implement commit all and revert all for world state checkpoints#21532
PhilWindle wants to merge 4 commits intomerge-train/spartanfrom
pw/revert-to

Conversation

@PhilWindle
Copy link
Collaborator

@PhilWindle PhilWindle commented Mar 13, 2026

Summary

  • Adds depth-aware commitAllCheckpointsTo(depth) and revertAllCheckpointsTo(depth) to the world state checkpoint system. These revert/commit all checkpoints at or above the given depth (inclusive), preserving any checkpoints created by callers below that depth.
  • createCheckpoint() now returns the depth of the newly created checkpoint, threading it through the full C++ async callback chain (cache → tree store → append-only tree → world state → NAPI → TypeScript).
  • ForkCheckpoint stores its depth and exposes revertToCheckpoint() which encapsulates the revert-to-depth pattern, replacing the previous revertAllCheckpoints() + markCompleted() two-step.
  • The public processor uses revertToCheckpoint() on tx timeout/panic, so per-tx reverts no longer destroy checkpoints created by callers (e.g., CheckpointBuilder).

Changes

C++ (barretenberg)

  • ContentAddressedCache: checkpoint() returns depth, new commit_to_depth()/revert_to_depth() methods
  • CachedContentAddressedTreeStore: passes through depth-aware operations
  • ContentAddressedAppendOnlyTree: CheckpointCallback now receives TypedResponse<CheckpointResponse> with depth
  • WorldState: checkpoint() returns depth, commit_all_checkpoints_to/revert_all_checkpoints_to take required depth
  • NAPI layer: new ForkIdWithDepthRequest/CheckpointDepthResponse message types

TypeScript

  • MerkleTreeCheckpointOperations interface: createCheckpoint() returns Promise<number>, depth is required on commitAllCheckpointsTo/revertAllCheckpointsTo
  • MerkleTreesFacade: passes depth through native message channel
  • ForkCheckpoint: stores depth, new revertToCheckpoint() method
  • PublicProcessor: uses checkpoint.revertToCheckpoint() on error paths

Tests

  • C++ cache tests: depth return, commit_to_depth, revert_to_depth, edge cases
  • C++ append-only tree tests: depth return, commit/revert to depth
  • TypeScript native world state tests: depth return, commit/revert to depth, backward compat
  • TypeScript fork checkpoint unit tests
  • TypeScript public processor tests: verifies depth passed on revert

Test plan

  • C++ cache tests pass (crypto_content_addressed_cache_tests)
  • C++ append-only tree tests pass (crypto_content_addressed_append_only_tree_tests)
  • TypeScript native_world_state.test.ts passes
  • TypeScript fork_checkpoint.test.ts passes
  • TypeScript public_processor.test.ts passes
  • TypeScript timeout_race.test.ts passes

@spalladino spalladino added the claude-review Triggers an automated Claude code review label Mar 13, 2026
@AztecBot
Copy link
Collaborator

AztecBot commented Mar 13, 2026

Run #1 — Session completed (3m)
Live status

Reviewed #21532 — 23 files, 829 additions. LGTM. No bugs found. The depth-aware checkpoint commit/revert design is clean, correctly fixes the per-tx revert destroying caller checkpoints, and has thorough test coverage across C++ and TS. Full review: https://gist.github.com/AztecBot/7c1effe73ef529c1f0ede41684b6f1af

@AztecBot AztecBot added claude-review-complete Claude code review has been completed and removed claude-review Triggers an automated Claude code review labels Mar 13, 2026
Copy link
Contributor

@spalladino spalladino left a comment

Choose a reason for hiding this comment

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

I feel I'm missing some understanding of how things worked beforehand. Left a few questions that may help me follow along.

Comment on lines +264 to +269
if (target_depth == 0 || target_depth > journals_.size()) {
throw std::runtime_error("Invalid depth for revert_to_depth");
}
while (journals_.size() >= target_depth) {
revert();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm probably missing something, but shouldn't we allow a revert_to_depth(0) that reverts everything? Same for commit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Rightly or wrongly, it's inclusive. So revert_to_depth(1) reverts everything.

Comment on lines +689 to +690
cache.commit_to_depth(1);
EXPECT_EQ(cache.depth(), 0u);
Copy link
Contributor

Choose a reason for hiding this comment

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

I see, so a commit-all or revert-all is to 1. I'd've expected that commit_to/revert_to(N) means the depth after running that is N.

Comment on lines 25 to +46
async revert(): Promise<void> {
if (this.completed) {
return;
}

await this.fork.revertCheckpoint();
this.completed = true;
}

/**
* Reverts all checkpoints at or above this checkpoint's depth (inclusive),
* destroying this checkpoint and any nested checkpoints created on top of it,
* while preserving any checkpoints created by callers below our depth.
*/
async revertToCheckpoint(): Promise<void> {
if (this.completed) {
return;
}

await this.fork.revertAllCheckpointsTo(this.depth);
this.completed = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm missing something here: what's the difference between these two methods? Doesn't reverting a checkpoint also revert all checkpoint built in top of it? In which scenario wouldn't we want that?

Copy link
Collaborator Author

@PhilWindle PhilWindle Mar 13, 2026

Choose a reason for hiding this comment

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

The original intention of this class was a simple, almost RAII like wrapper around a single checkpoint. So the expected flow was little more than

function processFunction() {
   const cp = ForkCheckpoint.new();
   try {
      // Execute code that might call processFunction() again
   } catch () {
      cp.revert();
   } finally { 
      cp.commit();
   }
}

The AVM never actually uses this. It's used in one place in public_processor for setting up the base checkpoint for the transaction. If all succeeds (or errors gracefully), then we call commit/revert for that one final checkpoint, either applying or removing that tx's side effects. If we error ungracefully, the AVM will have lost track of checkpoints and we just have to hit the big revertToCheckpoint button deleting every modification the AVM made ready for the next transaction.

@spalladino
Copy link
Contributor

Claude seems to like it!

Co-authored-by: Santiago Palladino <santiago@aztecprotocol.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-to-v4-next claude-review-complete Claude code review has been completed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants