Skip to content

Commit

Permalink
[pallet-broker] add extrinsic to reserve a system core without having…
Browse files Browse the repository at this point in the history
… to wait two sale boundaries (#4273)

When calling the reserve extrinsic after sales have started, the
assignment will be reserved, but two sale period boundaries must pass
before the core is actually assigned.

Since this can take between 28 and 56 days on production networks, a new
extrinsic is introduced to shorten the timeline.

This essentially performs three actions:
1. Reserve it (applies after two sale boundaries)
2. Add it to the Workplan for the next sale period
3. Add it to the Workplan for the rest of the current sale period from
the next timeslice to be commmitted.

The caller must ensure that a core is first added, with most relay chain
implementations having a delay of two session boundaries until it comes
into effect.

Alternatively the extrinsic can be called on a core whose workload can
be clobbered from now until the reservation kicks in (the sale period
after the next). Any workplan entries for that core at other timeslices
should be first removed by the caller.

---------

Co-authored-by: command-bot <>
  • Loading branch information
seadanda authored Dec 21, 2024
1 parent d0c8a07 commit f9cdf41
Show file tree
Hide file tree
Showing 8 changed files with 481 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,24 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Broker::SaleInfo` (r:1 w:0)
/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
/// Storage: `Broker::Reservations` (r:1 w:1)
/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`)
/// Storage: `Broker::Status` (r:1 w:0)
/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
/// Storage: `Broker::Workplan` (r:0 w:2)
/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
fn force_reserve() -> Weight {
// Proof Size summary in bytes:
// Measured: `11125`
// Estimated: `13506`
// Minimum execution time: 32_286_000 picoseconds.
Weight::from_parts(33_830_000, 0)
.saturating_add(Weight::from_parts(0, 13506))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Broker::Leases` (r:1 w:1)
/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(401), added: 896, mode: `MaxEncodedLen`)
fn swap_leases() -> Weight {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,24 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `Broker::SaleInfo` (r:1 w:0)
/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
/// Storage: `Broker::Reservations` (r:1 w:1)
/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`)
/// Storage: `Broker::Status` (r:1 w:0)
/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
/// Storage: `Broker::Workplan` (r:0 w:2)
/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
fn force_reserve() -> Weight {
// Proof Size summary in bytes:
// Measured: `11125`
// Estimated: `13506`
// Minimum execution time: 31_464_000 picoseconds.
Weight::from_parts(32_798_000, 0)
.saturating_add(Weight::from_parts(0, 13506))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `Broker::Leases` (r:1 w:1)
/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`)
fn swap_leases() -> Weight {
Expand Down
19 changes: 19 additions & 0 deletions prdoc/pr_4273.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "[pallet-broker] add extrinsic to reserve a system core without having to wait two sale boundaries"

doc:
- audience: Runtime User
description: |
When calling the reserve extrinsic after sales have started, the assignment will be reserved,
but two sale period boundaries must pass before the core is actually assigned. A new
`force_reserve` extrinsic is introduced to allow a core to be immediately assigned.

crates:
- name: pallet-broker
bump: major
- name: coretime-rococo-runtime
bump: patch
- name: coretime-westend-runtime
bump: patch
41 changes: 41 additions & 0 deletions substrate/frame/broker/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,47 @@ mod benches {
Ok(())
}

#[benchmark]
fn force_reserve() -> Result<(), BenchmarkError> {
Configuration::<T>::put(new_config_record::<T>());
// Assume Reservations to be almost filled for worst case.
let reservation_count = T::MaxReservedCores::get().saturating_sub(1);
setup_reservations::<T>(reservation_count);

// Assume leases to be filled for worst case
setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);

let origin =
T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;

// Sales must be started.
Broker::<T>::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap())
.map_err(|_| BenchmarkError::Weightless)?;

// Add a core.
let status = Status::<T>::get().unwrap();
Broker::<T>::do_request_core_count(status.core_count + 1).unwrap();

advance_to::<T>(T::TimeslicePeriod::get().try_into().ok().unwrap());
let schedule = new_schedule();

#[extrinsic_call]
_(origin as T::RuntimeOrigin, schedule.clone(), status.core_count);

assert_eq!(Reservations::<T>::decode_len().unwrap(), T::MaxReservedCores::get() as usize);

let sale_info = SaleInfo::<T>::get().unwrap();
assert_eq!(
Workplan::<T>::get((sale_info.region_begin, status.core_count)),
Some(schedule.clone())
);
// We called at timeslice 1, therefore 2 was already processed and 3 is the next possible
// assignment point.
assert_eq!(Workplan::<T>::get((3, status.core_count)), Some(schedule));

Ok(())
}

#[benchmark]
fn swap_leases() -> Result<(), BenchmarkError> {
let admin_origin =
Expand Down
21 changes: 21 additions & 0 deletions substrate/frame/broker/src/dispatchable_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,27 @@ impl<T: Config> Pallet<T> {
Ok(())
}

pub(crate) fn do_force_reserve(workload: Schedule, core: CoreIndex) -> DispatchResult {
// Sales must have started, otherwise reserve is equivalent.
let sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;

// Reserve - starts at second sale period boundary from now.
Self::do_reserve(workload.clone())?;

// Add to workload - grants one region from the next sale boundary.
Workplan::<T>::insert((sale.region_begin, core), &workload);

// Assign now until the next sale boundary unless the next timeslice is already the sale
// boundary.
let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
let timeslice = status.last_committed_timeslice.saturating_add(1);
if timeslice < sale.region_begin {
Workplan::<T>::insert((timeslice, core), &workload);
}

Ok(())
}

pub(crate) fn do_set_lease(task: TaskId, until: Timeslice) -> DispatchResult {
let mut r = Leases::<T>::get();
ensure!(until > Self::current_timeslice(), Error::<T>::AlreadyExpired);
Expand Down
26 changes: 26 additions & 0 deletions substrate/frame/broker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,9 @@ pub mod pallet {

/// Reserve a core for a workload.
///
/// The workload will be given a reservation, but two sale period boundaries must pass
/// before the core is actually assigned.
///
/// - `origin`: Must be Root or pass `AdminOrigin`.
/// - `workload`: The workload which should be permanently placed on a core.
#[pallet::call_index(1)]
Expand Down Expand Up @@ -943,6 +946,29 @@ pub mod pallet {
Ok(())
}

/// Reserve a core for a workload immediately.
///
/// - `origin`: Must be Root or pass `AdminOrigin`.
/// - `workload`: The workload which should be permanently placed on a core starting
/// immediately.
/// - `core`: The core to which the assignment should be made until the reservation takes
/// effect. It is left to the caller to either add this new core or reassign any other
/// tasks to this existing core.
///
/// This reserves the workload and then injects the workload into the Workplan for the next
/// two sale periods. This overwrites any existing assignments for this core at the start of
/// the next sale period.
#[pallet::call_index(23)]
pub fn force_reserve(
origin: OriginFor<T>,
workload: Schedule,
core: CoreIndex,
) -> DispatchResultWithPostInfo {
T::AdminOrigin::ensure_origin_or_root(origin)?;
Self::do_force_reserve(workload, core)?;
Ok(Pays::No.into())
}

#[pallet::call_index(99)]
#[pallet::weight(T::WeightInfo::swap_leases())]
pub fn swap_leases(origin: OriginFor<T>, id: TaskId, other: TaskId) -> DispatchResult {
Expand Down
Loading

0 comments on commit f9cdf41

Please sign in to comment.