feat(aggoracle): add UseUpdateExitRoot mode to forward both exit roots#1597
feat(aggoracle): add UseUpdateExitRoot mode to forward both exit roots#1597revitteth wants to merge 1 commit intoagglayer:developfrom
Conversation
…faucet_registry migration End-to-end validation of the RD-862 fix on the kurtosis stack required three small alignments: 1. `params.yaml`: set `aggkit_image: "miden-agglayer/aggkit:rd-862"` so the kurtosis-cdk parser threads our locally-built patched aggkit through to `bridge_service.star::_deploy_aggkit`. Without this the default `ghcr.io/agglayer/aggkit:0.9.0-rc2` was picked up and the `UseUpdateExitRoot` config was silently ignored. 2. `bridge_service.star`: add `UseUpdateExitRoot = true` to the `[AggOracle.EVMSender]` section of the inlined aggkit config template. This is the flag the patched aggkit reads to switch `EVMChainGERSender` from `direct_injection` to `direct_update_exit_root` mode. 3. `miden_services.star`: append `migrations/002_faucet_registry.sql` (CREATE TABLE faucet_registry + idx_faucet_origin) to the inlined migration SQL. Previously only `001_initial.sql` was inlined, so miden-proxy startup failed with `db error ERROR: relation "faucet_registry" does not exist`. Added a comment marking the inline as a snapshot and pointing at the long-term fix (upload the migrations dir as a files-artifact). Validation: kurtosis enclave deployed cleanly with all services RUNNING. aggkit logs show `EVMChainGERSender initialized in direct_update_exit_root mode` followed by `update-exit-root GER transaction submitted` — i.e. the two-root calldata path. miden-proxy logs show 0 `L1 exit roots don't match injected GER` warnings (orphan count = 0). The L1→Miden test deposit completes; both injected GERs committed without decomposition. Refs: - gateway-fm/miden-agglayer#37 - agglayer/aggkit#1597 - Linear RD-862 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@mandrigin — could you take a look when you have a moment? GitHub won't let me request you formally as a reviewer (not a collaborator on agglayer/aggkit) so cc-ing here. This closes RD-862 on the gateway-fm/miden-agglayer side: under rapid L1 deposit load, ~85% of GERs orphan because the receiving service can't reverse Backward-compatible (additive interface methods, new The receiving-side PR is at gateway-fm/miden-agglayer#37 if you want the full picture. |
Closes the GER decomposition race (RD-862) on sovereign chains that can't reverse keccak(mainnetExitRoot || rollupExitRoot). When configured with `UseUpdateExitRoot: true`, the EVM ChainSender forwards the uncombined (mainnet, rollup) pair via updateExitRoot(bytes32,bytes32) instead of the combined hash via insertGlobalExitRoot(bytes32). The downstream service no longer needs to fetch live L1 state to decompose, eliminating the race window that strands deposits as ready_for_claim=false under rapid-burst L1 traffic. Why a new mode (vs. flipping DirectInjectionMode): - Existing DirectInjectionMode is the universal default and other rollups may rely on the combined-hash semantics. Gating behind a config flag preserves backward compat. - AggOracleCommitteeMode is unaffected; the committee path still uses proposeGlobalExitRoot(combined). How it threads through: - L1InfoTreeSyncer interface gains GetLatestL1InfoLeaf() so the oracle can fetch the full leaf instead of just the GER hash. The implementer (l1infotreesync.L1InfoTreeSync) already exposes this method. - ChainSender interface gains UsesTwoRootMode() + ProcessGERWithRoots() for additive opt-in. Existing ProcessGER() unchanged. - AggOracle.processLatestGER() branches on the sender's mode flag and fetches/dispatches accordingly. - EVMChainGERSender.InjectExitRoots() packs updateExitRoot calldata manually because cdk-contracts-tooling@v0.0.12's agglayergerl2 binding only exposes the 1-arg `updateExitRoot(bytes32)` overload. The 2-arg `updateExitRoot(bytes32 newRollupExitRoot, bytes32 newMainnetExitRoot)` is the variant that actually carries the pair (selector 0x736ca7f4). Validation: - `go test ./aggoracle/...` — green - e2e on miden-agglayer's docker-compose stack: orphan GER rate at N=30 back-to-back deposits dropped from 85% (18/21) to 0% (0/8), deposit→ready_for_claim conversion 30/30. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d46f980 to
490ec81
Compare
|
@arnaubennassar @joanestebanr — apologies for the misdirected cc to mandrigin in my earlier comment (had a wrong mental model of who maintains aggkit). Could either of you take a look when you have a moment? CI is fully green on the substance side:
The one red check ( Recap of the change: opt-in Thanks! |
Summary
Adds an opt-in
UseUpdateExitRootmode toAggOracle.EVMSenderso the chain sender forwards the uncombined(mainnet, rollup)exit root pair viaupdateExitRoot(bytes32 newRollupExitRoot, bytes32 newMainnetExitRoot)instead of the combinedkeccak(mainnet||rollup)hash viainsertGlobalExitRoot(bytes32 combined).Why
On sovereign chains that can't reverse
keccak(mainnet||rollup)(e.g. Miden), the combined-hash path forces the receiving service to fetch live L1 state and verifykeccak(latest_mainnet, latest_rollup) == combinedGER. Under rapid L1 deposit load this races: by the time a given GER lands at the destination, L1 has typically advanced and the verification fails — the GER is stored without resolved roots, and any deposit covered by it can be strandedready_for_claim=falseindefinitely.We measured 85% orphan rate at N=30 back-to-back deposits on the receiving service (gateway-fm/miden-agglayer): 18 of 21 GERs failed to decompose. Forwarding the pair from the
L1InfoTreeLeafaggoracle already has eliminates the race entirely — orphan rate 0%, deposit→ready_for_claim conversion 30/30.Design
DirectInjectionMode(default) — unchanged, callsinsertGlobalExitRoot(combined).AggOracleCommitteeMode— unchanged, callsproposeGlobalExitRoot(combined).DirectUpdateExitRootMode(new) — callsupdateExitRoot(rollup, mainnet).EVMConfig.UseUpdateExitRoot bool.L1InfoTreeSyncerinterface gainsGetLatestL1InfoLeaf()(the impl already exposes it).ChainSenderinterface gains additiveUsesTwoRootMode()+ProcessGERWithRoots()methods. ExistingProcessGER(ctx, ger)left in place.AggOracle.processLatestGER()branches onUsesTwoRootMode()and dispatches accordingly.Manual calldata packing
EVMChainGERSender.InjectExitRoots()packsupdateExitRoot(bytes32,bytes32)calldata by hand using the known selector0x736ca7f4. The reason:cdk-contracts-tooling@v0.0.12'sagglayergerl2Go binding only exposes the 1-argupdateExitRoot(bytes32)overload, soabi.Pack(funcName, rollup, mainnet)would fail. The 2-arg selector is what the Miden sovereign GER manager (and any contract that supports recomputing the combined GER on-chain) actually expects.If/when
cdk-contracts-toolingships a binding with both overloads, the manual packing can swap back toabi.Pack.Test plan
go test ./aggoracle/...— green (oracle + chaingersender)Reference
🤖 Generated with Claude Code