Refactor/remove chain links proxy - #116
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removes
pallet-account-mappingand theprivate_linkcircuit from the runtime, and givespallet-zk-verifiera way to erase a circuit the runtime no longer implements.The pallet bound H160 addresses to
AccountId32, managed aliases and chain links, and exposed two proxy-dispatch routes (dispatch_as_linked_account,dispatch_as_private_link) that ran arbitrary calls on behalf of another account viadispatch_bypass_filter. Private transfers never needed it — the shielded pool already accepts any address shape — and the private-link route was dead code:DisabledPrivateLinkVerifieralways returnedfalse, so the circuit had never verified a single proof on chain.Net −6567 lines, 27 files deleted, one added.
What this removes
frame/account-mapping/frame/evm/precompile/account-mapping/0x0800, 21 selectorsartifacts/verification_key_private_link.jsonZkVerifierPort::verify_private_link_proofAddressMappingis untouched. The EVM↔Substrate mapping is structural ([H160 | 0x00×12]) and lives in runtime code, not in the pallet —account_mapping_runtime.rsis renamed toevm_account.rsto make that ownership obvious. Frontier keeps working exactly as before.What this adds
purge_circuit(circuit_id)— call index 7, Rootremove_verification_keyandretire_versionboth refuse to touch a circuit's active version, which is what stops a live circuit from ending up with no key to verify against. That same guard makes them unable to retire a circuit as a whole: its last version is, by construction, the active one. So circuit 5's key stayed on chain after the runtime stopped implementing it, andget_all_circuit_versionskept listing it — that runtime API iterates storage keys with no allowlist, so explorers displayed a circuit the runtime could not serve.purge_circuitcloses that gap and only that gap. A circuit is purgeable only whenexpected_public_inputsreturnsNonefor its id, so transfer (1), unshield (2) and value_proof (6) are refused for as long as they remain compiled in — regardless of what storage holds. Ids aboveu8::MAXare rejected outright rather than truncated into that lookup, so a future circuit numbered past 255 cannot alias onto a live id.The call clears
ActiveCircuitVersionrather than requiring it to be empty. Requiring it would have made the extrinsic unreachable: the firstregister_verification_keyactivates the version it registers, and no extrinsic ever clears that entry. See "How the tests caught a real bug" below.migrations::v1::MigrateToV1Drops circuit 5 during the upgrade so chains carrying the key from an earlier runtime need no governance call. The pallet had no
STORAGE_VERSIONand nomigrationsmodule before this; both are added.Clears by prefix across all five maps rather than iterating one map's versions — an earlier
remove_verification_keycould strand entries in the satellite maps with noVerificationKeysrow to enumerate them from.Fix:
remove_verification_keyno longer strands entriesIt cleared
VerificationKeysandRetiredVersionsbut leftVkHashesandVerificationStatsbehind. Those entries were unreachable by any call, and they skewed the version countstore_vkuses to enforceMAX_VERSIONS_PER_CIRCUIT. All four maps are now cleared together.How the tests caught a real bug
purge_circuitoriginally had a second guard:ActiveCircuitVersionhad to be empty. 106 unit tests passed.The first run of the on-chain harness failed:
The extrinsic could never execute.
register_verification_keyauto-activates the first version, andgrepforActiveCircuitVersion::<T>::{remove,kill,take}across the pallet returns zero matches —set_active_versiononly overwrites, and bothretire_versionandremove_verification_keyrefuse the active version. A one-way trap.The unit tests passed because a test helper called
ActiveCircuitVersion::<Test>::remove()directly — a path no origin can invoke. Green, testing something unreachable. That helper is gone, and the replacement test builds the circuit using only extrinsics.An adversarial review of the fixed code then found the
CircuitPurged { removed }field reportedmaxacross the four maps rather than the total, under-reporting whenever the maps held different version sets (a stranded active pointer emittedremoved: 0having cleared one entry). Now it counts entries; the weight is still charged per version, which is what the benchmark measures.Verification
pallet-zk-verifierunit tests--features try-runtime)cargo test --release --lib --allruntime-benchmarks,skip-proof-verification,try-runtime,-D warnings)scripts/vk/purge_e2e.cjs)try-runtime against a 541 482-key snapshot of live testnet: migrations succeed, storage root identical across two runs (idempotent), 0.10% of the block budget, PoV 2.6 KiB. With
spec_version6 it runs without--disable-spec-version-check.E2E harness (new,
scripts/vk/purge_e2e.cjs— manual, CI'scargo test --libdoes not see it): 74 checks over 13 sections against a dev chain. Notable coverage:purge(1),purge(2),purge(6)refused, storage byte-identical afterwardspurge(256/257/260/65535)refused — theu8aliasing fix holds on chain, not just in testsremovedaccurateverify_proofon a purged circuit →CircuitNotFound; a live circuit still reaches the pairingremoved == 129sudo_unchecked_weightcannot bypass the guardWeights
weights.rsregenerated forpallet-zk-verifieron reference hardware (Hetzner CCX33, AMD EPYC-Genoa, steps 50 / repeat 20).purge_circuitmeasures59.5µs + 10.75µs × v, 9 + 4v reads, 3 + 4v writes, standard error 0.25%. At the version cap it stays well under 10% of a 2000 ms block — asserted bypurge_circuit_at_the_cap_fits_in_a_block.The benchmark itself was fixed along the way: it seeded only two of the four maps the extrinsic clears, so it would have measured half the work.
Version bump
spec_version5 → 6,transaction_version1 → 2.Testnet reports
spec_version5, so asetCodecarrying 5 applies nothing — try-runtime refuses it outright.transaction_versionmoves becausepurge_circuitadds call index 7;SignedExtrais unchanged, but wallets key their call encoding off it.Logged in
RUNTIME_VERSIONS.md.Chain spec
scripts/generate-specs/testnet.shbootnodes moved from therpc-*hostnames top2p-*. libp2p dials port 30333 directly, so therpc-*names sent peer traffic down a path that only fronts the HTTP RPC. Peer IDs unchanged.Breaking changes
0x0800is gone. Contracts calling it now revert.transaction_version1 → 2. Offline-signed extrinsics prepared against the old runtime are invalidated.CircuitNotFound. No funds are affected: it was never a note-spending circuit, andDisabledPrivateLinkVerifiermeant it never verified anything on chain.