Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
000df20
slot_based: Reduce authoring duration of the last produced block
lexnv Oct 29, 2025
3b0b715
slot_based: Update blocks produced regardless of runtime failures
lexnv Oct 29, 2025
34d6b7a
Update from github-actions[bot] running command 'prdoc --audience nod…
github-actions[bot] Oct 29, 2025
9c19866
Merge remote-tracking branch 'origin/master' into lexnv/adjust-aura-a…
lexnv Oct 30, 2025
774871b
slot_based: Detect when the slot changes
lexnv Oct 30, 2025
cccae18
Squashed commit of the following:
lexnv Oct 30, 2025
e5fa9f6
Squashed commit of the following:
lexnv Oct 31, 2025
e48868e
Merge remote-tracking branch 'origin/master' into lexnv/adjust-aura-a…
lexnv Oct 31, 2025
a82895f
Merge remote-tracking branch 'origin/lexnv/es-westend' into lexnv/adj…
lexnv Oct 31, 2025
7a21bad
slot_based: Adjust last block authoring by 1s
lexnv Nov 4, 2025
da80cea
slot-based: Skip building blocks in the last 1s of the slot
lexnv Nov 4, 2025
190a4ac
slot-based: Revert tracing info to baseline
lexnv Nov 4, 2025
0cd8b63
slot-based: Log inside the async task
lexnv Nov 4, 2025
aa01b93
Merge branch 'lexnv/es-westend' into lexnv/adjust-aura-auth-duration
lexnv Nov 5, 2025
4851062
slot-based: Add threashold for slot skipping
lexnv Nov 5, 2025
c943bdc
slot-timer: Downgrade logs to debug
lexnv Nov 5, 2025
32e1235
Update cumulus/client/consensus/aura/src/collators/slot_based/slot_ti…
lexnv Nov 7, 2025
624909f
slot_timer: Bump BLOCK_PRODUCTION_THRESHOLD_MS
lexnv Nov 7, 2025
a25b927
slot_based: Rename variables
lexnv Nov 7, 2025
6569846
slot_timer: Rename to time_until_next_block
lexnv Nov 7, 2025
6a47b55
slot_timer: Break into sep methods
lexnv Nov 7, 2025
3538f0d
slot_timer: Avoid calling into the runtime more than once
lexnv Nov 7, 2025
509b22a
slot_timer: Ensure to not adjust on one authority
lexnv Nov 7, 2025
0c7f02e
slot_timer/tests: Add tests fro compute_time_until_next_slot_change
lexnv Nov 7, 2025
2106f44
Update pr doc
lexnv Nov 7, 2025
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 @@ -139,7 +139,6 @@ where
P::Signature: TryFrom<Vec<u8>> + Member + Codec,
{
async move {
tracing::info!(target: LOG_TARGET, "Starting slot-based block-builder task.");
let BuilderTaskParams {
relay_client,
create_inherent_data_providers,
Expand All @@ -158,6 +157,8 @@ where
max_pov_percentage,
} = params;

tracing::info!(target: LOG_TARGET, ?para_id, ?authoring_duration, ?relay_chain_slot_duration, "Starting slot-based block-builder task.");

let mut slot_timer = SlotTimer::<_, _, P>::new_with_offset(
para_client.clone(),
slot_offset,
Expand Down Expand Up @@ -386,11 +387,8 @@ where
validation_data.max_pov_size * 85 / 100
} as usize;

let adjusted_authoring_duration = match slot_timer.time_until_next_slot() {
Ok((duration, _slot)) => std::cmp::min(authoring_duration, duration),
Err(_) => authoring_duration,
};

let adjusted_authoring_duration =
slot_timer.adjust_authoring_duration(authoring_duration);
tracing::debug!(target: crate::LOG_TARGET, duration = ?adjusted_authoring_duration, "Adjusted proposal duration.");

let Ok(Some(candidate)) = collator
Expand Down
114 changes: 107 additions & 7 deletions cumulus/client/consensus/aura/src/collators/slot_based/slot_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ use std::{
/// Defensive mechanism, corresponds to 12 cores at 6 second block time.
const BLOCK_PRODUCTION_MINIMUM_INTERVAL_MS: Duration = Duration::from_millis(500);

/// The amount of time the authoring duration of the last block production attempt
/// should be reduced by to fit into the slot timing.
const BLOCK_PRODUCTION_ADJUSTMENT_MS: Duration = Duration::from_millis(500);
Copy link
Contributor

Choose a reason for hiding this comment

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

This does not work for 500ms blocks. Shouldn't this actually be computed based on some average expected latency ?

Copy link
Contributor

@sandreim sandreim Oct 31, 2025

Choose a reason for hiding this comment

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

Lets consider the case of 500ms blocks (be it ES or @bkchr blocks). Let's say we'd have an authorship duration for last block in slot of just 50ms (not possible with this PR) and latency between current author and next author is 200ms. The timeline is as follows:

  • last block X production at height N begins at T
  • block is announced at T + 50ms
  • next guy receives announcement and requests the block at T + 250ms
  • author receives the request at T + 450ms
  • next guy begins producing it's first block Y of the slot at height N at T + 500ms
  • next guy receives block X at T + 650ms

So this won't really work because it takes 3 x latency to even receive the block . I think we should consider reducing authoring over a larger period of time to account for the latency and maximum weight of the last block authored in a slot.

For the example above not building any blocks in the last 1s of the slot would do it.

@skunert WDYT ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, this makes sense to me! Since we can have quite large ~250ms latencies, the last 1s should be conservative enough to capture most cases 🙏

Have adjusted the code a bit to skip building the last 1s, relying on the time of the "next slot"


#[derive(Debug)]
pub(crate) struct SlotInfo {
pub timestamp: Timestamp,
Expand All @@ -46,7 +50,7 @@ pub(crate) struct SlotInfo {
/// Manages block-production timings based on chain parameters and assigned cores.
#[derive(Debug)]
pub(crate) struct SlotTimer<Block, Client, P> {
/// Client that is used for runtime calls
/// Parachain client that is used for runtime calls
client: Arc<Client>,
/// Offset the current time by this duration.
time_offset: Duration,
Expand All @@ -56,10 +60,47 @@ pub(crate) struct SlotTimer<Block, Client, P> {
/// attempts we should trigger per relay chain block.
relay_slot_duration: Duration,
/// Stores the latest slot that was reported by [`Self::wait_until_next_slot`].
last_reported_slot: Option<Slot>,
last_reported_slot: LastReportedSlot,
_marker: std::marker::PhantomData<(Block, Box<dyn Fn(P) + Send + Sync + 'static>)>,
}

/// Information about the last reported slot.
#[derive(Debug, Default, Clone, Copy)]
struct LastReportedSlot {
/// The reported slot.
slot: Option<Slot>,
/// The number of blocks produced for that slot.
blocks_produced: usize,
}

impl LastReportedSlot {
/// Increment the number of blocks produced for the slot.
///
/// Returns the incremented number of blocks produced.
fn increment_blocks_produced(&mut self) -> usize {
self.blocks_produced = self.blocks_produced.saturating_add(1);
self.blocks_produced
}

/// Set a new slot, resetting the blocks produced if the slot changed.
fn set_new_slot(&mut self, slot: Slot) {
match self.slot {
Some(s) if s == slot => return,
_ => {},
};

tracing::debug!(
target: LOG_TARGET,
previous_slot = ?self.slot,
next_slot = ?slot,
"Resetting blocks produced for new slot."
);

self.slot = Some(slot);
self.blocks_produced = 0;
}
}

/// Compute when to try block-authoring next.
/// The exact time point is determined by the slot duration of relay- and parachain as
/// well as the last observed core count. If more cores are available, we attempt to author blocks
Expand Down Expand Up @@ -147,7 +188,7 @@ where
time_offset,
last_reported_core_num: None,
relay_slot_duration,
last_reported_slot: None,
last_reported_slot: LastReportedSlot::default(),
_marker: Default::default(),
}
}
Expand All @@ -173,6 +214,61 @@ where
))
}

/// Adjust the authoring duration to fit into the slot timing.
///
/// Returns the adjusted authoring duration and the slot that it corresponds to.
pub fn adjust_authoring_duration(&mut self, authoring_duration: Duration) -> Duration {
// Update the number of parachain blocks produced.
let blocks_produced_so_far = self.last_reported_slot.increment_blocks_produced();

let Ok((duration, slot)) = self.time_until_next_slot() else {
tracing::error!(
target: LOG_TARGET,
"Failed to fetch slot duration from runtime. Using unadjusted authoring duration."
);
return authoring_duration;
};

// Determine how many parachain blocks are produced per relay slot
// based on the authoring duration.
let blocks_produced_per_relay_slot = (self.relay_slot_duration.as_millis() /
authoring_duration.as_millis() as u128) as usize;
Copy link
Member

Choose a reason for hiding this comment

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

You can have slots that are longer than one relay chain slot.


tracing::debug!(
target: LOG_TARGET,
blocks_produced_for_slot = ?blocks_produced_so_far,
?blocks_produced_per_relay_slot,
?authoring_duration,
?duration,
"Adjusting authoring duration for slot.",
);

// If the next attempt duration is less than the authoring duration,
// ensure we fit into the slot.
let mut authoring_duration = authoring_duration.min(duration);

// The authoring of the last block that fits into the relay chain is reduced to fit into the
// slot timing. For example, when the block is full we need sufficient time to
// propagate and import it before the next slot starts.
if blocks_produced_so_far >= blocks_produced_per_relay_slot {
Copy link
Contributor

Choose a reason for hiding this comment

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

This will not always work if we skip building some block. Since we know the block production interval, we can easily compute the time index when the last block production begins.

authoring_duration = std::cmp::max(
authoring_duration.saturating_sub(BLOCK_PRODUCTION_ADJUSTMENT_MS),
BLOCK_PRODUCTION_MINIMUM_INTERVAL_MS,
);

tracing::debug!(
target: LOG_TARGET,
aura_slot = ?slot,
blocks_produced_for_slot = ?blocks_produced_so_far,
?blocks_produced_per_relay_slot,
?authoring_duration,
"Adjusted the last block production attempt for the slot."
);
}

authoring_duration
}

/// Returns a future that resolves when the next block production should be attempted.
pub async fn wait_until_next_slot(&mut self) -> Result<(), ()> {
let Ok(slot_duration) = crate::slot_duration(&*self.client) else {
Expand All @@ -185,10 +281,13 @@ where
match self.last_reported_slot {
// If we already reported a slot, we don't want to skip a slot. But we also don't want
// to go through all the slots if a node was halted for some reason.
Some(ls) if ls + 1 < next_aura_slot && next_aura_slot <= ls + 3 => {
next_aura_slot = ls + 1u64;
LastReportedSlot { slot: Some(slot), .. }
if slot + 1 < next_aura_slot && next_aura_slot <= slot + 3 =>
{
next_aura_slot = slot + 1u64;
},
None | Some(_) => {
_ => {
tracing::trace!(target: LOG_TARGET, ?time_until_next_attempt, "Sleeping until the next slot.");
tokio::time::sleep(time_until_next_attempt).await;
},
}
Expand All @@ -200,7 +299,8 @@ where
"New block production opportunity."
);

self.last_reported_slot = Some(next_aura_slot);
self.last_reported_slot.set_new_slot(next_aura_slot);

Ok(())
}
}
Expand Down
83 changes: 83 additions & 0 deletions prdoc/pr_10154.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
title: 'aura/slot_based: Reduce authoring duration of the last produced block '
doc:
- audience: Node Dev
description: "This PR reduces the authoring duration of the last parachain produced\
\ block. Effectively ensures that the next author has sufficient time to import\
\ the previous block.\n\n- Only the last authoring duration is reduced by 500ms\
\ (`BLOCK_PRODUCTION_ADJUSTMENT_MS`)\n- The authorizing duration was already capped\
\ at a minimum of 500ms (`BLOCK_PRODUCTION_MINIMUM_INTERVAL_MS`)\n- The number\
\ of para blocks that the relay chain can fit is inferred by the `relay slot duration\
\ / default authoring time`\n- `SlotInfo` is adjusted to account for the number\
\ of para blocks produced so far\n- While at it, have added a few extra trace\
\ logs\n\n### Testing Done\n\nTested on top of:\n- https://github.com/paritytech/polkadot-sdk/pull/9880\n\
\n```\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain] New block production\
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like some logs snuck in here? :D

\ opportunity. slot_duration=SlotDuration(6000) aura_slot=Slot(293621792)\nDEBUG\
\ tokio-runtime-worker aura::cumulus: [Parachain] Parachain slot adjusted to relay\
\ chain. timestamp=Timestamp(1761730746000) slot=Slot(293621791)\nDEBUG tokio-runtime-worker\
\ aura::cumulus: [Parachain] Going to claim core relay_parent=0xa486a45c3a10e9ade7d4ddefef7615d4b001d47c962013386b95719d8f37aeae\
\ core_selector=CoreSelector(0) claim_queue_offset=ClaimQueueOffset(1)\nDEBUG\
\ tokio-runtime-worker aura::cumulus: [Parachain] Building block. unincluded_segment_len=0\
\ relay_parent=0xa486\u2026aeae relay_parent_num=61 relay_parent_offset=1 included_hash=0x21c3\u2026\
ac1b included_num=84 parent=0x21c3\u2026ac1b slot=Slot(293621791)\nDEBUG tokio-runtime-worker\
\ aura::cumulus: [Parachain] Expected to produce for 3 cores but only have 1 slots.\
\ Attempting to produce multiple blocks per slot. block_production_interval=2s\n\
DEBUG tokio-runtime-worker aura::cumulus: [Parachain] Adjusting authoring duration\
\ for slot. blocks_produced_for_slot=1 blocks_produced_per_relay_slot=3 authoring_duration=2s\
\ duration=1.99s\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain] Adjusted\
\ proposal duration. duration=1.99s\n\n INFO tokio-runtime-worker sc_basic_authorship::basic_authorship:\
\ [Parachain] \U0001F381 Prepared block for proposing at 85 (8 ms) hash: 0x7579638578324c2f7b5b9f8af0f2bb1aa238f665db70dfcd1ddcb77475733e28;\
\ parent_hash: 0x21c3\u2026ac1b; end: NoMoreTransactions; extrinsics_count: 2\n\
\ INFO tokio-runtime-worker substrate: [Parachain] \U0001F195 Imported #85 (0x21c3\u2026\
ac1b \u2192 0x183a\u20265b0d)\n\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain]\
\ Expected to produce for 3 cores but only have 1 slots. Attempting to produce\
\ multiple blocks per slot. block_production_interval=2s\nTRACE tokio-runtime-worker\
\ aura::cumulus: [Parachain] Determined next block production opportunity. time_until_next_attempt=1.977s\
\ aura_slot=Slot(293621792) last_reported=LastReportedSlot { slot: Some(Slot(293621792)),\
\ blocks_produced: 1 }\nTRACE tokio-runtime-worker aura::cumulus: [Parachain]\
\ Sleeping until the next slot. time_until_next_attempt=1.977s\nDEBUG tokio-runtime-worker\
\ aura::cumulus: [Parachain] New block production opportunity. slot_duration=SlotDuration(6000)\
\ aura_slot=Slot(293621792)\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain]\
\ Parachain slot adjusted to relay chain. timestamp=Timestamp(1761730746000) slot=Slot(293621791)\n\
DEBUG tokio-runtime-worker aura::cumulus: [Parachain] Going to claim core relay_parent=0xa486a45c3a10e9ade7d4ddefef7615d4b001d47c962013386b95719d8f37aeae\
\ core_selector=CoreSelector(1) claim_queue_offset=ClaimQueueOffset(1)\nDEBUG\
\ tokio-runtime-worker aura::cumulus: [Parachain] Building block. unincluded_segment_len=1\
\ relay_parent=0xa486\u2026aeae relay_parent_num=61 relay_parent_offset=1 included_hash=0x21c3\u2026\
ac1b included_num=84 parent=0x183a\u20265b0d slot=Slot(293621791)\nDEBUG tokio-runtime-worker\
\ aura::cumulus: [Parachain] Expected to produce for 3 cores but only have 1 slots.\
\ Attempting to produce multiple blocks per slot. block_production_interval=2s\n\
DEBUG tokio-runtime-worker aura::cumulus: [Parachain] Adjusting authoring duration\
\ for slot. blocks_produced_for_slot=2 blocks_produced_per_relay_slot=3 authoring_duration=2s\
\ duration=1.988s\n\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain] Adjusted\
\ proposal duration. duration=1.988s\n INFO tokio-runtime-worker sc_basic_authorship::basic_authorship:\
\ [Parachain] \U0001F381 Prepared block for proposing at 86 (232 ms) hash: 0x17b4f3fc6f13a8bad577d3e12f026a8c51b37fecd33551beb32e059fc510c941;\
\ parent_hash: 0x183a\u20265b0d; end: NoMoreTransactions; extrinsics_count: 2\n\
\ INFO tokio-runtime-worker substrate: [Parachain] \U0001F195 Imported #86 (0x183a\u2026\
5b0d \u2192 0x9504\u2026a66d)\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain]\
\ Expected to produce for 3 cores but only have 1 slots. Attempting to produce\
\ multiple blocks per slot. block_production_interval=2s\nTRACE tokio-runtime-worker\
\ aura::cumulus: [Parachain] Determined next block production opportunity. time_until_next_attempt=1.751s\
\ aura_slot=Slot(293621792) last_reported=LastReportedSlot { slot: Some(Slot(293621792)),\
\ blocks_produced: 2 }\nTRACE tokio-runtime-worker aura::cumulus: [Parachain]\
\ Sleeping until the next slot. time_until_next_attempt=1.751s\nDEBUG tokio-runtime-worker\
\ aura::cumulus: [Parachain] New block production opportunity. slot_duration=SlotDuration(6000)\
\ aura_slot=Slot(293621792)\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain]\
\ Parachain slot adjusted to relay chain. timestamp=Timestamp(1761730746000) slot=Slot(293621791)\n\
DEBUG tokio-runtime-worker aura::cumulus: [Parachain] Going to claim core relay_parent=0xa486a45c3a10e9ade7d4ddefef7615d4b001d47c962013386b95719d8f37aeae\
\ core_selector=CoreSelector(2) claim_queue_offset=ClaimQueueOffset(1)\nDEBUG\
\ tokio-runtime-worker aura::cumulus: [Parachain] Building block. unincluded_segment_len=2\
\ relay_parent=0xa486\u2026aeae relay_parent_num=61 relay_parent_offset=1 included_hash=0x21c3\u2026\
ac1b included_num=84 parent=0x9504\u2026a66d slot=Slot(293621791)\nDEBUG tokio-runtime-worker\
\ aura::cumulus: [Parachain] Expected to produce for 3 cores but only have 1 slots.\
\ Attempting to produce multiple blocks per slot. block_production_interval=2s\n\
\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain] Adjusting authoring duration\
\ for slot. blocks_produced_for_slot=3 blocks_produced_per_relay_slot=3 authoring_duration=2s\
\ duration=1.99s\nDEBUG tokio-runtime-worker aura::cumulus: [Parachain] Adjusted\
\ the last block production attempt for the slot. aura_slot=Slot(293621793) blocks_produced_for_slot=3\
\ blocks_produced_per_relay_slot=3 authoring_duration=1.49s\nDEBUG tokio-runtime-worker\
\ aura::cumulus: [Parachain] Adjusted proposal duration. duration=1.49s\n```\n\
\n- The first two blocks are adjusted to `duration=1.99s` and `duration=1.988s`\
\ respectively\n- The last block is adjusted to `duration=1.49s` (ie reduced by\
\ another 500ms)\n\nPart of: https://github.com/paritytech/polkadot-sdk/issues/9848"
crates:
- name: cumulus-client-consensus-aura
bump: patch