Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Sassafras Prototype 2.2 #12314

Merged
merged 27 commits into from
Oct 29, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e4364e3
First experiments with Sassafras equivocations report
davxy Sep 12, 2022
b267418
Preparation for sassafras client tests
davxy Sep 20, 2022
1292e86
Cleanup and first working client test with multiple peers
davxy Sep 23, 2022
d0c8bc2
Merge branch 'davxy-sassafras-protocol' into davxy/sassafras-protocol…
davxy Sep 24, 2022
0a20351
Dummy commit
davxy Sep 24, 2022
326ac44
Conflicts resolution
davxy Sep 24, 2022
5b43f28
Test code refactory
davxy Sep 27, 2022
2dd6086
Better submit-tickets extrinsic tag
davxy Sep 27, 2022
829b90b
Refactory of client tests
davxy Oct 6, 2022
a2d66b6
Aux data revert implementation
davxy Oct 6, 2022
ca4b563
Handle skipped epochs on block-import
davxy Oct 7, 2022
e7be289
Skipped epoch test and fix
davxy Oct 11, 2022
7690a5c
Fix to epoch start slot computation
davxy Oct 14, 2022
6c20538
Minor tweaks
davxy Oct 17, 2022
2361064
Trivial comments refactory
davxy Oct 24, 2022
a26a31e
Do not alter original epoch changes node on epoch skip
davxy Oct 24, 2022
f3ebc2b
Insert tickets aux data after block import
davxy Oct 24, 2022
84bfdff
Tests environment refactory
davxy Oct 24, 2022
cfe639e
Use in-memory keystore for tests
davxy Oct 24, 2022
51e81a2
Push lock file
davxy Oct 24, 2022
04e92f6
Use test accounts keyring
davxy Oct 24, 2022
193134a
Test for secondary slots claims
davxy Oct 24, 2022
8bd8aed
Improved tests after epoch changes tree fix
davxy Oct 25, 2022
76c24bd
Tests for blocks verification
davxy Oct 29, 2022
950020b
Next epoch tickets incremental sort
davxy Oct 29, 2022
94e9ee8
Incremental sortition test
davxy Oct 29, 2022
d1a7edd
Set proper tickets tx longevity
davxy Oct 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Next epoch tickets incremental sort
  • Loading branch information
davxy committed Oct 29, 2022
commit 950020b5d741b14ca11009c5d29f3cc2493fd873
91 changes: 53 additions & 38 deletions frame/sassafras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,21 @@ mod mock;
#[cfg(all(feature = "std", test))]
mod tests;

// To manage epoch changes via session pallet instead of the built-in method
// method (`SameAuthoritiesForever`).
pub mod session;

// Re-export pallet symbols.
pub use pallet::*;

/// Tickets related metadata that is commonly used together.
#[derive(Debug, Default, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, Copy)]
pub struct TicketsMetadata {
/// Number of tickets available for even and odd sessions, respectivelly.
/// I.e. the index is computed as session-index modulo 2.
/// Number of tickets available into the tickets buffers.
/// The array index is computed as epoch index modulo 2.
pub tickets_count: [u32; 2],
/// Number of tickets segments
/// Number of outstanding tickets segments requiring to be sorted and stored
/// in one of the epochs tickets buffer
pub segments_count: u32,
}

