Skip to content

Latest commit

 

History

History
177 lines (128 loc) · 9.54 KB

high-level-overview.md

File metadata and controls

177 lines (128 loc) · 9.54 KB

High-Level Bridge Documentation

Purpose

Trustless connecting between two Substrate-based chains using GRANDPA finality.

Overview

Even though we support two-way bridging, the documentation will generally talk about a one-sided interaction. That's to say, we will only talk about syncing headers and messages from a source chain to a target chain. This is because the two-sided interaction is really just the one-sided interaction with the source and target chains switched.

To understand the full interaction with the bridge, take a look at the testing scenarios document. It describes potential use cases and describes how each of the layers outlined below is involved.

The bridge is built from various components. Here is a quick overview of the important ones.

Header Sync

A light client of the source chain built into the target chain's runtime. It is a single FRAME pallet. It provides a "source of truth" about the source chain headers which have been finalized. This is useful for higher level applications.

Headers Relayer

A standalone application connected to both chains. It submits every source chain header it sees to the target chain through RPC.

Message Delivery

A FRAME pallet built on top of the header sync pallet. It allows users to submit messages to the source chain, which are to be delivered to the target chain. The delivery protocol doesn't care about the payload more than it has to. Handles replay protection and message ordering.

Message Dispatch

A FRAME pallet responsible for interpreting the payload of delivered messages.

Message Relayer

A standalone application handling delivery of the messages from source chain to the target chain.

Processes

High level sequence charts of the process can be found in a separate document.

Substrate (GRANDPA) Header Sync

The header sync pallet (pallet-substrate-bridge) is an on-chain light client for chains which use GRANDPA finality. It is part of the target chain's runtime, and accepts headers from the source chain. Its main goals are to accept valid headers, track GRANDPA finality set changes, and verify GRANDPA finality proofs (a.k.a justifications).

The pallet does not care about what block production mechanism is used for the source chain (e.g Aura or BABE) as long as it uses the GRANDPA finality gadget. Due to this it is possible for the pallet to import (but not necessarily finalize) headers which are not valid according to the source chain's block production mechanism.

The pallet has support for tracking forks and uses the longest chain rule to determine what the canonical chain is. The pallet allows headers to be imported on a different fork from the canonical one as long as the headers being imported don't conflict with already finalized headers (for example, it will not allow importing a header at a lower height than the best finalized header).

When tracking authority set changes, the pallet - unlike the full GRANDPA protocol - does not support tracking multiple authority set changes across forks. Each fork can have at most one pending authority set change. This is done to prevent DoS attacks if GRANDPA on the source chain were to stall for a long time (the pallet would have to do a lot of expensive ancestry checks to catch up).

Referer to the pallet documentation for more details.

Header Relayer strategy

There is currently no reward strategy for the relayers at all. They also are not required to be staked or registered on-chain, unlike in other bridge designs. We consider the header sync to be an essential part of the bridge and the incentivisation should be happening on the higher layers.

At the moment, signed transactions are the only way to submit headers to the header sync pallet. However, in the future we would like to use unsigned transactions for headers delivery. This will allow transaction de-duplication to be done at the transaction pool level and also remove the cost for message relayers to run header relayers.

Message Passing

Once header sync is maintained, the target side of the bridge can receive and verify proofs about events happening on the source chain, or its internal state. On top of this, we built a message passing protocol which consists of two parts described in following sections: message delivery and message dispatch.

Message Lanes Delivery

The Message delivery pallet is responsible for queueing up messages and delivering them in order on the target chain. It also dispatches messages, but we will cover that in the next section.

The pallet supports multiple lanes (channels) where messages can be added. Every lane can be considered completely independent from others, which allows them to make progress in parallel. Different lanes can be configured to validated messages differently (e.g higher rewards, specific types of payload, etc.) and may be associated with a particular "user application" built on top of the bridge. Note that messages in the same lane MUST be delivered in the same order they were queued up.

The message delivery protocol does not care about the payload it transports and can be coupled with an arbitrary message dispatch mechanism that will interpret and execute the payload if delivery conditions are met. Each delivery on the target chain is confirmed back to the source chain by the relayer. This is so that she can collect the reward for delivering these messages.

Users of the pallet add their messages to an "outbound lane" on the source chain. When a block is finalized message relayers are responsible for reading the current queue of messages and submitting some (or all) of them to the "inbound lane" of the target chain. Each message has a nonce associated with it, which serves as the ordering of messages. The inbound lane stores the last delivered nonce to prevent replaying messages. To succesfuly deliver the message to the inbound lane on target chain the relayer has to present present a storage proof which shows that the message was part of the outbound lane on the source chain.

During delivery of messages they are immediately dispatched on the target chain and the relayer is required to declare the correct weight to cater for all messages dispatch and pay all required fees of the target chain. To make sure the relayer is incentivised to do so, on the source chain:

  • the user provides a declared dispatch weight of the payload
  • the pallet calculates the expected fee on the target chain based on the declared weight
  • the pallet converts the target fee into source tokens (based on a price oracle) and reserves enough tokens to cover for the delivery, dispatch, confirmation and additional relayers reward.

If the declared weight turns out to be too low on the target chain the message is delivered but it immediately fails to dispatch. The fee and reward is collected by the relayer upon confirmation of delivery.

Due to the fact that message lanes require delivery confirmation transactions, they also strictly require bi-directional header sync (i.e. you can't use message delivery with one-way header sync).

Dispatching Messages

The Message dispatch pallet is used to perform the actions specified by messages which have come over the bridge. For Substrate-based chains this means interpreting the source chain's message as a Call on the target chain.

An example Call of the target chain would look something like this:

target_runtime::Call::Balances(target_runtime::pallet_balances::Call::transfer(recipient, amount))

When sending a Call it must first be SCALE encoded and then sent to the source chain. The Call is then delivered by the message lane delivery mechanism from the source chain to the target chain. When a message is received the inbound message lane on the target chain will try and decode the message payload into a Call enum. If it's successful it will be dispatched after we check that the weight of the call does not exceed the weight declared by the sender. The relayer pays fees for executing the transaction on the target chain, but her costs should be covered by the sender on the source chain.

When dispatching messages there are three Origins which can be used by the target chain:

  1. Root Origin
  2. Source Origin
  3. Target Origin

Senders of a message can indicate which one of the three origins they would like to dispatch their message with. However, there are restrictions on who/what is allowed to dispatch messages with a particular origin.

The Root origin represents the source chain's Root account on the target chain. This origin can can only be dispatched on the target chain if the "send message" request was made by the Root origin of the source chain - otherwise the message will fail to be dispatched.

The Source origin represents an account without a private key on the target chain. This account will be generated/derived using the account ID of the sender on the source chain. We don't necessarily require the source account id to be associated with a private key on the source chain either. This is useful for representing things such as source chain proxies or pallets.

The Target origin represents an account with a private key on the target chain. The sender on the source chain needs to prove ownership of this account by using their target chain private key to sign: (Call, SourceChainAccountId).encode(). This will be included in the message payload and verified by the target chain before dispatch.

See CallOrigin documentation for more details.

Message Relayers Strategy