babe: support online configuration upgrades (#5514)

* babe: support online configuration upgrades

* Switch to use NextConfigDescriptor instead of changing runtime interface

* Fix tests

* epoch-changes: map function that allows converting with different epoch types

* Add migration script for the epoch config change

* Fix migration tests

* Fix migration: Epoch should be EpochV0

* Update client/consensus/babe/src/lib.rs

Co-Authored-By: André Silva <123550+andresilva@users.noreply.github.com>

* Fix new epochChanges version

* Fix unused imports

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
Wei Tang
2020-04-24 15:59:14 +02:00
committed by GitHub
parent a01e608dff
commit 770cc24c47
12 changed files with 300 additions and 86 deletions
+96 -42
View File
@@ -59,11 +59,13 @@
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub use sp_consensus_babe::{
BabeApi, ConsensusLog, BABE_ENGINE_ID, SlotNumber, BabeConfiguration,
BabeApi, ConsensusLog, BABE_ENGINE_ID, SlotNumber,
BabeEpochConfiguration, BabeGenesisConfiguration,
AuthorityId, AuthorityPair, AuthoritySignature,
BabeAuthorityWeight, VRF_OUTPUT_LENGTH,
digests::{
CompatibleDigestItem, NextEpochDescriptor, PreDigest, PrimaryPreDigest, SecondaryPreDigest,
CompatibleDigestItem, NextEpochDescriptor, NextConfigDescriptor,
PreDigest, PrimaryPreDigest, SecondaryPreDigest,
},
};
pub use sp_consensus::SyncOracle;
@@ -118,36 +120,43 @@ use sp_api::ApiExt;
mod aux_schema;
mod verification;
mod migration;
pub mod authorship;
#[cfg(test)]
mod tests;
/// BABE epoch information
#[derive(Decode, Encode, Default, PartialEq, Eq, Clone, Debug)]
#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)]
pub struct Epoch {
/// The epoch index
/// The epoch index.
pub epoch_index: u64,
/// The starting slot of the epoch,
/// The starting slot of the epoch.
pub start_slot: SlotNumber,
/// The duration of this epoch
/// The duration of this epoch.
pub duration: SlotNumber,
/// The authorities and their weights
/// The authorities and their weights.
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
/// Randomness for this epoch
/// Randomness for this epoch.
pub randomness: [u8; VRF_OUTPUT_LENGTH],
/// Configuration of the epoch.
pub config: BabeEpochConfiguration,
}
impl EpochT for Epoch {
type NextEpochDescriptor = NextEpochDescriptor;
type NextEpochDescriptor = (NextEpochDescriptor, BabeEpochConfiguration);
type SlotNumber = SlotNumber;
fn increment(&self, descriptor: NextEpochDescriptor) -> Epoch {
fn increment(
&self,
(descriptor, config): (NextEpochDescriptor, BabeEpochConfiguration)
) -> Epoch {
Epoch {
epoch_index: self.epoch_index + 1,
start_slot: self.start_slot + self.duration,
duration: self.duration,
authorities: descriptor.authorities,
randomness: descriptor.randomness,
config,
}
}
@@ -160,6 +169,27 @@ impl EpochT for Epoch {
}
}
impl Epoch {
/// Create the genesis epoch (epoch #0). This is defined to start at the slot of
/// the first block, so that has to be provided.
pub fn genesis(
genesis_config: &BabeGenesisConfiguration,
slot_number: SlotNumber
) -> Epoch {
Epoch {
epoch_index: 0,
start_slot: slot_number,
duration: genesis_config.epoch_length,
authorities: genesis_config.genesis_authorities.clone(),
randomness: genesis_config.randomness.clone(),
config: BabeEpochConfiguration {
c: genesis_config.c,
secondary_slots: genesis_config.secondary_slots,
},
}
}
}
#[derive(derive_more::Display, Debug)]
enum Error<B: BlockT> {
#[display(fmt = "Multiple BABE pre-runtime digests, rejecting!")]
@@ -168,6 +198,8 @@ enum Error<B: BlockT> {
NoPreRuntimeDigest,
#[display(fmt = "Multiple BABE epoch change digests, rejecting!")]
MultipleEpochChangeDigests,
#[display(fmt = "Multiple BABE config change digests, rejecting!")]
MultipleConfigChangeDigests,
#[display(fmt = "Could not extract timestamp and slot: {:?}", _0)]
Extraction(sp_consensus::Error),
#[display(fmt = "Could not fetch epoch at {:?}", _0)]
@@ -200,6 +232,8 @@ enum Error<B: BlockT> {
FetchParentHeader(sp_blockchain::Error),
#[display(fmt = "Expected epoch change to happen at {:?}, s{}", _0, _1)]
ExpectedEpochChange(B::Hash, u64),
#[display(fmt = "Unexpected config change")]
UnexpectedConfigChange,
#[display(fmt = "Unexpected epoch change")]
UnexpectedEpochChange,
#[display(fmt = "Parent block of {} has no associated weight", _0)]
@@ -236,7 +270,7 @@ pub static INTERMEDIATE_KEY: &[u8] = b"babe1";
// and `super::babe::Config` can be eliminated.
// https://github.com/paritytech/substrate/issues/2434
#[derive(Clone)]
pub struct Config(sc_consensus_slots::SlotDuration<BabeConfiguration>);
pub struct Config(sc_consensus_slots::SlotDuration<BabeGenesisConfiguration>);
impl Config {
/// Either fetch the slot duration from disk or compute it from the genesis
@@ -253,24 +287,12 @@ impl Config {
}
}
}
/// Create the genesis epoch (epoch #0). This is defined to start at the slot of
/// the first block, so that has to be provided.
pub fn genesis_epoch(&self, slot_number: SlotNumber) -> Epoch {
Epoch {
epoch_index: 0,
start_slot: slot_number,
duration: self.epoch_length,
authorities: self.genesis_authorities.clone(),
randomness: self.randomness.clone(),
}
}
}
impl std::ops::Deref for Config {
type Target = BabeConfiguration;
type Target = BabeGenesisConfiguration;
fn deref(&self) -> &BabeConfiguration {
fn deref(&self) -> &BabeGenesisConfiguration {
&*self.0
}
}
@@ -428,7 +450,7 @@ impl<B, C, E, I, Error, SO> sc_consensus_slots::SimpleSlotWorker<B> for BabeWork
fn authorities_len(&self, epoch_descriptor: &Self::EpochData) -> Option<usize> {
self.epoch_changes.lock()
.viable_epoch(&epoch_descriptor, |slot| self.config.genesis_epoch(slot))
.viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot))
.map(|epoch| epoch.as_ref().authorities.len())
}
@@ -443,9 +465,8 @@ impl<B, C, E, I, Error, SO> sc_consensus_slots::SimpleSlotWorker<B> for BabeWork
slot_number,
self.epoch_changes.lock().viable_epoch(
&epoch_descriptor,
|slot| self.config.genesis_epoch(slot)
|slot| Epoch::genesis(&self.config, slot)
)?.as_ref(),
&*self.config,
&self.keystore,
);
@@ -599,6 +620,24 @@ fn find_next_epoch_digest<B: BlockT>(header: &B::Header)
Ok(epoch_digest)
}
/// Extract the BABE config change digest from the given header, if it exists.
fn find_next_config_digest<B: BlockT>(header: &B::Header)
-> Result<Option<NextConfigDescriptor>, Error<B>>
where DigestItemFor<B>: CompatibleDigestItem,
{
let mut config_digest: Option<_> = None;
for log in header.digest().logs() {
trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log);
let log = log.try_to::<ConsensusLog>(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID));
match (log, config_digest.is_some()) {
(Some(ConsensusLog::NextConfigData(_)), true) => return Err(babe_err(Error::MultipleConfigChangeDigests)),
(Some(ConsensusLog::NextConfigData(config)), false) => config_digest = Some(config),
_ => trace!(target: "babe", "Ignoring digest not meant for us"),
}
}
Ok(config_digest)
}
#[derive(Default, Clone)]
struct TimeSource(Arc<Mutex<(Option<Duration>, Vec<(Instant, u64)>)>>);
@@ -726,7 +765,7 @@ impl<Block, Client> Verifier<Block> for BabeVerifier<Block, Client> where
.ok_or_else(|| Error::<Block>::FetchEpoch(parent_hash))?;
let viable_epoch = epoch_changes.viable_epoch(
&epoch_descriptor,
|slot| self.config.genesis_epoch(slot)
|slot| Epoch::genesis(&self.config, slot)
).ok_or_else(|| Error::<Block>::FetchEpoch(parent_hash))?;
// We add one to the current slot to allow for some small drift.
@@ -736,7 +775,6 @@ impl<Block, Client> Verifier<Block> for BabeVerifier<Block, Client> where
pre_digest: Some(pre_digest.clone()),
slot_now: slot_now + 1,
epoch: viable_epoch.as_ref(),
config: &self.config,
};
match verification::check_header::<Block>(v_params)? {
@@ -958,19 +996,32 @@ impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client,
// search for this all the time so we can reject unexpected announcements.
let next_epoch_digest = find_next_epoch_digest::<Block>(&block.header)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
let next_config_digest = find_next_config_digest::<Block>(&block.header)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
match (first_in_epoch, next_epoch_digest.is_some()) {
(true, true) => {},
(false, false) => {},
(true, false) => {
match (first_in_epoch, next_epoch_digest.is_some(), next_config_digest.is_some()) {
(true, true, _) => {},
(false, false, false) => {},
(false, false, true) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::UnexpectedConfigChange).into(),
)
)
},
(true, false, _) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::ExpectedEpochChange(hash, slot_number)).into(),
)
);
)
},
(false, true) => {
return Err(ConsensusError::ClientImport(Error::<Block>::UnexpectedEpochChange.into()));
(false, true, _) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::UnexpectedEpochChange).into(),
)
)
},
}
@@ -985,11 +1036,15 @@ impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client,
let viable_epoch = epoch_changes.viable_epoch(
&epoch_descriptor,
|slot| self.config.genesis_epoch(slot),
|slot| Epoch::genesis(&self.config, slot)
).ok_or_else(|| {
ConsensusError::ClientImport(Error::<Block>::FetchEpoch(parent_hash).into())
})?;
let epoch_config = next_config_digest.unwrap_or_else(
|| viable_epoch.as_ref().config.clone()
);
// restrict info logging during initial sync to avoid spam
let log_level = if block.origin == BlockOrigin::NetworkInitialSync {
log::Level::Debug
@@ -1006,7 +1061,7 @@ impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client,
viable_epoch.as_ref().start_slot,
);
let next_epoch = viable_epoch.increment(next_epoch_descriptor);
let next_epoch = viable_epoch.increment((next_epoch_descriptor, epoch_config));
log!(target: "babe",
log_level,
@@ -1152,7 +1207,7 @@ pub fn block_import<Client, Block: BlockT, I>(
) -> ClientResult<(BabeBlockImport<Block, Client, I>, BabeLink<Block>)> where
Client: AuxStore + HeaderBackend<Block> + HeaderMetadata<Block, Error = sp_blockchain::Error>,
{
let epoch_changes = aux_schema::load_epoch_changes::<Block, _>(&*client)?;
let epoch_changes = aux_schema::load_epoch_changes::<Block, _>(&*client, &config)?;
let link = BabeLink {
epoch_changes: epoch_changes.clone(),
time_source: Default::default(),
@@ -1245,13 +1300,12 @@ pub mod test_helpers {
&parent.hash(),
parent.number().clone(),
slot_number,
|slot| link.config.genesis_epoch(slot),
|slot| Epoch::genesis(&link.config, slot),
).unwrap().unwrap();
authorship::claim_slot(
slot_number,
&epoch,
&link.config,
keystore,
).map(|(digest, _)| digest)
}