1use super::{
18 block_weight_over_target_block_weight, inside_pre_validate, is_first_block_in_core_with_digest,
19 BlockWeightMode, MaxParachainBlockWeight, FULL_CORE_WEIGHT, LOG_TARGET,
20};
21use crate::WeightInfo;
22use alloc::vec::Vec;
23use codec::{Decode, DecodeWithMemTracking, Encode};
24use cumulus_primitives_core::CumulusDigestItem;
25use frame_support::{
26 dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo},
27 pallet_prelude::{
28 InvalidTransaction, TransactionSource, TransactionValidityError, ValidTransaction,
29 },
30 weights::Weight,
31};
32use scale_info::TypeInfo;
33use sp_core::Get;
34use sp_runtime::{
35 traits::{DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, TransactionExtension},
36 DispatchResult,
37};
38
39#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo)]
91#[derive_where::derive_where(Clone, Eq, PartialEq, Default; Inner)]
92#[scale_info(skip_type_params(Config, TargetBlockRate))]
93pub struct DynamicMaxBlockWeight<
94 Config,
95 Inner,
96 TargetBlockRate,
97 const MAX_TRANSACTION_TO_CONSIDER: u32 = 10,
98 const ALLOW_NORMAL: bool = true,
99>(pub Inner, core::marker::PhantomData<(Config, TargetBlockRate)>);
100
101impl<T, S, TargetBlockRate, const MAX_TRANSACTION_TO_CONSIDER: u32, const ALLOW_NORMAL: bool>
102 DynamicMaxBlockWeight<T, S, TargetBlockRate, MAX_TRANSACTION_TO_CONSIDER, ALLOW_NORMAL>
103{
104 pub fn new(s: S) -> Self {
106 Self(s, Default::default())
107 }
108}
109
110impl<
111 Config,
112 Inner,
113 TargetBlockRate,
114 const MAX_TRANSACTION_TO_CONSIDER: u32,
115 const ALLOW_NORMAL: bool,
116 > DynamicMaxBlockWeight<Config, Inner, TargetBlockRate, MAX_TRANSACTION_TO_CONSIDER, ALLOW_NORMAL>
117where
118 Config: crate::Config,
119 TargetBlockRate: Get<u32>,
120{
121 fn pre_validate_extrinsic(
123 info: &DispatchInfo,
124 len: usize,
125 ) -> Result<(), TransactionValidityError> {
126 let is_not_inherent = frame_system::Pallet::<Config>::inherents_applied();
127 let extrinsic_index = frame_system::Pallet::<Config>::extrinsic_index().unwrap_or_default();
128 let transaction_index = is_not_inherent.then(|| extrinsic_index);
129
130 crate::BlockWeightMode::<Config>::mutate(|mode| {
131 let current_mode = mode.get_or_insert_with(|| BlockWeightMode::<Config>::fraction_of_core(transaction_index));
132
133 if current_mode.is_stale() {
137 *current_mode = BlockWeightMode::fraction_of_core(transaction_index);
138 }
139
140 log::trace!(
141 target: LOG_TARGET,
142 "About to pre-validate an extrinsic. current_mode={current_mode:?}, transaction_index={transaction_index:?}"
143 );
144
145 let is_potential =
146 matches!(current_mode, &mut BlockWeightMode::PotentialFullCore { .. });
147
148 match current_mode {
149 BlockWeightMode::<Config>::FullCore { .. } => {},
151 BlockWeightMode::<Config>::PotentialFullCore { first_transaction_index, .. } |
152 BlockWeightMode::<Config>::FractionOfCore { first_transaction_index, .. } => {
153 debug_assert!(
154 !is_potential,
155 "`PotentialFullCore` should resolve to `FullCore` or `FractionOfCore` after applying a transaction.",
156 );
157
158 let digest = frame_system::Pallet::<Config>::digest();
159 let block_weight_over_limit = extrinsic_index == 0
160 && block_weight_over_target_block_weight::<Config, TargetBlockRate>();
161
162 let block_weights = inside_pre_validate::using(&mut true, || Config::BlockWeights::get());
169 let class_weights = block_weights.get(info.class);
170 let target_block_weight =
171 MaxParachainBlockWeight::<Config, TargetBlockRate>::target_block_weight_with_digest(&digest)
172 .saturating_sub(block_weights.base_block);
173
174 let target_weight = class_weights
178 .max_extrinsic
179 .or(class_weights.max_total)
180 .unwrap_or(target_block_weight);
181
182 if block_weight_over_limit {
184 *mode = Some(BlockWeightMode::<Config>::full_core());
185
186 frame_system::Pallet::<Config>::deposit_log(
188 CumulusDigestItem::UseFullCore.to_digest_item(),
189 );
190
191 if !is_first_block_in_core_with_digest(&digest).unwrap_or(false) {
192 frame_system::Pallet::<Config>::register_extra_weight_unchecked(
195 FULL_CORE_WEIGHT,
196 DispatchClass::Mandatory,
197 );
198 }
199
200 log::error!(
201 target: LOG_TARGET,
202 "Inherent block logic took longer than the target block weight, \
203 `DynamicMaxBlockWeightHooks` not registered as `PreInherents` hook!",
204 );
205 } else if info
206 .total_weight()
207 .saturating_add(Weight::from_parts(0, len as u64))
209 .any_gt(target_weight)
210 {
211 let class_allowed = ALLOW_NORMAL || matches!(info.class, DispatchClass::Operational | DispatchClass::Mandatory);
213
214 let is_first_block = is_first_block_in_core_with_digest(&digest).unwrap_or(true);
218
219 if transaction_index.unwrap_or_default().saturating_sub(first_transaction_index.unwrap_or_default()) < MAX_TRANSACTION_TO_CONSIDER
220 && is_first_block && class_allowed {
221 log::trace!(
222 target: LOG_TARGET,
223 "Enabling `PotentialFullCore` mode for extrinsic",
224 );
225
226 *mode = Some(BlockWeightMode::<Config>::potential_full_core(
227 first_transaction_index.or(transaction_index),
230 target_weight,
231 ));
232 } else {
233 log::trace!(
234 target: LOG_TARGET,
235 "Transaction is over the block limit, but is either outside of the allowed window or the dispatch class is not allowed.",
236 );
237
238 return Err(InvalidTransaction::ExhaustsResources)
239 }
240 } else {
241 if is_potential {
242 log::trace!(
243 target: LOG_TARGET,
244 "Resetting back to `FractionOfCore`"
245 );
246 } else {
247 log::trace!(
248 target: LOG_TARGET,
249 "Not changing block weight mode"
250 );
251 }
252
253 *mode =
254 Some(BlockWeightMode::<Config>::fraction_of_core(first_transaction_index.or(transaction_index)));
255 }
256 },
257 };
258
259 Ok(())
260 }).map_err(Into::into)
261 }
262
263 fn post_dispatch_extrinsic(info: &DispatchInfo) -> Weight {
268 crate::BlockWeightMode::<Config>::mutate(|weight_mode| {
269 let Some(mode) = weight_mode else { return Weight::zero() };
270
271 match mode {
272 BlockWeightMode::<Config>::FullCore { .. } => {
274 Config::WeightInfo::block_weight_tx_extension_max_weight()
275 .saturating_sub(Config::WeightInfo::block_weight_tx_extension_full_core())
276 },
277 BlockWeightMode::<Config>::FractionOfCore { .. } => {
278 let digest = frame_system::Pallet::<Config>::digest();
279 let is_above_limit =
280 block_weight_over_target_block_weight::<Config, TargetBlockRate>();
281
282 if is_above_limit {
285 log::error!(
286 target: LOG_TARGET,
287 "Extrinsic ({}) used more weight than what it had announced and pushed the \
288 block above the allowed weight limit!",
289 frame_system::Pallet::<Config>::extrinsic_index().unwrap_or_default()
290 );
291
292 if !is_first_block_in_core_with_digest(&digest).unwrap_or(false) {
296 log::error!(
297 target: LOG_TARGET,
298 "Registering `FULL_CORE_WEIGHT` to ensure no other transaction is included \
299 in this block, because this isn't the first block in the core!",
300 );
301
302 frame_system::Pallet::<Config>::register_extra_weight_unchecked(
303 FULL_CORE_WEIGHT,
304 DispatchClass::Mandatory,
305 );
306 }
307
308 *weight_mode = Some(BlockWeightMode::<Config>::full_core());
309
310 frame_system::Pallet::<Config>::deposit_log(
312 CumulusDigestItem::UseFullCore.to_digest_item(),
313 );
314 }
315
316 Config::WeightInfo::block_weight_tx_extension_max_weight().saturating_sub(
317 Config::WeightInfo::block_weight_tx_extension_stays_fraction_of_core(),
318 )
319 },
320 BlockWeightMode::<Config>::PotentialFullCore {
322 first_transaction_index,
323 target_weight,
324 ..
325 } => {
326 let block_weight = frame_system::BlockWeight::<Config>::get();
327 let extrinsic_class_weight = block_weight.get(info.class);
328
329 if extrinsic_class_weight.any_gt(*target_weight) ||
332 block_weight_over_target_block_weight::<Config, TargetBlockRate>()
333 {
334 log::trace!(
335 target: LOG_TARGET,
336 "Extrinsic class weight {extrinsic_class_weight:?} above target weight {target_weight:?}, enabling `FullCore` mode."
337 );
338
339 *weight_mode = Some(BlockWeightMode::<Config>::full_core());
340
341 frame_system::Pallet::<Config>::deposit_log(
343 CumulusDigestItem::UseFullCore.to_digest_item(),
344 );
345 } else {
346 log::trace!(
347 target: LOG_TARGET,
348 "Extrinsic class weight {extrinsic_class_weight:?} not above target \
349 weight {target_weight:?}, going back to `FractionOfCore` mode."
350 );
351
352 *weight_mode = Some(BlockWeightMode::<Config>::fraction_of_core(
353 *first_transaction_index,
354 ));
355 }
356
357 Weight::zero()
359 },
360 }
361 })
362 }
363}
364
365impl<
366 Config,
367 Inner,
368 TargetBlockRate,
369 const MAX_TRANSACTION_TO_CONSIDER: u32,
370 const ALLOW_NORMAL: bool,
371 > From<Inner>
372 for DynamicMaxBlockWeight<
373 Config,
374 Inner,
375 TargetBlockRate,
376 MAX_TRANSACTION_TO_CONSIDER,
377 ALLOW_NORMAL,
378 >
379{
380 fn from(s: Inner) -> Self {
381 Self::new(s)
382 }
383}
384
385impl<
386 Config,
387 Inner: core::fmt::Debug,
388 TargetBlockRate,
389 const MAX_TRANSACTION_TO_CONSIDER: u32,
390 const ALLOW_NORMAL: bool,
391 > core::fmt::Debug
392 for DynamicMaxBlockWeight<
393 Config,
394 Inner,
395 TargetBlockRate,
396 MAX_TRANSACTION_TO_CONSIDER,
397 ALLOW_NORMAL,
398 >
399{
400 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
401 write!(f, "DynamicMaxBlockWeight<{:?}>", self.0)
402 }
403}
404
405impl<
406 Config: crate::Config + Send + Sync,
407 Inner: TransactionExtension<Config::RuntimeCall>,
408 TargetBlockRate: Get<u32> + Send + Sync + 'static,
409 const MAX_TRANSACTION_TO_CONSIDER: u32,
410 const ALLOW_NORMAL: bool,
411 > TransactionExtension<Config::RuntimeCall>
412 for DynamicMaxBlockWeight<
413 Config,
414 Inner,
415 TargetBlockRate,
416 MAX_TRANSACTION_TO_CONSIDER,
417 ALLOW_NORMAL,
418 >
419where
420 Config::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
421{
422 const IDENTIFIER: &'static str = "DynamicMaxBlockWeight<Use `metadata()`!>";
423
424 type Implicit = Inner::Implicit;
425
426 type Val = Inner::Val;
427
428 type Pre = Inner::Pre;
429
430 fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
431 self.0.implicit()
432 }
433
434 fn metadata() -> Vec<sp_runtime::traits::TransactionExtensionMetadata> {
435 let mut inner = Inner::metadata();
436 inner.push(sp_runtime::traits::TransactionExtensionMetadata {
437 identifier: "DynamicMaxBlockWeight",
438 ty: scale_info::meta_type::<()>(),
439 implicit: scale_info::meta_type::<()>(),
440 });
441 inner
442 }
443
444 fn weight(&self, call: &Config::RuntimeCall) -> Weight {
445 Config::WeightInfo::block_weight_tx_extension_max_weight()
446 .saturating_add(self.0.weight(call))
447 }
448
449 fn validate(
450 &self,
451 origin: Config::RuntimeOrigin,
452 call: &Config::RuntimeCall,
453 info: &DispatchInfoOf<Config::RuntimeCall>,
454 len: usize,
455 self_implicit: Self::Implicit,
456 inherited_implication: &impl Implication,
457 source: TransactionSource,
458 ) -> Result<(ValidTransaction, Self::Val, Config::RuntimeOrigin), TransactionValidityError> {
459 Self::pre_validate_extrinsic(info, len)?;
460
461 self.0
462 .validate(origin, call, info, len, self_implicit, inherited_implication, source)
463 }
464
465 fn prepare(
466 self,
467 val: Self::Val,
468 origin: &Config::RuntimeOrigin,
469 call: &Config::RuntimeCall,
470 info: &DispatchInfoOf<Config::RuntimeCall>,
471 len: usize,
472 ) -> Result<Self::Pre, TransactionValidityError> {
473 self.0.prepare(val, origin, call, info, len)
474 }
475
476 fn post_dispatch_details(
477 pre: Self::Pre,
478 info: &DispatchInfoOf<Config::RuntimeCall>,
479 post_info: &PostDispatchInfo,
480 len: usize,
481 result: &DispatchResult,
482 ) -> Result<Weight, TransactionValidityError> {
483 let weight_refund = Inner::post_dispatch_details(pre, info, post_info, len, result)?;
484
485 let extra_refund = Self::post_dispatch_extrinsic(info);
486
487 Ok(weight_refund.saturating_add(extra_refund))
488 }
489
490 fn bare_validate(
491 call: &Config::RuntimeCall,
492 info: &DispatchInfoOf<Config::RuntimeCall>,
493 len: usize,
494 ) -> frame_support::pallet_prelude::TransactionValidity {
495 Self::pre_validate_extrinsic(info, len)?;
496
497 Inner::bare_validate(call, info, len)
498 }
499
500 fn bare_validate_and_prepare(
501 call: &Config::RuntimeCall,
502 info: &DispatchInfoOf<Config::RuntimeCall>,
503 len: usize,
504 ) -> Result<(), TransactionValidityError> {
505 Self::pre_validate_extrinsic(info, len)?;
506
507 Inner::bare_validate_and_prepare(call, info, len)
508 }
509
510 fn bare_post_dispatch(
511 info: &DispatchInfoOf<Config::RuntimeCall>,
512 post_info: &mut PostDispatchInfoOf<Config::RuntimeCall>,
513 len: usize,
514 result: &DispatchResult,
515 ) -> Result<(), TransactionValidityError> {
516 Inner::bare_post_dispatch(info, post_info, len, result)?;
517
518 Self::post_dispatch_extrinsic(info);
519
520 Ok(())
521 }
522}