Upgrade Parachain for Asynchronous Backing Compatibility
This guide is relevant for cumulus based parachain projects started in 2023 or before. Later
projects should already be async backing compatible. If starting a new parachain project, please use
an async backing compatible template such as
cumulus/parachain-template
.
The rollout process for Async Backing has three phases. Phases 1 and 2 below put new infrastructure in place. Then we can simply turn on async backing in phase 3. But first, some pre-reqs and context to set the stage.
Async Backing Prerequisites
For more contextual information about asynchronous backing, see this page.
Pull the latest version of Cumulus for use with your parachain. It contains necessary changes for async backing compatibility. Latest on master branch of Polkadot-SDK is currently sufficient. Any 2024 release will work as well.
Async Backing Terminology and Parameters
Time for a bit of context before we get started. The following concepts will aid in demystifying the collator side of Async Backing and establish a basic understanding of the changes being made:
- Unincluded segment - From the perspective of a parachain block under construction, the unincluded segment describes a chain of recent block ancestors which have yet to be included on the relay chain. The ability to build new blocks on top of the unincluded segment rather than on top of blocks freshly included in the relay chain is the core of asynchronous backing.
- Capacity - The maximum size of the unincluded segment. The longer this is, the farther ahead a parachain can work, producing new candidates before the ancestors of those candidates have been seen as included on-chain. Practically, a capacity of 2-3 is sufficient to realize the full benefits of asynchronous backing, at least until the release of elastic scaling.
- Velocity - The base rate at which a parachain should produce blocks. A velocity of 1 indicates
that 1 parachain block should be produced per relay chain block. In order to fill the unincluded
segment with candidates, collators may build up to
Velocity + 1
candidates per aura slot while there is remaining capacity. When elastic scaling has been released velocities greater than 1 will be supported. - AllowMultipleBlocksPerSlot - If this is
true
, Aura will allow slots to stay the same across sequential parablocks. Otherwise the slot number must increase with each block. To fill the unincluded segment as described above we need this to betrue
. - FixedVelocityConsensusHook - This is a variety of
ConsensusHook
intended to be passed toparachain-system
as part of itsConfig
. It is triggered on initialization of a new runtime. An instance ofFixedVelocityConsensusHook
is defined with both a fixed capacity and velocity. It aborts the runtime early if either capacity or velocity is exceeded, as the collator shouldn’t be creating additional blocks in that case. - AsyncBackingParams.max_candidate_depth - This parameter determines the maximum unincluded
segment depth the relay chain will support. Candidates sent to validators which exceed
max_candidate_depth
will be ignored.Capacity
, as mentioned above, should not exceedmax_candidate_depth
. - AsyncBackingParams.allowed_ancestry_len - Each parachain block candidate has a
relay_parent
from which its execution and validation context is derived. Before async backing therelay_parent
for a candidate not yet backed was required to be the fresh head of a fork. With async backing we can relax this requirement. Instead we set a conservative maximum age in blocks for therelay_parent
s of candidates in the unincluded segment. This age,allowed_ancestry_len
lives on the relay chain and is queried by parachains when deciding which block to build on top of. - Lookahead Collator - A collator for Aura that looks ahead of the most recently included parachain block when determining what to build upon. This collator also builds additional blocks when the maximum backlog is not saturated. The size of the backlog is determined by invoking the AuraUnincludedSegmentApi. If that runtime API is not supported, this assumes a maximum backlog size of 1.
Phase 1 - Update Parachain Runtime
This phase involves configuring your parachain’s runtime to make use of async backing system.
Establish constants for
capacity
andvelocity
and set both of them to 1 in/runtime/src/lib.rs
.Establish a constant relay chain slot duration measured in milliseconds equal to
6000
in/runtime/src/lib.rs
.
- Establish constants
MILLISECS_PER_BLOCK
andSLOT_DURATION
if not already present in/runtime/src/lib.rs
.
Configure
cumulus_pallet_parachain_system
inruntime/src/lib.rs
- Define a
FixedVelocityConsensusHook
using our capacity, velocity, and relay slot duration constants. Use this to set the parachain systemConsensusHook
property.
- Set the parachain system property
CheckAssociatedRelayNumber
toRelayNumberMonotonicallyIncreases
- Define a
Configure
pallet_aura
inruntime/src/lib.rs
- Set
AllowMultipleBlocksPerSlot
to false - Define
pallet_aura::SlotDuration
using our constantSLOT_DURATION
- Set
Update
aura_api::SlotDuration()
to match the constantSLOT_DURATION
Implement the AuraUnincludedSegmentApi, which allows the collator client to query its runtime to determine whether it should author a block.
Add the dependency
cumulus-primitives-aura
to theruntime/Cargo.toml
file for your runtimeInside the
impl_runtime_apis!
block for your runtime, implement theAuraUnincludedSegmentApi
as shown below.Important note: With a capacity of 1 we have an effective velocity of ½ even when velocity is configured to some larger value. This is because capacity will be filled after a single block is produced and will only be freed up after that block is included on the relay chain, which takes 2 relay blocks to accomplish. Thus with capacity 1 and velocity 1 we get the customary 12 second parachain block time.
If your
runtime/src/lib.rs
provides aCheckInherents
type toregister_validate_block
, remove it.FixedVelocityConsensusHook
makes it unnecessary. The following example shows howregister_validate_block
should look after removingCheckInherents
.
Phase 2 - Update Parachain Nodes
This phase consists of plugging in the new lookahead collator node.
- Import
cumulus_primitives_core::ValidationCode
tonode/src/service.rs
- In
node/src/service.rs
, modifysc_service::spawn_tasks
to use a clone ofBackend
rather than the original
- Add
backend
as a parameter tostart_consensus()
innode/src/service.rs
- In
start_consensus()
import the lookahead collator rather than the basic collator
- In
start_consensus()
replace theBasicAuraParams
struct withAuraParams
- Change the struct type from
BasicAuraParams
toAuraParams
- In the
para_client
field, pass in a cloned para client rather than the original - Add a
para_backend
parameter afterpara_client
, passing in our para backend - Provide a
code_hash_provider
closure like that shown below - Increase
authoring_duration
from 500 milliseconds to 1500
- Change the struct type from
Note: Set authoring_duration
to whatever you want, taking your own hardware into account. But if
the backer who should be slower than you due to reading from disk, times out at two seconds your
candidates will be rejected.
- In
start_consensus()
replacebasic_aura::run
withaura::run
Phase 3 - Activate Async Backing
This phase consists of changes to your parachain’s runtime that activate async backing feature.
- Configure
pallet_aura
, settingAllowMultipleBlocksPerSlot
to true inruntime/src/lib.rs
.
- Increase the maximum unincluded segment capacity in
runtime/src/lib.rs
.
- Either decrease
MILLISECS_PER_BLOCK
to 6000 or increaseBLOCK_PROCESSING_VELOCITY
to 2 inruntime/src/lib.rs
.
- Note: For a parachain which measures time in terms of its own block number rather than by relay block number it may be preferable to increase velocity. Changing block time may cause complications, requiring additional changes. See the section “Timing by Block Number”.
- Update
MAXIMUM_BLOCK_WEIGHT
to reflect the increased time available for block production.
- Add a feature flagged alternative for
MinimumPeriod
inpallet_timestamp
. The type should beConstU64<0>
with the feature flag experimental, andConstU64<{SLOT_DURATION / 2}>
without.
Timing by Block Number
With asynchronous backing it will be possible for parachains to opt for a block time of 6 seconds rather than 12 seconds. But modifying block duration isn’t so simple for a parachain which was measuring time in terms of its own block number. It could result in expected and actual time not matching up, stalling the parachain.
One strategy to deal with this issue is to instead rely on relay chain block numbers for timing.
Relay block number is kept track of by each parachain in pallet-parachain-system
with the storage
value LastRelayChainBlockNumber
. This value can be obtained and used wherever timing based on
block number is needed.