referrerpolicy=no-referrer-when-downgrade

pallet_sassafras/
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//! Extension module for Sassafras consensus.
19//!
20//! [Sassafras](https://research.web3.foundation/Polkadot/protocols/block-production/SASSAFRAS)
21//! is a constant-time block production protocol that aims to ensure that there is
22//! exactly one block produced with constant time intervals rather than multiple or none.
23//!
24//! We run a lottery to distribute block production slots in an epoch and to fix the
25//! order validators produce blocks in, by the beginning of an epoch.
26//!
27//! Each validator signs the same VRF input and publishes the output on-chain. This
28//! value is their lottery ticket that can be validated against their public key.
29//!
30//! We want to keep lottery winners secret, i.e. do not publish their public keys.
31//! At the beginning of the epoch all the validators tickets are published but not
32//! their public keys.
33//!
34//! A valid tickets is validated when an honest validator reclaims it on block
35//! production.
36//!
37//! To prevent submission of fake tickets, resulting in empty slots, the validator
38//! when submitting the ticket accompanies it with a SNARK of the statement: "Here's
39//! my VRF output that has been generated using the given VRF input and my secret
40//! key. I'm not telling you my keys, but my public key is among those of the
41//! nominated validators", that is validated before the lottery.
42//!
43//! To anonymously publish the ticket to the chain a validator sends their tickets
44//! to a random validator who later puts it on-chain as a transaction.
45
46#![deny(warnings)]
47#![warn(unused_must_use, unsafe_code, unused_variables, unused_imports, missing_docs)]
48#![cfg_attr(not(feature = "std"), no_std)]
49
50extern crate alloc;
51
52use codec::{Decode, Encode, MaxEncodedLen};
53use log::{debug, error, trace, warn};
54use scale_info::TypeInfo;
55
56use alloc::vec::Vec;
57use frame_support::{
58	dispatch::{DispatchResultWithPostInfo, Pays},
59	traits::{Defensive, Get},
60	weights::Weight,
61	BoundedVec, WeakBoundedVec,
62};
63use frame_system::{
64	offchain::{CreateBare, SubmitTransaction},
65	pallet_prelude::BlockNumberFor,
66};
67use sp_consensus_sassafras::{
68	digests::{ConsensusLog, NextEpochDescriptor, SlotClaim},
69	vrf, AuthorityId, Epoch, EpochConfiguration, Randomness, Slot, TicketBody, TicketEnvelope,
70	TicketId, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID,
71};
72use sp_io::hashing;
73use sp_runtime::{
74	generic::DigestItem,
75	traits::{One, Zero},
76	BoundToRuntimeAppPublic,
77};
78
79#[cfg(feature = "runtime-benchmarks")]
80mod benchmarking;
81#[cfg(all(feature = "std", test))]
82mod mock;
83#[cfg(all(feature = "std", test))]
84mod tests;
85
86pub mod weights;
87pub use weights::WeightInfo;
88
89pub use pallet::*;
90
91const LOG_TARGET: &str = "sassafras::runtime";
92
93// Max length for segments holding unsorted tickets.
94const SEGMENT_MAX_SIZE: u32 = 128;
95
96/// Authorities bounded vector convenience type.
97pub type AuthoritiesVec<T> = WeakBoundedVec<AuthorityId, <T as Config>::MaxAuthorities>;
98
99/// Epoch length defined by the configuration.
100pub type EpochLengthFor<T> = <T as Config>::EpochLength;
101
102/// Tickets metadata.
103#[derive(Debug, Default, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, Copy)]
104pub struct TicketsMetadata {
105	/// Number of outstanding next epoch tickets requiring to be sorted.
106	///
107	/// These tickets are held by the [`UnsortedSegments`] storage map in segments
108	/// containing at most `SEGMENT_MAX_SIZE` items.
109	pub unsorted_tickets_count: u32,
110
111	/// Number of tickets available for current and next epoch.
112	///
113	/// These tickets are held by the [`TicketsIds`] storage map.
114	///
115	/// The array entry to be used for the current epoch is computed as epoch index modulo 2.
116	pub tickets_count: [u32; 2],
117}
118
119#[frame_support::pallet]
120pub mod pallet {
121	use super::*;
122	use frame_support::pallet_prelude::*;
123	use frame_system::pallet_prelude::*;
124
125	/// The Sassafras pallet.
126	#[pallet::pallet]
127	pub struct Pallet<T>(_);
128
129	/// Configuration parameters.
130	#[pallet::config]
131	pub trait Config: frame_system::Config + CreateBare<Call<Self>> {
132		/// Amount of slots that each epoch should last.
133		#[pallet::constant]
134		type EpochLength: Get<u32>;
135
136		/// Max number of authorities allowed.
137		#[pallet::constant]
138		type MaxAuthorities: Get<u32>;
139
140		/// Epoch change trigger.
141		///
142		/// Logic to be triggered on every block to query for whether an epoch has ended
143		/// and to perform the transition to the next epoch.
144		type EpochChangeTrigger: EpochChangeTrigger;
145
146		/// Weight information for all calls of this pallet.
147		type WeightInfo: WeightInfo;
148	}
149
150	/// Sassafras runtime errors.
151	#[pallet::error]
152	pub enum Error<T> {
153		/// Submitted configuration is invalid.
154		InvalidConfiguration,
155	}
156
157	/// Current epoch index.
158	#[pallet::storage]
159	pub type EpochIndex<T> = StorageValue<_, u64, ValueQuery>;
160
161	/// Current epoch authorities.
162	#[pallet::storage]
163	pub type Authorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
164
165	/// Next epoch authorities.
166	#[pallet::storage]
167	pub type NextAuthorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
168
169	/// First block slot number.
170	///
171	/// As the slots may not be zero-based, we record the slot value for the fist block.
172	/// This allows to always compute relative indices for epochs and slots.
173	#[pallet::storage]
174	pub type GenesisSlot<T> = StorageValue<_, Slot, ValueQuery>;
175
176	/// Current block slot number.
177	#[pallet::storage]
178	pub type CurrentSlot<T> = StorageValue<_, Slot, ValueQuery>;
179
180	/// Current epoch randomness.
181	#[pallet::storage]
182	pub type CurrentRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
183
184	/// Next epoch randomness.
185	#[pallet::storage]
186	pub type NextRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
187
188	/// Randomness accumulator.
189	///
190	/// Excluded the first imported block, its value is updated on block finalization.
191	#[pallet::storage]
192	pub(crate) type RandomnessAccumulator<T> = StorageValue<_, Randomness, ValueQuery>;
193
194	/// Per slot randomness used to feed the randomness accumulator.
195	///
196	/// The value is ephemeral and is cleared on block finalization.
197	#[pallet::storage]
198	pub(crate) type SlotRandomness<T> = StorageValue<_, Randomness>;
199
200	/// The configuration for the current epoch.
201	#[pallet::storage]
202	pub type EpochConfig<T> = StorageValue<_, EpochConfiguration, ValueQuery>;
203
204	/// The configuration for the next epoch.
205	#[pallet::storage]
206	pub type NextEpochConfig<T> = StorageValue<_, EpochConfiguration>;
207
208	/// Pending epoch configuration change that will be set as `NextEpochConfig` when the next
209	/// epoch is enacted.
210	///
211	/// In other words, a configuration change submitted during epoch N will be enacted on epoch
212	/// N+2. This is to maintain coherence for already submitted tickets for epoch N+1 that where
213	/// computed using configuration parameters stored for epoch N+1.
214	#[pallet::storage]
215	pub type PendingEpochConfigChange<T> = StorageValue<_, EpochConfiguration>;
216
217	/// Stored tickets metadata.
218	#[pallet::storage]
219	pub type TicketsMeta<T> = StorageValue<_, TicketsMetadata, ValueQuery>;
220
221	/// Tickets identifiers map.
222	///
223	/// The map holds tickets ids for the current and next epoch.
224	///
225	/// The key is a tuple composed by:
226	/// - `u8` equal to epoch's index modulo 2;
227	/// - `u32` equal to the ticket's index in a sorted list of epoch's tickets.
228	///
229	/// Epoch X first N-th ticket has key (X mod 2, N)
230	///
231	/// Note that the ticket's index doesn't directly correspond to the slot index within the epoch.
232	/// The assignment is computed dynamically using an *outside-in* strategy.
233	///
234	/// Be aware that entries within this map are never removed, only overwritten.
235	/// Last element index should be fetched from the [`TicketsMeta`] value.
236	#[pallet::storage]
237	pub type TicketsIds<T> = StorageMap<_, Identity, (u8, u32), TicketId>;
238
239	/// Tickets to be used for current and next epoch.
240	#[pallet::storage]
241	pub type TicketsData<T> = StorageMap<_, Identity, TicketId, TicketBody>;
242
243	/// Next epoch tickets unsorted segments.
244	///
245	/// Contains lists of tickets where each list represents a batch of tickets
246	/// received via the `submit_tickets` extrinsic.
247	///
248	/// Each segment has max length [`SEGMENT_MAX_SIZE`].
249	#[pallet::storage]
250	pub type UnsortedSegments<T: Config> =
251		StorageMap<_, Identity, u32, BoundedVec<TicketId, ConstU32<SEGMENT_MAX_SIZE>>, ValueQuery>;
252
253	/// The most recently set of tickets which are candidates to become the next
254	/// epoch tickets.
255	#[pallet::storage]
256	pub type SortedCandidates<T> =
257		StorageValue<_, BoundedVec<TicketId, EpochLengthFor<T>>, ValueQuery>;
258
259	/// Parameters used to construct the epoch's ring verifier.
260	///
261	/// In practice: Updatable Universal Reference String and the seed.
262	#[pallet::storage]
263	pub type RingContext<T: Config> = StorageValue<_, vrf::RingContext>;
264
265	/// Ring verifier data for the current epoch.
266	#[pallet::storage]
267	pub type RingVerifierData<T: Config> = StorageValue<_, vrf::RingVerifierKey>;
268
269	/// Genesis configuration for Sassafras protocol.
270	#[pallet::genesis_config]
271	#[derive(frame_support::DefaultNoBound)]
272	pub struct GenesisConfig<T: Config> {
273		/// Genesis authorities.
274		pub authorities: Vec<AuthorityId>,
275		/// Genesis epoch configuration.
276		pub epoch_config: EpochConfiguration,
277		/// Phantom config
278		#[serde(skip)]
279		pub _phantom: core::marker::PhantomData<T>,
280	}
281
282	#[pallet::genesis_build]
283	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
284		fn build(&self) {
285			EpochConfig::<T>::put(self.epoch_config);
286			Pallet::<T>::genesis_authorities_initialize(&self.authorities);
287
288			#[cfg(feature = "construct-dummy-ring-context")]
289			{
290				debug!(target: LOG_TARGET, "Constructing dummy ring context");
291				let ring_ctx = vrf::RingContext::new_testing();
292				RingContext::<T>::put(ring_ctx);
293				Pallet::<T>::update_ring_verifier(&self.authorities);
294			}
295		}
296	}
297
298	#[pallet::hooks]
299	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
300		fn on_initialize(block_num: BlockNumberFor<T>) -> Weight {
301			debug_assert_eq!(block_num, frame_system::Pallet::<T>::block_number());
302
303			let claim = <frame_system::Pallet<T>>::digest()
304				.logs
305				.iter()
306				.find_map(|item| item.pre_runtime_try_to::<SlotClaim>(&SASSAFRAS_ENGINE_ID))
307				.expect("Valid block must have a slot claim. qed");
308
309			CurrentSlot::<T>::put(claim.slot);
310
311			if block_num == One::one() {
312				Self::post_genesis_initialize(claim.slot);
313			}
314
315			let randomness = claim.vrf_signature.pre_output.make_bytes();
316			SlotRandomness::<T>::put(randomness);
317
318			let trigger_weight = T::EpochChangeTrigger::trigger::<T>(block_num);
319
320			T::WeightInfo::on_initialize() + trigger_weight
321		}
322
323		fn on_finalize(_: BlockNumberFor<T>) {
324			// At the end of the block, we can safely include the current slot randomness
325			// to the accumulator. If we've determined that this block was the first in
326			// a new epoch, the changeover logic has already occurred at this point
327			// (i.e. `enact_epoch_change` has already been called).
328			let randomness = SlotRandomness::<T>::take()
329				.expect("Unconditionally populated in `on_initialize`; `on_finalize` is always called after; qed");
330			Self::deposit_slot_randomness(&randomness);
331
332			// Check if we are in the epoch's second half.
333			// If so, start sorting the next epoch tickets.
334			let epoch_length = T::EpochLength::get();
335			let current_slot_idx = Self::current_slot_index();
336			if current_slot_idx >= epoch_length / 2 {
337				let mut metadata = TicketsMeta::<T>::get();
338				if metadata.unsorted_tickets_count != 0 {
339					let next_epoch_idx = EpochIndex::<T>::get() + 1;
340					let next_epoch_tag = (next_epoch_idx & 1) as u8;
341					let slots_left = epoch_length.checked_sub(current_slot_idx).unwrap_or(1);
342					Self::sort_segments(
343						metadata
344							.unsorted_tickets_count
345							.div_ceil(SEGMENT_MAX_SIZE * slots_left as u32),
346						next_epoch_tag,
347						&mut metadata,
348					);
349					TicketsMeta::<T>::set(metadata);
350				}
351			}
352		}
353	}
354
355	#[pallet::call]
356	impl<T: Config> Pallet<T> {
357		/// Submit next epoch tickets candidates.
358		///
359		/// The number of tickets allowed to be submitted in one call is equal to the epoch length.
360		#[pallet::call_index(0)]
361		#[pallet::weight(T::WeightInfo::submit_tickets(tickets.len() as u32))]
362		pub fn submit_tickets(
363			origin: OriginFor<T>,
364			tickets: BoundedVec<TicketEnvelope, EpochLengthFor<T>>,
365		) -> DispatchResultWithPostInfo {
366			ensure_none(origin)?;
367
368			debug!(target: LOG_TARGET, "Received {} tickets", tickets.len());
369
370			let epoch_length = T::EpochLength::get();
371			let current_slot_idx = Self::current_slot_index();
372			if current_slot_idx > epoch_length / 2 {
373				warn!(target: LOG_TARGET, "Tickets shall be submitted in the first epoch half",);
374				return Err("Tickets shall be submitted in the first epoch half".into());
375			}
376
377			let Some(verifier) =
378				RingVerifierData::<T>::get().map(|vk| vrf::RingContext::verifier_no_context(vk))
379			else {
380				warn!(target: LOG_TARGET, "Ring verifier key not initialized");
381				return Err("Ring verifier key not initialized".into());
382			};
383
384			let next_authorities = NextAuthorities::<T>::get();
385
386			// Compute tickets threshold
387			let next_config =
388				NextEpochConfig::<T>::get().unwrap_or_else(|| EpochConfig::<T>::get());
389			let ticket_threshold = sp_consensus_sassafras::ticket_id_threshold(
390				next_config.redundancy_factor,
391				epoch_length as u32,
392				next_config.attempts_number,
393				next_authorities.len() as u32,
394			);
395
396			// Get next epoch params
397			let randomness = NextRandomness::<T>::get();
398			let epoch_idx = EpochIndex::<T>::get() + 1;
399
400			let mut valid_tickets = BoundedVec::with_bounded_capacity(tickets.len());
401
402			for ticket in tickets {
403				debug!(target: LOG_TARGET, "Checking ring proof");
404
405				// Check threshold constraint
406				let ticket_id = vrf::make_ticket_id(&ticket.signature.pre_output);
407				if ticket_id >= ticket_threshold {
408					debug!(target: LOG_TARGET, "Ignoring ticket over threshold ({:032x} >= {:032x})", ticket_id, ticket_threshold);
409					continue;
410				}
411
412				// Check for duplicates
413				if TicketsData::<T>::contains_key(ticket_id) {
414					debug!(target: LOG_TARGET, "Ignoring duplicate ticket ({:032x})", ticket_id);
415					continue;
416				}
417
418				// Check ring signature
419				let ticket_id_input =
420					vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx);
421				let sign_data = vrf::ticket_body_sign_data(&ticket.body, ticket_id_input);
422				if !ticket.signature.ring_vrf_verify(&sign_data, &verifier) {
423					debug!(target: LOG_TARGET, "Proof verification failure for ticket ({:032x})", ticket_id);
424					continue;
425				}
426
427				if let Ok(_) = valid_tickets.try_push(ticket_id).defensive_proof(
428					"Input segment has same length as bounded destination vector; qed",
429				) {
430					TicketsData::<T>::set(ticket_id, Some(ticket.body));
431				}
432			}
433
434			if !valid_tickets.is_empty() {
435				Self::append_tickets(valid_tickets);
436			}
437
438			Ok(Pays::No.into())
439		}
440
441		/// Plan an epoch configuration change.
442		///
443		/// The epoch configuration change is recorded and will be announced at the beginning
444		/// of the next epoch together with next epoch authorities information.
445		/// In other words, the configuration will be enacted one epoch later.
446		///
447		/// Multiple calls to this method will replace any existing planned config change
448		/// that has not been enacted yet.
449		#[pallet::call_index(1)]
450		#[pallet::weight(T::WeightInfo::plan_config_change())]
451		pub fn plan_config_change(
452			origin: OriginFor<T>,
453			config: EpochConfiguration,
454		) -> DispatchResult {
455			ensure_root(origin)?;
456
457			ensure!(
458				config.redundancy_factor != 0 && config.attempts_number != 0,
459				Error::<T>::InvalidConfiguration
460			);
461			PendingEpochConfigChange::<T>::put(config);
462			Ok(())
463		}
464	}
465
466	#[allow(deprecated)]
467	#[pallet::validate_unsigned]
468	impl<T: Config> ValidateUnsigned for Pallet<T> {
469		type Call = Call<T>;
470
471		fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
472			let Call::submit_tickets { tickets } = call else {
473				return InvalidTransaction::Call.into();
474			};
475
476			// Discard tickets not coming from the local node or that are not included in a block
477			if source == TransactionSource::External {
478				warn!(
479					target: LOG_TARGET,
480					"Rejecting unsigned `submit_tickets` transaction from external source",
481				);
482				return InvalidTransaction::BadSigner.into();
483			}
484
485			// Current slot should be less than half of epoch length.
486			let epoch_length = T::EpochLength::get();
487			let current_slot_idx = Self::current_slot_index();
488			if current_slot_idx > epoch_length / 2 {
489				warn!(target: LOG_TARGET, "Tickets shall be proposed in the first epoch half",);
490				return InvalidTransaction::Stale.into();
491			}
492
493			// This should be set such that it is discarded after the first epoch half
494			let tickets_longevity = epoch_length / 2 - current_slot_idx;
495			let tickets_tag = tickets.using_encoded(|bytes| hashing::blake2_256(bytes));
496
497			ValidTransaction::with_tag_prefix("Sassafras")
498				.priority(TransactionPriority::max_value())
499				.longevity(tickets_longevity as u64)
500				.and_provides(tickets_tag)
501				.propagate(true)
502				.build()
503		}
504	}
505}
506
507// Inherent methods
508impl<T: Config> Pallet<T> {
509	/// Determine whether an epoch change should take place at this block.
510	///
511	/// Assumes that initialization has already taken place.
512	pub(crate) fn should_end_epoch(block_num: BlockNumberFor<T>) -> bool {
513		// The epoch has technically ended during the passage of time between this block and the
514		// last, but we have to "end" the epoch now, since there is no earlier possible block we
515		// could have done it.
516		//
517		// The exception is for block 1: the genesis has slot 0, so we treat epoch 0 as having
518		// started at the slot of block 1. We want to use the same randomness and validator set as
519		// signalled in the genesis, so we don't rotate the epoch.
520		block_num > One::one() && Self::current_slot_index() >= T::EpochLength::get()
521	}
522
523	/// Current slot index relative to the current epoch.
524	fn current_slot_index() -> u32 {
525		Self::slot_index(CurrentSlot::<T>::get())
526	}
527
528	/// Slot index relative to the current epoch.
529	fn slot_index(slot: Slot) -> u32 {
530		slot.checked_sub(*Self::current_epoch_start())
531			.and_then(|v| v.try_into().ok())
532			.unwrap_or(u32::MAX)
533	}
534
535	/// Finds the start slot of the current epoch.
536	///
537	/// Only guaranteed to give correct results after `initialize` of the first
538	/// block in the chain (as its result is based off of `GenesisSlot`).
539	fn current_epoch_start() -> Slot {
540		Self::epoch_start(EpochIndex::<T>::get())
541	}
542
543	/// Get the epoch's first slot.
544	fn epoch_start(epoch_index: u64) -> Slot {
545		const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \
546							 if u64 is not enough we should crash for safety; qed.";
547
548		let epoch_start = epoch_index.checked_mul(T::EpochLength::get() as u64).expect(PROOF);
549		GenesisSlot::<T>::get().checked_add(epoch_start).expect(PROOF).into()
550	}
551
552	pub(crate) fn update_ring_verifier(authorities: &[AuthorityId]) {
553		debug!(target: LOG_TARGET, "Loading ring context");
554		let Some(ring_ctx) = RingContext::<T>::get() else {
555			debug!(target: LOG_TARGET, "Ring context not initialized");
556			return;
557		};
558
559		let pks: Vec<_> = authorities.iter().map(|auth| *auth.as_ref()).collect();
560
561		debug!(target: LOG_TARGET, "Building ring verifier (ring size: {})", pks.len());
562		let verifier_data = ring_ctx.verifier_key(&pks);
563
564		RingVerifierData::<T>::put(verifier_data);
565	}
566
567	/// Enact an epoch change.
568	///
569	/// WARNING: Should be called on every block once and if and only if [`should_end_epoch`]
570	/// has returned `true`.
571	///
572	/// If we detect one or more skipped epochs the policy is to use the authorities and values
573	/// from the first skipped epoch. The tickets data is invalidated.
574	pub(crate) fn enact_epoch_change(
575		authorities: WeakBoundedVec<AuthorityId, T::MaxAuthorities>,
576		next_authorities: WeakBoundedVec<AuthorityId, T::MaxAuthorities>,
577	) {
578		if next_authorities != authorities {
579			Self::update_ring_verifier(&next_authorities);
580		}
581
582		// Update authorities
583		Authorities::<T>::put(&authorities);
584		NextAuthorities::<T>::put(&next_authorities);
585
586		// Update epoch index
587		let mut epoch_idx = EpochIndex::<T>::get() + 1;
588
589		let slot_idx = CurrentSlot::<T>::get().saturating_sub(Self::epoch_start(epoch_idx));
590		if slot_idx >= T::EpochLength::get() {
591			// Detected one or more skipped epochs, clear tickets data and recompute epoch index.
592			Self::reset_tickets_data();
593			let skipped_epochs = *slot_idx / T::EpochLength::get() as u64;
594			epoch_idx += skipped_epochs;
595			warn!(
596				target: LOG_TARGET,
597				"Detected {} skipped epochs, resuming from epoch {}",
598				skipped_epochs,
599				epoch_idx
600			);
601		}
602
603		let mut metadata = TicketsMeta::<T>::get();
604		let mut metadata_dirty = false;
605
606		EpochIndex::<T>::put(epoch_idx);
607
608		let next_epoch_idx = epoch_idx + 1;
609
610		// Updates current epoch randomness and computes the *next* epoch randomness.
611		let next_randomness = Self::update_epoch_randomness(next_epoch_idx);
612
613		if let Some(config) = NextEpochConfig::<T>::take() {
614			EpochConfig::<T>::put(config);
615		}
616
617		let next_config = PendingEpochConfigChange::<T>::take();
618		if let Some(next_config) = next_config {
619			NextEpochConfig::<T>::put(next_config);
620		}
621
622		// After we update the current epoch, we signal the *next* epoch change
623		// so that nodes can track changes.
624		let next_epoch = NextEpochDescriptor {
625			randomness: next_randomness,
626			authorities: next_authorities.into_inner(),
627			config: next_config,
628		};
629		Self::deposit_next_epoch_descriptor_digest(next_epoch);
630
631		let epoch_tag = (epoch_idx & 1) as u8;
632
633		// Optionally finish sorting
634		if metadata.unsorted_tickets_count != 0 {
635			Self::sort_segments(u32::MAX, epoch_tag, &mut metadata);
636			metadata_dirty = true;
637		}
638
639		// Clear the "prev ≡ next (mod 2)" epoch tickets counter and bodies.
640		// Ids are left since are just cyclically overwritten on-the-go.
641		let prev_epoch_tag = epoch_tag ^ 1;
642		let prev_epoch_tickets_count = &mut metadata.tickets_count[prev_epoch_tag as usize];
643		if *prev_epoch_tickets_count != 0 {
644			for idx in 0..*prev_epoch_tickets_count {
645				if let Some(ticket_id) = TicketsIds::<T>::get((prev_epoch_tag, idx)) {
646					TicketsData::<T>::remove(ticket_id);
647				}
648			}
649			*prev_epoch_tickets_count = 0;
650			metadata_dirty = true;
651		}
652
653		if metadata_dirty {
654			TicketsMeta::<T>::set(metadata);
655		}
656	}
657
658	// Call this function on epoch change to enact current epoch randomness.
659	//
660	// Returns the next epoch randomness.
661	fn update_epoch_randomness(next_epoch_index: u64) -> Randomness {
662		let curr_epoch_randomness = NextRandomness::<T>::get();
663		CurrentRandomness::<T>::put(curr_epoch_randomness);
664
665		let accumulator = RandomnessAccumulator::<T>::get();
666
667		let mut buf = [0; RANDOMNESS_LENGTH + 8];
668		buf[..RANDOMNESS_LENGTH].copy_from_slice(&accumulator[..]);
669		buf[RANDOMNESS_LENGTH..].copy_from_slice(&next_epoch_index.to_le_bytes());
670
671		let next_randomness = hashing::blake2_256(&buf);
672		NextRandomness::<T>::put(&next_randomness);
673
674		next_randomness
675	}
676
677	// Deposit per-slot randomness.
678	fn deposit_slot_randomness(randomness: &Randomness) {
679		let accumulator = RandomnessAccumulator::<T>::get();
680
681		let mut buf = [0; 2 * RANDOMNESS_LENGTH];
682		buf[..RANDOMNESS_LENGTH].copy_from_slice(&accumulator[..]);
683		buf[RANDOMNESS_LENGTH..].copy_from_slice(&randomness[..]);
684
685		let accumulator = hashing::blake2_256(&buf);
686		RandomnessAccumulator::<T>::put(accumulator);
687	}
688
689	// Deposit next epoch descriptor in the block header digest.
690	fn deposit_next_epoch_descriptor_digest(desc: NextEpochDescriptor) {
691		let item = ConsensusLog::NextEpochData(desc);
692		let log = DigestItem::Consensus(SASSAFRAS_ENGINE_ID, item.encode());
693		<frame_system::Pallet<T>>::deposit_log(log)
694	}
695
696	// Initialize authorities on genesis phase.
697	//
698	// Genesis authorities may have been initialized via other means (e.g. via session pallet).
699	//
700	// If this function has already been called with some authorities, then the new list
701	// should match the previously set one.
702	fn genesis_authorities_initialize(authorities: &[AuthorityId]) {
703		let prev_authorities = Authorities::<T>::get();
704
705		if !prev_authorities.is_empty() {
706			// This function has already been called.
707			if prev_authorities.as_slice() == authorities {
708				return;
709			} else {
710				panic!("Authorities were already initialized");
711			}
712		}
713
714		let authorities = WeakBoundedVec::try_from(authorities.to_vec())
715			.expect("Initial number of authorities should be lower than T::MaxAuthorities");
716		Authorities::<T>::put(&authorities);
717		NextAuthorities::<T>::put(&authorities);
718	}
719
720	// Method to be called on first block `on_initialize` to properly populate some key parameters.
721	fn post_genesis_initialize(slot: Slot) {
722		// Keep track of the actual first slot used (may not be zero based).
723		GenesisSlot::<T>::put(slot);
724
725		// Properly initialize randomness using genesis hash and current slot.
726		// This is important to guarantee that a different set of tickets are produced for:
727		// - different chains which share the same ring parameters and
728		// - same chain started with a different slot base.
729		let genesis_hash = frame_system::Pallet::<T>::parent_hash();
730		let mut buf = genesis_hash.as_ref().to_vec();
731		buf.extend_from_slice(&slot.to_le_bytes());
732		let randomness = hashing::blake2_256(buf.as_slice());
733		RandomnessAccumulator::<T>::put(randomness);
734
735		let next_randomness = Self::update_epoch_randomness(1);
736
737		// Deposit a log as this is the first block in first epoch.
738		let next_epoch = NextEpochDescriptor {
739			randomness: next_randomness,
740			authorities: NextAuthorities::<T>::get().into_inner(),
741			config: None,
742		};
743		Self::deposit_next_epoch_descriptor_digest(next_epoch);
744	}
745
746	/// Current epoch information.
747	pub fn current_epoch() -> Epoch {
748		let index = EpochIndex::<T>::get();
749		Epoch {
750			index,
751			start: Self::epoch_start(index),
752			length: T::EpochLength::get(),
753			authorities: Authorities::<T>::get().into_inner(),
754			randomness: CurrentRandomness::<T>::get(),
755			config: EpochConfig::<T>::get(),
756		}
757	}
758
759	/// Next epoch information.
760	pub fn next_epoch() -> Epoch {
761		let index = EpochIndex::<T>::get() + 1;
762		Epoch {
763			index,
764			start: Self::epoch_start(index),
765			length: T::EpochLength::get(),
766			authorities: NextAuthorities::<T>::get().into_inner(),
767			randomness: NextRandomness::<T>::get(),
768			config: NextEpochConfig::<T>::get().unwrap_or_else(|| EpochConfig::<T>::get()),
769		}
770	}
771
772	/// Fetch expected ticket-id for the given slot according to an "outside-in" sorting strategy.
773	///
774	/// Given an ordered sequence of tickets [t0, t1, t2, ..., tk] to be assigned to n slots,
775	/// with n >= k, then the tickets are assigned to the slots according to the following
776	/// strategy:
777	///
778	/// slot-index  : [ 0,  1,  2, ............ , n ]
779	/// tickets     : [ t1, t3, t5, ... , t4, t2, t0 ].
780	///
781	/// With slot-index computed as `epoch_start() - slot`.
782	///
783	/// If `slot` value falls within the current epoch then we fetch tickets from the current epoch
784	/// tickets list.
785	///
786	/// If `slot` value falls within the next epoch then we fetch tickets from the next epoch
787	/// tickets ids list. Note that in this case we may have not finished receiving all the tickets
788	/// for that epoch yet. The next epoch tickets should be considered "stable" only after the
789	/// current epoch first half slots were elapsed (see `submit_tickets_unsigned_extrinsic`).
790	///
791	/// Returns `None` if, according to the sorting strategy, there is no ticket associated to the
792	/// specified slot-index (happens if a ticket falls in the middle of an epoch and n > k),
793	/// or if the slot falls beyond the next epoch.
794	///
795	/// Before importing the first block this returns `None`.
796	pub fn slot_ticket_id(slot: Slot) -> Option<TicketId> {
797		if frame_system::Pallet::<T>::block_number().is_zero() {
798			return None;
799		}
800		let epoch_idx = EpochIndex::<T>::get();
801		let epoch_len = T::EpochLength::get();
802		let mut slot_idx = Self::slot_index(slot);
803		let mut metadata = TicketsMeta::<T>::get();
804
805		let get_ticket_idx = |slot_idx| {
806			let ticket_idx = if slot_idx < epoch_len / 2 {
807				2 * slot_idx + 1
808			} else {
809				2 * (epoch_len - (slot_idx + 1))
810			};
811			debug!(
812				target: LOG_TARGET,
813				"slot-idx {} <-> ticket-idx {}",
814				slot_idx,
815				ticket_idx
816			);
817			ticket_idx as u32
818		};
819
820		let mut epoch_tag = (epoch_idx & 1) as u8;
821
822		if epoch_len <= slot_idx && slot_idx < 2 * epoch_len {
823			// Try to get a ticket for the next epoch. Since its state values were not enacted yet,
824			// we may have to finish sorting the tickets.
825			epoch_tag ^= 1;
826			slot_idx -= epoch_len;
827			if metadata.unsorted_tickets_count != 0 {
828				Self::sort_segments(u32::MAX, epoch_tag, &mut metadata);
829				TicketsMeta::<T>::set(metadata);
830			}
831		} else if slot_idx >= 2 * epoch_len {
832			return None;
833		}
834
835		let ticket_idx = get_ticket_idx(slot_idx);
836		if ticket_idx < metadata.tickets_count[epoch_tag as usize] {
837			TicketsIds::<T>::get((epoch_tag, ticket_idx))
838		} else {
839			None
840		}
841	}
842
843	/// Returns ticket id and data associated with the given `slot`.
844	///
845	/// Refer to the `slot_ticket_id` documentation for the slot-ticket association
846	/// criteria.
847	pub fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketBody)> {
848		Self::slot_ticket_id(slot).and_then(|id| TicketsData::<T>::get(id).map(|body| (id, body)))
849	}
850
851	// Sort and truncate candidate tickets, cleanup storage.
852	fn sort_and_truncate(candidates: &mut Vec<u128>, max_tickets: usize) -> u128 {
853		candidates.sort_unstable();
854		candidates.drain(max_tickets..).for_each(TicketsData::<T>::remove);
855		candidates[max_tickets - 1]
856	}
857
858	/// Sort the tickets which belong to the epoch with the specified `epoch_tag`.
859	///
860	/// At most `max_segments` are taken from the `UnsortedSegments` structure.
861	///
862	/// The tickets of the removed segments are merged with the tickets on the `SortedCandidates`
863	/// which is then sorted an truncated to contain at most `MaxTickets` entries.
864	///
865	/// If all the entries in `UnsortedSegments` are consumed, then `SortedCandidates` is elected
866	/// as the next epoch tickets, else it is saved to be used by next calls of this function.
867	pub(crate) fn sort_segments(max_segments: u32, epoch_tag: u8, metadata: &mut TicketsMetadata) {
868		let unsorted_segments_count = metadata.unsorted_tickets_count.div_ceil(SEGMENT_MAX_SIZE);
869		let max_segments = max_segments.min(unsorted_segments_count);
870		let max_tickets = Self::epoch_length() as usize;
871
872		// Fetch the sorted candidates (if any).
873		let mut candidates = SortedCandidates::<T>::take().into_inner();
874
875		// There is an upper bound to check only if we already sorted the max number
876		// of allowed tickets.
877		let mut upper_bound = *candidates.get(max_tickets - 1).unwrap_or(&TicketId::MAX);
878
879		let mut require_sort = false;
880
881		// Consume at most `max_segments` segments.
882		// During the process remove every stale ticket from `TicketsData` storage.
883		for segment_idx in (0..unsorted_segments_count).rev().take(max_segments as usize) {
884			let segment = UnsortedSegments::<T>::take(segment_idx);
885			metadata.unsorted_tickets_count -= segment.len() as u32;
886
887			// Push only ids with a value less than the current `upper_bound`.
888			let prev_len = candidates.len();
889			for ticket_id in segment {
890				if ticket_id < upper_bound {
891					candidates.push(ticket_id);
892				} else {
893					TicketsData::<T>::remove(ticket_id);
894				}
895			}
896			require_sort = candidates.len() != prev_len;
897
898			// As we approach the tail of the segments buffer the `upper_bound` value is expected
899			// to decrease (fast). We thus expect the number of tickets pushed into the
900			// `candidates` vector to follow an exponential drop.
901			//
902			// Given this, sorting and truncating after processing each segment may be an overkill
903			// as we may find pushing few tickets more and more often. Is preferable to perform
904			// the sort and truncation operations only when we reach some bigger threshold
905			// (currently set as twice the capacity of `SortCandidate`).
906			//
907			// The more is the protocol's redundancy factor (i.e. the ratio between tickets allowed
908			// to be submitted and the epoch length) the more this check becomes relevant.
909			if candidates.len() > 2 * max_tickets {
910				upper_bound = Self::sort_and_truncate(&mut candidates, max_tickets);
911				require_sort = false;
912			}
913		}
914
915		if candidates.len() > max_tickets {
916			Self::sort_and_truncate(&mut candidates, max_tickets);
917		} else if require_sort {
918			candidates.sort_unstable();
919		}
920
921		if metadata.unsorted_tickets_count == 0 {
922			// Sorting is over, write to next epoch map.
923			candidates.iter().enumerate().for_each(|(i, id)| {
924				TicketsIds::<T>::insert((epoch_tag, i as u32), id);
925			});
926			metadata.tickets_count[epoch_tag as usize] = candidates.len() as u32;
927		} else {
928			// Keep the partial result for the next calls.
929			SortedCandidates::<T>::set(BoundedVec::truncate_from(candidates));
930		}
931	}
932
933	/// Append a set of tickets to the segments map.
934	pub(crate) fn append_tickets(mut tickets: BoundedVec<TicketId, EpochLengthFor<T>>) {
935		debug!(target: LOG_TARGET, "Appending batch with {} tickets", tickets.len());
936		tickets.iter().for_each(|t| trace!(target: LOG_TARGET, "  + {t:032x}"));
937
938		let mut metadata = TicketsMeta::<T>::get();
939		let mut segment_idx = metadata.unsorted_tickets_count / SEGMENT_MAX_SIZE;
940
941		while !tickets.is_empty() {
942			let rem = metadata.unsorted_tickets_count % SEGMENT_MAX_SIZE;
943			let to_be_added = tickets.len().min((SEGMENT_MAX_SIZE - rem) as usize);
944
945			let mut segment = UnsortedSegments::<T>::get(segment_idx);
946			let _ = segment
947				.try_extend(tickets.drain(..to_be_added))
948				.defensive_proof("We don't add more than `SEGMENT_MAX_SIZE` and this is the maximum bound for the vector.");
949			UnsortedSegments::<T>::insert(segment_idx, segment);
950
951			metadata.unsorted_tickets_count += to_be_added as u32;
952			segment_idx += 1;
953		}
954
955		TicketsMeta::<T>::set(metadata);
956	}
957
958	/// Remove all tickets related data.
959	///
960	/// May not be efficient as the calling places may repeat some of this operations
961	/// but is a very extraordinary operation (hopefully never happens in production)
962	/// and better safe than sorry.
963	fn reset_tickets_data() {
964		let metadata = TicketsMeta::<T>::get();
965
966		// Remove even/odd-epoch data.
967		for epoch_tag in 0..=1 {
968			for idx in 0..metadata.tickets_count[epoch_tag] {
969				if let Some(id) = TicketsIds::<T>::get((epoch_tag as u8, idx)) {
970					TicketsData::<T>::remove(id);
971				}
972			}
973		}
974
975		// Remove all unsorted tickets segments.
976		let segments_count = metadata.unsorted_tickets_count.div_ceil(SEGMENT_MAX_SIZE);
977		(0..segments_count).for_each(UnsortedSegments::<T>::remove);
978
979		// Reset sorted candidates
980		SortedCandidates::<T>::kill();
981
982		// Reset tickets metadata
983		TicketsMeta::<T>::kill();
984	}
985
986	/// Submit next epoch validator tickets via an unsigned extrinsic constructed with a call to
987	/// `submit_unsigned_transaction`.
988	///
989	/// The submitted tickets are added to the next epoch outstanding tickets as long as the
990	/// extrinsic is called within the first half of the epoch. Tickets received during the
991	/// second half are dropped.
992	pub fn submit_tickets_unsigned_extrinsic(tickets: Vec<TicketEnvelope>) -> bool {
993		let tickets = BoundedVec::truncate_from(tickets);
994		let call = Call::submit_tickets { tickets };
995		let xt = T::create_bare(call.into());
996		match SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
997			Ok(_) => true,
998			Err(e) => {
999				error!(target: LOG_TARGET, "Error submitting tickets {:?}", e);
1000				false
1001			},
1002		}
1003	}
1004
1005	/// Epoch length
1006	pub fn epoch_length() -> u32 {
1007		T::EpochLength::get()
1008	}
1009
1010	/// Get current epoch index.
1011	pub fn epoch_index() -> u64 {
1012		EpochIndex::<T>::get()
1013	}
1014
1015	/// Get current epoch authorities.
1016	pub fn authorities() -> Vec<AuthorityId> {
1017		Authorities::<T>::get().into_inner()
1018	}
1019
1020	/// Get next epoch authorities.
1021	pub fn next_authorities() -> Vec<AuthorityId> {
1022		NextAuthorities::<T>::get().into_inner()
1023	}
1024
1025	/// Get genesis slot.
1026	pub fn genesis_slot() -> Slot {
1027		GenesisSlot::<T>::get()
1028	}
1029
1030	/// Get current slot.
1031	pub fn current_slot() -> Slot {
1032		CurrentSlot::<T>::get()
1033	}
1034
1035	/// Get current epoch randomness.
1036	pub fn randomness() -> Randomness {
1037		CurrentRandomness::<T>::get()
1038	}
1039
1040	/// Get next epoch randomness.
1041	pub fn next_randomness() -> Randomness {
1042		NextRandomness::<T>::get()
1043	}
1044
1045	/// Get randomness accumulator.
1046	pub fn randomness_accumulator() -> Randomness {
1047		RandomnessAccumulator::<T>::get()
1048	}
1049
1050	/// Get current epoch configuration.
1051	pub fn config() -> EpochConfiguration {
1052		EpochConfig::<T>::get()
1053	}
1054
1055	/// Get next epoch configuration.
1056	pub fn next_config() -> Option<EpochConfiguration> {
1057		NextEpochConfig::<T>::get()
1058	}
1059
1060	/// Get ring context.
1061	pub fn ring_context() -> Option<vrf::RingContext> {
1062		RingContext::<T>::get()
1063	}
1064}
1065
1066/// Trigger an epoch change, if any should take place.
1067pub trait EpochChangeTrigger {
1068	/// May trigger an epoch change, if any should take place.
1069	///
1070	/// Returns an optional `Weight` if epoch change has been triggered.
1071	///
1072	/// This should be called during every block, after initialization is done.
1073	fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight;
1074}
1075
1076/// An `EpochChangeTrigger` which does nothing.
1077///
1078/// In practice this means that the epoch change logic is left to some external component
1079/// (e.g. pallet-session).
1080pub struct EpochChangeExternalTrigger;
1081
1082impl EpochChangeTrigger for EpochChangeExternalTrigger {
1083	fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight {
1084		// nothing - trigger is external.
1085		Weight::zero()
1086	}
1087}
1088
1089/// An `EpochChangeTrigger` which recycle the same authorities set forever.
1090///
1091/// The internal trigger should only be used when no other module is responsible for
1092/// changing authority set.
1093pub struct EpochChangeInternalTrigger;
1094
1095impl EpochChangeTrigger for EpochChangeInternalTrigger {
1096	fn trigger<T: Config>(block_num: BlockNumberFor<T>) -> Weight {
1097		if Pallet::<T>::should_end_epoch(block_num) {
1098			let authorities = NextAuthorities::<T>::get();
1099			let next_authorities = authorities.clone();
1100			let len = next_authorities.len() as u32;
1101			Pallet::<T>::enact_epoch_change(authorities, next_authorities);
1102			T::WeightInfo::enact_epoch_change(len, T::EpochLength::get())
1103		} else {
1104			Weight::zero()
1105		}
1106	}
1107}
1108
1109impl<T: Config> BoundToRuntimeAppPublic for Pallet<T> {
1110	type Public = AuthorityId;
1111}