1#![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
65pub 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 type AuthorityId: Member
89 + Parameter
90 + RuntimeAppPublic
91 + MaybeSerializeDeserialize
92 + MaxEncodedLen;
93 type MaxAuthorities: Get<u32>;
95
96 type DisabledValidators: DisabledValidators;
100
101 type AllowMultipleBlocksPerSlot: Get<bool>;
114
115 #[pallet::constant]
120 type SlotDuration: Get<<Self as pallet_timestamp::Config>::Moment>;
121 }
122
123 #[pallet::pallet]
124 pub struct Pallet<T>(core::marker::PhantomData<T>);
125
126 #[pallet::hooks]
127 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
128 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
129 if let Some(new_slot) = Self::current_slot_from_digests() {
130 let current_slot = CurrentSlot::<T>::get();
131
132 if T::AllowMultipleBlocksPerSlot::get() {
133 assert!(current_slot <= new_slot, "Slot must not decrease");
134 } else {
135 assert!(current_slot < new_slot, "Slot must increase");
136 }
137
138 CurrentSlot::<T>::put(new_slot);
139
140 if let Some(n_authorities) = <Authorities<T>>::decode_len() {
141 let authority_index = *new_slot % n_authorities as u64;
142 if T::DisabledValidators::is_disabled(authority_index as u32) {
143 panic!(
144 "Validator with index {:?} is disabled and should not be attempting to author blocks.",
145 authority_index,
146 );
147 }
148 }
149
150 T::DbWeight::get().reads_writes(2, 1)
154 } else {
155 T::DbWeight::get().reads(1)
156 }
157 }
158
159 #[cfg(feature = "try-runtime")]
160 fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
161 Self::do_try_state()
162 }
163 }
164
165 #[pallet::storage]
167 pub type Authorities<T: Config> =
168 StorageValue<_, BoundedVec<T::AuthorityId, T::MaxAuthorities>, ValueQuery>;
169
170 #[pallet::storage]
174 pub type CurrentSlot<T: Config> = StorageValue<_, Slot, ValueQuery>;
175
176 #[pallet::genesis_config]
177 #[derive(frame_support::DefaultNoBound)]
178 pub struct GenesisConfig<T: Config> {
179 pub authorities: Vec<T::AuthorityId>,
180 }
181
182 #[pallet::genesis_build]
183 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
184 fn build(&self) {
185 Pallet::<T>::initialize_authorities(&self.authorities);
186 }
187 }
188}
189
190impl<T: Config> Pallet<T> {
191 pub fn change_authorities(new: BoundedVec<T::AuthorityId, T::MaxAuthorities>) {
198 if new.is_empty() {
199 log::warn!(target: LOG_TARGET, "Ignoring empty authority change.");
200
201 return
202 }
203
204 <Authorities<T>>::put(&new);
205
206 let log = DigestItem::Consensus(
207 AURA_ENGINE_ID,
208 ConsensusLog::AuthoritiesChange(new.into_inner()).encode(),
209 );
210 <frame_system::Pallet<T>>::deposit_log(log);
211 }
212
213 pub fn initialize_authorities(authorities: &[T::AuthorityId]) {
219 if !authorities.is_empty() {
220 assert!(<Authorities<T>>::get().is_empty(), "Authorities are already initialized!");
221 let bounded = <BoundedSlice<'_, _, T::MaxAuthorities>>::try_from(authorities)
222 .expect("Initial authority set must be less than T::MaxAuthorities");
223 <Authorities<T>>::put(bounded);
224 }
225 }
226
227 pub fn authorities_len() -> usize {
229 Authorities::<T>::decode_len().unwrap_or(0)
230 }
231
232 fn current_slot_from_digests() -> Option<Slot> {
234 let digest = frame_system::Pallet::<T>::digest();
235 let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
236 for (id, mut data) in pre_runtime_digests {
237 if id == AURA_ENGINE_ID {
238 return Slot::decode(&mut data).ok()
239 }
240 }
241
242 None
243 }
244
245 pub fn slot_duration() -> T::Moment {
247 T::SlotDuration::get()
248 }
249
250 #[cfg(any(test, feature = "try-runtime"))]
272 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
273 let current_slot =
276 Self::current_slot_from_digests().unwrap_or_else(|| CurrentSlot::<T>::get());
277
278 if !T::AllowMultipleBlocksPerSlot::get() {
281 frame_support::ensure!(
282 current_slot < u64::MAX,
283 "Current slot has reached maximum value and cannot be incremented further.",
284 );
285 }
286
287 let authorities_len =
288 <Authorities<T>>::decode_len().ok_or("Failed to decode authorities length")?;
289
290 frame_support::ensure!(!authorities_len.is_zero(), "Authorities must be non-empty.");
292
293 let authority_index = *current_slot % authorities_len as u64;
295 frame_support::ensure!(
296 !T::DisabledValidators::is_disabled(authority_index as u32),
297 "Current validator is disabled and should not be attempting to author blocks.",
298 );
299
300 let timestamp = pallet_timestamp::Pallet::<T>::get();
302
303 if !timestamp.is_zero() {
304 let slot_duration = Self::slot_duration();
305
306 let timestamp_slot = Slot::from((timestamp / slot_duration).saturated_into::<u64>());
307 frame_support::ensure!(
308 current_slot == timestamp_slot,
309 "Timestamp slot must match CurrentSlot.",
310 );
311 }
312
313 Ok(())
314 }
315}
316
317impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
318 type Public = T::AuthorityId;
319}
320
321impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
322 type Key = T::AuthorityId;
323
324 fn on_genesis_session<'a, I: 'a>(validators: I)
325 where
326 I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
327 {
328 let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
329 Self::initialize_authorities(&authorities);
330 }
331
332 fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I)
333 where
334 I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
335 {
336 if changed {
338 let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
339 let last_authorities = Authorities::<T>::get();
340 if last_authorities != next_authorities {
341 if next_authorities.len() as u32 > T::MaxAuthorities::get() {
342 log::warn!(
343 target: LOG_TARGET,
344 "next authorities list larger than {}, truncating",
345 T::MaxAuthorities::get(),
346 );
347 }
348 let bounded = <BoundedVec<_, T::MaxAuthorities>>::truncate_from(next_authorities);
349 Self::change_authorities(bounded);
350 }
351 }
352 }
353
354 fn on_disabled(i: u32) {
355 let log = DigestItem::Consensus(
356 AURA_ENGINE_ID,
357 ConsensusLog::<T::AuthorityId>::OnDisabled(i as AuthorityIndex).encode(),
358 );
359
360 <frame_system::Pallet<T>>::deposit_log(log);
361 }
362}
363
364impl<T: Config> FindAuthor<u32> for Pallet<T> {
365 fn find_author<'a, I>(digests: I) -> Option<u32>
366 where
367 I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
368 {
369 for (id, mut data) in digests.into_iter() {
370 if id == AURA_ENGINE_ID {
371 let slot = Slot::decode(&mut data).ok()?;
372 let author_index = *slot % Self::authorities_len() as u64;
373 return Some(author_index as u32)
374 }
375 }
376
377 None
378 }
379}
380
381#[doc(hidden)]
384pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
385
386impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::AuthorityId>
387 for FindAccountFromAuthorIndex<T, Inner>
388{
389 fn find_author<'a, I>(digests: I) -> Option<T::AuthorityId>
390 where
391 I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
392 {
393 let i = Inner::find_author(digests)?;
394
395 let validators = Authorities::<T>::get();
396 validators.get(i as usize).cloned()
397 }
398}
399
400pub type AuraAuthorId<T> = FindAccountFromAuthorIndex<T, Pallet<T>>;
402
403impl<T: Config> IsMember<T::AuthorityId> for Pallet<T> {
404 fn is_member(authority_id: &T::AuthorityId) -> bool {
405 Authorities::<T>::get().iter().any(|id| id == authority_id)
406 }
407}
408
409impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T> {
410 fn on_timestamp_set(moment: T::Moment) {
411 let slot_duration = Self::slot_duration();
412 assert!(!slot_duration.is_zero(), "Aura slot duration cannot be zero.");
413
414 let timestamp_slot = moment / slot_duration;
415 let timestamp_slot = Slot::from(timestamp_slot.saturated_into::<u64>());
416
417 assert_eq!(
418 CurrentSlot::<T>::get(),
419 timestamp_slot,
420 "Timestamp slot must match `CurrentSlot`. This likely means that the configured block \
421 time in the node and/or rest of the runtime is not compatible with Aura's \
422 `SlotDuration`",
423 );
424 }
425}