referrerpolicy=no-referrer-when-downgrade

sc_consensus_slots/
slots.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//! Utility stream for yielding slots in a loop.
20//!
21//! This is used instead of `futures_timer::Interval` because it was unreliable.
22
23use super::{InherentDataProviderExt, Slot, LOG_TARGET};
24use sp_consensus::{SelectChain, SyncOracle};
25use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
26use sp_runtime::traits::{Block as BlockT, HashingFor, Header as HeaderT};
27use sp_trie::recorder::Recorder;
28
29use futures_timer::Delay;
30use std::time::{Duration, Instant};
31
32/// Returns current duration since unix epoch.
33pub fn duration_now() -> Duration {
34	use std::time::SystemTime;
35	let now = SystemTime::now();
36	now.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| {
37		panic!("Current time {:?} is before unix epoch. Something is wrong: {:?}", now, e)
38	})
39}
40
41/// Returns the duration until the next slot from now.
42pub fn time_until_next_slot(slot_duration: Duration) -> Duration {
43	let now = duration_now().as_millis();
44
45	let next_slot = (now + slot_duration.as_millis()) / slot_duration.as_millis();
46	let remaining_millis = next_slot * slot_duration.as_millis() - now;
47	Duration::from_millis(remaining_millis as u64)
48}
49
50/// Information about a slot.
51pub struct SlotInfo<B: BlockT> {
52	/// The slot number as found in the inherent data.
53	pub slot: Slot,
54	/// The instant at which the slot ends.
55	pub ends_at: Instant,
56	/// The inherent data provider.
57	pub create_inherent_data: Box<dyn InherentDataProvider>,
58	/// Slot duration.
59	pub duration: Duration,
60	/// The chain header this slot is based on.
61	pub chain_head: B::Header,
62	/// Some potential block size limit for the block to be authored at this slot.
63	///
64	/// For more information see [`Proposer::propose`](sp_consensus::Proposer::propose).
65	pub block_size_limit: Option<usize>,
66	/// Optional [`Recorder`] to use when build the block.
67	pub storage_proof_recorder: Option<Recorder<HashingFor<B>>>,
68}
69
70impl<B: BlockT> SlotInfo<B> {
71	/// Create a new [`SlotInfo`].
72	///
73	/// `ends_at` is calculated using `timestamp` and `duration`.
74	pub fn new(
75		slot: Slot,
76		create_inherent_data: Box<dyn InherentDataProvider>,
77		duration: Duration,
78		chain_head: B::Header,
79		block_size_limit: Option<usize>,
80	) -> Self {
81		Self {
82			slot,
83			create_inherent_data,
84			duration,
85			chain_head,
86			block_size_limit,
87			ends_at: Instant::now() + time_until_next_slot(duration),
88			storage_proof_recorder: None,
89		}
90	}
91
92	/// Create a new [`SlotInfo`] with a storage proof recorder.
93	///
94	/// `ends_at` is calculated using `timestamp` and `duration`.
95	pub fn with_storage_proof_recorder(
96		slot: Slot,
97		create_inherent_data: Box<dyn InherentDataProvider>,
98		duration: Duration,
99		chain_head: B::Header,
100		block_size_limit: Option<usize>,
101		storage_proof_recorder: Recorder<HashingFor<B>>,
102	) -> Self {
103		Self {
104			slot,
105			create_inherent_data,
106			duration,
107			chain_head,
108			block_size_limit,
109			ends_at: Instant::now() + time_until_next_slot(duration),
110			storage_proof_recorder: Some(storage_proof_recorder),
111		}
112	}
113}
114
115/// A stream that returns every time there is a new slot.
116pub(crate) struct Slots<Block, SC, IDP, SO> {
117	last_slot: Slot,
118	slot_duration: Duration,
119	until_next_slot: Option<Delay>,
120	create_inherent_data_providers: IDP,
121	select_chain: SC,
122	sync_oracle: SO,
123	_phantom: std::marker::PhantomData<Block>,
124}
125
126impl<Block, SC, IDP, SO> Slots<Block, SC, IDP, SO> {
127	/// Create a new `Slots` stream.
128	pub fn new(
129		slot_duration: Duration,
130		create_inherent_data_providers: IDP,
131		select_chain: SC,
132		sync_oracle: SO,
133	) -> Self {
134		Slots {
135			last_slot: 0.into(),
136			slot_duration,
137			until_next_slot: None,
138			create_inherent_data_providers,
139			select_chain,
140			sync_oracle,
141			_phantom: Default::default(),
142		}
143	}
144}
145
146impl<Block, SC, IDP, SO> Slots<Block, SC, IDP, SO>
147where
148	Block: BlockT,
149	SC: SelectChain<Block>,
150	IDP: CreateInherentDataProviders<Block, ()> + 'static,
151	IDP::InherentDataProviders: crate::InherentDataProviderExt,
152	SO: SyncOracle,
153{
154	/// Returns a future that fires when the next slot starts.
155	pub async fn next_slot(&mut self) -> SlotInfo<Block> {
156		loop {
157			// Wait for slot timeout
158			self.until_next_slot
159				.take()
160				.unwrap_or_else(|| {
161					// Schedule first timeout.
162					let wait_dur = time_until_next_slot(self.slot_duration);
163					Delay::new(wait_dur)
164				})
165				.await;
166
167			// Schedule delay for next slot.
168			let wait_dur = time_until_next_slot(self.slot_duration);
169			self.until_next_slot = Some(Delay::new(wait_dur));
170
171			if self.sync_oracle.is_major_syncing() {
172				log::debug!(target: LOG_TARGET, "Skipping slot: major sync is in progress.");
173				continue;
174			}
175
176			let chain_head = match self.select_chain.best_chain().await {
177				Ok(x) => x,
178				Err(e) => {
179					log::warn!(
180						target: LOG_TARGET,
181						"Unable to author block in slot. No best block header: {}",
182						e,
183					);
184					// Let's retry at the next slot.
185					continue
186				},
187			};
188
189			let inherent_data_providers = match self
190				.create_inherent_data_providers
191				.create_inherent_data_providers(chain_head.hash(), ())
192				.await
193			{
194				Ok(x) => x,
195				Err(e) => {
196					log::warn!(
197						target: LOG_TARGET,
198						"Unable to author block in slot. Failure creating inherent data provider: {}",
199						e,
200					);
201					// Let's retry at the next slot.
202					continue
203				},
204			};
205
206			let slot = inherent_data_providers.slot();
207
208			// Never yield the same slot twice.
209			if slot > self.last_slot {
210				self.last_slot = slot;
211
212				break SlotInfo::new(
213					slot,
214					Box::new(inherent_data_providers),
215					self.slot_duration,
216					chain_head,
217					None,
218				)
219			}
220		}
221	}
222}