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 #[pallet::getter(fn epoch_index)]
160 pub type EpochIndex<T> = StorageValue<_, u64, ValueQuery>;
161
162 #[pallet::storage]
164 #[pallet::getter(fn authorities)]
165 pub type Authorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
166
167 #[pallet::storage]
169 #[pallet::getter(fn next_authorities)]
170 pub type NextAuthorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
171
172 #[pallet::storage]
177 #[pallet::getter(fn genesis_slot)]
178 pub type GenesisSlot<T> = StorageValue<_, Slot, ValueQuery>;
179
180 #[pallet::storage]
182 #[pallet::getter(fn current_slot)]
183 pub type CurrentSlot<T> = StorageValue<_, Slot, ValueQuery>;
184
185 #[pallet::storage]
187 #[pallet::getter(fn randomness)]
188 pub type CurrentRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
189
190 #[pallet::storage]
192 #[pallet::getter(fn next_randomness)]
193 pub type NextRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
194
195 #[pallet::storage]
199 #[pallet::getter(fn randomness_accumulator)]
200 pub(crate) type RandomnessAccumulator<T> = StorageValue<_, Randomness, ValueQuery>;
201
202 #[pallet::storage]
206 pub(crate) type SlotRandomness<T> = StorageValue<_, Randomness>;
207
208 #[pallet::storage]
210 #[pallet::getter(fn config)]
211 pub type EpochConfig<T> = StorageValue<_, EpochConfiguration, ValueQuery>;
212
213 #[pallet::storage]
215 #[pallet::getter(fn next_config)]
216 pub type NextEpochConfig<T> = StorageValue<_, EpochConfiguration>;
217
218 #[pallet::storage]
225 pub type PendingEpochConfigChange<T> = StorageValue<_, EpochConfiguration>;
226
227 #[pallet::storage]
229 pub type TicketsMeta<T> = StorageValue<_, TicketsMetadata, ValueQuery>;
230
231 #[pallet::storage]
247 pub type TicketsIds<T> = StorageMap<_, Identity, (u8, u32), TicketId>;
248
249 #[pallet::storage]
251 pub type TicketsData<T> = StorageMap<_, Identity, TicketId, TicketBody>;
252
253 #[pallet::storage]
260 pub type UnsortedSegments<T: Config> =
261 StorageMap<_, Identity, u32, BoundedVec<TicketId, ConstU32<SEGMENT_MAX_SIZE>>, ValueQuery>;
262
263 #[pallet::storage]
266 pub type SortedCandidates<T> =
267 StorageValue<_, BoundedVec<TicketId, EpochLengthFor<T>>, ValueQuery>;
268
269 #[pallet::storage]
273 #[pallet::getter(fn ring_context)]
274 pub type RingContext<T: Config> = StorageValue<_, vrf::RingContext>;
275
276 #[pallet::storage]
278 pub type RingVerifierData<T: Config> = StorageValue<_, vrf::RingVerifierKey>;
279
280 #[pallet::genesis_config]
282 #[derive(frame_support::DefaultNoBound)]
283 pub struct GenesisConfig<T: Config> {
284 pub authorities: Vec<AuthorityId>,
286 pub epoch_config: EpochConfiguration,
288 #[serde(skip)]
290 pub _phantom: core::marker::PhantomData<T>,
291 }
292
293 #[pallet::genesis_build]
294 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
295 fn build(&self) {
296 EpochConfig::<T>::put(self.epoch_config);
297 Pallet::<T>::genesis_authorities_initialize(&self.authorities);
298
299 #[cfg(feature = "construct-dummy-ring-context")]
300 {
301 debug!(target: LOG_TARGET, "Constructing dummy ring context");
302 let ring_ctx = vrf::RingContext::new_testing();
303 RingContext::<T>::put(ring_ctx);
304 Pallet::<T>::update_ring_verifier(&self.authorities);
305 }
306 }
307 }
308
309 #[pallet::hooks]
310 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
311 fn on_initialize(block_num: BlockNumberFor<T>) -> Weight {
312 debug_assert_eq!(block_num, frame_system::Pallet::<T>::block_number());
313
314 let claim = <frame_system::Pallet<T>>::digest()
315 .logs
316 .iter()
317 .find_map(|item| item.pre_runtime_try_to::<SlotClaim>(&SASSAFRAS_ENGINE_ID))
318 .expect("Valid block must have a slot claim. qed");
319
320 CurrentSlot::<T>::put(claim.slot);
321
322 if block_num == One::one() {
323 Self::post_genesis_initialize(claim.slot);
324 }
325
326 let randomness = claim.vrf_signature.pre_output.make_bytes();
327 SlotRandomness::<T>::put(randomness);
328
329 let trigger_weight = T::EpochChangeTrigger::trigger::<T>(block_num);
330
331 T::WeightInfo::on_initialize() + trigger_weight
332 }
333
334 fn on_finalize(_: BlockNumberFor<T>) {
335 let randomness = SlotRandomness::<T>::take()
340 .expect("Unconditionally populated in `on_initialize`; `on_finalize` is always called after; qed");
341 Self::deposit_slot_randomness(&randomness);
342
343 let epoch_length = T::EpochLength::get();
346 let current_slot_idx = Self::current_slot_index();
347 if current_slot_idx >= epoch_length / 2 {
348 let mut metadata = TicketsMeta::<T>::get();
349 if metadata.unsorted_tickets_count != 0 {
350 let next_epoch_idx = EpochIndex::<T>::get() + 1;
351 let next_epoch_tag = (next_epoch_idx & 1) as u8;
352 let slots_left = epoch_length.checked_sub(current_slot_idx).unwrap_or(1);
353 Self::sort_segments(
354 metadata
355 .unsorted_tickets_count
356 .div_ceil(SEGMENT_MAX_SIZE * slots_left as u32),
357 next_epoch_tag,
358 &mut metadata,
359 );
360 TicketsMeta::<T>::set(metadata);
361 }
362 }
363 }
364 }
365
366 #[pallet::call]
367 impl<T: Config> Pallet<T> {
368 #[pallet::call_index(0)]
372 #[pallet::weight(T::WeightInfo::submit_tickets(tickets.len() as u32))]
373 pub fn submit_tickets(
374 origin: OriginFor<T>,
375 tickets: BoundedVec<TicketEnvelope, EpochLengthFor<T>>,
376 ) -> DispatchResultWithPostInfo {
377 ensure_none(origin)?;
378
379 debug!(target: LOG_TARGET, "Received {} tickets", tickets.len());
380
381 let epoch_length = T::EpochLength::get();
382 let current_slot_idx = Self::current_slot_index();
383 if current_slot_idx > epoch_length / 2 {
384 warn!(target: LOG_TARGET, "Tickets shall be submitted in the first epoch half",);
385 return Err("Tickets shall be submitted in the first epoch half".into())
386 }
387
388 let Some(verifier) =
389 RingVerifierData::<T>::get().map(|vk| vrf::RingContext::verifier_no_context(vk))
390 else {
391 warn!(target: LOG_TARGET, "Ring verifier key not initialized");
392 return Err("Ring verifier key not initialized".into())
393 };
394
395 let next_authorities = Self::next_authorities();
396
397 let next_config = Self::next_config().unwrap_or_else(|| Self::config());
399 let ticket_threshold = sp_consensus_sassafras::ticket_id_threshold(
400 next_config.redundancy_factor,
401 epoch_length as u32,
402 next_config.attempts_number,
403 next_authorities.len() as u32,
404 );
405
406 let randomness = NextRandomness::<T>::get();
408 let epoch_idx = EpochIndex::<T>::get() + 1;
409
410 let mut valid_tickets = BoundedVec::with_bounded_capacity(tickets.len());
411
412 for ticket in tickets {
413 debug!(target: LOG_TARGET, "Checking ring proof");
414
415 let ticket_id = vrf::make_ticket_id(&ticket.signature.pre_output);
417 if ticket_id >= ticket_threshold {
418 debug!(target: LOG_TARGET, "Ignoring ticket over threshold ({:032x} >= {:032x})", ticket_id, ticket_threshold);
419 continue
420 }
421
422 if TicketsData::<T>::contains_key(ticket_id) {
424 debug!(target: LOG_TARGET, "Ignoring duplicate ticket ({:032x})", ticket_id);
425 continue
426 }
427
428 let ticket_id_input =
430 vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx);
431 let sign_data = vrf::ticket_body_sign_data(&ticket.body, ticket_id_input);
432 if !ticket.signature.ring_vrf_verify(&sign_data, &verifier) {
433 debug!(target: LOG_TARGET, "Proof verification failure for ticket ({:032x})", ticket_id);
434 continue
435 }
436
437 if let Ok(_) = valid_tickets.try_push(ticket_id).defensive_proof(
438 "Input segment has same length as bounded destination vector; qed",
439 ) {
440 TicketsData::<T>::set(ticket_id, Some(ticket.body));
441 }
442 }
443
444 if !valid_tickets.is_empty() {
445 Self::append_tickets(valid_tickets);
446 }
447
448 Ok(Pays::No.into())
449 }
450
451 #[pallet::call_index(1)]
460 #[pallet::weight(T::WeightInfo::plan_config_change())]
461 pub fn plan_config_change(
462 origin: OriginFor<T>,
463 config: EpochConfiguration,
464 ) -> DispatchResult {
465 ensure_root(origin)?;
466
467 ensure!(
468 config.redundancy_factor != 0 && config.attempts_number != 0,
469 Error::<T>::InvalidConfiguration
470 );
471 PendingEpochConfigChange::<T>::put(config);
472 Ok(())
473 }
474 }
475
476 #[pallet::validate_unsigned]
477 impl<T: Config> ValidateUnsigned for Pallet<T> {
478 type Call = Call<T>;
479
480 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
481 let Call::submit_tickets { tickets } = call else {
482 return InvalidTransaction::Call.into()
483 };
484
485 if source == TransactionSource::External {
487 warn!(
488 target: LOG_TARGET,
489 "Rejecting unsigned `submit_tickets` transaction from external source",
490 );
491 return InvalidTransaction::BadSigner.into()
492 }
493
494 let epoch_length = T::EpochLength::get();
496 let current_slot_idx = Self::current_slot_index();
497 if current_slot_idx > epoch_length / 2 {
498 warn!(target: LOG_TARGET, "Tickets shall be proposed in the first epoch half",);
499 return InvalidTransaction::Stale.into()
500 }
501
502 let tickets_longevity = epoch_length / 2 - current_slot_idx;
504 let tickets_tag = tickets.using_encoded(|bytes| hashing::blake2_256(bytes));
505
506 ValidTransaction::with_tag_prefix("Sassafras")
507 .priority(TransactionPriority::max_value())
508 .longevity(tickets_longevity as u64)
509 .and_provides(tickets_tag)
510 .propagate(true)
511 .build()
512 }
513 }
514}
515
516impl<T: Config> Pallet<T> {
518 pub(crate) fn should_end_epoch(block_num: BlockNumberFor<T>) -> bool {
522 block_num > One::one() && Self::current_slot_index() >= T::EpochLength::get()
530 }
531
532 fn current_slot_index() -> u32 {
534 Self::slot_index(CurrentSlot::<T>::get())
535 }
536
537 fn slot_index(slot: Slot) -> u32 {
539 slot.checked_sub(*Self::current_epoch_start())
540 .and_then(|v| v.try_into().ok())
541 .unwrap_or(u32::MAX)
542 }
543
544 fn current_epoch_start() -> Slot {
549 Self::epoch_start(EpochIndex::<T>::get())
550 }
551
552 fn epoch_start(epoch_index: u64) -> Slot {
554 const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \
555 if u64 is not enough we should crash for safety; qed.";
556
557 let epoch_start = epoch_index.checked_mul(T::EpochLength::get() as u64).expect(PROOF);
558 GenesisSlot::<T>::get().checked_add(epoch_start).expect(PROOF).into()
559 }
560
561 pub(crate) fn update_ring_verifier(authorities: &[AuthorityId]) {
562 debug!(target: LOG_TARGET, "Loading ring context");
563 let Some(ring_ctx) = RingContext::<T>::get() else {
564 debug!(target: LOG_TARGET, "Ring context not initialized");
565 return
566 };
567
568 let pks: Vec<_> = authorities.iter().map(|auth| *auth.as_ref()).collect();
569
570 debug!(target: LOG_TARGET, "Building ring verifier (ring size: {})", pks.len());
571 let verifier_data = ring_ctx.verifier_key(&pks);
572
573 RingVerifierData::<T>::put(verifier_data);
574 }
575
576 pub(crate) fn enact_epoch_change(
584 authorities: WeakBoundedVec<AuthorityId, T::MaxAuthorities>,
585 next_authorities: WeakBoundedVec<AuthorityId, T::MaxAuthorities>,
586 ) {
587 if next_authorities != authorities {
588 Self::update_ring_verifier(&next_authorities);
589 }
590
591 Authorities::<T>::put(&authorities);
593 NextAuthorities::<T>::put(&next_authorities);
594
595 let mut epoch_idx = EpochIndex::<T>::get() + 1;
597
598 let slot_idx = CurrentSlot::<T>::get().saturating_sub(Self::epoch_start(epoch_idx));
599 if slot_idx >= T::EpochLength::get() {
600 Self::reset_tickets_data();
602 let skipped_epochs = *slot_idx / T::EpochLength::get() as u64;
603 epoch_idx += skipped_epochs;
604 warn!(
605 target: LOG_TARGET,
606 "Detected {} skipped epochs, resuming from epoch {}",
607 skipped_epochs,
608 epoch_idx
609 );
610 }
611
612 let mut metadata = TicketsMeta::<T>::get();
613 let mut metadata_dirty = false;
614
615 EpochIndex::<T>::put(epoch_idx);
616
617 let next_epoch_idx = epoch_idx + 1;
618
619 let next_randomness = Self::update_epoch_randomness(next_epoch_idx);
621
622 if let Some(config) = NextEpochConfig::<T>::take() {
623 EpochConfig::<T>::put(config);
624 }
625
626 let next_config = PendingEpochConfigChange::<T>::take();
627 if let Some(next_config) = next_config {
628 NextEpochConfig::<T>::put(next_config);
629 }
630
631 let next_epoch = NextEpochDescriptor {
634 randomness: next_randomness,
635 authorities: next_authorities.into_inner(),
636 config: next_config,
637 };
638 Self::deposit_next_epoch_descriptor_digest(next_epoch);
639
640 let epoch_tag = (epoch_idx & 1) as u8;
641
642 if metadata.unsorted_tickets_count != 0 {
644 Self::sort_segments(u32::MAX, epoch_tag, &mut metadata);
645 metadata_dirty = true;
646 }
647
648 let prev_epoch_tag = epoch_tag ^ 1;
651 let prev_epoch_tickets_count = &mut metadata.tickets_count[prev_epoch_tag as usize];
652 if *prev_epoch_tickets_count != 0 {
653 for idx in 0..*prev_epoch_tickets_count {
654 if let Some(ticket_id) = TicketsIds::<T>::get((prev_epoch_tag, idx)) {
655 TicketsData::<T>::remove(ticket_id);
656 }
657 }
658 *prev_epoch_tickets_count = 0;
659 metadata_dirty = true;
660 }
661
662 if metadata_dirty {
663 TicketsMeta::<T>::set(metadata);
664 }
665 }
666
667 fn update_epoch_randomness(next_epoch_index: u64) -> Randomness {
671 let curr_epoch_randomness = NextRandomness::<T>::get();
672 CurrentRandomness::<T>::put(curr_epoch_randomness);
673
674 let accumulator = RandomnessAccumulator::<T>::get();
675
676 let mut buf = [0; RANDOMNESS_LENGTH + 8];
677 buf[..RANDOMNESS_LENGTH].copy_from_slice(&accumulator[..]);
678 buf[RANDOMNESS_LENGTH..].copy_from_slice(&next_epoch_index.to_le_bytes());
679
680 let next_randomness = hashing::blake2_256(&buf);
681 NextRandomness::<T>::put(&next_randomness);
682
683 next_randomness
684 }
685
686 fn deposit_slot_randomness(randomness: &Randomness) {
688 let accumulator = RandomnessAccumulator::<T>::get();
689
690 let mut buf = [0; 2 * RANDOMNESS_LENGTH];
691 buf[..RANDOMNESS_LENGTH].copy_from_slice(&accumulator[..]);
692 buf[RANDOMNESS_LENGTH..].copy_from_slice(&randomness[..]);
693
694 let accumulator = hashing::blake2_256(&buf);
695 RandomnessAccumulator::<T>::put(accumulator);
696 }
697
698 fn deposit_next_epoch_descriptor_digest(desc: NextEpochDescriptor) {
700 let item = ConsensusLog::NextEpochData(desc);
701 let log = DigestItem::Consensus(SASSAFRAS_ENGINE_ID, item.encode());
702 <frame_system::Pallet<T>>::deposit_log(log)
703 }
704
705 fn genesis_authorities_initialize(authorities: &[AuthorityId]) {
712 let prev_authorities = Authorities::<T>::get();
713
714 if !prev_authorities.is_empty() {
715 if prev_authorities.as_slice() == authorities {
717 return
718 } else {
719 panic!("Authorities were already initialized");
720 }
721 }
722
723 let authorities = WeakBoundedVec::try_from(authorities.to_vec())
724 .expect("Initial number of authorities should be lower than T::MaxAuthorities");
725 Authorities::<T>::put(&authorities);
726 NextAuthorities::<T>::put(&authorities);
727 }
728
729 fn post_genesis_initialize(slot: Slot) {
731 GenesisSlot::<T>::put(slot);
733
734 let genesis_hash = frame_system::Pallet::<T>::parent_hash();
739 let mut buf = genesis_hash.as_ref().to_vec();
740 buf.extend_from_slice(&slot.to_le_bytes());
741 let randomness = hashing::blake2_256(buf.as_slice());
742 RandomnessAccumulator::<T>::put(randomness);
743
744 let next_randomness = Self::update_epoch_randomness(1);
745
746 let next_epoch = NextEpochDescriptor {
748 randomness: next_randomness,
749 authorities: Self::next_authorities().into_inner(),
750 config: None,
751 };
752 Self::deposit_next_epoch_descriptor_digest(next_epoch);
753 }
754
755 pub fn current_epoch() -> Epoch {
757 let index = EpochIndex::<T>::get();
758 Epoch {
759 index,
760 start: Self::epoch_start(index),
761 length: T::EpochLength::get(),
762 authorities: Self::authorities().into_inner(),
763 randomness: Self::randomness(),
764 config: Self::config(),
765 }
766 }
767
768 pub fn next_epoch() -> Epoch {
770 let index = EpochIndex::<T>::get() + 1;
771 Epoch {
772 index,
773 start: Self::epoch_start(index),
774 length: T::EpochLength::get(),
775 authorities: Self::next_authorities().into_inner(),
776 randomness: Self::next_randomness(),
777 config: Self::next_config().unwrap_or_else(|| Self::config()),
778 }
779 }
780
781 pub fn slot_ticket_id(slot: Slot) -> Option<TicketId> {
806 if frame_system::Pallet::<T>::block_number().is_zero() {
807 return None
808 }
809 let epoch_idx = EpochIndex::<T>::get();
810 let epoch_len = T::EpochLength::get();
811 let mut slot_idx = Self::slot_index(slot);
812 let mut metadata = TicketsMeta::<T>::get();
813
814 let get_ticket_idx = |slot_idx| {
815 let ticket_idx = if slot_idx < epoch_len / 2 {
816 2 * slot_idx + 1
817 } else {
818 2 * (epoch_len - (slot_idx + 1))
819 };
820 debug!(
821 target: LOG_TARGET,
822 "slot-idx {} <-> ticket-idx {}",
823 slot_idx,
824 ticket_idx
825 );
826 ticket_idx as u32
827 };
828
829 let mut epoch_tag = (epoch_idx & 1) as u8;
830
831 if epoch_len <= slot_idx && slot_idx < 2 * epoch_len {
832 epoch_tag ^= 1;
835 slot_idx -= epoch_len;
836 if metadata.unsorted_tickets_count != 0 {
837 Self::sort_segments(u32::MAX, epoch_tag, &mut metadata);
838 TicketsMeta::<T>::set(metadata);
839 }
840 } else if slot_idx >= 2 * epoch_len {
841 return None
842 }
843
844 let ticket_idx = get_ticket_idx(slot_idx);
845 if ticket_idx < metadata.tickets_count[epoch_tag as usize] {
846 TicketsIds::<T>::get((epoch_tag, ticket_idx))
847 } else {
848 None
849 }
850 }
851
852 pub fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketBody)> {
857 Self::slot_ticket_id(slot).and_then(|id| TicketsData::<T>::get(id).map(|body| (id, body)))
858 }
859
860 fn sort_and_truncate(candidates: &mut Vec<u128>, max_tickets: usize) -> u128 {
862 candidates.sort_unstable();
863 candidates.drain(max_tickets..).for_each(TicketsData::<T>::remove);
864 candidates[max_tickets - 1]
865 }
866
867 pub(crate) fn sort_segments(max_segments: u32, epoch_tag: u8, metadata: &mut TicketsMetadata) {
877 let unsorted_segments_count = metadata.unsorted_tickets_count.div_ceil(SEGMENT_MAX_SIZE);
878 let max_segments = max_segments.min(unsorted_segments_count);
879 let max_tickets = Self::epoch_length() as usize;
880
881 let mut candidates = SortedCandidates::<T>::take().into_inner();
883
884 let mut upper_bound = *candidates.get(max_tickets - 1).unwrap_or(&TicketId::MAX);
887
888 let mut require_sort = false;
889
890 for segment_idx in (0..unsorted_segments_count).rev().take(max_segments as usize) {
893 let segment = UnsortedSegments::<T>::take(segment_idx);
894 metadata.unsorted_tickets_count -= segment.len() as u32;
895
896 let prev_len = candidates.len();
898 for ticket_id in segment {
899 if ticket_id < upper_bound {
900 candidates.push(ticket_id);
901 } else {
902 TicketsData::<T>::remove(ticket_id);
903 }
904 }
905 require_sort = candidates.len() != prev_len;
906
907 if candidates.len() > 2 * max_tickets {
919 upper_bound = Self::sort_and_truncate(&mut candidates, max_tickets);
920 require_sort = false;
921 }
922 }
923
924 if candidates.len() > max_tickets {
925 Self::sort_and_truncate(&mut candidates, max_tickets);
926 } else if require_sort {
927 candidates.sort_unstable();
928 }
929
930 if metadata.unsorted_tickets_count == 0 {
931 candidates.iter().enumerate().for_each(|(i, id)| {
933 TicketsIds::<T>::insert((epoch_tag, i as u32), id);
934 });
935 metadata.tickets_count[epoch_tag as usize] = candidates.len() as u32;
936 } else {
937 SortedCandidates::<T>::set(BoundedVec::truncate_from(candidates));
939 }
940 }
941
942 pub(crate) fn append_tickets(mut tickets: BoundedVec<TicketId, EpochLengthFor<T>>) {
944 debug!(target: LOG_TARGET, "Appending batch with {} tickets", tickets.len());
945 tickets.iter().for_each(|t| trace!(target: LOG_TARGET, " + {t:032x}"));
946
947 let mut metadata = TicketsMeta::<T>::get();
948 let mut segment_idx = metadata.unsorted_tickets_count / SEGMENT_MAX_SIZE;
949
950 while !tickets.is_empty() {
951 let rem = metadata.unsorted_tickets_count % SEGMENT_MAX_SIZE;
952 let to_be_added = tickets.len().min((SEGMENT_MAX_SIZE - rem) as usize);
953
954 let mut segment = UnsortedSegments::<T>::get(segment_idx);
955 let _ = segment
956 .try_extend(tickets.drain(..to_be_added))
957 .defensive_proof("We don't add more than `SEGMENT_MAX_SIZE` and this is the maximum bound for the vector.");
958 UnsortedSegments::<T>::insert(segment_idx, segment);
959
960 metadata.unsorted_tickets_count += to_be_added as u32;
961 segment_idx += 1;
962 }
963
964 TicketsMeta::<T>::set(metadata);
965 }
966
967 fn reset_tickets_data() {
973 let metadata = TicketsMeta::<T>::get();
974
975 for epoch_tag in 0..=1 {
977 for idx in 0..metadata.tickets_count[epoch_tag] {
978 if let Some(id) = TicketsIds::<T>::get((epoch_tag as u8, idx)) {
979 TicketsData::<T>::remove(id);
980 }
981 }
982 }
983
984 let segments_count = metadata.unsorted_tickets_count.div_ceil(SEGMENT_MAX_SIZE);
986 (0..segments_count).for_each(UnsortedSegments::<T>::remove);
987
988 SortedCandidates::<T>::kill();
990
991 TicketsMeta::<T>::kill();
993 }
994
995 pub fn submit_tickets_unsigned_extrinsic(tickets: Vec<TicketEnvelope>) -> bool {
1002 let tickets = BoundedVec::truncate_from(tickets);
1003 let call = Call::submit_tickets { tickets };
1004 let xt = T::create_bare(call.into());
1005 match SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
1006 Ok(_) => true,
1007 Err(e) => {
1008 error!(target: LOG_TARGET, "Error submitting tickets {:?}", e);
1009 false
1010 },
1011 }
1012 }
1013
1014 pub fn epoch_length() -> u32 {
1016 T::EpochLength::get()
1017 }
1018}
1019
1020pub trait EpochChangeTrigger {
1022 fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight;
1028}
1029
1030pub struct EpochChangeExternalTrigger;
1035
1036impl EpochChangeTrigger for EpochChangeExternalTrigger {
1037 fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight {
1038 Weight::zero()
1040 }
1041}
1042
1043pub struct EpochChangeInternalTrigger;
1048
1049impl EpochChangeTrigger for EpochChangeInternalTrigger {
1050 fn trigger<T: Config>(block_num: BlockNumberFor<T>) -> Weight {
1051 if Pallet::<T>::should_end_epoch(block_num) {
1052 let authorities = Pallet::<T>::next_authorities();
1053 let next_authorities = authorities.clone();
1054 let len = next_authorities.len() as u32;
1055 Pallet::<T>::enact_epoch_change(authorities, next_authorities);
1056 T::WeightInfo::enact_epoch_change(len, T::EpochLength::get())
1057 } else {
1058 Weight::zero()
1059 }
1060 }
1061}
1062
1063impl<T: Config> BoundToRuntimeAppPublic for Pallet<T> {
1064 type Public = AuthorityId;
1065}