Skip to content

test: migrate miden integration fixtures to protocol/client 0.15.x#5

Draft
revitteth wants to merge 15 commits into
mainfrom
feat/protocol-0.15-migration
Draft

test: migrate miden integration fixtures to protocol/client 0.15.x#5
revitteth wants to merge 15 commits into
mainfrom
feat/protocol-0.15-migration

Conversation

@revitteth

@revitteth revitteth commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Migrate aggkit-proxy miden test fixtures to protocol/client 0.15.x

Draft — in lockstep with gateway-fm/miden-agglayer#80. Pins track the unreleased miden-client 0.15 branch (PR 0xMiden/rust-sdk#2224); re-pin to the v0.15.x tags when they ship.

What this does

  • Cargo pins bumped to protocol/standards/tx 0.15 + the miden-client 0.15 branch.
  • Rust fixtures (tests/phase1-3.rs) ported to 0.15: auth RPO→Poseidon2 (AuthSingleSig + AuthScheme::Falcon512Poseidon2); async keystore.add_key(key, account_id) (moved post-build); AccountStorageMode folded into AccountType (kind now derives from components); BasicFungibleFaucetFungibleFaucet::builder (AssetAmount max-supply + TokenName); build_consume_notes takes Vec<Note> via InputNoteRecord::try_into; fallible Felt::new. Compiles clean (cargo check --all-targets); these are live tests (need a node).
  • Python phase3 suite aligned with the miden-agglayer proxy and now 80 passed / 0 failed / 7 skipped against it (env-driven: PROXY_URL/MIDEN_NODE_URL/BRIDGE_SERVICE_URL):
    • gas tests: the proxy deliberately returns eth_gasPrice=0x3b9aca00 (1 gwei, EVM-tooling compat) and eth_estimateGas=0x0 (Miden has no gas) — assertions updated (were expecting 0 / 21000, from the old gutted proxy).
    • error tests: catch by JSON-RPC .code instead of the RPCError class (pytest loads conftest as the top-level module, so from tests.conftest import RPCError is a different class object that pytest.raises wouldn't match).

Questions for the Miden team (0.15 integration)

The open upstream questions surfaced by this migration are protocol/node-level, not proxy-specific, so they are documented in full on the companion PR gateway-fm/miden-agglayer#80 ("Questions for the Miden team"). Summary:

  1. Node store drops AssetCallbackFlag in vault-key lookups → silent bridge-out tx-loss (3 sites; patch attached on #80).
  2. Version-skew / lockstep release of the agglayer base crate vs the node (MASM procedure-root mismatch 0.15.0 ↔ 0.15.2).
  3. MIDEN_NETWORK_ID topology pinning (77 in MASM vs L1 RollupManager net 1; no single id satisfies both stock ends).

Known open

  • phase2 python tests are pre-existing dead scaffolding — they import a conftest API (ClaimAssetParams, GlobalIndex, ProxyRPCClient, CLAIM_NOTE_FELT_SIZE, …) that conftest.py never defined, so they fail at collection and have never run. Out of scope here (not touched by this migration); the CLAIM encoding they were meant to unit-test is covered end-to-end by the miden-agglayer bridge-in e2e.

mandrigin and others added 15 commits March 10, 2026 13:08
…#1882)

Bump miden-client from exp-agglayer-v0.2 tag to agglayer-integration-tests
branch, along with all base crates to 0.14.0-alpha (miden-protocol,
miden-standards, miden-agglayer, miden-tx) and rusqlite to 0.37.

API migrations:
- AuthFalcon512Rpo → AuthSingleSig with AuthScheme::Falcon512Rpo
- ClaimNoteParams → ClaimNoteStorage with typed ProofData/LeafData
- create_agglayer_faucet_builder (private) → create_agglayer_faucet (public)
- create_bridge_account gains bridge_admin_id + ger_manager_id params
- create_claim_note takes (storage, faucet_id, sender_id, rng)
- build_p2id_recipient → P2idNoteStorage::into_recipient
- create_p2id_note → P2idNote::create
- Note::inputs() → Note::storage(), NoteInputs → NoteStorage
- Keystore::add_key now async and requires account_id
- ClientBuilder::filesystem_keystore now returns Result

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from miden-node (exp-agglayer-v0.2, 0.13) to testing-node-builder
from miden-client (agglayer-integration-tests branch, 0.14.0-alpha).
This is the only binary that supports agglayer genesis accounts
(bridge_admin, ger_manager, bridge, faucet).

Includes sed patch to bind RPC to 0.0.0.0 for Docker port forwarding
(upstream testing-node-builder hardcodes 127.0.0.1).

Tested: agglayer_bridge_in_out e2e test passes against Dockerized node.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "geth" engine type is no longer supported by kurtosis-cdk;
switch to "ethereum-package" which deploys geth+lighthouse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 0.14 miden-client faucet auto-consumes CLAIM notes via the NTX builder,
so the proxy no longer needs to execute Phase 2 (faucet consumption + P2ID
minting). This removes ~220 lines of Phase 2 logic including P2ID script
registration, block-wait polling, foreign account setup, and output note
tracking.

Also fixes list-unclaimed-notes.sh Docker path (/data → /app/data) to match
the testing-node-builder's actual data directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add submit_ger_to_miden(): when aggoracle sends insertGlobalExitRoot,
  create and submit an UpdateGerNote so the bridge account stores the GER
  on-chain. Without this, the faucet's CLAIM note script fails
  verify_leaf_bridge (GER lookup returns nothing).

- Fix double-scaling bug in CLAIM amount: raw wei bytes now flow through
  ClaimSubmissionData.amount_wei instead of being pre-scaled to u64.
  Scaling (÷ 10^10) happens once in submit_claim_to_miden() via
  EthAmount::scale_to_token_amount(), matching the integration test pattern.
  Previously the amount was scaled in the RPC handler then scaled again,
  producing 0.

- Fix list-notes.sh: correct DB path (/app/data/ not /data/) and replace
  non-existent 'inputs' column with 'target_account_id'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix block number parsing, address encoding, and block hash generation
to enable reliable bridge synchronization and end-to-end deposit flow.

- Parse decimal block numbers from bridge-service ("12" not just "0x12")
- Fix Miden-to-Eth address encoding: 4-byte padding to match bridge
  contract's prefix(8)/suffix(8) split at offset 4
- Simplify block hashes to keccak("miden_block_evm_<N>") — pure function
  of block number, eliminates false reorg detection from creation order
- Re-tagged pending events use deterministic hashes directly

Verified full flow: L1 deposit → bridge sync → CLAIM → faucet → P2ID → claim

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…service

The zkevm-bridge-service's reorg detection computes block hashes via
Go's ethclient HeaderByNumber + header.Hash() = keccak256(rlp(header)),
NOT from the JSON response's hash field. Our synthetic hash was always
different, causing false reorgs on every sync cycle.

Fix block_state.rs to build a real alloy_consensus::Header and compute
keccak256(rlp(header)) — matching Go's computation exactly.

Fix ClaimEvent to use the v2 topic hash (uint256 globalIndex) instead of
the old v1 hash (uint32 leafType), and encode the actual globalIndex in
the event data so each claim gets a unique primary key in the bridge DB.

Add stress test scripts and report documenting 10-deposit full pipeline:
L1 deposit → bridge sync → ready_for_claim → CLAIM note → P2ID → claim.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, eth_getTransactionReceipt returned status=0x1 (success)
immediately after the CLAIM note transaction was submitted to Miden.
However, a CLAIM note submission always "succeeds" as a valid Miden
transaction — regardless of whether the agglayer faucet actually
consumes the note.

The faucet only consumes a CLAIM note when:
1. The Global Exit Root (GER) is registered in bridge storage
2. The SMT proof verifies against the registered GER
3. The faucet is online and processing notes

If any condition fails, the note sits unconsumed forever. The bridge
would see a success receipt, mark the deposit as "claimed", but no
P2ID note would be minted — deposited funds lost with no error signal.

The fix introduces a new TxStatus::AwaitingConsumption state:
- After CLAIM note submission: status = AwaitingConsumption (not Confirmed)
- Bridge polls getTransactionReceipt: proxy syncs with Miden node,
  checks if the output note state is "Consumed"
- If consumed: transition to Confirmed, synthesize ClaimEvent, return
  success receipt (status=0x1)
- If not consumed yet: return null (tx pending from bridge's POV)
- If not consumed within 50 blocks: return reverted receipt (status=0x0)

This only affects claimAsset transactions. GER injections and other
tx types still confirm immediately.

Tested E2E: deposit → bridge sync → claimAsset → receipt pending →
CLAIM note consumed at next block → receipt success → bridge confirms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the aggkit-proxy (vibecoded) implementation with miden-agglayer
in the Kurtosis test infrastructure. This includes:

- Deploy PostgreSQL for miden-agglayer's persistent store
- Run database migration (001_initial.sql) inline via plan.exec
- Pass L1 RPC URL and contract addresses (bridge, GER, rollup manager)
  to the proxy via environment variables
- Use miden-agglayer CLI args (--chain-id, --miden-node, --port)
- Enable aggsender component alongside aggoracle in aggkit config
- Add AggSender configuration for L2→L1 certificate submission
- Point AggLayer's full-node-rpc to Miden proxy
- Improve miden-node logging (ntx_builder debug)
- Patch miden-node ntx-builder for CLAIM failure logging

Tested: Kurtosis deploy succeeds, L1 deposit → L2 claim works end-to-end,
GER injection active, ClaimEvent logs emitted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Kurtosis package (kurtosis/miden-cdk/, see commit e7b4660) already
deploys gateway-fm/miden-agglayer as the production miden-proxy service.
The lib + main + bin code in src/ was the prior vibecoded implementation
and has been dead code since the supplant. Worse, it didn't compile
against current miden-client (API drift across `Felt::as_int`,
`OutputNote::Full`, `create_agglayer_faucet` signature, etc.) so
`cargo check` was failing on this branch.

What's removed:
- src/main.rs and the entire `miden-rpc-proxy` binary.
- src/{address_mapper,agglayer_faucet,asset_mapping,block_state,
  claim_tracker,client,config,decode,error,log_synthesis,receipt,
  storage,types}.rs — the vibecoded modules. Equivalents (in some
  cases improved) live in gateway-fm/miden-agglayer.
- src/bin/{verify_notes,claim_note}.rs — operator helpers that also
  drifted off miden-client v0.14.4 (use deprecated `Felt::as_int`).
  If we want them back, port them against the current API.
- examples/gen_signed_tx.rs — same drift class.

What's kept:
- tests/phase{1,2,3}.rs + tests/common — miden-client integration
  tests. Independent of the proxy. Currently have their own API drift
  vs v0.14.4 (separate fix); not blocking the supplant.
- kurtosis/miden-cdk/ — Starlark package that deploys miden-agglayer +
  aggkit + bridge-service + agglayer + miden-node.
- scripts/ — operator/test harnesses that drive the kurtosis stack.

Cargo.toml dropped to a thin shell:
- Pin miden-client to `tag = "v0.14.4"` (was `branch = "agglayer-integration-tests"`)
  to match gateway-fm/miden-agglayer's pin.
- Loose carets on miden-protocol/standards/tx so cargo can resolve
  the exact subversion v0.14.4 transitively requires.
- Drop jsonrpsee/tower/alloy/etc — none of the surviving code uses them.

Follow-up (separate work):
- Port tests/phase{1,2,3}.rs to miden-client v0.14.4 API
  (`build_consume_notes` now takes Vec<Note>, etc.).
- Run scripts/e2e-kurtosis.sh against the patched
  miden-agglayer:rd-862 image to validate the full e2e flow.
- Once gateway-fm/miden-agglayer's RD-862 fix and agglayer/aggkit's
  upstream PR land, swap the kurtosis package's image refs to public
  tagged releases.

Refs:
- gateway-fm/miden-agglayer#37
- agglayer/aggkit#1597
- Linear RD-862

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…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>
miden-agglayer reads the L1 GER contract address from env var
GER_L1_ADDRESS (src/main.rs:80, clap attr `env = "GER_L1_ADDRESS"`).
Kurtosis package was passing it as L1_GER_ADDRESS, so the var was
silently dropped — the proxy logged:

  WARN L1 RPC URL or GER contract address missing; L1InfoTreeIndexer
  disabled. Without it, GER orphan resolution falls back to the
  racing view-call path in service_send_raw_txn.rs and may produce
  orphan GERs under deposit load.

With the rename, the indexer activates on bring-up and we see e.g.

  INFO L1InfoTreeIndexer: indexed exit-root pair, event:
  "UpdateL1InfoTree", mainnet: 2554ea8a..., rollup: 0000...,
  combined: 716b1dba..., block: 147
  INFO L1InfoTreeIndexer batch processed, from: 147, to: 147,
  head: 147, log_count: 1, indexed: 1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps miden-protocol/standards/tx to 0.15 and miden-client to the upstream protocol-0.15 branch (PR 0xMiden/rust-sdk#2224), in lockstep with gateway-fm/miden-agglayer.

Ports phase1-3 fixtures. Auth switched from RPO Falcon512 to Poseidon2 (AuthSingleSig with AuthScheme::Falcon512Poseidon2). Keystore add_key is now async and takes the account id, moved after build. AccountStorageMode folded into AccountType Private/Public (account kind now derives from components). BasicFungibleFaucet becomes FungibleFaucet::builder (name/symbol/decimals/max_supply AssetAmount). build_consume_notes takes Vec Note via InputNoteRecord try_into. cargo check --all-targets green (tests are live and compiled only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation

The phase3 pytest suite runs against the miden-agglayer proxy (PROXY_URL/MIDEN_NODE_URL/BRIDGE_SERVICE_URL env-driven). Six tests carried stale expectations from the old gutted miden-rpc-proxy and now pass against the real proxy: 80 passed, 7 skipped.

Gas (test_tc_3_2): the miden-agglayer proxy deliberately returns eth_gasPrice=0x3b9aca00 (1 gwei, for EVM-tooling compatibility) and eth_estimateGas=0x0 (Miden has no gas), per service.rs. Updated assertions to match (was expecting 0 and 21000).

Errors (test_tc_3_1, test_tc_3_12): catch by JSON-RPC .code instead of the RPCError class. pytest loads conftest as the top-level conftest module, so the fixture raises conftest.MidenRPCError while 'from tests.conftest import RPCError' is a different class object that pytest.raises would not match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a standalone zkevm-autoclaimer to the miden-cdk kurtosis package so L2->L1
exits are claimed on L1 automatically (sponsoring claimAsset), gated behind a
`miden.deploy_autoclaimer` flag (default off).

- bridge_service.star: new _deploy_autoclaimer — funds the claim sponsor on L1
  (it pays gas for claimAsset), renders an autoclaim config (SourceNetworkID =
  l2_network_id so we only sponsor our own rollup's exits; L2RPC = L1 RPC since
  the claim lands on L1; PolygonBridgeAddress = L1 bridge), reuses the funded
  claimsponsor keystore, and runs /app/zkevm-autoclaimer from the patched
  bridge-service image. deploy() gains a deploy_autoclaimer arg.
- main.star: MIDEN_DEFAULTS.deploy_autoclaimer = False; threaded into
  bridge_service.deploy().

Files reformatted by `kurtosis lint --format` (black).

Validated independently against an upstream kurtosis-cdk enclave: the
autoclaimer found settled L2->L1 exits and sponsored claimAsset on L1 for a
backlog of deposits (network 1).
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.

2 participants