referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/on_demand/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! The parachain on demand assignment module.
18//!
19//! Implements a mechanism for taking in orders for on-demand parachain (previously parathreads)
20//! assignments. This module is not handled by the initializer but is instead instantiated in the
21//! `construct_runtime` macro.
22//!
23//! The module uses a single queue for all on-demand orders. Orders are generally processed in the
24//! order they are received, but with an important constraint: only one order per ParaId can be
25//! assigned in each scheduling round. If multiple orders for the same ParaId exist in the queue,
26//! only the first will be assigned, and subsequent orders for that ParaId will remain queued until
27//! the next round.
28
29use core::mem;
30
31use sp_runtime::traits::Zero;
32mod benchmarking;
33pub mod migration;
34
35extern crate alloc;
36
37use crate::{configuration, paras};
38use alloc::{collections::BTreeSet, vec::Vec};
39use frame_support::{
40	pallet_prelude::*,
41	traits::{
42		defensive_prelude::*,
43		Currency,
44		ExistenceRequirement::{self, AllowDeath, KeepAlive},
45		WithdrawReasons,
46	},
47	PalletId,
48};
49use frame_system::{pallet_prelude::*, Pallet as System};
50use polkadot_primitives::{Id as ParaId, ON_DEMAND_MAX_QUEUE_MAX_SIZE};
51use sp_runtime::{
52	traits::{AccountIdConversion, One, SaturatedConversion},
53	FixedPointNumber, FixedPointOperand, FixedU128, Perbill, Saturating,
54};
55
56pub use pallet::*;
57
58mod mock_helpers;
59#[cfg(test)]
60mod tests;
61
62const LOG_TARGET: &str = "runtime::parachains::on-demand";
63
64pub trait WeightInfo {
65	fn place_order_allow_death() -> Weight;
66	fn place_order_keep_alive() -> Weight;
67	fn place_order_with_credits() -> Weight;
68}
69
70/// A weight info that is only suitable for testing.
71pub struct TestWeightInfo;
72
73impl WeightInfo for TestWeightInfo {
74	fn place_order_allow_death() -> Weight {
75		Weight::MAX
76	}
77
78	fn place_order_keep_alive() -> Weight {
79		Weight::MAX
80	}
81
82	fn place_order_with_credits() -> Weight {
83		Weight::MAX
84	}
85}
86
87/// Defines how the account wants to pay for on-demand.
88#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Clone, Eq)]
89enum PaymentType {
90	/// Use credits to purchase on-demand coretime.
91	Credits,
92	/// Use account's free balance to purchase on-demand coretime.
93	Balance,
94}
95
96/// Shorthand for the Balance type the runtime is using.
97pub type BalanceOf<T> =
98	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
99
100/// All queued on-demand orders.
101#[derive(Encode, Decode, TypeInfo)]
102pub struct OrderQueue<N> {
103	queue: BoundedVec<EnqueuedOrder<N>, ConstU32<ON_DEMAND_MAX_QUEUE_MAX_SIZE>>,
104}
105
106impl<N> OrderQueue<N> {
107	/// Pop `num_cores` from the queue, assuming `now` as the current block number.
108	pub fn pop_assignment_for_cores<T: Config>(
109		&mut self,
110		now: N,
111		mut num_cores: u32,
112	) -> impl Iterator<Item = ParaId>
113	where
114		N: Saturating + Ord + One + Copy,
115	{
116		let mut popped = BTreeSet::new();
117		let mut remaining_orders = Vec::with_capacity(self.queue.len());
118		for order in mem::take(&mut self.queue) {
119			// Order is ready 2 blocks later (asynchronous backing):
120			let ready_at = order.ordered_at.saturating_plus_one().saturating_plus_one();
121			let is_ready = ready_at <= now;
122
123			if num_cores > 0 && is_ready && popped.insert(order.para_id) {
124				num_cores -= 1;
125			} else {
126				remaining_orders.push(order);
127			}
128		}
129		self.queue = BoundedVec::truncate_from(remaining_orders);
130		popped.into_iter()
131	}
132
133	fn new() -> Self {
134		OrderQueue { queue: BoundedVec::new() }
135	}
136
137	/// Try to push an additional order.
138	///
139	/// Fails if queue is already at capacity.
140	fn try_push(&mut self, now: N, para_id: ParaId) -> Result<(), ParaId> {
141		self.queue
142			.try_push(EnqueuedOrder { para_id, ordered_at: now })
143			.map_err(|o| o.para_id)
144	}
145
146	fn len(&self) -> usize {
147		self.queue.len()
148	}
149}
150
151/// Data about a placed on-demand order.
152#[derive(Encode, Decode, TypeInfo)]
153struct EnqueuedOrder<N> {
154	/// The parachain the order was placed for.
155	para_id: ParaId,
156	/// The block number the order came in.
157	ordered_at: N,
158}
159
160/// Queue data for on-demand.
161#[derive(Encode, Decode, TypeInfo)]
162struct OrderStatus<N> {
163	/// Last calculated traffic value.
164	traffic: FixedU128,
165
166	/// Enqueued orders.
167	queue: OrderQueue<N>,
168}
169
170impl<N> Default for OrderStatus<N> {
171	fn default() -> OrderStatus<N> {
172		OrderStatus { traffic: FixedU128::default(), queue: OrderQueue::new() }
173	}
174}
175
176/// Errors that can happen during spot traffic calculation.
177#[derive(PartialEq, Debug)]
178pub enum SpotTrafficCalculationErr {
179	/// The order queue capacity is at 0.
180	QueueCapacityIsZero,
181	/// The queue size is larger than the queue capacity.
182	QueueSizeLargerThanCapacity,
183	/// Arithmetic error during division, either division by 0 or over/underflow.
184	Division,
185}
186
187#[frame_support::pallet]
188pub mod pallet {
189
190	use super::*;
191	use polkadot_primitives::Id as ParaId;
192
193	const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
194
195	#[pallet::pallet]
196	#[pallet::without_storage_info]
197	#[pallet::storage_version(STORAGE_VERSION)]
198	pub struct Pallet<T>(_);
199
200	#[pallet::config]
201	pub trait Config: frame_system::Config + configuration::Config + paras::Config {
202		/// The runtime's definition of an event.
203		#[allow(deprecated)]
204		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
205
206		/// The runtime's definition of a Currency.
207		type Currency: Currency<Self::AccountId>;
208
209		/// Something that provides the weight of this pallet.
210		type WeightInfo: WeightInfo;
211
212		/// The default value for the spot traffic multiplier.
213		#[pallet::constant]
214		type TrafficDefaultValue: Get<FixedU128>;
215
216		/// The maximum number of blocks some historical revenue
217		/// information stored for.
218		#[pallet::constant]
219		type MaxHistoricalRevenue: Get<u32>;
220
221		/// Identifier for the internal revenue balance.
222		#[pallet::constant]
223		type PalletId: Get<PalletId>;
224	}
225
226	/// Priority queue for all orders which don't yet (or not any more) have any core affinity.
227	#[pallet::storage]
228	pub(super) type OrderStatus<T: Config> =
229		StorageValue<_, super::OrderStatus<BlockNumberFor<T>>, ValueQuery>;
230
231	/// Keeps track of accumulated revenue from on demand order sales.
232	#[pallet::storage]
233	pub(super) type Revenue<T: Config> =
234		StorageValue<_, BoundedVec<BalanceOf<T>, T::MaxHistoricalRevenue>, ValueQuery>;
235
236	/// Keeps track of credits owned by each account.
237	#[pallet::storage]
238	pub type Credits<T: Config> =
239		StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;
240
241	#[pallet::event]
242	#[pallet::generate_deposit(pub(super) fn deposit_event)]
243	pub enum Event<T: Config> {
244		/// An order was placed at some spot price amount by orderer ordered_by
245		OnDemandOrderPlaced { para_id: ParaId, spot_price: BalanceOf<T>, ordered_by: T::AccountId },
246		/// The value of the spot price has likely changed
247		SpotPriceSet { spot_price: BalanceOf<T> },
248		/// An account was given credits.
249		AccountCredited { who: T::AccountId, amount: BalanceOf<T> },
250	}
251
252	#[pallet::error]
253	pub enum Error<T> {
254		/// The order queue is full, `place_order` will not continue.
255		QueueFull,
256		/// The current spot price is higher than the max amount specified in the `place_order`
257		/// call, making it invalid.
258		SpotPriceHigherThanMaxAmount,
259		/// The account doesn't have enough credits to purchase on-demand coretime.
260		InsufficientCredits,
261	}
262
263	#[pallet::hooks]
264	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
265		fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
266			// Update revenue information storage.
267			Revenue::<T>::mutate(|revenue| {
268				if let Some(overdue) =
269					revenue.force_insert_keep_left(0, 0u32.into()).defensive_unwrap_or(None)
270				{
271					// We have some overdue revenue not claimed by the Coretime Chain, let's
272					// accumulate it at the oldest stored block
273					if let Some(last) = revenue.last_mut() {
274						*last = last.saturating_add(overdue);
275					}
276				}
277			});
278
279			let config = configuration::ActiveConfig::<T>::get();
280			// We need to update the spot traffic on block initialize in order to account for idle
281			// blocks.
282			OrderStatus::<T>::mutate(|order_status| {
283				Self::update_spot_traffic(&config, order_status);
284			});
285
286			// Reads: `Revenue`, `ActiveConfig`, `OrderStatus`
287			// Writes: `Revenue`, `OrderStatus`
288			T::DbWeight::get().reads_writes(3, 2)
289		}
290	}
291
292	#[pallet::call]
293	impl<T: Config> Pallet<T> {
294		/// Create a single on demand core order.
295		/// Will use the spot price for the current block and will reap the account if needed.
296		///
297		/// Parameters:
298		/// - `origin`: The sender of the call, funds will be withdrawn from this account.
299		/// - `max_amount`: The maximum balance to withdraw from the origin to place an order.
300		/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
301		///
302		/// Errors:
303		/// - `InsufficientBalance`: from the Currency implementation
304		/// - `QueueFull`
305		/// - `SpotPriceHigherThanMaxAmount`
306		///
307		/// Events:
308		/// - `OnDemandOrderPlaced`
309		#[pallet::call_index(0)]
310		#[pallet::weight(<T as Config>::WeightInfo::place_order_allow_death())]
311		#[allow(deprecated)]
312		#[deprecated(note = "This will be removed in favor of using `place_order_with_credits`")]
313		pub fn place_order_allow_death(
314			origin: OriginFor<T>,
315			max_amount: BalanceOf<T>,
316			para_id: ParaId,
317		) -> DispatchResult {
318			let sender = ensure_signed(origin)?;
319			Pallet::<T>::do_place_order(
320				sender,
321				max_amount,
322				para_id,
323				AllowDeath,
324				PaymentType::Balance,
325			)
326		}
327
328		/// Same as the [`place_order_allow_death`](Self::place_order_allow_death) call , but with a
329		/// check that placing the order will not reap the account.
330		///
331		/// Parameters:
332		/// - `origin`: The sender of the call, funds will be withdrawn from this account.
333		/// - `max_amount`: The maximum balance to withdraw from the origin to place an order.
334		/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
335		///
336		/// Errors:
337		/// - `InsufficientBalance`: from the Currency implementation
338		/// - `QueueFull`
339		/// - `SpotPriceHigherThanMaxAmount`
340		///
341		/// Events:
342		/// - `OnDemandOrderPlaced`
343		#[pallet::call_index(1)]
344		#[pallet::weight(<T as Config>::WeightInfo::place_order_keep_alive())]
345		#[allow(deprecated)]
346		#[deprecated(note = "This will be removed in favor of using `place_order_with_credits`")]
347		pub fn place_order_keep_alive(
348			origin: OriginFor<T>,
349			max_amount: BalanceOf<T>,
350			para_id: ParaId,
351		) -> DispatchResult {
352			let sender = ensure_signed(origin)?;
353			Pallet::<T>::do_place_order(
354				sender,
355				max_amount,
356				para_id,
357				KeepAlive,
358				PaymentType::Balance,
359			)
360		}
361
362		/// Create a single on demand core order with credits.
363		/// Will charge the owner's on-demand credit account the spot price for the current block.
364		///
365		/// Parameters:
366		/// - `origin`: The sender of the call, on-demand credits will be withdrawn from this
367		///   account.
368		/// - `max_amount`: The maximum number of credits to spend from the origin to place an
369		///   order.
370		/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
371		///
372		/// Errors:
373		/// - `InsufficientCredits`
374		/// - `QueueFull`
375		/// - `SpotPriceHigherThanMaxAmount`
376		///
377		/// Events:
378		/// - `OnDemandOrderPlaced`
379		#[pallet::call_index(2)]
380		#[pallet::weight(<T as Config>::WeightInfo::place_order_with_credits())]
381		pub fn place_order_with_credits(
382			origin: OriginFor<T>,
383			max_amount: BalanceOf<T>,
384			para_id: ParaId,
385		) -> DispatchResult {
386			let sender = ensure_signed(origin)?;
387			Pallet::<T>::do_place_order(
388				sender,
389				max_amount,
390				para_id,
391				KeepAlive,
392				PaymentType::Credits,
393			)
394		}
395	}
396}
397
398// Internal functions and interface to scheduler/wrapping assignment provider.
399impl<T: Config> Pallet<T>
400where
401	BalanceOf<T>: FixedPointOperand,
402{
403	/// Pop assignments for the given number of on-demand cores in a block.
404	pub fn pop_assignment_for_cores(
405		now: BlockNumberFor<T>,
406		num_cores: u32,
407	) -> impl Iterator<Item = ParaId> {
408		pallet::OrderStatus::<T>::mutate(|order_status| {
409			order_status.queue.pop_assignment_for_cores::<T>(now, num_cores)
410		})
411	}
412
413	/// Look into upcoming orders.
414	///
415	/// The returned `OrderQueue` allows for simulating upcoming
416	/// `pop_assignment_for_cores` calls.
417	///
418	/// **Note**: The current implementation returns the entire queue (up to 10,000 orders).
419	/// Callers typically only need `num_cores * scheduling_lookahead` orders (e.g., 10 cores *
420	/// 5 lookahead = 50 orders). Future implementations should consider adding a limit parameter
421	/// to avoid returning unnecessary data and enable more efficient storage schemes.
422	pub fn peek_order_queue() -> OrderQueue<BlockNumberFor<T>> {
423		pallet::OrderStatus::<T>::get().queue
424	}
425
426	/// Push an order back to the back of the queue.
427	///
428	/// The order could not be served for some reason, give it another chance.
429	///
430	/// Parameters:
431	/// - `para_id`: The para that did not make it.
432	pub fn push_back_order(para_id: ParaId) {
433		pallet::OrderStatus::<T>::mutate(|order_status| {
434			let now = <frame_system::Pallet<T>>::block_number();
435			if let Err(e) = order_status.queue.try_push(now, para_id) {
436				log::debug!(target: LOG_TARGET, "Pushing back order failed (queue too long): {:?}", e);
437			};
438		});
439	}
440
441	/// Adds credits to the specified account.
442	///
443	/// Parameters:
444	/// - `who`: Credit receiver.
445	/// - `amount`: The amount of new credits the account will receive.
446	pub fn credit_account(who: T::AccountId, amount: BalanceOf<T>) {
447		Credits::<T>::mutate(who.clone(), |credits| {
448			*credits = credits.saturating_add(amount);
449		});
450		Pallet::<T>::deposit_event(Event::<T>::AccountCredited { who, amount });
451	}
452
453	/// Helper function for `place_order_*` calls. Used to differentiate between placing orders
454	/// with a keep alive check or to allow the account to be reaped. The amount charged is
455	/// stored to the pallet account to be later paid out as revenue.
456	///
457	/// Parameters:
458	/// - `sender`: The sender of the call, funds will be withdrawn from this account.
459	/// - `max_amount`: The maximum balance to withdraw from the origin to place an order.
460	/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
461	/// - `existence_requirement`: Whether or not to ensure that the account will not be reaped.
462	/// - `payment_type`: Defines how the user wants to pay for on-demand.
463	///
464	/// Errors:
465	/// - `InsufficientBalance`: from the Currency implementation
466	/// - `QueueFull`
467	/// - `SpotPriceHigherThanMaxAmount`
468	///
469	/// Events:
470	/// - `OnDemandOrderPlaced`
471	fn do_place_order(
472		sender: <T as frame_system::Config>::AccountId,
473		max_amount: BalanceOf<T>,
474		para_id: ParaId,
475		existence_requirement: ExistenceRequirement,
476		payment_type: PaymentType,
477	) -> DispatchResult {
478		let config = configuration::ActiveConfig::<T>::get();
479
480		pallet::OrderStatus::<T>::mutate(|order_status| {
481			Self::update_spot_traffic(&config, order_status);
482			let traffic = order_status.traffic;
483
484			// Calculate spot price
485			let spot_price: BalanceOf<T> = traffic.saturating_mul_int(
486				config.scheduler_params.on_demand_base_fee.saturated_into::<BalanceOf<T>>(),
487			);
488
489			// Is the current price higher than `max_amount`
490			ensure!(spot_price.le(&max_amount), Error::<T>::SpotPriceHigherThanMaxAmount);
491
492			ensure!(
493				order_status.queue.len() <
494					config.scheduler_params.on_demand_queue_max_size as usize,
495				Error::<T>::QueueFull
496			);
497
498			match payment_type {
499				PaymentType::Balance => {
500					// Charge the sending account the spot price. The amount will be teleported to
501					// the broker chain once it requests revenue information.
502					let amt = T::Currency::withdraw(
503						&sender,
504						spot_price,
505						WithdrawReasons::FEE,
506						existence_requirement,
507					)?;
508
509					// Consume the negative imbalance and deposit it into the pallet account. Make
510					// sure the account preserves even without the existential deposit.
511					let pot = Self::account_id();
512					if !System::<T>::account_exists(&pot) {
513						System::<T>::inc_providers(&pot);
514					}
515					T::Currency::resolve_creating(&pot, amt);
516				},
517				PaymentType::Credits => {
518					let credits = Credits::<T>::get(&sender);
519
520					// Charge the sending account the spot price in credits.
521					let new_credits_value =
522						credits.checked_sub(&spot_price).ok_or(Error::<T>::InsufficientCredits)?;
523
524					if new_credits_value.is_zero() {
525						Credits::<T>::remove(&sender);
526					} else {
527						Credits::<T>::insert(&sender, new_credits_value);
528					}
529				},
530			}
531
532			// Add the amount to the current block's (index 0) revenue information.
533			Revenue::<T>::mutate(|bounded_revenue| {
534				if let Some(current_block) = bounded_revenue.get_mut(0) {
535					*current_block = current_block.saturating_add(spot_price);
536				} else {
537					// Revenue has already been claimed in the same block, including the block
538					// itself. It shouldn't normally happen as revenue claims in the future are
539					// not allowed.
540					bounded_revenue.try_push(spot_price).defensive_ok();
541				}
542			});
543
544			let now = <frame_system::Pallet<T>>::block_number();
545			order_status
546				.queue
547				.try_push(now, para_id)
548				.defensive_map_err(|_| Error::<T>::QueueFull)?;
549
550			Pallet::<T>::deposit_event(Event::<T>::OnDemandOrderPlaced {
551				para_id,
552				spot_price,
553				ordered_by: sender,
554			});
555
556			Ok(())
557		})
558	}
559
560	/// Calculate and update spot traffic.
561	fn update_spot_traffic(
562		config: &configuration::HostConfiguration<BlockNumberFor<T>>,
563		order_status: &mut OrderStatus<BlockNumberFor<T>>,
564	) {
565		let old_traffic = order_status.traffic;
566		match Self::calculate_spot_traffic(
567			old_traffic,
568			config.scheduler_params.on_demand_queue_max_size,
569			order_status.queue.len() as u32,
570			config.scheduler_params.on_demand_target_queue_utilization,
571			config.scheduler_params.on_demand_fee_variability,
572		) {
573			Ok(new_traffic) => {
574				// Only update storage on change
575				if new_traffic != old_traffic {
576					order_status.traffic = new_traffic;
577
578					// calculate the new spot price
579					let spot_price: BalanceOf<T> = new_traffic.saturating_mul_int(
580						config.scheduler_params.on_demand_base_fee.saturated_into::<BalanceOf<T>>(),
581					);
582
583					// emit the event for updated new price
584					Pallet::<T>::deposit_event(Event::<T>::SpotPriceSet { spot_price });
585				}
586			},
587			Err(err) => {
588				log::debug!(
589					target: LOG_TARGET,
590					"Error calculating spot traffic: {:?}", err
591				);
592			},
593		};
594	}
595
596	/// The spot price multiplier. This is based on the transaction fee calculations defined in:
597	/// https://research.web3.foundation/Polkadot/overview/token-economics#setting-transaction-fees
598	///
599	/// Parameters:
600	/// - `traffic`: The previously calculated multiplier, can never go below 1.0.
601	/// - `queue_capacity`: The max size of the order book.
602	/// - `queue_size`: How many orders are currently in the order book.
603	/// - `target_queue_utilisation`: How much of the queue_capacity should be ideally occupied,
604	///   expressed in percentages(perbill).
605	/// - `variability`: A variability factor, i.e. how quickly the spot price adjusts. This number
606	///   can be chosen by p/(k*(1-s)) where p is the desired ratio increase in spot price over k
607	///   number of blocks. s is the target_queue_utilisation. A concrete example: v =
608	///   0.05/(20*(1-0.25)) = 0.0033.
609	///
610	/// Returns:
611	/// - A `FixedU128` in the range of  `Config::TrafficDefaultValue` - `FixedU128::MAX` on
612	///   success.
613	///
614	/// Errors:
615	/// - `SpotTrafficCalculationErr::QueueCapacityIsZero`
616	/// - `SpotTrafficCalculationErr::QueueSizeLargerThanCapacity`
617	/// - `SpotTrafficCalculationErr::Division`
618	fn calculate_spot_traffic(
619		traffic: FixedU128,
620		queue_capacity: u32,
621		queue_size: u32,
622		target_queue_utilisation: Perbill,
623		variability: Perbill,
624	) -> Result<FixedU128, SpotTrafficCalculationErr> {
625		// Return early if queue has no capacity.
626		if queue_capacity == 0 {
627			return Err(SpotTrafficCalculationErr::QueueCapacityIsZero);
628		}
629
630		// Return early if queue size is greater than capacity.
631		if queue_size > queue_capacity {
632			return Err(SpotTrafficCalculationErr::QueueSizeLargerThanCapacity);
633		}
634
635		// (queue_size / queue_capacity) - target_queue_utilisation
636		let queue_util_ratio = FixedU128::from_rational(queue_size.into(), queue_capacity.into());
637		let positive = queue_util_ratio >= target_queue_utilisation.into();
638		let queue_util_diff = queue_util_ratio.max(target_queue_utilisation.into()) -
639			queue_util_ratio.min(target_queue_utilisation.into());
640
641		// variability * queue_util_diff
642		let var_times_qud = queue_util_diff.saturating_mul(variability.into());
643
644		// variability^2 * queue_util_diff^2
645		let var_times_qud_pow = var_times_qud.saturating_mul(var_times_qud);
646
647		// (variability^2 * queue_util_diff^2)/2
648		let div_by_two: FixedU128;
649		match var_times_qud_pow.const_checked_div(2.into()) {
650			Some(dbt) => div_by_two = dbt,
651			None => return Err(SpotTrafficCalculationErr::Division),
652		}
653
654		// traffic * (1 + queue_util_diff) + div_by_two
655		if positive {
656			let new_traffic = queue_util_diff
657				.saturating_add(div_by_two)
658				.saturating_add(One::one())
659				.saturating_mul(traffic);
660			Ok(new_traffic.max(<T as Config>::TrafficDefaultValue::get()))
661		} else {
662			let new_traffic = queue_util_diff.saturating_sub(div_by_two).saturating_mul(traffic);
663			Ok(new_traffic.max(<T as Config>::TrafficDefaultValue::get()))
664		}
665	}
666
667	/// Collect the revenue from the `when` blockheight
668	pub fn claim_revenue_until(when: BlockNumberFor<T>) -> BalanceOf<T> {
669		let now = <frame_system::Pallet<T>>::block_number();
670		let mut amount: BalanceOf<T> = BalanceOf::<T>::zero();
671		Revenue::<T>::mutate(|revenue| {
672			while !revenue.is_empty() {
673				let index = (revenue.len() - 1) as u32;
674				if when > now.saturating_sub(index.into()) {
675					amount = amount.saturating_add(revenue.pop().defensive_unwrap_or(0u32.into()));
676				} else {
677					break;
678				}
679			}
680		});
681
682		amount
683	}
684
685	/// Account of the pallet pot, where the funds from instantaneous coretime sale are accumulated.
686	pub fn account_id() -> T::AccountId {
687		T::PalletId::get().into_account_truncating()
688	}
689
690	#[cfg(feature = "runtime-benchmarks")]
691	pub fn populate_queue(para_id: ParaId, num: u32) {
692		let now = <frame_system::Pallet<T>>::block_number();
693		pallet::OrderStatus::<T>::mutate(|order_status| {
694			for _ in 0..num {
695				order_status.queue.try_push(now, para_id).unwrap();
696			}
697		});
698	}
699
700	#[cfg(feature = "runtime-benchmarks")]
701	pub(crate) fn set_revenue(rev: BoundedVec<BalanceOf<T>, T::MaxHistoricalRevenue>) {
702		Revenue::<T>::put(rev);
703	}
704
705	#[cfg(test)]
706	fn set_order_status(new_status: OrderStatus<BlockNumberFor<T>>) {
707		pallet::OrderStatus::<T>::set(new_status);
708	}
709
710	#[cfg(test)]
711	fn get_order_status() -> OrderStatus<BlockNumberFor<T>> {
712		pallet::OrderStatus::<T>::get()
713	}
714
715	#[cfg(test)]
716	fn get_traffic_default_value() -> FixedU128 {
717		<T as Config>::TrafficDefaultValue::get()
718	}
719
720	#[cfg(test)]
721	fn get_revenue() -> Vec<BalanceOf<T>> {
722		Revenue::<T>::get().to_vec()
723	}
724}