Expand Down Expand Up @@ -145,15 +149,15 @@ pub mod pallet {
ValueQuery,
>;

/// Next session authorities.
/// Next epoch authorities.
#[pallet::storage]
pub type NextAuthorities<T: Config> = StorageValue<
_,
WeakBoundedVec<(AuthorityId, SassafrasAuthorityWeight), T::MaxAuthorities>,
ValueQuery,
>;

/// The slot at which the first session started.
/// The slot at which the first epoch started.
/// This is `None` until the first block is imported on chain.
#[pallet::storage]
#[pallet::getter(fn genesis_slot)]
Expand All @@ -164,12 +168,12 @@ pub mod pallet {
#[pallet::getter(fn current_slot)]
pub type CurrentSlot<T> = StorageValue<_, Slot, ValueQuery>;

/// Current session randomness.
/// Current epoch randomness.
#[pallet::storage]
#[pallet::getter(fn randomness)]
pub type CurrentRandomness<T> = StorageValue<_, Randomness, ValueQuery>;

/// Next session randomness.
/// Next epoch randomness.
#[pallet::storage]
pub type NextRandomness<T> = StorageValue<_, Randomness, ValueQuery>;

Expand All @@ -194,24 +198,24 @@ pub mod pallet {

/// Pending epoch configuration change that will be set as `NextEpochConfig` when the next
/// epoch is enacted.
/// In other words, a config change submitted during session N will be enacted on session N+2.
/// In other words, a config change submitted during epoch N will be enacted on epoch N+2.
/// This is to maintain coherence for already submitted tickets for epoch N+1 that where
/// computed using configuration parameters stored for session N+1.
/// computed using configuration parameters stored for epoch N+1.
#[pallet::storage]
pub(super) type PendingEpochConfigChange<T> = StorageValue<_, SassafrasEpochConfiguration>;

/// Stored tickets metadata.
#[pallet::storage]
pub type TicketsMeta<T> = StorageValue<_, TicketsMetadata, ValueQuery>;

/// Tickets to be used for current and next session.
/// The key consists of a
/// - `u8` equal to session-index mod 2
/// Tickets to be used for current and next epoch.
/// The key is a tuple composed by:
/// - `u8` equal to epoch-index mod 2
/// - `u32` equal to the slot-index.
#[pallet::storage]
pub type Tickets<T> = StorageMap<_, Identity, (u8, u32), Ticket>;

/// Next session tickets temporary accumulator.
/// Next epoch tickets temporary accumulator.
/// Special `u32::MAX` key is reserved for partially sorted segment.
#[pallet::storage]
pub type NextTicketsSegments<T: Config> =
Expand Down Expand Up @@ -271,9 +275,7 @@ pub mod pallet {

Initialized::<T>::put(pre_digest);

// TODO-SASS-P3: incremental partial ordering for Next epoch tickets.

// Enact session change, if necessary.
// Enact epoch change, if necessary.
T::EpochChangeTrigger::trigger::<T>(now);

Weight::zero()
Expand All @@ -288,14 +290,35 @@ pub mod pallet {
let pre_digest = Initialized::<T>::take()
.expect("Finalization is called after initialization; qed.");
Self::deposit_randomness(pre_digest.vrf_output.as_bytes());

// If we are in the second half of the epoch, we can start sorting the next epoch
// tickets.
let epoch_duration = T::EpochDuration::get();
let current_slot_idx = Self::slot_index(pre_digest.slot);
if current_slot_idx >= epoch_duration / 2 {
let mut metadata = TicketsMeta::<T>::get();
if metadata.segments_count != 0 {
let epoch_idx = EpochIndex::<T>::get() + 1;
let epoch_key = (epoch_idx & 1) as u8;
if metadata.segments_count != 0 {
let slots_left = epoch_duration.checked_sub(current_slot_idx).unwrap_or(1);
Self::sort_tickets(
u32::max(1, metadata.segments_count / slots_left as u32),
epoch_key,
&mut metadata,
);
TicketsMeta::<T>::set(metadata);
}
}
}
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Submit next epoch tickets.
///
/// TODO-SASS-P3: this is an unsigned extrinsic. Can we remov ethe weight?
/// TODO-SASS-P3: this is an unsigned extrinsic. Can we remove the weight?
#[pallet::weight(10_000)]
pub fn submit_tickets(
origin: OriginFor<T>,
Expand All @@ -316,11 +339,11 @@ pub mod pallet {

/// Plan an epoch config change.
///
/// The epoch config change is recorded and will be enacted on the next call to
/// `enact_session_change`.
///
/// The config will be activated one epoch after. Multiple calls to this method will
/// replace any existing planned config change that had not been enacted yet.
/// The epoch config change is recorded and will be announced at the begin of the
/// next epoch together with next epoch authorities information.
/// In other words the configuration will be activated one epoch after.
/// Multiple calls to this method will replace any existing planned config change that had
/// not been enacted yet.
///
/// TODO: TODO-SASS-P4: proper weight
#[pallet::weight(10_000)]
Expand Down Expand Up @@ -443,17 +466,9 @@ pub mod pallet {

// Inherent methods
impl<T: Config> Pallet<T> {
// // TODO-SASS-P2: I don't think this is really required
// /// Determine the Sassafras slot duration based on the Timestamp module configuration.
// pub fn slot_duration() -> T::Moment {
// // We double the minimum block-period so each author can always propose within
// // the majority of their slot.
// <T as pallet_timestamp::Config>::MinimumPeriod::get().saturating_mul(2u32.into())
// }

/// Determine whether an epoch change should take place at this block.
/// Assumes that initialization has already taken place.
pub fn should_end_session(now: T::BlockNumber) -> bool {
pub fn should_end_epoch(now: T::BlockNumber) -> bool {
// The epoch has technically ended during the passage of time between this block and the
// last, but we have to "end" the epoch now, since there is no earlier possible block we
// could have done it.
Expand All @@ -477,15 +492,15 @@ impl<T: Config> Pallet<T> {
slot.checked_sub(Self::current_epoch_start().into()).unwrap_or(u64::MAX)
}

/// DANGEROUS: Enact an epoch change. Should be done on every block where `should_end_session`
/// DANGEROUS: Enact an epoch change. Should be done on every block where `should_end_epoch`
/// has returned `true`, and the caller is the only caller of this function.
///
/// Typically, this is not handled directly by the user, but by higher-level validator-set
/// manager logic like `pallet-session`.
/// Typically, this is not handled directly, but by a higher-level validator-set
/// manager like `pallet-session`.
///
/// If we detect one or more skipped epochs the policy is to use the authorities and values
/// from the first skipped epoch. The tickets are invalidated.
pub(crate) fn enact_session_change(
pub(crate) fn enact_epoch_change(
authorities: WeakBoundedVec<(AuthorityId, SassafrasAuthorityWeight), T::MaxAuthorities>,
next_authorities: WeakBoundedVec<
(AuthorityId, SassafrasAuthorityWeight),
Expand Down Expand Up @@ -693,7 +708,7 @@ impl<T: Config> Pallet<T> {
// Lexicographically sort the tickets who belongs to the next epoch.
// The tickets are fetched from at most `max_iter` segments received via the `submit_tickets`
// extrinsic. The resulting sorted vector is truncated and if all the segments where sorted
// it is saved to be as the next session tickets.
// it is saved to be as the next epoch tickets.
// Else the result is saved to be used by next calls.
fn sort_tickets(max_iter: u32, epoch_key: u8, metadata: &mut TicketsMetadata) {
let mut segments_count = metadata.segments_count;
Expand Down Expand Up @@ -807,11 +822,11 @@ pub struct SameAuthoritiesForever;

impl EpochChangeTrigger for SameAuthoritiesForever {
fn trigger<T: Config>(now: T::BlockNumber) {
if <Pallet<T>>::should_end_session(now) {
if <Pallet<T>>::should_end_epoch(now) {
let authorities = <Pallet<T>>::authorities();
let next_authorities = authorities.clone();

<Pallet<T>>::enact_session_change(authorities, next_authorities);
<Pallet<T>>::enact_epoch_change(authorities, next_authorities);
}
}
}
Expand Down
25 changes: 19 additions & 6 deletions frame/sassafras/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,13 @@ frame_support::construct_runtime!(
}
);

/// Build and returns test storage externalities
pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities {
new_test_ext_with_pairs(authorities_len).1
}

/// Build and returns test storage externalities and authority set pairs used
/// by Sassafras genesis configuration.
pub fn new_test_ext_with_pairs(
authorities_len: usize,
) -> (Vec<AuthorityPair>, sp_io::TestExternalities) {
Expand All @@ -120,13 +123,16 @@ pub fn new_test_ext_with_pairs(

let authorities = pairs.iter().map(|p| (p.public(), 1)).collect();

let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
let mut storage = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();

let config = pallet_sassafras::GenesisConfig { authorities, epoch_config: Default::default() };
<pallet_sassafras::GenesisConfig as GenesisBuild<Test>>::assimilate_storage(&config, &mut t)
.unwrap();
<pallet_sassafras::GenesisConfig as GenesisBuild<Test>>::assimilate_storage(
&config,
&mut storage,
)
.unwrap();

(pairs, t.into())
(pairs, storage.into())
}

fn make_ticket_vrf(slot: Slot, attempt: u32, pair: &AuthorityPair) -> (VRFOutput, VRFProof) {
Expand All @@ -150,6 +156,8 @@ fn make_ticket_vrf(slot: Slot, attempt: u32, pair: &AuthorityPair) -> (VRFOutput
(output, proof)
}

/// Construct at most `attempts` tickets for the given `slot`.
/// TODO-SASS-P3: filter out invalid tickets according to test threshold.
pub fn make_tickets(slot: Slot, attempts: u32, pair: &AuthorityPair) -> Vec<(VRFOutput, VRFProof)> {
(0..attempts)
.into_iter()
Expand All @@ -163,7 +171,7 @@ fn make_slot_vrf(slot: Slot, pair: &AuthorityPair) -> (VRFOutput, VRFProof) {
let mut epoch = Sassafras::epoch_index();
let mut randomness = Sassafras::randomness();

// Check if epoch is going to change on initialization
// Check if epoch is going to change on initialization.
let epoch_start = Sassafras::current_epoch_start();
if epoch_start != 0_u64 && slot >= epoch_start + EPOCH_DURATION {
epoch += slot.saturating_sub(epoch_start).saturating_div(EPOCH_DURATION);
Expand All @@ -178,6 +186,7 @@ fn make_slot_vrf(slot: Slot, pair: &AuthorityPair) -> (VRFOutput, VRFProof) {
(output, proof)
}

/// Produce a `PreDigest` instance for the given parameters.
pub fn make_pre_digest(
authority_idx: AuthorityIndex,
slot: Slot,
Expand All @@ -187,6 +196,8 @@ pub fn make_pre_digest(
PreDigest { authority_idx, slot, vrf_output, vrf_proof, ticket_aux: None }
}

/// Produce a `PreDigest` instance for the given parameters and wrap the result into a `Digest`
/// instance.
pub fn make_wrapped_pre_digest(
authority_idx: AuthorityIndex,
slot: Slot,
Expand All @@ -198,6 +209,7 @@ pub fn make_wrapped_pre_digest(
Digest { logs: vec![log] }
}

/// Progress the pallet state up to the given block `number` and `slot`.
pub fn go_to_block(number: u64, slot: Slot, pair: &AuthorityPair) -> Digest {
Sassafras::on_finalize(System::block_number());
let parent_hash = System::finalize().hash();
Expand All @@ -211,7 +223,8 @@ pub fn go_to_block(number: u64, slot: Slot, pair: &AuthorityPair) -> Digest {
digest
}

/// Slots will grow accordingly to blocks
/// Progress the pallet state up to the given block `number`.
/// Slots will grow linearly accordingly to blocks.
pub fn progress_to_block(number: u64, pair: &AuthorityPair) -> Option<Digest> {
let mut slot = Sassafras::current_slot() + 1;
let mut digest = None;
Expand Down
4 changes: 2 additions & 2 deletions frame/sassafras/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl<T: Config> ShouldEndSession<T::BlockNumber> for Pallet<T> {
// possible that Sassafras's own `on_initialize` has not run yet, so let's ensure that we
// have initialized the pallet and updated the current slot.
Self::on_initialize(now);
Self::should_end_session(now)
Self::should_end_epoch(now)
}
}

Expand Down Expand Up @@ -66,7 +66,7 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
),
);

Self::enact_session_change(bounded_authorities, next_bounded_authorities)
Self::enact_epoch_change(bounded_authorities, next_bounded_authorities)
}

fn on_disabled(i: u32) {
Expand Down
Loading