Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2069,9 +2069,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1755,9 +1755,9 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!(
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1577,9 +1577,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1439,9 +1439,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1347,9 +1347,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1187,9 +1187,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1204,9 +1204,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,9 +510,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions cumulus/parachains/runtimes/people/people-rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,9 +1118,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions cumulus/parachains/runtimes/people/people-westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,9 +1136,9 @@ impl_runtime_apis! {
}
}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_num_cores: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}

Expand Down
4 changes: 2 additions & 2 deletions cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ macro_rules! impl_node_runtime_apis {
}
}

impl cumulus_primitives_core::SlotSchedule<$block> for $runtime {
fn next_slot_schedule(_: u32) -> cumulus_primitives_core::NextSlotSchedule {
impl cumulus_primitives_core::TargetBlockRate<$block> for $runtime {
fn target_block_rate() -> u32 {
unimplemented!()
}
}
Expand Down
162 changes: 11 additions & 151 deletions cumulus/primitives/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ extern crate alloc;

use alloc::vec::Vec;
use codec::{Compact, Decode, DecodeAll, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::time::Duration;
use polkadot_parachain_primitives::primitives::HeadData;
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
Expand Down Expand Up @@ -467,46 +466,6 @@ pub struct CollationInfo {
pub head_data: HeadData,
}

/// The schedule for the next relay chain slot.
///
/// Returns the maximum number of parachain blocks to produce and the block time per block to use.
#[derive(Clone, Debug, codec::Decode, codec::Encode, PartialEq, TypeInfo)]
pub struct NextSlotSchedule {
/// The maximum number of blocks to produce in the relay chain slot.
///
/// The node is free to produce less blocks.
pub number_of_blocks: u32,
/// The target block time in wall clock time for each block.
///
/// The maximum should be [`REF_TIME_PER_CORE_IN_SECS`] or otherwise blocks may fail to
/// validate on the relay chain.
pub block_time: Duration,
}

impl NextSlotSchedule {
/// Creates a schedule that produces one block, occupying an entire core.
pub fn one_block_using_one_core() -> Self {
Self { number_of_blocks: 1, block_time: Duration::from_secs(REF_TIME_PER_CORE_IN_SECS) }
}

/// A schedule that maps `x` blocks onto `y` cores.
pub fn x_blocks_using_y_cores(blocks: u32, cores: u32) -> Self {
let ref_time_per_core = Duration::from_secs(REF_TIME_PER_CORE_IN_SECS);

if blocks == 0 || cores == 0 {
return Self { number_of_blocks: 0, block_time: Duration::from_secs(0) }
}

// In wall clock time we can not go above `6s` (relay chain slot duration), so we need to
// cap there.
let block_time = (ref_time_per_core * cores).min(Duration::from_secs(6)) / blocks;
// One block can at max occupy one core.
let block_time = block_time.min(ref_time_per_core);

Self { block_time, number_of_blocks: blocks }
}
}

sp_api::decl_runtime_apis! {
/// Runtime api to collect information about a collation.
///
Expand Down Expand Up @@ -540,117 +499,18 @@ sp_api::decl_runtime_apis! {
fn relay_parent_offset() -> u32;
}

/// API for parachain slot scheduling.
/// API for parachain target block rate.
///
/// This runtime API allows the parachain runtime to communicate the block interval
/// to the node side. The node will call this API every relay chain slot (~6 seconds)
/// to get the scheduled parachain block interval.
pub trait SlotSchedule {
/// Get the block production schedule for the next relay chain slot.
///
/// - `num_cores`: The number of cores assigned to this parachain
/// This runtime API allows the parachain runtime to communicate the target block rate
/// to the node side. The target block rate is always valid for the next relay chain slot.
///
/// The runtime can not enforce this target block rate. It only acts as a maximum, but not more.
/// In the end it depends on the collator how many blocks will be produced. If there are no cores
/// available or the collator is offline, no blocks at all will be produced.
pub trait TargetBlockRate {
/// Get the target block rate for this parachain.
///
/// Returns a [`NextSlotSchedule`].
fn next_slot_schedule(num_cores: u32) -> NextSlotSchedule;
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn one_block_using_one_core_works() {
let schedule = NextSlotSchedule::one_block_using_one_core();
assert_eq!(schedule.number_of_blocks, 1);
assert_eq!(schedule.block_time, Duration::from_secs(REF_TIME_PER_CORE_IN_SECS));
}

#[test]
fn x_blocks_using_y_cores_basic_functionality() {
// 2 blocks using 1 core: each block gets 1 second
let schedule = NextSlotSchedule::x_blocks_using_y_cores(2, 1);
assert_eq!(schedule.number_of_blocks, 2);
assert_eq!(schedule.block_time, Duration::from_secs(1));

// 4 blocks using 2 cores: each block gets 1 second
let schedule = NextSlotSchedule::x_blocks_using_y_cores(4, 2);
assert_eq!(schedule.number_of_blocks, 4);
assert_eq!(schedule.block_time, Duration::from_secs(1));

// 2 blocks using 2 cores: each block gets 2 seconds (max)
let schedule = NextSlotSchedule::x_blocks_using_y_cores(2, 2);
assert_eq!(schedule.number_of_blocks, 2);
assert_eq!(schedule.block_time, Duration::from_secs(2));
}

#[test]
fn x_blocks_using_y_cores_caps_block_time_at_ref_time() {
let schedule = NextSlotSchedule::x_blocks_using_y_cores(2, 10);
assert_eq!(schedule.number_of_blocks, 2);
assert_eq!(schedule.block_time, Duration::from_secs(REF_TIME_PER_CORE_IN_SECS));

let schedule = NextSlotSchedule::x_blocks_using_y_cores(1, 5);
assert_eq!(schedule.number_of_blocks, 1);
assert_eq!(schedule.block_time, Duration::from_secs(REF_TIME_PER_CORE_IN_SECS));
}

#[test]
fn x_blocks_using_y_cores_edge_cases() {
// Zero blocks
let schedule = NextSlotSchedule::x_blocks_using_y_cores(0, 1);
assert_eq!(schedule.number_of_blocks, 0);
assert_eq!(schedule.block_time, Duration::from_secs(0));

// Zero cores (should not panic, though not realistic)
let schedule = NextSlotSchedule::x_blocks_using_y_cores(2, 0);
assert_eq!(schedule.number_of_blocks, 0);
assert_eq!(schedule.block_time, Duration::from_secs(0));

// Large numbers
let schedule = NextSlotSchedule::x_blocks_using_y_cores(100, 50);
assert_eq!(schedule.number_of_blocks, 100);
assert_eq!(schedule.block_time, Duration::from_millis(60));
}

#[test]
fn x_blocks_using_y_cores_various_ratios() {
// 6 blocks, 3 cores: each block gets 1 second
let schedule = NextSlotSchedule::x_blocks_using_y_cores(6, 3);
assert_eq!(schedule.number_of_blocks, 6);
assert_eq!(schedule.block_time, Duration::from_secs(1));

// 8 blocks, 4 cores: each block gets 1 second
let schedule = NextSlotSchedule::x_blocks_using_y_cores(8, 4);
assert_eq!(schedule.number_of_blocks, 8);
assert_eq!(schedule.block_time, Duration::from_millis(750));

// 4 blocks, 8 cores: each block gets 2 seconds (capped)
let schedule = NextSlotSchedule::x_blocks_using_y_cores(4, 8);
assert_eq!(schedule.number_of_blocks, 4);
assert_eq!(schedule.block_time, Duration::from_millis(1500));

// 10 blocks, 2 cores: each block gets `400ms`
let schedule = NextSlotSchedule::x_blocks_using_y_cores(10, 2);
assert_eq!(schedule.number_of_blocks, 10);
assert_eq!(schedule.block_time, Duration::from_millis(400));
}

#[test]
fn x_blocks_using_y_cores_fractional_seconds() {
// 6 blocks, 1 core: each block gets `333.333... ms (2000ms / 6)`
let schedule = NextSlotSchedule::x_blocks_using_y_cores(6, 1);
assert_eq!(schedule.number_of_blocks, 6);
assert_eq!(schedule.block_time, Duration::from_nanos(333_333_333));

// 8 blocks, 1 core: each block gets `250ms`
let schedule = NextSlotSchedule::x_blocks_using_y_cores(8, 1);
assert_eq!(schedule.number_of_blocks, 8);
assert_eq!(schedule.block_time, Duration::from_millis(250));

// 12 blocks, 1 core: each block gets `~166.666ms`
let schedule = NextSlotSchedule::x_blocks_using_y_cores(12, 1);
assert_eq!(schedule.number_of_blocks, 12);
assert_eq!(schedule.block_time, Duration::from_nanos(166_666_666));
/// Returns the target number of blocks per relay chain slot.
fn target_block_rate() -> u32;
Copy link
Copy Markdown
Contributor

@sandreim sandreim Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're actually exposing the BLOCK_PROCESSING_VELOCITY constant via runtime API.

Then let's have the impls actually return the constant.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean here. We do not expose this via a runtime api and I also don't really want to expose the velocity which needs to be bigger than the number of blocks to produce.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The velocity as it is documented means how many parachain blocks are processed by the relay chain per parent. Limits the number of blocks authored per slot. What is a bit confusing is the relay parent thing, but the limit is clearly what your target_block_rate returns. Maybe I am missing something.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The velocity as it is documented means how many parachain blocks are processed by the relay chain per parent.

No. It means how many blocks we are allowed to have "in flight", aka which are not yet included by the relay chain. So, when we want to have 12 blocks, the velocity needs to be at least 24 blocks. Because we are building on relay block X the first 12 blocks and when building the next 12 blocks on X + 1, the first block did not yet get included :) (still waiting for availability)

Somewhere on my todo list I have an item to write an issue to make this velocity a dynamic value, determined based on the number of cores and your target block number (don't remember the exact details and need to read again my notes).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you are describing is UNINCLUDED_SEGMENT_CAPACITY

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good point. :P I will use them later on, ty for pointing it out.

}
}
6 changes: 3 additions & 3 deletions cumulus/test/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,9 +631,9 @@ impl_runtime_apis! {

}

impl cumulus_primitives_core::SlotSchedule<Block> for Runtime {
fn next_slot_schedule(_: u32) -> cumulus_primitives_core::NextSlotSchedule {
cumulus_primitives_core::NextSlotSchedule::one_block_using_one_core()
impl cumulus_primitives_core::TargetBlockRate<Block> for Runtime {
fn target_block_rate() -> u32 {
1
}
}
}
Expand Down
Loading