feat: Implement commit all and revert all for world state checkpoints#21532
feat: Implement commit all and revert all for world state checkpoints#21532PhilWindle merged 6 commits intomerge-train/spartanfrom
Conversation
|
⏳ Run #1 — Session completed (3m) 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 |
spalladino
left a comment
There was a problem hiding this comment.
I feel I'm missing some understanding of how things worked beforehand. Left a few questions that may help me follow along.
| if (target_depth == 0 || target_depth > journals_.size()) { | ||
| throw std::runtime_error("Invalid depth for revert_to_depth"); | ||
| } | ||
| while (journals_.size() >= target_depth) { | ||
| revert(); | ||
| } |
There was a problem hiding this comment.
I'm probably missing something, but shouldn't we allow a revert_to_depth(0) that reverts everything? Same for commit?
There was a problem hiding this comment.
Rightly or wrongly, it's inclusive. So revert_to_depth(1) reverts everything.
There was a problem hiding this comment.
Modified to be exclusive. So revert_to_depth(0) would revert all.
| cache.commit_to_depth(1); | ||
| EXPECT_EQ(cache.depth(), 0u); |
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
|
Claude seems to like it! |
Co-authored-by: Santiago Palladino <santiago@aztecprotocol.com>
| // 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(); |
There was a problem hiding this comment.
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?
| 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, | ||
| }); | ||
| } |
There was a problem hiding this comment.
I don't think this method is used anywhere?
…#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
|
✅ Successfully backported to backport-to-v4-staging #21582. |
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
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>
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>
Summary
commitAllCheckpointsTo(depth)andrevertAllCheckpointsTo(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).ForkCheckpointstores its depth and exposesrevertToCheckpoint()which encapsulates the revert-to-depth pattern, replacing the previousrevertAllCheckpoints()+markCompleted()two-step.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, newcommit_to_depth()/revert_to_depth()methodsCachedContentAddressedTreeStore: passes through depth-aware operationsContentAddressedAppendOnlyTree:CheckpointCallbacknow receivesTypedResponse<CheckpointResponse>with depthWorldState:checkpoint()returns depth,commit_all_checkpoints_to/revert_all_checkpoints_totake required depthForkIdWithDepthRequest/CheckpointDepthResponsemessage typesTypeScript
MerkleTreeCheckpointOperationsinterface:createCheckpoint()returnsPromise<number>, depth is required oncommitAllCheckpointsTo/revertAllCheckpointsToMerkleTreesFacade: passes depth through native message channelForkCheckpoint: stores depth, newrevertToCheckpoint()methodPublicProcessor: usescheckpoint.revertToCheckpoint()on error pathsTests
commit_to_depth,revert_to_depth, edge casesTest plan
crypto_content_addressed_cache_tests)crypto_content_addressed_append_only_tree_tests)native_world_state.test.tspassesfork_checkpoint.test.tspassespublic_processor.test.tspassestimeout_race.test.tspasses