referrerpolicy=no-referrer-when-downgrade

pallet_aura/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Aura Module
19//!
20//! - [`Config`]
21//! - [`Pallet`]
22//!
23//! ## Overview
24//!
25//! The Aura module extends Aura consensus by managing offline reporting.
26//!
27//! ## Interface
28//!
29//! ### Public Functions
30//!
31//! - `slot_duration` - Determine the Aura slot-duration based on the Timestamp module
32//!   configuration.
33//!
34//! ## Related Modules
35//!
36//! - [Timestamp](../pallet_timestamp/index.html): The Timestamp module is used in Aura to track
37//! consensus rounds (via `slots`).
38
39#![cfg_attr(not(feature = "std"), no_std)]
40
41extern crate alloc;
42
43use alloc::vec::Vec;
44use codec::{Decode, Encode, MaxEncodedLen};
45use frame_support::{
46	traits::{DisabledValidators, FindAuthor, Get, OnTimestampSet, OneSessionHandler},
47	BoundedSlice, BoundedVec, ConsensusEngineId, Parameter,
48};
49use log;
50use sp_consensus_aura::{AuthorityIndex, ConsensusLog, Slot, AURA_ENGINE_ID};
51use sp_runtime::{
52	generic::DigestItem,
53	traits::{IsMember, Member, SaturatedConversion, Saturating, Zero},
54	RuntimeAppPublic,
55};
56
57pub mod migrations;
58mod mock;
59mod tests;
60
61pub use pallet::*;
62
63const LOG_TARGET: &str = "runtime::aura";
64
65/// A slot duration provider which infers the slot duration from the
66/// [`pallet_timestamp::Config::MinimumPeriod`] by multiplying it by two, to ensure
67/// that authors have the majority of their slot to author within.
68///
69/// This was the default behavior of the Aura pallet and may be used for
70/// backwards compatibility.
71pub struct MinimumPeriodTimesTwo<T>(core::marker::PhantomData<T>);
72
73impl<T: pallet_timestamp::Config> Get<T::Moment> for MinimumPeriodTimesTwo<T> {
74	fn get() -> T::Moment {
75		<T as pallet_timestamp::Config>::MinimumPeriod::get().saturating_mul(2u32.into())
76	}
77}
78
79#[frame_support::pallet]
80pub mod pallet {
81	use super::*;
82	use frame_support::pallet_prelude::*;
83	use frame_system::pallet_prelude::*;
84
85	#[pallet::config]
86	pub trait Config: pallet_timestamp::Config + frame_system::Config {
87		/// The identifier type for an authority.
88		type AuthorityId: Member
89			+ Parameter
90			+ RuntimeAppPublic
91			+ MaybeSerializeDeserialize
92			+ MaxEncodedLen;
93		/// The maximum number of authorities that the pallet can hold.
94		type MaxAuthorities: Get<u32>;
95
96		/// A way to check whether a given validator is disabled and should not be authoring blocks.
97		/// Blocks authored by a disabled validator will lead to a panic as part of this module's
98		/// initialization.
99		type DisabledValidators: DisabledValidators;
100
101		/// Whether to allow block authors to create multiple blocks per slot.
102		///
103		/// If this is `true`, the pallet will allow slots to stay the same across sequential
104		/// blocks. If this is `false`, the pallet will require that subsequent blocks always have
105		/// higher slots than previous ones.
106		///
107		/// Regardless of the setting of this storage value, the pallet will always enforce the
108		/// invariant that slots don't move backwards as the chain progresses.
109		///
110		/// The typical value for this should be 'false' unless this pallet is being augmented by
111		/// another pallet which enforces some limitation on the number of blocks authors can create
112		/// using the same slot.
113		type AllowMultipleBlocksPerSlot: Get<bool>;
114
115		/// The slot duration Aura should run with, expressed in milliseconds.
116		///
117		/// The effective value of this type can be changed with a runtime upgrade.
118		///
119		/// For backwards compatibility either use [`MinimumPeriodTimesTwo`] or a const.
120		#[pallet::constant]
121		type SlotDuration: Get<<Self as pallet_timestamp::Config>::Moment>;
122	}
123
124	#[pallet::pallet]
125	pub struct Pallet<T>(core::marker::PhantomData<T>);
126
127	#[pallet::hooks]
128	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
129		fn on_runtime_upgrade() -> Weight {
130			use pallet_timestamp::Pallet as Timestamp;
131
132			let new_slot_duration = T::SlotDuration::get();
133
134			let current_timestamp = Timestamp::<T>::get();
135			let old_slot = CurrentSlot::<T>::get();
136
137			let new_slot = current_timestamp / new_slot_duration;
138			let new_slot = Slot::from(new_slot.saturated_into::<u64>());
139
140			if old_slot != new_slot {
141				CurrentSlot::<T>::put(new_slot);
142				log::info!(
143					target: LOG_TARGET,
144					"Migrated CurrentSlot from {} to {} (timestamp: {:?}, new_slot_duration: {:?})",
145					u64::from(old_slot),
146					u64::from(new_slot),
147					current_timestamp,
148					new_slot_duration
149				);
150				T::DbWeight::get().reads_writes(2, 1)
151			} else {
152				log::debug!(
153					target: LOG_TARGET,
154					"CurrentSlot is already correct ({}), no migration needed",
155					u64::from(old_slot)
156				);
157				T::DbWeight::get().reads(2)
158			}
159		}
160
161		fn integrity_test() {
162			let slot_duration = T::SlotDuration::get();
163			assert!(!slot_duration.is_zero(), "Aura slot duration cannot be zero.");
164		}
165
166		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
167			if let Some(new_slot) = Self::current_slot_from_digests() {
168				let current_slot = CurrentSlot::<T>::get();
169
170				if T::AllowMultipleBlocksPerSlot::get() {
171					assert!(current_slot <= new_slot, "Slot must not decrease");
172				} else {
173					assert!(current_slot < new_slot, "Slot must increase");
174				}
175
176				CurrentSlot::<T>::put(new_slot);
177
178				if let Some(n_authorities) = <Authorities<T>>::decode_len() {
179					let authority_index = *new_slot % n_authorities as u64;
180					if T::DisabledValidators::is_disabled(authority_index as u32) {
181						panic!(
182							"Validator with index {:?} is disabled and should not be attempting to author blocks.",
183							authority_index,
184						);
185					}
186				}
187
188				// TODO [#3398] Generate offence report for all authorities that skipped their
189				// slots.
190
191				T::DbWeight::get().reads_writes(2, 1)
192			} else {
193				T::DbWeight::get().reads(1)
194			}
195		}
196
197		#[cfg(feature = "try-runtime")]
198		fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
199			Self::do_try_state()
200		}
201	}
202
203	/// The current authority set.
204	#[pallet::storage]
205	pub type Authorities<T: Config> =
206		StorageValue<_, BoundedVec<T::AuthorityId, T::MaxAuthorities>, ValueQuery>;
207
208	/// The current slot of this block.
209	///
210	/// This will be set in `on_initialize`.
211	#[pallet::storage]
212	pub type CurrentSlot<T: Config> = StorageValue<_, Slot, ValueQuery>;
213
214	#[pallet::genesis_config]
215	#[derive(frame_support::DefaultNoBound)]
216	pub struct GenesisConfig<T: Config> {
217		pub authorities: Vec<T::AuthorityId>,
218	}
219
220	#[pallet::genesis_build]
221	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
222		fn build(&self) {
223			Pallet::<T>::initialize_authorities(&self.authorities);
224		}
225	}
226}
227
228impl<T: Config> Pallet<T> {
229	/// Change authorities.
230	///
231	/// The storage will be applied immediately.
232	/// And aura consensus log will be appended to block's log.
233	///
234	/// This is a no-op if `new` is empty.
235	pub fn change_authorities(new: BoundedVec<T::AuthorityId, T::MaxAuthorities>) {
236		if new.is_empty() {
237			log::warn!(target: LOG_TARGET, "Ignoring empty authority change.");
238
239			return;
240		}
241
242		<Authorities<T>>::put(&new);
243
244		let log = DigestItem::Consensus(
245			AURA_ENGINE_ID,
246			ConsensusLog::AuthoritiesChange(new.into_inner()).encode(),
247		);
248		<frame_system::Pallet<T>>::deposit_log(log);
249	}
250
251	/// Initial authorities.
252	///
253	/// The storage will be applied immediately.
254	///
255	/// The authorities length must be equal or less than T::MaxAuthorities.
256	pub fn initialize_authorities(authorities: &[T::AuthorityId]) {
257		if !authorities.is_empty() {
258			assert!(<Authorities<T>>::get().is_empty(), "Authorities are already initialized!");
259			let bounded = <BoundedSlice<'_, _, T::MaxAuthorities>>::try_from(authorities)
260				.expect("Initial authority set must be less than T::MaxAuthorities");
261			<Authorities<T>>::put(bounded);
262		}
263	}
264
265	/// Return current authorities length.
266	pub fn authorities_len() -> usize {
267		Authorities::<T>::decode_len().unwrap_or(0)
268	}
269
270	/// Map a slot to an author index in the current authority set, or `None` if the set is
271	/// empty. The set size is read from [`Authorities`].
272	pub fn slot_author_index(slot: Slot) -> Option<u32> {
273		let authorities_count = Self::authorities_len();
274		if authorities_count == 0 {
275			return None;
276		}
277		Some((u64::from(slot) % authorities_count as u64) as u32)
278	}
279
280	/// Get the current slot from the pre-runtime digests.
281	fn current_slot_from_digests() -> Option<Slot> {
282		let digest = frame_system::Pallet::<T>::digest();
283		let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
284		for (id, mut data) in pre_runtime_digests {
285			if id == AURA_ENGINE_ID {
286				return Slot::decode(&mut data).ok();
287			}
288		}
289
290		None
291	}
292
293	/// Determine the Aura slot-duration based on the Timestamp module configuration.
294	pub fn slot_duration() -> T::Moment {
295		T::SlotDuration::get()
296	}
297
298	/// Ensure the correctness of the state of this pallet.
299	///
300	/// This should be valid before or after each state transition of this pallet.
301	///
302	/// # Invariants
303	///
304	/// ## `CurrentSlot`
305	///
306	/// If we don't allow for multiple blocks per slot, then the current slot must be less than the
307	/// maximal slot number. Otherwise, it can be arbitrary.
308	///
309	/// ## `Authorities`
310	///
311	/// * The authorities must be non-empty.
312	/// * The current authority cannot be disabled.
313	/// * The number of authorities must be less than or equal to `T::MaxAuthorities`. This however,
314	///   is guarded by the type system.
315	///
316	/// ## Timestamp Consistency
317	///
318	/// The timestamp divided by the slot duration must equal the current slot (after genesis).
319	#[cfg(any(test, feature = "try-runtime"))]
320	pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
321		// We don't have any guarantee that we are already after `on_initialize` and thus we have to
322		// check the current slot from the digest or take the last known slot.
323		let current_slot =
324			Self::current_slot_from_digests().unwrap_or_else(|| CurrentSlot::<T>::get());
325
326		// Check that the current slot is less than the maximal slot number, unless we allow for
327		// multiple blocks per slot.
328		if !T::AllowMultipleBlocksPerSlot::get() {
329			frame_support::ensure!(
330				current_slot < u64::MAX,
331				"Current slot has reached maximum value and cannot be incremented further.",
332			);
333		}
334
335		let authorities_len =
336			<Authorities<T>>::decode_len().ok_or("Failed to decode authorities length")?;
337
338		// Check that the authorities are non-empty.
339		frame_support::ensure!(!authorities_len.is_zero(), "Authorities must be non-empty.");
340
341		// Check that the current authority is not disabled.
342		let authority_index =
343			Self::slot_author_index(current_slot).ok_or("Authorities must be non-empty.")?;
344		frame_support::ensure!(
345			!T::DisabledValidators::is_disabled(authority_index),
346			"Current validator is disabled and should not be attempting to author blocks.",
347		);
348
349		// Check that the timestamp is consistent with the current slot.
350		let timestamp = pallet_timestamp::Pallet::<T>::get();
351
352		if !timestamp.is_zero() {
353			let slot_duration = Self::slot_duration();
354
355			let timestamp_slot = Slot::from((timestamp / slot_duration).saturated_into::<u64>());
356			frame_support::ensure!(
357				current_slot == timestamp_slot,
358				"Timestamp slot must match CurrentSlot.",
359			);
360		}
361
362		Ok(())
363	}
364}
365
366impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
367	type Public = T::AuthorityId;
368}
369
370impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
371	type Key = T::AuthorityId;
372
373	fn on_genesis_session<'a, I: 'a>(validators: I)
374	where
375		I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
376	{
377		let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
378		Self::initialize_authorities(&authorities);
379	}
380
381	fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I)
382	where
383		I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
384	{
385		// instant changes
386		if changed {
387			let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
388			let last_authorities = Authorities::<T>::get();
389			if last_authorities != next_authorities {
390				if next_authorities.len() as u32 > T::MaxAuthorities::get() {
391					log::warn!(
392						target: LOG_TARGET,
393						"next authorities list larger than {}, truncating",
394						T::MaxAuthorities::get(),
395					);
396				}
397				let bounded = <BoundedVec<_, T::MaxAuthorities>>::truncate_from(next_authorities);
398				Self::change_authorities(bounded);
399			}
400		}
401	}
402
403	fn on_disabled(i: u32) {
404		let log = DigestItem::Consensus(
405			AURA_ENGINE_ID,
406			ConsensusLog::<T::AuthorityId>::OnDisabled(i as AuthorityIndex).encode(),
407		);
408
409		<frame_system::Pallet<T>>::deposit_log(log);
410	}
411}
412
413impl<T: Config> FindAuthor<u32> for Pallet<T> {
414	fn find_author<'a, I>(digests: I) -> Option<u32>
415	where
416		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
417	{
418		for (id, mut data) in digests.into_iter() {
419			if id == AURA_ENGINE_ID {
420				let slot = Slot::decode(&mut data).ok()?;
421				return Self::slot_author_index(slot);
422			}
423		}
424
425		None
426	}
427}
428
429/// We can not implement `FindAuthor` twice, because the compiler does not know if
430/// `u32 == T::AuthorityId` and thus, prevents us to implement the trait twice.
431#[doc(hidden)]
432pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
433
434impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::AuthorityId>
435	for FindAccountFromAuthorIndex<T, Inner>
436{
437	fn find_author<'a, I>(digests: I) -> Option<T::AuthorityId>
438	where
439		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
440	{
441		let i = Inner::find_author(digests)?;
442
443		let validators = Authorities::<T>::get();
444		validators.get(i as usize).cloned()
445	}
446}
447
448/// Find the authority ID of the Aura authority who authored the current block.
449pub type AuraAuthorId<T> = FindAccountFromAuthorIndex<T, Pallet<T>>;
450
451impl<T: Config> IsMember<T::AuthorityId> for Pallet<T> {
452	fn is_member(authority_id: &T::AuthorityId) -> bool {
453		Authorities::<T>::get().iter().any(|id| id == authority_id)
454	}
455}
456
457impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T> {
458	fn on_timestamp_set(moment: T::Moment) {
459		let slot_duration = Self::slot_duration();
460		assert!(!slot_duration.is_zero(), "Aura slot duration cannot be zero.");
461
462		let timestamp_slot = moment / slot_duration;
463		let timestamp_slot = Slot::from(timestamp_slot.saturated_into::<u64>());
464
465		assert_eq!(
466			CurrentSlot::<T>::get(),
467			timestamp_slot,
468			"Timestamp slot must match `CurrentSlot`. This likely means that the configured block \
469			time in the node and/or rest of the runtime is not compatible with Aura's \
470			`SlotDuration`",
471		);
472	}
473}