In the scenarios, for simplicity, we call the chains Kusama (KSM token) and Polkadot (DOT token), but they should be applicable to any other chains. The first scenario has detailed description about the entire process (also see the sequence diagram). Other scenarios only contain a simplified interaction focusing on things that are unique for that particular scenario.
Notation:
- kX - user X interacting with Kusama chain.
k(kX)
- Kusama account id of user kX (native account id; usable on Kusama)p(kX)
- Polkadot account id of user kX (account id derived fromk(kX)
usable on Polkadot)- [Kusama] ... - Interaction happens on Kusama (e.g. the user interacts with Kusama chain)
- [Polkadot] ... - Interaction happens on Polkadot
Kusama's Alice (kAlice) receives 5 DOTs from Polkadot's Bob (pBob) and sends half of them to kCharlie.
-
Generate kAlice's DOT address (
p(kAlice)
). See function:bp_runtime::derive_account_id(b"pdot", kAlice)
or:
let hash = bp_polkadot::derive_kusama_account_id(kAlice); let p_kAlice = bp_polkadot::AccountIdConverter::convert(hash);
-
[Polkadot] pBob transfers 5 DOTs to
p(kAlice)
- Creates & Signs a transaction with
Call::Transfer(..)
- It is included in block.
- kAlice observers Polkadot chain to see her balance at
p(kAlice)
updated.
- Creates & Signs a transaction with
-
[Kusama] kAlice sends 2.5 DOTs to
p(kCharlie)
-
kAlice prepares:
let call = polkadot::Call::Balances(polkadot::Balances::Transfer(p(kCharlie), 2.5DOT)).encode(); let weight = call.get_dispatch_info().weight;
-
kAlice prepares Kusama transaction:
kusama::Call::Messages::<Instance=Polkadot>::send_message( // dot-transfer-lane (truncated to 4bytes) lane_id, payload: MessagePayload { // Get from current polkadot runtime (kind of hardcoded) spec_version: 1, // kAlice should know the exact dispatch weight of the call on the target // source verifies: at least to cover call.length() and below max weight weight, // simply bytes, we don't know anything about that on the source chain call, // origin that should be used during dispatch on the target chain origin: CallOrigin::SourceAccount(kAlice), }, delivery_and_dispatch_fee: { (single_message_delivery_weight // source weight = X * target weight + convert_target_weight_to_source_weight(weight) + confirmation_transaction_weight ) // This uses an on-chain oracle to convert weights of the target chain to source fee * weight_to_fee // additional reward for the relayer (pallet parameter) + relayers_fee }, )
-
[Kusama] kAlice sends Kusama transaction with the above
Call
and pays regular fees. The dispatch additionally reservers target-chain delivery and dispatch fees (including relayer's reward).
-
-
[Kusama] kAlice's transaction is included in block
B1
-
Relayer sees that
B1
has not yet been delivered to the target chain. Sync loop code. -
Relayer prepares transaction which delivers
B1
and with all of the missing ancestors to the target chain (one header per transaction). -
After the transaction is succesfully dispatched the Polkadot on-chain light client of the Kusama chain learns about block
B1
- it is stored in the on-chain storage.
-
Relayer is subscribed to finality events on Kusama. Relayer gets a finality notification for block
B3
. -
The header sync informs the target chain about
B1..B3
blocks (see point 6). -
Relayer learns about missing finalization of
B1..B3
on the target chain, see finality maintenance code. -
Relayer submits justification for
B3
to the target chain (finalize_header
). See #421 for multiple authority set changes support in Relayer (i.e. what block the target chain expects, not only what I have).Relayer is doing two things:
- syncing on demand (what blocks miss finality)
- and syncing as notifications are received (recently finalized on-chain)
-
Eventually Polkadot on-chain light client of Kusama learns about finality of
B1
.
-
The relayer checks the on-chain storage (last finalized header on the source, best header on the target):
- Kusama outbound lane
- Polkadot inbound lane
Lanes contains
latest_generated_nonce
andlatest_received_nonce
respectively. The relayer syncs messages between that range.
-
The relayer gets a proof for every message in that range (using the RPC of messages module)
-
The relayer creates a message delivery transaction (but it has weight, size, and count limits). The count limit is there to make the loop of delivery code bounded.
receive_message_proof( relayer_id, // account id of the source chain proof, // messages + proofs (hash of source block `B1`, nonces, lane_id + storage proof) dispatch_weight // relayer declares how much it will take to dispatch all messages in that transaction, )
The
proof
can also contain an update of outbound lane state of source chain, which indicates the delivery confirmation of these messages and reward payment, so that the target chain can truncate its unpayed rewards vector.The target chain stores
relayer_ids
that delivered messages because the relayer can generate a storage proof to show that they did indeed deliver those messages. The reward is paid on the source chain and we inform the target chain about that fact so it can prune theserelayer_ids
.It's totally fine if there are no messages, and we only include the reward payment proof when calling that function.
-
🥳 the message is now delivered and dispatched on the target chain!
-
The relayer now needs to confirm the delivery to claim her payment and reward on the source chain.
-
The relayer creates a transaction on the source chain with call:
receive_messages_delivery_proof(
proof, // hash of the finalized target chain block, lane_id, storage proof
)
- The UI should warn before (or prevent) sending to
k(kCharlie)
!
kAlice receives 10 DOTs from pBob and nominates p(pCharlie)
and p(pDave)
.
- Generate kAlice's DOT address (
p(kAlice)
) - [Polkadot] pBob transfers 5 DOTs to
p(kAlice)
- [Kusama] kAlice sends a batch transaction:
staking::Bond
transaction to create stash account choosingp(kAlice)
as the controller account.staking::Nominate(vec![p(pCharlie)])
to nominate pCharlie using the controller account.
pBob sends 15 DOTs to Kusama Treasury which Kusama Governance decides to transfer to kCharlie.
- Generate source account for the treasury (
kTreasury
). - [Polkadot] pBob tarnsfers 15 DOTs to
p(kTreasury)
. - [Kusama] Send a governance proposal to send a bridge message which transfers funds to
p(kCharlie)
. - [Kusama] Dispatch the governance proposal using
kTreasury
account id.
Assuming p(pAlice)
has at least 7 DOTs already.
- Generate multisig account id:
pMultiSig = multi_account_id(&[p(kAlice), p(pAlice)], 1)
. - [Kusama] Transfer 7 DOTs to
pMultiSig
usingTargetAccount
origin ofpAlice
. - [Kusama] Transfer 2 DOTs to
p(kAlice)
from the multisig:- Send
multisig::as_multi_threshold_1(vec![p(pAlice)], balances::Transfer(p(kAlice), 2))
- Send
We can assume he holds KSM. Problem: he can pay fees, but can't really send (sign) a transaction? Shall we support some kind of dispatcher?
We use SourceRoot
call to transfer her's DOTs to Kusama treasury. Source chain root
should also be able to send messages as CallOrigin::SourceAccount(Alice)
though.