Skip to content

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

Merged
PhilWindle merged 6 commits intomerge-train/spartanfrom
pw/revert-to
Mar 16, 2026
Merged

feat: Implement commit all and revert all for world state checkpoints#21532
PhilWindle merged 6 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Modified to be exclusive. So revert_to_depth(0) would revert all.

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!

Comment on lines +328 to +331
// Revert all checkpoints at or above this checkpoint's depth (inclusive), destroying any outstanding state
// updates from this tx and any nested checkpoints created during execution. This preserves any checkpoints
// created by callers below our depth.
await checkpoint.revertToCheckpoint();
Copy link
Contributor

Choose a reason for hiding this comment

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

To me, this call implies there were checkpoints created in the CPP simulator. If that's the case, shouldn't the code responsible for creating the extra checkpoints clean after itself?

Comment on lines +338 to 344
public async commitAllCheckpointsTo(depth: number): Promise<void> {
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
await this.instance.call(WorldStateMessageType.COMMIT_ALL_CHECKPOINTS, { forkId: this.revision.forkId });
await this.instance.call(WorldStateMessageType.COMMIT_ALL_CHECKPOINTS, {
forkId: this.revision.forkId,
depth,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this method is used anywhere?

@PhilWindle PhilWindle merged commit dbd9dd8 into merge-train/spartan Mar 16, 2026
23 checks passed
@PhilWindle PhilWindle deleted the pw/revert-to branch March 16, 2026 11:15
AztecBot pushed a commit that referenced this pull request Mar 16, 2026
…#21532)

## 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
@AztecBot
Copy link
Collaborator

✅ Successfully backported to backport-to-v4-staging #21582.

github-merge-queue bot pushed a commit that referenced this pull request Mar 16, 2026
BEGIN_COMMIT_OVERRIDE
feat: add ETHEREUM_HTTP_TIMEOUT_MS env var for viem HTTP transport
(#20919)
fix(archiver): filter tagged log queries by block number (#21388)
fix(node): handle slot zero in getL2ToL1Messages (#21386)
feat(sequencer): redistribute checkpoint budget evenly across remaining
blocks (#21378)
fix: fall back to package.json for CLI version detection (#21382)
chore: Removed multiplier config (#21412)
chore: Removed default snapshot url config (#21413)
chore: Read tx filestores from network config (#21416)
fix(node): check world state against requested block hash (#21385)
feat(p2p): use l2 priority fee only for tx priority (#21420)
feat(p2p): reject and evict txs with insufficient max fee per gas
(#21281)
revert "feat(p2p): reject and evict txs with insufficient max fee per
gas (#21281)" (#21432)
chore: Reduce log spam (#21436)
fix(tx): reject txs with invalid setup when unprotecting (#21224)
fix: orchestrator enqueue yield (#21286)
chore(builder): check archive tree next leaf index during block building
(#21457)
fix: scenario deployment (#21428)
chore: add claude skill to read network-logs (#21495)
chore: update claude network-logs skill (#21523)
feat(rpc): add package version to RPC response headers (#21526)
chore(prover): silence "epoch to prove" debug logs (#21527)
chore(sequencer): do not log blob data (#21530)
fix: dependabot alerts (#21531)
docs(p2p): nicer READMEs (#21456)
fix(archiver): guard getL1ToL2Messages against incomplete message sync
(#21494)
fix(sequencer): await syncing proposed block to archiver (#21554)
feat(ethereum): check VK tree root and protocol contracts hash in rollup
compatibility (#21537)
fix: marking peer as dumb on failed responses (#21316)
fix(kv-store): make LMDB clear and drop operations atomic across
sub-databases (#21539)
feat(world-state): add blockHash verification to syncImmediate (#21556)
chore(monitor): print out l2 fees components (#21559)
chore: rm faucet (#21538)
chore: remove old merkle trees (#21577)
feat: Implement commit all and revert all for world state checkpoints
(#21532)
chore: skip flaky browser acir tests in CI (#21596)
fix: Better detection for epoch prune (#21478)
chore: logging (#21604)
fix: Don't update state if we failed to execute sufficient transactions
(#21443)
END_COMMIT_OVERRIDE
alexghr pushed a commit that referenced this pull request Mar 17, 2026
BEGIN_COMMIT_OVERRIDE
fix(aztec-nr): return Option from decode functions and fix event
commitment capacity (backport #21264) (#21360)
fix: backport #21271 — handle bad note lengths on
compute_note_hash_and_nullifier (#21364)
fix: not reusing tags of partially reverted txs (#20817)
chore: revert accidental backport of #20817 (#21583)
feat: Implement commit all and revert all for world state checkpoints
(#21532)
cherry-pick: fix: dependabot alerts (#21531)
fix: dependabot alerts (backport #21531 to v4) (#21592)
fix: backport #21443 — Don't update state if we failed to execute
sufficient transactions (v4) (#21610)
chore: Fix msgpack serialisation (#21612)
END_COMMIT_OVERRIDE

---------

Co-authored-by: Jan Beneš <janbenes1234@gmail.com>
Co-authored-by: PhilWindle <60546371+PhilWindle@users.noreply.github.com>
Co-authored-by: Phil Windle <philip.windle@gmail.com>
Co-authored-by: Santiago Palladino <santiago@aztecprotocol.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: ludamad <adam.domurad@gmail.com>
alexghr added a commit that referenced this pull request Mar 17, 2026
BEGIN_COMMIT_OVERRIDE
fix(aztec-nr): return Option from decode functions and fix event
commitment capacity (backport #21264) (#21360)
fix: backport #21271 — handle bad note lengths on
compute_note_hash_and_nullifier (#21364)
fix: not reusing tags of partially reverted txs (#20817)
chore: revert accidental backport of #20817 (#21583)
feat: Implement commit all and revert all for world state checkpoints
(#21532)
cherry-pick: fix: dependabot alerts (#21531)
fix: dependabot alerts (backport #21531 to v4) (#21592)
fix: backport #21443 — Don't update state if we failed to execute
sufficient transactions (v4) (#21610)
chore: Fix msgpack serialisation (#21612)
fix(p2p): fall back to maxTxsPerCheckpoint for per-block tx validation
(#21605)
chore: merge v4 into backport-to-v4-staging (#21618)
fix(revert): avm sim uses event loop again (#21138) (#21630)
fix(e2e): remove historic/finalized block checks from epochs_multiple
test (#21642)
fix: clamp finalized block to oldest available in world-state (#21643)
fix: skip handleChainFinalized when block is behind oldest available
(#21656)
chore: demote finalized block skip log to trace (#21661)
fix: off-by-1 in getBlockHashMembershipWitness archive snapshot
(backport #21648) (#21663)
fix: capture txs not available error reason in proposal handler (#21670)
chore: add L1 inclusion time to stg public (#21665)
END_COMMIT_OVERRIDE

---------

Co-authored-by: Jan Beneš <janbenes1234@gmail.com>
Co-authored-by: PhilWindle <60546371+PhilWindle@users.noreply.github.com>
Co-authored-by: Phil Windle <philip.windle@gmail.com>
Co-authored-by: Santiago Palladino <santiago@aztecprotocol.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: ludamad <adam.domurad@gmail.com>
Co-authored-by: Alex Gherghisan <alexghr@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants