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

babe: allow skipping over empty epochs #11727

Merged
merged 20 commits into from
Dec 24, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
62 changes: 58 additions & 4 deletions client/consensus/babe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvid
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
generic::{BlockId, OpaqueDigestItemId},
traits::{Block as BlockT, Header, NumberFor, SaturatedConversion, Saturating, Zero},
traits::{Block as BlockT, Header, NumberFor, One, SaturatedConversion, Saturating, Zero},
DigestItem,
};

Expand Down Expand Up @@ -1355,15 +1355,43 @@ pub struct BabeBlockImport<Block: BlockT, Client, I> {
inner: I,
client: Arc<Client>,
epoch_changes: SharedEpochChanges<Block, Epoch>,
genesis_slot: Option<Slot>,
config: Config,
}

impl<Block: BlockT, Client, I> BabeBlockImport<Block, Client, I>
where
Client: HeaderBackend<Block>,
{
fn genesis_slot(&mut self) -> Option<Slot> {
if self.genesis_slot.is_some() {
return self.genesis_slot.clone()
}

let genesis_slot =
self.client.header(BlockId::Number(One::one())).ok().flatten().map(|header| {
let pre_digest = find_pre_digest::<Block>(&header).expect(
"valid babe headers must contain a predigest; header has been already verified; qed",
);

pre_digest.slot()
});

if genesis_slot.is_some() {
self.genesis_slot = genesis_slot.clone();
andresilva marked this conversation as resolved.
Show resolved Hide resolved
}

genesis_slot
}
}

impl<Block: BlockT, I: Clone, Client> Clone for BabeBlockImport<Block, Client, I> {
fn clone(&self) -> Self {
BabeBlockImport {
inner: self.inner.clone(),
client: self.client.clone(),
epoch_changes: self.epoch_changes.clone(),
genesis_slot: self.genesis_slot.clone(),
config: self.config.clone(),
}
}
Expand All @@ -1376,7 +1404,7 @@ impl<Block: BlockT, Client, I> BabeBlockImport<Block, Client, I> {
block_import: I,
config: Config,
) -> Self {
BabeBlockImport { client, inner: block_import, epoch_changes, config }
BabeBlockImport { client, inner: block_import, epoch_changes, config, genesis_slot: None }
}
}

Expand Down Expand Up @@ -1515,6 +1543,8 @@ where
))
}

let genesis_slot = self.genesis_slot();

