|
| 1 | +//! # Upgrade Parachain for Asynchronous Backing Compatibility |
| 2 | +//! |
| 3 | +//! This guide is relevant for cumulus based parachain projects started in 2023 or before, whose |
| 4 | +//! backing process is synchronous where parablocks can only be built on the latest Relay Chain |
| 5 | +//! block. Async Backing allows collators to build parablocks on older Relay Chain blocks and create |
| 6 | +//! pipelines of multiple pending parablocks. This parallel block generation increases efficiency |
| 7 | +//! and throughput. For more information on Async backing and its terminology, refer to the document |
| 8 | +//! on [the Polkadot Wiki.](https://wiki.polkadot.network/docs/maintain-guides-async-backing) |
| 9 | +//! |
| 10 | +//! > If starting a new parachain project, please use an async backing compatible template such as |
| 11 | +//! > the |
| 12 | +//! > [parachain template](https://github.com/paritytech/polkadot-sdk/tree/master/templates/parachain). |
| 13 | +//! The rollout process for Async Backing has three phases. Phases 1 and 2 below put new |
| 14 | +//! infrastructure in place. Then we can simply turn on async backing in phase 3. |
| 15 | +//! |
| 16 | +//! ## Prerequisite |
| 17 | +//! |
| 18 | +//! The relay chain needs to have async backing enabled so double-check that the relay-chain |
| 19 | +//! configuration contains the following three parameters (especially when testing locally e.g. with |
| 20 | +//! zombienet): |
| 21 | +//! |
| 22 | +//! ```json |
| 23 | +//! "async_backing_params": { |
| 24 | +//! "max_candidate_depth": 3, |
| 25 | +//! "allowed_ancestry_len": 2 |
| 26 | +//! }, |
| 27 | +//! "scheduling_lookahead": 2 |
| 28 | +//! ``` |
| 29 | +//! |
| 30 | +//! <div class="warning">`scheduling_lookahead` must be set to 2, otherwise parachain block times |
| 31 | +//! will degrade to worse than with sync backing!</div> |
| 32 | +//! |
| 33 | +//! ## Phase 1 - Update Parachain Runtime |
| 34 | +//! |
| 35 | +//! This phase involves configuring your parachain’s runtime `/runtime/src/lib.rs` to make use of |
| 36 | +//! async backing system. |
| 37 | +//! |
| 38 | +//! 1. Establish and ensure constants for `capacity` and `velocity` are both set to 1 in the |
| 39 | +//! runtime. |
| 40 | +//! 2. Establish and ensure the constant relay chain slot duration measured in milliseconds equal to |
| 41 | +//! `6000` in the runtime. |
| 42 | +//! ```rust |
| 43 | +//! // Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the |
| 44 | +//! // relay chain. |
| 45 | +//! pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; |
| 46 | +//! // How many parachain blocks are processed by the relay chain per parent. Limits the number of |
| 47 | +//! // blocks authored per slot. |
| 48 | +//! pub const BLOCK_PROCESSING_VELOCITY: u32 = 1; |
| 49 | +//! // Relay chain slot duration, in milliseconds. |
| 50 | +//! pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; |
| 51 | +//! ``` |
| 52 | +//! |
| 53 | +//! 3. Establish constants `MILLISECS_PER_BLOCK` and `SLOT_DURATION` if not already present in the |
| 54 | +//! runtime. |
| 55 | +//! ```ignore |
| 56 | +//! // `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked |
| 57 | +//! // up by `pallet_aura` to implement `fn slot_duration()`. |
| 58 | +//! // |
| 59 | +//! // Change this to adjust the block time. |
| 60 | +//! pub const MILLISECS_PER_BLOCK: u64 = 12000; |
| 61 | +//! pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; |
| 62 | +//! ``` |
| 63 | +//! |
| 64 | +//! 4. Configure `cumulus_pallet_parachain_system` in the runtime. |
| 65 | +//! |
| 66 | +//! - Define a `FixedVelocityConsensusHook` using our capacity, velocity, and relay slot duration |
| 67 | +//! constants. Use this to set the parachain system `ConsensusHook` property. |
| 68 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/lib.rs", ConsensusHook)] |
| 69 | +//! ```ignore |
| 70 | +//! impl cumulus_pallet_parachain_system::Config for Runtime { |
| 71 | +//! .. |
| 72 | +//! type ConsensusHook = ConsensusHook; |
| 73 | +//! .. |
| 74 | +//! } |
| 75 | +//! ``` |
| 76 | +//! - Set the parachain system property `CheckAssociatedRelayNumber` to |
| 77 | +//! `RelayNumberMonotonicallyIncreases` |
| 78 | +//! ```ignore |
| 79 | +//! impl cumulus_pallet_parachain_system::Config for Runtime { |
| 80 | +//! .. |
| 81 | +//! type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; |
| 82 | +//! .. |
| 83 | +//! } |
| 84 | +//! ``` |
| 85 | +//! |
| 86 | +//! 5. Configure `pallet_aura` in the runtime. |
| 87 | +//! |
| 88 | +//! - Set `AllowMultipleBlocksPerSlot` to `false` (don't worry, we will set it to `true` when we |
| 89 | +//! activate async backing in phase 3). |
| 90 | +//! |
| 91 | +//! - Define `pallet_aura::SlotDuration` using our constant `SLOT_DURATION` |
| 92 | +//! ```ignore |
| 93 | +//! impl pallet_aura::Config for Runtime { |
| 94 | +//! .. |
| 95 | +//! type AllowMultipleBlocksPerSlot = ConstBool<false>; |
| 96 | +//! #[cfg(feature = "experimental")] |
| 97 | +//! type SlotDuration = ConstU64<SLOT_DURATION>; |
| 98 | +//! .. |
| 99 | +//! } |
| 100 | +//! ``` |
| 101 | +//! |
| 102 | +//! 6. Update `sp_consensus_aura::AuraApi::slot_duration` in `sp_api::impl_runtime_apis` to match |
| 103 | +//! the constant `SLOT_DURATION` |
| 104 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/apis.rs", impl_slot_duration)] |
| 105 | +//! |
| 106 | +//! 7. Implement the `AuraUnincludedSegmentApi`, which allows the collator client to query its |
| 107 | +//! runtime to determine whether it should author a block. |
| 108 | +//! |
| 109 | +//! - Add the dependency `cumulus-primitives-aura` to the `runtime/Cargo.toml` file for your |
| 110 | +//! runtime |
| 111 | +//! ```ignore |
| 112 | +//! .. |
| 113 | +//! cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } |
| 114 | +//! .. |
| 115 | +//! ``` |
| 116 | +//! |
| 117 | +//! - In the same file, add `"cumulus-primitives-aura/std",` to the `std` feature. |
| 118 | +//! |
| 119 | +//! - Inside the `impl_runtime_apis!` block for your runtime, implement the |
| 120 | +//! `cumulus_primitives_aura::AuraUnincludedSegmentApi` as shown below. |
| 121 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/apis.rs", impl_can_build_upon)] |
| 122 | +//! |
| 123 | +//! **Note:** With a capacity of 1 we have an effective velocity of ½ even when velocity is |
| 124 | +//! configured to some larger value. This is because capacity will be filled after a single block is |
| 125 | +//! produced and will only be freed up after that block is included on the relay chain, which takes |
| 126 | +//! 2 relay blocks to accomplish. Thus with capacity 1 and velocity 1 we get the customary 12 second |
| 127 | +//! parachain block time. |
| 128 | +//! |
| 129 | +//! 8. If your `runtime/src/lib.rs` provides a `CheckInherents` type to `register_validate_block`, |
| 130 | +//! remove it. `FixedVelocityConsensusHook` makes it unnecessary. The following example shows how |
| 131 | +//! `register_validate_block` should look after removing `CheckInherents`. |
| 132 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/lib.rs", register_validate_block)] |
| 133 | +//! |
| 134 | +//! |
| 135 | +//! ## Phase 2 - Update Parachain Nodes |
| 136 | +//! |
| 137 | +//! This phase consists of plugging in the new lookahead collator node. |
| 138 | +//! |
| 139 | +//! 1. Import `cumulus_primitives_core::ValidationCode` to `node/src/service.rs`. |
| 140 | +#![doc = docify::embed!("../../templates/parachain/node/src/service.rs", cumulus_primitives)] |
| 141 | +//! |
| 142 | +//! 2. In `node/src/service.rs`, modify `sc_service::spawn_tasks` to use a clone of `Backend` rather |
| 143 | +//! than the original |
| 144 | +//! ```ignore |
| 145 | +//! sc_service::spawn_tasks(sc_service::SpawnTasksParams { |
| 146 | +//! .. |
| 147 | +//! backend: backend.clone(), |
| 148 | +//! .. |
| 149 | +//! })?; |
| 150 | +//! ``` |
| 151 | +//! |
| 152 | +//! 3. Add `backend` as a parameter to `start_consensus()` in `node/src/service.rs` |
| 153 | +//! ```text |
| 154 | +//! fn start_consensus( |
| 155 | +//! .. |
| 156 | +//! backend: Arc<ParachainBackend>, |
| 157 | +//! .. |
| 158 | +//! ``` |
| 159 | +//! ```ignore |
| 160 | +//! if validator { |
| 161 | +//! start_consensus( |
| 162 | +//! .. |
| 163 | +//! backend.clone(), |
| 164 | +//! .. |
| 165 | +//! )?; |
| 166 | +//! } |
| 167 | +//! ``` |
| 168 | +//! |
| 169 | +//! 4. In `node/src/service.rs` import the lookahead collator rather than the basic collator |
| 170 | +#![doc = docify::embed!("../../templates/parachain/node/src/service.rs", lookahead_collator)] |
| 171 | +//! |
| 172 | +//! 5. In `start_consensus()` replace the `BasicAuraParams` struct with `AuraParams` |
| 173 | +//! - Change the struct type from `BasicAuraParams` to `AuraParams` |
| 174 | +//! - In the `para_client` field, pass in a cloned para client rather than the original |
| 175 | +//! - Add a `para_backend` parameter after `para_client`, passing in our para backend |
| 176 | +//! - Provide a `code_hash_provider` closure like that shown below |
| 177 | +//! - Increase `authoring_duration` from 500 milliseconds to 1500 |
| 178 | +//! ```ignore |
| 179 | +//! let params = AuraParams { |
| 180 | +//! .. |
| 181 | +//! para_client: client.clone(), |
| 182 | +//! para_backend: backend.clone(), |
| 183 | +//! .. |
| 184 | +//! code_hash_provider: move |block_hash| { |
| 185 | +//! client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) |
| 186 | +//! }, |
| 187 | +//! .. |
| 188 | +//! authoring_duration: Duration::from_millis(1500), |
| 189 | +//! .. |
| 190 | +//! }; |
| 191 | +//! ``` |
| 192 | +//! |
| 193 | +//! **Note:** Set `authoring_duration` to whatever you want, taking your own hardware into account. |
| 194 | +//! But if the backer who should be slower than you due to reading from disk, times out at two |
| 195 | +//! seconds your candidates will be rejected. |
| 196 | +//! |
| 197 | +//! 6. In `start_consensus()` replace `basic_aura::run` with `aura::run` |
| 198 | +//! ```ignore |
| 199 | +//! let fut = |
| 200 | +//! aura::run::<Block, sp_consensus_aura::sr25519::AuthorityPair, _, _, _, _, _, _, _, _, _>( |
| 201 | +//! params, |
| 202 | +//! ); |
| 203 | +//! task_manager.spawn_essential_handle().spawn("aura", None, fut); |
| 204 | +//! ``` |
| 205 | +//! |
| 206 | +//! ## Phase 3 - Activate Async Backing |
| 207 | +//! |
| 208 | +//! This phase consists of changes to your parachain’s runtime that activate async backing feature. |
| 209 | +//! |
| 210 | +//! 1. Configure `pallet_aura`, setting `AllowMultipleBlocksPerSlot` to true in |
| 211 | +//! `runtime/src/lib.rs`. |
| 212 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/configs/mod.rs", aura_config)] |
| 213 | +//! |
| 214 | +//! 2. Increase the maximum `UNINCLUDED_SEGMENT_CAPACITY` in `runtime/src/lib.rs`. |
| 215 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/lib.rs", async_backing_params)] |
| 216 | +//! |
| 217 | +//! 3. Decrease `MILLISECS_PER_BLOCK` to 6000. |
| 218 | +//! |
| 219 | +//! - Note: For a parachain which measures time in terms of its own block number rather than by |
| 220 | +//! relay block number it may be preferable to increase velocity. Changing block time may cause |
| 221 | +//! complications, requiring additional changes. See the section “Timing by Block Number”. |
| 222 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/lib.rs", block_times)] |
| 223 | +//! |
| 224 | +//! 4. Update `MAXIMUM_BLOCK_WEIGHT` to reflect the increased time available for block production. |
| 225 | +#![doc = docify::embed!("../../templates/parachain/runtime/src/lib.rs", max_block_weight)] |
| 226 | +//! |
| 227 | +//! 5. Add a feature flagged alternative for `MinimumPeriod` in `pallet_timestamp`. The type should |
| 228 | +//! be `ConstU64<0>` with the feature flag experimental, and `ConstU64<{SLOT_DURATION / 2}>` |
| 229 | +//! without. |
| 230 | +//! ```ignore |
| 231 | +//! impl pallet_timestamp::Config for Runtime { |
| 232 | +//! .. |
| 233 | +//! #[cfg(feature = "experimental")] |
| 234 | +//! type MinimumPeriod = ConstU64<0>; |
| 235 | +//! #[cfg(not(feature = "experimental"))] |
| 236 | +//! type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; |
| 237 | +//! .. |
| 238 | +//! } |
| 239 | +//! ``` |
| 240 | +//! |
| 241 | +//! ## Timing by Block Number |
| 242 | +//! |
| 243 | +//! With asynchronous backing it will be possible for parachains to opt for a block time of 6 |
| 244 | +//! seconds rather than 12 seconds. But modifying block duration isn’t so simple for a parachain |
| 245 | +//! which was measuring time in terms of its own block number. It could result in expected and |
| 246 | +//! actual time not matching up, stalling the parachain. |
| 247 | +//! |
| 248 | +//! One strategy to deal with this issue is to instead rely on relay chain block numbers for timing. |
| 249 | +//! Relay block number is kept track of by each parachain in `pallet-parachain-system` with the |
| 250 | +//! storage value `LastRelayChainBlockNumber`. This value can be obtained and used wherever timing |
| 251 | +//! based on block number is needed. |
| 252 | +
|
| 253 | +#![deny(rustdoc::broken_intra_doc_links)] |
| 254 | +#![deny(rustdoc::private_intra_doc_links)] |
0 commit comments