mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 01:11:10 +00:00
Make consensus SlotWorker don't assume a slot is time / duration (#7441)
* Make consensus `SlotWorker` don't assume a slot is time / duration This removes the last bit of assumption that a slot is always `time / duration`. This will be required by parachains where a slot will be the relay chain block number. Besides this there are also some other drive by changes. One more notable is that `on_slot` now returns a `SlotResult` that holds the block and a potential storage proof. To simplify the implementation and usage of the `SimpleSlotWorker` the `SlotWorker` trait is now implemented for each type that implements `SimpleSlotWorker`. * Update client/consensus/slots/src/lib.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Update client/consensus/slots/src/lib.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
mod slots;
|
||||
mod aux_schema;
|
||||
|
||||
pub use slots::{SignedDuration, SlotInfo};
|
||||
pub use slots::SlotInfo;
|
||||
use slots::Slots;
|
||||
pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
|
||||
|
||||
@@ -48,13 +48,29 @@ use parking_lot::Mutex;
|
||||
pub type StorageChanges<Transaction, Block> =
|
||||
sp_state_machine::StorageChanges<Transaction, HashFor<Block>, NumberFor<Block>>;
|
||||
|
||||
/// The result of [`SlotWorker::on_slot`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SlotResult<Block: BlockT> {
|
||||
/// The block that was built.
|
||||
pub block: Block,
|
||||
/// The optional storage proof that was calculated while building the block.
|
||||
///
|
||||
/// This needs to be enabled for the proposer to get this storage proof.
|
||||
pub storage_proof: Option<sp_trie::StorageProof>,
|
||||
}
|
||||
|
||||
/// A worker that should be invoked at every new slot.
|
||||
///
|
||||
/// The implementation should not make any assumptions of the slot being bound to the time or
|
||||
/// similar. The only valid assumption is that the slot number is always increasing.
|
||||
pub trait SlotWorker<B: BlockT> {
|
||||
/// The type of the future that will be returned when a new slot is
|
||||
/// triggered.
|
||||
type OnSlot: Future<Output = Result<(), sp_consensus::Error>>;
|
||||
/// The type of the future that will be returned when a new slot is triggered.
|
||||
type OnSlot: Future<Output = Option<SlotResult<B>>>;
|
||||
|
||||
/// Called when a new slot is triggered.
|
||||
///
|
||||
/// Returns a future that resolves to a [`SlotResult`] iff a block was successfully built in
|
||||
/// the slot. Otherwise `None` is returned.
|
||||
fn on_slot(&mut self, chain_head: B::Header, slot_info: SlotInfo) -> Self::OnSlot;
|
||||
}
|
||||
|
||||
@@ -90,7 +106,11 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
|
||||
/// Returns the epoch data necessary for authoring. For time-dependent epochs,
|
||||
/// use the provided slot number as a canonical source of time.
|
||||
fn epoch_data(&self, header: &B::Header, slot_number: u64) -> Result<Self::EpochData, sp_consensus::Error>;
|
||||
fn epoch_data(
|
||||
&self,
|
||||
header: &B::Header,
|
||||
slot_number: u64,
|
||||
) -> Result<Self::EpochData, sp_consensus::Error>;
|
||||
|
||||
/// Returns the number of authorities given the epoch data.
|
||||
/// None indicate that the authorities information is incomplete.
|
||||
@@ -111,7 +131,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
_header: &B::Header,
|
||||
_slot_number: u64,
|
||||
_epoch_data: &Self::EpochData,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
/// Return the pre digest data to include in a block authored with the given claim.
|
||||
fn pre_digest_data(
|
||||
@@ -158,32 +178,38 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
fn proposing_remaining_duration(
|
||||
&self,
|
||||
_head: &B::Header,
|
||||
slot_info: &SlotInfo
|
||||
slot_info: &SlotInfo,
|
||||
) -> Option<Duration> {
|
||||
Some(self.slot_remaining_duration(slot_info))
|
||||
}
|
||||
|
||||
/// Implements the `on_slot` functionality from `SlotWorker`.
|
||||
fn on_slot(&mut self, chain_head: B::Header, slot_info: SlotInfo)
|
||||
-> Pin<Box<dyn Future<Output = Result<(), sp_consensus::Error>> + Send>> where
|
||||
Self: Send + Sync,
|
||||
/// Implements [`SlotWorker::on_slot`].
|
||||
fn on_slot(
|
||||
&mut self,
|
||||
chain_head: B::Header,
|
||||
slot_info: SlotInfo,
|
||||
) -> Pin<Box<dyn Future<Output = Option<SlotResult<B>>> + Send>>
|
||||
where
|
||||
<Self::Proposer as Proposer<B>>::Proposal: Unpin + Send + 'static,
|
||||
{
|
||||
let (timestamp, slot_number, slot_duration) =
|
||||
(slot_info.timestamp, slot_info.number, slot_info.duration);
|
||||
let (timestamp, slot_number) = (slot_info.timestamp, slot_info.number);
|
||||
|
||||
{
|
||||
let slot_now = SignedDuration::default().slot_now(slot_duration);
|
||||
if slot_now > slot_number {
|
||||
// if this is behind, return.
|
||||
debug!(target: self.logging_target(),
|
||||
"Skipping proposal slot {} since our current view is {}",
|
||||
slot_number, slot_now,
|
||||
let slot_remaining_duration = self.slot_remaining_duration(&slot_info);
|
||||
let proposing_remaining_duration = self.proposing_remaining_duration(&chain_head, &slot_info);
|
||||
|
||||
let proposing_remaining = match proposing_remaining_duration {
|
||||
Some(r) if r.as_secs() == 0 && r.as_nanos() == 0 => {
|
||||
debug!(
|
||||
target: self.logging_target(),
|
||||
"Skipping proposal slot {} since there's no time left to propose",
|
||||
slot_number,
|
||||
);
|
||||
|
||||
return Box::pin(future::ready(Ok(())));
|
||||
}
|
||||
}
|
||||
return Box::pin(future::ready(None));
|
||||
},
|
||||
Some(r) => Box::new(Delay::new(r)) as Box<dyn Future<Output = ()> + Unpin + Send>,
|
||||
None => Box::new(future::pending()) as Box<_>,
|
||||
};
|
||||
|
||||
let epoch_data = match self.epoch_data(&chain_head, slot_number) {
|
||||
Ok(epoch_data) => epoch_data,
|
||||
@@ -196,7 +222,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
"err" => ?err,
|
||||
);
|
||||
|
||||
return Box::pin(future::ready(Ok(())));
|
||||
return Box::pin(future::ready(None));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,16 +241,17 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
"authorities_len" => authorities_len,
|
||||
);
|
||||
|
||||
return Box::pin(future::ready(Ok(())));
|
||||
return Box::pin(future::ready(None));
|
||||
}
|
||||
|
||||
let claim = match self.claim_slot(&chain_head, slot_number, &epoch_data) {
|
||||
None => return Box::pin(future::ready(Ok(()))),
|
||||
None => return Box::pin(future::ready(None)),
|
||||
Some(claim) => claim,
|
||||
};
|
||||
|
||||
debug!(
|
||||
target: self.logging_target(), "Starting authorship at slot {}; timestamp = {}",
|
||||
target: self.logging_target(),
|
||||
"Starting authorship at slot {}; timestamp = {}",
|
||||
slot_number,
|
||||
timestamp,
|
||||
);
|
||||
@@ -244,8 +271,6 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
err
|
||||
});
|
||||
|
||||
let slot_remaining_duration = self.slot_remaining_duration(&slot_info);
|
||||
let proposing_remaining_duration = self.proposing_remaining_duration(&chain_head, &slot_info);
|
||||
let logs = self.pre_digest_data(slot_number, &claim);
|
||||
|
||||
// deadline our production to approx. the end of the slot
|
||||
@@ -258,15 +283,10 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
RecordProof::No,
|
||||
).map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e))));
|
||||
|
||||
let delay: Box<dyn Future<Output=()> + Unpin + Send> = match proposing_remaining_duration {
|
||||
Some(r) => Box::new(Delay::new(r)),
|
||||
None => Box::new(future::pending()),
|
||||
};
|
||||
|
||||
let proposal_work =
|
||||
Box::new(futures::future::select(proposing, delay).map(move |v| match v {
|
||||
futures::future::Either::Left((b, _)) => b.map(|b| (b, claim)),
|
||||
futures::future::Either::Right(_) => {
|
||||
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);
|
||||
// If the node was compiled with debug, tell the user to use release optimizations.
|
||||
#[cfg(build_type="debug")]
|
||||
@@ -274,16 +294,18 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
telemetry!(CONSENSUS_INFO; "slots.discarding_proposal_took_too_long";
|
||||
"slot" => slot_number,
|
||||
);
|
||||
|
||||
Err(sp_consensus::Error::ClientImport("Timeout in the Slots proposer".into()))
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
let block_import_params_maker = self.block_import_params();
|
||||
let block_import = self.block_import();
|
||||
let logging_target = self.logging_target();
|
||||
|
||||
Box::pin(proposal_work.and_then(move |(proposal, claim)| {
|
||||
let (header, body) = proposal.block.deconstruct();
|
||||
proposal_work.and_then(move |(proposal, claim)| async move {
|
||||
let (block, storage_proof) = (proposal.block, proposal.proof);
|
||||
let (header, body) = block.clone().deconstruct();
|
||||
let header_num = *header.number();
|
||||
let header_hash = header.hash();
|
||||
let parent_hash = *header.parent_hash();
|
||||
@@ -295,12 +317,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
proposal.storage_changes,
|
||||
claim,
|
||||
epoch_data,
|
||||
);
|
||||
|
||||
let block_import_params = match block_import_params {
|
||||
Ok(params) => params,
|
||||
Err(e) => return future::err(e),
|
||||
};
|
||||
)?;
|
||||
|
||||
info!(
|
||||
"🔖 Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
|
||||
@@ -316,18 +333,32 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
);
|
||||
|
||||
if let Err(err) = block_import.lock().import_block(block_import_params, Default::default()) {
|
||||
warn!(target: logging_target,
|
||||
warn!(
|
||||
target: logging_target,
|
||||
"Error with block built on {:?}: {:?}",
|
||||
parent_hash,
|
||||
err,
|
||||
);
|
||||
|
||||
telemetry!(CONSENSUS_WARN; "slots.err_with_block_built_on";
|
||||
"hash" => ?parent_hash, "err" => ?err,
|
||||
telemetry!(
|
||||
CONSENSUS_WARN; "slots.err_with_block_built_on";
|
||||
"hash" => ?parent_hash,
|
||||
"err" => ?err,
|
||||
);
|
||||
}
|
||||
future::ready(Ok(()))
|
||||
}))
|
||||
|
||||
Ok(SlotResult { block, storage_proof })
|
||||
}).then(|r| async move {
|
||||
r.map_err(|e| warn!(target: "slots", "Encountered consensus error: {:?}", e)).ok()
|
||||
}).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT, T: SimpleSlotWorker<B>> SlotWorker<B> for T {
|
||||
type OnSlot = Pin<Box<dyn Future<Output = Option<SlotResult<B>>> + Send>>;
|
||||
|
||||
fn on_slot(&mut self, chain_head: B::Header, slot_info: SlotInfo) -> Self::OnSlot {
|
||||
SimpleSlotWorker::on_slot(self, chain_head, slot_info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,10 +369,6 @@ pub trait SlotCompatible {
|
||||
&self,
|
||||
inherent: &InherentData,
|
||||
) -> Result<(u64, u64, std::time::Duration), sp_consensus::Error>;
|
||||
|
||||
/// Get the difference between chain time and local time. Defaults to
|
||||
/// always returning zero.
|
||||
fn time_offset() -> SignedDuration { Default::default() }
|
||||
}
|
||||
|
||||
/// Start a new slot worker.
|
||||
@@ -403,11 +430,7 @@ where
|
||||
Either::Right(future::ready(Ok(())))
|
||||
} else {
|
||||
Either::Left(
|
||||
worker.on_slot(chain_head, slot_info)
|
||||
.map_err(|e| {
|
||||
warn!(target: "slots", "Encountered consensus error: {:?}", e);
|
||||
})
|
||||
.or_else(|_| future::ready(Ok(())))
|
||||
worker.on_slot(chain_head, slot_info).then(|_| future::ready(Ok(())))
|
||||
)
|
||||
}
|
||||
}).then(|res| {
|
||||
@@ -569,7 +592,6 @@ mod test {
|
||||
fn slot(n: u64) -> super::slots::SlotInfo {
|
||||
super::slots::SlotInfo {
|
||||
number: n,
|
||||
last_number: n - 1,
|
||||
duration: SLOT_DURATION.as_millis() as u64,
|
||||
timestamp: Default::default(),
|
||||
inherent_data: Default::default(),
|
||||
|
||||
@@ -37,30 +37,6 @@ pub fn duration_now() -> Duration {
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
/// A `Duration` with a sign (before or after). Immutable.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct SignedDuration {
|
||||
offset: Duration,
|
||||
is_positive: bool,
|
||||
}
|
||||
|
||||
impl SignedDuration {
|
||||
/// Construct a `SignedDuration`
|
||||
pub fn new(offset: Duration, is_positive: bool) -> Self {
|
||||
Self { offset, is_positive }
|
||||
}
|
||||
|
||||
/// Get the slot for now. Panics if `slot_duration` is 0.
|
||||
pub fn slot_now(&self, slot_duration: u64) -> u64 {
|
||||
(if self.is_positive {
|
||||
duration_now() + self.offset
|
||||
} else {
|
||||
duration_now() - self.offset
|
||||
}.as_millis() as u64) / slot_duration
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the duration until the next slot, based on current duration since
|
||||
pub fn time_until_next(now: Duration, slot_duration: u64) -> Duration {
|
||||
let remaining_full_millis = slot_duration - (now.as_millis() as u64 % slot_duration) - 1;
|
||||
@@ -71,8 +47,6 @@ pub fn time_until_next(now: Duration, slot_duration: u64) -> Duration {
|
||||
pub struct SlotInfo {
|
||||
/// The slot number.
|
||||
pub number: u64,
|
||||
/// The last slot number produced.
|
||||
pub last_number: u64,
|
||||
/// Current timestamp.
|
||||
pub timestamp: u64,
|
||||
/// The instant at which the slot ends.
|
||||
@@ -150,13 +124,11 @@ impl<SC: SlotCompatible> Stream for Slots<SC> {
|
||||
|
||||
// never yield the same slot twice.
|
||||
if slot_num > self.last_slot {
|
||||
let last_slot = self.last_slot;
|
||||
self.last_slot = slot_num;
|
||||
|
||||
break Poll::Ready(Some(Ok(SlotInfo {
|
||||
number: slot_num,
|
||||
duration: self.slot_duration,
|
||||
last_number: last_slot,
|
||||
timestamp,
|
||||
ends_at,
|
||||
inherent_data,
|
||||
@@ -166,5 +138,4 @@ impl<SC: SlotCompatible> Stream for Slots<SC> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<SC> Unpin for Slots<SC> {
|
||||
}
|
||||
impl<SC> Unpin for Slots<SC> {}
|
||||
|
||||
Reference in New Issue
Block a user