From f30a966d5e4c98c91e56a2a293723f4199f59979 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Tue, 10 Mar 2026 18:05:20 +0000 Subject: [PATCH 1/2] feat(agglayer): store block number with each registered GER Store the transaction's reference block number alongside the GER flag in the bridge's GER map, changing the value from [1, 0, 0, 0] to [1, block_num, 0, 0]. This enables recovery scenarios that need to know when each GER was registered. The assert_valid_ger procedure now checks only the flag element instead of comparing the full word, since the block_num element is variable. Closes #2578 Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/miden-agglayer/SPEC.md | 6 +++-- .../asm/agglayer/bridge/bridge_config.masm | 26 ++++++++++++------- .../tests/agglayer/update_ger.rs | 7 ++--- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/crates/miden-agglayer/SPEC.md b/crates/miden-agglayer/SPEC.md index 7fc10e19b0..f94d389c21 100644 --- a/crates/miden-agglayer/SPEC.md +++ b/crates/miden-agglayer/SPEC.md @@ -104,7 +104,9 @@ Asserts the note sender matches the bridge admin stored in Asserts the note sender matches the GER manager stored in `miden::agglayer::bridge::ger_manager`, then computes `KEY = rpo256::merge(GER_UPPER, GER_LOWER)` and stores -`KEY -> [1, 0, 0, 0]` in the `ger` map slot. This marks the GER as "known". +`KEY -> [1, block_num, 0, 0]` in the `ger` map slot, where `block_num` is the +transaction's reference block number. This marks the GER as "known" and records +when it was registered. #### `bridge_in::verify_leaf_bridge` @@ -129,7 +131,7 @@ Verifies a bridge-in claim: | Slot name | Slot type | Key encoding | Value encoding | Purpose | |-----------|-----------|-------------|----------------|---------| -| `miden::agglayer::bridge::ger` | Map | `rpo256::merge(GER_UPPER, GER_LOWER)` | `[1, 0, 0, 0]` if known; `[0, 0, 0, 0]` if absent | Known Global Exit Root set | +| `miden::agglayer::bridge::ger` | Map | `rpo256::merge(GER_UPPER, GER_LOWER)` | `[1, block_num, 0, 0]` if known (block_num = reference block at registration); `[0, 0, 0, 0]` if absent | Known Global Exit Root set | | `miden::agglayer::let` | Map | `[h, 0, 0, 0]` and `[h, 1, 0, 0]` (for h = 0..31) | Per index h: two keys yield one double-word (2 words = 8 felts, a Keccak-256 digest). Absent keys return zeros. | Local Exit Tree MMR frontier | | `miden::agglayer::let::root_lo` | Value | -- | `[root_0, root_1, root_2, root_3]` | LET root low word (Keccak-256 lower 16 bytes) | | `miden::agglayer::let::root_hi` | Value | -- | `[root_4, root_5, root_6, root_7]` | LET root high word (Keccak-256 upper 16 bytes) | diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm index 2e2d80da89..90d8b152c5 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm @@ -3,6 +3,7 @@ use miden::protocol::account_id use miden::protocol::active_account use miden::protocol::active_note use miden::protocol::native_account +use miden::protocol::tx # ERRORS # ================================================================================================= @@ -31,7 +32,8 @@ const IS_FAUCET_REGISTERED_FLAG=1 #! Updates the Global Exit Root (GER) in the bridge account storage. #! #! Computes hash(GER) = rpo256::merge(GER_UPPER, GER_LOWER) and stores it in a map -#! with value [GER_KNOWN_FLAG, 0, 0, 0] to indicate the GER is known. +#! with value [GER_KNOWN_FLAG, block_num, 0, 0] to indicate the GER is known and +#! record the block at which it was registered. #! #! Panics if the note sender is not the global exit root manager. #! @@ -49,9 +51,15 @@ pub proc update_ger exec.rpo256::merge # => [GER_HASH, pad(12)] - # prepare VALUE = [0, 0, 0, GER_KNOWN_FLAG] - push.GER_KNOWN_FLAG.0.0.0 - # => [0, 0, 0, GER_KNOWN_FLAG, GER_HASH, pad(12)] + # get the reference block number + exec.tx::get_block_number + # => [block_num, GER_HASH, pad(12)] + + # prepare VALUE = [0, 0, block_num, GER_KNOWN_FLAG] + push.GER_KNOWN_FLAG + swap + push.0.0 + # => [0, 0, block_num, GER_KNOWN_FLAG, GER_HASH, pad(12)] swapw # => [GER_HASH, VALUE, pad(12)] @@ -86,13 +94,13 @@ pub proc assert_valid_ger # => [slot_id_prefix, slot_id_suffix, GER_HASH] exec.active_account::get_map_item - # => [VALUE] + # => [0, 0, block_num, GER_KNOWN_FLAG] - # assert the GER is known in storage (VALUE = [0, 0, 0, GER_KNOWN_FLAG]) - push.GER_KNOWN_FLAG.0.0.0 - # => [0, 0, 0, GER_KNOWN_FLAG, VALUE] + # assert the GER is known in storage (check only the flag element) + drop drop drop + # => [GER_KNOWN_FLAG] - assert_eqw.err=ERR_GER_NOT_FOUND + assert.err=ERR_GER_NOT_FOUND # => [] end diff --git a/crates/miden-testing/tests/agglayer/update_ger.rs b/crates/miden-testing/tests/agglayer/update_ger.rs index 910046db96..47c1bd4d05 100644 --- a/crates/miden-testing/tests/agglayer/update_ger.rs +++ b/crates/miden-testing/tests/agglayer/update_ger.rs @@ -119,9 +119,10 @@ async fn update_ger_note_updates_storage() -> anyhow::Result<()> { .get_map_item(ger_storage_slot, ger_hash) .expect("GER hash should be stored in the map"); - // The stored value should be [GER_KNOWN_FLAG, 0, 0, 0] = [1, 0, 0, 0] - let expected_value: Word = [Felt::ONE, Felt::ZERO, Felt::ZERO, Felt::ZERO].into(); - assert_eq!(stored_value, expected_value, "GER hash should map to [1, 0, 0, 0]"); + // The stored value should be [GER_KNOWN_FLAG, block_num, 0, 0] + let block_num = mock_chain.latest_block_header().block_num(); + let expected_value: Word = [Felt::ONE, Felt::from(block_num), Felt::ZERO, Felt::ZERO].into(); + assert_eq!(stored_value, expected_value, "GER hash should map to [1, block_num, 0, 0]"); Ok(()) } From 1fcbd9d5b62f2c8bb8356977f0e69f84b8342874 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Tue, 10 Mar 2026 18:08:49 +0000 Subject: [PATCH 2/2] refactor: push GER_KNOWN_FLAG before get_block_number to avoid swap Address review feedback: reorder stack operations so the flag is pushed before getting the block number, eliminating the need for a swap. Also add changelog entry. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + .../miden-agglayer/asm/agglayer/bridge/bridge_config.masm | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c548751e0..0d9df6188d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Store transaction reference block number alongside each registered GER in the bridge's GER map ([#2579](https://github.com/0xMiden/protocol/pull/2579)). - Added AggLayer faucet registry to bridge account with conversion metadata, `CONFIG_AGG_BRIDGE` note for faucet registration, and FPI-based asset conversion in `bridge_out` ([#2426](https://github.com/0xMiden/miden-base/pull/2426)). - Enable `CodeBuilder` to add advice map entries to compiled scripts ([#2275](https://github.com/0xMiden/miden-base/pull/2275)). - Added `BlockNumber::MAX` constant to represent the maximum block number ([#2324](https://github.com/0xMiden/miden-base/pull/2324)). diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm index 90d8b152c5..e2af6240ab 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm @@ -51,13 +51,9 @@ pub proc update_ger exec.rpo256::merge # => [GER_HASH, pad(12)] - # get the reference block number - exec.tx::get_block_number - # => [block_num, GER_HASH, pad(12)] - # prepare VALUE = [0, 0, block_num, GER_KNOWN_FLAG] push.GER_KNOWN_FLAG - swap + exec.tx::get_block_number push.0.0 # => [0, 0, block_num, GER_KNOWN_FLAG, GER_HASH, pad(12)]