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]
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 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 #[pallet::storage]
205 pub type Authorities<T: Config> =
206 StorageValue<_, BoundedVec<T::AuthorityId, T::MaxAuthorities>, ValueQuery>;
207
208 #[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 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 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 pub fn authorities_len() -> usize {
267 Authorities::<T>::decode_len().unwrap_or(0)
268 }
269
270 fn current_slot_from_digests() -> Option<Slot> {
272 let digest = frame_system::Pallet::<T>::digest();
273 let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
274 for (id, mut data) in pre_runtime_digests {
275 if id == AURA_ENGINE_ID {
276 return Slot::decode(&mut data).ok();
277 }
278 }
279
280 None
281 }
282
283 pub fn slot_duration() -> T::Moment {
285 T::SlotDuration::get()
286 }
287
288 #[cfg(any(test, feature = "try-runtime"))]
310 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
311 let current_slot =
314 Self::current_slot_from_digests().unwrap_or_else(|| CurrentSlot::<T>::get());
315
316 if !T::AllowMultipleBlocksPerSlot::get() {
319 frame_support::ensure!(
320 current_slot < u64::MAX,
321 "Current slot has reached maximum value and cannot be incremented further.",
322 );
323 }
324
325 let authorities_len =
326 <Authorities<T>>::decode_len().ok_or("Failed to decode authorities length")?;
327
328 frame_support::ensure!(!authorities_len.is_zero(), "Authorities must be non-empty.");
330
331 let authority_index = *current_slot % authorities_len as u64;
333 frame_support::ensure!(
334 !T::DisabledValidators::is_disabled(authority_index as u32),
335 "Current validator is disabled and should not be attempting to author blocks.",
336 );
337
338 let timestamp = pallet_timestamp::Pallet::<T>::get();
340
341 if !timestamp.is_zero() {
342 let slot_duration = Self::slot_duration();
343
344 let timestamp_slot = Slot::from((timestamp / slot_duration).saturated_into::<u64>());
345 frame_support::ensure!(
346 current_slot == timestamp_slot,
347 "Timestamp slot must match CurrentSlot.",
348 );
349 }
350
351 Ok(())
352 }
353}
354
355impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
356 type Public = T::AuthorityId;
357}
358
359impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
360 type Key = T::AuthorityId;
361
362 fn on_genesis_session<'a, I: 'a>(validators: I)
363 where
364 I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
365 {
366 let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
367 Self::initialize_authorities(&authorities);
368 }
369
370 fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I)
371 where
372 I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
373 {
374 if changed {
376 let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
377 let last_authorities = Authorities::<T>::get();
378 if last_authorities != next_authorities {
379 if next_authorities.len() as u32 > T::MaxAuthorities::get() {
380 log::warn!(
381 target: LOG_TARGET,
382 "next authorities list larger than {}, truncating",
383 T::MaxAuthorities::get(),
384 );
385 }
386 let bounded = <BoundedVec<_, T::MaxAuthorities>>::truncate_from(next_authorities);
387 Self::change_authorities(bounded);
388 }
389 }
390 }
391
392 fn on_disabled(i: u32) {
393 let log = DigestItem::Consensus(
394 AURA_ENGINE_ID,
395 ConsensusLog::<T::AuthorityId>::OnDisabled(i as AuthorityIndex).encode(),
396 );
397
398 <frame_system::Pallet<T>>::deposit_log(log);
399 }
400}
401
402impl<T: Config> FindAuthor<u32> for Pallet<T> {
403 fn find_author<'a, I>(digests: I) -> Option<u32>
404 where
405 I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
406 {
407 for (id, mut data) in digests.into_iter() {
408 if id == AURA_ENGINE_ID {
409 let slot = Slot::decode(&mut data).ok()?;
410 let author_index = *slot % Self::authorities_len() as u64;
411 return Some(author_index as u32);
412 }
413 }
414
415 None
416 }
417}
418
419#[doc(hidden)]
422pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
423
424impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::AuthorityId>
425 for FindAccountFromAuthorIndex<T, Inner>
426{
427 fn find_author<'a, I>(digests: I) -> Option<T::AuthorityId>
428 where
429 I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
430 {
431 let i = Inner::find_author(digests)?;
432
433 let validators = Authorities::<T>::get();
434 validators.get(i as usize).cloned()
435 }
436}
437
438pub type AuraAuthorId<T> = FindAccountFromAuthorIndex<T, Pallet<T>>;
440
441impl<T: Config> IsMember<T::AuthorityId> for Pallet<T> {
442 fn is_member(authority_id: &T::AuthorityId) -> bool {
443 Authorities::<T>::get().iter().any(|id| id == authority_id)
444 }
445}
446
447impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T> {
448 fn on_timestamp_set(moment: T::Moment) {
449 let slot_duration = Self::slot_duration();
450 assert!(!slot_duration.is_zero(), "Aura slot duration cannot be zero.");
451
452 let timestamp_slot = moment / slot_duration;
453 let timestamp_slot = Slot::from(timestamp_slot.saturated_into::<u64>());
454
455 assert_eq!(
456 CurrentSlot::<T>::get(),
457 timestamp_slot,
458 "Timestamp slot must match `CurrentSlot`. This likely means that the configured block \
459 time in the node and/or rest of the runtime is not compatible with Aura's \
460 `SlotDuration`",
461 );
462 }
463}