referrerpolicy=no-referrer-when-downgrade

sc_consensus_slots/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Slots functionality for Substrate.
20//!
21//! Some consensus algorithms have a concept of *slots*, which are intervals in
22//! time during which certain events can and/or must occur.  This crate
23//! provides generic functionality for slots.
24
25#![forbid(unsafe_code)]
26#![warn(missing_docs)]
27
28mod aux_schema;
29mod slots;
30
31pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
32use slots::Slots;
33pub use slots::{time_until_next_slot, SlotInfo};
34
35use futures::{future::Either, Future, TryFutureExt};
36use futures_timer::Delay;
37use log::{debug, info, warn};
38use sc_consensus::{BlockImport, JustificationSyncLink};
39use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO, CONSENSUS_WARN};
40use sp_arithmetic::traits::BaseArithmetic;
41use sp_consensus::{Proposal, ProposeArgs, Proposer, SelectChain, SyncOracle};
42use sp_consensus_slots::{Slot, SlotDuration};
43use sp_inherents::CreateInherentDataProviders;
44use sp_runtime::traits::{Block as BlockT, HashingFor, Header as HeaderT};
45use std::{
46	ops::Deref,
47	time::{Duration, Instant},
48};
49
50const LOG_TARGET: &str = "slots";
51
52/// The changes that need to applied to the storage to create the state for a block.
53///
54/// See [`sp_state_machine::StorageChanges`] for more information.
55pub type StorageChanges<Block> = sp_state_machine::StorageChanges<HashingFor<Block>>;
56
57/// A worker that should be invoked at every new slot.
58///
59/// The implementation should not make any assumptions of the slot being bound to the time or
60/// similar. The only valid assumption is that the slot number is always increasing.
61#[async_trait::async_trait]
62pub trait SlotWorker<B: BlockT> {
63	/// Called when a new slot is triggered.
64	///
65	/// Returns a future that resolves to a block.
66	///
67	/// If block production failed, `None` is returned.
68	async fn on_slot(&mut self, slot_info: SlotInfo<B>) -> Option<B>;
69}
70
71/// A skeleton implementation for `SlotWorker` which tries to claim a slot at
72/// its beginning and tries to produce a block if successfully claimed, timing
73/// out if block production takes too long.
74#[async_trait::async_trait]
75pub trait SimpleSlotWorker<B: BlockT> {
76	/// A handle to a `BlockImport`.
77	type BlockImport: BlockImport<B> + Send + 'static;
78
79	/// A handle to a `SyncOracle`.
80	type SyncOracle: SyncOracle;
81
82	/// A handle to a `JustificationSyncLink`, allows hooking into the sync module to control the
83	/// justification sync process.
84	type JustificationSyncLink: JustificationSyncLink<B>;
85
86	/// The type of future resolving to the proposer.
87	type CreateProposer: Future<Output = Result<Self::Proposer, sp_consensus::Error>>
88		+ Send
89		+ Unpin
90		+ 'static;
91
92	/// The type of proposer to use to build blocks.
93	type Proposer: Proposer<B> + Send;
94
95	/// Data associated with a slot claim.
96	type Claim: Send + Sync + 'static;
97
98	/// Auxiliary data necessary for authoring.
99	type AuxData: Send + Sync + 'static;
100
101	/// The logging target to use when logging messages.
102	fn logging_target(&self) -> &'static str;
103
104	/// A handle to a `BlockImport`.
105	fn block_import(&mut self) -> &mut Self::BlockImport;
106
107	/// Returns the auxiliary data necessary for authoring.
108	fn aux_data(
109		&self,
110		header: &B::Header,
111		slot: Slot,
112	) -> Result<Self::AuxData, sp_consensus::Error>;
113
114	/// Returns the number of authorities.
115	/// None indicate that the authorities information is incomplete.
116	fn authorities_len(&self, aux_data: &Self::AuxData) -> Option<usize>;
117
118	/// Tries to claim the given slot, returning an object with claim data if successful.
119	async fn claim_slot(
120		&mut self,
121		header: &B::Header,
122		slot: Slot,
123		aux_data: &Self::AuxData,
124	) -> Option<Self::Claim>;
125
126	/// Notifies the given slot. Similar to `claim_slot`, but will be called no matter whether we
127	/// need to author blocks or not.
128	fn notify_slot(&self, _header: &B::Header, _slot: Slot, _aux_data: &Self::AuxData) {}
129
130	/// Return the pre digest data to include in a block authored with the given claim.
131	fn pre_digest_data(&self, slot: Slot, claim: &Self::Claim) -> Vec<sp_runtime::DigestItem>;
132
133	/// Returns a function which produces a `BlockImportParams`.
134	async fn block_import_params(
135		&self,
136		header: B::Header,
137		header_hash: &B::Hash,
138		body: Vec<B::Extrinsic>,
139		storage_changes: StorageChanges<B>,
140		public: Self::Claim,
141		aux_data: Self::AuxData,
142	) -> Result<sc_consensus::BlockImportParams<B>, sp_consensus::Error>;
143
144	/// Whether to force authoring if offline.
145	fn force_authoring(&self) -> bool;
146
147	/// Returns whether the block production should back off.
148	///
149	/// By default this function always returns `false`.
150	///
151	/// An example strategy that back offs if the finalized head is lagging too much behind the tip
152	/// is implemented by [`BackoffAuthoringOnFinalizedHeadLagging`].
153	fn should_backoff(&self, _slot: Slot, _chain_head: &B::Header) -> bool {
154		false
155	}
156
157	/// Returns a handle to a `SyncOracle`.
158	fn sync_oracle(&mut self) -> &mut Self::SyncOracle;
159
160	/// Returns a handle to a `JustificationSyncLink`.
161	fn justification_sync_link(&mut self) -> &mut Self::JustificationSyncLink;
162
163	/// Returns a `Proposer` to author on top of the given block.
164	fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer;
165
166	/// Returns a [`TelemetryHandle`] if any.
167	fn telemetry(&self) -> Option<TelemetryHandle>;
168
169	/// Remaining duration for proposing.
170	fn proposing_remaining_duration(&self, slot_info: &SlotInfo<B>) -> Duration;
171
172	/// Propose a block by `Proposer`.
173	async fn propose(
174		&mut self,
175		proposer: Self::Proposer,
176		claim: &Self::Claim,
177		slot_info: SlotInfo<B>,
178		end_proposing_at: Instant,
179	) -> Option<Proposal<B>> {
180		let slot = slot_info.slot;
181		let telemetry = self.telemetry();
182		let log_target = self.logging_target();
183
184		let inherent_data =
185			Self::create_inherent_data(&slot_info, &log_target, end_proposing_at).await?;
186
187		let proposing_remaining_duration =
188			end_proposing_at.saturating_duration_since(Instant::now());
189		let logs = self.pre_digest_data(slot, claim);
190
191		// deadline our production to 98% of the total time left for proposing. As we deadline
192		// the proposing below to the same total time left, the 2% margin should be enough for
193		// the result to be returned.
194		let propose_args = ProposeArgs {
195			inherent_data,
196			inherent_digests: sp_runtime::generic::Digest { logs },
197			max_duration: proposing_remaining_duration.mul_f32(0.98),
198			block_size_limit: slot_info.block_size_limit,
199			storage_proof_recorder: slot_info.storage_proof_recorder,
200			..Default::default()
201		};
202
203		let proposing = proposer
204			.propose(propose_args)
205			.map_err(|e| sp_consensus::Error::ClientImport(e.to_string()));
206
207		let proposal = match futures::future::select(
208			proposing,
209			Delay::new(proposing_remaining_duration),
210		)
211		.await
212		{
213			Either::Left((Ok(p), _)) => p,
214			Either::Left((Err(err), _)) => {
215				warn!(target: log_target, "Proposing failed: {}", err);
216
217				return None;
218			},
219			Either::Right(_) => {
220				info!(
221					target: log_target,
222					"โŒ›๏ธ Discarding proposal for slot {}; block production took too long", slot,
223				);
224				// If the node was compiled with debug, tell the user to use release optimizations.
225				#[cfg(build_profile = "debug")]
226				info!(
227					target: log_target,
228					"๐Ÿ‘‰ Recompile your node in `--release` mode to mitigate this problem.",
229				);
230				telemetry!(
231					telemetry;
232					CONSENSUS_INFO;
233					"slots.discarding_proposal_took_too_long";
234					"slot" => *slot,
235				);
236
237				return None;
238			},
239		};
240
241		Some(proposal)
242	}
243
244	/// Calls `create_inherent_data` and handles errors.
245	async fn create_inherent_data(
246		slot_info: &SlotInfo<B>,
247		logging_target: &str,
248		end_proposing_at: Instant,
249	) -> Option<sp_inherents::InherentData> {
250		let remaining_duration = end_proposing_at.saturating_duration_since(Instant::now());
251		let delay = Delay::new(remaining_duration);
252		let cid = slot_info.create_inherent_data.create_inherent_data();
253		let inherent_data = match futures::future::select(delay, cid).await {
254			Either::Right((Ok(data), _)) => data,
255			Either::Right((Err(err), _)) => {
256				warn!(
257					target: logging_target,
258					"Unable to create inherent data for block {:?}: {}",
259					slot_info.chain_head.hash(),
260					err,
261				);
262
263				return None;
264			},
265			Either::Left(_) => {
266				warn!(
267					target: logging_target,
268					"Creating inherent data took more time than we had left for slot {} for block {:?}.",
269					slot_info.slot,
270					slot_info.chain_head.hash(),
271				);
272
273				return None;
274			},
275		};
276
277		Some(inherent_data)
278	}
279
280	/// Implements [`SlotWorker::on_slot`].
281	async fn on_slot(&mut self, slot_info: SlotInfo<B>) -> Option<B>
282	where
283		Self: Sync,
284	{
285		let slot = slot_info.slot;
286		let telemetry = self.telemetry();
287		let logging_target = self.logging_target();
288
289		let proposing_remaining_duration = self.proposing_remaining_duration(&slot_info);
290
291		let end_proposing_at = if proposing_remaining_duration == Duration::default() {
292			debug!(
293				target: logging_target,
294				"Skipping proposal slot {} since there's no time left to propose", slot,
295			);
296
297			return None;
298		} else {
299			Instant::now() + proposing_remaining_duration
300		};
301
302		let aux_data = match self.aux_data(&slot_info.chain_head, slot) {
303			Ok(aux_data) => aux_data,
304			Err(err) => {
305				warn!(
306					target: logging_target,
307					"Unable to fetch auxiliary data for block {:?}: {}",
308					slot_info.chain_head.hash(),
309					err,
310				);
311
312				telemetry!(
313					telemetry;
314					CONSENSUS_WARN;
315					"slots.unable_fetching_authorities";
316					"slot" => ?slot_info.chain_head.hash(),
317					"err" => ?err,
318				);
319
320				return None;
321			},
322		};
323
324		self.notify_slot(&slot_info.chain_head, slot, &aux_data);
325
326		let authorities_len = self.authorities_len(&aux_data);
327
328		if !self.force_authoring() &&
329			self.sync_oracle().is_offline() &&
330			authorities_len.map(|a| a > 1).unwrap_or(false)
331		{
332			debug!(target: logging_target, "Skipping proposal slot. Waiting for the network.");
333			telemetry!(
334				telemetry;
335				CONSENSUS_DEBUG;
336				"slots.skipping_proposal_slot";
337				"authorities_len" => authorities_len,
338			);
339
340			return None;
341		}
342
343		let claim = self.claim_slot(&slot_info.chain_head, slot, &aux_data).await?;
344
345		if self.should_backoff(slot, &slot_info.chain_head) {
346			return None;
347		}
348
349		debug!(target: logging_target, "Starting authorship at slot: {slot}");
350
351		telemetry!(telemetry; CONSENSUS_DEBUG; "slots.starting_authorship"; "slot_num" => slot);
352
353		let proposer = match self.proposer(&slot_info.chain_head).await {
354			Ok(p) => p,
355			Err(err) => {
356				warn!(target: logging_target, "Unable to author block in slot {slot:?}: {err}");
357
358				telemetry!(
359					telemetry;
360					CONSENSUS_WARN;
361					"slots.unable_authoring_block";
362					"slot" => *slot,
363					"err" => ?err
364				);
365
366				return None;
367			},
368		};
369
370		let proposal = self.propose(proposer, &claim, slot_info, end_proposing_at).await?;
371
372		let block = proposal.block;
373		let (header, body) = block.deconstruct();
374		let header_num = *header.number();
375		let header_hash = header.hash();
376		let parent_hash = *header.parent_hash();
377
378		let block_import_params = match self
379			.block_import_params(
380				header,
381				&header_hash,
382				body.clone(),
383				proposal.storage_changes,
384				claim,
385				aux_data,
386			)
387			.await
388		{
389			Ok(bi) => bi,
390			Err(err) => {
391				warn!(target: logging_target, "Failed to create block import params: {}", err);
392
393				return None;
394			},
395		};
396
397		info!(
398			target: logging_target,
399			"๐Ÿ”– Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
400			header_num,
401			block_import_params.post_hash(),
402			header_hash,
403		);
404
405		telemetry!(
406			telemetry;
407			CONSENSUS_INFO;
408			"slots.pre_sealed_block";
409			"header_num" => ?header_num,
410			"hash_now" => ?block_import_params.post_hash(),
411			"hash_previously" => ?header_hash,
412		);
413
414		let header = block_import_params.post_header();
415		match self.block_import().import_block(block_import_params).await {
416			Ok(res) => {
417				res.handle_justification(
418					&header.hash(),
419					*header.number(),
420					self.justification_sync_link(),
421				);
422			},
423			Err(err) => {
424				warn!(
425					target: logging_target,
426					"Error with block built on {:?}: {}", parent_hash, err,
427				);
428
429				telemetry!(
430					telemetry;
431					CONSENSUS_WARN;
432					"slots.err_with_block_built_on";
433					"hash" => ?parent_hash,
434					"err" => ?err,
435				);
436			},
437		}
438
439		Some(B::new(header, body))
440	}
441}
442
443/// A type that implements [`SlotWorker`] for a type that implements [`SimpleSlotWorker`].
444///
445/// This is basically a workaround for Rust not supporting specialization. Otherwise we could
446/// implement [`SlotWorker`] for any `T` that implements [`SimpleSlotWorker`], but currently
447/// that would prevent downstream users to implement [`SlotWorker`] for their own types.
448pub struct SimpleSlotWorkerToSlotWorker<T>(pub T);
449
450#[async_trait::async_trait]
451impl<T: SimpleSlotWorker<B> + Send + Sync, B: BlockT> SlotWorker<B>
452	for SimpleSlotWorkerToSlotWorker<T>
453{
454	async fn on_slot(&mut self, slot_info: SlotInfo<B>) -> Option<B> {
455		self.0.on_slot(slot_info).await
456	}
457}
458
459/// Slot specific extension that the inherent data provider needs to implement.
460pub trait InherentDataProviderExt {
461	/// The current slot that will be found in the [`InherentData`](`sp_inherents::InherentData`).
462	fn slot(&self) -> Slot;
463}
464
465/// Small macro for implementing `InherentDataProviderExt` for inherent data provider tuple.
466macro_rules! impl_inherent_data_provider_ext_tuple {
467	( S $(, $TN:ident)* $( , )?) => {
468		impl<S, $( $TN ),*>  InherentDataProviderExt for (S, $($TN),*)
469		where
470			S: Deref<Target = Slot>,
471		{
472			fn slot(&self) -> Slot {
473				*self.0.deref()
474			}
475		}
476	}
477}
478
479impl_inherent_data_provider_ext_tuple!(S);
480impl_inherent_data_provider_ext_tuple!(S, A);
481impl_inherent_data_provider_ext_tuple!(S, A, B);
482impl_inherent_data_provider_ext_tuple!(S, A, B, C);
483impl_inherent_data_provider_ext_tuple!(S, A, B, C, D);
484impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E);
485impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F);
486impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G);
487impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G, H);
488impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G, H, I);
489impl_inherent_data_provider_ext_tuple!(S, A, B, C, D, E, F, G, H, I, J);
490
491/// Start a new slot worker.
492///
493/// Every time a new slot is triggered, `worker.on_slot` is called and the future it returns is
494/// polled until completion, unless we are major syncing.
495pub async fn start_slot_worker<B, C, W, SO, CIDP>(
496	slot_duration: SlotDuration,
497	client: C,
498	mut worker: W,
499	sync_oracle: SO,
500	create_inherent_data_providers: CIDP,
501) where
502	B: BlockT,
503	C: SelectChain<B>,
504	W: SlotWorker<B>,
505	SO: SyncOracle + Send,
506	CIDP: CreateInherentDataProviders<B, ()> + Send + 'static,
507	CIDP::InherentDataProviders: InherentDataProviderExt + Send,
508{
509	let mut slots = Slots::new(
510		slot_duration.as_duration(),
511		create_inherent_data_providers,
512		client,
513		sync_oracle,
514	);
515
516	loop {
517		let slot_info = slots.next_slot().await;
518		let _ = worker.on_slot(slot_info).await;
519	}
520}
521
522/// A header which has been checked
523pub enum CheckedHeader<H, S> {
524	/// A header which has slot in the future. this is the full header (not stripped)
525	/// and the slot in which it should be processed.
526	Deferred(H, Slot),
527	/// A header which is fully checked, including signature. This is the pre-header
528	/// accompanied by the seal components.
529	///
530	/// Includes the digest item that encoded the seal.
531	Checked(H, S),
532}
533
534/// A unit type wrapper to express the proportion of a slot.
535pub struct SlotProportion(f32);
536
537impl SlotProportion {
538	/// Create a new proportion.
539	///
540	/// The given value `inner` should be in the range `[0,1]`. If the value is not in the required
541	/// range, it is clamped into the range.
542	pub fn new(inner: f32) -> Self {
543		Self(inner.clamp(0.0, 1.0))
544	}
545
546	/// Returns the inner that is guaranteed to be in the range `[0,1]`.
547	pub fn get(&self) -> f32 {
548		self.0
549	}
550}
551
552/// The strategy used to calculate the slot lenience used to increase the block proposal time when
553/// slots have been skipped with no blocks authored.
554pub enum SlotLenienceType {
555	/// Increase the lenience linearly with the number of skipped slots.
556	Linear,
557	/// Increase the lenience exponentially with the number of skipped slots.
558	Exponential,
559}
560
561impl SlotLenienceType {
562	fn as_str(&self) -> &'static str {
563		match self {
564			SlotLenienceType::Linear => "linear",
565			SlotLenienceType::Exponential => "exponential",
566		}
567	}
568}
569
570/// Calculate the remaining duration for block proposal taking into account whether any slots have
571/// been skipped and applying the given lenience strategy. If `max_block_proposal_slot_portion` is
572/// not none this method guarantees that the returned duration must be lower or equal to
573/// `slot_info.duration * max_block_proposal_slot_portion`.
574pub fn proposing_remaining_duration<Block: BlockT>(
575	parent_slot: Option<Slot>,
576	slot_info: &SlotInfo<Block>,
577	block_proposal_slot_portion: &SlotProportion,
578	max_block_proposal_slot_portion: Option<&SlotProportion>,
579	slot_lenience_type: SlotLenienceType,
580	log_target: &str,
581) -> Duration {
582	use sp_runtime::traits::Zero;
583
584	let proposing_duration = slot_info.duration.mul_f32(block_proposal_slot_portion.get());
585
586	let slot_remaining = slot_info
587		.ends_at
588		.checked_duration_since(std::time::Instant::now())
589		.unwrap_or_default();
590
591	let proposing_duration = std::cmp::min(slot_remaining, proposing_duration);
592
593	let max_proposing_duration =
594		max_block_proposal_slot_portion.map(|p| slot_info.duration.mul_f32(p.get()));
595
596	// Cap proposing duration across all paths, in case `block_proposal_slot_portion`
597	// exceeds `max_block_proposal_slot_portion`.
598	let proposing_duration = max_proposing_duration
599		.map_or(proposing_duration, |max| std::cmp::min(proposing_duration, max));
600
601	// If parent is genesis block, we don't require any lenience factor.
602	if slot_info.chain_head.number().is_zero() {
603		return proposing_duration;
604	}
605
606	let parent_slot = match parent_slot {
607		Some(parent_slot) => parent_slot,
608		None => return proposing_duration,
609	};
610
611	let slot_lenience = match slot_lenience_type {
612		SlotLenienceType::Exponential => slot_lenience_exponential(parent_slot, slot_info),
613		SlotLenienceType::Linear => slot_lenience_linear(parent_slot, slot_info),
614	};
615
616	if let Some(slot_lenience) = slot_lenience {
617		let lenient_proposing_duration =
618			proposing_duration + slot_lenience.mul_f32(block_proposal_slot_portion.get());
619
620		// if we defined a maximum portion of the slot for proposal then we must make sure the
621		// lenience doesn't go over it
622		let lenient_proposing_duration = max_proposing_duration
623			.map_or(lenient_proposing_duration, |max| {
624				std::cmp::min(lenient_proposing_duration, max)
625			});
626
627		debug!(
628			target: log_target,
629			"No block for {} slots. Applying {} lenience, total proposing duration: {}ms",
630			slot_info.slot.saturating_sub(parent_slot + 1),
631			slot_lenience_type.as_str(),
632			lenient_proposing_duration.as_millis(),
633		);
634
635		lenient_proposing_duration
636	} else {
637		proposing_duration
638	}
639}
640
641/// Calculate a slot duration lenience based on the number of missed slots from current
642/// to parent. If the number of skipped slots is greater than 0 this method will apply
643/// an exponential backoff of at most `2^7 * slot_duration`, if no slots were skipped
644/// this method will return `None.`
645pub fn slot_lenience_exponential<Block: BlockT>(
646	parent_slot: Slot,
647	slot_info: &SlotInfo<Block>,
648) -> Option<Duration> {
649	// never give more than 2^this times the lenience.
650	const BACKOFF_CAP: u64 = 7;
651
652	// how many slots it takes before we double the lenience.
653	const BACKOFF_STEP: u64 = 2;
654
655	// we allow a lenience of the number of slots since the head of the
656	// chain was produced, minus 1 (since there is always a difference of at least 1)
657	//
658	// exponential back-off.
659	// in normal cases we only attempt to issue blocks up to the end of the slot.
660	// when the chain has been stalled for a few slots, we give more lenience.
661	let skipped_slots = *slot_info.slot.saturating_sub(parent_slot + 1);
662
663	if skipped_slots == 0 {
664		None
665	} else {
666		let slot_lenience = skipped_slots / BACKOFF_STEP;
667		let slot_lenience = std::cmp::min(slot_lenience, BACKOFF_CAP);
668		let slot_lenience = 1 << slot_lenience;
669		Some(slot_lenience * slot_info.duration)
670	}
671}
672
673/// Calculate a slot duration lenience based on the number of missed slots from current
674/// to parent. If the number of skipped slots is greater than 0 this method will apply
675/// a linear backoff of at most `20 * slot_duration`, if no slots were skipped
676/// this method will return `None.`
677pub fn slot_lenience_linear<Block: BlockT>(
678	parent_slot: Slot,
679	slot_info: &SlotInfo<Block>,
680) -> Option<Duration> {
681	// never give more than 20 times more lenience.
682	const BACKOFF_CAP: u64 = 20;
683
684	// we allow a lenience of the number of slots since the head of the
685	// chain was produced, minus 1 (since there is always a difference of at least 1)
686	//
687	// linear back-off.
688	// in normal cases we only attempt to issue blocks up to the end of the slot.
689	// when the chain has been stalled for a few slots, we give more lenience.
690	let skipped_slots = *slot_info.slot.saturating_sub(parent_slot + 1);
691
692	if skipped_slots == 0 {
693		None
694	} else {
695		let slot_lenience = std::cmp::min(skipped_slots, BACKOFF_CAP);
696		// We cap `slot_lenience` to `20`, so it should always fit into an `u32`.
697		Some(slot_info.duration * (slot_lenience as u32))
698	}
699}
700
701/// Trait for providing the strategy for when to backoff block authoring.
702pub trait BackoffAuthoringBlocksStrategy<N> {
703	/// Returns true if we should backoff authoring new blocks.
704	fn should_backoff(
705		&self,
706		chain_head_number: N,
707		chain_head_slot: Slot,
708		finalized_number: N,
709		slow_now: Slot,
710		logging_target: &str,
711	) -> bool;
712}
713
714/// A simple default strategy for how to decide backing off authoring blocks if the number of
715/// unfinalized blocks grows too large.
716#[derive(Clone)]
717pub struct BackoffAuthoringOnFinalizedHeadLagging<N> {
718	/// The max interval to backoff when authoring blocks, regardless of delay in finality.
719	pub max_interval: N,
720	/// The number of unfinalized blocks allowed before starting to consider to backoff authoring
721	/// blocks. Note that depending on the value for `authoring_bias`, there might still be an
722	/// additional wait until block authorship starts getting declined.
723	pub unfinalized_slack: N,
724	/// Scales the backoff rate. A higher value effectively means we backoff slower, taking longer
725	/// time to reach the maximum backoff as the unfinalized head of chain grows.
726	pub authoring_bias: N,
727}
728
729/// These parameters is supposed to be some form of sensible defaults.
730impl<N: BaseArithmetic> Default for BackoffAuthoringOnFinalizedHeadLagging<N> {
731	fn default() -> Self {
732		Self {
733			// Never wait more than 100 slots before authoring blocks, regardless of delay in
734			// finality.
735			max_interval: 100.into(),
736			// Start to consider backing off block authorship once we have 50 or more unfinalized
737			// blocks at the head of the chain.
738			unfinalized_slack: 50.into(),
739			// A reasonable default for the authoring bias, or reciprocal interval scaling, is 2.
740			// Effectively meaning that consider the unfinalized head suffix length to grow half as
741			// fast as in actuality.
742			authoring_bias: 2.into(),
743		}
744	}
745}
746
747impl<N> BackoffAuthoringBlocksStrategy<N> for BackoffAuthoringOnFinalizedHeadLagging<N>
748where
749	N: BaseArithmetic + Copy,
750{
751	fn should_backoff(
752		&self,
753		chain_head_number: N,
754		chain_head_slot: Slot,
755		finalized_number: N,
756		slot_now: Slot,
757		logging_target: &str,
758	) -> bool {
759		// This should not happen, but we want to keep the previous behaviour if it does.
760		if slot_now <= chain_head_slot {
761			return false;
762		}
763
764		// There can be race between getting the finalized number and getting the best number.
765		// So, better be safe than sorry.
766		let unfinalized_block_length = chain_head_number.saturating_sub(finalized_number);
767		let interval =
768			unfinalized_block_length.saturating_sub(self.unfinalized_slack) / self.authoring_bias;
769		let interval = interval.min(self.max_interval);
770
771		// We're doing arithmetic between block and slot numbers.
772		let interval: u64 = interval.unique_saturated_into();
773
774		// If interval is nonzero we backoff if the current slot isn't far enough ahead of the chain
775		// head.
776		if *slot_now <= *chain_head_slot + interval {
777			info!(
778				target: logging_target,
779				"Backing off claiming new slot for block authorship: finality is lagging.",
780			);
781			true
782		} else {
783			false
784		}
785	}
786}
787
788impl<N> BackoffAuthoringBlocksStrategy<N> for () {
789	fn should_backoff(
790		&self,
791		_chain_head_number: N,
792		_chain_head_slot: Slot,
793		_finalized_number: N,
794		_slot_now: Slot,
795		_logging_target: &str,
796	) -> bool {
797		false
798	}
799}
800
801#[cfg(test)]
802mod test {
803	use super::*;
804	use sp_runtime::traits::NumberFor;
805	use std::time::{Duration, Instant};
806	use substrate_test_runtime_client::runtime::{Block, Header};
807
808	const SLOT_DURATION: Duration = Duration::from_millis(6000);
809
810	fn slot(slot: u64) -> super::slots::SlotInfo<Block> {
811		slot_with_head_number(slot, 1)
812	}
813
814	fn slot_with_head_number(slot: u64, head_number: u64) -> super::slots::SlotInfo<Block> {
815		super::slots::SlotInfo {
816			slot: slot.into(),
817			duration: SLOT_DURATION,
818			create_inherent_data: Box::new(()),
819			ends_at: Instant::now() + SLOT_DURATION,
820			chain_head: Header::new(
821				head_number,
822				Default::default(),
823				Default::default(),
824				Default::default(),
825				Default::default(),
826			),
827			block_size_limit: None,
828			storage_proof_recorder: None,
829		}
830	}
831
832	#[test]
833	fn linear_slot_lenience() {
834		// if no slots are skipped there should be no lenience
835		assert_eq!(super::slot_lenience_linear(1u64.into(), &slot(2)), None);
836
837		// otherwise the lenience is incremented linearly with
838		// the number of skipped slots.
839		for n in 3..=22 {
840			assert_eq!(
841				super::slot_lenience_linear(1u64.into(), &slot(n)),
842				Some(SLOT_DURATION * (n - 2) as u32),
843			);
844		}
845
846		// but we cap it to a maximum of 20 slots
847		assert_eq!(super::slot_lenience_linear(1u64.into(), &slot(23)), Some(SLOT_DURATION * 20));
848	}
849
850	#[test]
851	fn exponential_slot_lenience() {
852		// if no slots are skipped there should be no lenience
853		assert_eq!(super::slot_lenience_exponential(1u64.into(), &slot(2)), None);
854
855		// otherwise the lenience is incremented exponentially every two slots
856		for n in 3..=17 {
857			assert_eq!(
858				super::slot_lenience_exponential(1u64.into(), &slot(n)),
859				Some(SLOT_DURATION * 2u32.pow((n / 2 - 1) as u32)),
860			);
861		}
862
863		// but we cap it to a maximum of 14 slots
864		assert_eq!(
865			super::slot_lenience_exponential(1u64.into(), &slot(18)),
866			Some(SLOT_DURATION * 2u32.pow(7)),
867		);
868
869		assert_eq!(
870			super::slot_lenience_exponential(1u64.into(), &slot(19)),
871			Some(SLOT_DURATION * 2u32.pow(7)),
872		);
873	}
874
875	#[test]
876	fn proposing_remaining_duration_should_apply_lenience_based_on_proposal_slot_proportion() {
877		assert_eq!(
878			proposing_remaining_duration(
879				Some(0.into()),
880				&slot(2),
881				&SlotProportion(0.25),
882				None,
883				SlotLenienceType::Linear,
884				"test",
885			),
886			SLOT_DURATION.mul_f32(0.25 * 2.0),
887		);
888	}
889
890	#[test]
891	fn proposing_remaining_duration_should_never_exceed_max_proposal_slot_proportion() {
892		assert_eq!(
893			proposing_remaining_duration(
894				Some(0.into()),
895				&slot(100),
896				&SlotProportion(0.25),
897				Some(SlotProportion(0.9)).as_ref(),
898				SlotLenienceType::Exponential,
899				"test",
900			),
901			SLOT_DURATION.mul_f32(0.9),
902		);
903	}
904
905	#[test]
906	fn proposing_remaining_duration_caps_every_path_at_max_proposal_slot_proportion() {
907		let block_portion = SlotProportion(0.5);
908		let max_portion = SlotProportion(0.25);
909		let expected = SLOT_DURATION.mul_f32(0.25);
910
911		// No slots skipped, so no lenience is applied.
912		assert_eq!(
913			proposing_remaining_duration(
914				Some(1.into()),
915				&slot(2),
916				&block_portion,
917				Some(&max_portion),
918				SlotLenienceType::Linear,
919				"test",
920			),
921			expected,
922		);
923
924		// Unknown parent slot.
925		assert_eq!(
926			proposing_remaining_duration(
927				None,
928				&slot(2),
929				&block_portion,
930				Some(&max_portion),
931				SlotLenienceType::Linear,
932				"test",
933			),
934			expected,
935		);
936
937		// Parent is the genesis block.
938		assert_eq!(
939			proposing_remaining_duration(
940				Some(1.into()),
941				&slot_with_head_number(5, 0),
942				&block_portion,
943				Some(&max_portion),
944				SlotLenienceType::Exponential,
945				"test",
946			),
947			expected,
948		);
949
950		// Lenience is applied, but the maximum still wins.
951		assert_eq!(
952			proposing_remaining_duration(
953				Some(1.into()),
954				&slot(4),
955				&block_portion,
956				Some(&max_portion),
957				SlotLenienceType::Linear,
958				"test",
959			),
960			expected,
961		);
962
963		// Without a maximum the full block proposal portion is still returned.
964		assert_eq!(
965			proposing_remaining_duration(
966				Some(1.into()),
967				&slot(2),
968				&block_portion,
969				None,
970				SlotLenienceType::Linear,
971				"test",
972			),
973			SLOT_DURATION.mul_f32(0.5),
974		);
975	}
976
977	#[derive(PartialEq, Debug)]
978	struct HeadState {
979		head_number: NumberFor<Block>,
980		head_slot: u64,
981		slot_now: NumberFor<Block>,
982	}
983
984	impl HeadState {
985		fn author_block(&mut self) {
986			// Add a block to the head, and set latest slot to the current
987			self.head_number += 1;
988			self.head_slot = self.slot_now;
989			// Advance slot to next
990			self.slot_now += 1;
991		}
992
993		fn dont_author_block(&mut self) {
994			self.slot_now += 1;
995		}
996	}
997
998	#[test]
999	fn should_never_backoff_when_head_not_advancing() {
1000		let strategy = BackoffAuthoringOnFinalizedHeadLagging::<NumberFor<Block>> {
1001			max_interval: 100,
1002			unfinalized_slack: 5,
1003			authoring_bias: 2,
1004		};
1005
1006		let head_number = 1;
1007		let head_slot = 1;
1008		let finalized_number = 1;
1009		let slot_now = 2;
1010
1011		let should_backoff: Vec<bool> = (slot_now..1000)
1012			.map(|s| {
1013				strategy.should_backoff(
1014					head_number,
1015					head_slot.into(),
1016					finalized_number,
1017					s.into(),
1018					"slots",
1019				)
1020			})
1021			.collect();
1022
1023		// Should always be false, since the head isn't advancing
1024		let expected: Vec<bool> = (slot_now..1000).map(|_| false).collect();
1025		assert_eq!(should_backoff, expected);
1026	}
1027
1028	#[test]
1029	fn should_stop_authoring_if_blocks_are_still_produced_when_finality_stalled() {
1030		let strategy = BackoffAuthoringOnFinalizedHeadLagging::<NumberFor<Block>> {
1031			max_interval: 100,
1032			unfinalized_slack: 5,
1033			authoring_bias: 2,
1034		};
1035
1036		let mut head_number = 1;
1037		let mut head_slot = 1;
1038		let finalized_number = 1;
1039		let slot_now = 2;
1040
1041		let should_backoff: Vec<bool> = (slot_now..300)
1042			.map(move |s| {
1043				let b = strategy.should_backoff(
1044					head_number,
1045					head_slot.into(),
1046					finalized_number,
1047					s.into(),
1048					"slots",
1049				);
1050				// Chain is still advancing (by someone else)
1051				head_number += 1;
1052				head_slot = s;
1053				b
1054			})
1055			.collect();
1056
1057		// Should always be true after a short while, since the chain is advancing but finality is
1058		// stalled
1059		let expected: Vec<bool> = (slot_now..300).map(|s| s > 8).collect();
1060		assert_eq!(should_backoff, expected);
1061	}
1062
1063	#[test]
1064	fn should_never_backoff_if_max_interval_is_reached() {
1065		let strategy = BackoffAuthoringOnFinalizedHeadLagging::<NumberFor<Block>> {
1066			max_interval: 100,
1067			unfinalized_slack: 5,
1068			authoring_bias: 2,
1069		};
1070
1071		// The limit `max_interval` is used when the unfinalized chain grows to
1072		// 	`max_interval * authoring_bias + unfinalized_slack`,
1073		// which for the above parameters becomes
1074		// 	100 * 2 + 5 = 205.
1075		// Hence we trigger this with head_number > finalized_number + 205.
1076		let head_number = 207;
1077		let finalized_number = 1;
1078
1079		// The limit is then used once the current slot is `max_interval` ahead of slot of the head.
1080		let head_slot = 1;
1081		let slot_now = 2;
1082		let max_interval = strategy.max_interval;
1083
1084		let should_backoff: Vec<bool> = (slot_now..200)
1085			.map(|s| {
1086				strategy.should_backoff(
1087					head_number,
1088					head_slot.into(),
1089					finalized_number,
1090					s.into(),
1091					"slots",
1092				)
1093			})
1094			.collect();
1095
1096		// Should backoff (true) until we are `max_interval` number of slots ahead of the chain
1097		// head slot, then we never backoff (false).
1098		let expected: Vec<bool> = (slot_now..200).map(|s| s <= max_interval + head_slot).collect();
1099		assert_eq!(should_backoff, expected);
1100	}
1101
1102	#[test]
1103	fn should_backoff_authoring_when_finality_stalled() {
1104		let param = BackoffAuthoringOnFinalizedHeadLagging {
1105			max_interval: 100,
1106			unfinalized_slack: 5,
1107			authoring_bias: 2,
1108		};
1109
1110		let finalized_number = 2;
1111		let mut head_state = HeadState { head_number: 4, head_slot: 10, slot_now: 11 };
1112
1113		let should_backoff = |head_state: &HeadState| -> bool {
1114			<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
1115				&param,
1116				head_state.head_number,
1117				head_state.head_slot.into(),
1118				finalized_number,
1119				head_state.slot_now.into(),
1120				"slots",
1121			)
1122		};
1123
1124		let backoff: Vec<bool> = (head_state.slot_now..200)
1125			.map(|_| {
1126				if should_backoff(&head_state) {
1127					head_state.dont_author_block();
1128					true
1129				} else {
1130					head_state.author_block();
1131					false
1132				}
1133			})
1134			.collect();
1135
1136		// Gradually start to backoff more and more frequently
1137		let expected = [
1138			false, false, false, false, false, // no effect
1139			true, false, true, false, // 1:1
1140			true, true, false, true, true, false, // 2:1
1141			true, true, true, false, true, true, true, false, // 3:1
1142			true, true, true, true, false, true, true, true, true, false, // 4:1
1143			true, true, true, true, true, false, true, true, true, true, true, false, // 5:1
1144			true, true, true, true, true, true, false, true, true, true, true, true, true,
1145			false, // 6:1
1146			true, true, true, true, true, true, true, false, true, true, true, true, true, true,
1147			true, false, // 7:1
1148			true, true, true, true, true, true, true, true, false, true, true, true, true, true,
1149			true, true, true, false, // 8:1
1150			true, true, true, true, true, true, true, true, true, false, true, true, true, true,
1151			true, true, true, true, true, false, // 9:1
1152			true, true, true, true, true, true, true, true, true, true, false, true, true, true,
1153			true, true, true, true, true, true, true, false, // 10:1
1154			true, true, true, true, true, true, true, true, true, true, true, false, true, true,
1155			true, true, true, true, true, true, true, true, true, false, // 11:1
1156			true, true, true, true, true, true, true, true, true, true, true, true, false, true,
1157			true, true, true, true, true, true, true, true, true, true, true, false, // 12:1
1158			true, true, true, true,
1159		];
1160
1161		assert_eq!(backoff.as_slice(), &expected[..]);
1162	}
1163
1164	#[test]
1165	fn should_never_wait_more_than_max_interval() {
1166		let param = BackoffAuthoringOnFinalizedHeadLagging {
1167			max_interval: 100,
1168			unfinalized_slack: 5,
1169			authoring_bias: 2,
1170		};
1171
1172		let finalized_number = 2;
1173		let starting_slot = 11;
1174		let mut head_state = HeadState { head_number: 4, head_slot: 10, slot_now: starting_slot };
1175
1176		let should_backoff = |head_state: &HeadState| -> bool {
1177			<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
1178				&param,
1179				head_state.head_number,
1180				head_state.head_slot.into(),
1181				finalized_number,
1182				head_state.slot_now.into(),
1183				"slots",
1184			)
1185		};
1186
1187		let backoff: Vec<bool> = (head_state.slot_now..40000)
1188			.map(|_| {
1189				if should_backoff(&head_state) {
1190					head_state.dont_author_block();
1191					true
1192				} else {
1193					head_state.author_block();
1194					false
1195				}
1196			})
1197			.collect();
1198
1199		let slots_claimed: Vec<usize> = backoff
1200			.iter()
1201			.enumerate()
1202			.filter(|&(_i, x)| x == &false)
1203			.map(|(i, _x)| i + starting_slot as usize)
1204			.collect();
1205
1206		let last_slot = backoff.len() + starting_slot as usize;
1207		let mut last_two_claimed = slots_claimed.iter().rev().take(2);
1208
1209		// Check that we claimed all the way to the end. Check two slots for when we have an uneven
1210		// number of slots_claimed.
1211		let expected_distance = param.max_interval as usize + 1;
1212		assert_eq!(last_slot - last_two_claimed.next().unwrap(), 92);
1213		assert_eq!(last_slot - last_two_claimed.next().unwrap(), 92 + expected_distance);
1214
1215		let intervals: Vec<_> = slots_claimed.windows(2).map(|x| x[1] - x[0]).collect();
1216
1217		// The key thing is that the distance between claimed slots is capped to `max_interval + 1`
1218		// assert_eq!(max_observed_interval, Some(&expected_distance));
1219		assert_eq!(intervals.iter().max(), Some(&expected_distance));
1220
1221		// But lets assert all distances, which we expect to grow linearly until `max_interval + 1`
1222		let expected_intervals: Vec<_> =
1223			(0..497).map(|i| (i / 2).clamp(1, expected_distance)).collect();
1224
1225		assert_eq!(intervals, expected_intervals);
1226	}
1227
1228	fn run_until_max_interval(param: BackoffAuthoringOnFinalizedHeadLagging<u64>) -> (u64, u64) {
1229		let finalized_number = 0;
1230		let mut head_state = HeadState { head_number: 0, head_slot: 0, slot_now: 1 };
1231
1232		let should_backoff = |head_state: &HeadState| -> bool {
1233			<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
1234				&param,
1235				head_state.head_number,
1236				head_state.head_slot.into(),
1237				finalized_number,
1238				head_state.slot_now.into(),
1239				"slots",
1240			)
1241		};
1242
1243		// Number of blocks until we reach the max interval
1244		let block_for_max_interval =
1245			param.max_interval * param.authoring_bias + param.unfinalized_slack;
1246
1247		while head_state.head_number < block_for_max_interval {
1248			if should_backoff(&head_state) {
1249				head_state.dont_author_block();
1250			} else {
1251				head_state.author_block();
1252			}
1253		}
1254
1255		let slot_time = 6;
1256		let time_to_reach_limit = slot_time * head_state.slot_now;
1257		(block_for_max_interval, time_to_reach_limit)
1258	}
1259
1260	// Denoting
1261	// 	C: unfinalized_slack
1262	// 	M: authoring_bias
1263	// 	X: max_interval
1264	// then the number of slots to reach the max interval can be computed from
1265	// 	(start_slot + C) + M * sum(n, 1, X)
1266	// or
1267	// 	(start_slot + C) + M * X*(X+1)/2
1268	fn expected_time_to_reach_max_interval(
1269		param: &BackoffAuthoringOnFinalizedHeadLagging<u64>,
1270	) -> (u64, u64) {
1271		let c = param.unfinalized_slack;
1272		let m = param.authoring_bias;
1273		let x = param.max_interval;
1274		let slot_time = 6;
1275
1276		let block_for_max_interval = x * m + c;
1277
1278		// The 1 is because we start at slot_now = 1.
1279		let expected_number_of_slots = (1 + c) + m * x * (x + 1) / 2;
1280		let time_to_reach = expected_number_of_slots * slot_time;
1281
1282		(block_for_max_interval, time_to_reach)
1283	}
1284
1285	#[test]
1286	fn time_to_reach_upper_bound_for_smaller_slack() {
1287		let param = BackoffAuthoringOnFinalizedHeadLagging {
1288			max_interval: 100,
1289			unfinalized_slack: 5,
1290			authoring_bias: 2,
1291		};
1292		let expected = expected_time_to_reach_max_interval(&param);
1293		let (block_for_max_interval, time_to_reach_limit) = run_until_max_interval(param);
1294		assert_eq!((block_for_max_interval, time_to_reach_limit), expected);
1295		// Note: 16 hours is 57600 sec
1296		assert_eq!((block_for_max_interval, time_to_reach_limit), (205, 60636));
1297	}
1298
1299	#[test]
1300	fn time_to_reach_upper_bound_for_larger_slack() {
1301		let param = BackoffAuthoringOnFinalizedHeadLagging {
1302			max_interval: 100,
1303			unfinalized_slack: 50,
1304			authoring_bias: 2,
1305		};
1306		let expected = expected_time_to_reach_max_interval(&param);
1307		let (block_for_max_interval, time_to_reach_limit) = run_until_max_interval(param);
1308		assert_eq!((block_for_max_interval, time_to_reach_limit), expected);
1309		assert_eq!((block_for_max_interval, time_to_reach_limit), (250, 60906));
1310	}
1311}