Files
pezkuwi-subxt/substrate/client/network
Gavin Wood fd5f9292f5 FRAME: Create TransactionExtension as a replacement for SignedExtension (#2280)
Closes #2160

First part of [Extrinsic
Horizon](https://github.com/paritytech/polkadot-sdk/issues/2415)

Introduces a new trait `TransactionExtension` to replace
`SignedExtension`. Introduce the idea of transactions which obey the
runtime's extensions and have according Extension data (né Extra data)
yet do not have hard-coded signatures.

Deprecate the terminology of "Unsigned" when used for
transactions/extrinsics owing to there now being "proper" unsigned
transactions which obey the extension framework and "old-style" unsigned
which do not. Instead we have __*General*__ for the former and
__*Bare*__ for the latter. (Ultimately, the latter will be phased out as
a type of transaction, and Bare will only be used for Inherents.)

Types of extrinsic are now therefore:
- Bare (no hardcoded signature, no Extra data; used to be known as
"Unsigned")
- Bare transactions (deprecated): Gossiped, validated with
`ValidateUnsigned` (deprecated) and the `_bare_compat` bits of
`TransactionExtension` (deprecated).
  - Inherents: Not gossiped, validated with `ProvideInherent`.
- Extended (Extra data): Gossiped, validated via `TransactionExtension`.
  - Signed transactions (with a hardcoded signature).
  - General transactions (without a hardcoded signature).

`TransactionExtension` differs from `SignedExtension` because:
- A signature on the underlying transaction may validly not be present.
- It may alter the origin during validation.
- `pre_dispatch` is renamed to `prepare` and need not contain the checks
present in `validate`.
- `validate` and `prepare` is passed an `Origin` rather than a
`AccountId`.
- `validate` may pass arbitrary information into `prepare` via a new
user-specifiable type `Val`.
- `AdditionalSigned`/`additional_signed` is renamed to
`Implicit`/`implicit`. It is encoded *for the entire transaction* and
passed in to each extension as a new argument to `validate`. This
facilitates the ability of extensions to acts as underlying crypto.

There is a new `DispatchTransaction` trait which contains only default
function impls and is impl'ed for any `TransactionExtension` impler. It
provides several utility functions which reduce some of the tedium from
using `TransactionExtension` (indeed, none of its regular functions
should now need to be called directly).

Three transaction version discriminator ("versions") are now
permissible:
- 0b000000100: Bare (used to be called "Unsigned"): contains Signature
or Extra (extension data). After bare transactions are no longer
supported, this will strictly identify an Inherents only.
- 0b100000100: Old-school "Signed" Transaction: contains Signature and
Extra (extension data).
- 0b010000100: New-school "General" Transaction: contains Extra
(extension data), but no Signature.

For the New-school General Transaction, it becomes trivial for authors
to publish extensions to the mechanism for authorizing an Origin, e.g.
through new kinds of key-signing schemes, ZK proofs, pallet state,
mutations over pre-authenticated origins or any combination of the
above.

## Code Migration

### NOW: Getting it to build

Wrap your `SignedExtension`s in `AsTransactionExtension`. This should be
accompanied by renaming your aggregate type in line with the new
terminology. E.g. Before:

```rust
/// The SignedExtension to the basic transaction logic.
pub type SignedExtra = (
	/* snip */
	MySpecialSignedExtension,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>;
```

After:

```rust
/// The extension to the basic transaction logic.
pub type TxExtension = (
	/* snip */
	AsTransactionExtension<MySpecialSignedExtension>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;
```

You'll also need to alter any transaction building logic to add a
`.into()` to make the conversion happen. E.g. Before:

```rust
fn construct_extrinsic(
		/* snip */
) -> UncheckedExtrinsic {
	let extra: SignedExtra = (
		/* snip */
		MySpecialSignedExtension::new(/* snip */),
	);
	let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
	let signature = payload.using_encoded(|e| sender.sign(e));
	UncheckedExtrinsic::new_signed(
		/* snip */
		Signature::Sr25519(signature),
		extra,
	)
}
```

After:

```rust
fn construct_extrinsic(
		/* snip */
) -> UncheckedExtrinsic {
	let tx_ext: TxExtension = (
		/* snip */
		MySpecialSignedExtension::new(/* snip */).into(),
	);
	let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap();
	let signature = payload.using_encoded(|e| sender.sign(e));
	UncheckedExtrinsic::new_signed(
		/* snip */
		Signature::Sr25519(signature),
		tx_ext,
	)
}
```

### SOON: Migrating to `TransactionExtension`

Most `SignedExtension`s can be trivially converted to become a
`TransactionExtension`. There are a few things to know.

- Instead of a single trait like `SignedExtension`, you should now
implement two traits individually: `TransactionExtensionBase` and
`TransactionExtension`.
- Weights are now a thing and must be provided via the new function `fn
weight`.

#### `TransactionExtensionBase`

This trait takes care of anything which is not dependent on types
specific to your runtime, most notably `Call`.

- `AdditionalSigned`/`additional_signed` is renamed to
`Implicit`/`implicit`.
- Weight must be returned by implementing the `weight` function. If your
extension is associated with a pallet, you'll probably want to do this
via the pallet's existing benchmarking infrastructure.

#### `TransactionExtension`

Generally:
- `pre_dispatch` is now `prepare` and you *should not reexecute the
`validate` functionality in there*!
- You don't get an account ID any more; you get an origin instead. If
you need to presume an account ID, then you can use the trait function
`AsSystemOriginSigner::as_system_origin_signer`.
- You get an additional ticket, similar to `Pre`, called `Val`. This
defines data which is passed from `validate` into `prepare`. This is
important since you should not be duplicating logic from `validate` to
`prepare`, you need a way of passing your working from the former into
the latter. This is it.
- This trait takes two type parameters: `Call` and `Context`. `Call` is
the runtime call type which used to be an associated type; you can just
move it to become a type parameter for your trait impl. `Context` is not
currently used and you can safely implement over it as an unbounded
type.
- There's no `AccountId` associated type any more. Just remove it.

Regarding `validate`:
- You get three new parameters in `validate`; all can be ignored when
migrating from `SignedExtension`.
- `validate` returns a tuple on success; the second item in the tuple is
the new ticket type `Self::Val` which gets passed in to `prepare`. If
you use any information extracted during `validate` (off-chain and
on-chain, non-mutating) in `prepare` (on-chain, mutating) then you can
pass it through with this. For the tuple's last item, just return the
`origin` argument.

Regarding `prepare`:
- This is renamed from `pre_dispatch`, but there is one change:
- FUNCTIONALITY TO VALIDATE THE TRANSACTION NEED NOT BE DUPLICATED FROM
`validate`!!
- (This is different to `SignedExtension` which was required to run the
same checks in `pre_dispatch` as in `validate`.)

Regarding `post_dispatch`:
- Since there are no unsigned transactions handled by
`TransactionExtension`, `Pre` is always defined, so the first parameter
is `Self::Pre` rather than `Option<Self::Pre>`.

If you make use of `SignedExtension::validate_unsigned` or
`SignedExtension::pre_dispatch_unsigned`, then:
- Just use the regular versions of these functions instead.
- Have your logic execute in the case that the `origin` is `None`.
- Ensure your transaction creation logic creates a General Transaction
rather than a Bare Transaction; this means having to include all
`TransactionExtension`s' data.
- `ValidateUnsigned` can still be used (for now) if you need to be able
to construct transactions which contain none of the extension data,
however these will be phased out in stage 2 of the Transactions Horizon,
so you should consider moving to an extension-centric design.

## TODO

- [x] Introduce `CheckSignature` impl of `TransactionExtension` to
ensure it's possible to have crypto be done wholly in a
`TransactionExtension`.
- [x] Deprecate `SignedExtension` and move all uses in codebase to
`TransactionExtension`.
  - [x] `ChargeTransactionPayment`
  - [x] `DummyExtension`
  - [x] `ChargeAssetTxPayment` (asset-tx-payment)
  - [x] `ChargeAssetTxPayment` (asset-conversion-tx-payment)
  - [x] `CheckWeight`
  - [x] `CheckTxVersion`
  - [x] `CheckSpecVersion`
  - [x] `CheckNonce`
  - [x] `CheckNonZeroSender`
  - [x] `CheckMortality`
  - [x] `CheckGenesis`
  - [x] `CheckOnlySudoAccount`
  - [x] `WatchDummy`
  - [x] `PrevalidateAttests`
  - [x] `GenericSignedExtension`
  - [x] `SignedExtension` (chain-polkadot-bulletin)
  - [x] `RefundSignedExtensionAdapter`
- [x] Implement `fn weight` across the board.
- [ ] Go through all pre-existing extensions which assume an account
signer and explicitly handle the possibility of another kind of origin.
- [x] `CheckNonce` should probably succeed in the case of a non-account
origin.
- [x] `CheckNonZeroSender` should succeed in the case of a non-account
origin.
- [x] `ChargeTransactionPayment` and family should fail in the case of a
non-account origin.
  - [ ] 
- [x] Fix any broken tests.

---------

Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Signed-off-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io>
Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Co-authored-by: Nikhil Gupta <17176722+gupnik@users.noreply.github.com>
Co-authored-by: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com>
Co-authored-by: Chevdor <chevdor@users.noreply.github.com>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Maciej <maciej.zyszkiewicz@parity.io>
Co-authored-by: Javier Viola <javier@parity.io>
Co-authored-by: Marcin S. <marcin@realemail.net>
Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
Co-authored-by: Javier Bullrich <javier@bullrich.dev>
Co-authored-by: Koute <koute@users.noreply.github.com>
Co-authored-by: Adrian Catangiu <adrian@parity.io>
Co-authored-by: Vladimir Istyufeev <vladimir@parity.io>
Co-authored-by: Ross Bulat <ross@parity.io>
Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com>
Co-authored-by: ordian <write@reusable.software>
Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com>
Co-authored-by: Dmitry Markin <dmitry@markin.tech>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com>
Co-authored-by: Julian Eager <eagr@tutanota.com>
Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com>
Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Dónal Murray <donal.murray@parity.io>
Co-authored-by: yjh <yjh465402634@gmail.com>
Co-authored-by: Tom Mi <tommi@niemi.lol>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Will | Paradox | ParaNodes.io <79228812+paradox-tt@users.noreply.github.com>
Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>
Co-authored-by: Joshy Orndorff <git-user-email.h0ly5@simplelogin.com>
Co-authored-by: PG Herveou <pgherveou@gmail.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Juan Girini <juangirini@gmail.com>
Co-authored-by: bader y <ibnbassem@gmail.com>
Co-authored-by: James Wilson <james@jsdw.me>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: asynchronous rob <rphmeier@gmail.com>
Co-authored-by: Parth <desaiparth08@gmail.com>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
Co-authored-by: Jonathan Udd <jonathan@dwellir.com>
Co-authored-by: Serban Iorga <serban@parity.io>
Co-authored-by: Egor_P <egor@parity.io>
Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: Evgeny Snitko <evgeny@parity.io>
Co-authored-by: Just van Stam <vstam1@users.noreply.github.com>
Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
Co-authored-by: dzmitry-lahoda <dzmitry@lahoda.pro>
Co-authored-by: zhiqiangxu <652732310@qq.com>
Co-authored-by: Nazar Mokrynskyi <nazar@mokrynskyi.com>
Co-authored-by: Anwesh <anweshknayak@gmail.com>
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: kianenigma <kian@parity.io>
Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>
Co-authored-by: Muharem <ismailov.m.h@gmail.com>
Co-authored-by: joepetrowski <joe@parity.io>
Co-authored-by: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com>
Co-authored-by: Gabriel Facco de Arruda <arrudagates@gmail.com>
Co-authored-by: Squirrel <gilescope@gmail.com>
Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>
Co-authored-by: georgepisaltu <george.pisaltu@parity.io>
Co-authored-by: command-bot <>
2024-03-04 19:12:43 +00:00
..
2023-09-04 12:02:32 +03:00

Substrate-specific P2P networking.

Important: This crate is unstable and the API and usage may change.

Node identities and addresses

In a decentralized network, each node possesses a network private key and a network public key. In Substrate, the keys are based on the ed25519 curve.

From a node's public key, we can derive its identity. In Substrate and libp2p, a node's identity is represented with the [PeerId] struct. All network communications between nodes on the network use encryption derived from both sides's keys, which means that identities cannot be faked.

A node's identity uniquely identifies a machine on the network. If you start two or more clients using the same network key, large interferences will happen.

Substrate's network protocol

Substrate's networking protocol is based upon libp2p. It is at the moment not possible and not planned to permit using something else than the libp2p network stack and the rust-libp2p library. However the libp2p framework is very flexible and the rust-libp2p library could be extended to support a wider range of protocols than what is offered by libp2p.

Discovery mechanisms

In order for our node to join a peer-to-peer network, it has to know a list of nodes that are part of said network. This includes nodes identities and their address (how to reach them). Building such a list is called the discovery mechanism. There are three mechanisms that Substrate uses:

  • Bootstrap nodes. These are hard-coded node identities and addresses passed alongside with the network configuration.
  • mDNS. We perform a UDP broadcast on the local network. Nodes that listen may respond with their identity. More info here. mDNS can be disabled in the network configuration.
  • Kademlia random walk. Once connected, we perform random Kademlia FIND_NODE requests on the configured Kademlia DHTs (one per configured chain protocol) in order for nodes to propagate to us their view of the network. More information about Kademlia can be found on Wikipedia.

Connection establishment

When node Alice knows node Bob's identity and address, it can establish a connection with Bob. All connections must always use encryption and multiplexing. While some node addresses (eg. addresses using /quic) already imply which encryption and/or multiplexing to use, for others the multistream-select protocol is used in order to negotiate an encryption layer and/or a multiplexing layer.

The connection establishment mechanism is called the transport.

As of the writing of this documentation, the following base-layer protocols are supported by Substrate:

  • TCP/IP for addresses of the form /ip4/1.2.3.4/tcp/5. Once the TCP connection is open, an encryption and a multiplexing layer are negotiated on top.
  • WebSockets for addresses of the form /ip4/1.2.3.4/tcp/5/ws. A TCP/IP connection is open and the WebSockets protocol is negotiated on top. Communications then happen inside WebSockets data frames. Encryption and multiplexing are additionally negotiated again inside this channel.
  • DNS for addresses of the form /dns/example.com/tcp/5 or /dns/example.com/tcp/5/ws. A node's address can contain a domain name.
  • (All of the above using IPv6 instead of IPv4.)

On top of the base-layer protocol, the Noise protocol is negotiated and applied. The exact handshake protocol is experimental and is subject to change.

The following multiplexing protocols are supported:

Substreams

Once a connection has been established and uses multiplexing, substreams can be opened. When a substream is open, the multistream-select protocol is used to negotiate which protocol to use on that given substream.

Protocols that are specific to a certain chain have a <protocol-id> in their name. This "protocol ID" is defined in the chain specifications. For example, the protocol ID of Polkadot is "dot". In the protocol names below, <protocol-id> must be replaced with the corresponding protocol ID.

Note

: It is possible for the same connection to be used for multiple chains. For example, one can use both the /dot/sync/2 and /sub/sync/2 protocols on the same connection, provided that the remote supports them.

Substrate uses the following standard libp2p protocols:

  • /ipfs/ping/1.0.0. We periodically open an ephemeral substream in order to ping the remote and check whether the connection is still alive. Failure for the remote to reply leads to a disconnection.
  • /ipfs/id/1.0.0. We periodically open an ephemeral substream in order to ask information from the remote.
  • /<protocol_id>/kad. We periodically open ephemeral substreams for Kademlia random walk queries. Each Kademlia query is done in a separate substream.

Additionally, Substrate uses the following non-libp2p-standard protocols:

  • /substrate/<protocol-id>/<version> (where <protocol-id> must be replaced with the protocol ID of the targeted chain, and <version> is a number between 2 and 6). For each connection we optionally keep an additional substream for all Substrate-based communications alive. This protocol is considered legacy, and is progressively being replaced with alternatives. This is designated as "The legacy Substrate substream" in this documentation. See below for more details.
  • /<protocol-id>/sync/2 is a request-response protocol (see below) that lets one perform requests for information about blocks. Each request is the encoding of a BlockRequest and each response is the encoding of a BlockResponse, as defined in the api.v1.proto file in this source tree.
  • /<protocol-id>/light/2 is a request-response protocol (see below) that lets one perform light-client-related requests for information about the state. Each request is the encoding of a light::Request and each response is the encoding of a light::Response, as defined in the light.v1.proto file in this source tree.
  • /<protocol-id>/transactions/1 is a notifications protocol (see below) where transactions are pushed to other nodes. The handshake is empty on both sides. The message format is a SCALE-encoded list of transactions, where each transaction is an opaque list of bytes.
  • /<protocol-id>/block-announces/1 is a notifications protocol (see below) where block announces are pushed to other nodes. The handshake is empty on both sides. The message format is a SCALE-encoded tuple containing a block header followed with an opaque list of bytes containing some data associated with this block announcement, e.g. a candidate message.
  • Notifications protocols that are registered using NetworkConfiguration::notifications_protocols. For example: /paritytech/grandpa/1. See below for more information.

The legacy Substrate substream

Substrate uses a component named the peerset manager (PSM). Through the discovery mechanism, the PSM is aware of the nodes that are part of the network and decides which nodes we should perform Substrate-based communications with. For these nodes, we open a connection if necessary and open a unique substream for Substrate-based communications. If the PSM decides that we should disconnect a node, then that substream is closed.

For more information about the PSM, see the sc-peerset crate.

Note that at the moment there is no mechanism in place to solve the issues that arise where the two sides of a connection open the unique substream simultaneously. In order to not run into issues, only the dialer of a connection is allowed to open the unique substream. When the substream is closed, the entire connection is closed as well. This is a bug that will be resolved by deprecating the protocol entirely.

Within the unique Substrate substream, messages encoded using [parity-scale-codec``](https://github.com/paritytech/parity-scale-codec) are exchanged. The detail of theses messages is not totally in place, but they can be found in the message.rs` file.

Once the substream is open, the first step is an exchange of a status message from both sides, containing information such as the chain root hash, head of chain, and so on.

Communications within this substream include:

  • Syncing. Blocks are announced and requested from other nodes.
  • Light-client requests. When a light client requires information, a random node we have a substream open with is chosen, and the information is requested from it.
  • Gossiping. Used for example by grandpa.

Request-response protocols

A so-called request-response protocol is defined as follow:

  • When a substream is opened, the opening side sends a message whose content is protocol-specific. The message must be prefixed with an LEB128-encoded number indicating its length. After the message has been sent, the writing side is closed.
  • The remote sends back the response prefixed with a LEB128-encoded length, and closes its side as well.

Each request is performed in a new separate substream.

Notifications protocols

A so-called notifications protocol is defined as follow:

  • When a substream is opened, the opening side sends a handshake message whose content is protocol-specific. The handshake message must be prefixed with an LEB128-encoded number indicating its length. The handshake message can be of length 0, in which case the sender has to send a single 0.
  • The receiver then either immediately closes the substream, or answers with its own LEB128-prefixed protocol-specific handshake response. The message can be of length 0, in which case a single 0 has to be sent back.
  • Once the handshake has completed, the notifications protocol is unidirectional. Only the node which initiated the substream can push notifications. If the remote wants to send notifications as well, it has to open its own undirectional substream.
  • Each notification must be prefixed with an LEB128-encoded length. The encoding of the messages is specific to each protocol.
  • Either party can signal that it doesn't want a notifications substream anymore by closing its writing side. The other party should respond by closing its own writing side soon after.

The API of sc-network allows one to register user-defined notification protocols. sc-network automatically tries to open a substream towards each node for which the legacy Substream substream is open. The handshake is then performed automatically.

For example, the sc-consensus-grandpa crate registers the /paritytech/grandpa/1 notifications protocol.

At the moment, for backwards-compatibility, notification protocols are tied to the legacy Substrate substream. Additionally, the handshake message is hardcoded to be a single 8-bits integer representing the role of the node:

  • 1 for a full node.
  • 2 for a light node.
  • 4 for an authority.

In the future, though, these restrictions will be removed.

Sync

The crate implements a number of syncing algorithms. The main purpose of the syncing algorithm is get the chain to the latest state and keep it synced with the rest of the network by downloading and importing new data as soon as it becomes available. Once the node starts it catches up with the network with one of the initial sync methods listed below, and once it is completed uses a keep-up sync to download new blocks.

Full and light sync

This is the default syncing method for the initial and keep-up sync. The algorithm starts with the current best block and downloads block data progressively from multiple peers if available. Once there's a sequence of blocks ready to be imported they are fed to the import queue. Full nodes download and execute full blocks, while light nodes only download and import headers. This continues until each peers has no more new blocks to give.

For each peer the sync maintains the number of our common best block with that peer. This number is updates whenever peer announce new blocks or our best block advances. This allows to keep track of peers that have new block data and request new information as soon as it is announced. In keep-up mode, we also track peers that announce blocks on all branches and not just the best branch. The sync algorithm tries to be greedy and download all data that's announced.

Fast sync

In this mode the initial downloads and verifies full header history. This allows to validate authority set transitions and arrive at a recent header. After header chain is verified and imported the node starts downloading a state snapshot using the state request protocol. Each StateRequest contains a starting storage key, which is empty for the first request. StateResponse contains a storage proof for a sequence of keys and values in the storage starting (but not including) from the key that is in the request. After iterating the proof trie against the storage root that is in the target header, the node issues The next StateRequest with set starting key set to the last key from the previous response. This continues until trie iteration reaches the end. The state is then imported into the database and the keep-up sync starts in normal full/light sync mode.

Warp sync

This is similar to fast sync, but instead of downloading and verifying full header chain, the algorithm only downloads finalized authority set changes.

GRANDPA warp sync

GRANDPA keeps justifications for each finalized authority set change. Each change is signed by the authorities from the previous set. By downloading and verifying these signed hand-offs starting from genesis, we arrive at a recent header faster than downloading full header chain. Each WarpSyncRequest contains a block hash to a to start collecting proofs from. WarpSyncResponse contains a sequence of block headers and justifications. The proof downloader checks the justifications and continues requesting proofs from the last header hash, until it arrives at some recent header.

Once the finality chain is proved for a header, the state matching the header is downloaded much like during the fast sync. The state is verified to match the header storage root. After the state is imported into the database it is queried for the information that allows GRANDPA and BABE to continue operating from that state. This includes BABE epoch information and GRANDPA authority set id.

Background block download

After the latest state has been imported the node is fully operational, but is still missing historic block data. I.e. it is unable to serve bock bodies and headers other than the most recent one. To make sure all nodes have block history available, a background sync process is started that downloads all the missing blocks. It is run in parallel with the keep-up sync and does not interfere with downloading of the recent blocks. During this download we also import GRANDPA justifications for blocks with authority set changes, so that the warp-synced node has all the data to serve for other nodes nodes that might want to sync from it with any method.

Usage

Using the sc-network crate is done through the [NetworkWorker] struct. Create this struct by passing a [config::Params], then poll it as if it was a Future. You can extract an Arc<NetworkService> from the NetworkWorker, which can be shared amongst multiple places in order to give orders to the networking.

See the [config] module for more information about how to configure the networking.

After the NetworkWorker has been created, the important things to do are:

  • Calling NetworkWorker::poll in order to advance the network. This can be done by dispatching a background task with the [NetworkWorker].
  • Calling on_block_import whenever a block is added to the client.
  • Calling on_block_finalized whenever a block is finalized.
  • Calling trigger_repropagate when a transaction is added to the pool.

More precise usage details are still being worked on and will likely change in the future.

License: GPL-3.0-or-later WITH Classpath-exception-2.0