Skip to content

Commit d404241

Browse files
committed
Add async backing guide
1 parent 81ab749 commit d404241

File tree

2 files changed

+346
-0
lines changed

2 files changed

+346
-0
lines changed

develop/parachains/maintenance/.pages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ nav:
66
# - 'Debug Production Issues': debug-prod-issues.md
77
- 'Runtime Metrics and Monitoring': runtime-metrics-monitoring.md
88
- 'Unlock Parachain': unlock-parachain.md
9+
- 'Asynchronous Backing': asynchronous-backing.md
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
---
2+
title: Asynchronous Backing
3+
description: Learn how to increase the efficiency and throughput of your parachain by upgrading it to leverage asynchronous backing.
4+
---
5+
6+
# Upgrade Parachain for Asynchronous Backing Compatibility
7+
8+
## Introduction
9+
10+
This guide is relevant for Cumulus based parachain projects started in 2023 or before, whose
11+
backing process is synchronous where parablocks can only be built on the latest relay chain
12+
block. Async backing allows collators to build parablocks on older relay chain blocks and create
13+
pipelines of multiple pending parablocks. This parallel block generation increases efficiency
14+
and throughput.
15+
16+
!!!note
17+
If starting a new parachain project, please use an async backing compatible template such as
18+
the [parachain template](https://github.com/paritytech/polkadot-sdk-parachain-template).
19+
The rollout process for async backing has three phases. Phases 1 and 2 below put new
20+
infrastructure in place. Then we can simply turn on async backing in phase 3.
21+
22+
## Prerequisite
23+
24+
The relay chain needs to have async backing enabled so double-check that the relay chain
25+
configuration contains the following three parameters (especially when testing locally e.g. with
26+
zombienet):
27+
28+
```json
29+
"async_backing_params": {
30+
"max_candidate_depth": 3,
31+
"allowed_ancestry_len": 2
32+
},
33+
"scheduling_lookahead": 2
34+
```
35+
36+
!!! warning
37+
`scheduling_lookahead` must be set to 2, otherwise parachain
38+
block times will degrade to worse than with sync backing!
39+
40+
## Phase 1 - Update Parachain Runtime
41+
42+
This phase involves configuring your parachain's runtime `/runtime/src/lib.rs` to make use of
43+
async backing system.
44+
45+
1. Establish and ensure constants for `capacity` and `velocity` are both set to 1 in the
46+
runtime.
47+
2. Establish and ensure the constant relay chain slot duration measured in milliseconds equal to
48+
`6000` in the runtime.
49+
```rust
50+
// Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the
51+
// relay chain.
52+
pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1;
53+
// How many parachain blocks are processed by the relay chain per parent. Limits the number of
54+
// blocks authored per slot.
55+
pub const BLOCK_PROCESSING_VELOCITY: u32 = 1;
56+
// Relay chain slot duration, in milliseconds.
57+
pub const RELAY_chain_SLOT_DURATION_MILLIS: u32 = 6000;
58+
```
59+
60+
3. Establish constants `MILLISECS_PER_BLOCK` and `SLOT_DURATION` if not already present in the
61+
runtime.
62+
```rust
63+
// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked
64+
// up by `pallet_aura` to implement `fn slot_duration()`.
65+
//
66+
// Change this to adjust the block time.
67+
pub const MILLISECS_PER_BLOCK: u64 = 12000;
68+
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
69+
```
70+
71+
4. Configure `cumulus_pallet_parachain_system` in the runtime.
72+
73+
Define a `FixedVelocityConsensusHook` using our capacity, velocity, and relay slot duration
74+
constants. Use this to set the parachain system `ConsensusHook` property.
75+
76+
```rust
77+
type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook<
78+
Runtime,
79+
RELAY_CHAIN_SLOT_DURATION_MILLIS,
80+
BLOCK_PROCESSING_VELOCITY,
81+
UNINCLUDED_SEGMENT_CAPACITY,
82+
>;
83+
```
84+
85+
```rust
86+
impl cumulus_pallet_parachain_system::Config for Runtime {
87+
..
88+
type ConsensusHook = ConsensusHook;
89+
..
90+
}
91+
```
92+
93+
Set the parachain system property `CheckAssociatedRelayNumber` to
94+
`RelayNumberMonotonicallyIncreases`
95+
```rust
96+
impl cumulus_pallet_parachain_system::Config for Runtime {
97+
..
98+
type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases;
99+
..
100+
}
101+
```
102+
103+
5. Configure `pallet_aura` in the runtime.
104+
105+
Set `AllowMultipleBlocksPerSlot` to `false` (don't worry, we will set it to `true` when we
106+
activate async backing in phase 3).
107+
108+
Define `pallet_aura::SlotDuration` using our constant `SLOT_DURATION`
109+
110+
```rust
111+
impl pallet_aura::Config for Runtime {
112+
..
113+
type AllowMultipleBlocksPerSlot = ConstBool<false>;
114+
#[cfg(feature = "experimental")]
115+
type SlotDuration = ConstU64<SLOT_DURATION>;
116+
..
117+
}
118+
```
119+
120+
6. Update `sp_consensus_aura::AuraApi::slot_duration` in `sp_api::impl_runtime_apis` to match
121+
the constant `SLOT_DURATION`
122+
123+
```rust
124+
fn impl_slot_duration() -> sp_consensus_aura::SlotDuration {
125+
sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION)
126+
}
127+
```
128+
129+
7. Implement the `AuraUnincludedSegmentApi`, which allows the collator client to query its
130+
runtime to determine whether it should author a block.
131+
132+
- Add the dependency `cumulus-primitives-aura` to the `runtime/Cargo.toml` file for your
133+
runtime
134+
135+
```rust
136+
..
137+
cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false }
138+
..
139+
```
140+
141+
In the same file, add `"cumulus-primitives-aura/std",` to the `std` feature.
142+
143+
Inside the `impl_runtime_apis!` block for your runtime, implement the
144+
`cumulus_primitives_aura::AuraUnincludedSegmentApi` as shown below.
145+
146+
```rust
147+
fn impl_can_build_upon(
148+
included_hash: <Block as BlockT>::Hash,
149+
slot: cumulus_primitives_aura::Slot,
150+
) -> bool {
151+
ConsensusHook::can_build_upon(included_hash, slot)
152+
}
153+
```
154+
155+
!!!note
156+
With a capacity of 1 we have an effective velocity of ½ even when velocity is
157+
configured to some larger value. This is because capacity will be filled after a single block is
158+
produced and will only be freed up after that block is included on the relay chain, which takes
159+
2 relay blocks to accomplish. Thus with capacity 1 and velocity 1 we get the customary 12 second
160+
parachain block time.
161+
162+
8. If your `runtime/src/lib.rs` provides a `CheckInherents` type to `register_validate_block`,
163+
remove it. `FixedVelocityConsensusHook` makes it unnecessary. The following example shows how
164+
`register_validate_block` should look after removing `CheckInherents`.
165+
166+
```rust
167+
cumulus_pallet_parachain_system::register_validate_block! {
168+
Runtime = Runtime,
169+
BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::<Runtime, Executive>,
170+
}
171+
```
172+
173+
## Phase 2 - Update Parachain Nodes
174+
175+
This phase consists of plugging in the new lookahead collator node.
176+
177+
1. Import `cumulus_primitives_core::ValidationCode` to `node/src/service.rs`.
178+
179+
```rust
180+
use cumulus_primitives_core::{
181+
relay_chain::{CollatorPair, ValidationCode},
182+
GetParachainInfo, ParaId,
183+
};
184+
```
185+
186+
2. In `node/src/service.rs`, modify `sc_service::spawn_tasks` to use a clone of `Backend` rather
187+
than the original
188+
189+
```rust
190+
sc_service::spawn_tasks(sc_service::SpawnTasksParams {
191+
..
192+
backend: backend.clone(),
193+
..
194+
})?;
195+
```
196+
197+
3. Add `backend` as a parameter to `start_consensus()` in `node/src/service.rs`
198+
199+
```rust
200+
fn start_consensus(
201+
..
202+
backend: Arc<ParachainBackend>,
203+
..
204+
```
205+
```rust
206+
if validator {
207+
start_consensus(
208+
..
209+
backend.clone(),
210+
..
211+
)?;
212+
}
213+
```
214+
215+
4. In `node/src/service.rs` import the lookahead collator rather than the basic collator
216+
217+
```rust
218+
use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params as AuraParams};
219+
```
220+
221+
5. In `start_consensus()` replace the `BasicAuraParams` struct with `AuraParams`
222+
- Change the struct type from `BasicAuraParams` to `AuraParams`
223+
- In the `para_client` field, pass in a cloned para client rather than the original
224+
- Add a `para_backend` parameter after `para_client`, passing in our para backend
225+
- Provide a `code_hash_provider` closure like that shown below
226+
- Increase `authoring_duration` from 500 milliseconds to 2000
227+
```rust
228+
let params = AuraParams {
229+
..
230+
para_client: client.clone(),
231+
para_backend: backend.clone(),
232+
..
233+
code_hash_provider: move |block_hash| {
234+
client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash())
235+
},
236+
..
237+
authoring_duration: Duration::from_millis(2000),
238+
..
239+
};
240+
```
241+
242+
!!!note
243+
Set `authoring_duration` to whatever you want, taking your own hardware into account.
244+
But if the backer who should be slower than you due to reading from disk, times out at two
245+
seconds your candidates will be rejected.
246+
247+
6. In `start_consensus()` replace `basic_aura::run` with `aura::run`
248+
```rust
249+
let fut =
250+
aura::run::<Block, sp_consensus_aura::sr25519::AuthorityPair, _, _, _, _, _, _, _, _, _>(
251+
params,
252+
);
253+
task_manager.spawn_essential_handle().spawn("aura", None, fut);
254+
```
255+
256+
## Phase 3 - Activate Async Backing
257+
258+
This phase consists of changes to your parachain's runtime that activate async backing feature.
259+
260+
1. Configure `pallet_aura`, setting `AllowMultipleBlocksPerSlot` to true in
261+
`runtime/src/lib.rs`.
262+
263+
```rust
264+
impl pallet_aura::Config for Runtime {
265+
type AuthorityId = AuraId;
266+
type DisabledValidators = ();
267+
type MaxAuthorities = ConstU32<100_000>;
268+
type AllowMultipleBlocksPerSlot = ConstBool<true>;
269+
type SlotDuration = ConstU64<SLOT_DURATION>;
270+
}
271+
```
272+
273+
2. Increase the maximum `UNINCLUDED_SEGMENT_CAPACITY` in `runtime/src/lib.rs`.
274+
275+
```rust
276+
mod async_backing_params {
277+
/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included
278+
/// into the relay chain.
279+
pub(crate) const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3;
280+
/// How many parachain blocks are processed by the relay chain per parent. Limits the
281+
/// number of blocks authored per slot.
282+
pub(crate) const BLOCK_PROCESSING_VELOCITY: u32 = 1;
283+
/// Relay chain slot duration, in milliseconds.
284+
pub(crate) const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
285+
}
286+
```
287+
288+
3. Decrease `MILLISECS_PER_BLOCK` to 6000.
289+
290+
!!!note
291+
For a parachain which measures time in terms of its own block number rather than by relay block
292+
number it may be preferable to increase velocity. Changing block time may cause complications,
293+
requiring additional changes. See the section "Timing by Block Number".
294+
295+
```rust
296+
mod block_times {
297+
/// This determines the average expected block time that we are targeting. Blocks will be
298+
/// produced at a minimum duration defined by `SLOT_DURATION`. `SLOT_DURATION` is picked up by
299+
/// `pallet_timestamp` which is in turn picked up by `pallet_aura` to implement `fn
300+
/// slot_duration()`.
301+
///
302+
/// Change this to adjust the block time.
303+
pub const MILLI_SECS_PER_BLOCK: u64 = 6000;
304+
305+
// NOTE: Currently it is not possible to change the slot duration after the chain has started.
306+
// Attempting to do so will brick block production.
307+
pub const SLOT_DURATION: u64 = MILLI_SECS_PER_BLOCK;
308+
}
309+
```
310+
311+
4. Update `MAXIMUM_BLOCK_WEIGHT` to reflect the increased time available for block production.
312+
313+
```rust
314+
const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(
315+
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
316+
cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64,
317+
);
318+
```
319+
320+
5. Add a feature flagged alternative for `MinimumPeriod` in `pallet_timestamp`. The type should
321+
be `ConstU64<0>` with the feature flag experimental, and `ConstU64<{SLOT_DURATION / 2}>`
322+
without.
323+
324+
```rust
325+
impl pallet_timestamp::Config for Runtime {
326+
..
327+
#[cfg(feature = "experimental")]
328+
type MinimumPeriod = ConstU64<0>;
329+
#[cfg(not(feature = "experimental"))]
330+
type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>;
331+
..
332+
}
333+
```
334+
335+
## Timing by Block Number
336+
337+
With asynchronous backing it will be possible for parachains to opt for a block time of 6
338+
seconds rather than 12 seconds. But modifying block duration isn't so simple for a parachain
339+
which was measuring time in terms of its own block number. It could result in expected and
340+
actual time not matching up, stalling the parachain.
341+
342+
One strategy to deal with this issue is to instead rely on relay chain block numbers for timing.
343+
Relay block number is kept track of by each parachain in `pallet-parachain-system` with the
344+
storage value `LastRelaychainBlockNumber`. This value can be obtained and used wherever timing
345+
based on block number is needed.

0 commit comments

Comments
 (0)