[WIP] Polkadot-Kusama Bridge support#1029
Draft
karolk91 wants to merge 7 commits into
Draft
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.
A chopsticks-native bridge relayer that delivers
pallet_bridge_messagesoutbound messages between two forked bridge hubs, plus the underlyingstate_getReadProofJSON-RPC handler it needs.Two user-facing surfaces:
connectBridgeHubs(source, destination, { signer })mirrorsconnectUpward/connectHorizontal— subscribe once, react to every new source block forever, terminate viahandle.disconnect().chopsticks bridge -r polkadot -p polkadot-bridge-hub -R kusama -P kusama-bridge-hubspins up both ecosystems and wires the relay in both directions.Architecture
Two ecosystems (Polkadot + Kusama bridge hubs and their parachains) run as independent chopsticks instances wired together by an in-tool relayer. The relayer is the bridge component — it plays the role substrate-relay plays in production.
Key design choices:
Skip
pallet_bridge_grandpa+pallet_bridge_parachainsfinality flow. Production substrate-relay submitssubmit_finality_proof_ex(relay finality) →submit_parachain_heads_ex(BHP head proof against relay state) →receive_messages_proof. We writeImportedParaHeadsdirectly viadev_setStorage, so onlyreceive_messages_proofactually runs through pallet logic. Tested the same end-to-end, simpler.state_getReadProofcomposes upstream + overlays. Chopsticks keeps no full trie, so the handler fetches a base proof from upstream and re-applies chopsticks-side values via the executor'screateProof. The proof's recomputed root reflects local overrides; verifiers should derive the expected state_root from the proof bytes, not fromchain_getHeader(at).state_root. For chopsticks-only blocks unknown to upstream the request falls back to upstream head.Write the proof's own root to
ImportedParaHeads, notsource.header.state_root. They diverge when (a) chopsticks-only blocks force the upstream-head fallback, or (b)dev_setStoragehas touched keys covered by the proof. The connector derives the root viafindTrieRoot(the unique node hash not referenced as a 32-byte sub-sequence inside any other node).Pallet names auto-detected by storage shape. Mirrors how
connectUpwardhardcodesparachainSystem.upwardMessages(cumulus pallet has a fixed name) — bridge pallets are instantiable, so each runtime names them differently (BridgeKusamaMessageson BHP,BridgePolkadotMessageson BHK). Detection finds any pallet exposingoutboundLanes+outboundMessages; errors with clear remediation if zero or multiple matches. Same forpallet_bridge_parachains.Real polkadot.js signing with the no-callback
signAndSendform (resolves on pool submission). Callback form deadlocks against chopsticks's manualdev_newBlock.mockSignatureHost: true(chopsticks default) accepts real sigs normally; the signer must hold a balance on dest viadev_setStorage. Matches what production substrate-relay does — bring your own funded key.First-sight baseline. Subscription records each lane's current
latest_generated_nonceas the baseline on first observation, so fresh forks don't replay historical bridge traffic.Alternatives explored
Full GRANDPA justification forging — the path #695 explored. Build a synthetic GRANDPA authority set, sign justifications, submit
submit_finality_proof_ex→ relay tracker advances → submitsubmit_parachain_heads_exwith proofs against relay state →receive_messages_proof. Works but requires also advancing the source's relay chain viasetHeadso itsparas::Headsstorage matches what we proved. Three pallets' worth of plumbing for the same end result. We skip all of it by writingImportedParaHeadsdirectly.state_getReadProofwith no base nodes — Our composition(createProof(upstream_base, overlays)) produces a coherent proof whose root we derive from the bytes viafindTrieRoot.Library-only API (no CLI subcommand) — would have been less work. Rejected because the existing
xcmsubcommand sets the precedent: bridges deserve the same one-liner ergonomics. CLI auto-detects bridge hubs by shape so the user only types config paths.In-process connector via
Blockchaininstances — twoBlockchainreferences and use chopsticks-internal APIs directly rejected becausesetupNetworkskeys topology off a single relay chain (packages/utils/src/index.ts:194), so a two-relay setup can't live in one process today. Out-of-process model viaApiPromiseover WS works regardless of process layout and was the right v1.References
state_getReadProof; GRANDPA + BEEFY mocks from feat: add grandpa justifications mock rpc #695 remain useful independentlyTesting
packages/e2e/src/state.test.ts— 3 new tests forstate_getReadProof(proof reflects overrides, rejects child keys, rejects empty list)packages/e2e/src/bridge.test.ts— end-to-end against live BHP + BHK forks: injects two outbound messages across separate source blocks, assertsBridgePolkadotMessages.MessagesReceivedfires on BHK each time (proves continuous-subscription model)InitiateTransfer→ BHPMessageAccepted→ bridge handle delivers → BHKMessagesReceived+RewardRegistered+ XCMP-out → AHKMessageQueue.Processed{success:true}+Whitelist.CallWhitelisted