#![deny(warnings)]
#![warn(unused_must_use, unsafe_code, unused_variables, unused_imports, missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use codec::{Decode, Encode, MaxEncodedLen};
use log::{debug, error, trace, warn};
use scale_info::TypeInfo;
use alloc::vec::Vec;
use frame_support::{
dispatch::{DispatchResultWithPostInfo, Pays},
traits::{Defensive, Get},
weights::Weight,
BoundedVec, WeakBoundedVec,
};
use frame_system::{
offchain::{CreateInherent, SubmitTransaction},
pallet_prelude::BlockNumberFor,
};
use sp_consensus_sassafras::{
digests::{ConsensusLog, NextEpochDescriptor, SlotClaim},
vrf, AuthorityId, Epoch, EpochConfiguration, Randomness, Slot, TicketBody, TicketEnvelope,
TicketId, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID,
};
use sp_io::hashing;
use sp_runtime::{
generic::DigestItem,
traits::{One, Zero},
BoundToRuntimeAppPublic,
};
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;
pub mod weights;
pub use weights::WeightInfo;
pub use pallet::*;
const LOG_TARGET: &str = "sassafras::runtime";
const RANDOMNESS_VRF_CONTEXT: &[u8] = b"SassafrasOnChainRandomness";
const SEGMENT_MAX_SIZE: u32 = 128;
pub type AuthoritiesVec<T> = WeakBoundedVec<AuthorityId, <T as Config>::MaxAuthorities>;
pub type EpochLengthFor<T> = <T as Config>::EpochLength;
#[derive(Debug, Default, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, Copy)]
pub struct TicketsMetadata {
pub unsorted_tickets_count: u32,
pub tickets_count: [u32; 2],
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config + CreateInherent<Call<Self>> {
#[pallet::constant]
type EpochLength: Get<u32>;
#[pallet::constant]
type MaxAuthorities: Get<u32>;
type EpochChangeTrigger: EpochChangeTrigger;
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
InvalidConfiguration,
}
#[pallet::storage]
#[pallet::getter(fn epoch_index)]
pub type EpochIndex<T> = StorageValue<_, u64, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn authorities)]
pub type Authorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn next_authorities)]
pub type NextAuthorities<T: Config> = StorageValue<_, AuthoritiesVec<T>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn genesis_slot)]
pub type GenesisSlot<T> = StorageValue<_, Slot, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn current_slot)]
pub type CurrentSlot<T> = StorageValue<_, Slot, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn randomness)]
pub type CurrentRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn next_randomness)]
pub type NextRandomness<T> = StorageValue<_, Randomness, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn randomness_accumulator)]
pub(crate) type RandomnessAccumulator<T> = StorageValue<_, Randomness, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn config)]
pub type EpochConfig<T> = StorageValue<_, EpochConfiguration, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn next_config)]
pub type NextEpochConfig<T> = StorageValue<_, EpochConfiguration>;
#[pallet::storage]
pub type PendingEpochConfigChange<T> = StorageValue<_, EpochConfiguration>;
#[pallet::storage]
pub type TicketsMeta<T> = StorageValue<_, TicketsMetadata, ValueQuery>;
#[pallet::storage]
pub type TicketsIds<T> = StorageMap<_, Identity, (u8, u32), TicketId>;
#[pallet::storage]
pub type TicketsData<T> = StorageMap<_, Identity, TicketId, TicketBody>;
#[pallet::storage]
pub type UnsortedSegments<T: Config> =
StorageMap<_, Identity, u32, BoundedVec<TicketId, ConstU32<SEGMENT_MAX_SIZE>>, ValueQuery>;
#[pallet::storage]
pub type SortedCandidates<T> =
StorageValue<_, BoundedVec<TicketId, EpochLengthFor<T>>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn ring_context)]
pub type RingContext<T: Config> = StorageValue<_, vrf::RingContext>;
#[pallet::storage]
pub type RingVerifierData<T: Config> = StorageValue<_, vrf::RingVerifierData>;
#[pallet::storage]
pub(crate) type ClaimTemporaryData<T> = StorageValue<_, vrf::VrfPreOutput>;
#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub authorities: Vec<AuthorityId>,
pub epoch_config: EpochConfiguration,
#[serde(skip)]
pub _phantom: core::marker::PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
EpochConfig::<T>::put(self.epoch_config);
Pallet::<T>::genesis_authorities_initialize(&self.authorities);
#[cfg(feature = "construct-dummy-ring-context")]
{
debug!(target: LOG_TARGET, "Constructing dummy ring context");
let ring_ctx = vrf::RingContext::new_testing();
RingContext::<T>::put(ring_ctx);
Pallet::<T>::update_ring_verifier(&self.authorities);
}
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(block_num: BlockNumberFor<T>) -> Weight {
debug_assert_eq!(block_num, frame_system::Pallet::<T>::block_number());
let claim = <frame_system::Pallet<T>>::digest()
.logs
.iter()
.find_map(|item| item.pre_runtime_try_to::<SlotClaim>(&SASSAFRAS_ENGINE_ID))
.expect("Valid block must have a slot claim. qed");
CurrentSlot::<T>::put(claim.slot);
if block_num == One::one() {
Self::post_genesis_initialize(claim.slot);
}
let randomness_pre_output = claim
.vrf_signature
.pre_outputs
.get(0)
.expect("Valid claim must have VRF signature; qed");
ClaimTemporaryData::<T>::put(randomness_pre_output);
let trigger_weight = T::EpochChangeTrigger::trigger::<T>(block_num);
T::WeightInfo::on_initialize() + trigger_weight
}
fn on_finalize(_: BlockNumberFor<T>) {
let randomness_input = vrf::slot_claim_input(
&Self::randomness(),
CurrentSlot::<T>::get(),
EpochIndex::<T>::get(),
);
let randomness_pre_output = ClaimTemporaryData::<T>::take()
.expect("Unconditionally populated in `on_initialize`; `on_finalize` is always called after; qed");
let randomness = randomness_pre_output
.make_bytes::<RANDOMNESS_LENGTH>(RANDOMNESS_VRF_CONTEXT, &randomness_input);
Self::deposit_slot_randomness(&randomness);
let epoch_length = T::EpochLength::get();
let current_slot_idx = Self::current_slot_index();
if current_slot_idx >= epoch_length / 2 {
let mut metadata = TicketsMeta::<T>::get();
if metadata.unsorted_tickets_count != 0 {
let next_epoch_idx = EpochIndex::<T>::get() + 1;
let next_epoch_tag = (next_epoch_idx & 1) as u8;
let slots_left = epoch_length.checked_sub(current_slot_idx).unwrap_or(1);
Self::sort_segments(
metadata
.unsorted_tickets_count
.div_ceil(SEGMENT_MAX_SIZE * slots_left as u32),
next_epoch_tag,
&mut metadata,
);
TicketsMeta::<T>::set(metadata);
}
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::submit_tickets(tickets.len() as u32))]
pub fn submit_tickets(
origin: OriginFor<T>,
tickets: BoundedVec<TicketEnvelope, EpochLengthFor<T>>,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
debug!(target: LOG_TARGET, "Received {} tickets", tickets.len());
let epoch_length = T::EpochLength::get();
let current_slot_idx = Self::current_slot_index();
if current_slot_idx > epoch_length / 2 {
warn!(target: LOG_TARGET, "Tickets shall be submitted in the first epoch half",);
return Err("Tickets shall be submitted in the first epoch half".into())
}
let Some(verifier) = RingVerifierData::<T>::get().map(|v| v.into()) else {
warn!(target: LOG_TARGET, "Ring verifier key not initialized");
return Err("Ring verifier key not initialized".into())
};
let next_authorities = Self::next_authorities();
let next_config = Self::next_config().unwrap_or_else(|| Self::config());
let ticket_threshold = sp_consensus_sassafras::ticket_id_threshold(
next_config.redundancy_factor,
epoch_length as u32,
next_config.attempts_number,
next_authorities.len() as u32,
);
let randomness = NextRandomness::<T>::get();
let epoch_idx = EpochIndex::<T>::get() + 1;
let mut valid_tickets = BoundedVec::with_bounded_capacity(tickets.len());
for ticket in tickets {
debug!(target: LOG_TARGET, "Checking ring proof");
let Some(ticket_id_pre_output) = ticket.signature.pre_outputs.get(0) else {
debug!(target: LOG_TARGET, "Missing ticket VRF pre-output from ring signature");
continue
};
let ticket_id_input =
vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx);
let ticket_id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_pre_output);
if ticket_id >= ticket_threshold {
debug!(target: LOG_TARGET, "Ignoring ticket over threshold ({:032x} >= {:032x})", ticket_id, ticket_threshold);
continue
}
if TicketsData::<T>::contains_key(ticket_id) {
debug!(target: LOG_TARGET, "Ignoring duplicate ticket ({:032x})", ticket_id);
continue
}
let sign_data = vrf::ticket_body_sign_data(&ticket.body, ticket_id_input);
if !ticket.signature.ring_vrf_verify(&sign_data, &verifier) {
debug!(target: LOG_TARGET, "Proof verification failure for ticket ({:032x})", ticket_id);
continue
}
if let Ok(_) = valid_tickets.try_push(ticket_id).defensive_proof(
"Input segment has same length as bounded destination vector; qed",
) {
TicketsData::<T>::set(ticket_id, Some(ticket.body));
}
}
if !valid_tickets.is_empty() {
Self::append_tickets(valid_tickets);
}
Ok(Pays::No.into())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::plan_config_change())]
pub fn plan_config_change(
origin: OriginFor<T>,
config: EpochConfiguration,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(
config.redundancy_factor != 0 && config.attempts_number != 0,
Error::<T>::InvalidConfiguration
);
PendingEpochConfigChange::<T>::put(config);
Ok(())
}
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
let Call::submit_tickets { tickets } = call else {
return InvalidTransaction::Call.into()
};
if source == TransactionSource::External {
warn!(
target: LOG_TARGET,
"Rejecting unsigned `submit_tickets` transaction from external source",
);
return InvalidTransaction::BadSigner.into()
}
let epoch_length = T::EpochLength::get();
let current_slot_idx = Self::current_slot_index();
if current_slot_idx > epoch_length / 2 {
warn!(target: LOG_TARGET, "Tickets shall be proposed in the first epoch half",);
return InvalidTransaction::Stale.into()
}
let tickets_longevity = epoch_length / 2 - current_slot_idx;
let tickets_tag = tickets.using_encoded(|bytes| hashing::blake2_256(bytes));
ValidTransaction::with_tag_prefix("Sassafras")
.priority(TransactionPriority::max_value())
.longevity(tickets_longevity as u64)
.and_provides(tickets_tag)
.propagate(true)
.build()
}
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn should_end_epoch(block_num: BlockNumberFor<T>) -> bool {
block_num > One::one() && Self::current_slot_index() >= T::EpochLength::get()
}
fn current_slot_index() -> u32 {
Self::slot_index(CurrentSlot::<T>::get())
}
fn slot_index(slot: Slot) -> u32 {
slot.checked_sub(*Self::current_epoch_start())
.and_then(|v| v.try_into().ok())
.unwrap_or(u32::MAX)
}
fn current_epoch_start() -> Slot {
Self::epoch_start(EpochIndex::<T>::get())
}
fn epoch_start(epoch_index: u64) -> Slot {
const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \
if u64 is not enough we should crash for safety; qed.";
let epoch_start = epoch_index.checked_mul(T::EpochLength::get() as u64).expect(PROOF);
GenesisSlot::<T>::get().checked_add(epoch_start).expect(PROOF).into()
}
pub(crate) fn update_ring_verifier(authorities: &[AuthorityId]) {
debug!(target: LOG_TARGET, "Loading ring context");
let Some(ring_ctx) = RingContext::<T>::get() else {
debug!(target: LOG_TARGET, "Ring context not initialized");
return
};
let pks: Vec<_> = authorities.iter().map(|auth| *auth.as_ref()).collect();
debug!(target: LOG_TARGET, "Building ring verifier (ring size: {})", pks.len());
let verifier_data = ring_ctx
.verifier_data(&pks)
.expect("Failed to build ring verifier. This is a bug");
RingVerifierData::<T>::put(verifier_data);
}
pub(crate) fn enact_epoch_change(
authorities: WeakBoundedVec<AuthorityId, T::MaxAuthorities>,
next_authorities: WeakBoundedVec<AuthorityId, T::MaxAuthorities>,
) {
if next_authorities != authorities {
Self::update_ring_verifier(&next_authorities);
}
Authorities::<T>::put(&authorities);
NextAuthorities::<T>::put(&next_authorities);
let mut epoch_idx = EpochIndex::<T>::get() + 1;
let slot_idx = CurrentSlot::<T>::get().saturating_sub(Self::epoch_start(epoch_idx));
if slot_idx >= T::EpochLength::get() {
Self::reset_tickets_data();
let skipped_epochs = *slot_idx / T::EpochLength::get() as u64;
epoch_idx += skipped_epochs;
warn!(
target: LOG_TARGET,
"Detected {} skipped epochs, resuming from epoch {}",
skipped_epochs,
epoch_idx
);
}
let mut metadata = TicketsMeta::<T>::get();
let mut metadata_dirty = false;
EpochIndex::<T>::put(epoch_idx);
let next_epoch_idx = epoch_idx + 1;
let next_randomness = Self::update_epoch_randomness(next_epoch_idx);
if let Some(config) = NextEpochConfig::<T>::take() {
EpochConfig::<T>::put(config);
}
let next_config = PendingEpochConfigChange::<T>::take();
if let Some(next_config) = next_config {
NextEpochConfig::<T>::put(next_config);
}
let next_epoch = NextEpochDescriptor {
randomness: next_randomness,
authorities: next_authorities.into_inner(),
config: next_config,
};
Self::deposit_next_epoch_descriptor_digest(next_epoch);
let epoch_tag = (epoch_idx & 1) as u8;
if metadata.unsorted_tickets_count != 0 {
Self::sort_segments(u32::MAX, epoch_tag, &mut metadata);
metadata_dirty = true;
}
let prev_epoch_tag = epoch_tag ^ 1;
let prev_epoch_tickets_count = &mut metadata.tickets_count[prev_epoch_tag as usize];
if *prev_epoch_tickets_count != 0 {
for idx in 0..*prev_epoch_tickets_count {
if let Some(ticket_id) = TicketsIds::<T>::get((prev_epoch_tag, idx)) {
TicketsData::<T>::remove(ticket_id);
}
}
*prev_epoch_tickets_count = 0;
metadata_dirty = true;
}
if metadata_dirty {
TicketsMeta::<T>::set(metadata);
}
}
fn update_epoch_randomness(next_epoch_index: u64) -> Randomness {
let curr_epoch_randomness = NextRandomness::<T>::get();
CurrentRandomness::<T>::put(curr_epoch_randomness);
let accumulator = RandomnessAccumulator::<T>::get();
let mut buf = [0; RANDOMNESS_LENGTH + 8];
buf[..RANDOMNESS_LENGTH].copy_from_slice(&accumulator[..]);
buf[RANDOMNESS_LENGTH..].copy_from_slice(&next_epoch_index.to_le_bytes());
let next_randomness = hashing::blake2_256(&buf);
NextRandomness::<T>::put(&next_randomness);
next_randomness
}
fn deposit_slot_randomness(randomness: &Randomness) {
let accumulator = RandomnessAccumulator::<T>::get();
let mut buf = [0; 2 * RANDOMNESS_LENGTH];
buf[..RANDOMNESS_LENGTH].copy_from_slice(&accumulator[..]);
buf[RANDOMNESS_LENGTH..].copy_from_slice(&randomness[..]);
let accumulator = hashing::blake2_256(&buf);
RandomnessAccumulator::<T>::put(accumulator);
}
fn deposit_next_epoch_descriptor_digest(desc: NextEpochDescriptor) {
let item = ConsensusLog::NextEpochData(desc);
let log = DigestItem::Consensus(SASSAFRAS_ENGINE_ID, item.encode());
<frame_system::Pallet<T>>::deposit_log(log)
}
fn genesis_authorities_initialize(authorities: &[AuthorityId]) {
let prev_authorities = Authorities::<T>::get();
if !prev_authorities.is_empty() {
if prev_authorities.as_slice() == authorities {
return
} else {
panic!("Authorities were already initialized");
}
}
let authorities = WeakBoundedVec::try_from(authorities.to_vec())
.expect("Initial number of authorities should be lower than T::MaxAuthorities");
Authorities::<T>::put(&authorities);
NextAuthorities::<T>::put(&authorities);
}
fn post_genesis_initialize(slot: Slot) {
GenesisSlot::<T>::put(slot);
let genesis_hash = frame_system::Pallet::<T>::parent_hash();
let mut buf = genesis_hash.as_ref().to_vec();
buf.extend_from_slice(&slot.to_le_bytes());
let randomness = hashing::blake2_256(buf.as_slice());
RandomnessAccumulator::<T>::put(randomness);
let next_randomness = Self::update_epoch_randomness(1);
let next_epoch = NextEpochDescriptor {
randomness: next_randomness,
authorities: Self::next_authorities().into_inner(),
config: None,
};
Self::deposit_next_epoch_descriptor_digest(next_epoch);
}
pub fn current_epoch() -> Epoch {
let index = EpochIndex::<T>::get();
Epoch {
index,
start: Self::epoch_start(index),
length: T::EpochLength::get(),
authorities: Self::authorities().into_inner(),
randomness: Self::randomness(),
config: Self::config(),
}
}
pub fn next_epoch() -> Epoch {
let index = EpochIndex::<T>::get() + 1;
Epoch {
index,
start: Self::epoch_start(index),
length: T::EpochLength::get(),
authorities: Self::next_authorities().into_inner(),
randomness: Self::next_randomness(),
config: Self::next_config().unwrap_or_else(|| Self::config()),
}
}
pub fn slot_ticket_id(slot: Slot) -> Option<TicketId> {
if frame_system::Pallet::<T>::block_number().is_zero() {
return None
}
let epoch_idx = EpochIndex::<T>::get();
let epoch_len = T::EpochLength::get();
let mut slot_idx = Self::slot_index(slot);
let mut metadata = TicketsMeta::<T>::get();
let get_ticket_idx = |slot_idx| {
let ticket_idx = if slot_idx < epoch_len / 2 {
2 * slot_idx + 1
} else {
2 * (epoch_len - (slot_idx + 1))
};
debug!(
target: LOG_TARGET,
"slot-idx {} <-> ticket-idx {}",
slot_idx,
ticket_idx
);
ticket_idx as u32
};
let mut epoch_tag = (epoch_idx & 1) as u8;
if epoch_len <= slot_idx && slot_idx < 2 * epoch_len {
epoch_tag ^= 1;
slot_idx -= epoch_len;
if metadata.unsorted_tickets_count != 0 {
Self::sort_segments(u32::MAX, epoch_tag, &mut metadata);
TicketsMeta::<T>::set(metadata);
}
} else if slot_idx >= 2 * epoch_len {
return None
}
let ticket_idx = get_ticket_idx(slot_idx);
if ticket_idx < metadata.tickets_count[epoch_tag as usize] {
TicketsIds::<T>::get((epoch_tag, ticket_idx))
} else {
None
}
}
pub fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketBody)> {
Self::slot_ticket_id(slot).and_then(|id| TicketsData::<T>::get(id).map(|body| (id, body)))
}
fn sort_and_truncate(candidates: &mut Vec<u128>, max_tickets: usize) -> u128 {
candidates.sort_unstable();
candidates.drain(max_tickets..).for_each(TicketsData::<T>::remove);
candidates[max_tickets - 1]
}
pub(crate) fn sort_segments(max_segments: u32, epoch_tag: u8, metadata: &mut TicketsMetadata) {
let unsorted_segments_count = metadata.unsorted_tickets_count.div_ceil(SEGMENT_MAX_SIZE);
let max_segments = max_segments.min(unsorted_segments_count);
let max_tickets = Self::epoch_length() as usize;
let mut candidates = SortedCandidates::<T>::take().into_inner();
let mut upper_bound = *candidates.get(max_tickets - 1).unwrap_or(&TicketId::MAX);
let mut require_sort = false;
for segment_idx in (0..unsorted_segments_count).rev().take(max_segments as usize) {
let segment = UnsortedSegments::<T>::take(segment_idx);
metadata.unsorted_tickets_count -= segment.len() as u32;
let prev_len = candidates.len();
for ticket_id in segment {
if ticket_id < upper_bound {
candidates.push(ticket_id);
} else {
TicketsData::<T>::remove(ticket_id);
}
}
require_sort = candidates.len() != prev_len;
if candidates.len() > 2 * max_tickets {
upper_bound = Self::sort_and_truncate(&mut candidates, max_tickets);
require_sort = false;
}
}
if candidates.len() > max_tickets {
Self::sort_and_truncate(&mut candidates, max_tickets);
} else if require_sort {
candidates.sort_unstable();
}
if metadata.unsorted_tickets_count == 0 {
candidates.iter().enumerate().for_each(|(i, id)| {
TicketsIds::<T>::insert((epoch_tag, i as u32), id);
});
metadata.tickets_count[epoch_tag as usize] = candidates.len() as u32;
} else {
SortedCandidates::<T>::set(BoundedVec::truncate_from(candidates));
}
}
pub(crate) fn append_tickets(mut tickets: BoundedVec<TicketId, EpochLengthFor<T>>) {
debug!(target: LOG_TARGET, "Appending batch with {} tickets", tickets.len());
tickets.iter().for_each(|t| trace!(target: LOG_TARGET, " + {t:032x}"));
let mut metadata = TicketsMeta::<T>::get();
let mut segment_idx = metadata.unsorted_tickets_count / SEGMENT_MAX_SIZE;
while !tickets.is_empty() {
let rem = metadata.unsorted_tickets_count % SEGMENT_MAX_SIZE;
let to_be_added = tickets.len().min((SEGMENT_MAX_SIZE - rem) as usize);
let mut segment = UnsortedSegments::<T>::get(segment_idx);
let _ = segment
.try_extend(tickets.drain(..to_be_added))
.defensive_proof("We don't add more than `SEGMENT_MAX_SIZE` and this is the maximum bound for the vector.");
UnsortedSegments::<T>::insert(segment_idx, segment);
metadata.unsorted_tickets_count += to_be_added as u32;
segment_idx += 1;
}
TicketsMeta::<T>::set(metadata);
}
fn reset_tickets_data() {
let metadata = TicketsMeta::<T>::get();
for epoch_tag in 0..=1 {
for idx in 0..metadata.tickets_count[epoch_tag] {
if let Some(id) = TicketsIds::<T>::get((epoch_tag as u8, idx)) {
TicketsData::<T>::remove(id);
}
}
}
let segments_count = metadata.unsorted_tickets_count.div_ceil(SEGMENT_MAX_SIZE);
(0..segments_count).for_each(UnsortedSegments::<T>::remove);
SortedCandidates::<T>::kill();
TicketsMeta::<T>::kill();
}
pub fn submit_tickets_unsigned_extrinsic(tickets: Vec<TicketEnvelope>) -> bool {
let tickets = BoundedVec::truncate_from(tickets);
let call = Call::submit_tickets { tickets };
let xt = T::create_inherent(call.into());
match SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
Ok(_) => true,
Err(e) => {
error!(target: LOG_TARGET, "Error submitting tickets {:?}", e);
false
},
}
}
pub fn epoch_length() -> u32 {
T::EpochLength::get()
}
}
pub trait EpochChangeTrigger {
fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight;
}
pub struct EpochChangeExternalTrigger;
impl EpochChangeTrigger for EpochChangeExternalTrigger {
fn trigger<T: Config>(_: BlockNumberFor<T>) -> Weight {
Weight::zero()
}
}
pub struct EpochChangeInternalTrigger;
impl EpochChangeTrigger for EpochChangeInternalTrigger {
fn trigger<T: Config>(block_num: BlockNumberFor<T>) -> Weight {
if Pallet::<T>::should_end_epoch(block_num) {
let authorities = Pallet::<T>::next_authorities();
let next_authorities = authorities.clone();
let len = next_authorities.len() as u32;
Pallet::<T>::enact_epoch_change(authorities, next_authorities);
T::WeightInfo::enact_epoch_change(len, T::EpochLength::get())
} else {
Weight::zero()
}
}
}
impl<T: Config> BoundToRuntimeAppPublic for Pallet<T> {
type Public = AuthorityId;
}