feat(agglayer): add GER removal mechanism#2837
Conversation
e6bc635 to
0511b44
Compare
8ebfee1 to
82b6703
Compare
1ab2ca7 to
6f83e87
Compare
|
Putting into draft mode until |
2ffbdff to
bcce07b
Compare
bcce07b to
6962c85
Compare
Adds a remove_ger MASM procedure gated by a new ger_remover role (separate from the existing GER manager) that revokes a previously registered Global Exit Root and folds it into a running keccak256 hash chain. Mirrors the Solidity sovereign-chain removeGlobalExitRoots / removedGERHashChain pair. Adds a REMOVE_GER note script, a RemoveGerNote Rust helper, integration tests, and updates the spec.
6962c85 to
4b74055
Compare
… in SPEC Updates the sections that were missed when the GER removal mechanism was added: the administration section now lists the GER remover as a third role, the bridge component procedure list includes remove_ger, the storage table covers the ger_remover_account_id and removed_ger_hash_chain_lo/hi slots, and a new section 4.5 specifies the REMOVE_GER note (BURN/P2ID/MINT renumbered to 4.6-4.8).
- remove_ger now clears the map entry with a single set_map_item and asserts the returned OLD_VALUE equals GER_KNOWN_FLAG, matching the update_ger idiom and dropping one SMT operation per removal. - Extract the duplicated felt-to-LE-bytes conversion shared by cgi_chain_hash and removed_ger_hash_chain into a chain_hash_bytes helper, and return a RemovedGerHashChain newtype (Keccak256Output) instead of a raw byte array, mirroring CgiChainHash. - Deduplicate the per-test wallet/bridge setup in the remove_ger integration tests into a setup_bridge helper.
mmagician
left a comment
There was a problem hiding this comment.
Approved modulo:
- a few general questions
- SPEC needs more info
the refactors (code de-dup) are optional and not even sure if possible cleanly, just a direction to explore
| #! - the GER is not currently registered in the bridge's GER map. | ||
| #! | ||
| #! Invocation: call | ||
| pub proc remove_ger |
| #! Loads the old removed-GER hash chain onto the stack, below the existing GER preimage. | ||
| #! | ||
| #! Inputs: [GER_LOWER[4], GER_UPPER[4]] | ||
| #! Outputs: [OLD_CHAIN[8], GER_LOWER, GER_UPPER] | ||
| #! | ||
| #! Invocation: exec | ||
| proc load_removed_ger_hash_chain_data |
There was a problem hiding this comment.
I think this procedure doesn't do anything with the inputs, and we usually just mark it as [] (i.e. it only appends elements to the output)
| #! Outputs: [] | ||
| #! | ||
| #! Invocation: exec | ||
| proc update_removed_ger_hash_chain |
There was a problem hiding this comment.
Question: what is the initial value of the OLD_CHAIN when we haven't removed anything yet?
And does it match solidity's reference?
| call.bridge_config::remove_ger | ||
| # => [pad(16)] |
There was a problem hiding this comment.
It looks like this note script is very similar to the GER injection script, with the only difference being in the procedure called on the bridge account. I wonder if we could re-use any code?
| ger_manager_id: AccountId, | ||
| ger_remover_id: AccountId, |
There was a problem hiding this comment.
I would re-work the naming a bit.
"manager" sounds like it can already remove items, which is not the case here.
So maybe ger_injector or ger_updater? Though they only sound half good :)
There was a problem hiding this comment.
similar comment as for the MASM note script, it would be great if we could re-use some code between the update and remove note, as these files are almost identical now
| A separate GER Remover role can revoke a previously-registered GER by sending a | ||
| `REMOVE_GER` note. The bridge consumes such a note and: |
There was a problem hiding this comment.
I would add a note somewhere in the spec for the conditions under which a GER removal can/is expected to happen, and its impact on the Miden chain. This is non-trivial, it likely means that something has gone wrong upstream in AggLayer IIUC, but would be good to research the exact conditions & consequences in more detail so we know why we have the removal in the first place.
There was a problem hiding this comment.
Also, worth noting that the manager can re-register a removed GER, so a compromised manager can undo the REMOVE_GER emergency patch and re-open the attack window.
This is mitigated only if the manager is rotated out, but this is currently unimplemented until #2706 lands.
| /// Tests that calling REMOVE_GER twice on the same GER reverts the second call with | ||
| /// ERR_GER_NOT_FOUND. Locks in the invariant that a removed entry stays at [0,0,0,0] | ||
| /// and cannot be re-removed. | ||
| #[tokio::test] | ||
| async fn remove_ger_double_remove_reverts() -> anyhow::Result<()> { |
There was a problem hiding this comment.
I think this test subsumes one of the previous tests which just verifies ERR_GER_NOT_FOUND
There was a problem hiding this comment.
wow that's pretty comprehensive
| let mut builder = MockChain::builder(); | ||
|
|
||
| let bridge_admin = builder.add_existing_wallet(Auth::BasicAuth { | ||
| auth_scheme: AuthScheme::Falcon512Poseidon2, | ||
| })?; | ||
| let ger_manager = builder.add_existing_wallet(Auth::BasicAuth { | ||
| auth_scheme: AuthScheme::Falcon512Poseidon2, | ||
| })?; | ||
| let ger_remover = builder.add_existing_wallet(Auth::BasicAuth { | ||
| auth_scheme: AuthScheme::Falcon512Poseidon2, | ||
| })?; | ||
|
|
||
| let bridge_seed = builder.rng_mut().draw_word(); | ||
| let bridge_account = create_existing_bridge_account( | ||
| bridge_seed, | ||
| bridge_admin.id(), | ||
| ger_manager.id(), | ||
| ger_remover.id(), | ||
| ); | ||
| builder.add_account(bridge_account.clone())?; |
There was a problem hiding this comment.
AFAICS most (all?) of these tests share the same setup
| pub fn removed_ger_hash_chain( | ||
| bridge_account: &Account, | ||
| ) -> Result<[u8; 32], AgglayerBridgeError> { |
There was a problem hiding this comment.
nit: rather than using a bare [u8; 32], let's have a stronger type, I think Keccak256Output should suffice, or maybe a dedicated wrapper if it makes more sense for some reason?
…alue - load_removed_ger_hash_chain_data only appends OLD_CHAIN and passes the GER preimage through untouched, so its doc declares Inputs: [] / Outputs: [OLD_CHAIN[8]]. - Document that the chain seeds from the empty word [0, 0, 0, 0], so the first removal yields keccak256(0..0 || ger), matching the zero-initialized removedGERHashChain bytes32 in Solidity's GlobalExitRootManagerL2SovereignChain.
…veat Adds the conditions under which a GER removal is expected (an exceptional, emergency-only response to an invalid/erroneous upstream root) and its impact on the Miden chain (unprocessed CLAIMs against the removed GER revert; already-processed claims are unaffected). Makes explicit that a compromised or faulty GER manager can re-register a removed GER and undo the emergency patch, bounded only once role rotation lands (#2706).
remove_ger_double_remove_reverts already exercises the ERR_GER_NOT_FOUND path (and the stronger invariant that a removed entry stays at [0,0,0,0] and cannot be re-removed), subsuming the standalone never-registered case.
…d REMOVE_GER UpdateGerNote::create and RemoveGerNote::create built structurally identical notes (8 felts of GER storage, a network-account target on the bridge, public metadata, no assets), differing only in the note script. Extract the shared body into a crate-internal create_ger_note helper that each note type calls with its own script.
Closes #2702.
Adds a GER removal mechanism mirroring Solidity's
GlobalExitRootManagerL2SovereignChain.removeGlobalExitRoots:ger_removerrole (separate from the GER manager) stored in the bridge account.remove_gerMASM proc inbridge_config.masmthat asserts the sender is the GER remover, asserts the GER is currently registered, clearsger_map[ger]to[0,0,0,0], and folds the removed GER into a running keccak256 hash chain (new_chain = keccak256(prev_chain || ger)) — same shape as the existingupdate_cgi_chain_hashpattern inbridge_in.masm.REMOVE_GERnote script +RemoveGerNoteRust helper.ger_remover_account_id,removed_ger_hash_chain_lo,removed_ger_hash_chain_hi.keccak256(0…0 || ger)), unknown-GER reverts withERR_GER_NOT_FOUND, non-remover sender reverts withERR_SENDER_NOT_GER_REMOVER.AggLayerBridge::new,create_bridge_account, andcreate_existing_bridge_accountnow take a thirdger_remover_idargument; all existing call sites (tests + bench-transaction) updated.