#header #pallet #bridge #set #grandpa #chain #block

no-std pallet-bridge-grandpa

Module implementing GRANDPA on-chain light client used for bridging consensus of substrate-based chains. (polkadot v1.10.0)

14 releases (breaking)

0.11.0 Apr 9, 2024
0.10.0 Mar 18, 2024
0.9.0 Feb 27, 2024
0.6.0 Dec 13, 2023
0.0.0 Jun 20, 2023

#823 in Magic Beans

Download history 21/week @ 2023-12-22 410/week @ 2023-12-29 483/week @ 2024-01-05 239/week @ 2024-01-12 197/week @ 2024-01-19 217/week @ 2024-01-26 79/week @ 2024-02-02 280/week @ 2024-02-09 695/week @ 2024-02-16 935/week @ 2024-02-23 495/week @ 2024-03-01 126/week @ 2024-03-08 510/week @ 2024-03-15 468/week @ 2024-03-22 255/week @ 2024-03-29 451/week @ 2024-04-05

1,705 downloads per month
Used in 8 crates (5 directly)

GPL-3.0-or-later…

2.5MB
42K SLoC

Bridge GRANDPA Pallet

The bridge GRANDPA pallet is a light client for the GRANDPA finality gadget, running at the bridged chain. It may import headers and their GRANDPA finality proofs (justifications) of the bridged chain. Imported headers then may be used to verify storage proofs by other pallets. This makes the bridge GRANDPA pallet a basic pallet of all bridges with Substrate-based chains. It is used by all bridge types (bridge between standalone chains, between parachains and any combination of those) and is used by other bridge pallets. It is used by the parachains light client (bridge parachains pallet) and by messages pallet.

A Brief Introduction into GRANDPA Finality

You can find detailed information on GRANDPA, by exploring its repository. Here is the minimal required GRANDPA information to understand how pallet works.

Any Substrate chain may use different block authorship algorithms (like BABE or Aura) to determine block producers and generate blocks. This has nothing common with finality, though - the task of block authorship is to coordinate blocks generation. Any block may be reverted (if there's a fork) if it is not finalized. The finality solution for (standalone) Substrate-based chains is the GRANDPA finality gadget. If some block is finalized by the gadget, it can't be reverted.

In GRANDPA, there are validators, identified by their public keys. They select some generated block and produce signatures on this block hash. If there are enough (more than 2 / 3 * N, where N is number of validators) signatures, then the block is considered finalized. The set of signatures for the block is called justification. Anyone who knows the public keys of validators is able to verify GRANDPA justification and that it is generated for provided header.

There are two main things in GRANDPA that help building light clients:

  • there's no need to import all headers of the bridged chain. Light client may import finalized headers or just some of finalized headers that it consider useful. While the validators set stays the same, the client may import any header that is finalized by this set;

  • when validators set changes, the GRANDPA gadget adds next set to the header. So light client doesn't need to verify storage proofs when this happens - it only needs to look at the header and see if it changes the set. Once set is changed, all following justifications are generated by the new set. Header that is changing the set is called "mandatory" in the pallet. As the name says, the light client need to import all such headers to be able to operate properly.

Pallet Operations

The main entrypoint of the pallet is the submit_finality_proof_ex call. It has three arguments - the finalized headers, associated GRANDPA justification and ID of the authority set that has generated this justification. The call simply verifies the justification using current validators set and checks if header is better than the previous best header. If both checks are passed, the header (only its useful fields) is inserted into the runtime storage and may be used by other pallets to verify storage proofs.

The submitter pays regular fee for submitting all headers, except for the mandatory header. Since it is required for the pallet operations, submitting such header is free. So if you're ok with session-length lags (meaning that there's exactly 1 mandatory header per session), the cost of pallet calls is zero.

When the pallet sees mandatory header, it updates the validators set with the set from the header. All following justifications (until next mandatory header) must be generated by this new set.

Pallet Initialization

As the previous section states, there are two things that are mandatory for pallet operations: best finalized header and the current validators set. Without it the pallet can't import any headers. But how to provide initial values for these fields? There are two options.

First option, while it is easier, doesn't work in all cases. It is to start chain with initial header and validators set specified in the chain specification. This won't work, however, if we want to add bridge to already started chain.

For the latter case we have the initialize call. It accepts the initial header and initial validators set. The call may be called by the governance, root or by the pallet owner (if it is set).

Non-Essential Functionality

There may be a special account in every runtime where the bridge GRANDPA module is deployed. This account, named 'module owner', is like a module-level sudo account - he's able to halt and resume all module operations without requiring runtime upgrade. Calls that are related to this account are:

  • fn set_owner(): current module owner may call it to transfer "ownership" to another account;

  • fn set_operating_mode(): the module owner (or sudo account) may call this function to stop all module operations. After this call, all finality proofs will be rejected until further set_operating_mode call'. This call may be used when something extraordinary happens with the bridge;

  • fn initialize(): module owner may call this function to initialize the bridge.

If pallet owner is not defined, the governance may be used to make those calls.

Signed Extension to Reject Obsolete Headers

It'd be better for anyone (for chain and for submitters) to reject all transactions that are submitting already known headers to the pallet. This way, we leave block space to other useful transactions and we don't charge concurrent submitters for their honest actions.

To deal with that, we have a signed extension that may be added to the runtime. It does exactly what is required - rejects all transactions with already known headers. The submitter pays nothing for such transactions - they're simply removed from the transaction pool, when the block is built.

You may also take a look at the generate_bridge_reject_obsolete_headers_and_messages macro that bundles several similar signed extensions in a single one.

GRANDPA Finality Relay

We have an offchain actor, who is watching for GRANDPA justifications and submits them to the bridged chain. It is the finality relay - you may look at the crate level documentation and the code.

Dependencies

~20–34MB
~558K SLoC