Introduce a Slot type (#7997)

* Introduce a `Slot` type

Instead of having some type definition that only was used in half of the
code or directly using `u64`, this adds a new unit type wrapper `Slot`.
This makes it especially easy for the outside api to know what type is
expected/returned.

* Change epoch duratioC

* rename all instances of slot number to slot

* Make the constructor private

Co-authored-by: André Silva <andrerfosilva@gmail.com>
This commit is contained in:
Bastian Köcher
2021-01-28 20:44:22 +01:00
committed by GitHub
parent 6c2dd28dfb
commit b6294418f8
34 changed files with 549 additions and 445 deletions
@@ -21,7 +21,7 @@
use codec::{Encode, Decode};
use sc_client_api::backend::AuxStore;
use sp_blockchain::{Result as ClientResult, Error as ClientError};
use sp_consensus_slots::EquivocationProof;
use sp_consensus_slots::{EquivocationProof, Slot};
use sp_runtime::traits::Header;
const SLOT_HEADER_MAP_KEY: &[u8] = b"slot_header_map";
@@ -52,8 +52,8 @@ fn load_decode<C, T>(backend: &C, key: &[u8]) -> ClientResult<Option<T>>
/// Note: it detects equivocations only when slot_now - slot <= MAX_SLOT_CAPACITY.
pub fn check_equivocation<C, H, P>(
backend: &C,
slot_now: u64,
slot: u64,
slot_now: Slot,
slot: Slot,
header: &H,
signer: &P,
) -> ClientResult<Option<EquivocationProof<H, P>>>
@@ -63,7 +63,7 @@ pub fn check_equivocation<C, H, P>(
P: Clone + Encode + Decode + PartialEq,
{
// We don't check equivocations for old headers out of our capacity.
if slot_now.saturating_sub(slot) > MAX_SLOT_CAPACITY {
if slot_now.saturating_sub(*slot) > Slot::from(MAX_SLOT_CAPACITY) {
return Ok(None);
}
@@ -77,7 +77,7 @@ pub fn check_equivocation<C, H, P>(
// Get first slot saved.
let slot_header_start = SLOT_HEADER_START.to_vec();
let first_saved_slot = load_decode::<_, u64>(backend, &slot_header_start[..])?
let first_saved_slot = load_decode::<_, Slot>(backend, &slot_header_start[..])?
.unwrap_or(slot);
if slot_now < first_saved_slot {
@@ -92,7 +92,7 @@ pub fn check_equivocation<C, H, P>(
// 2) with different hash
if header.hash() != prev_header.hash() {
return Ok(Some(EquivocationProof {
slot_number: slot,
slot,
offender: signer.clone(),
first_header: prev_header.clone(),
second_header: header.clone(),
@@ -109,11 +109,11 @@ pub fn check_equivocation<C, H, P>(
let mut keys_to_delete = vec![];
let mut new_first_saved_slot = first_saved_slot;
if slot_now - first_saved_slot >= PRUNING_BOUND {
if *slot_now - *first_saved_slot >= PRUNING_BOUND {
let prefix = SLOT_HEADER_MAP_KEY.to_vec();
new_first_saved_slot = slot_now.saturating_sub(MAX_SLOT_CAPACITY);
for s in first_saved_slot..new_first_saved_slot {
for s in u64::from(first_saved_slot)..new_first_saved_slot.into() {
let mut p = prefix.clone();
s.using_encoded(|s| p.extend(s));
keys_to_delete.push(p);
@@ -174,8 +174,8 @@ mod test {
assert!(
check_equivocation(
&client,
2,
2,
2.into(),
2.into(),
&header1,
&public,
).unwrap().is_none(),
@@ -184,8 +184,8 @@ mod test {
assert!(
check_equivocation(
&client,
3,
2,
3.into(),
2.into(),
&header1,
&public,
).unwrap().is_none(),
@@ -195,8 +195,8 @@ mod test {
assert!(
check_equivocation(
&client,
4,
2,
4.into(),
2.into(),
&header2,
&public,
).unwrap().is_some(),
@@ -206,8 +206,8 @@ mod test {
assert!(
check_equivocation(
&client,
5,
4,
5.into(),
4.into(),
&header3,
&public,
).unwrap().is_none(),
@@ -217,8 +217,8 @@ mod test {
assert!(
check_equivocation(
&client,
PRUNING_BOUND + 2,
MAX_SLOT_CAPACITY + 4,
(PRUNING_BOUND + 2).into(),
(MAX_SLOT_CAPACITY + 4).into(),
&header4,
&public,
).unwrap().is_none(),
@@ -228,8 +228,8 @@ mod test {
assert!(
check_equivocation(
&client,
PRUNING_BOUND + 3,
MAX_SLOT_CAPACITY + 4,
(PRUNING_BOUND + 3).into(),
(MAX_SLOT_CAPACITY + 4).into(),
&header5,
&public,
).unwrap().is_some(),
@@ -239,8 +239,8 @@ mod test {
assert!(
check_equivocation(
&client,
PRUNING_BOUND + 4,
4,
(PRUNING_BOUND + 4).into(),
4.into(),
&header6,
&public,
).unwrap().is_none(),
+64 -58
View File
@@ -41,6 +41,7 @@ use parking_lot::Mutex;
use sp_api::{ProvideRuntimeApi, ApiRef};
use sp_arithmetic::traits::BaseArithmetic;
use sp_consensus::{BlockImport, Proposer, SyncOracle, SelectChain, CanAuthorWith, SlotData, RecordProof};
use sp_consensus_slots::Slot;
use sp_inherents::{InherentData, InherentDataProviders};
use sp_runtime::{
generic::BlockId,
@@ -115,7 +116,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
fn epoch_data(
&self,
header: &B::Header,
slot_number: u64,
slot: Slot,
) -> Result<Self::EpochData, sp_consensus::Error>;
/// Returns the number of authorities given the epoch data.
@@ -126,7 +127,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
fn claim_slot(
&self,
header: &B::Header,
slot_number: u64,
slot: Slot,
epoch_data: &Self::EpochData,
) -> Option<Self::Claim>;
@@ -135,14 +136,14 @@ pub trait SimpleSlotWorker<B: BlockT> {
fn notify_slot(
&self,
_header: &B::Header,
_slot_number: u64,
_slot: Slot,
_epoch_data: &Self::EpochData,
) {}
/// Return the pre digest data to include in a block authored with the given claim.
fn pre_digest_data(
&self,
slot_number: u64,
slot: Slot,
claim: &Self::Claim,
) -> Vec<sp_runtime::DigestItem<B::Hash>>;
@@ -170,7 +171,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
///
/// An example strategy that back offs if the finalized head is lagging too much behind the tip
/// is implemented by [`BackoffAuthoringOnFinalizedHeadLagging`].
fn should_backoff(&self, _slot_number: u64, _chain_head: &B::Header) -> bool {
fn should_backoff(&self, _slot: Slot, _chain_head: &B::Header) -> bool {
false
}
@@ -208,7 +209,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
where
<Self::Proposer as Proposer<B>>::Proposal: Unpin + Send + 'static,
{
let (timestamp, slot_number) = (slot_info.timestamp, slot_info.number);
let (timestamp, slot) = (slot_info.timestamp, slot_info.slot);
let slot_remaining_duration = self.slot_remaining_duration(&slot_info);
let proposing_remaining_duration = self.proposing_remaining_duration(&chain_head, &slot_info);
@@ -218,7 +219,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
debug!(
target: self.logging_target(),
"Skipping proposal slot {} since there's no time left to propose",
slot_number,
slot,
);
return Box::pin(future::ready(None));
@@ -227,7 +228,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
None => Box::new(future::pending()) as Box<_>,
};
let epoch_data = match self.epoch_data(&chain_head, slot_number) {
let epoch_data = match self.epoch_data(&chain_head, slot) {
Ok(epoch_data) => epoch_data,
Err(err) => {
warn!("Unable to fetch epoch data at block {:?}: {:?}", chain_head.hash(), err);
@@ -242,7 +243,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
}
};
self.notify_slot(&chain_head, slot_number, &epoch_data);
self.notify_slot(&chain_head, slot, &epoch_data);
let authorities_len = self.authorities_len(&epoch_data);
@@ -260,38 +261,43 @@ pub trait SimpleSlotWorker<B: BlockT> {
return Box::pin(future::ready(None));
}
let claim = match self.claim_slot(&chain_head, slot_number, &epoch_data) {
let claim = match self.claim_slot(&chain_head, slot, &epoch_data) {
None => return Box::pin(future::ready(None)),
Some(claim) => claim,
};
if self.should_backoff(slot_number, &chain_head) {
if self.should_backoff(slot, &chain_head) {
return Box::pin(future::ready(None));
}
debug!(
target: self.logging_target(),
"Starting authorship at slot {}; timestamp = {}",
slot_number,
slot,
timestamp,
);
telemetry!(CONSENSUS_DEBUG; "slots.starting_authorship";
"slot_num" => slot_number,
telemetry!(
CONSENSUS_DEBUG;
"slots.starting_authorship";
"slot_num" => *slot,
"timestamp" => timestamp,
);
let awaiting_proposer = self.proposer(&chain_head).map_err(move |err| {
warn!("Unable to author block in slot {:?}: {:?}", slot_number, err);
warn!("Unable to author block in slot {:?}: {:?}", slot, err);
telemetry!(CONSENSUS_WARN; "slots.unable_authoring_block";
"slot" => slot_number, "err" => ?err
telemetry!(
CONSENSUS_WARN;
"slots.unable_authoring_block";
"slot" => *slot,
"err" => ?err
);
err
});
let logs = self.pre_digest_data(slot_number, &claim);
let logs = self.pre_digest_data(slot, &claim);
// deadline our production to approx. the end of the slot
let proposing = awaiting_proposer.and_then(move |proposer| proposer.propose(
@@ -307,12 +313,14 @@ pub trait SimpleSlotWorker<B: BlockT> {
futures::future::select(proposing, proposing_remaining).map(move |v| match v {
Either::Left((b, _)) => b.map(|b| (b, claim)),
Either::Right(_) => {
info!("⌛️ Discarding proposal for slot {}; block production took too long", slot_number);
info!("⌛️ Discarding proposal for slot {}; block production took too long", slot);
// If the node was compiled with debug, tell the user to use release optimizations.
#[cfg(build_type="debug")]
info!("👉 Recompile your node in `--release` mode to mitigate this problem.");
telemetry!(CONSENSUS_INFO; "slots.discarding_proposal_took_too_long";
"slot" => slot_number,
telemetry!(
CONSENSUS_INFO;
"slots.discarding_proposal_took_too_long";
"slot" => *slot,
);
Err(sp_consensus::Error::ClientImport("Timeout in the Slots proposer".into()))
@@ -388,7 +396,7 @@ pub trait SlotCompatible {
fn extract_timestamp_and_slot(
&self,
inherent: &InherentData,
) -> Result<(u64, u64, std::time::Duration), sp_consensus::Error>;
) -> Result<(u64, Slot, std::time::Duration), sp_consensus::Error>;
}
/// Start a new slot worker.
@@ -429,12 +437,12 @@ where
return Either::Right(future::ready(Ok(())));
}
let slot_num = slot_info.number;
let slot = slot_info.slot;
let chain_head = match client.best_chain() {
Ok(x) => x,
Err(e) => {
warn!(target: "slots", "Unable to author block in slot {}. \
no best block header: {:?}", slot_num, e);
no best block header: {:?}", slot, e);
return Either::Right(future::ready(Ok(())));
}
};
@@ -444,7 +452,7 @@ where
target: "slots",
"Unable to author block in slot {},. `can_author_with` returned: {} \
Probably a node update is required!",
slot_num,
slot,
err,
);
Either::Right(future::ready(Ok(())))
@@ -465,7 +473,7 @@ where
pub enum CheckedHeader<H, S> {
/// A header which has slot in the future. this is the full header (not stripped)
/// and the slot in which it should be processed.
Deferred(H, u64),
Deferred(H, Slot),
/// A header which is fully checked, including signature. This is the pre-header
/// accompanied by the seal components.
///
@@ -473,8 +481,6 @@ pub enum CheckedHeader<H, S> {
Checked(H, S),
}
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error<T> where T: Debug {
@@ -561,7 +567,7 @@ impl<T: Clone + Send + Sync + 'static> SlotDuration<T> {
/// to parent. If the number of skipped slots is greated than 0 this method will apply
/// an exponential backoff of at most `2^7 * slot_duration`, if no slots were skipped
/// this method will return `None.`
pub fn slot_lenience_exponential(parent_slot: u64, slot_info: &SlotInfo) -> Option<Duration> {
pub fn slot_lenience_exponential(parent_slot: Slot, slot_info: &SlotInfo) -> Option<Duration> {
// never give more than 2^this times the lenience.
const BACKOFF_CAP: u64 = 7;
@@ -574,7 +580,7 @@ pub fn slot_lenience_exponential(parent_slot: u64, slot_info: &SlotInfo) -> Opti
// exponential back-off.
// in normal cases we only attempt to issue blocks up to the end of the slot.
// when the chain has been stalled for a few slots, we give more lenience.
let skipped_slots = slot_info.number.saturating_sub(parent_slot + 1);
let skipped_slots = *slot_info.slot.saturating_sub(parent_slot + 1);
if skipped_slots == 0 {
None
@@ -590,7 +596,7 @@ pub fn slot_lenience_exponential(parent_slot: u64, slot_info: &SlotInfo) -> Opti
/// to parent. If the number of skipped slots is greated than 0 this method will apply
/// a linear backoff of at most `20 * slot_duration`, if no slots were skipped
/// this method will return `None.`
pub fn slot_lenience_linear(parent_slot: u64, slot_info: &SlotInfo) -> Option<Duration> {
pub fn slot_lenience_linear(parent_slot: Slot, slot_info: &SlotInfo) -> Option<Duration> {
// never give more than 20 times more lenience.
const BACKOFF_CAP: u64 = 20;
@@ -600,7 +606,7 @@ pub fn slot_lenience_linear(parent_slot: u64, slot_info: &SlotInfo) -> Option<Du
// linear back-off.
// in normal cases we only attempt to issue blocks up to the end of the slot.
// when the chain has been stalled for a few slots, we give more lenience.
let skipped_slots = slot_info.number.saturating_sub(parent_slot + 1);
let skipped_slots = *slot_info.slot.saturating_sub(parent_slot + 1);
if skipped_slots == 0 {
None
@@ -616,9 +622,9 @@ pub trait BackoffAuthoringBlocksStrategy<N> {
fn should_backoff(
&self,
chain_head_number: N,
chain_head_slot: u64,
chain_head_slot: Slot,
finalized_number: N,
slow_now: u64,
slow_now: Slot,
logging_target: &str,
) -> bool;
}
@@ -663,9 +669,9 @@ where
fn should_backoff(
&self,
chain_head_number: N,
chain_head_slot: u64,
chain_head_slot: Slot,
finalized_number: N,
slot_now: u64,
slot_now: Slot,
logging_target: &str,
) -> bool {
// This should not happen, but we want to keep the previous behaviour if it does.
@@ -683,7 +689,7 @@ where
// If interval is nonzero we backoff if the current slot isn't far enough ahead of the chain
// head.
if slot_now <= chain_head_slot + interval {
if *slot_now <= *chain_head_slot + interval {
info!(
target: logging_target,
"Backing off claiming new slot for block authorship: finality is lagging.",
@@ -699,9 +705,9 @@ impl<N> BackoffAuthoringBlocksStrategy<N> for () {
fn should_backoff(
&self,
_chain_head_number: N,
_chain_head_slot: u64,
_chain_head_slot: Slot,
_finalized_number: N,
_slot_now: u64,
_slot_now: Slot,
_logging_target: &str,
) -> bool {
false
@@ -717,9 +723,9 @@ mod test {
const SLOT_DURATION: Duration = Duration::from_millis(6000);
fn slot(n: u64) -> super::slots::SlotInfo {
fn slot(slot: u64) -> super::slots::SlotInfo {
super::slots::SlotInfo {
number: n,
slot: slot.into(),
duration: SLOT_DURATION.as_millis() as u64,
timestamp: Default::default(),
inherent_data: Default::default(),
@@ -730,20 +736,20 @@ mod test {
#[test]
fn linear_slot_lenience() {
// if no slots are skipped there should be no lenience
assert_eq!(super::slot_lenience_linear(1, &slot(2)), None);
assert_eq!(super::slot_lenience_linear(1.into(), &slot(2)), None);
// otherwise the lenience is incremented linearly with
// the number of skipped slots.
for n in 3..=22 {
assert_eq!(
super::slot_lenience_linear(1, &slot(n)),
super::slot_lenience_linear(1.into(), &slot(n)),
Some(SLOT_DURATION * (n - 2) as u32),
);
}
// but we cap it to a maximum of 20 slots
assert_eq!(
super::slot_lenience_linear(1, &slot(23)),
super::slot_lenience_linear(1.into(), &slot(23)),
Some(SLOT_DURATION * 20),
);
}
@@ -751,24 +757,24 @@ mod test {
#[test]
fn exponential_slot_lenience() {
// if no slots are skipped there should be no lenience
assert_eq!(super::slot_lenience_exponential(1, &slot(2)), None);
assert_eq!(super::slot_lenience_exponential(1.into(), &slot(2)), None);
// otherwise the lenience is incremented exponentially every two slots
for n in 3..=17 {
assert_eq!(
super::slot_lenience_exponential(1, &slot(n)),
super::slot_lenience_exponential(1.into(), &slot(n)),
Some(SLOT_DURATION * 2u32.pow((n / 2 - 1) as u32)),
);
}
// but we cap it to a maximum of 14 slots
assert_eq!(
super::slot_lenience_exponential(1, &slot(18)),
super::slot_lenience_exponential(1.into(), &slot(18)),
Some(SLOT_DURATION * 2u32.pow(7)),
);
assert_eq!(
super::slot_lenience_exponential(1, &slot(19)),
super::slot_lenience_exponential(1.into(), &slot(19)),
Some(SLOT_DURATION * 2u32.pow(7)),
);
}
@@ -808,7 +814,7 @@ mod test {
let slot_now = 2;
let should_backoff: Vec<bool> = (slot_now..1000)
.map(|s| strategy.should_backoff(head_number, head_slot, finalized_number, s, "slots"))
.map(|s| strategy.should_backoff(head_number, head_slot.into(), finalized_number, s.into(), "slots"))
.collect();
// Should always be false, since the head isn't advancing
@@ -833,9 +839,9 @@ mod test {
.map(move |s| {
let b = strategy.should_backoff(
head_number,
head_slot,
head_slot.into(),
finalized_number,
s,
s.into(),
"slots",
);
// Chain is still advancing (by someone else)
@@ -872,7 +878,7 @@ mod test {
let max_interval = strategy.max_interval;
let should_backoff: Vec<bool> = (slot_now..200)
.map(|s| strategy.should_backoff(head_number, head_slot, finalized_number, s, "slots"))
.map(|s| strategy.should_backoff(head_number, head_slot.into(), finalized_number, s.into(), "slots"))
.collect();
// Should backoff (true) until we are `max_interval` number of slots ahead of the chain
@@ -900,9 +906,9 @@ mod test {
<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
&param,
head_state.head_number,
head_state.head_slot,
head_state.head_slot.into(),
finalized_number,
head_state.slot_now,
head_state.slot_now.into(),
"slots",
)
};
@@ -972,9 +978,9 @@ mod test {
<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
&param,
head_state.head_number,
head_state.head_slot,
head_state.head_slot.into(),
finalized_number,
head_state.slot_now,
head_state.slot_now.into(),
"slots",
)
};
@@ -1036,9 +1042,9 @@ mod test {
<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
&param,
head_state.head_number,
head_state.head_slot,
head_state.head_slot.into(),
finalized_number,
head_state.slot_now,
head_state.slot_now.into(),
"slots",
)
};
@@ -20,7 +20,7 @@
//!
//! This is used instead of `futures_timer::Interval` because it was unreliable.
use super::SlotCompatible;
use super::{SlotCompatible, Slot};
use sp_consensus::Error;
use futures::{prelude::*, task::Context, task::Poll};
use sp_inherents::{InherentData, InherentDataProviders};
@@ -48,7 +48,7 @@ pub fn time_until_next(now: Duration, slot_duration: u64) -> Duration {
/// Information about a slot.
pub struct SlotInfo {
/// The slot number.
pub number: u64,
pub slot: Slot,
/// Current timestamp.
pub timestamp: u64,
/// The instant at which the slot ends.
@@ -61,7 +61,7 @@ pub struct SlotInfo {
/// A stream that returns every time there is a new slot.
pub(crate) struct Slots<SC> {
last_slot: u64,
last_slot: Slot,
slot_duration: u64,
inner_delay: Option<Delay>,
inherent_data_providers: InherentDataProviders,
@@ -76,7 +76,7 @@ impl<SC> Slots<SC> {
timestamp_extractor: SC,
) -> Self {
Slots {
last_slot: 0,
last_slot: 0.into(),
slot_duration,
inner_delay: None,
inherent_data_providers,
@@ -114,7 +114,7 @@ impl<SC: SlotCompatible> Stream for Slots<SC> {
Err(err) => return Poll::Ready(Some(Err(sp_consensus::Error::InherentData(err)))),
};
let result = self.timestamp_extractor.extract_timestamp_and_slot(&inherent_data);
let (timestamp, slot_num, offset) = match result {
let (timestamp, slot, offset) = match result {
Ok(v) => v,
Err(err) => return Poll::Ready(Some(Err(err))),
};
@@ -125,11 +125,11 @@ impl<SC: SlotCompatible> Stream for Slots<SC> {
self.inner_delay = Some(Delay::new(ends_in));
// never yield the same slot twice.
if slot_num > self.last_slot {
self.last_slot = slot_num;
if slot > self.last_slot {
self.last_slot = slot;
break Poll::Ready(Some(Ok(SlotInfo {
number: slot_num,
slot,
duration: self.slot_duration,
timestamp,
ends_at,