Expand description
How to enable Async Backing on parachain projects that started in 2023 or before.
§Upgrade Parachain for Asynchronous Backing Compatibility
This guide is relevant for cumulus based parachain projects started in 2023 or before, whose backing process is synchronous where parablocks can only be built on the latest Relay Chain block. Async Backing allows collators to build parablocks on older Relay Chain blocks and create pipelines of multiple pending parablocks. This parallel block generation increases efficiency and throughput. For more information on Async backing and its terminology, refer to the document on the Polkadot SDK docs.
If starting a new parachain project, please use an async backing compatible template such as the 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.
§Prerequisite
The relay chain needs to have async backing enabled so double-check that the relay-chain configuration contains the following parameter (especially when testing locally e.g. with zombienet):
"scheduler_params": {
"lookahead": 3
}lookahead must be set to at least 3, otherwise parachain
block times will degrade to worse than with sync backing!Note: async_backing_params (max_candidate_depth and allowed_ancestry_len) are no longer
required to be explicitly configured. Async backing works with them set to their default values
of 0.
§Phase 1 - Update Parachain Runtime
This phase involves configuring your parachain’s runtime /runtime/src/lib.rs to make use of
async backing system.
- Establish and ensure constants for
capacityandvelocityin the runtime. - Establish and ensure the constant relay chain slot duration measured in milliseconds equal to
6000in the runtime.
// Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the
// relay chain.
pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3;
// How many parachain blocks are processed by the relay chain per parent. Limits the number of
// blocks authored per slot.
pub const BLOCK_PROCESSING_VELOCITY: u32 = 1;
// Relay chain slot duration, in milliseconds.
pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;- Establish constants
MILLISECS_PER_BLOCKandSLOT_DURATIONif not already present in the runtime.
// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked
// up by `pallet_aura` to implement `fn slot_duration()`.
//
// Change this to adjust the block time.
pub const MILLISECS_PER_BLOCK: u64 = 12000;
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;- Configure
cumulus_pallet_parachain_systemin the runtime.
- Define a
FixedVelocityConsensusHookusing our capacity, velocity, and relay slot duration constants. Use this to set the parachain systemConsensusHookproperty.
type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook<
Runtime,
RELAY_CHAIN_SLOT_DURATION_MILLIS,
BLOCK_PROCESSING_VELOCITY,
UNINCLUDED_SEGMENT_CAPACITY,
>;impl cumulus_pallet_parachain_system::Config for Runtime {
..
type ConsensusHook = ConsensusHook;
..
}- Set the parachain system property
CheckAssociatedRelayNumbertoRelayNumberMonotonicallyIncreases
impl cumulus_pallet_parachain_system::Config for Runtime {
..
type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases;
..
}- Configure
pallet_aurain the runtime.
-
Set
AllowMultipleBlocksPerSlottotrueto allow multiple blocks per slot. -
Define
pallet_aura::SlotDurationusing our constantSLOT_DURATION
impl pallet_aura::Config for Runtime {
..
type AllowMultipleBlocksPerSlot = ConstBool<true>;
type SlotDuration = ConstU64<SLOT_DURATION>;
..
}- Update
sp_consensus_aura::AuraApi::slot_durationinsp_api::impl_runtime_apisto match the constantSLOT_DURATION
fn impl_slot_duration() -> sp_consensus_aura::SlotDuration {
sp_consensus_aura::SlotDuration::from_millis(SLOT_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-aurato theruntime/Cargo.tomlfile for your runtime
- Add the dependency
..
cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false }
..-
In the same file, add
"cumulus-primitives-aura/std",to thestdfeature. -
Inside the
impl_runtime_apis!block for your runtime, implement thecumulus_primitives_aura::AuraUnincludedSegmentApias shown below.
fn impl_can_build_upon(
included_hash: <Block as BlockT>::Hash,
slot: cumulus_primitives_aura::Slot,
) -> bool {
ConsensusHook::can_build_upon(included_hash, slot)
}Note: With the default capacity of 3 and velocity of 1, a single parachain block is authored per relay chain block, giving a 6 second parachain block time.
- If your
runtime/src/lib.rsprovides aCheckInherentstype toregister_validate_block, remove it.FixedVelocityConsensusHookmakes it unnecessary. The following example shows howregister_validate_blockshould look after removingCheckInherents.
cumulus_pallet_parachain_system::register_validate_block! {
Runtime = Runtime,
BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::<Runtime, Executive>,
}§Phase 2 - Update Parachain Nodes
This phase consists of plugging in the new lookahead collator node.
- Import
cumulus_primitives_core::ValidationCodetonode/src/service.rs.
use cumulus_primitives_core::{
relay_chain::{CollatorPair, ValidationCode},
GetParachainInfo, ParaId,
};- In
node/src/service.rs, modifysc_service::spawn_tasksto use a clone ofBackendrather than the original
sc_service::spawn_tasks(sc_service::SpawnTasksParams {
..
backend: backend.clone(),
..
})?;- Add
backendas a parameter tostart_consensus()innode/src/service.rs
fn start_consensus(
..
backend: Arc<ParachainBackend>,
..if validator {
start_consensus(
..
backend.clone(),
..
)?;
}- In
node/src/service.rsimport the lookahead collator rather than the basic collator
use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params as AuraParams};- In
start_consensus()replace theBasicAuraParamsstruct withAuraParams- Change the struct type from
BasicAuraParamstoAuraParams - In the
para_clientfield, pass in a cloned para client rather than the original - Add a
para_backendparameter afterpara_client, passing in our para backend - Provide a
code_hash_providerclosure like that shown below - Increase
authoring_durationfrom 500 milliseconds to 2000
- Change the struct type from
let params = AuraParams {
..
para_client: client.clone(),
para_backend: backend.clone(),
..
code_hash_provider: move |block_hash| {
client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash())
},
..
authoring_duration: Duration::from_millis(2000),
..
};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::runwithaura::run
let fut =
aura::run::<Block, sp_consensus_aura::sr25519::AuthorityPair, _, _, _, _, _, _, _, _, _>(
params,
);
task_manager.spawn_essential_handle().spawn("aura", None, fut);§Phase 3 - Activate Async Backing
This phase consists of changes to your parachain’s runtime that activate async backing feature.
- Verify
pallet_aurahasAllowMultipleBlocksPerSlotset totrueinruntime/src/lib.rs(this should already be done from Phase 1).
impl pallet_aura::Config for Runtime {
type AuthorityId = AuraId;
type DisabledValidators = ();
type MaxAuthorities = ConstU32<100_000>;
type AllowMultipleBlocksPerSlot = ConstBool<true>;
type SlotDuration = ConstU64<SLOT_DURATION>;
}- Verify
UNINCLUDED_SEGMENT_CAPACITYis set to at least3inruntime/src/lib.rs.
mod async_backing_params {
/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included
/// into the relay chain.
pub(crate) const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3;
/// How many parachain blocks are processed by the relay chain per parent. Limits the
/// number of blocks authored per slot.
pub(crate) const BLOCK_PROCESSING_VELOCITY: u32 = 1;
/// Relay chain slot duration, in milliseconds.
pub(crate) const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
}- Decrease
MILLISECS_PER_BLOCKto 6000.
- 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”.
mod block_times {
/// This determines the average expected block time that we are targeting. Blocks will be
/// produced at a minimum duration defined by `SLOT_DURATION`. `SLOT_DURATION` is picked up by
/// `pallet_timestamp` which is in turn picked up by `pallet_aura` to implement `fn
/// slot_duration()`.
///
/// Change this to adjust the block time.
pub const MILLI_SECS_PER_BLOCK: u64 = 6000;
// NOTE: Currently it is not possible to change the slot duration after the chain has started.
// Attempting to do so will brick block production.
pub const SLOT_DURATION: u64 = MILLI_SECS_PER_BLOCK;
}- Update
MAXIMUM_BLOCK_WEIGHTto reflect the increased time available for block production.
const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64,
);- Set
MinimumPeriodto0inpallet_timestamp. This is required to allow multiple blocks within the same slot.
impl pallet_timestamp::Config for Runtime {
..
type MinimumPeriod = ConstU64<0>;
..
}§Timing by Block Number
With asynchronous backing, parachains produce blocks every 6 seconds rather than 12 seconds. This means that any on-chain logic that derives time from parachain block numbers will see time move twice as fast. This could result in expected and actual time not matching up, potentially causing issues with vesting schedules, unlock periods, or other time-dependent logic.
The recommended strategy is to rely on relay chain block numbers for timing instead of
parachain block numbers. 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.
Alternatively, pallet_timestamp provides wall-clock time which is independent of block
number and is not affected by changes in block time.