// if there's a pending epoch we'll save the previous epoch changes here
// this way we can revert it if there's any error
let mut old_epoch_changes = None;
Expand Down Expand Up @@ -1581,8 +1611,8 @@ where
if let Some(next_epoch_descriptor) = next_epoch_digest {
old_epoch_changes = Some((*epoch_changes).clone());

let viable_epoch = epoch_changes
.viable_epoch(&epoch_descriptor, |slot| {
let mut viable_epoch = epoch_changes
.viable_epoch_mut(&epoch_descriptor, |slot| {
Epoch::genesis(&self.config.genesis_config, slot)
})
.ok_or_else(|| {
Expand All @@ -1600,6 +1630,29 @@ where
log::Level::Info
};

if let Some(genesis_slot) = genesis_slot {
let epoch_index = sp_consensus_babe::epoch_index(
slot,
genesis_slot,
viable_epoch.as_ref().duration,
);

if epoch_index != viable_epoch.as_ref().epoch_index {
warn!(target: "babe", "👶 Epoch(s) skipped: from {} to {}",
viable_epoch.as_ref().epoch_index, epoch_index,
);

let epoch_start = sp_consensus_babe::epoch_start_slot(
epoch_index,
genesis_slot,
viable_epoch.as_ref().duration,
);

viable_epoch.as_mut().epoch_index = epoch_index;
viable_epoch.as_mut().start_slot = epoch_start;
}
}

log!(target: "babe",
log_level,
"👶 New epoch {} launching at block {} (block slot {} >= start slot {}).",
Expand Down Expand Up @@ -1627,6 +1680,7 @@ where
let prune_and_import = || {
prune_finalized(self.client.clone(), &mut epoch_changes)?;

epoch_changes.sync();
andresilva marked this conversation as resolved.
Show resolved Hide resolved
epoch_changes
.import(
descendent_query(&*self.client),
Expand Down
22 changes: 22 additions & 0 deletions client/consensus/epochs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,28 @@ where
}
}

pub fn sync(&mut self) {
self.inner = self.inner.clone().map(&mut |hash, number, epoch_header| {
let epoch = self.epochs.get(&(*hash, *number)).unwrap();

let epoch_data = match epoch {
PersistedEpoch::Genesis(..) => return epoch_header,
PersistedEpoch::Regular(epoch_data) => epoch_data,
};

let epoch_header = match epoch_header {
PersistedEpochHeader::Genesis(..) => epoch_header,
PersistedEpochHeader::Regular(mut epoch_header) => {
epoch_header.start_slot = epoch_data.start_slot();
epoch_header.end_slot = epoch_data.end_slot();
PersistedEpochHeader::Regular(epoch_header)
},
};

epoch_header
});
}

/// Prune out finalized epochs, except for the ancestor of the finalized
/// block. The given slot should be the slot number at which the finalized
/// block was authored.
Expand Down
49 changes: 29 additions & 20 deletions frame/babe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,10 +574,19 @@ impl<T: Config> Pallet<T> {
// by the session module to be called before this.
debug_assert!(Self::initialized().is_some());

// Update epoch index
let epoch_index = EpochIndex::<T>::get()
.checked_add(1)
.expect("epoch indices will never reach 2^64 before the death of the universe; qed");
// Update epoch index.
//
// NOTE: we figure out the epoch index from the slot, which may not
// necessarily be contiguous if the chain was offline for more than
// `T::EpochDuration` slots. When skipping from epoch N to e.g. N+4, we
// will be using the randomness and authorities for that epoch that had
// been previously announced for epoch N+1, and the randomness collected
// during the current epoch (N) will be used for epoch N+5.
let epoch_index = sp_consensus_babe::epoch_index(
CurrentSlot::<T>::get(),
GenesisSlot::<T>::get(),
T::EpochDuration::get(),
);

EpochIndex::<T>::put(epoch_index);
Authorities::<T>::put(authorities);
Expand Down Expand Up @@ -624,11 +633,16 @@ impl<T: Config> Pallet<T> {
}
}

/// Finds the start slot of the current epoch. only guaranteed to
/// give correct results after `initialize` of the first block
/// in the chain (as its result is based off of `GenesisSlot`).
/// Finds the start slot of the current epoch.
///
/// Only guaranteed to give correct results after `initialize` of the first
/// block in the chain (as its result is based off of `GenesisSlot`).
pub fn current_epoch_start() -> Slot {
Self::epoch_start(EpochIndex::<T>::get())
sp_consensus_babe::epoch_start_slot(
EpochIndex::<T>::get(),
GenesisSlot::<T>::get(),
T::EpochDuration::get(),
)
}

/// Produces information about the current epoch.
Expand All @@ -652,9 +666,15 @@ impl<T: Config> Pallet<T> {
if u64 is not enough we should crash for safety; qed.",
);

let start_slot = sp_consensus_babe::epoch_start_slot(
next_epoch_index,
GenesisSlot::<T>::get(),
T::EpochDuration::get(),
);

Epoch {
epoch_index: next_epoch_index,
start_slot: Self::epoch_start(next_epoch_index),
start_slot,
duration: T::EpochDuration::get(),
authorities: NextAuthorities::<T>::get().to_vec(),
randomness: NextRandomness::<T>::get(),
Expand All @@ -666,17 +686,6 @@ impl<T: Config> Pallet<T> {
}
}

fn epoch_start(epoch_index: u64) -> Slot {
// (epoch_index * epoch_duration) + genesis_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::EpochDuration::get()).expect(PROOF);

epoch_start.checked_add(*GenesisSlot::<T>::get()).expect(PROOF).into()
}

fn deposit_consensus<U: Encode>(new: U) {
let log = DigestItem::Consensus(BABE_ENGINE_ID, new.encode());
<frame_system::Pallet<T>>::deposit_log(log)
Expand Down
21 changes: 21 additions & 0 deletions primitives/consensus/babe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,27 @@ pub struct Epoch {
pub config: BabeEpochConfiguration,
}

/// Returns the epoch index the given slot belongs to.
pub fn epoch_index(slot: Slot, genesis_slot: Slot, epoch_duration: u64) -> u64 {
if slot < genesis_slot {
return 0
}

(*slot - *genesis_slot) / epoch_duration
andresilva marked this conversation as resolved.
Show resolved Hide resolved
}

/// Returns the first slot at the given epoch index.
pub fn epoch_start_slot(epoch_index: u64, genesis_slot: Slot, epoch_duration: u64) -> Slot {
// (epoch_index * epoch_duration) + genesis_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(epoch_duration).expect(PROOF);

epoch_start.checked_add(*genesis_slot).expect(PROOF).into()
andresilva marked this conversation as resolved.
Show resolved Hide resolved
}

sp_api::decl_runtime_apis! {
/// API necessary for block authorship with BABE.
#[api_version(2)]
Expand Down