1#![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
93const SEGMENT_MAX_SIZE: u32 = 128;
95
96pub type AuthoritiesVec<T> = WeakBoundedVec<AuthorityId, <T as Config>::MaxAuthorities>;
98
99pub type EpochLengthFor<T> = <T as Config>::EpochLength;
101
102#[derive(Debug, Default, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, Copy)]
104pub struct TicketsMetadata {
105 pub unsorted_tickets_count: u32,
110
111 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 #[pallet::pallet]
127 pub struct Pallet<T>(_);
128
129 #[pallet::config]
131 pub trait Config: frame_system::Config + CreateBare<Call<Self>> {
132 #[pallet::constant]
134 type EpochLength: Get<u32>;
135
136 #[pallet::constant]
138 type MaxAuthorities: Get<u32>;
139
140 type EpochChangeTrigger: EpochChangeTrigger;
145
146 type WeightInfo: WeightInfo;
148 }
149
150 #[pallet::error]
152 pub enum Error<T> {
153 InvalidConfiguration,
155 }
156
157 #[pallet::storage]
159 pub type EpochIndex<T> = StorageValue<_, u64, ValueQuery>;
160
161 #[pallet::storage]
163 pub type Authorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
164
165 #[pallet::storage]
167 pub type NextAuthorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
168
169 #[pallet::storage]
174 pub type GenesisSlot<T> = StorageValue<_, Slot, ValueQuery>;
175
176 #[pallet::storage]
178 pub type CurrentSlot<T> = StorageValue<_, Slot, ValueQuery>;
179
180 #[pallet::storage]
182 pub type CurrentRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
183
184 #[pallet::storage]
186 pub type NextRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
187
188 #[pallet::storage]
192 pub(crate) type RandomnessAccumulator<T> = StorageValue<_, Randomness, ValueQuery>;
193
194 #[pallet::storage]
198 pub(crate) type SlotRandomness<T> = StorageValue<_, Randomness>;
199
200 #[pallet::storage]
202 pub type EpochConfig<T> = StorageValue<_, EpochConfiguration, ValueQuery>;
203
204 #[pallet::storage]
206 pub type NextEpochConfig<T> = StorageValue<_, EpochConfiguration>;
207
208 #[pallet::storage]
215 pub type PendingEpochConfigChange<T> = StorageValue<_, EpochConfiguration>;
216
217 #[pallet::storage]
219 pub type TicketsMeta<T> = StorageValue<_, TicketsMetadata, ValueQuery>;
220
221 #[pallet::storage]
237 pub type TicketsIds<T> = StorageMap<_, Identity, (u8, u32), TicketId>;
238
239 #[pallet::storage]
241 pub type TicketsData<T> = StorageMap<_, Identity, TicketId, TicketBody>;
242
243 #[pallet::storage]
250 pub type UnsortedSegments<T: Config> =
251 StorageMap<_, Identity, u32, BoundedVec<TicketId, ConstU32<SEGMENT_MAX_SIZE>>, ValueQuery>;
252
253 #[pallet::storage]
256 pub type SortedCandidates<T> =
257 StorageValue<_, BoundedVec<TicketId, EpochLengthFor<T>>, ValueQuery>;
258
259 #[pallet::storage]
263 pub type RingContext<T: Config> = StorageValue<_, vrf::RingContext>;
264
265 #[pallet::storage]
267 pub type RingVerifierData<T: Config> = StorageValue<_, vrf::RingVerifierKey>;
268
269 #[pallet::genesis_config]
271 #[derive(frame_support::DefaultNoBound)]
272 pub struct GenesisConfig<T: Config> {
273 pub authorities: Vec<AuthorityId>,
275 pub epoch_config: EpochConfiguration,
277 #[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 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 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 #[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 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 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 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 if TicketsData::<T>::contains_key(ticket_id) {
414 debug!(target: LOG_TARGET, "Ignoring duplicate ticket ({:032x})", ticket_id);
415 continue;
416 }
417
418 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 #[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 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 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 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
507impl<T: Config> Pallet<T> {
509 pub(crate) fn should_end_epoch(block_num: BlockNumberFor<T>) -> bool {
513 block_num > One::one() && Self::current_slot_index() >= T::EpochLength::get()
521 }
522
523 fn current_slot_index() -> u32 {
525 Self::slot_index(CurrentSlot::<T>::get())
526 }
527
528 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 fn current_epoch_start() -> Slot {
540 Self::epoch_start(EpochIndex::<T>::get())
541 }
542
543 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 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 Authorities::<T>::put(&authorities);
584 NextAuthorities::<T>::put(&next_authorities);
585
586 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 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 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 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 if metadata.unsorted_tickets_count != 0 {
635 Self::sort_segments(u32::MAX, epoch_tag, &mut metadata);
636 metadata_dirty = true;
637 }
638
639 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 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 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 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 fn genesis_authorities_initialize(authorities: &[AuthorityId]) {
703 let prev_authorities = Authorities::<T>::get();
704
705 if !prev_authorities.is_empty() {
706 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 fn post_genesis_initialize(slot: Slot) {
722 GenesisSlot::<T>::put(slot);
724
725 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 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 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 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 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 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 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 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 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 let mut candidates = SortedCandidates::<T>::take().into_inner();
874
875 let mut upper_bound = *candidates.get(max_tickets - 1).unwrap_or(&TicketId::MAX);
878
879 let mut require_sort = false;
880
881 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 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 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 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 SortedCandidates::<T>::set(BoundedVec::truncate_from(candidates));
930 }
931 }
932
933 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 fn reset_tickets_data() {
964 let metadata = TicketsMeta::<T>::get();
965
966 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 let segments_count = metadata.unsorted_tickets_count.div_ceil(SEGMENT_MAX_SIZE);
977 (0..segments_count).for_each(UnsortedSegments::<T>::remove);
978
979 SortedCandidates::<T>::kill();
981
982 TicketsMeta::<T>::kill();
984 }
985
986 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 pub fn epoch_length() -> u32 {
1007 T::EpochLength::get()
1008 }
1009
1010 pub fn epoch_index() -> u64 {
1012 EpochIndex::<T>::get()
1013 }
1014
1015 pub fn authorities() -> Vec<AuthorityId> {
1017 Authorities::<T>::get().into_inner()
1018 }
1019
1020 pub fn next_authorities() -> Vec<AuthorityId> {
1022 NextAuthorities::<T>::get().into_inner()
1023 }
1024
1025 pub fn genesis_slot() -> Slot {
1027 GenesisSlot::<T>::get()
1028 }
1029
1030 pub fn current_slot() -> Slot {
1032 CurrentSlot::<T>::get()
1033 }
1034
1035 pub fn randomness() -> Randomness {
1037 CurrentRandomness::<T>::get()
1038 }
1039
1040 pub fn next_randomness() -> Randomness {
1042 NextRandomness::<T>::get()
1043 }
1044
1045 pub fn randomness_accumulator() -> Randomness {
1047 RandomnessAccumulator::<T>::get()
1048 }
1049
1050 pub fn config() -> EpochConfiguration {
1052 EpochConfig::<T>::get()
1053 }
1054
1055 pub fn next_config() -> Option<EpochConfiguration> {
1057 NextEpochConfig::<T>::get()
1058 }
1059
1060 pub fn ring_context() -> Option<vrf::RingContext> {
1062 RingContext::<T>::get()
1063 }
1064}
1065
1066pub trait EpochChangeTrigger {
1068 fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight;
1074}
1075
1076pub struct EpochChangeExternalTrigger;
1081
1082impl EpochChangeTrigger for EpochChangeExternalTrigger {
1083 fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight {
1084 Weight::zero()
1086 }
1087}
1088
1089pub 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}