Allow BabeConsensusDataProvider fork existing chain (#7078)

* parent affbc38afd
author Seun Lanlege <seunlanlege@gmail.com> 1599568164 +0100
committer Seun Lanlege <seunlanlege@gmail.com> 1604321289 +0100
gpgsig -----BEGIN PGP SIGNATURE-----

 iQGzBAABCgAdFiEECvQ02MnjnssnSbjr3HzzEhjN254FAl+gAAkACgkQ3HzzEhjN
 254soAv+KO5JA0HXSe0R0XS5TnwA3IxYsW+UvdF5dXFeC3jFdGTMvor818uoBePD
 dxzYEsUK6gjsNcM9+hpFhoy5JnUrUPInd2BZ7pmZiDuXmYJrHi0s7K5qL0EYDoe0
 m1egPNNyRR125ozJ24M+09c3OQsi3bvTx1TJaV9Aov8hK4So8UmlJTHWpkLw97ku
 HuTre2IPSFbV4GwJE40V+KNuDVHxaKL7zrInYScqbr6/hOTqBCvFn4ib3CjpF5HG
 zDAA5S2PrcbL9NQOothVcVB/TZr3IkhglCFqEjVyCX80IL0JkNZkw8jAh0B8uqXx
 Ug/c1/Mssa8F1jLZMmW45Cway60txqVbcWntPJAymGJbrRErOO/++oUrV0u1C65u
 LW7gXAaIJWQTX9KnX0SEyejNod7ubZktBz7n5WfkJAPIzdw5wtJalhLa673YTgQ9
 zyTPKiWjJj2myCq1AYrJvlK8hSsIBqbBFcUf1zX4SzZWKS+5mtp51o4gfVzcCRPd
 z/6/iPbB
 =g5tx
 -----END PGP SIGNATURE-----

BabeConsensusDataProvider works with existing chains

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Seun Lanlege
2020-11-04 14:16:08 +01:00
committed by GitHub
parent a12df194c6
commit 6328f8740c
20 changed files with 263 additions and 125 deletions
@@ -15,11 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
derive_more = "0.99.2"
futures = "0.3.4"
jsonrpc-core = "15.0.0"
jsonrpc-core-client = "15.0.0"
jsonrpc-derive = "15.0.0"
jsonrpc-core = "15.1.0"
jsonrpc-core-client = "15.1.0"
jsonrpc-derive = "15.1.0"
log = "0.4.8"
parking_lot = "0.10.0"
codec = { package = "parity-scale-codec", version = "1.3.1" }
serde = { version = "1.0", features=["derive"] }
assert_matches = "1.3.0"
@@ -35,6 +36,7 @@ sp-inherents = { path = "../../../primitives/inherents", version = "2.0.0" }
sp-runtime = { path = "../../../primitives/runtime", version = "2.0.0" }
sp-core = { path = "../../../primitives/core", version = "2.0.0" }
sp-keystore = { path = "../../../primitives/keystore", version = "0.8.0" }
sp-keyring = { path = "../../../primitives/keyring", version = "2.0.0" }
sp-api = { path = "../../../primitives/api", version = "2.0.0" }
sp-transaction-pool = { path = "../../../primitives/transaction-pool", version = "2.0.0" }
sp-timestamp = { path = "../../../primitives/timestamp", version = "2.0.0" }
@@ -20,7 +20,7 @@
use super::ConsensusDataProvider;
use crate::Error;
use codec::Encode;
use std::{
any::Any,
borrow::Cow,
@@ -30,21 +30,24 @@ use std::{
use sc_client_api::AuxStore;
use sc_consensus_babe::{
Config, Epoch, authorship, CompatibleDigestItem, BabeIntermediate,
register_babe_inherent_data_provider, INTERMEDIATE_KEY,
register_babe_inherent_data_provider, INTERMEDIATE_KEY, find_pre_digest,
};
use sc_consensus_epochs::{SharedEpochChanges, descendent_query};
use sc_consensus_epochs::{SharedEpochChanges, descendent_query, ViableEpochDescriptor, EpochHeader};
use sp_keystore::SyncCryptoStorePtr;
use sp_api::{ProvideRuntimeApi, TransactionFor};
use sp_blockchain::{HeaderBackend, HeaderMetadata};
use sp_consensus::BlockImportParams;
use sp_consensus_babe::{BabeApi, inherents::BabeInherentData};
use sp_keystore::SyncCryptoStorePtr;
use sp_consensus_babe::{
BabeApi, inherents::BabeInherentData, ConsensusLog, BABE_ENGINE_ID, AuthorityId,
digests::{PreDigest, SecondaryPlainPreDigest, NextEpochDescriptor}, BabeAuthorityWeight,
};
use sp_inherents::{InherentDataProviders, InherentData, ProvideInherentData, InherentIdentifier};
use sp_runtime::{
traits::{DigestItemFor, DigestFor, Block as BlockT, Header as _},
generic::Digest,
traits::{DigestItemFor, DigestFor, Block as BlockT, Zero, Header},
generic::{Digest, BlockId},
};
use sp_timestamp::{InherentType, InherentError, INHERENT_IDENTIFIER};
use sp_timestamp::{InherentType, InherentError, INHERENT_IDENTIFIER, TimestampInherentData};
/// Provides BABE-compatible predigests and BlockImportParams.
/// Intended for use with BABE runtimes.
@@ -60,12 +63,15 @@ pub struct BabeConsensusDataProvider<B: BlockT, C> {
/// BABE config, gotten from the runtime.
config: Config,
/// Authorities to be used for this babe chain.
authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
}
impl<B, C> BabeConsensusDataProvider<B, C>
where
B: BlockT,
C: AuxStore + ProvideRuntimeApi<B>,
C: AuxStore + HeaderBackend<B> + ProvideRuntimeApi<B> + HeaderMetadata<B, Error = sp_blockchain::Error>,
C::Api: BabeApi<B, Error = sp_blockchain::Error>,
{
pub fn new(
@@ -73,9 +79,14 @@ impl<B, C> BabeConsensusDataProvider<B, C>
keystore: SyncCryptoStorePtr,
provider: &InherentDataProviders,
epoch_changes: SharedEpochChanges<B, Epoch>,
authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
) -> Result<Self, Error> {
if authorities.is_empty() {
return Err(Error::StringError("Cannot supply empty authority set!".into()))
}
let config = Config::get_or_compute(&*client)?;
let timestamp_provider = SlotTimestampProvider::new(config.slot_duration)?;
let timestamp_provider = SlotTimestampProvider::new(client.clone())?;
provider.register_provider(timestamp_provider)?;
register_babe_inherent_data_provider(provider, config.slot_duration)?;
@@ -85,21 +96,11 @@ impl<B, C> BabeConsensusDataProvider<B, C>
client,
keystore,
epoch_changes,
authorities,
})
}
}
impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
where
B: BlockT,
C: AuxStore + HeaderBackend<B> + HeaderMetadata<B, Error = sp_blockchain::Error> + ProvideRuntimeApi<B>,
C::Api: BabeApi<B, Error = sp_blockchain::Error>,
{
type Transaction = TransactionFor<C, B>;
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<DigestFor<B>, Error> {
let slot_number = inherents.babe_inherent_data()?;
fn epoch(&self, parent: &B::Header, slot_number: u64) -> Result<Epoch, Error> {
let epoch_changes = self.epoch_changes.lock();
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
@@ -121,15 +122,70 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
sp_consensus::Error::InvalidAuthoritiesSet
})?;
// this is a dev node environment, we should always be able to claim a slot.
let (predigest, _) = authorship::claim_slot(slot_number, epoch.as_ref(), &self.keystore)
.ok_or_else(|| Error::StringError("failed to claim slot for authorship".into()))?;
Ok(epoch.as_ref().clone())
}
}
Ok(Digest {
logs: vec![
impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
where
B: BlockT,
C: AuxStore + HeaderBackend<B> + HeaderMetadata<B, Error = sp_blockchain::Error> + ProvideRuntimeApi<B>,
C::Api: BabeApi<B, Error = sp_blockchain::Error>,
{
type Transaction = TransactionFor<C, B>;
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<DigestFor<B>, Error> {
let slot_number = inherents.babe_inherent_data()?;
let epoch = self.epoch(parent, slot_number)?;
// this is a dev node environment, we should always be able to claim a slot.
let logs = if let Some((predigest, _)) = authorship::claim_slot(slot_number, &epoch, &self.keystore) {
vec![
<DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(predigest),
],
})
]
} else {
// well we couldn't claim a slot because this is an existing chain and we're not in the authorities.
// we need to tell BabeBlockImport that the epoch has changed, and we put ourselves in the authorities.
let predigest = PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
slot_number,
authority_index: 0_u32,
});
let mut epoch_changes = self.epoch_changes.lock();
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
parent.number().clone(),
slot_number,
)
.map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))?
.ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?;
let epoch_mut = match epoch_descriptor {
ViableEpochDescriptor::Signaled(identifier, _epoch_header) => {
epoch_changes.epoch_mut(&identifier)
.ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?
},
_ => unreachable!("we couldn't claim a slot, so this isn't the genesis epoch; qed")
};
// mutate the current epoch
epoch_mut.authorities = self.authorities.clone();
let next_epoch = ConsensusLog::NextEpochData(NextEpochDescriptor {
authorities: self.authorities.clone(),
// copy the old randomness
randomness: epoch_mut.randomness.clone(),
});
vec![
DigestItemFor::<B>::PreRuntime(BABE_ENGINE_ID, predigest.encode()),
DigestItemFor::<B>::Consensus(BABE_ENGINE_ID, next_epoch.encode())
]
};
Ok(Digest { logs })
}
fn append_block_import(
@@ -139,16 +195,42 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
inherents: &InherentData
) -> Result<(), Error> {
let slot_number = inherents.babe_inherent_data()?;
let epoch_descriptor = self.epoch_changes.lock()
let epoch_changes = self.epoch_changes.lock();
let mut epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
parent.number().clone(),
slot_number,
)
.map_err(|e| Error::StringError(format!("failed to fetch epoch data: {}", e)))?
.map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))?
.ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?;
// drop the lock
drop(epoch_changes);
// a quick check to see if we're in the authorities
let epoch = self.epoch(parent, slot_number)?;
let (authority, _) = self.authorities.first().expect("authorities is non-emptyp; qed");
let has_authority = epoch.authorities.iter()
.find(|(id, _)| *id == *authority)
.is_some();
if !has_authority {
log::info!(target: "manual-seal", "authority not found");
let slot_number = inherents.timestamp_inherent_data()? / self.config.slot_duration;
// manually hard code epoch descriptor
epoch_descriptor = match epoch_descriptor {
ViableEpochDescriptor::Signaled(identifier, _header) => {
ViableEpochDescriptor::Signaled(
identifier,
EpochHeader {
start_slot: slot_number,
end_slot: slot_number * self.config.epoch_length,
},
)
},
_ => unreachable!("we're not in the authorities, so this isn't the genesis epoch; qed")
};
}
params.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
@@ -168,12 +250,32 @@ struct SlotTimestampProvider {
impl SlotTimestampProvider {
/// create a new mocked time stamp provider.
fn new(slot_duration: u64) -> Result<Self, Error> {
let now = SystemTime::now();
let duration = now.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|err| Error::StringError(format!("{}", err)))?;
fn new<B, C>(client: Arc<C>) -> Result<Self, Error>
where
B: BlockT,
C: AuxStore + HeaderBackend<B> + ProvideRuntimeApi<B>,
C::Api: BabeApi<B, Error = sp_blockchain::Error>,
{
let slot_duration = Config::get_or_compute(&*client)?.slot_duration;
let info = client.info();
// looks like this isn't the first block, rehydrate the fake time.
// otherwise we'd be producing blocks for older slots.
let duration = if info.best_number != Zero::zero() {
let header = client.header(BlockId::Hash(info.best_hash))?.unwrap();
let slot_number = find_pre_digest::<B>(&header).unwrap().slot_number();
// add the slot duration so there's no collision of slots
(slot_number * slot_duration) + slot_duration
} else {
// this is the first block, use the correct time.
let now = SystemTime::now();
now.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|err| Error::StringError(format!("{}", err)))?
.as_millis() as u64
};
Ok(Self {
time: atomic::AtomicU64::new(duration.as_millis() as u64),
time: atomic::AtomicU64::new(duration),
slot_duration,
})
}