mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 15:11:03 +00:00
Run cargo fmt on the whole code base (#9394)
* Run cargo fmt on the whole code base * Second run * Add CI check * Fix compilation * More unnecessary braces * Handle weights * Use --all * Use correct attributes... * Fix UI tests * AHHHHHHHHH * 🤦 * Docs * Fix compilation * 🤷 * Please stop * 🤦 x 2 * More * make rustfmt.toml consistent with polkadot Co-authored-by: André Silva <andrerfosilva@gmail.com>
This commit is contained in:
@@ -18,36 +18,37 @@
|
||||
|
||||
//! Module implementing the logic for verifying and importing AuRa blocks.
|
||||
|
||||
use crate::{AuthorityId, find_pre_digest, slot_author, aura_err, Error, authorities};
|
||||
use std::{
|
||||
sync::Arc, marker::PhantomData, hash::Hash, fmt::Debug,
|
||||
};
|
||||
use crate::{aura_err, authorities, find_pre_digest, slot_author, AuthorityId, Error};
|
||||
use codec::{Codec, Decode, Encode};
|
||||
use log::{debug, info, trace};
|
||||
use prometheus_endpoint::Registry;
|
||||
use codec::{Encode, Decode, Codec};
|
||||
use sp_consensus::{
|
||||
BlockImport, CanAuthorWith, ForkChoiceStrategy, BlockImportParams,
|
||||
BlockOrigin, Error as ConsensusError,
|
||||
import_queue::{
|
||||
Verifier, BasicQueue, DefaultImportQueue, BoxJustificationImport,
|
||||
},
|
||||
};
|
||||
use sc_client_api::{BlockOf, UsageProvider, backend::AuxStore};
|
||||
use sp_blockchain::{well_known_cache_keys::{self, Id as CacheKeyId}, ProvideCache, HeaderBackend};
|
||||
use sc_client_api::{backend::AuxStore, BlockOf, UsageProvider};
|
||||
use sc_consensus_slots::{check_equivocation, CheckedHeader, InherentDataProviderExt};
|
||||
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE};
|
||||
use sp_api::{ApiExt, ProvideRuntimeApi};
|
||||
use sp_block_builder::BlockBuilder as BlockBuilderApi;
|
||||
use sp_runtime::{generic::{BlockId, OpaqueDigestItemId}, Justifications};
|
||||
use sp_runtime::traits::{Block as BlockT, Header, DigestItemFor};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_blockchain::{
|
||||
well_known_cache_keys::{self, Id as CacheKeyId},
|
||||
HeaderBackend, ProvideCache,
|
||||
};
|
||||
use sp_consensus::{
|
||||
import_queue::{BasicQueue, BoxJustificationImport, DefaultImportQueue, Verifier},
|
||||
BlockImport, BlockImportParams, BlockOrigin, CanAuthorWith, Error as ConsensusError,
|
||||
ForkChoiceStrategy,
|
||||
};
|
||||
use sp_consensus_aura::{
|
||||
digests::CompatibleDigestItem, inherents::AuraInherentData, AuraApi, ConsensusLog,
|
||||
AURA_ENGINE_ID,
|
||||
};
|
||||
use sp_consensus_slots::Slot;
|
||||
use sp_core::crypto::Pair;
|
||||
use sp_inherents::{CreateInherentDataProviders, InherentDataProvider as _};
|
||||
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_TRACE, CONSENSUS_DEBUG};
|
||||
use sc_consensus_slots::{CheckedHeader, check_equivocation, InherentDataProviderExt};
|
||||
use sp_consensus_slots::Slot;
|
||||
use sp_api::ApiExt;
|
||||
use sp_consensus_aura::{
|
||||
digests::CompatibleDigestItem, AuraApi, inherents::AuraInherentData,
|
||||
ConsensusLog, AURA_ENGINE_ID,
|
||||
use sp_runtime::{
|
||||
generic::{BlockId, OpaqueDigestItemId},
|
||||
traits::{Block as BlockT, DigestItemFor, Header},
|
||||
Justifications,
|
||||
};
|
||||
use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc};
|
||||
|
||||
/// check a header has been signed by the right key. If the slot is too far in the future, an error
|
||||
/// will be returned. If it's successful, returns the pre-header and the digest item
|
||||
@@ -61,7 +62,8 @@ fn check_header<C, B: BlockT, P: Pair>(
|
||||
hash: B::Hash,
|
||||
authorities: &[AuthorityId<P>],
|
||||
check_for_equivocation: CheckForEquivocation,
|
||||
) -> Result<CheckedHeader<B::Header, (Slot, DigestItemFor<B>)>, Error<B>> where
|
||||
) -> Result<CheckedHeader<B::Header, (Slot, DigestItemFor<B>)>, Error<B>>
|
||||
where
|
||||
DigestItemFor<B>: CompatibleDigestItem<P::Signature>,
|
||||
P::Signature: Codec,
|
||||
C: sc_client_api::backend::AuxStore,
|
||||
@@ -69,9 +71,7 @@ fn check_header<C, B: BlockT, P: Pair>(
|
||||
{
|
||||
let seal = header.digest_mut().pop().ok_or_else(|| Error::HeaderUnsealed(hash))?;
|
||||
|
||||
let sig = seal.as_aura_seal().ok_or_else(|| {
|
||||
aura_err(Error::HeaderBadSeal(hash))
|
||||
})?;
|
||||
let sig = seal.as_aura_seal().ok_or_else(|| aura_err(Error::HeaderBadSeal(hash)))?;
|
||||
|
||||
let slot = find_pre_digest::<B, P::Signature>(&header)?;
|
||||
|
||||
@@ -81,20 +81,17 @@ fn check_header<C, B: BlockT, P: Pair>(
|
||||
} else {
|
||||
// check the signature is valid under the expected authority and
|
||||
// chain state.
|
||||
let expected_author = slot_author::<P>(slot, &authorities)
|
||||
.ok_or_else(|| Error::SlotAuthorNotFound)?;
|
||||
let expected_author =
|
||||
slot_author::<P>(slot, &authorities).ok_or_else(|| Error::SlotAuthorNotFound)?;
|
||||
|
||||
let pre_hash = header.hash();
|
||||
|
||||
if P::verify(&sig, pre_hash.as_ref(), expected_author) {
|
||||
if check_for_equivocation.check_for_equivocation() {
|
||||
if let Some(equivocation_proof) = check_equivocation(
|
||||
client,
|
||||
slot_now,
|
||||
slot,
|
||||
&header,
|
||||
expected_author,
|
||||
).map_err(Error::Client)? {
|
||||
if let Some(equivocation_proof) =
|
||||
check_equivocation(client, slot_now, slot, &header, expected_author)
|
||||
.map_err(Error::Client)?
|
||||
{
|
||||
info!(
|
||||
target: "aura",
|
||||
"Slot author is equivocating at slot {} with headers {:?} and {:?}",
|
||||
@@ -141,7 +138,8 @@ impl<C, P, CAW, CIDP> AuraVerifier<C, P, CAW, CIDP> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, P, CAW, CIDP> AuraVerifier<C, P, CAW, CIDP> where
|
||||
impl<C, P, CAW, CIDP> AuraVerifier<C, P, CAW, CIDP>
|
||||
where
|
||||
P: Send + Sync + 'static,
|
||||
CAW: Send + Sync + 'static,
|
||||
CIDP: Send,
|
||||
@@ -152,8 +150,10 @@ impl<C, P, CAW, CIDP> AuraVerifier<C, P, CAW, CIDP> where
|
||||
block_id: BlockId<B>,
|
||||
inherent_data: sp_inherents::InherentData,
|
||||
create_inherent_data_providers: CIDP::InherentDataProviders,
|
||||
) -> Result<(), Error<B>> where
|
||||
C: ProvideRuntimeApi<B>, C::Api: BlockBuilderApi<B>,
|
||||
) -> Result<(), Error<B>>
|
||||
where
|
||||
C: ProvideRuntimeApi<B>,
|
||||
C::Api: BlockBuilderApi<B>,
|
||||
CAW: CanAuthorWith<B>,
|
||||
CIDP: CreateInherentDataProviders<B, ()>,
|
||||
{
|
||||
@@ -167,11 +167,11 @@ impl<C, P, CAW, CIDP> AuraVerifier<C, P, CAW, CIDP> where
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let inherent_res = self.client.runtime_api().check_inherents(
|
||||
&block_id,
|
||||
block,
|
||||
inherent_data,
|
||||
).map_err(|e| Error::Client(e.into()))?;
|
||||
let inherent_res = self
|
||||
.client
|
||||
.runtime_api()
|
||||
.check_inherents(&block_id, block, inherent_data)
|
||||
.map_err(|e| Error::Client(e.into()))?;
|
||||
|
||||
if !inherent_res.ok() {
|
||||
for (i, e) in inherent_res.into_errors() {
|
||||
@@ -187,13 +187,14 @@ impl<C, P, CAW, CIDP> AuraVerifier<C, P, CAW, CIDP> where
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> where
|
||||
C: ProvideRuntimeApi<B> +
|
||||
Send +
|
||||
Sync +
|
||||
sc_client_api::backend::AuxStore +
|
||||
ProvideCache<B> +
|
||||
BlockOf,
|
||||
impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP>
|
||||
where
|
||||
C: ProvideRuntimeApi<B>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ sc_client_api::backend::AuxStore
|
||||
+ ProvideCache<B>
|
||||
+ BlockOf,
|
||||
C::Api: BlockBuilderApi<B> + AuraApi<B, AuthorityId<P>> + ApiExt<B>,
|
||||
DigestItemFor<B>: CompatibleDigestItem<P::Signature>,
|
||||
P: Pair + Send + Sync + 'static,
|
||||
@@ -215,15 +216,14 @@ impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> w
|
||||
let authorities = authorities(self.client.as_ref(), &BlockId::Hash(parent_hash))
|
||||
.map_err(|e| format!("Could not fetch authorities at {:?}: {:?}", parent_hash, e))?;
|
||||
|
||||
let create_inherent_data_providers = self.create_inherent_data_providers
|
||||
.create_inherent_data_providers(
|
||||
parent_hash,
|
||||
(),
|
||||
)
|
||||
let create_inherent_data_providers = self
|
||||
.create_inherent_data_providers
|
||||
.create_inherent_data_providers(parent_hash, ())
|
||||
.await
|
||||
.map_err(|e| Error::<B>::Client(sp_blockchain::Error::Application(e)))?;
|
||||
|
||||
let mut inherent_data = create_inherent_data_providers.create_inherent_data()
|
||||
let mut inherent_data = create_inherent_data_providers
|
||||
.create_inherent_data()
|
||||
.map_err(Error::<B>::Inherent)?;
|
||||
|
||||
let slot_now = create_inherent_data_providers.slot();
|
||||
@@ -238,7 +238,8 @@ impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> w
|
||||
hash,
|
||||
&authorities[..],
|
||||
self.check_for_equivocation,
|
||||
).map_err(|e| e.to_string())?;
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
match checked_header {
|
||||
CheckedHeader::Checked(pre_header, (slot, seal)) => {
|
||||
// if the body is passed through, we need to use the runtime
|
||||
@@ -250,7 +251,8 @@ impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> w
|
||||
inherent_data.aura_replace_inherent_data(slot);
|
||||
|
||||
// skip the inherents verification if the runtime API is old.
|
||||
if self.client
|
||||
if self
|
||||
.client
|
||||
.runtime_api()
|
||||
.has_api_with::<dyn BlockBuilderApi<B>, _>(
|
||||
&BlockId::Hash(parent_hash),
|
||||
@@ -263,7 +265,9 @@ impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> w
|
||||
BlockId::Hash(parent_hash),
|
||||
inherent_data,
|
||||
create_inherent_data_providers,
|
||||
).await.map_err(|e| e.to_string())?;
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
let (_, inner_body) = block.deconstruct();
|
||||
@@ -279,16 +283,18 @@ impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> w
|
||||
);
|
||||
|
||||
// Look for an authorities-change log.
|
||||
let maybe_keys = pre_header.digest()
|
||||
let maybe_keys = pre_header
|
||||
.digest()
|
||||
.logs()
|
||||
.iter()
|
||||
.filter_map(|l| l.try_to::<ConsensusLog<AuthorityId<P>>>(
|
||||
OpaqueDigestItemId::Consensus(&AURA_ENGINE_ID)
|
||||
))
|
||||
.filter_map(|l| {
|
||||
l.try_to::<ConsensusLog<AuthorityId<P>>>(OpaqueDigestItemId::Consensus(
|
||||
&AURA_ENGINE_ID,
|
||||
))
|
||||
})
|
||||
.find_map(|l| match l {
|
||||
ConsensusLog::AuthoritiesChange(a) => Some(
|
||||
vec![(well_known_cache_keys::AUTHORITIES, a.encode())]
|
||||
),
|
||||
ConsensusLog::AuthoritiesChange(a) =>
|
||||
Some(vec![(well_known_cache_keys::AUTHORITIES, a.encode())]),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
@@ -300,7 +306,7 @@ impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> w
|
||||
import_block.post_hash = Some(hash);
|
||||
|
||||
Ok((import_block, maybe_keys))
|
||||
}
|
||||
},
|
||||
CheckedHeader::Deferred(a, b) => {
|
||||
debug!(target: "aura", "Checking {:?} failed; {:?}, {:?}.", hash, a, b);
|
||||
telemetry!(
|
||||
@@ -312,7 +318,7 @@ impl<B: BlockT, C, P, CAW, CIDP> Verifier<B> for AuraVerifier<C, P, CAW, CIDP> w
|
||||
"b" => ?b,
|
||||
);
|
||||
Err(format!("Header {:?} rejected: too far in the future", hash))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,8 +381,9 @@ pub fn import_queue<'a, P, Block, I, C, S, CAW, CIDP>(
|
||||
can_author_with,
|
||||
check_for_equivocation,
|
||||
telemetry,
|
||||
}: ImportQueueParams<'a, Block, I, C, S, CAW, CIDP>
|
||||
) -> Result<DefaultImportQueue<Block, C>, sp_consensus::Error> where
|
||||
}: ImportQueueParams<'a, Block, I, C, S, CAW, CIDP>,
|
||||
) -> Result<DefaultImportQueue<Block, C>, sp_consensus::Error>
|
||||
where
|
||||
Block: BlockT,
|
||||
C::Api: BlockBuilderApi<Block> + AuraApi<Block, AuthorityId<P>> + ApiExt<Block>,
|
||||
C: 'static
|
||||
@@ -388,7 +395,7 @@ pub fn import_queue<'a, P, Block, I, C, S, CAW, CIDP>(
|
||||
+ AuxStore
|
||||
+ UsageProvider<Block>
|
||||
+ HeaderBackend<Block>,
|
||||
I: BlockImport<Block, Error=ConsensusError, Transaction = sp_api::TransactionFor<C, Block>>
|
||||
I: BlockImport<Block, Error = ConsensusError, Transaction = sp_api::TransactionFor<C, Block>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
@@ -401,23 +408,15 @@ pub fn import_queue<'a, P, Block, I, C, S, CAW, CIDP>(
|
||||
CIDP: CreateInherentDataProviders<Block, ()> + Sync + Send + 'static,
|
||||
CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync,
|
||||
{
|
||||
let verifier = build_verifier::<P, _, _, _>(
|
||||
BuildVerifierParams {
|
||||
client,
|
||||
create_inherent_data_providers,
|
||||
can_author_with,
|
||||
check_for_equivocation,
|
||||
telemetry,
|
||||
},
|
||||
);
|
||||
let verifier = build_verifier::<P, _, _, _>(BuildVerifierParams {
|
||||
client,
|
||||
create_inherent_data_providers,
|
||||
can_author_with,
|
||||
check_for_equivocation,
|
||||
telemetry,
|
||||
});
|
||||
|
||||
Ok(BasicQueue::new(
|
||||
verifier,
|
||||
Box::new(block_import),
|
||||
justification_import,
|
||||
spawner,
|
||||
registry,
|
||||
))
|
||||
Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry))
|
||||
}
|
||||
|
||||
/// Parameters of [`build_verifier`].
|
||||
@@ -442,7 +441,7 @@ pub fn build_verifier<P, C, CIDP, CAW>(
|
||||
can_author_with,
|
||||
check_for_equivocation,
|
||||
telemetry,
|
||||
}: BuildVerifierParams<C, CIDP, CAW>
|
||||
}: BuildVerifierParams<C, CIDP, CAW>,
|
||||
) -> AuraVerifier<C, P, CAW, CIDP> {
|
||||
AuraVerifier::<_, P, _, _>::new(
|
||||
client,
|
||||
|
||||
@@ -31,50 +31,53 @@
|
||||
//! NOTE: Aura itself is designed to be generic over the crypto used.
|
||||
#![forbid(missing_docs, unsafe_code)]
|
||||
use std::{
|
||||
sync::Arc, marker::PhantomData, hash::Hash, fmt::Debug, pin::Pin,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use futures::prelude::*;
|
||||
use log::{debug, trace};
|
||||
|
||||
use codec::{Encode, Decode, Codec};
|
||||
use codec::{Codec, Decode, Encode};
|
||||
|
||||
use sp_consensus::{
|
||||
BlockImport, Environment, Proposer, CanAuthorWith, ForkChoiceStrategy, BlockImportParams,
|
||||
BlockOrigin, Error as ConsensusError, SelectChain, StateAction,
|
||||
};
|
||||
use sc_client_api::{backend::AuxStore, BlockOf, UsageProvider};
|
||||
use sp_blockchain::{Result as CResult, ProvideCache, HeaderBackend};
|
||||
use sp_core::crypto::Public;
|
||||
use sp_application_crypto::{AppKey, AppPublic};
|
||||
use sp_runtime::{generic::BlockId, traits::NumberFor};
|
||||
use sp_runtime::traits::{Block as BlockT, Header, DigestItemFor, Zero, Member};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_core::crypto::Pair;
|
||||
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
|
||||
use sp_inherents::CreateInherentDataProviders;
|
||||
use sc_telemetry::TelemetryHandle;
|
||||
use sc_consensus_slots::{
|
||||
SlotInfo, BackoffAuthoringBlocksStrategy, InherentDataProviderExt, StorageChanges,
|
||||
BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SlotInfo, StorageChanges,
|
||||
};
|
||||
use sc_telemetry::TelemetryHandle;
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_application_crypto::{AppKey, AppPublic};
|
||||
use sp_blockchain::{HeaderBackend, ProvideCache, Result as CResult};
|
||||
use sp_consensus::{
|
||||
BlockImport, BlockImportParams, BlockOrigin, CanAuthorWith, Environment,
|
||||
Error as ConsensusError, ForkChoiceStrategy, Proposer, SelectChain, StateAction,
|
||||
};
|
||||
use sp_consensus_slots::Slot;
|
||||
use sp_core::crypto::{Pair, Public};
|
||||
use sp_inherents::CreateInherentDataProviders;
|
||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
||||
use sp_runtime::{
|
||||
generic::BlockId,
|
||||
traits::{Block as BlockT, DigestItemFor, Header, Member, NumberFor, Zero},
|
||||
};
|
||||
|
||||
mod import_queue;
|
||||
|
||||
pub use sp_consensus_aura::{
|
||||
ConsensusLog, AuraApi, AURA_ENGINE_ID, digests::CompatibleDigestItem,
|
||||
inherents::{
|
||||
InherentType as AuraInherent,
|
||||
INHERENT_IDENTIFIER, InherentDataProvider,
|
||||
},
|
||||
};
|
||||
pub use sp_consensus::SyncOracle;
|
||||
pub use import_queue::{
|
||||
ImportQueueParams, import_queue, CheckForEquivocation,
|
||||
build_verifier, BuildVerifierParams, AuraVerifier,
|
||||
build_verifier, import_queue, AuraVerifier, BuildVerifierParams, CheckForEquivocation,
|
||||
ImportQueueParams,
|
||||
};
|
||||
pub use sc_consensus_slots::SlotProportion;
|
||||
pub use sp_consensus::SyncOracle;
|
||||
pub use sp_consensus_aura::{
|
||||
digests::CompatibleDigestItem,
|
||||
inherents::{InherentDataProvider, InherentType as AuraInherent, INHERENT_IDENTIFIER},
|
||||
AuraApi, ConsensusLog, AURA_ENGINE_ID,
|
||||
};
|
||||
|
||||
type AuthorityId<P> = <P as Pair>::Public;
|
||||
|
||||
@@ -82,7 +85,8 @@ type AuthorityId<P> = <P as Pair>::Public;
|
||||
pub type SlotDuration = sc_consensus_slots::SlotDuration<sp_consensus_aura::SlotDuration>;
|
||||
|
||||
/// Get type of `SlotDuration` for Aura.
|
||||
pub fn slot_duration<A, B, C>(client: &C) -> CResult<SlotDuration> where
|
||||
pub fn slot_duration<A, B, C>(client: &C) -> CResult<SlotDuration>
|
||||
where
|
||||
A: Codec,
|
||||
B: BlockT,
|
||||
C: AuxStore + ProvideRuntimeApi<B> + UsageProvider<B>,
|
||||
@@ -93,7 +97,9 @@ pub fn slot_duration<A, B, C>(client: &C) -> CResult<SlotDuration> where
|
||||
|
||||
/// Get slot author for given block along with authorities.
|
||||
fn slot_author<P: Pair>(slot: Slot, authorities: &[AuthorityId<P>]) -> Option<&AuthorityId<P>> {
|
||||
if authorities.is_empty() { return None }
|
||||
if authorities.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
let idx = *slot % (authorities.len() as u64);
|
||||
assert!(
|
||||
@@ -101,9 +107,10 @@ fn slot_author<P: Pair>(slot: Slot, authorities: &[AuthorityId<P>]) -> Option<&A
|
||||
"It is impossible to have a vector with length beyond the address space; qed",
|
||||
);
|
||||
|
||||
let current_author = authorities.get(idx as usize)
|
||||
.expect("authorities not empty; index constrained to list length;\
|
||||
this is a valid index; qed");
|
||||
let current_author = authorities.get(idx as usize).expect(
|
||||
"authorities not empty; index constrained to list length;\
|
||||
this is a valid index; qed",
|
||||
);
|
||||
|
||||
Some(current_author)
|
||||
}
|
||||
@@ -325,9 +332,8 @@ where
|
||||
type BlockImport = I;
|
||||
type SyncOracle = SO;
|
||||
type JustificationSyncLink = L;
|
||||
type CreateProposer = Pin<Box<
|
||||
dyn Future<Output = Result<E::Proposer, sp_consensus::Error>> + Send + 'static
|
||||
>>;
|
||||
type CreateProposer =
|
||||
Pin<Box<dyn Future<Output = Result<E::Proposer, sp_consensus::Error>> + Send + 'static>>;
|
||||
type Proposer = E::Proposer;
|
||||
type Claim = P::Public;
|
||||
type EpochData = Vec<AuthorityId<P>>;
|
||||
@@ -376,22 +382,25 @@ where
|
||||
slot: Slot,
|
||||
_claim: &Self::Claim,
|
||||
) -> Vec<sp_runtime::DigestItem<B::Hash>> {
|
||||
vec![
|
||||
<DigestItemFor<B> as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot),
|
||||
]
|
||||
vec![<DigestItemFor<B> as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)]
|
||||
}
|
||||
|
||||
fn block_import_params(&self) -> Box<dyn Fn(
|
||||
B::Header,
|
||||
&B::Hash,
|
||||
Vec<B::Extrinsic>,
|
||||
StorageChanges<sp_api::TransactionFor<C, B>, B>,
|
||||
Self::Claim,
|
||||
Self::EpochData,
|
||||
) -> Result<
|
||||
sp_consensus::BlockImportParams<B, sp_api::TransactionFor<C, B>>,
|
||||
sp_consensus::Error> + Send + 'static>
|
||||
{
|
||||
fn block_import_params(
|
||||
&self,
|
||||
) -> Box<
|
||||
dyn Fn(
|
||||
B::Header,
|
||||
&B::Hash,
|
||||
Vec<B::Extrinsic>,
|
||||
StorageChanges<sp_api::TransactionFor<C, B>, B>,
|
||||
Self::Claim,
|
||||
Self::EpochData,
|
||||
) -> Result<
|
||||
sp_consensus::BlockImportParams<B, sp_api::TransactionFor<C, B>>,
|
||||
sp_consensus::Error,
|
||||
> + Send
|
||||
+ 'static,
|
||||
> {
|
||||
let keystore = self.keystore.clone();
|
||||
Box::new(move |header, header_hash, body, storage_changes, public, _epoch| {
|
||||
// sign the pre-sealed hash of the block and then
|
||||
@@ -402,28 +411,28 @@ where
|
||||
&*keystore,
|
||||
<AuthorityId<P> as AppKey>::ID,
|
||||
&public_type_pair,
|
||||
header_hash.as_ref()
|
||||
).map_err(|e| sp_consensus::Error::CannotSign(
|
||||
public.clone(), e.to_string(),
|
||||
))?
|
||||
.ok_or_else(|| sp_consensus::Error::CannotSign(
|
||||
public.clone(), "Could not find key in keystore.".into(),
|
||||
))?;
|
||||
let signature = signature.clone().try_into()
|
||||
.map_err(|_| sp_consensus::Error::InvalidSignature(
|
||||
signature, public
|
||||
))?;
|
||||
header_hash.as_ref(),
|
||||
)
|
||||
.map_err(|e| sp_consensus::Error::CannotSign(public.clone(), e.to_string()))?
|
||||
.ok_or_else(|| {
|
||||
sp_consensus::Error::CannotSign(
|
||||
public.clone(),
|
||||
"Could not find key in keystore.".into(),
|
||||
)
|
||||
})?;
|
||||
let signature = signature
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?;
|
||||
|
||||
let signature_digest_item = <
|
||||
DigestItemFor<B> as CompatibleDigestItem<P::Signature>
|
||||
>::aura_seal(signature);
|
||||
let signature_digest_item =
|
||||
<DigestItemFor<B> as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
|
||||
|
||||
let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
|
||||
import_block.post_digests.push(signature_digest_item);
|
||||
import_block.body = Some(body);
|
||||
import_block.state_action = StateAction::ApplyChanges(
|
||||
sp_consensus::StorageChanges::Changes(storage_changes)
|
||||
);
|
||||
import_block.state_action =
|
||||
StateAction::ApplyChanges(sp_consensus::StorageChanges::Changes(storage_changes));
|
||||
import_block.fork_choice = Some(ForkChoiceStrategy::LongestChain);
|
||||
|
||||
Ok(import_block)
|
||||
@@ -443,7 +452,7 @@ where
|
||||
self.client.info().finalized_number,
|
||||
slot,
|
||||
self.logging_target(),
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -458,9 +467,11 @@ where
|
||||
}
|
||||
|
||||
fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer {
|
||||
Box::pin(self.env.init(block).map_err(|e| {
|
||||
sp_consensus::Error::ClientImport(format!("{:?}", e)).into()
|
||||
}))
|
||||
Box::pin(
|
||||
self.env
|
||||
.init(block)
|
||||
.map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)).into()),
|
||||
)
|
||||
}
|
||||
|
||||
fn telemetry(&self) -> Option<TelemetryHandle> {
|
||||
@@ -515,7 +526,7 @@ impl<B: BlockT> std::convert::From<Error<B>> for String {
|
||||
|
||||
fn find_pre_digest<B: BlockT, Signature: Codec>(header: &B::Header) -> Result<Slot, Error<B>> {
|
||||
if header.number().is_zero() {
|
||||
return Ok(0.into());
|
||||
return Ok(0.into())
|
||||
}
|
||||
|
||||
let mut pre_digest: Option<Slot> = None;
|
||||
@@ -530,13 +541,15 @@ fn find_pre_digest<B: BlockT, Signature: Codec>(header: &B::Header) -> Result<Sl
|
||||
pre_digest.ok_or_else(|| aura_err(Error::NoDigestFound))
|
||||
}
|
||||
|
||||
fn authorities<A, B, C>(client: &C, at: &BlockId<B>) -> Result<Vec<A>, ConsensusError> where
|
||||
fn authorities<A, B, C>(client: &C, at: &BlockId<B>) -> Result<Vec<A>, ConsensusError>
|
||||
where
|
||||
A: Codec + Debug,
|
||||
B: BlockT,
|
||||
C: ProvideRuntimeApi<B> + BlockOf + ProvideCache<B>,
|
||||
C::Api: AuraApi<B, A>,
|
||||
{
|
||||
client.runtime_api()
|
||||
client
|
||||
.runtime_api()
|
||||
.authorities(at)
|
||||
.ok()
|
||||
.ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet.into())
|
||||
@@ -545,26 +558,31 @@ fn authorities<A, B, C>(client: &C, at: &BlockId<B>) -> Result<Vec<A>, Consensus
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_consensus::{
|
||||
NoNetwork as DummyOracle, Proposal, AlwaysCanAuthor, DisableProofRecording,
|
||||
import_queue::BoxJustificationImport, SlotData,
|
||||
};
|
||||
use sc_network_test::{Block as TestBlock, *};
|
||||
use sp_runtime::traits::{Block as BlockT, DigestFor};
|
||||
use sc_network::config::ProtocolConfig;
|
||||
use parking_lot::Mutex;
|
||||
use sp_keyring::sr25519::Keyring;
|
||||
use sc_client_api::BlockchainEvents;
|
||||
use sp_consensus_aura::sr25519::AuthorityPair;
|
||||
use sc_consensus_slots::{SimpleSlotWorker, BackoffAuthoringOnFinalizedHeadLagging};
|
||||
use std::{task::Poll, time::{Instant, Duration}};
|
||||
use sc_block_builder::BlockBuilderProvider;
|
||||
use sp_runtime::traits::Header as _;
|
||||
use substrate_test_runtime_client::{TestClient, runtime::{Header, H256}};
|
||||
use sc_client_api::BlockchainEvents;
|
||||
use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker};
|
||||
use sc_keystore::LocalKeystore;
|
||||
use sc_network::config::ProtocolConfig;
|
||||
use sc_network_test::{Block as TestBlock, *};
|
||||
use sp_application_crypto::key_types::AURA;
|
||||
use sp_consensus::{
|
||||
import_queue::BoxJustificationImport, AlwaysCanAuthor, DisableProofRecording,
|
||||
NoNetwork as DummyOracle, Proposal, SlotData,
|
||||
};
|
||||
use sp_consensus_aura::sr25519::AuthorityPair;
|
||||
use sp_inherents::InherentData;
|
||||
use sp_keyring::sr25519::Keyring;
|
||||
use sp_runtime::traits::{Block as BlockT, DigestFor, Header as _};
|
||||
use sp_timestamp::InherentDataProvider as TimestampInherentDataProvider;
|
||||
use std::{
|
||||
task::Poll,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use substrate_test_runtime_client::{
|
||||
runtime::{Header, H256},
|
||||
TestClient,
|
||||
};
|
||||
|
||||
type Error = sp_blockchain::Error;
|
||||
|
||||
@@ -576,19 +594,15 @@ mod tests {
|
||||
type CreateProposer = futures::future::Ready<Result<DummyProposer, Error>>;
|
||||
type Error = Error;
|
||||
|
||||
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header)
|
||||
-> Self::CreateProposer
|
||||
{
|
||||
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header) -> Self::CreateProposer {
|
||||
futures::future::ready(Ok(DummyProposer(parent_header.number + 1, self.0.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Proposer<TestBlock> for DummyProposer {
|
||||
type Error = Error;
|
||||
type Transaction = sc_client_api::TransactionFor<
|
||||
substrate_test_runtime_client::Backend,
|
||||
TestBlock
|
||||
>;
|
||||
type Transaction =
|
||||
sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>;
|
||||
type Proposal = future::Ready<Result<Proposal<TestBlock, Self::Transaction, ()>, Error>>;
|
||||
type ProofRecording = DisableProofRecording;
|
||||
type Proof = ();
|
||||
@@ -616,11 +630,13 @@ mod tests {
|
||||
PeersFullClient,
|
||||
AuthorityPair,
|
||||
AlwaysCanAuthor,
|
||||
Box<dyn CreateInherentDataProviders<
|
||||
TestBlock,
|
||||
(),
|
||||
InherentDataProviders = (TimestampInherentDataProvider, InherentDataProvider)
|
||||
>>
|
||||
Box<
|
||||
dyn CreateInherentDataProviders<
|
||||
TestBlock,
|
||||
(),
|
||||
InherentDataProviders = (TimestampInherentDataProvider, InherentDataProvider),
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
type AuraPeer = Peer<(), PeersClient>;
|
||||
|
||||
@@ -635,14 +651,15 @@ mod tests {
|
||||
|
||||
/// Create new test network with peers and given config.
|
||||
fn from_config(_config: &ProtocolConfig) -> Self {
|
||||
AuraTestNet {
|
||||
peers: Vec::new(),
|
||||
}
|
||||
AuraTestNet { peers: Vec::new() }
|
||||
}
|
||||
|
||||
fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig, _peer_data: &())
|
||||
-> Self::Verifier
|
||||
{
|
||||
fn make_verifier(
|
||||
&self,
|
||||
client: PeersClient,
|
||||
_cfg: &ProtocolConfig,
|
||||
_peer_data: &(),
|
||||
) -> Self::Verifier {
|
||||
match client {
|
||||
PeersClient::Full(client, _) => {
|
||||
let slot_duration = slot_duration(&*client).expect("slot duration available");
|
||||
@@ -668,7 +685,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_block_import(&self, client: PeersClient) -> (
|
||||
fn make_block_import(
|
||||
&self,
|
||||
client: PeersClient,
|
||||
) -> (
|
||||
BlockImportAdapter<Self::BlockImport>,
|
||||
Option<BoxJustificationImport<Block>>,
|
||||
Self::PeerData,
|
||||
@@ -693,11 +713,7 @@ mod tests {
|
||||
sp_tracing::try_init_simple();
|
||||
let net = AuraTestNet::new(3);
|
||||
|
||||
let peers = &[
|
||||
(0, Keyring::Alice),
|
||||
(1, Keyring::Bob),
|
||||
(2, Keyring::Charlie),
|
||||
];
|
||||
let peers = &[(0, Keyring::Alice), (1, Keyring::Bob), (2, Keyring::Charlie)];
|
||||
|
||||
let net = Arc::new(Mutex::new(net));
|
||||
let mut import_notifications = Vec::new();
|
||||
@@ -710,9 +726,9 @@ mod tests {
|
||||
let client = peer.client().as_full().expect("full clients are created").clone();
|
||||
let select_chain = peer.select_chain().expect("full client has a select chain");
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore."));
|
||||
|
||||
let keystore = Arc::new(
|
||||
LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."),
|
||||
);
|
||||
|
||||
SyncCryptoStore::sr25519_generate_new(&*keystore, AURA, Some(&key.to_seed()))
|
||||
.expect("Creates authority key");
|
||||
@@ -720,38 +736,46 @@ mod tests {
|
||||
|
||||
let environ = DummyFactory(client.clone());
|
||||
import_notifications.push(
|
||||
client.import_notification_stream()
|
||||
.take_while(|n| future::ready(!(n.origin != BlockOrigin::Own && n.header.number() < &5)))
|
||||
.for_each(move |_| future::ready(()))
|
||||
client
|
||||
.import_notification_stream()
|
||||
.take_while(|n| {
|
||||
future::ready(!(n.origin != BlockOrigin::Own && n.header.number() < &5))
|
||||
})
|
||||
.for_each(move |_| future::ready(())),
|
||||
);
|
||||
|
||||
let slot_duration = slot_duration(&*client).expect("slot duration available");
|
||||
|
||||
aura_futures.push(start_aura::<AuthorityPair, _, _, _, _, _, _, _, _, _, _, _>(StartAuraParams {
|
||||
slot_duration,
|
||||
block_import: client.clone(),
|
||||
select_chain,
|
||||
client,
|
||||
proposer_factory: environ,
|
||||
sync_oracle: DummyOracle,
|
||||
justification_sync_link: (),
|
||||
create_inherent_data_providers: |_, _| async {
|
||||
let timestamp = TimestampInherentDataProvider::from_system_time();
|
||||
let slot = InherentDataProvider::from_timestamp_and_duration(
|
||||
*timestamp,
|
||||
Duration::from_secs(6),
|
||||
);
|
||||
aura_futures.push(
|
||||
start_aura::<AuthorityPair, _, _, _, _, _, _, _, _, _, _, _>(StartAuraParams {
|
||||
slot_duration,
|
||||
block_import: client.clone(),
|
||||
select_chain,
|
||||
client,
|
||||
proposer_factory: environ,
|
||||
sync_oracle: DummyOracle,
|
||||
justification_sync_link: (),
|
||||
create_inherent_data_providers: |_, _| async {
|
||||
let timestamp = TimestampInherentDataProvider::from_system_time();
|
||||
let slot = InherentDataProvider::from_timestamp_and_duration(
|
||||
*timestamp,
|
||||
Duration::from_secs(6),
|
||||
);
|
||||
|
||||
Ok((timestamp, slot))
|
||||
},
|
||||
force_authoring: false,
|
||||
backoff_authoring_blocks: Some(BackoffAuthoringOnFinalizedHeadLagging::default()),
|
||||
keystore,
|
||||
can_author_with: sp_consensus::AlwaysCanAuthor,
|
||||
block_proposal_slot_portion: SlotProportion::new(0.5),
|
||||
max_block_proposal_slot_portion: None,
|
||||
telemetry: None,
|
||||
}).expect("Starts aura"));
|
||||
Ok((timestamp, slot))
|
||||
},
|
||||
force_authoring: false,
|
||||
backoff_authoring_blocks: Some(
|
||||
BackoffAuthoringOnFinalizedHeadLagging::default(),
|
||||
),
|
||||
keystore,
|
||||
can_author_with: sp_consensus::AlwaysCanAuthor,
|
||||
block_proposal_slot_portion: SlotProportion::new(0.5),
|
||||
max_block_proposal_slot_portion: None,
|
||||
telemetry: None,
|
||||
})
|
||||
.expect("Starts aura"),
|
||||
);
|
||||
}
|
||||
|
||||
futures::executor::block_on(future::select(
|
||||
@@ -759,10 +783,7 @@ mod tests {
|
||||
net.lock().poll(cx);
|
||||
Poll::<()>::Pending
|
||||
}),
|
||||
future::select(
|
||||
future::join_all(aura_futures),
|
||||
future::join_all(import_notifications)
|
||||
)
|
||||
future::select(future::join_all(aura_futures), future::join_all(import_notifications)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -771,11 +792,14 @@ mod tests {
|
||||
let client = substrate_test_runtime_client::new();
|
||||
|
||||
assert_eq!(client.chain_info().best_number, 0);
|
||||
assert_eq!(authorities(&client, &BlockId::Number(0)).unwrap(), vec![
|
||||
Keyring::Alice.public().into(),
|
||||
Keyring::Bob.public().into(),
|
||||
Keyring::Charlie.public().into()
|
||||
]);
|
||||
assert_eq!(
|
||||
authorities(&client, &BlockId::Number(0)).unwrap(),
|
||||
vec![
|
||||
Keyring::Alice.public().into(),
|
||||
Keyring::Bob.public().into(),
|
||||
Keyring::Charlie.public().into()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -785,12 +809,11 @@ mod tests {
|
||||
let mut authorities = vec![
|
||||
Keyring::Alice.public().into(),
|
||||
Keyring::Bob.public().into(),
|
||||
Keyring::Charlie.public().into()
|
||||
Keyring::Charlie.public().into(),
|
||||
];
|
||||
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore = LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore.");
|
||||
let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore.");
|
||||
let public = SyncCryptoStore::sr25519_generate_new(&keystore, AuthorityPair::ID, None)
|
||||
.expect("Key should be created");
|
||||
authorities.push(public.into());
|
||||
@@ -822,7 +845,7 @@ mod tests {
|
||||
H256::from_low_u64_be(0),
|
||||
H256::from_low_u64_be(0),
|
||||
Default::default(),
|
||||
Default::default()
|
||||
Default::default(),
|
||||
);
|
||||
assert!(worker.claim_slot(&head, 0.into(), &authorities).is_none());
|
||||
assert!(worker.claim_slot(&head, 1.into(), &authorities).is_none());
|
||||
@@ -839,12 +862,13 @@ mod tests {
|
||||
let net = AuraTestNet::new(4);
|
||||
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore = LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore.");
|
||||
let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore.");
|
||||
SyncCryptoStore::sr25519_generate_new(
|
||||
&keystore,
|
||||
AuthorityPair::ID, Some(&Keyring::Alice.to_seed()),
|
||||
).expect("Key should be created");
|
||||
AuthorityPair::ID,
|
||||
Some(&Keyring::Alice.to_seed()),
|
||||
)
|
||||
.expect("Key should be created");
|
||||
|
||||
let net = Arc::new(Mutex::new(net));
|
||||
|
||||
@@ -870,17 +894,16 @@ mod tests {
|
||||
|
||||
let head = client.header(&BlockId::Number(0)).unwrap().unwrap();
|
||||
|
||||
let res = futures::executor::block_on(worker.on_slot(
|
||||
SlotInfo {
|
||||
slot: 0.into(),
|
||||
timestamp: 0.into(),
|
||||
ends_at: Instant::now() + Duration::from_secs(100),
|
||||
inherent_data: InherentData::new(),
|
||||
duration: Duration::from_millis(1000),
|
||||
chain_head: head,
|
||||
block_size_limit: None,
|
||||
}
|
||||
)).unwrap();
|
||||
let res = futures::executor::block_on(worker.on_slot(SlotInfo {
|
||||
slot: 0.into(),
|
||||
timestamp: 0.into(),
|
||||
ends_at: Instant::now() + Duration::from_secs(100),
|
||||
inherent_data: InherentData::new(),
|
||||
duration: Duration::from_millis(1000),
|
||||
chain_head: head,
|
||||
block_size_limit: None,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
// The returned block should be imported and we should be able to get its header by now.
|
||||
assert!(client.header(&BlockId::Hash(res.block.hash())).unwrap().is_some());
|
||||
|
||||
@@ -18,30 +18,21 @@
|
||||
|
||||
//! RPC api for babe.
|
||||
|
||||
use sc_consensus_babe::{Epoch, authorship, Config};
|
||||
use futures::{FutureExt as _, TryFutureExt as _};
|
||||
use jsonrpc_core::{
|
||||
Error as RpcError,
|
||||
futures::future as rpc_future,
|
||||
};
|
||||
use jsonrpc_core::{futures::future as rpc_future, Error as RpcError};
|
||||
use jsonrpc_derive::rpc;
|
||||
use sc_consensus_babe::{authorship, Config, Epoch};
|
||||
use sc_consensus_epochs::{descendent_query, Epoch as EpochT, SharedEpochChanges};
|
||||
use sp_consensus_babe::{
|
||||
AuthorityId,
|
||||
BabeApi as BabeRuntimeApi,
|
||||
digests::PreDigest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_core::{
|
||||
crypto::Public,
|
||||
};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use sp_api::{ProvideRuntimeApi, BlockId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_api::{BlockId, ProvideRuntimeApi};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
|
||||
use sp_consensus::{Error as ConsensusError, SelectChain};
|
||||
use sp_consensus_babe::{digests::PreDigest, AuthorityId, BabeApi as BabeRuntimeApi};
|
||||
use sp_core::crypto::Public;
|
||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
||||
use sp_runtime::traits::{Block as BlockT, Header as _};
|
||||
use sp_consensus::{SelectChain, Error as ConsensusError};
|
||||
use sp_blockchain::{HeaderBackend, HeaderMetadata, Error as BlockChainError};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
type FutureResult<T> = Box<dyn rpc_future::Future<Item = T, Error = RpcError> + Send>;
|
||||
@@ -81,14 +72,7 @@ impl<B: BlockT, C, SC> BabeRpcHandler<B, C, SC> {
|
||||
select_chain: SC,
|
||||
deny_unsafe: DenyUnsafe,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
shared_epoch_changes,
|
||||
keystore,
|
||||
babe_config,
|
||||
select_chain,
|
||||
deny_unsafe,
|
||||
}
|
||||
Self { client, shared_epoch_changes, keystore, babe_config, select_chain, deny_unsafe }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,16 +88,10 @@ where
|
||||
{
|
||||
fn epoch_authorship(&self) -> FutureResult<HashMap<AuthorityId, EpochAuthorship>> {
|
||||
if let Err(err) = self.deny_unsafe.check_if_safe() {
|
||||
return Box::new(rpc_future::err(err.into()));
|
||||
return Box::new(rpc_future::err(err.into()))
|
||||
}
|
||||
|
||||
let (
|
||||
babe_config,
|
||||
keystore,
|
||||
shared_epoch,
|
||||
client,
|
||||
select_chain,
|
||||
) = (
|
||||
let (babe_config, keystore, shared_epoch, client, select_chain) = (
|
||||
self.babe_config.clone(),
|
||||
self.keystore.clone(),
|
||||
self.shared_epoch_changes.clone(),
|
||||
@@ -126,14 +104,9 @@ where
|
||||
.runtime_api()
|
||||
.current_epoch_start(&BlockId::Hash(header.hash()))
|
||||
.map_err(|err| Error::StringError(format!("{:?}", err)))?;
|
||||
let epoch = epoch_data(
|
||||
&shared_epoch,
|
||||
&client,
|
||||
&babe_config,
|
||||
*epoch_start,
|
||||
&select_chain,
|
||||
)
|
||||
.await?;
|
||||
let epoch =
|
||||
epoch_data(&shared_epoch, &client, &babe_config, *epoch_start, &select_chain)
|
||||
.await?;
|
||||
let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot());
|
||||
|
||||
let mut claims: HashMap<AuthorityId, EpochAuthorship> = HashMap::new();
|
||||
@@ -163,10 +136,10 @@ where
|
||||
match claim {
|
||||
PreDigest::Primary { .. } => {
|
||||
claims.entry(key).or_default().primary.push(slot);
|
||||
}
|
||||
},
|
||||
PreDigest::SecondaryPlain { .. } => {
|
||||
claims.entry(key).or_default().secondary.push(slot);
|
||||
}
|
||||
},
|
||||
PreDigest::SecondaryVRF { .. } => {
|
||||
claims.entry(key).or_default().secondary_vrf.push(slot.into());
|
||||
},
|
||||
@@ -199,7 +172,7 @@ pub enum Error {
|
||||
/// Consensus error
|
||||
Consensus(ConsensusError),
|
||||
/// Errors that can be formatted as a String
|
||||
StringError(String)
|
||||
StringError(String),
|
||||
}
|
||||
|
||||
impl From<Error> for jsonrpc_core::Error {
|
||||
@@ -226,13 +199,15 @@ where
|
||||
SC: SelectChain<B>,
|
||||
{
|
||||
let parent = select_chain.best_chain().await?;
|
||||
epoch_changes.shared_data().epoch_data_for_child_of(
|
||||
descendent_query(&**client),
|
||||
&parent.hash(),
|
||||
parent.number().clone(),
|
||||
slot.into(),
|
||||
|slot| Epoch::genesis(&babe_config, slot),
|
||||
)
|
||||
epoch_changes
|
||||
.shared_data()
|
||||
.epoch_data_for_child_of(
|
||||
descendent_query(&**client),
|
||||
&parent.hash(),
|
||||
parent.number().clone(),
|
||||
slot.into(),
|
||||
|slot| Epoch::genesis(&babe_config, slot),
|
||||
)
|
||||
.map_err(|e| Error::Consensus(ConsensusError::ChainLookup(format!("{:?}", e))))?
|
||||
.ok_or(Error::Consensus(ConsensusError::InvalidAuthoritiesSet))
|
||||
}
|
||||
@@ -240,31 +215,27 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use substrate_test_runtime_client::{
|
||||
runtime::Block,
|
||||
Backend,
|
||||
DefaultTestClientBuilderExt,
|
||||
TestClient,
|
||||
TestClientBuilderExt,
|
||||
TestClientBuilder,
|
||||
};
|
||||
use sp_application_crypto::AppPair;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_core::{crypto::key_types::BABE};
|
||||
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
|
||||
use sc_keystore::LocalKeystore;
|
||||
use sp_application_crypto::AppPair;
|
||||
use sp_core::crypto::key_types::BABE;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
||||
use substrate_test_runtime_client::{
|
||||
runtime::Block, Backend, DefaultTestClientBuilderExt, TestClient, TestClientBuilder,
|
||||
TestClientBuilderExt,
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
use sc_consensus_babe::{Config, block_import, AuthorityPair};
|
||||
use jsonrpc_core::IoHandler;
|
||||
use sc_consensus_babe::{block_import, AuthorityPair, Config};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// creates keystore backed by a temp file
|
||||
fn create_temp_keystore<P: AppPair>(
|
||||
authority: Sr25519Keyring,
|
||||
) -> (SyncCryptoStorePtr, tempfile::TempDir) {
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let keystore =
|
||||
Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore"));
|
||||
SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(&authority.to_seed()))
|
||||
.expect("Creates authority key");
|
||||
|
||||
@@ -272,17 +243,14 @@ mod tests {
|
||||
}
|
||||
|
||||
fn test_babe_rpc_handler(
|
||||
deny_unsafe: DenyUnsafe
|
||||
deny_unsafe: DenyUnsafe,
|
||||
) -> BabeRpcHandler<Block, TestClient, sc_consensus::LongestChain<Backend, Block>> {
|
||||
let builder = TestClientBuilder::new();
|
||||
let (client, longest_chain) = builder.build_with_longest_chain();
|
||||
let client = Arc::new(client);
|
||||
let config = Config::get_or_compute(&*client).expect("config available");
|
||||
let (_, link) = block_import(
|
||||
config.clone(),
|
||||
client.clone(),
|
||||
client.clone(),
|
||||
).expect("can initialize block-import");
|
||||
let (_, link) = block_import(config.clone(), client.clone(), client.clone())
|
||||
.expect("can initialize block-import");
|
||||
|
||||
let epoch_changes = link.epoch_changes().clone();
|
||||
let keystore = create_temp_keystore::<AuthorityPair>(Sr25519Keyring::Alice).0;
|
||||
|
||||
@@ -18,23 +18,17 @@
|
||||
|
||||
//! BABE authority selection and slot claiming.
|
||||
|
||||
use super::Epoch;
|
||||
use codec::Encode;
|
||||
use schnorrkel::{keys::PublicKey, vrf::VRFInOut};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_consensus_babe::{
|
||||
BABE_VRF_PREFIX, AuthorityId, BabeAuthorityWeight, make_transcript, make_transcript_data,
|
||||
Slot,
|
||||
};
|
||||
use sp_consensus_babe::digests::{
|
||||
PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest,
|
||||
digests::{PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest},
|
||||
make_transcript, make_transcript_data, AuthorityId, BabeAuthorityWeight, Slot, BABE_VRF_PREFIX,
|
||||
};
|
||||
use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof};
|
||||
use sp_core::{U256, blake2_256, crypto::Public};
|
||||
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
|
||||
use codec::Encode;
|
||||
use schnorrkel::{
|
||||
keys::PublicKey,
|
||||
vrf::VRFInOut,
|
||||
};
|
||||
use super::Epoch;
|
||||
use sp_core::{blake2_256, crypto::Public, U256};
|
||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
||||
|
||||
/// Calculates the primary selection threshold for a given authority, taking
|
||||
/// into account `c` (`1 - c` represents the probability of a slot being empty).
|
||||
@@ -49,8 +43,7 @@ pub(super) fn calculate_primary_threshold(
|
||||
|
||||
let c = c.0 as f64 / c.1 as f64;
|
||||
|
||||
let theta =
|
||||
authorities[authority_index].1 as f64 /
|
||||
let theta = authorities[authority_index].1 as f64 /
|
||||
authorities.iter().map(|(_, weight)| weight).sum::<u64>() as f64;
|
||||
|
||||
assert!(theta > 0.0, "authority with weight 0.");
|
||||
@@ -74,14 +67,14 @@ pub(super) fn calculate_primary_threshold(
|
||||
"returns None when the given value is negative; \
|
||||
p is defined as `1 - n` where n is defined in (0, 1]; \
|
||||
p must be a value in [0, 1); \
|
||||
qed."
|
||||
qed.",
|
||||
);
|
||||
|
||||
let denom = p.denom().to_biguint().expect(
|
||||
"returns None when the given value is negative; \
|
||||
p is defined as `1 - n` where n is defined in (0, 1]; \
|
||||
p must be a value in [0, 1); \
|
||||
qed."
|
||||
qed.",
|
||||
);
|
||||
|
||||
((BigUint::one() << 128) * numer / denom).to_u128().expect(
|
||||
@@ -108,7 +101,7 @@ pub(super) fn secondary_slot_author(
|
||||
randomness: [u8; 32],
|
||||
) -> Option<&AuthorityId> {
|
||||
if authorities.is_empty() {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
let rand = U256::from((randomness, slot).using_encoded(blake2_256));
|
||||
@@ -116,9 +109,10 @@ pub(super) fn secondary_slot_author(
|
||||
let authorities_len = U256::from(authorities.len());
|
||||
let idx = rand % authorities_len;
|
||||
|
||||
let expected_author = authorities.get(idx.as_u32() as usize)
|
||||
.expect("authorities not empty; index constrained to list length; \
|
||||
this is a valid index; qed");
|
||||
let expected_author = authorities.get(idx.as_u32() as usize).expect(
|
||||
"authorities not empty; index constrained to list length; \
|
||||
this is a valid index; qed",
|
||||
);
|
||||
|
||||
Some(&expected_author.0)
|
||||
}
|
||||
@@ -136,23 +130,15 @@ fn claim_secondary_slot(
|
||||
let Epoch { authorities, randomness, epoch_index, .. } = epoch;
|
||||
|
||||
if authorities.is_empty() {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
let expected_author = secondary_slot_author(
|
||||
slot,
|
||||
authorities,
|
||||
*randomness,
|
||||
)?;
|
||||
let expected_author = secondary_slot_author(slot, authorities, *randomness)?;
|
||||
|
||||
for (authority_id, authority_index) in keys {
|
||||
if authority_id == expected_author {
|
||||
let pre_digest = if author_secondary_vrf {
|
||||
let transcript_data = make_transcript_data(
|
||||
randomness,
|
||||
slot,
|
||||
*epoch_index,
|
||||
);
|
||||
let transcript_data = make_transcript_data(randomness, slot, *epoch_index);
|
||||
let result = SyncCryptoStore::sr25519_vrf_sign(
|
||||
&**keystore,
|
||||
AuthorityId::ID,
|
||||
@@ -169,7 +155,10 @@ fn claim_secondary_slot(
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if SyncCryptoStore::has_keys(&**keystore, &[(authority_id.to_raw_vec(), AuthorityId::ID)]) {
|
||||
} else if SyncCryptoStore::has_keys(
|
||||
&**keystore,
|
||||
&[(authority_id.to_raw_vec(), AuthorityId::ID)],
|
||||
) {
|
||||
Some(PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
|
||||
slot,
|
||||
authority_index: *authority_index as u32,
|
||||
@@ -179,7 +168,7 @@ fn claim_secondary_slot(
|
||||
};
|
||||
|
||||
if let Some(pre_digest) = pre_digest {
|
||||
return Some((pre_digest, authority_id.clone()));
|
||||
return Some((pre_digest, authority_id.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +185,9 @@ pub fn claim_slot(
|
||||
epoch: &Epoch,
|
||||
keystore: &SyncCryptoStorePtr,
|
||||
) -> Option<(PreDigest, AuthorityId)> {
|
||||
let authorities = epoch.authorities.iter()
|
||||
let authorities = epoch
|
||||
.authorities
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, a)| (a.0.clone(), index))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -211,22 +202,21 @@ pub fn claim_slot_using_keys(
|
||||
keystore: &SyncCryptoStorePtr,
|
||||
keys: &[(AuthorityId, usize)],
|
||||
) -> Option<(PreDigest, AuthorityId)> {
|
||||
claim_primary_slot(slot, epoch, epoch.config.c, keystore, &keys)
|
||||
.or_else(|| {
|
||||
if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() ||
|
||||
epoch.config.allowed_slots.is_secondary_vrf_slots_allowed()
|
||||
{
|
||||
claim_secondary_slot(
|
||||
slot,
|
||||
&epoch,
|
||||
keys,
|
||||
&keystore,
|
||||
epoch.config.allowed_slots.is_secondary_vrf_slots_allowed(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
claim_primary_slot(slot, epoch, epoch.config.c, keystore, &keys).or_else(|| {
|
||||
if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() ||
|
||||
epoch.config.allowed_slots.is_secondary_vrf_slots_allowed()
|
||||
{
|
||||
claim_secondary_slot(
|
||||
slot,
|
||||
&epoch,
|
||||
keys,
|
||||
&keystore,
|
||||
epoch.config.allowed_slots.is_secondary_vrf_slots_allowed(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Claim a primary slot if it is our turn. Returns `None` if it is not our turn.
|
||||
@@ -243,16 +233,8 @@ fn claim_primary_slot(
|
||||
let Epoch { authorities, randomness, epoch_index, .. } = epoch;
|
||||
|
||||
for (authority_id, authority_index) in keys {
|
||||
let transcript = make_transcript(
|
||||
randomness,
|
||||
slot,
|
||||
*epoch_index
|
||||
);
|
||||
let transcript_data = make_transcript_data(
|
||||
randomness,
|
||||
slot,
|
||||
*epoch_index
|
||||
);
|
||||
let transcript = make_transcript(randomness, slot, *epoch_index);
|
||||
let transcript_data = make_transcript_data(randomness, slot, *epoch_index);
|
||||
// Compute the threshold we will use.
|
||||
//
|
||||
// We already checked that authorities contains `key.public()`, so it can't
|
||||
@@ -279,7 +261,7 @@ fn claim_primary_slot(
|
||||
authority_index: *authority_index as u32,
|
||||
});
|
||||
|
||||
return Some((pre_digest, authority_id.clone()));
|
||||
return Some((pre_digest, authority_id.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,10 +272,10 @@ fn claim_primary_slot(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use sp_core::{sr25519::Pair, crypto::Pair as _};
|
||||
use sp_consensus_babe::{AuthorityId, BabeEpochConfiguration, AllowedSlots};
|
||||
use sc_keystore::LocalKeystore;
|
||||
use sp_consensus_babe::{AllowedSlots, AuthorityId, BabeEpochConfiguration};
|
||||
use sp_core::{crypto::Pair as _, sr25519::Pair};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn claim_secondary_plain_slot_works() {
|
||||
@@ -302,7 +284,8 @@ mod tests {
|
||||
&*keystore,
|
||||
AuthorityId::ID,
|
||||
Some(sp_core::crypto::DEV_PHRASE),
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let authorities = vec![
|
||||
(AuthorityId::from(Pair::generate().0.public()), 5),
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
|
||||
//! Schema for BABE epoch changes in the aux-db.
|
||||
|
||||
use log::info;
|
||||
use codec::{Decode, Encode};
|
||||
use log::info;
|
||||
|
||||
use crate::{migration::EpochV0, Epoch};
|
||||
use sc_client_api::backend::AuxStore;
|
||||
use sp_blockchain::{Result as ClientResult, Error as ClientError};
|
||||
use sp_runtime::traits::Block as BlockT;
|
||||
use sc_consensus_epochs::{migration::EpochChangesForV0, EpochChangesFor, SharedEpochChanges};
|
||||
use sp_blockchain::{Error as ClientError, Result as ClientResult};
|
||||
use sp_consensus_babe::{BabeBlockWeight, BabeGenesisConfiguration};
|
||||
use sc_consensus_epochs::{EpochChangesFor, SharedEpochChanges, migration::EpochChangesForV0};
|
||||
use crate::{Epoch, migration::EpochV0};
|
||||
use sp_runtime::traits::Block as BlockT;
|
||||
|
||||
const BABE_EPOCH_CHANGES_VERSION: &[u8] = b"babe_epoch_changes_version";
|
||||
const BABE_EPOCH_CHANGES_KEY: &[u8] = b"babe_epoch_changes";
|
||||
@@ -38,16 +38,16 @@ pub fn block_weight_key<H: Encode>(block_hash: H) -> Vec<u8> {
|
||||
}
|
||||
|
||||
fn load_decode<B, T>(backend: &B, key: &[u8]) -> ClientResult<Option<T>>
|
||||
where
|
||||
B: AuxStore,
|
||||
T: Decode,
|
||||
where
|
||||
B: AuxStore,
|
||||
T: Decode,
|
||||
{
|
||||
let corrupt = |e: codec::Error| {
|
||||
ClientError::Backend(format!("BABE DB is corrupted. Decode error: {}", e))
|
||||
};
|
||||
match backend.get_aux(key)? {
|
||||
None => Ok(None),
|
||||
Some(t) => T::decode(&mut &t[..]).map(Some).map_err(corrupt)
|
||||
Some(t) => T::decode(&mut &t[..]).map(Some).map_err(corrupt),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,32 +59,26 @@ pub fn load_epoch_changes<Block: BlockT, B: AuxStore>(
|
||||
let version = load_decode::<_, u32>(backend, BABE_EPOCH_CHANGES_VERSION)?;
|
||||
|
||||
let maybe_epoch_changes = match version {
|
||||
None => load_decode::<_, EpochChangesForV0<Block, EpochV0>>(
|
||||
backend,
|
||||
BABE_EPOCH_CHANGES_KEY,
|
||||
)?.map(|v0| v0.migrate().map(|_, _, epoch| epoch.migrate(config))),
|
||||
Some(1) => load_decode::<_, EpochChangesFor<Block, EpochV0>>(
|
||||
backend,
|
||||
BABE_EPOCH_CHANGES_KEY,
|
||||
)?.map(|v1| v1.map(|_, _, epoch| epoch.migrate(config))),
|
||||
Some(BABE_EPOCH_CHANGES_CURRENT_VERSION) => load_decode::<_, EpochChangesFor<Block, Epoch>>(
|
||||
backend,
|
||||
BABE_EPOCH_CHANGES_KEY,
|
||||
)?,
|
||||
Some(other) => {
|
||||
return Err(ClientError::Backend(
|
||||
format!("Unsupported BABE DB version: {:?}", other)
|
||||
))
|
||||
},
|
||||
None =>
|
||||
load_decode::<_, EpochChangesForV0<Block, EpochV0>>(backend, BABE_EPOCH_CHANGES_KEY)?
|
||||
.map(|v0| v0.migrate().map(|_, _, epoch| epoch.migrate(config))),
|
||||
Some(1) =>
|
||||
load_decode::<_, EpochChangesFor<Block, EpochV0>>(backend, BABE_EPOCH_CHANGES_KEY)?
|
||||
.map(|v1| v1.map(|_, _, epoch| epoch.migrate(config))),
|
||||
Some(BABE_EPOCH_CHANGES_CURRENT_VERSION) =>
|
||||
load_decode::<_, EpochChangesFor<Block, Epoch>>(backend, BABE_EPOCH_CHANGES_KEY)?,
|
||||
Some(other) =>
|
||||
return Err(ClientError::Backend(format!("Unsupported BABE DB version: {:?}", other))),
|
||||
};
|
||||
|
||||
let epoch_changes = SharedEpochChanges::<Block, Epoch>::new(maybe_epoch_changes.unwrap_or_else(|| {
|
||||
info!(
|
||||
target: "babe",
|
||||
"👶 Creating empty BABE epoch changes on what appears to be first startup.",
|
||||
);
|
||||
EpochChangesFor::<Block, Epoch>::default()
|
||||
}));
|
||||
let epoch_changes =
|
||||
SharedEpochChanges::<Block, Epoch>::new(maybe_epoch_changes.unwrap_or_else(|| {
|
||||
info!(
|
||||
target: "babe",
|
||||
"👶 Creating empty BABE epoch changes on what appears to be first startup.",
|
||||
);
|
||||
EpochChangesFor::<Block, Epoch>::default()
|
||||
}));
|
||||
|
||||
// rebalance the tree after deserialization. this isn't strictly necessary
|
||||
// since the tree is now rebalanced on every update operation. but since the
|
||||
@@ -99,15 +93,16 @@ pub fn load_epoch_changes<Block: BlockT, B: AuxStore>(
|
||||
pub(crate) fn write_epoch_changes<Block: BlockT, F, R>(
|
||||
epoch_changes: &EpochChangesFor<Block, Epoch>,
|
||||
write_aux: F,
|
||||
) -> R where
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
|
||||
{
|
||||
BABE_EPOCH_CHANGES_CURRENT_VERSION.using_encoded(|version| {
|
||||
let encoded_epoch_changes = epoch_changes.encode();
|
||||
write_aux(
|
||||
&[(BABE_EPOCH_CHANGES_KEY, encoded_epoch_changes.as_slice()),
|
||||
(BABE_EPOCH_CHANGES_VERSION, version)],
|
||||
)
|
||||
write_aux(&[
|
||||
(BABE_EPOCH_CHANGES_KEY, encoded_epoch_changes.as_slice()),
|
||||
(BABE_EPOCH_CHANGES_VERSION, version),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,15 +111,12 @@ pub(crate) fn write_block_weight<H: Encode, F, R>(
|
||||
block_hash: H,
|
||||
block_weight: BabeBlockWeight,
|
||||
write_aux: F,
|
||||
) -> R where
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&[(Vec<u8>, &[u8])]) -> R,
|
||||
{
|
||||
let key = block_weight_key(block_hash);
|
||||
block_weight.using_encoded(|s|
|
||||
write_aux(
|
||||
&[(key, s)],
|
||||
)
|
||||
)
|
||||
block_weight.using_encoded(|s| write_aux(&[(key, s)]))
|
||||
}
|
||||
|
||||
/// Load the cumulative chain-weight associated with a block.
|
||||
@@ -140,13 +132,13 @@ mod test {
|
||||
use super::*;
|
||||
use crate::migration::EpochV0;
|
||||
use fork_tree::ForkTree;
|
||||
use substrate_test_runtime_client;
|
||||
use sc_consensus_epochs::{EpochHeader, PersistedEpoch, PersistedEpochHeader};
|
||||
use sc_network_test::Block as TestBlock;
|
||||
use sp_consensus::Error as ConsensusError;
|
||||
use sp_consensus_babe::{AllowedSlots, BabeGenesisConfiguration};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::traits::NumberFor;
|
||||
use sp_consensus_babe::{AllowedSlots, BabeGenesisConfiguration};
|
||||
use sc_consensus_epochs::{PersistedEpoch, PersistedEpochHeader, EpochHeader};
|
||||
use sp_consensus::Error as ConsensusError;
|
||||
use sc_network_test::Block as TestBlock;
|
||||
use substrate_test_runtime_client;
|
||||
|
||||
#[test]
|
||||
fn load_decode_from_v0_epoch_changes() {
|
||||
@@ -159,26 +151,30 @@ mod test {
|
||||
};
|
||||
let client = substrate_test_runtime_client::new();
|
||||
let mut v0_tree = ForkTree::<H256, NumberFor<TestBlock>, _>::new();
|
||||
v0_tree.import::<_, ConsensusError>(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
PersistedEpoch::Regular(epoch),
|
||||
&|_, _| Ok(false), // Test is single item only so this can be set to false.
|
||||
).unwrap();
|
||||
v0_tree
|
||||
.import::<_, ConsensusError>(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
PersistedEpoch::Regular(epoch),
|
||||
&|_, _| Ok(false), // Test is single item only so this can be set to false.
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
client.insert_aux(
|
||||
&[(BABE_EPOCH_CHANGES_KEY,
|
||||
&EpochChangesForV0::<TestBlock, EpochV0>::from_raw(v0_tree).encode()[..])],
|
||||
&[],
|
||||
).unwrap();
|
||||
client
|
||||
.insert_aux(
|
||||
&[(
|
||||
BABE_EPOCH_CHANGES_KEY,
|
||||
&EpochChangesForV0::<TestBlock, EpochV0>::from_raw(v0_tree).encode()[..],
|
||||
)],
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(),
|
||||
None,
|
||||
);
|
||||
assert_eq!(load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), None,);
|
||||
|
||||
let epoch_changes = load_epoch_changes::<TestBlock, _>(
|
||||
&client, &BabeGenesisConfiguration {
|
||||
&client,
|
||||
&BabeGenesisConfiguration {
|
||||
slot_duration: 10,
|
||||
epoch_length: 4,
|
||||
c: (3, 10),
|
||||
@@ -186,10 +182,12 @@ mod test {
|
||||
randomness: Default::default(),
|
||||
allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots,
|
||||
},
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
epoch_changes.shared_data()
|
||||
epoch_changes
|
||||
.shared_data()
|
||||
.tree()
|
||||
.iter()
|
||||
.map(|(_, _, epoch)| epoch.clone())
|
||||
@@ -200,16 +198,10 @@ mod test {
|
||||
})],
|
||||
); // PersistedEpochHeader does not implement Debug, so we use assert! directly.
|
||||
|
||||
write_epoch_changes::<TestBlock, _, _>(
|
||||
&epoch_changes.shared_data(),
|
||||
|values| {
|
||||
client.insert_aux(values, &[]).unwrap();
|
||||
},
|
||||
);
|
||||
write_epoch_changes::<TestBlock, _, _>(&epoch_changes.shared_data(), |values| {
|
||||
client.insert_aux(values, &[]).unwrap();
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(),
|
||||
Some(2),
|
||||
);
|
||||
assert_eq!(load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), Some(2),);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,12 +16,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use sc_consensus_epochs::Epoch as EpochT;
|
||||
use crate::{
|
||||
Epoch, AuthorityId, BabeAuthorityWeight, BabeGenesisConfiguration,
|
||||
BabeEpochConfiguration, VRF_OUTPUT_LENGTH, NextEpochDescriptor,
|
||||
AuthorityId, BabeAuthorityWeight, BabeEpochConfiguration, BabeGenesisConfiguration, Epoch,
|
||||
NextEpochDescriptor, VRF_OUTPUT_LENGTH,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use sc_consensus_epochs::Epoch as EpochT;
|
||||
use sp_consensus_slots::Slot;
|
||||
|
||||
/// BABE epoch information, version 0.
|
||||
@@ -43,10 +43,7 @@ impl EpochT for EpochV0 {
|
||||
type NextEpochDescriptor = NextEpochDescriptor;
|
||||
type Slot = Slot;
|
||||
|
||||
fn increment(
|
||||
&self,
|
||||
descriptor: NextEpochDescriptor
|
||||
) -> EpochV0 {
|
||||
fn increment(&self, descriptor: NextEpochDescriptor) -> EpochV0 {
|
||||
EpochV0 {
|
||||
epoch_index: self.epoch_index + 1,
|
||||
start_slot: self.start_slot + self.duration,
|
||||
@@ -74,10 +71,7 @@ impl EpochV0 {
|
||||
duration: self.duration,
|
||||
authorities: self.authorities,
|
||||
randomness: self.randomness,
|
||||
config: BabeEpochConfiguration {
|
||||
c: config.c,
|
||||
allowed_slots: config.allowed_slots,
|
||||
},
|
||||
config: BabeEpochConfiguration { c: config.c, allowed_slots: config.allowed_slots },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,35 +23,33 @@
|
||||
#![allow(deprecated)]
|
||||
use super::*;
|
||||
use authorship::claim_slot;
|
||||
use sp_core::crypto::Pair;
|
||||
use sp_keystore::{
|
||||
SyncCryptoStore,
|
||||
vrf::make_transcript as transcript_from_data,
|
||||
use futures::executor::block_on;
|
||||
use log::debug;
|
||||
use rand::RngCore;
|
||||
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
|
||||
use sc_block_builder::{BlockBuilder, BlockBuilderProvider};
|
||||
use sc_client_api::{backend::TransactionFor, BlockchainEvents};
|
||||
use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging;
|
||||
use sc_keystore::LocalKeystore;
|
||||
use sc_network::config::ProtocolConfig;
|
||||
use sc_network_test::{Block as TestBlock, *};
|
||||
use sp_application_crypto::key_types::BABE;
|
||||
use sp_consensus::{
|
||||
import_queue::{BoxBlockImport, BoxJustificationImport},
|
||||
AlwaysCanAuthor, DisableProofRecording, NoNetwork as DummyOracle, Proposal,
|
||||
};
|
||||
use sp_consensus_babe::{
|
||||
AuthorityPair, Slot, AllowedSlots, make_transcript, make_transcript_data,
|
||||
inherents::InherentDataProvider,
|
||||
inherents::InherentDataProvider, make_transcript, make_transcript_data, AllowedSlots,
|
||||
AuthorityPair, Slot,
|
||||
};
|
||||
use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging;
|
||||
use sc_block_builder::{BlockBuilder, BlockBuilderProvider};
|
||||
use sp_consensus::{
|
||||
NoNetwork as DummyOracle, Proposal, DisableProofRecording, AlwaysCanAuthor,
|
||||
import_queue::{BoxBlockImport, BoxJustificationImport},
|
||||
use sp_core::crypto::Pair;
|
||||
use sp_keystore::{vrf::make_transcript as transcript_from_data, SyncCryptoStore};
|
||||
use sp_runtime::{
|
||||
generic::DigestItem,
|
||||
traits::{Block as BlockT, DigestFor},
|
||||
};
|
||||
use sc_network_test::{Block as TestBlock, *};
|
||||
use sc_network::config::ProtocolConfig;
|
||||
use sp_runtime::{generic::DigestItem, traits::{Block as BlockT, DigestFor}};
|
||||
use sc_client_api::{BlockchainEvents, backend::TransactionFor};
|
||||
use log::debug;
|
||||
use std::{time::Duration, cell::RefCell, task::Poll};
|
||||
use rand::RngCore;
|
||||
use rand_chacha::{
|
||||
rand_core::SeedableRng, ChaChaRng,
|
||||
};
|
||||
use sc_keystore::LocalKeystore;
|
||||
use sp_application_crypto::key_types::BABE;
|
||||
use futures::executor::block_on;
|
||||
use sp_timestamp::InherentDataProvider as TimestampInherentDataProvider;
|
||||
use std::{cell::RefCell, task::Poll, time::Duration};
|
||||
|
||||
type Item = DigestItem<Hash>;
|
||||
|
||||
@@ -95,10 +93,7 @@ impl Environment<TestBlock> for DummyFactory {
|
||||
type Proposer = DummyProposer;
|
||||
type Error = Error;
|
||||
|
||||
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header)
|
||||
-> Self::CreateProposer
|
||||
{
|
||||
|
||||
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header) -> Self::CreateProposer {
|
||||
let parent_slot = crate::find_pre_digest::<TestBlock>(parent_header)
|
||||
.expect("parent header has a pre-digest")
|
||||
.slot();
|
||||
@@ -113,23 +108,24 @@ impl Environment<TestBlock> for DummyFactory {
|
||||
}
|
||||
|
||||
impl DummyProposer {
|
||||
fn propose_with(&mut self, pre_digests: DigestFor<TestBlock>)
|
||||
-> future::Ready<
|
||||
Result<
|
||||
Proposal<
|
||||
TestBlock,
|
||||
sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>,
|
||||
()
|
||||
>,
|
||||
Error
|
||||
>
|
||||
>
|
||||
{
|
||||
let block_builder = self.factory.client.new_block_at(
|
||||
&BlockId::Hash(self.parent_hash),
|
||||
pre_digests,
|
||||
false,
|
||||
).unwrap();
|
||||
fn propose_with(
|
||||
&mut self,
|
||||
pre_digests: DigestFor<TestBlock>,
|
||||
) -> future::Ready<
|
||||
Result<
|
||||
Proposal<
|
||||
TestBlock,
|
||||
sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>,
|
||||
(),
|
||||
>,
|
||||
Error,
|
||||
>,
|
||||
> {
|
||||
let block_builder = self
|
||||
.factory
|
||||
.client
|
||||
.new_block_at(&BlockId::Hash(self.parent_hash), pre_digests, false)
|
||||
.unwrap();
|
||||
|
||||
let mut block = match block_builder.build().map_err(|e| e.into()) {
|
||||
Ok(b) => b.block,
|
||||
@@ -143,13 +139,14 @@ impl DummyProposer {
|
||||
// figure out if we should add a consensus digest, since the test runtime
|
||||
// doesn't.
|
||||
let epoch_changes = self.factory.epoch_changes.shared_data();
|
||||
let epoch = epoch_changes.epoch_data_for_child_of(
|
||||
descendent_query(&*self.factory.client),
|
||||
&self.parent_hash,
|
||||
self.parent_number,
|
||||
this_slot,
|
||||
|slot| Epoch::genesis(&self.factory.config, slot),
|
||||
)
|
||||
let epoch = epoch_changes
|
||||
.epoch_data_for_child_of(
|
||||
descendent_query(&*self.factory.client),
|
||||
&self.parent_hash,
|
||||
self.parent_number,
|
||||
this_slot,
|
||||
|slot| Epoch::genesis(&self.factory.config, slot),
|
||||
)
|
||||
.expect("client has data to find epoch")
|
||||
.expect("can compute epoch for baked block");
|
||||
|
||||
@@ -162,7 +159,8 @@ impl DummyProposer {
|
||||
let digest_data = ConsensusLog::NextEpochData(NextEpochDescriptor {
|
||||
authorities: epoch.authorities.clone(),
|
||||
randomness: epoch.randomness.clone(),
|
||||
}).encode();
|
||||
})
|
||||
.encode();
|
||||
let digest = DigestItem::Consensus(BABE_ENGINE_ID, digest_data);
|
||||
block.header.digest_mut().push(digest)
|
||||
}
|
||||
@@ -176,7 +174,8 @@ impl DummyProposer {
|
||||
|
||||
impl Proposer<TestBlock> for DummyProposer {
|
||||
type Error = Error;
|
||||
type Transaction = sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>;
|
||||
type Transaction =
|
||||
sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>;
|
||||
type Proposal = future::Ready<Result<Proposal<TestBlock, Self::Transaction, ()>, Error>>;
|
||||
type ProofRecording = DisableProofRecording;
|
||||
type Proof = ();
|
||||
@@ -201,9 +200,9 @@ pub struct PanickingBlockImport<B>(B);
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B: BlockImport<TestBlock>> BlockImport<TestBlock> for PanickingBlockImport<B>
|
||||
where
|
||||
B::Transaction: Send,
|
||||
B: Send,
|
||||
where
|
||||
B::Transaction: Send,
|
||||
B: Send,
|
||||
{
|
||||
type Error = B::Error;
|
||||
type Transaction = B::Transaction;
|
||||
@@ -233,10 +232,8 @@ pub struct BabeTestNet {
|
||||
type TestHeader = <TestBlock as BlockT>::Header;
|
||||
type TestExtrinsic = <TestBlock as BlockT>::Extrinsic;
|
||||
|
||||
type TestSelectChain = substrate_test_runtime_client::LongestChain<
|
||||
substrate_test_runtime_client::Backend,
|
||||
TestBlock,
|
||||
>;
|
||||
type TestSelectChain =
|
||||
substrate_test_runtime_client::LongestChain<substrate_test_runtime_client::Backend, TestBlock>;
|
||||
|
||||
pub struct TestVerifier {
|
||||
inner: BabeVerifier<
|
||||
@@ -244,11 +241,13 @@ pub struct TestVerifier {
|
||||
PeersFullClient,
|
||||
TestSelectChain,
|
||||
AlwaysCanAuthor,
|
||||
Box<dyn CreateInherentDataProviders<
|
||||
TestBlock,
|
||||
(),
|
||||
InherentDataProviders = (TimestampInherentDataProvider, InherentDataProvider)
|
||||
>>
|
||||
Box<
|
||||
dyn CreateInherentDataProviders<
|
||||
TestBlock,
|
||||
(),
|
||||
InherentDataProviders = (TimestampInherentDataProvider, InherentDataProvider),
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
mutator: Mutator,
|
||||
}
|
||||
@@ -274,7 +273,12 @@ impl Verifier<TestBlock> for TestVerifier {
|
||||
pub struct PeerData {
|
||||
link: BabeLink<TestBlock>,
|
||||
block_import: Mutex<
|
||||
Option<BoxBlockImport<TestBlock, TransactionFor<substrate_test_runtime_client::Backend, TestBlock>>>
|
||||
Option<
|
||||
BoxBlockImport<
|
||||
TestBlock,
|
||||
TransactionFor<substrate_test_runtime_client::Backend, TestBlock>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
@@ -286,32 +290,27 @@ impl TestNetFactory for BabeTestNet {
|
||||
/// Create new test network with peers and given config.
|
||||
fn from_config(_config: &ProtocolConfig) -> Self {
|
||||
debug!(target: "babe", "Creating test network from config");
|
||||
BabeTestNet {
|
||||
peers: Vec::new(),
|
||||
}
|
||||
BabeTestNet { peers: Vec::new() }
|
||||
}
|
||||
|
||||
fn make_block_import(&self, client: PeersClient)
|
||||
-> (
|
||||
BlockImportAdapter<Self::BlockImport>,
|
||||
Option<BoxJustificationImport<Block>>,
|
||||
Option<PeerData>,
|
||||
)
|
||||
{
|
||||
fn make_block_import(
|
||||
&self,
|
||||
client: PeersClient,
|
||||
) -> (
|
||||
BlockImportAdapter<Self::BlockImport>,
|
||||
Option<BoxJustificationImport<Block>>,
|
||||
Option<PeerData>,
|
||||
) {
|
||||
let client = client.as_full().expect("only full clients are tested");
|
||||
|
||||
let config = Config::get_or_compute(&*client).expect("config available");
|
||||
let (block_import, link) = crate::block_import(
|
||||
config,
|
||||
client.clone(),
|
||||
client.clone(),
|
||||
).expect("can initialize block-import");
|
||||
let (block_import, link) = crate::block_import(config, client.clone(), client.clone())
|
||||
.expect("can initialize block-import");
|
||||
|
||||
let block_import = PanickingBlockImport(block_import);
|
||||
|
||||
let data_block_import = Mutex::new(
|
||||
Some(Box::new(block_import.clone()) as BoxBlockImport<_, _>)
|
||||
);
|
||||
let data_block_import =
|
||||
Mutex::new(Some(Box::new(block_import.clone()) as BoxBlockImport<_, _>));
|
||||
(
|
||||
BlockImportAdapter::new(block_import),
|
||||
None,
|
||||
@@ -324,16 +323,16 @@ impl TestNetFactory for BabeTestNet {
|
||||
client: PeersClient,
|
||||
_cfg: &ProtocolConfig,
|
||||
maybe_link: &Option<PeerData>,
|
||||
)
|
||||
-> Self::Verifier
|
||||
{
|
||||
) -> Self::Verifier {
|
||||
use substrate_test_runtime_client::DefaultTestClientBuilderExt;
|
||||
|
||||
let client = client.as_full().expect("only full clients are used in test");
|
||||
trace!(target: "babe", "Creating a verifier");
|
||||
|
||||
// ensure block import and verifier are linked correctly.
|
||||
let data = maybe_link.as_ref().expect("babe link always provided to verifier instantiation");
|
||||
let data = maybe_link
|
||||
.as_ref()
|
||||
.expect("babe link always provided to verifier instantiation");
|
||||
|
||||
let (_, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
|
||||
|
||||
@@ -369,10 +368,7 @@ impl TestNetFactory for BabeTestNet {
|
||||
&self.peers
|
||||
}
|
||||
|
||||
fn mut_peers<F: FnOnce(&mut Vec<BabePeer>)>(
|
||||
&mut self,
|
||||
closure: F,
|
||||
) {
|
||||
fn mut_peers<F: FnOnce(&mut Vec<BabePeer>)>(&mut self, closure: F) {
|
||||
closure(&mut self.peers);
|
||||
}
|
||||
}
|
||||
@@ -382,9 +378,7 @@ impl TestNetFactory for BabeTestNet {
|
||||
fn rejects_empty_block() {
|
||||
sp_tracing::try_init_simple();
|
||||
let mut net = BabeTestNet::new(3);
|
||||
let block_builder = |builder: BlockBuilder<_, _, _>| {
|
||||
builder.build().unwrap().block
|
||||
};
|
||||
let block_builder = |builder: BlockBuilder<_, _, _>| builder.build().unwrap().block;
|
||||
net.mut_peers(|peer| {
|
||||
peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder);
|
||||
})
|
||||
@@ -397,11 +391,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static
|
||||
MUTATOR.with(|m| *m.borrow_mut() = mutator.clone());
|
||||
let net = BabeTestNet::new(3);
|
||||
|
||||
let peers = &[
|
||||
(0, "//Alice"),
|
||||
(1, "//Bob"),
|
||||
(2, "//Charlie"),
|
||||
];
|
||||
let peers = &[(0, "//Alice"), (1, "//Bob"), (2, "//Charlie")];
|
||||
|
||||
let net = Arc::new(Mutex::new(net));
|
||||
let mut import_notifications = Vec::new();
|
||||
@@ -415,9 +405,10 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static
|
||||
let select_chain = peer.select_chain().expect("Full client has select_chain");
|
||||
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(seed)).expect("Generates authority key");
|
||||
let keystore: SyncCryptoStorePtr =
|
||||
Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore"));
|
||||
SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(seed))
|
||||
.expect("Generates authority key");
|
||||
keystore_paths.push(keystore_path);
|
||||
|
||||
let mut got_own = false;
|
||||
@@ -435,47 +426,54 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static
|
||||
import_notifications.push(
|
||||
// run each future until we get one of our own blocks with number higher than 5
|
||||
// that was produced locally.
|
||||
client.import_notification_stream()
|
||||
.take_while(move |n| future::ready(n.header.number() < &5 || {
|
||||
if n.origin == BlockOrigin::Own {
|
||||
got_own = true;
|
||||
} else {
|
||||
got_other = true;
|
||||
}
|
||||
client
|
||||
.import_notification_stream()
|
||||
.take_while(move |n| {
|
||||
future::ready(
|
||||
n.header.number() < &5 || {
|
||||
if n.origin == BlockOrigin::Own {
|
||||
got_own = true;
|
||||
} else {
|
||||
got_other = true;
|
||||
}
|
||||
|
||||
// continue until we have at least one block of our own
|
||||
// and one of another peer.
|
||||
!(got_own && got_other)
|
||||
}))
|
||||
.for_each(|_| future::ready(()) )
|
||||
// continue until we have at least one block of our own
|
||||
// and one of another peer.
|
||||
!(got_own && got_other)
|
||||
},
|
||||
)
|
||||
})
|
||||
.for_each(|_| future::ready(())),
|
||||
);
|
||||
|
||||
babe_futures.push(
|
||||
start_babe(BabeParams {
|
||||
block_import: data.block_import.lock().take().expect("import set up during init"),
|
||||
select_chain,
|
||||
client,
|
||||
env: environ,
|
||||
sync_oracle: DummyOracle,
|
||||
create_inherent_data_providers: Box::new(|_, _| async {
|
||||
let timestamp = TimestampInherentDataProvider::from_system_time();
|
||||
let slot = InherentDataProvider::from_timestamp_and_duration(
|
||||
*timestamp,
|
||||
Duration::from_secs(6),
|
||||
);
|
||||
|
||||
babe_futures.push(start_babe(BabeParams {
|
||||
block_import: data.block_import.lock().take().expect("import set up during init"),
|
||||
select_chain,
|
||||
client,
|
||||
env: environ,
|
||||
sync_oracle: DummyOracle,
|
||||
create_inherent_data_providers: Box::new(|_, _| async {
|
||||
let timestamp = TimestampInherentDataProvider::from_system_time();
|
||||
let slot = InherentDataProvider::from_timestamp_and_duration(
|
||||
*timestamp,
|
||||
Duration::from_secs(6),
|
||||
);
|
||||
|
||||
Ok((timestamp, slot))
|
||||
}),
|
||||
force_authoring: false,
|
||||
backoff_authoring_blocks: Some(BackoffAuthoringOnFinalizedHeadLagging::default()),
|
||||
babe_link: data.link.clone(),
|
||||
keystore,
|
||||
can_author_with: sp_consensus::AlwaysCanAuthor,
|
||||
justification_sync_link: (),
|
||||
block_proposal_slot_portion: SlotProportion::new(0.5),
|
||||
max_block_proposal_slot_portion: None,
|
||||
telemetry: None,
|
||||
}).expect("Starts babe"));
|
||||
Ok((timestamp, slot))
|
||||
}),
|
||||
force_authoring: false,
|
||||
backoff_authoring_blocks: Some(BackoffAuthoringOnFinalizedHeadLagging::default()),
|
||||
babe_link: data.link.clone(),
|
||||
keystore,
|
||||
can_author_with: sp_consensus::AlwaysCanAuthor,
|
||||
justification_sync_link: (),
|
||||
block_proposal_slot_portion: SlotProportion::new(0.5),
|
||||
max_block_proposal_slot_portion: None,
|
||||
telemetry: None,
|
||||
})
|
||||
.expect("Starts babe"),
|
||||
);
|
||||
}
|
||||
block_on(future::select(
|
||||
futures::future::poll_fn(move |cx| {
|
||||
@@ -489,7 +487,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static
|
||||
|
||||
Poll::<()>::Pending
|
||||
}),
|
||||
future::select(future::join_all(import_notifications), future::join_all(babe_futures))
|
||||
future::select(future::join_all(import_notifications), future::join_all(babe_futures)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -503,7 +501,8 @@ fn authoring_blocks() {
|
||||
fn rejects_missing_inherent_digest() {
|
||||
run_one_test(|header: &mut TestHeader, stage| {
|
||||
let v = std::mem::take(&mut header.digest_mut().logs);
|
||||
header.digest_mut().logs = v.into_iter()
|
||||
header.digest_mut().logs = v
|
||||
.into_iter()
|
||||
.filter(|v| stage == Stage::PostSeal || v.as_babe_pre_digest().is_none())
|
||||
.collect()
|
||||
})
|
||||
@@ -514,7 +513,8 @@ fn rejects_missing_inherent_digest() {
|
||||
fn rejects_missing_seals() {
|
||||
run_one_test(|header: &mut TestHeader, stage| {
|
||||
let v = std::mem::take(&mut header.digest_mut().logs);
|
||||
header.digest_mut().logs = v.into_iter()
|
||||
header.digest_mut().logs = v
|
||||
.into_iter()
|
||||
.filter(|v| stage == Stage::PreSeal || v.as_babe_seal().is_none())
|
||||
.collect()
|
||||
})
|
||||
@@ -525,7 +525,8 @@ fn rejects_missing_seals() {
|
||||
fn rejects_missing_consensus_digests() {
|
||||
run_one_test(|header: &mut TestHeader, stage| {
|
||||
let v = std::mem::take(&mut header.digest_mut().logs);
|
||||
header.digest_mut().logs = v.into_iter()
|
||||
header.digest_mut().logs = v
|
||||
.into_iter()
|
||||
.filter(|v| stage == Stage::PostSeal || v.as_next_epoch_descriptor().is_none())
|
||||
.collect()
|
||||
});
|
||||
@@ -560,8 +561,8 @@ fn sig_is_not_pre_digest() {
|
||||
fn can_author_block() {
|
||||
sp_tracing::try_init_simple();
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let keystore: SyncCryptoStorePtr =
|
||||
Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore"));
|
||||
let public = SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some("//Alice"))
|
||||
.expect("Generates authority pair");
|
||||
|
||||
@@ -601,8 +602,8 @@ fn can_author_block() {
|
||||
None => i += 1,
|
||||
Some(s) => {
|
||||
debug!(target: "babe", "Authored block {:?}", s.0);
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -622,26 +623,27 @@ fn propose_and_import_block<Transaction: Send + 'static>(
|
||||
});
|
||||
|
||||
let pre_digest = sp_runtime::generic::Digest {
|
||||
logs: vec![
|
||||
Item::babe_pre_digest(
|
||||
PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
|
||||
authority_index: 0,
|
||||
slot,
|
||||
}),
|
||||
),
|
||||
],
|
||||
logs: vec![Item::babe_pre_digest(PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
|
||||
authority_index: 0,
|
||||
slot,
|
||||
}))],
|
||||
};
|
||||
|
||||
let parent_hash = parent.hash();
|
||||
|
||||
let mut block = futures::executor::block_on(proposer.propose_with(pre_digest)).unwrap().block;
|
||||
|
||||
let epoch_descriptor = proposer_factory.epoch_changes.shared_data().epoch_descriptor_for_child_of(
|
||||
descendent_query(&*proposer_factory.client),
|
||||
&parent_hash,
|
||||
*parent.number(),
|
||||
slot,
|
||||
).unwrap().unwrap();
|
||||
let epoch_descriptor = proposer_factory
|
||||
.epoch_changes
|
||||
.shared_data()
|
||||
.epoch_descriptor_for_child_of(
|
||||
descendent_query(&*proposer_factory.client),
|
||||
&parent_hash,
|
||||
*parent.number(),
|
||||
slot,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let seal = {
|
||||
// sign the pre-sealed hash of the block and then
|
||||
@@ -706,13 +708,12 @@ fn importing_block_one_sets_genesis_epoch() {
|
||||
let genesis_epoch = Epoch::genesis(&data.link.config, 999.into());
|
||||
|
||||
let epoch_changes = data.link.epoch_changes.shared_data();
|
||||
let epoch_for_second_block = epoch_changes.epoch_data_for_child_of(
|
||||
descendent_query(&*client),
|
||||
&block_hash,
|
||||
1,
|
||||
1000.into(),
|
||||
|slot| Epoch::genesis(&data.link.config, slot),
|
||||
).unwrap().unwrap();
|
||||
let epoch_for_second_block = epoch_changes
|
||||
.epoch_data_for_child_of(descendent_query(&*client), &block_hash, 1, 1000.into(), |slot| {
|
||||
Epoch::genesis(&data.link.config, slot)
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(epoch_for_second_block, genesis_epoch);
|
||||
}
|
||||
@@ -779,16 +780,10 @@ fn importing_epoch_change_block_prunes_tree() {
|
||||
let fork_3 = propose_and_import_blocks(BlockId::Hash(canon_hashes[18]), 10);
|
||||
|
||||
// We should be tracking a total of 9 epochs in the fork tree
|
||||
assert_eq!(
|
||||
epoch_changes.shared_data().tree().iter().count(),
|
||||
9,
|
||||
);
|
||||
assert_eq!(epoch_changes.shared_data().tree().iter().count(), 9,);
|
||||
|
||||
// And only one root
|
||||
assert_eq!(
|
||||
epoch_changes.shared_data().tree().roots().count(),
|
||||
1,
|
||||
);
|
||||
assert_eq!(epoch_changes.shared_data().tree().roots().count(), 1,);
|
||||
|
||||
// We finalize block #13 from the canon chain, so on the next epoch
|
||||
// change the tree should be pruned, to not contain F (#7).
|
||||
@@ -796,32 +791,47 @@ fn importing_epoch_change_block_prunes_tree() {
|
||||
propose_and_import_blocks(BlockId::Hash(client.chain_info().best_hash), 7);
|
||||
|
||||
// at this point no hashes from the first fork must exist on the tree
|
||||
assert!(
|
||||
!epoch_changes.shared_data().tree().iter().map(|(h, _, _)| h).any(|h| fork_1.contains(h)),
|
||||
);
|
||||
assert!(!epoch_changes
|
||||
.shared_data()
|
||||
.tree()
|
||||
.iter()
|
||||
.map(|(h, _, _)| h)
|
||||
.any(|h| fork_1.contains(h)),);
|
||||
|
||||
// but the epoch changes from the other forks must still exist
|
||||
assert!(
|
||||
epoch_changes.shared_data().tree().iter().map(|(h, _, _)| h).any(|h| fork_2.contains(h))
|
||||
);
|
||||
assert!(epoch_changes
|
||||
.shared_data()
|
||||
.tree()
|
||||
.iter()
|
||||
.map(|(h, _, _)| h)
|
||||
.any(|h| fork_2.contains(h)));
|
||||
|
||||
assert!(
|
||||
epoch_changes.shared_data().tree().iter().map(|(h, _, _)| h).any(|h| fork_3.contains(h)),
|
||||
);
|
||||
assert!(epoch_changes
|
||||
.shared_data()
|
||||
.tree()
|
||||
.iter()
|
||||
.map(|(h, _, _)| h)
|
||||
.any(|h| fork_3.contains(h)),);
|
||||
|
||||
// finalizing block #25 from the canon chain should prune out the second fork
|
||||
client.finalize_block(BlockId::Hash(canon_hashes[24]), None, false).unwrap();
|
||||
propose_and_import_blocks(BlockId::Hash(client.chain_info().best_hash), 8);
|
||||
|
||||
// at this point no hashes from the second fork must exist on the tree
|
||||
assert!(
|
||||
!epoch_changes.shared_data().tree().iter().map(|(h, _, _)| h).any(|h| fork_2.contains(h)),
|
||||
);
|
||||
assert!(!epoch_changes
|
||||
.shared_data()
|
||||
.tree()
|
||||
.iter()
|
||||
.map(|(h, _, _)| h)
|
||||
.any(|h| fork_2.contains(h)),);
|
||||
|
||||
// while epoch changes from the last fork should still exist
|
||||
assert!(
|
||||
epoch_changes.shared_data().tree().iter().map(|(h, _, _)| h).any(|h| fork_3.contains(h)),
|
||||
);
|
||||
assert!(epoch_changes
|
||||
.shared_data()
|
||||
.tree()
|
||||
.iter()
|
||||
.map(|(h, _, _)| h)
|
||||
.any(|h| fork_3.contains(h)),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -856,20 +866,15 @@ fn verify_slots_are_strictly_increasing() {
|
||||
|
||||
// we should fail to import this block since the slot number didn't increase.
|
||||
// we will panic due to the `PanickingBlockImport` defined above.
|
||||
propose_and_import_block(
|
||||
&b1,
|
||||
Some(999.into()),
|
||||
&mut proposer_factory,
|
||||
&mut block_import,
|
||||
);
|
||||
propose_and_import_block(&b1, Some(999.into()), &mut proposer_factory, &mut block_import);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn babe_transcript_generation_match() {
|
||||
sp_tracing::try_init_simple();
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let keystore: SyncCryptoStorePtr =
|
||||
Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore"));
|
||||
let public = SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some("//Alice"))
|
||||
.expect("Generates authority pair");
|
||||
|
||||
@@ -890,9 +895,7 @@ fn babe_transcript_generation_match() {
|
||||
|
||||
let test = |t: merlin::Transcript| -> [u8; 16] {
|
||||
let mut b = [0u8; 16];
|
||||
t.build_rng()
|
||||
.finalize(&mut ChaChaRng::from_seed([0u8;32]))
|
||||
.fill_bytes(&mut b);
|
||||
t.build_rng().finalize(&mut ChaChaRng::from_seed([0u8; 32])).fill_bytes(&mut b);
|
||||
b
|
||||
};
|
||||
debug_assert!(test(orig_transcript) == test(transcript_from_data(new_transcript)));
|
||||
|
||||
@@ -17,18 +17,22 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Verification for BABE headers.
|
||||
use sp_runtime::{traits::Header, traits::DigestItemFor};
|
||||
use sp_core::{Pair, Public};
|
||||
use sp_consensus_babe::{make_transcript, AuthoritySignature, AuthorityPair, AuthorityId};
|
||||
use sp_consensus_babe::digests::{
|
||||
PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest,
|
||||
CompatibleDigestItem
|
||||
use super::{
|
||||
authorship::{calculate_primary_threshold, check_primary_threshold, secondary_slot_author},
|
||||
babe_err, find_pre_digest, BlockT, Epoch, Error,
|
||||
};
|
||||
use sc_consensus_slots::CheckedHeader;
|
||||
use sp_consensus_slots::Slot;
|
||||
use log::{debug, trace};
|
||||
use super::{find_pre_digest, babe_err, Epoch, BlockT, Error};
|
||||
use super::authorship::{calculate_primary_threshold, check_primary_threshold, secondary_slot_author};
|
||||
use sc_consensus_slots::CheckedHeader;
|
||||
use sp_consensus_babe::{
|
||||
digests::{
|
||||
CompatibleDigestItem, PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest,
|
||||
SecondaryVRFPreDigest,
|
||||
},
|
||||
make_transcript, AuthorityId, AuthorityPair, AuthoritySignature,
|
||||
};
|
||||
use sp_consensus_slots::Slot;
|
||||
use sp_core::{Pair, Public};
|
||||
use sp_runtime::traits::{DigestItemFor, Header};
|
||||
|
||||
/// BABE verification parameters
|
||||
pub(super) struct VerificationParams<'a, B: 'a + BlockT> {
|
||||
@@ -57,26 +61,24 @@ pub(super) struct VerificationParams<'a, B: 'a + BlockT> {
|
||||
/// with each having different validation logic.
|
||||
pub(super) fn check_header<B: BlockT + Sized>(
|
||||
params: VerificationParams<B>,
|
||||
) -> Result<CheckedHeader<B::Header, VerifiedHeaderInfo<B>>, Error<B>> where
|
||||
) -> Result<CheckedHeader<B::Header, VerifiedHeaderInfo<B>>, Error<B>>
|
||||
where
|
||||
DigestItemFor<B>: CompatibleDigestItem,
|
||||
{
|
||||
let VerificationParams {
|
||||
mut header,
|
||||
pre_digest,
|
||||
slot_now,
|
||||
epoch,
|
||||
} = params;
|
||||
let VerificationParams { mut header, pre_digest, slot_now, epoch } = params;
|
||||
|
||||
let authorities = &epoch.authorities;
|
||||
let pre_digest = pre_digest.map(Ok).unwrap_or_else(|| find_pre_digest::<B>(&header))?;
|
||||
|
||||
trace!(target: "babe", "Checking header");
|
||||
let seal = header.digest_mut().pop()
|
||||
let seal = header
|
||||
.digest_mut()
|
||||
.pop()
|
||||
.ok_or_else(|| babe_err(Error::HeaderUnsealed(header.hash())))?;
|
||||
|
||||
let sig = seal.as_babe_seal().ok_or_else(|| {
|
||||
babe_err(Error::HeaderBadSeal(header.hash()))
|
||||
})?;
|
||||
let sig = seal
|
||||
.as_babe_seal()
|
||||
.ok_or_else(|| babe_err(Error::HeaderBadSeal(header.hash())))?;
|
||||
|
||||
// the pre-hash of the header doesn't include the seal
|
||||
// and that's what we sign
|
||||
@@ -84,7 +86,7 @@ pub(super) fn check_header<B: BlockT + Sized>(
|
||||
|
||||
if pre_digest.slot() > slot_now {
|
||||
header.digest_mut().push(seal);
|
||||
return Ok(CheckedHeader::Deferred(header, pre_digest.slot()));
|
||||
return Ok(CheckedHeader::Deferred(header, pre_digest.slot()))
|
||||
}
|
||||
|
||||
let author = match authorities.get(pre_digest.authority_index() as usize) {
|
||||
@@ -100,45 +102,31 @@ pub(super) fn check_header<B: BlockT + Sized>(
|
||||
primary.slot,
|
||||
);
|
||||
|
||||
check_primary_header::<B>(
|
||||
pre_hash,
|
||||
primary,
|
||||
sig,
|
||||
&epoch,
|
||||
epoch.config.c,
|
||||
)?;
|
||||
check_primary_header::<B>(pre_hash, primary, sig, &epoch, epoch.config.c)?;
|
||||
},
|
||||
PreDigest::SecondaryPlain(secondary) if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() => {
|
||||
PreDigest::SecondaryPlain(secondary)
|
||||
if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() =>
|
||||
{
|
||||
debug!(target: "babe",
|
||||
"Verifying secondary plain block #{} at slot: {}",
|
||||
header.number(),
|
||||
secondary.slot,
|
||||
);
|
||||
|
||||
check_secondary_plain_header::<B>(
|
||||
pre_hash,
|
||||
secondary,
|
||||
sig,
|
||||
&epoch,
|
||||
)?;
|
||||
},
|
||||
PreDigest::SecondaryVRF(secondary) if epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() => {
|
||||
check_secondary_plain_header::<B>(pre_hash, secondary, sig, &epoch)?;
|
||||
}
|
||||
PreDigest::SecondaryVRF(secondary)
|
||||
if epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() =>
|
||||
{
|
||||
debug!(target: "babe",
|
||||
"Verifying secondary VRF block #{} at slot: {}",
|
||||
header.number(),
|
||||
secondary.slot,
|
||||
);
|
||||
|
||||
check_secondary_vrf_header::<B>(
|
||||
pre_hash,
|
||||
secondary,
|
||||
sig,
|
||||
&epoch,
|
||||
)?;
|
||||
},
|
||||
_ => {
|
||||
return Err(babe_err(Error::SecondarySlotAssignmentsDisabled));
|
||||
check_secondary_vrf_header::<B>(pre_hash, secondary, sig, &epoch)?;
|
||||
}
|
||||
_ => return Err(babe_err(Error::SecondarySlotAssignmentsDisabled)),
|
||||
}
|
||||
|
||||
let info = VerifiedHeaderInfo {
|
||||
@@ -170,27 +158,20 @@ fn check_primary_header<B: BlockT + Sized>(
|
||||
|
||||
if AuthorityPair::verify(&signature, pre_hash, &author) {
|
||||
let (inout, _) = {
|
||||
let transcript = make_transcript(
|
||||
&epoch.randomness,
|
||||
pre_digest.slot,
|
||||
epoch.epoch_index,
|
||||
);
|
||||
let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch.epoch_index);
|
||||
|
||||
schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| {
|
||||
p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof)
|
||||
}).map_err(|s| {
|
||||
babe_err(Error::VRFVerificationFailed(s))
|
||||
})?
|
||||
schnorrkel::PublicKey::from_bytes(author.as_slice())
|
||||
.and_then(|p| {
|
||||
p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof)
|
||||
})
|
||||
.map_err(|s| babe_err(Error::VRFVerificationFailed(s)))?
|
||||
};
|
||||
|
||||
let threshold = calculate_primary_threshold(
|
||||
c,
|
||||
&epoch.authorities,
|
||||
pre_digest.authority_index as usize,
|
||||
);
|
||||
let threshold =
|
||||
calculate_primary_threshold(c, &epoch.authorities, pre_digest.authority_index as usize);
|
||||
|
||||
if !check_primary_threshold(&inout, threshold) {
|
||||
return Err(babe_err(Error::VRFVerificationOfBlockFailed(author.clone(), threshold)));
|
||||
return Err(babe_err(Error::VRFVerificationOfBlockFailed(author.clone(), threshold)))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -211,16 +192,14 @@ fn check_secondary_plain_header<B: BlockT>(
|
||||
) -> Result<(), Error<B>> {
|
||||
// check the signature is valid under the expected authority and
|
||||
// chain state.
|
||||
let expected_author = secondary_slot_author(
|
||||
pre_digest.slot,
|
||||
&epoch.authorities,
|
||||
epoch.randomness,
|
||||
).ok_or_else(|| Error::NoSecondaryAuthorExpected)?;
|
||||
let expected_author =
|
||||
secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness)
|
||||
.ok_or_else(|| Error::NoSecondaryAuthorExpected)?;
|
||||
|
||||
let author = &epoch.authorities[pre_digest.authority_index as usize].0;
|
||||
|
||||
if expected_author != author {
|
||||
return Err(Error::InvalidAuthor(expected_author.clone(), author.clone()));
|
||||
return Err(Error::InvalidAuthor(expected_author.clone(), author.clone()))
|
||||
}
|
||||
|
||||
if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) {
|
||||
@@ -239,30 +218,22 @@ fn check_secondary_vrf_header<B: BlockT>(
|
||||
) -> Result<(), Error<B>> {
|
||||
// check the signature is valid under the expected authority and
|
||||
// chain state.
|
||||
let expected_author = secondary_slot_author(
|
||||
pre_digest.slot,
|
||||
&epoch.authorities,
|
||||
epoch.randomness,
|
||||
).ok_or_else(|| Error::NoSecondaryAuthorExpected)?;
|
||||
let expected_author =
|
||||
secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness)
|
||||
.ok_or_else(|| Error::NoSecondaryAuthorExpected)?;
|
||||
|
||||
let author = &epoch.authorities[pre_digest.authority_index as usize].0;
|
||||
|
||||
if expected_author != author {
|
||||
return Err(Error::InvalidAuthor(expected_author.clone(), author.clone()));
|
||||
return Err(Error::InvalidAuthor(expected_author.clone(), author.clone()))
|
||||
}
|
||||
|
||||
if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) {
|
||||
let transcript = make_transcript(
|
||||
&epoch.randomness,
|
||||
pre_digest.slot,
|
||||
epoch.epoch_index,
|
||||
);
|
||||
let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch.epoch_index);
|
||||
|
||||
schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| {
|
||||
p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof)
|
||||
}).map_err(|s| {
|
||||
babe_err(Error::VRFVerificationFailed(s))
|
||||
})?;
|
||||
schnorrkel::PublicKey::from_bytes(author.as_slice())
|
||||
.and_then(|p| p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof))
|
||||
.map_err(|s| babe_err(Error::VRFVerificationFailed(s)))?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
|
||||
@@ -18,30 +18,26 @@
|
||||
|
||||
//! Longest chain implementation
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::marker::PhantomData;
|
||||
use sc_client_api::backend;
|
||||
use sp_consensus::{SelectChain, Error as ConsensusError};
|
||||
use sp_blockchain::{Backend, HeaderBackend};
|
||||
use sp_consensus::{Error as ConsensusError, SelectChain};
|
||||
use sp_runtime::{
|
||||
traits::{NumberFor, Block as BlockT},
|
||||
generic::BlockId,
|
||||
traits::{Block as BlockT, NumberFor},
|
||||
};
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
/// Implement Longest Chain Select implementation
|
||||
/// where 'longest' is defined as the highest number of blocks
|
||||
pub struct LongestChain<B, Block> {
|
||||
backend: Arc<B>,
|
||||
_phantom: PhantomData<Block>
|
||||
_phantom: PhantomData<Block>,
|
||||
}
|
||||
|
||||
impl<B, Block> Clone for LongestChain<B, Block> {
|
||||
fn clone(&self) -> Self {
|
||||
let backend = self.backend.clone();
|
||||
LongestChain {
|
||||
backend,
|
||||
_phantom: Default::default()
|
||||
}
|
||||
LongestChain { backend, _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,21 +48,22 @@ where
|
||||
{
|
||||
/// Instantiate a new LongestChain for Backend B
|
||||
pub fn new(backend: Arc<B>) -> Self {
|
||||
LongestChain {
|
||||
backend,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
LongestChain { backend, _phantom: Default::default() }
|
||||
}
|
||||
|
||||
fn best_block_header(&self) -> sp_blockchain::Result<<Block as BlockT>::Header> {
|
||||
let info = self.backend.blockchain().info();
|
||||
let import_lock = self.backend.get_import_lock();
|
||||
let best_hash = self.backend
|
||||
let best_hash = self
|
||||
.backend
|
||||
.blockchain()
|
||||
.best_containing(info.best_hash, None, import_lock)?
|
||||
.unwrap_or(info.best_hash);
|
||||
|
||||
Ok(self.backend.blockchain().header(BlockId::Hash(best_hash))?
|
||||
Ok(self
|
||||
.backend
|
||||
.blockchain()
|
||||
.header(BlockId::Hash(best_hash))?
|
||||
.expect("given block hash was fetched from block in db; qed"))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
//! Provides a generic wrapper around shared data. See [`SharedData`] for more information.
|
||||
|
||||
use parking_lot::{Condvar, MappedMutexGuard, Mutex, MutexGuard};
|
||||
use std::sync::Arc;
|
||||
use parking_lot::{Mutex, MappedMutexGuard, Condvar, MutexGuard};
|
||||
|
||||
/// Created by [`SharedDataLocked::release_mutex`].
|
||||
///
|
||||
@@ -75,8 +75,7 @@ impl<'a, T> SharedDataLocked<'a, T> {
|
||||
/// Release the mutex, but keep the shared data locked.
|
||||
pub fn release_mutex(mut self) -> SharedDataLockedUpgradable<T> {
|
||||
SharedDataLockedUpgradable {
|
||||
shared_data: self.shared_data.take()
|
||||
.expect("`shared_data` is only taken on drop; qed"),
|
||||
shared_data: self.shared_data.take().expect("`shared_data` is only taken on drop; qed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +131,7 @@ struct SharedDataInner<T> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
///# use sc_consensus::shared_data::SharedData;
|
||||
/// # use sc_consensus::shared_data::SharedData;
|
||||
///
|
||||
/// let shared_data = SharedData::new(String::from("hello world"));
|
||||
///
|
||||
@@ -174,10 +173,7 @@ pub struct SharedData<T> {
|
||||
|
||||
impl<T> Clone for SharedData<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
cond_var: self.cond_var.clone(),
|
||||
}
|
||||
Self { inner: self.inner.clone(), cond_var: self.cond_var.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,10 +224,7 @@ impl<T> SharedData<T> {
|
||||
debug_assert!(!guard.locked);
|
||||
guard.locked = true;
|
||||
|
||||
SharedDataLocked {
|
||||
inner: guard,
|
||||
shared_data: Some(self.clone()),
|
||||
}
|
||||
SharedDataLocked { inner: guard, shared_data: Some(self.clone()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,16 @@
|
||||
|
||||
pub mod migration;
|
||||
|
||||
use std::{ops::Add, collections::BTreeMap, borrow::{Borrow, BorrowMut}};
|
||||
use codec::{Encode, Decode};
|
||||
use codec::{Decode, Encode};
|
||||
use fork_tree::ForkTree;
|
||||
use sc_client_api::utils::is_descendent_of;
|
||||
use sp_blockchain::{HeaderMetadata, HeaderBackend, Error as ClientError};
|
||||
use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata};
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor, One, Zero};
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
collections::BTreeMap,
|
||||
ops::Add,
|
||||
};
|
||||
|
||||
/// A builder for `is_descendent_of` functions.
|
||||
pub trait IsDescendentOfBuilder<Hash> {
|
||||
@@ -41,8 +45,7 @@ pub trait IsDescendentOfBuilder<Hash> {
|
||||
/// details aren't yet stored, but its parent is.
|
||||
///
|
||||
/// The format of `current` when `Some` is `(current, current_parent)`.
|
||||
fn build_is_descendent_of(&self, current: Option<(Hash, Hash)>)
|
||||
-> Self::IsDescendentOf;
|
||||
fn build_is_descendent_of(&self, current: Option<(Hash, Hash)>) -> Self::IsDescendentOf;
|
||||
}
|
||||
|
||||
/// Produce a descendent query object given the client.
|
||||
@@ -55,16 +58,18 @@ pub fn descendent_query<H, Block>(client: &H) -> HeaderBackendDescendentBuilder<
|
||||
pub struct HeaderBackendDescendentBuilder<H, Block>(H, std::marker::PhantomData<Block>);
|
||||
|
||||
impl<'a, H, Block> IsDescendentOfBuilder<Block::Hash>
|
||||
for HeaderBackendDescendentBuilder<&'a H, Block> where
|
||||
H: HeaderBackend<Block> + HeaderMetadata<Block, Error=ClientError>,
|
||||
for HeaderBackendDescendentBuilder<&'a H, Block>
|
||||
where
|
||||
H: HeaderBackend<Block> + HeaderMetadata<Block, Error = ClientError>,
|
||||
Block: BlockT,
|
||||
{
|
||||
type Error = ClientError;
|
||||
type IsDescendentOf = Box<dyn Fn(&Block::Hash, &Block::Hash) -> Result<bool, ClientError> + 'a>;
|
||||
|
||||
fn build_is_descendent_of(&self, current: Option<(Block::Hash, Block::Hash)>)
|
||||
-> Self::IsDescendentOf
|
||||
{
|
||||
fn build_is_descendent_of(
|
||||
&self,
|
||||
current: Option<(Block::Hash, Block::Hash)>,
|
||||
) -> Self::IsDescendentOf {
|
||||
Box::new(is_descendent_of(self.0, current))
|
||||
}
|
||||
}
|
||||
@@ -90,10 +95,7 @@ pub trait Epoch {
|
||||
|
||||
impl<'a, E: Epoch> From<&'a E> for EpochHeader<E> {
|
||||
fn from(epoch: &'a E) -> EpochHeader<E> {
|
||||
Self {
|
||||
start_slot: epoch.start_slot(),
|
||||
end_slot: epoch.end_slot(),
|
||||
}
|
||||
Self { start_slot: epoch.start_slot(), end_slot: epoch.end_slot() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +111,7 @@ pub struct EpochHeader<E: Epoch> {
|
||||
|
||||
impl<E: Epoch> Clone for EpochHeader<E> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
start_slot: self.start_slot,
|
||||
end_slot: self.end_slot,
|
||||
}
|
||||
Self { start_slot: self.start_slot, end_slot: self.end_slot }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +148,8 @@ pub enum ViableEpoch<E, ERef = E> {
|
||||
Signaled(ERef),
|
||||
}
|
||||
|
||||
impl<E, ERef> AsRef<E> for ViableEpoch<E, ERef> where
|
||||
impl<E, ERef> AsRef<E> for ViableEpoch<E, ERef>
|
||||
where
|
||||
ERef: Borrow<E>,
|
||||
{
|
||||
fn as_ref(&self) -> &E {
|
||||
@@ -160,7 +160,8 @@ impl<E, ERef> AsRef<E> for ViableEpoch<E, ERef> where
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, ERef> AsMut<E> for ViableEpoch<E, ERef> where
|
||||
impl<E, ERef> AsMut<E> for ViableEpoch<E, ERef>
|
||||
where
|
||||
ERef: BorrowMut<E>,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut E {
|
||||
@@ -171,7 +172,8 @@ impl<E, ERef> AsMut<E> for ViableEpoch<E, ERef> where
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, ERef> ViableEpoch<E, ERef> where
|
||||
impl<E, ERef> ViableEpoch<E, ERef>
|
||||
where
|
||||
E: Epoch + Clone,
|
||||
ERef: Borrow<E>,
|
||||
{
|
||||
@@ -187,18 +189,14 @@ impl<E, ERef> ViableEpoch<E, ERef> where
|
||||
/// Get cloned value for the viable epoch.
|
||||
pub fn into_cloned(self) -> ViableEpoch<E, E> {
|
||||
match self {
|
||||
ViableEpoch::UnimportedGenesis(e) =>
|
||||
ViableEpoch::UnimportedGenesis(e),
|
||||
ViableEpoch::UnimportedGenesis(e) => ViableEpoch::UnimportedGenesis(e),
|
||||
ViableEpoch::Signaled(e) => ViableEpoch::Signaled(e.borrow().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Increment the epoch, yielding an `IncrementedEpoch` to be imported
|
||||
/// into the fork-tree.
|
||||
pub fn increment(
|
||||
&self,
|
||||
next_descriptor: E::NextEpochDescriptor
|
||||
) -> IncrementedEpoch<E> {
|
||||
pub fn increment(&self, next_descriptor: E::NextEpochDescriptor) -> IncrementedEpoch<E> {
|
||||
let next = self.as_ref().increment(next_descriptor);
|
||||
let to_persist = match *self {
|
||||
ViableEpoch::UnimportedGenesis(ref epoch_0) =>
|
||||
@@ -216,7 +214,7 @@ pub enum ViableEpochDescriptor<Hash, Number, E: Epoch> {
|
||||
/// The epoch is an unimported genesis, with given start slot number.
|
||||
UnimportedGenesis(E::Slot),
|
||||
/// The epoch is signaled and has been imported, with given identifier and header.
|
||||
Signaled(EpochIdentifier<Hash, Number>, EpochHeader<E>)
|
||||
Signaled(EpochIdentifier<Hash, Number>, EpochHeader<E>),
|
||||
}
|
||||
|
||||
impl<Hash, Number, E: Epoch> ViableEpochDescriptor<Hash, Number, E> {
|
||||
@@ -243,8 +241,7 @@ impl<'a, E: Epoch> From<&'a PersistedEpoch<E>> for PersistedEpochHeader<E> {
|
||||
match epoch {
|
||||
PersistedEpoch::Genesis(ref epoch_0, ref epoch_1) =>
|
||||
PersistedEpochHeader::Genesis(epoch_0.into(), epoch_1.into()),
|
||||
PersistedEpoch::Regular(ref epoch_n) =>
|
||||
PersistedEpochHeader::Regular(epoch_n.into()),
|
||||
PersistedEpoch::Regular(ref epoch_n) => PersistedEpochHeader::Regular(epoch_n.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,7 +309,8 @@ fn fake_head_hash<H: AsRef<[u8]> + AsMut<[u8]> + Clone>(parent_hash: &H) -> H {
|
||||
h
|
||||
}
|
||||
|
||||
impl<Hash, Number, E: Epoch> Default for EpochChanges<Hash, Number, E> where
|
||||
impl<Hash, Number, E: Epoch> Default for EpochChanges<Hash, Number, E>
|
||||
where
|
||||
Hash: PartialEq + Ord,
|
||||
Number: Ord,
|
||||
{
|
||||
@@ -321,9 +319,10 @@ impl<Hash, Number, E: Epoch> Default for EpochChanges<Hash, Number, E> where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E>
|
||||
where
|
||||
Hash: PartialEq + Ord + AsRef<[u8]> + AsMut<[u8]> + Copy,
|
||||
Number: Ord + One + Zero + Add<Output=Number> + Copy,
|
||||
Number: Ord + One + Zero + Add<Output = Number> + Copy,
|
||||
{
|
||||
/// Create a new epoch change.
|
||||
pub fn new() -> Self {
|
||||
@@ -337,51 +336,38 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
}
|
||||
|
||||
/// Map the epoch changes from one storing data to a different one.
|
||||
pub fn map<B, F>(self, mut f: F) -> EpochChanges<Hash, Number, B> where
|
||||
B: Epoch<Slot=E::Slot>,
|
||||
pub fn map<B, F>(self, mut f: F) -> EpochChanges<Hash, Number, B>
|
||||
where
|
||||
B: Epoch<Slot = E::Slot>,
|
||||
F: FnMut(&Hash, &Number, E) -> B,
|
||||
{
|
||||
EpochChanges {
|
||||
inner: self.inner.map(&mut |_, _, header| {
|
||||
match header {
|
||||
PersistedEpochHeader::Genesis(epoch_0, epoch_1) => {
|
||||
PersistedEpochHeader::Genesis(
|
||||
EpochHeader {
|
||||
start_slot: epoch_0.start_slot,
|
||||
end_slot: epoch_0.end_slot,
|
||||
},
|
||||
EpochHeader {
|
||||
start_slot: epoch_1.start_slot,
|
||||
end_slot: epoch_1.end_slot,
|
||||
},
|
||||
)
|
||||
},
|
||||
PersistedEpochHeader::Regular(epoch_n) => {
|
||||
PersistedEpochHeader::Regular(
|
||||
EpochHeader {
|
||||
start_slot: epoch_n.start_slot,
|
||||
end_slot: epoch_n.end_slot,
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
inner: self.inner.map(&mut |_, _, header| match header {
|
||||
PersistedEpochHeader::Genesis(epoch_0, epoch_1) => PersistedEpochHeader::Genesis(
|
||||
EpochHeader { start_slot: epoch_0.start_slot, end_slot: epoch_0.end_slot },
|
||||
EpochHeader { start_slot: epoch_1.start_slot, end_slot: epoch_1.end_slot },
|
||||
),
|
||||
PersistedEpochHeader::Regular(epoch_n) =>
|
||||
PersistedEpochHeader::Regular(EpochHeader {
|
||||
start_slot: epoch_n.start_slot,
|
||||
end_slot: epoch_n.end_slot,
|
||||
}),
|
||||
}),
|
||||
epochs: self.epochs.into_iter().map(|((hash, number), epoch)| {
|
||||
let bepoch = match epoch {
|
||||
PersistedEpoch::Genesis(epoch_0, epoch_1) => {
|
||||
PersistedEpoch::Genesis(
|
||||
epochs: self
|
||||
.epochs
|
||||
.into_iter()
|
||||
.map(|((hash, number), epoch)| {
|
||||
let bepoch = match epoch {
|
||||
PersistedEpoch::Genesis(epoch_0, epoch_1) => PersistedEpoch::Genesis(
|
||||
f(&hash, &number, epoch_0),
|
||||
f(&hash, &number, epoch_1),
|
||||
)
|
||||
},
|
||||
PersistedEpoch::Regular(epoch_n) => {
|
||||
PersistedEpoch::Regular(
|
||||
f(&hash, &number, epoch_n)
|
||||
)
|
||||
},
|
||||
};
|
||||
((hash, number), bepoch)
|
||||
}).collect(),
|
||||
),
|
||||
PersistedEpoch::Regular(epoch_n) =>
|
||||
PersistedEpoch::Regular(f(&hash, &number, epoch_n)),
|
||||
};
|
||||
((hash, number), bepoch)
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,25 +381,17 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
number: Number,
|
||||
slot: E::Slot,
|
||||
) -> Result<(), fork_tree::Error<D::Error>> {
|
||||
let is_descendent_of = descendent_of_builder
|
||||
.build_is_descendent_of(None);
|
||||
let is_descendent_of = descendent_of_builder.build_is_descendent_of(None);
|
||||
|
||||
let predicate = |epoch: &PersistedEpochHeader<E>| match *epoch {
|
||||
PersistedEpochHeader::Genesis(_, ref epoch_1) =>
|
||||
slot >= epoch_1.end_slot,
|
||||
PersistedEpochHeader::Regular(ref epoch_n) =>
|
||||
slot >= epoch_n.end_slot,
|
||||
PersistedEpochHeader::Genesis(_, ref epoch_1) => slot >= epoch_1.end_slot,
|
||||
PersistedEpochHeader::Regular(ref epoch_n) => slot >= epoch_n.end_slot,
|
||||
};
|
||||
|
||||
// prune any epochs which could not be _live_ as of the children of the
|
||||
// finalized block, i.e. re-root the fork tree to the oldest ancestor of
|
||||
// (hash, number) where epoch.end_slot() >= finalized_slot
|
||||
let removed = self.inner.prune(
|
||||
hash,
|
||||
&number,
|
||||
&is_descendent_of,
|
||||
&predicate,
|
||||
)?;
|
||||
let removed = self.inner.prune(hash, &number, &is_descendent_of, &predicate)?;
|
||||
|
||||
for (hash, number, _) in removed {
|
||||
self.epochs.remove(&(hash, number));
|
||||
@@ -424,18 +402,18 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
|
||||
/// Get a reference to an epoch with given identifier.
|
||||
pub fn epoch(&self, id: &EpochIdentifier<Hash, Number>) -> Option<&E> {
|
||||
self.epochs.get(&(id.hash, id.number))
|
||||
.and_then(|v| {
|
||||
match v {
|
||||
PersistedEpoch::Genesis(ref epoch_0, _)
|
||||
if id.position == EpochIdentifierPosition::Genesis0 => Some(epoch_0),
|
||||
PersistedEpoch::Genesis(_, ref epoch_1)
|
||||
if id.position == EpochIdentifierPosition::Genesis1 => Some(epoch_1),
|
||||
PersistedEpoch::Regular(ref epoch_n)
|
||||
if id.position == EpochIdentifierPosition::Regular => Some(epoch_n),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
self.epochs.get(&(id.hash, id.number)).and_then(|v| match v {
|
||||
PersistedEpoch::Genesis(ref epoch_0, _)
|
||||
if id.position == EpochIdentifierPosition::Genesis0 =>
|
||||
Some(epoch_0),
|
||||
PersistedEpoch::Genesis(_, ref epoch_1)
|
||||
if id.position == EpochIdentifierPosition::Genesis1 =>
|
||||
Some(epoch_1),
|
||||
PersistedEpoch::Regular(ref epoch_n)
|
||||
if id.position == EpochIdentifierPosition::Regular =>
|
||||
Some(epoch_n),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a reference to a viable epoch with given descriptor.
|
||||
@@ -443,33 +421,32 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
&self,
|
||||
descriptor: &ViableEpochDescriptor<Hash, Number, E>,
|
||||
make_genesis: G,
|
||||
) -> Option<ViableEpoch<E, &E>> where
|
||||
G: FnOnce(E::Slot) -> E
|
||||
) -> Option<ViableEpoch<E, &E>>
|
||||
where
|
||||
G: FnOnce(E::Slot) -> E,
|
||||
{
|
||||
match descriptor {
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) => {
|
||||
Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot)))
|
||||
},
|
||||
ViableEpochDescriptor::Signaled(identifier, _) => {
|
||||
self.epoch(&identifier).map(ViableEpoch::Signaled)
|
||||
},
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) =>
|
||||
Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))),
|
||||
ViableEpochDescriptor::Signaled(identifier, _) =>
|
||||
self.epoch(&identifier).map(ViableEpoch::Signaled),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to an epoch with given identifier.
|
||||
pub fn epoch_mut(&mut self, id: &EpochIdentifier<Hash, Number>) -> Option<&mut E> {
|
||||
self.epochs.get_mut(&(id.hash, id.number))
|
||||
.and_then(|v| {
|
||||
match v {
|
||||
PersistedEpoch::Genesis(ref mut epoch_0, _)
|
||||
if id.position == EpochIdentifierPosition::Genesis0 => Some(epoch_0),
|
||||
PersistedEpoch::Genesis(_, ref mut epoch_1)
|
||||
if id.position == EpochIdentifierPosition::Genesis1 => Some(epoch_1),
|
||||
PersistedEpoch::Regular(ref mut epoch_n)
|
||||
if id.position == EpochIdentifierPosition::Regular => Some(epoch_n),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
self.epochs.get_mut(&(id.hash, id.number)).and_then(|v| match v {
|
||||
PersistedEpoch::Genesis(ref mut epoch_0, _)
|
||||
if id.position == EpochIdentifierPosition::Genesis0 =>
|
||||
Some(epoch_0),
|
||||
PersistedEpoch::Genesis(_, ref mut epoch_1)
|
||||
if id.position == EpochIdentifierPosition::Genesis1 =>
|
||||
Some(epoch_1),
|
||||
PersistedEpoch::Regular(ref mut epoch_n)
|
||||
if id.position == EpochIdentifierPosition::Regular =>
|
||||
Some(epoch_n),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a viable epoch with given descriptor.
|
||||
@@ -477,16 +454,15 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
&mut self,
|
||||
descriptor: &ViableEpochDescriptor<Hash, Number, E>,
|
||||
make_genesis: G,
|
||||
) -> Option<ViableEpoch<E, &mut E>> where
|
||||
G: FnOnce(E::Slot) -> E
|
||||
) -> Option<ViableEpoch<E, &mut E>>
|
||||
where
|
||||
G: FnOnce(E::Slot) -> E,
|
||||
{
|
||||
match descriptor {
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) => {
|
||||
Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot)))
|
||||
},
|
||||
ViableEpochDescriptor::Signaled(identifier, _) => {
|
||||
self.epoch_mut(&identifier).map(ViableEpoch::Signaled)
|
||||
},
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) =>
|
||||
Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))),
|
||||
ViableEpochDescriptor::Signaled(identifier, _) =>
|
||||
self.epoch_mut(&identifier).map(ViableEpoch::Signaled),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,18 +473,15 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
pub fn epoch_data<G>(
|
||||
&self,
|
||||
descriptor: &ViableEpochDescriptor<Hash, Number, E>,
|
||||
make_genesis: G
|
||||
) -> Option<E> where
|
||||
make_genesis: G,
|
||||
) -> Option<E>
|
||||
where
|
||||
G: FnOnce(E::Slot) -> E,
|
||||
E: Clone,
|
||||
{
|
||||
match descriptor {
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) => {
|
||||
Some(make_genesis(*slot))
|
||||
},
|
||||
ViableEpochDescriptor::Signaled(identifier, _) => {
|
||||
self.epoch(&identifier).cloned()
|
||||
},
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) => Some(make_genesis(*slot)),
|
||||
ViableEpochDescriptor::Signaled(identifier, _) => self.epoch(&identifier).cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +497,8 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
parent_number: Number,
|
||||
slot: E::Slot,
|
||||
make_genesis: G,
|
||||
) -> Result<Option<E>, fork_tree::Error<D::Error>> where
|
||||
) -> Result<Option<E>, fork_tree::Error<D::Error>>
|
||||
where
|
||||
G: FnOnce(E::Slot) -> E,
|
||||
E: Clone,
|
||||
{
|
||||
@@ -532,7 +506,7 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
descendent_of_builder,
|
||||
parent_hash,
|
||||
parent_number,
|
||||
slot
|
||||
slot,
|
||||
)?;
|
||||
|
||||
Ok(descriptor.and_then(|des| self.epoch_data(&des, make_genesis)))
|
||||
@@ -555,8 +529,8 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
// "descends" from our parent-hash.
|
||||
let fake_head_hash = fake_head_hash(parent_hash);
|
||||
|
||||
let is_descendent_of = descendent_of_builder
|
||||
.build_is_descendent_of(Some((fake_head_hash, *parent_hash)));
|
||||
let is_descendent_of =
|
||||
descendent_of_builder.build_is_descendent_of(Some((fake_head_hash, *parent_hash)));
|
||||
|
||||
if parent_number == Zero::zero() {
|
||||
// need to insert the genesis epoch.
|
||||
@@ -569,37 +543,41 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
// at epoch_1 -- all we're doing here is figuring out which node
|
||||
// we need.
|
||||
let predicate = |epoch: &PersistedEpochHeader<E>| match *epoch {
|
||||
PersistedEpochHeader::Genesis(ref epoch_0, _) =>
|
||||
epoch_0.start_slot <= slot,
|
||||
PersistedEpochHeader::Regular(ref epoch_n) =>
|
||||
epoch_n.start_slot <= slot,
|
||||
PersistedEpochHeader::Genesis(ref epoch_0, _) => epoch_0.start_slot <= slot,
|
||||
PersistedEpochHeader::Regular(ref epoch_n) => epoch_n.start_slot <= slot,
|
||||
};
|
||||
|
||||
self.inner.find_node_where(
|
||||
&fake_head_hash,
|
||||
&(parent_number + One::one()),
|
||||
&is_descendent_of,
|
||||
&predicate,
|
||||
)
|
||||
self.inner
|
||||
.find_node_where(
|
||||
&fake_head_hash,
|
||||
&(parent_number + One::one()),
|
||||
&is_descendent_of,
|
||||
&predicate,
|
||||
)
|
||||
.map(|n| {
|
||||
n.map(|node| (match node.data {
|
||||
// Ok, we found our node.
|
||||
// and here we figure out which of the internal epochs
|
||||
// of a genesis node to use based on their start slot.
|
||||
PersistedEpochHeader::Genesis(ref epoch_0, ref epoch_1) =>
|
||||
if epoch_1.start_slot <= slot {
|
||||
(EpochIdentifierPosition::Genesis1, epoch_1.clone())
|
||||
} else {
|
||||
(EpochIdentifierPosition::Genesis0, epoch_0.clone())
|
||||
n.map(|node| {
|
||||
(
|
||||
match node.data {
|
||||
// Ok, we found our node.
|
||||
// and here we figure out which of the internal epochs
|
||||
// of a genesis node to use based on their start slot.
|
||||
PersistedEpochHeader::Genesis(ref epoch_0, ref epoch_1) =>
|
||||
if epoch_1.start_slot <= slot {
|
||||
(EpochIdentifierPosition::Genesis1, epoch_1.clone())
|
||||
} else {
|
||||
(EpochIdentifierPosition::Genesis0, epoch_0.clone())
|
||||
},
|
||||
PersistedEpochHeader::Regular(ref epoch_n) =>
|
||||
(EpochIdentifierPosition::Regular, epoch_n.clone()),
|
||||
},
|
||||
PersistedEpochHeader::Regular(ref epoch_n) =>
|
||||
(EpochIdentifierPosition::Regular, epoch_n.clone()),
|
||||
}, node)).map(|((position, header), node)| {
|
||||
ViableEpochDescriptor::Signaled(EpochIdentifier {
|
||||
position,
|
||||
hash: node.hash,
|
||||
number: node.number
|
||||
}, header)
|
||||
node,
|
||||
)
|
||||
})
|
||||
.map(|((position, header), node)| {
|
||||
ViableEpochDescriptor::Signaled(
|
||||
EpochIdentifier { position, hash: node.hash, number: node.number },
|
||||
header,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -617,16 +595,11 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
|
||||
parent_hash: Hash,
|
||||
epoch: IncrementedEpoch<E>,
|
||||
) -> Result<(), fork_tree::Error<D::Error>> {
|
||||
let is_descendent_of = descendent_of_builder
|
||||
.build_is_descendent_of(Some((hash, parent_hash)));
|
||||
let is_descendent_of =
|
||||
descendent_of_builder.build_is_descendent_of(Some((hash, parent_hash)));
|
||||
let header = PersistedEpochHeader::<E>::from(&epoch.0);
|
||||
|
||||
let res = self.inner.import(
|
||||
hash,
|
||||
number,
|
||||
header,
|
||||
&is_descendent_of,
|
||||
);
|
||||
let res = self.inner.import(hash, number, header, &is_descendent_of);
|
||||
|
||||
match res {
|
||||
Ok(_) | Err(fork_tree::Error::Duplicate) => {
|
||||
@@ -653,8 +626,7 @@ pub type SharedEpochChanges<Block, Epoch> =
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::Epoch as EpochT;
|
||||
use super::{Epoch as EpochT, *};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct TestError;
|
||||
@@ -667,15 +639,14 @@ mod tests {
|
||||
|
||||
impl std::error::Error for TestError {}
|
||||
|
||||
impl<'a, F: 'a , H: 'a + PartialEq + std::fmt::Debug> IsDescendentOfBuilder<H> for &'a F
|
||||
where F: Fn(&H, &H) -> Result<bool, TestError>
|
||||
impl<'a, F: 'a, H: 'a + PartialEq + std::fmt::Debug> IsDescendentOfBuilder<H> for &'a F
|
||||
where
|
||||
F: Fn(&H, &H) -> Result<bool, TestError>,
|
||||
{
|
||||
type Error = TestError;
|
||||
type IsDescendentOf = Box<dyn Fn(&H, &H) -> Result<bool, TestError> + 'a>;
|
||||
|
||||
fn build_is_descendent_of(&self, current: Option<(H, H)>)
|
||||
-> Self::IsDescendentOf
|
||||
{
|
||||
fn build_is_descendent_of(&self, current: Option<(H, H)>) -> Self::IsDescendentOf {
|
||||
let f = *self;
|
||||
Box::new(move |base, head| {
|
||||
let mut head = head;
|
||||
@@ -683,7 +654,7 @@ mod tests {
|
||||
if let Some((ref c_head, ref c_parent)) = current {
|
||||
if head == c_head {
|
||||
if base == c_parent {
|
||||
return Ok(true);
|
||||
return Ok(true)
|
||||
} else {
|
||||
head = c_parent;
|
||||
}
|
||||
@@ -709,10 +680,7 @@ mod tests {
|
||||
type Slot = Slot;
|
||||
|
||||
fn increment(&self, _: ()) -> Self {
|
||||
Epoch {
|
||||
start_slot: self.start_slot + self.duration,
|
||||
duration: self.duration,
|
||||
}
|
||||
Epoch { start_slot: self.start_slot + self.duration, duration: self.duration }
|
||||
}
|
||||
|
||||
fn end_slot(&self) -> Slot {
|
||||
@@ -726,7 +694,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn genesis_epoch_is_created_but_not_imported() {
|
||||
//
|
||||
// A - B
|
||||
// \
|
||||
// — C
|
||||
@@ -741,12 +708,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let epoch_changes = EpochChanges::<_, _, Epoch>::new();
|
||||
let genesis_epoch = epoch_changes.epoch_descriptor_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"0",
|
||||
0,
|
||||
10101,
|
||||
).unwrap().unwrap();
|
||||
let genesis_epoch = epoch_changes
|
||||
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 10101)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
match genesis_epoch {
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) => {
|
||||
@@ -755,12 +720,10 @@ mod tests {
|
||||
_ => panic!("should be unimported genesis"),
|
||||
};
|
||||
|
||||
let genesis_epoch_2 = epoch_changes.epoch_descriptor_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"0",
|
||||
0,
|
||||
10102,
|
||||
).unwrap().unwrap();
|
||||
let genesis_epoch_2 = epoch_changes
|
||||
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 10102)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
match genesis_epoch_2 {
|
||||
ViableEpochDescriptor::UnimportedGenesis(slot) => {
|
||||
@@ -772,7 +735,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn epoch_changes_between_blocks() {
|
||||
//
|
||||
// A - B
|
||||
// \
|
||||
// — C
|
||||
@@ -786,34 +748,23 @@ mod tests {
|
||||
}
|
||||
};
|
||||
|
||||
let make_genesis = |slot| Epoch {
|
||||
start_slot: slot,
|
||||
duration: 100,
|
||||
};
|
||||
let make_genesis = |slot| Epoch { start_slot: slot, duration: 100 };
|
||||
|
||||
let mut epoch_changes = EpochChanges::<_, _, Epoch>::new();
|
||||
let genesis_epoch = epoch_changes.epoch_descriptor_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"0",
|
||||
0,
|
||||
100,
|
||||
).unwrap().unwrap();
|
||||
let genesis_epoch = epoch_changes
|
||||
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(genesis_epoch, ViableEpochDescriptor::UnimportedGenesis(100));
|
||||
|
||||
let import_epoch_1 = epoch_changes
|
||||
.viable_epoch(&genesis_epoch, &make_genesis)
|
||||
.unwrap()
|
||||
.increment(());
|
||||
let import_epoch_1 =
|
||||
epoch_changes.viable_epoch(&genesis_epoch, &make_genesis).unwrap().increment(());
|
||||
let epoch_1 = import_epoch_1.as_ref().clone();
|
||||
|
||||
epoch_changes.import(
|
||||
&is_descendent_of,
|
||||
*b"A",
|
||||
1,
|
||||
*b"0",
|
||||
import_epoch_1,
|
||||
).unwrap();
|
||||
epoch_changes
|
||||
.import(&is_descendent_of, *b"A", 1, *b"0", import_epoch_1)
|
||||
.unwrap();
|
||||
let genesis_epoch = epoch_changes.epoch_data(&genesis_epoch, &make_genesis).unwrap();
|
||||
|
||||
assert!(is_descendent_of(b"0", b"A").unwrap());
|
||||
@@ -823,13 +774,10 @@ mod tests {
|
||||
|
||||
{
|
||||
// x is still within the genesis epoch.
|
||||
let x = epoch_changes.epoch_data_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"A",
|
||||
1,
|
||||
end_slot - 1,
|
||||
&make_genesis,
|
||||
).unwrap().unwrap();
|
||||
let x = epoch_changes
|
||||
.epoch_data_for_child_of(&is_descendent_of, b"A", 1, end_slot - 1, &make_genesis)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(x, genesis_epoch);
|
||||
}
|
||||
@@ -837,13 +785,10 @@ mod tests {
|
||||
{
|
||||
// x is now at the next epoch, because the block is now at the
|
||||
// start slot of epoch 1.
|
||||
let x = epoch_changes.epoch_data_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"A",
|
||||
1,
|
||||
end_slot,
|
||||
&make_genesis,
|
||||
).unwrap().unwrap();
|
||||
let x = epoch_changes
|
||||
.epoch_data_for_child_of(&is_descendent_of, b"A", 1, end_slot, &make_genesis)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(x, epoch_1);
|
||||
}
|
||||
@@ -851,13 +796,16 @@ mod tests {
|
||||
{
|
||||
// x is now at the next epoch, because the block is now after
|
||||
// start slot of epoch 1.
|
||||
let x = epoch_changes.epoch_data_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"A",
|
||||
1,
|
||||
epoch_1.end_slot() - 1,
|
||||
&make_genesis,
|
||||
).unwrap().unwrap();
|
||||
let x = epoch_changes
|
||||
.epoch_data_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"A",
|
||||
1,
|
||||
epoch_1.end_slot() - 1,
|
||||
&make_genesis,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(x, epoch_1);
|
||||
}
|
||||
@@ -880,90 +828,65 @@ mod tests {
|
||||
|
||||
let duration = 100;
|
||||
|
||||
let make_genesis = |slot| Epoch {
|
||||
start_slot: slot,
|
||||
duration,
|
||||
};
|
||||
let make_genesis = |slot| Epoch { start_slot: slot, duration };
|
||||
|
||||
let mut epoch_changes = EpochChanges::new();
|
||||
let next_descriptor = ();
|
||||
|
||||
// insert genesis epoch for A
|
||||
{
|
||||
let genesis_epoch_a_descriptor = epoch_changes.epoch_descriptor_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"0",
|
||||
0,
|
||||
100,
|
||||
).unwrap().unwrap();
|
||||
let genesis_epoch_a_descriptor = epoch_changes
|
||||
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let incremented_epoch = epoch_changes
|
||||
.viable_epoch(&genesis_epoch_a_descriptor, &make_genesis)
|
||||
.unwrap()
|
||||
.increment(next_descriptor.clone());
|
||||
|
||||
epoch_changes.import(
|
||||
&is_descendent_of,
|
||||
*b"A",
|
||||
1,
|
||||
*b"0",
|
||||
incremented_epoch,
|
||||
).unwrap();
|
||||
epoch_changes
|
||||
.import(&is_descendent_of, *b"A", 1, *b"0", incremented_epoch)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// insert genesis epoch for X
|
||||
{
|
||||
let genesis_epoch_x_descriptor = epoch_changes.epoch_descriptor_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"0",
|
||||
0,
|
||||
1000,
|
||||
).unwrap().unwrap();
|
||||
let genesis_epoch_x_descriptor = epoch_changes
|
||||
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1000)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let incremented_epoch = epoch_changes
|
||||
.viable_epoch(&genesis_epoch_x_descriptor, &make_genesis)
|
||||
.unwrap()
|
||||
.increment(next_descriptor.clone());
|
||||
|
||||
epoch_changes.import(
|
||||
&is_descendent_of,
|
||||
*b"X",
|
||||
1,
|
||||
*b"0",
|
||||
incremented_epoch,
|
||||
).unwrap();
|
||||
epoch_changes
|
||||
.import(&is_descendent_of, *b"X", 1, *b"0", incremented_epoch)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// now check that the genesis epochs for our respective block 1s
|
||||
// respect the chain structure.
|
||||
{
|
||||
let epoch_for_a_child = epoch_changes.epoch_data_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"A",
|
||||
1,
|
||||
101,
|
||||
&make_genesis,
|
||||
).unwrap().unwrap();
|
||||
let epoch_for_a_child = epoch_changes
|
||||
.epoch_data_for_child_of(&is_descendent_of, b"A", 1, 101, &make_genesis)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(epoch_for_a_child, make_genesis(100));
|
||||
|
||||
let epoch_for_x_child = epoch_changes.epoch_data_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"X",
|
||||
1,
|
||||
1001,
|
||||
&make_genesis,
|
||||
).unwrap().unwrap();
|
||||
let epoch_for_x_child = epoch_changes
|
||||
.epoch_data_for_child_of(&is_descendent_of, b"X", 1, 1001, &make_genesis)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(epoch_for_x_child, make_genesis(1000));
|
||||
|
||||
let epoch_for_x_child_before_genesis = epoch_changes.epoch_data_for_child_of(
|
||||
&is_descendent_of,
|
||||
b"X",
|
||||
1,
|
||||
101,
|
||||
&make_genesis,
|
||||
).unwrap();
|
||||
let epoch_for_x_child_before_genesis = epoch_changes
|
||||
.epoch_data_for_child_of(&is_descendent_of, b"X", 1, 101, &make_genesis)
|
||||
.unwrap();
|
||||
|
||||
// even though there is a genesis epoch at that slot, it's not in
|
||||
// this chain.
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
//! Migration types for epoch changes.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use codec::{Encode, Decode};
|
||||
use crate::{Epoch, EpochChanges, PersistedEpoch, PersistedEpochHeader};
|
||||
use codec::{Decode, Encode};
|
||||
use fork_tree::ForkTree;
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
use crate::{Epoch, EpochChanges, PersistedEpoch, PersistedEpochHeader};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Legacy definition of epoch changes.
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
@@ -31,9 +31,11 @@ pub struct EpochChangesV0<Hash, Number, E: Epoch> {
|
||||
}
|
||||
|
||||
/// Type alias for legacy definition of epoch changes.
|
||||
pub type EpochChangesForV0<Block, Epoch> = EpochChangesV0<<Block as BlockT>::Hash, NumberFor<Block>, Epoch>;
|
||||
pub type EpochChangesForV0<Block, Epoch> =
|
||||
EpochChangesV0<<Block as BlockT>::Hash, NumberFor<Block>, Epoch>;
|
||||
|
||||
impl<Hash, Number, E: Epoch> EpochChangesV0<Hash, Number, E> where
|
||||
impl<Hash, Number, E: Epoch> EpochChangesV0<Hash, Number, E>
|
||||
where
|
||||
Hash: PartialEq + Ord + Copy,
|
||||
Number: Ord + Copy,
|
||||
{
|
||||
|
||||
@@ -19,26 +19,30 @@
|
||||
//! Extensions for manual seal to produce blocks valid for any runtime.
|
||||
use super::Error;
|
||||
|
||||
use sp_runtime::traits::{Block as BlockT, DigestFor};
|
||||
use sp_inherents::InherentData;
|
||||
use sp_consensus::BlockImportParams;
|
||||
use sp_inherents::InherentData;
|
||||
use sp_runtime::traits::{Block as BlockT, DigestFor};
|
||||
|
||||
pub mod babe;
|
||||
|
||||
/// Consensus data provider, manual seal uses this trait object for authoring blocks valid
|
||||
/// Consensus data provider, manual seal uses this trait object for authoring blocks valid
|
||||
/// for any runtime.
|
||||
pub trait ConsensusDataProvider<B: BlockT>: Send + Sync {
|
||||
/// Block import transaction type
|
||||
type Transaction;
|
||||
|
||||
/// Attempt to create a consensus digest.
|
||||
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<DigestFor<B>, Error>;
|
||||
fn create_digest(
|
||||
&self,
|
||||
parent: &B::Header,
|
||||
inherents: &InherentData,
|
||||
) -> Result<DigestFor<B>, Error>;
|
||||
|
||||
/// set up the neccessary import params.
|
||||
fn append_block_import(
|
||||
&self,
|
||||
parent: &B::Header,
|
||||
params: &mut BlockImportParams<B, Self::Transaction>,
|
||||
inherents: &InherentData
|
||||
inherents: &InherentData,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
@@ -21,30 +21,40 @@
|
||||
use super::ConsensusDataProvider;
|
||||
use crate::Error;
|
||||
use codec::Encode;
|
||||
use std::{borrow::Cow, sync::{Arc, atomic}, time::SystemTime};
|
||||
use sc_client_api::{AuxStore, UsageProvider};
|
||||
use sc_consensus_babe::{
|
||||
Config, Epoch, authorship, CompatibleDigestItem, BabeIntermediate, INTERMEDIATE_KEY,
|
||||
find_pre_digest,
|
||||
authorship, find_pre_digest, BabeIntermediate, CompatibleDigestItem, Config, Epoch,
|
||||
INTERMEDIATE_KEY,
|
||||
};
|
||||
use sc_consensus_epochs::{
|
||||
descendent_query, EpochHeader, SharedEpochChanges, ViableEpochDescriptor,
|
||||
};
|
||||
use sc_consensus_epochs::{SharedEpochChanges, descendent_query, ViableEpochDescriptor, EpochHeader};
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
sync::{atomic, Arc},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use sp_api::{ProvideRuntimeApi, TransactionFor};
|
||||
use sp_blockchain::{HeaderBackend, HeaderMetadata};
|
||||
use sp_consensus::{BlockImportParams, BlockOrigin, ForkChoiceStrategy};
|
||||
use sp_consensus_slots::Slot;
|
||||
use sp_consensus_babe::{
|
||||
BabeApi, inherents::BabeInherentData, ConsensusLog, BABE_ENGINE_ID, AuthorityId,
|
||||
digests::{PreDigest, SecondaryPlainPreDigest, NextEpochDescriptor}, BabeAuthorityWeight,
|
||||
use sp_consensus::{
|
||||
import_queue::{CacheKeyId, Verifier},
|
||||
BlockImportParams, BlockOrigin, ForkChoiceStrategy,
|
||||
};
|
||||
use sp_consensus_babe::{
|
||||
digests::{NextEpochDescriptor, PreDigest, SecondaryPlainPreDigest},
|
||||
inherents::BabeInherentData,
|
||||
AuthorityId, BabeApi, BabeAuthorityWeight, ConsensusLog, BABE_ENGINE_ID,
|
||||
};
|
||||
use sp_consensus_slots::Slot;
|
||||
use sp_inherents::{InherentData, InherentDataProvider, InherentIdentifier};
|
||||
use sp_runtime::{
|
||||
traits::{DigestItemFor, DigestFor, Block as BlockT, Zero, Header},
|
||||
generic::{Digest, BlockId}, Justifications,
|
||||
generic::{BlockId, Digest},
|
||||
traits::{Block as BlockT, DigestFor, DigestItemFor, Header, Zero},
|
||||
Justifications,
|
||||
};
|
||||
use sp_timestamp::{InherentType, INHERENT_IDENTIFIER, TimestampInherentData};
|
||||
use sp_consensus::import_queue::{Verifier, CacheKeyId};
|
||||
use sp_timestamp::{InherentType, TimestampInherentData, INHERENT_IDENTIFIER};
|
||||
|
||||
/// Provides BABE-compatible predigests and BlockImportParams.
|
||||
/// Intended for use with BABE runtimes.
|
||||
@@ -77,19 +87,16 @@ pub struct BabeVerifier<B: BlockT, C> {
|
||||
impl<B: BlockT, C> BabeVerifier<B, C> {
|
||||
/// create a nrew verifier
|
||||
pub fn new(epoch_changes: SharedEpochChanges<B, Epoch>, client: Arc<C>) -> BabeVerifier<B, C> {
|
||||
BabeVerifier {
|
||||
epoch_changes,
|
||||
client,
|
||||
}
|
||||
BabeVerifier { epoch_changes, client }
|
||||
}
|
||||
}
|
||||
|
||||
/// The verifier for the manual seal engine; instantly finalizes.
|
||||
#[async_trait::async_trait]
|
||||
impl<B, C> Verifier<B> for BabeVerifier<B, C>
|
||||
where
|
||||
B: BlockT,
|
||||
C: HeaderBackend<B> + HeaderMetadata<B, Error = sp_blockchain::Error>
|
||||
where
|
||||
B: BlockT,
|
||||
C: HeaderBackend<B> + HeaderMetadata<B, Error = sp_blockchain::Error>,
|
||||
{
|
||||
async fn verify(
|
||||
&mut self,
|
||||
@@ -107,7 +114,9 @@ impl<B, C> Verifier<B> for BabeVerifier<B, C>
|
||||
let pre_digest = find_pre_digest::<B>(&header)?;
|
||||
|
||||
let parent_hash = header.parent_hash();
|
||||
let parent = self.client.header(BlockId::Hash(*parent_hash))
|
||||
let parent = self
|
||||
.client
|
||||
.header(BlockId::Hash(*parent_hash))
|
||||
.ok()
|
||||
.flatten()
|
||||
.ok_or_else(|| format!("header for block {} not found", parent_hash))?;
|
||||
@@ -134,14 +143,14 @@ impl<B, C> Verifier<B> for BabeVerifier<B, C>
|
||||
}
|
||||
|
||||
impl<B, C> BabeConsensusDataProvider<B, C>
|
||||
where
|
||||
B: BlockT,
|
||||
C: AuxStore
|
||||
+ HeaderBackend<B>
|
||||
+ ProvideRuntimeApi<B>
|
||||
+ HeaderMetadata<B, Error = sp_blockchain::Error>
|
||||
+ UsageProvider<B>,
|
||||
C::Api: BabeApi<B>,
|
||||
where
|
||||
B: BlockT,
|
||||
C: AuxStore
|
||||
+ HeaderBackend<B>
|
||||
+ ProvideRuntimeApi<B>
|
||||
+ HeaderMetadata<B, Error = sp_blockchain::Error>
|
||||
+ UsageProvider<B>,
|
||||
C::Api: BabeApi<B>,
|
||||
{
|
||||
pub fn new(
|
||||
client: Arc<C>,
|
||||
@@ -155,13 +164,7 @@ impl<B, C> BabeConsensusDataProvider<B, C>
|
||||
|
||||
let config = Config::get_or_compute(&*client)?;
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
client,
|
||||
keystore,
|
||||
epoch_changes,
|
||||
authorities,
|
||||
})
|
||||
Ok(Self { config, client, keystore, epoch_changes, authorities })
|
||||
}
|
||||
|
||||
fn epoch(&self, parent: &B::Header, slot: Slot) -> Result<Epoch, Error> {
|
||||
@@ -177,10 +180,7 @@ impl<B, C> BabeConsensusDataProvider<B, C>
|
||||
.ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?;
|
||||
|
||||
let epoch = epoch_changes
|
||||
.viable_epoch(
|
||||
&epoch_descriptor,
|
||||
|slot| Epoch::genesis(&self.config, slot),
|
||||
)
|
||||
.viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot))
|
||||
.ok_or_else(|| {
|
||||
log::info!(target: "babe", "create_digest: no viable_epoch :(");
|
||||
sp_consensus::Error::InvalidAuthoritiesSet
|
||||
@@ -191,38 +191,37 @@ impl<B, C> BabeConsensusDataProvider<B, C>
|
||||
}
|
||||
|
||||
impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
|
||||
where
|
||||
B: BlockT,
|
||||
C: AuxStore
|
||||
+ HeaderBackend<B>
|
||||
+ HeaderMetadata<B, Error = sp_blockchain::Error>
|
||||
+ UsageProvider<B>
|
||||
+ ProvideRuntimeApi<B>,
|
||||
C::Api: BabeApi<B>,
|
||||
where
|
||||
B: BlockT,
|
||||
C: AuxStore
|
||||
+ HeaderBackend<B>
|
||||
+ HeaderMetadata<B, Error = sp_blockchain::Error>
|
||||
+ UsageProvider<B>
|
||||
+ ProvideRuntimeApi<B>,
|
||||
C::Api: BabeApi<B>,
|
||||
{
|
||||
type Transaction = TransactionFor<C, B>;
|
||||
|
||||
fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result<DigestFor<B>, Error> {
|
||||
let slot = inherents.babe_inherent_data()?
|
||||
fn create_digest(
|
||||
&self,
|
||||
parent: &B::Header,
|
||||
inherents: &InherentData,
|
||||
) -> Result<DigestFor<B>, Error> {
|
||||
let slot = inherents
|
||||
.babe_inherent_data()?
|
||||
.ok_or_else(|| Error::StringError("No babe inherent data".into()))?;
|
||||
let epoch = self.epoch(parent, slot)?;
|
||||
|
||||
// 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,
|
||||
&epoch,
|
||||
&self.keystore,
|
||||
) {
|
||||
vec![
|
||||
<DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(predigest),
|
||||
]
|
||||
let logs = if let Some((predigest, _)) =
|
||||
authorship::claim_slot(slot, &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,
|
||||
authority_index: 0_u32,
|
||||
});
|
||||
let predigest =
|
||||
PreDigest::SecondaryPlain(SecondaryPlainPreDigest { slot, authority_index: 0_u32 });
|
||||
|
||||
let mut epoch_changes = self.epoch_changes.shared_data();
|
||||
let epoch_descriptor = epoch_changes
|
||||
@@ -232,12 +231,15 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
|
||||
parent.number().clone(),
|
||||
slot,
|
||||
)
|
||||
.map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))?
|
||||
.map_err(|e| {
|
||||
Error::StringError(format!("failed to fetch epoch_descriptor: {}", e))
|
||||
})?
|
||||
.ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?;
|
||||
|
||||
match epoch_descriptor {
|
||||
ViableEpochDescriptor::Signaled(identifier, _epoch_header) => {
|
||||
let epoch_mut = epoch_changes.epoch_mut(&identifier)
|
||||
let epoch_mut = epoch_changes
|
||||
.epoch_mut(&identifier)
|
||||
.ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?;
|
||||
|
||||
// mutate the current epoch
|
||||
@@ -251,15 +253,13 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
|
||||
|
||||
vec![
|
||||
DigestItemFor::<B>::PreRuntime(BABE_ENGINE_ID, predigest.encode()),
|
||||
DigestItemFor::<B>::Consensus(BABE_ENGINE_ID, next_epoch.encode())
|
||||
DigestItemFor::<B>::Consensus(BABE_ENGINE_ID, next_epoch.encode()),
|
||||
]
|
||||
},
|
||||
ViableEpochDescriptor::UnimportedGenesis(_) => {
|
||||
// since this is the genesis, secondary predigest works for now.
|
||||
vec![
|
||||
DigestItemFor::<B>::PreRuntime(BABE_ENGINE_ID, predigest.encode()),
|
||||
]
|
||||
}
|
||||
vec![DigestItemFor::<B>::PreRuntime(BABE_ENGINE_ID, predigest.encode())]
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -270,9 +270,10 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
|
||||
&self,
|
||||
parent: &B::Header,
|
||||
params: &mut BlockImportParams<B, Self::Transaction>,
|
||||
inherents: &InherentData
|
||||
inherents: &InherentData,
|
||||
) -> Result<(), Error> {
|
||||
let slot = inherents.babe_inherent_data()?
|
||||
let slot = inherents
|
||||
.babe_inherent_data()?
|
||||
.ok_or_else(|| Error::StringError("No babe inherent data".into()))?;
|
||||
let epoch_changes = self.epoch_changes.shared_data();
|
||||
let mut epoch_descriptor = epoch_changes
|
||||
@@ -289,27 +290,27 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
|
||||
// a quick check to see if we're in the authorities
|
||||
let epoch = self.epoch(parent, slot)?;
|
||||
let (authority, _) = self.authorities.first().expect("authorities is non-emptyp; qed");
|
||||
let has_authority = epoch.authorities.iter()
|
||||
.find(|(id, _)| *id == *authority)
|
||||
.is_some();
|
||||
let has_authority = epoch.authorities.iter().find(|(id, _)| *id == *authority).is_some();
|
||||
|
||||
if !has_authority {
|
||||
log::info!(target: "manual-seal", "authority not found");
|
||||
let timestamp = inherents.timestamp_inherent_data()?
|
||||
let timestamp = inherents
|
||||
.timestamp_inherent_data()?
|
||||
.ok_or_else(|| Error::StringError("No timestamp inherent data".into()))?;
|
||||
let slot = *timestamp / self.config.slot_duration;
|
||||
// manually hard code epoch descriptor
|
||||
epoch_descriptor = match epoch_descriptor {
|
||||
ViableEpochDescriptor::Signaled(identifier, _header) => {
|
||||
ViableEpochDescriptor::Signaled(identifier, _header) =>
|
||||
ViableEpochDescriptor::Signaled(
|
||||
identifier,
|
||||
EpochHeader {
|
||||
start_slot: slot.into(),
|
||||
end_slot: (slot * self.config.epoch_length).into(),
|
||||
},
|
||||
)
|
||||
},
|
||||
_ => unreachable!("we're not in the authorities, so this isn't the genesis epoch; qed")
|
||||
),
|
||||
_ => unreachable!(
|
||||
"we're not in the authorities, so this isn't the genesis epoch; qed"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -326,16 +327,16 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
|
||||
/// Mocks the timestamp inherent to always produce the timestamp for the next babe slot.
|
||||
pub struct SlotTimestampProvider {
|
||||
time: atomic::AtomicU64,
|
||||
slot_duration: u64
|
||||
slot_duration: u64,
|
||||
}
|
||||
|
||||
impl SlotTimestampProvider {
|
||||
/// Create a new mocked time stamp provider.
|
||||
pub fn new<B, C>(client: Arc<C>) -> Result<Self, Error>
|
||||
where
|
||||
B: BlockT,
|
||||
C: AuxStore + HeaderBackend<B> + ProvideRuntimeApi<B> + UsageProvider<B>,
|
||||
C::Api: BabeApi<B>,
|
||||
where
|
||||
B: BlockT,
|
||||
C: AuxStore + HeaderBackend<B> + ProvideRuntimeApi<B> + UsageProvider<B>,
|
||||
C::Api: BabeApi<B>,
|
||||
{
|
||||
let slot_duration = Config::get_or_compute(&*client)?.slot_duration;
|
||||
let info = client.info();
|
||||
@@ -355,10 +356,7 @@ impl SlotTimestampProvider {
|
||||
.as_millis() as u64
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
time: atomic::AtomicU64::new(time),
|
||||
slot_duration,
|
||||
})
|
||||
Ok(Self { time: atomic::AtomicU64::new(time), slot_duration })
|
||||
}
|
||||
|
||||
/// Get the current slot number
|
||||
@@ -369,12 +367,13 @@ impl SlotTimestampProvider {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl InherentDataProvider for SlotTimestampProvider {
|
||||
fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), sp_inherents::Error> {
|
||||
fn provide_inherent_data(
|
||||
&self,
|
||||
inherent_data: &mut InherentData,
|
||||
) -> Result<(), sp_inherents::Error> {
|
||||
// we update the time here.
|
||||
let duration: InherentType = self.time.fetch_add(
|
||||
self.slot_duration,
|
||||
atomic::Ordering::SeqCst,
|
||||
).into();
|
||||
let duration: InherentType =
|
||||
self.time.fetch_add(self.slot_duration, atomic::Ordering::SeqCst).into();
|
||||
inherent_data.put_data(INHERENT_IDENTIFIER, &duration)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
//! A manual sealing engine: the engine listens for rpc calls to seal blocks and create forks.
|
||||
//! This is suitable for a testing environment.
|
||||
|
||||
use sp_consensus::{Error as ConsensusError, ImportResult};
|
||||
use futures::channel::{mpsc::SendError, oneshot};
|
||||
use sp_blockchain::Error as BlockchainError;
|
||||
use sp_consensus::{Error as ConsensusError, ImportResult};
|
||||
use sp_inherents::Error as InherentsError;
|
||||
use futures::channel::{oneshot, mpsc::SendError};
|
||||
|
||||
/// Error code for rpc
|
||||
mod codes {
|
||||
@@ -63,14 +63,14 @@ pub enum Error {
|
||||
#[display(fmt = "{}", _0)]
|
||||
#[from(ignore)]
|
||||
StringError(String),
|
||||
///send error
|
||||
/// send error
|
||||
#[display(fmt = "Consensus process is terminating")]
|
||||
Canceled(oneshot::Canceled),
|
||||
///send error
|
||||
/// send error
|
||||
#[display(fmt = "Consensus process is terminating")]
|
||||
SendError(SendError),
|
||||
/// Some other error.
|
||||
#[display(fmt="Other error: {}", _0)]
|
||||
#[display(fmt = "Other error: {}", _0)]
|
||||
Other(Box<dyn std::error::Error + Send>),
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ impl Error {
|
||||
InherentError(_) => codes::INHERENTS_ERROR,
|
||||
BlockchainError(_) => codes::BLOCKCHAIN_ERROR,
|
||||
SendError(_) | Canceled(_) => codes::SERVER_SHUTTING_DOWN,
|
||||
_ => codes::UNKNOWN_ERROR
|
||||
_ => codes::UNKNOWN_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ impl std::convert::From<Error> for jsonrpc_core::Error {
|
||||
jsonrpc_core::Error {
|
||||
code: jsonrpc_core::ErrorCode::ServerError(error.to_code()),
|
||||
message: format!("{}", error),
|
||||
data: None
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,9 @@
|
||||
//! Block finalization utilities
|
||||
|
||||
use crate::rpc;
|
||||
use sp_runtime::{
|
||||
Justification,
|
||||
traits::Block as BlockT,
|
||||
generic::BlockId,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use sc_client_api::backend::{Backend as ClientBackend, Finalizer};
|
||||
use std::marker::PhantomData;
|
||||
use sp_runtime::{generic::BlockId, traits::Block as BlockT, Justification};
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
/// params for block finalization.
|
||||
pub struct FinalizeBlockParams<B: BlockT, F, CB> {
|
||||
@@ -42,30 +37,23 @@ pub struct FinalizeBlockParams<B: BlockT, F, CB> {
|
||||
pub _phantom: PhantomData<CB>,
|
||||
}
|
||||
|
||||
|
||||
/// finalizes a block in the backend with the given params.
|
||||
pub async fn finalize_block<B, F, CB>(params: FinalizeBlockParams<B, F, CB>)
|
||||
where
|
||||
B: BlockT,
|
||||
F: Finalizer<B, CB>,
|
||||
CB: ClientBackend<B>,
|
||||
where
|
||||
B: BlockT,
|
||||
F: Finalizer<B, CB>,
|
||||
CB: ClientBackend<B>,
|
||||
{
|
||||
let FinalizeBlockParams {
|
||||
hash,
|
||||
mut sender,
|
||||
justification,
|
||||
finalizer,
|
||||
..
|
||||
} = params;
|
||||
let FinalizeBlockParams { hash, mut sender, justification, finalizer, .. } = params;
|
||||
|
||||
match finalizer.finalize_block(BlockId::Hash(hash), justification, true) {
|
||||
Err(e) => {
|
||||
log::warn!("Failed to finalize block {:?}", e);
|
||||
rpc::send_result(&mut sender, Err(e.into()))
|
||||
}
|
||||
},
|
||||
Ok(()) => {
|
||||
log::info!("✅ Successfully finalized block: {}", hash);
|
||||
rpc::send_result(&mut sender, Ok(()))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@
|
||||
//! This is suitable for a testing environment.
|
||||
|
||||
use futures::prelude::*;
|
||||
use sp_consensus::{
|
||||
Environment, Proposer, SelectChain, BlockImport,
|
||||
ForkChoiceStrategy, BlockImportParams, BlockOrigin,
|
||||
import_queue::{Verifier, BasicQueue, CacheKeyId, BoxBlockImport},
|
||||
};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_inherents::CreateInherentDataProviders;
|
||||
use sp_runtime::{traits::Block as BlockT, Justifications, ConsensusEngineId};
|
||||
use sc_client_api::backend::{Backend as ClientBackend, Finalizer};
|
||||
use std::{sync::Arc, marker::PhantomData};
|
||||
use prometheus_endpoint::Registry;
|
||||
use sc_client_api::backend::{Backend as ClientBackend, Finalizer};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_consensus::{
|
||||
import_queue::{BasicQueue, BoxBlockImport, CacheKeyId, Verifier},
|
||||
BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy, Proposer,
|
||||
SelectChain,
|
||||
};
|
||||
use sp_inherents::CreateInherentDataProviders;
|
||||
use sp_runtime::{traits::Block as BlockT, ConsensusEngineId, Justifications};
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
mod error;
|
||||
mod finalize_block;
|
||||
@@ -40,14 +40,14 @@ pub mod consensus;
|
||||
pub mod rpc;
|
||||
|
||||
pub use self::{
|
||||
error::Error,
|
||||
consensus::ConsensusDataProvider,
|
||||
error::Error,
|
||||
finalize_block::{finalize_block, FinalizeBlockParams},
|
||||
seal_block::{SealBlockParams, seal_block, MAX_PROPOSAL_DURATION},
|
||||
rpc::{EngineCommand, CreatedBlock},
|
||||
rpc::{CreatedBlock, EngineCommand},
|
||||
seal_block::{seal_block, SealBlockParams, MAX_PROPOSAL_DURATION},
|
||||
};
|
||||
use sp_api::{ProvideRuntimeApi, TransactionFor};
|
||||
use sc_transaction_pool_api::TransactionPool;
|
||||
use sp_api::{ProvideRuntimeApi, TransactionFor};
|
||||
|
||||
/// The `ConsensusEngineId` of Manual Seal.
|
||||
pub const MANUAL_SEAL_ENGINE_ID: ConsensusEngineId = [b'm', b'a', b'n', b'l'];
|
||||
@@ -80,17 +80,11 @@ pub fn import_queue<Block, Transaction>(
|
||||
spawner: &impl sp_core::traits::SpawnEssentialNamed,
|
||||
registry: Option<&Registry>,
|
||||
) -> BasicQueue<Block, Transaction>
|
||||
where
|
||||
Block: BlockT,
|
||||
Transaction: Send + Sync + 'static,
|
||||
where
|
||||
Block: BlockT,
|
||||
Transaction: Send + Sync + 'static,
|
||||
{
|
||||
BasicQueue::new(
|
||||
ManualSealVerifier,
|
||||
block_import,
|
||||
None,
|
||||
spawner,
|
||||
registry,
|
||||
)
|
||||
BasicQueue::new(ManualSealVerifier, block_import, None, spawner, registry)
|
||||
}
|
||||
|
||||
/// Params required to start the instant sealing authorship task.
|
||||
@@ -115,7 +109,8 @@ pub struct ManualSealParams<B: BlockT, BI, E, C: ProvideRuntimeApi<B>, TP, SC, C
|
||||
pub select_chain: SC,
|
||||
|
||||
/// Digest provider for inclusion in blocks.
|
||||
pub consensus_data_provider: Option<Box<dyn ConsensusDataProvider<B, Transaction = TransactionFor<C, B>>>>,
|
||||
pub consensus_data_provider:
|
||||
Option<Box<dyn ConsensusDataProvider<B, Transaction = TransactionFor<C, B>>>>,
|
||||
|
||||
/// Something that can create the inherent data providers.
|
||||
pub create_inherent_data_providers: CIDP,
|
||||
@@ -139,7 +134,8 @@ pub struct InstantSealParams<B: BlockT, BI, E, C: ProvideRuntimeApi<B>, TP, SC,
|
||||
pub select_chain: SC,
|
||||
|
||||
/// Digest provider for inclusion in blocks.
|
||||
pub consensus_data_provider: Option<Box<dyn ConsensusDataProvider<B, Transaction = TransactionFor<C, B>>>>,
|
||||
pub consensus_data_provider:
|
||||
Option<Box<dyn ConsensusDataProvider<B, Transaction = TransactionFor<C, B>>>>,
|
||||
|
||||
/// Something that can create the inherent data providers.
|
||||
pub create_inherent_data_providers: CIDP,
|
||||
@@ -156,58 +152,52 @@ pub async fn run_manual_seal<B, BI, CB, E, C, TP, SC, CS, CIDP>(
|
||||
select_chain,
|
||||
consensus_data_provider,
|
||||
create_inherent_data_providers,
|
||||
}: ManualSealParams<B, BI, E, C, TP, SC, CS, CIDP>
|
||||
)
|
||||
where
|
||||
B: BlockT + 'static,
|
||||
BI: BlockImport<B, Error = sp_consensus::Error, Transaction = sp_api::TransactionFor<C, B>>
|
||||
+ Send + Sync + 'static,
|
||||
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + 'static,
|
||||
CB: ClientBackend<B> + 'static,
|
||||
E: Environment<B> + 'static,
|
||||
E::Proposer: Proposer<B, Transaction = TransactionFor<C, B>>,
|
||||
CS: Stream<Item=EngineCommand<<B as BlockT>::Hash>> + Unpin + 'static,
|
||||
SC: SelectChain<B> + 'static,
|
||||
TransactionFor<C, B>: 'static,
|
||||
TP: TransactionPool<Block = B>,
|
||||
CIDP: CreateInherentDataProviders<B, ()>,
|
||||
}: ManualSealParams<B, BI, E, C, TP, SC, CS, CIDP>,
|
||||
) where
|
||||
B: BlockT + 'static,
|
||||
BI: BlockImport<B, Error = sp_consensus::Error, Transaction = sp_api::TransactionFor<C, B>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + 'static,
|
||||
CB: ClientBackend<B> + 'static,
|
||||
E: Environment<B> + 'static,
|
||||
E::Proposer: Proposer<B, Transaction = TransactionFor<C, B>>,
|
||||
CS: Stream<Item = EngineCommand<<B as BlockT>::Hash>> + Unpin + 'static,
|
||||
SC: SelectChain<B> + 'static,
|
||||
TransactionFor<C, B>: 'static,
|
||||
TP: TransactionPool<Block = B>,
|
||||
CIDP: CreateInherentDataProviders<B, ()>,
|
||||
{
|
||||
while let Some(command) = commands_stream.next().await {
|
||||
match command {
|
||||
EngineCommand::SealNewBlock {
|
||||
create_empty,
|
||||
finalize,
|
||||
parent_hash,
|
||||
sender,
|
||||
} => {
|
||||
seal_block(
|
||||
SealBlockParams {
|
||||
sender,
|
||||
parent_hash,
|
||||
finalize,
|
||||
create_empty,
|
||||
env: &mut env,
|
||||
select_chain: &select_chain,
|
||||
block_import: &mut block_import,
|
||||
consensus_data_provider: consensus_data_provider.as_ref().map(|p| &**p),
|
||||
pool: pool.clone(),
|
||||
client: client.clone(),
|
||||
create_inherent_data_providers: &create_inherent_data_providers,
|
||||
}
|
||||
).await;
|
||||
}
|
||||
EngineCommand::SealNewBlock { create_empty, finalize, parent_hash, sender } => {
|
||||
seal_block(SealBlockParams {
|
||||
sender,
|
||||
parent_hash,
|
||||
finalize,
|
||||
create_empty,
|
||||
env: &mut env,
|
||||
select_chain: &select_chain,
|
||||
block_import: &mut block_import,
|
||||
consensus_data_provider: consensus_data_provider.as_ref().map(|p| &**p),
|
||||
pool: pool.clone(),
|
||||
client: client.clone(),
|
||||
create_inherent_data_providers: &create_inherent_data_providers,
|
||||
})
|
||||
.await;
|
||||
},
|
||||
EngineCommand::FinalizeBlock { hash, sender, justification } => {
|
||||
let justification = justification.map(|j| (MANUAL_SEAL_ENGINE_ID, j));
|
||||
finalize_block(
|
||||
FinalizeBlockParams {
|
||||
hash,
|
||||
sender,
|
||||
justification,
|
||||
finalizer: client.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
).await
|
||||
}
|
||||
finalize_block(FinalizeBlockParams {
|
||||
hash,
|
||||
sender,
|
||||
justification,
|
||||
finalizer: client.clone(),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
.await
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,63 +214,57 @@ pub async fn run_instant_seal<B, BI, CB, E, C, TP, SC, CIDP>(
|
||||
select_chain,
|
||||
consensus_data_provider,
|
||||
create_inherent_data_providers,
|
||||
}: InstantSealParams<B, BI, E, C, TP, SC, CIDP>
|
||||
)
|
||||
where
|
||||
B: BlockT + 'static,
|
||||
BI: BlockImport<B, Error = sp_consensus::Error, Transaction = sp_api::TransactionFor<C, B>>
|
||||
+ Send + Sync + 'static,
|
||||
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + 'static,
|
||||
CB: ClientBackend<B> + 'static,
|
||||
E: Environment<B> + 'static,
|
||||
E::Proposer: Proposer<B, Transaction = TransactionFor<C, B>>,
|
||||
SC: SelectChain<B> + 'static,
|
||||
TransactionFor<C, B>: 'static,
|
||||
TP: TransactionPool<Block = B>,
|
||||
CIDP: CreateInherentDataProviders<B, ()>,
|
||||
}: InstantSealParams<B, BI, E, C, TP, SC, CIDP>,
|
||||
) where
|
||||
B: BlockT + 'static,
|
||||
BI: BlockImport<B, Error = sp_consensus::Error, Transaction = sp_api::TransactionFor<C, B>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + 'static,
|
||||
CB: ClientBackend<B> + 'static,
|
||||
E: Environment<B> + 'static,
|
||||
E::Proposer: Proposer<B, Transaction = TransactionFor<C, B>>,
|
||||
SC: SelectChain<B> + 'static,
|
||||
TransactionFor<C, B>: 'static,
|
||||
TP: TransactionPool<Block = B>,
|
||||
CIDP: CreateInherentDataProviders<B, ()>,
|
||||
{
|
||||
// instant-seal creates blocks as soon as transactions are imported
|
||||
// into the transaction pool.
|
||||
let commands_stream = pool.import_notification_stream()
|
||||
.map(|_| {
|
||||
EngineCommand::SealNewBlock {
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
parent_hash: None,
|
||||
sender: None,
|
||||
}
|
||||
});
|
||||
let commands_stream = pool.import_notification_stream().map(|_| EngineCommand::SealNewBlock {
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
parent_hash: None,
|
||||
sender: None,
|
||||
});
|
||||
|
||||
run_manual_seal(
|
||||
ManualSealParams {
|
||||
block_import,
|
||||
env,
|
||||
client,
|
||||
pool,
|
||||
commands_stream,
|
||||
select_chain,
|
||||
consensus_data_provider,
|
||||
create_inherent_data_providers,
|
||||
}
|
||||
).await
|
||||
run_manual_seal(ManualSealParams {
|
||||
block_import,
|
||||
env,
|
||||
client,
|
||||
pool,
|
||||
commands_stream,
|
||||
select_chain,
|
||||
consensus_data_provider,
|
||||
create_inherent_data_providers,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use substrate_test_runtime_client::{
|
||||
DefaultTestClientBuilderExt,
|
||||
TestClientBuilderExt,
|
||||
AccountKeyring::*,
|
||||
TestClientBuilder,
|
||||
};
|
||||
use sc_transaction_pool::{BasicPool, RevalidationType, Options};
|
||||
use substrate_test_runtime_transaction_pool::{TestApi, uxt};
|
||||
use sc_transaction_pool_api::{TransactionPool, MaintainedTransactionPool, TransactionSource};
|
||||
use sp_runtime::generic::BlockId;
|
||||
use sp_consensus::ImportedAux;
|
||||
use sc_basic_authorship::ProposerFactory;
|
||||
use sc_client_api::BlockBackend;
|
||||
use sc_transaction_pool::{BasicPool, Options, RevalidationType};
|
||||
use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionSource};
|
||||
use sp_consensus::ImportedAux;
|
||||
use sp_runtime::generic::BlockId;
|
||||
use substrate_test_runtime_client::{
|
||||
AccountKeyring::*, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt,
|
||||
};
|
||||
use substrate_test_runtime_transaction_pool::{uxt, TestApi};
|
||||
|
||||
fn api() -> Arc<TestApi> {
|
||||
Arc::new(TestApi::empty())
|
||||
@@ -303,40 +287,32 @@ mod tests {
|
||||
spawner.clone(),
|
||||
0,
|
||||
));
|
||||
let env = ProposerFactory::new(
|
||||
spawner.clone(),
|
||||
client.clone(),
|
||||
pool.clone(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
|
||||
// this test checks that blocks are created as soon as transactions are imported into the pool.
|
||||
let (sender, receiver) = futures::channel::oneshot::channel();
|
||||
let mut sender = Arc::new(Some(sender));
|
||||
let commands_stream = pool.pool().validated_pool().import_notification_stream()
|
||||
.map(move |_| {
|
||||
let commands_stream =
|
||||
pool.pool().validated_pool().import_notification_stream().map(move |_| {
|
||||
// we're only going to submit one tx so this fn will only be called once.
|
||||
let mut_sender = Arc::get_mut(&mut sender).unwrap();
|
||||
let mut_sender = Arc::get_mut(&mut sender).unwrap();
|
||||
let sender = std::mem::take(mut_sender);
|
||||
EngineCommand::SealNewBlock {
|
||||
create_empty: false,
|
||||
finalize: true,
|
||||
parent_hash: None,
|
||||
sender
|
||||
sender,
|
||||
}
|
||||
});
|
||||
let future = run_manual_seal(
|
||||
ManualSealParams {
|
||||
block_import: client.clone(),
|
||||
env,
|
||||
client: client.clone(),
|
||||
pool: pool.clone(),
|
||||
commands_stream,
|
||||
select_chain,
|
||||
create_inherent_data_providers: |_, _| async { Ok(()) },
|
||||
consensus_data_provider: None,
|
||||
}
|
||||
);
|
||||
let future = run_manual_seal(ManualSealParams {
|
||||
block_import: client.clone(),
|
||||
env,
|
||||
client: client.clone(),
|
||||
pool: pool.clone(),
|
||||
commands_stream,
|
||||
select_chain,
|
||||
create_inherent_data_providers: |_, _| async { Ok(()) },
|
||||
consensus_data_provider: None,
|
||||
});
|
||||
std::thread::spawn(|| {
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
// spawn the background authorship task
|
||||
@@ -380,27 +356,19 @@ mod tests {
|
||||
spawner.clone(),
|
||||
0,
|
||||
));
|
||||
let env = ProposerFactory::new(
|
||||
spawner.clone(),
|
||||
client.clone(),
|
||||
pool.clone(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
|
||||
// this test checks that blocks are created as soon as an engine command is sent over the stream.
|
||||
let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024);
|
||||
let future = run_manual_seal(
|
||||
ManualSealParams {
|
||||
block_import: client.clone(),
|
||||
env,
|
||||
client: client.clone(),
|
||||
pool: pool.clone(),
|
||||
commands_stream,
|
||||
select_chain,
|
||||
consensus_data_provider: None,
|
||||
create_inherent_data_providers: |_, _| async { Ok(()) },
|
||||
}
|
||||
);
|
||||
let future = run_manual_seal(ManualSealParams {
|
||||
block_import: client.clone(),
|
||||
env,
|
||||
client: client.clone(),
|
||||
pool: pool.clone(),
|
||||
commands_stream,
|
||||
select_chain,
|
||||
consensus_data_provider: None,
|
||||
create_inherent_data_providers: |_, _| async { Ok(()) },
|
||||
});
|
||||
std::thread::spawn(|| {
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
// spawn the background authorship task
|
||||
@@ -416,7 +384,9 @@ mod tests {
|
||||
sender: Some(tx),
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
}).await.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let created_block = rx.await.unwrap().unwrap();
|
||||
|
||||
// assert that the background task returns ok
|
||||
@@ -439,8 +409,10 @@ mod tests {
|
||||
sink.send(EngineCommand::FinalizeBlock {
|
||||
sender: Some(tx),
|
||||
hash: header.hash(),
|
||||
justification: None
|
||||
}).await.unwrap();
|
||||
justification: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
// assert that the background task returns ok
|
||||
assert_eq!(rx.await.unwrap().unwrap(), ());
|
||||
}
|
||||
@@ -461,27 +433,19 @@ mod tests {
|
||||
spawner.clone(),
|
||||
0,
|
||||
));
|
||||
let env = ProposerFactory::new(
|
||||
spawner.clone(),
|
||||
client.clone(),
|
||||
pool.clone(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None);
|
||||
// this test checks that blocks are created as soon as an engine command is sent over the stream.
|
||||
let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024);
|
||||
let future = run_manual_seal(
|
||||
ManualSealParams {
|
||||
block_import: client.clone(),
|
||||
env,
|
||||
client: client.clone(),
|
||||
pool: pool.clone(),
|
||||
commands_stream,
|
||||
select_chain,
|
||||
consensus_data_provider: None,
|
||||
create_inherent_data_providers: |_, _| async { Ok(()) },
|
||||
}
|
||||
);
|
||||
let future = run_manual_seal(ManualSealParams {
|
||||
block_import: client.clone(),
|
||||
env,
|
||||
client: client.clone(),
|
||||
pool: pool.clone(),
|
||||
commands_stream,
|
||||
select_chain,
|
||||
consensus_data_provider: None,
|
||||
create_inherent_data_providers: |_, _| async { Ok(()) },
|
||||
});
|
||||
std::thread::spawn(|| {
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
// spawn the background authorship task
|
||||
@@ -498,7 +462,9 @@ mod tests {
|
||||
sender: Some(tx),
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
}).await.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let created_block = rx.await.unwrap().unwrap();
|
||||
pool_api.increment_nonce(Alice.into());
|
||||
|
||||
@@ -524,31 +490,35 @@ mod tests {
|
||||
pool.maintain(sc_transaction_pool_api::ChainEvent::NewBestBlock {
|
||||
hash: header.hash(),
|
||||
tree_route: None,
|
||||
}).await;
|
||||
})
|
||||
.await;
|
||||
|
||||
let (tx1, rx1) = futures::channel::oneshot::channel();
|
||||
assert!(sink.send(EngineCommand::SealNewBlock {
|
||||
parent_hash: Some(created_block.hash),
|
||||
sender: Some(tx1),
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
}).await.is_ok());
|
||||
assert_matches::assert_matches!(
|
||||
rx1.await.expect("should be no error receiving"),
|
||||
Ok(_)
|
||||
);
|
||||
assert!(sink
|
||||
.send(EngineCommand::SealNewBlock {
|
||||
parent_hash: Some(created_block.hash),
|
||||
sender: Some(tx1),
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
assert_matches::assert_matches!(rx1.await.expect("should be no error receiving"), Ok(_));
|
||||
let block = client.block(&BlockId::Number(2)).unwrap().unwrap().block;
|
||||
pool_api.add_block(block, true);
|
||||
pool_api.increment_nonce(Alice.into());
|
||||
|
||||
assert!(pool.submit_one(&BlockId::Number(1), SOURCE, uxt(Bob, 0)).await.is_ok());
|
||||
let (tx2, rx2) = futures::channel::oneshot::channel();
|
||||
assert!(sink.send(EngineCommand::SealNewBlock {
|
||||
parent_hash: Some(created_block.hash),
|
||||
sender: Some(tx2),
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
}).await.is_ok());
|
||||
assert!(sink
|
||||
.send(EngineCommand::SealNewBlock {
|
||||
parent_hash: Some(created_block.hash),
|
||||
sender: Some(tx2),
|
||||
create_empty: false,
|
||||
finalize: false,
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
let imported = rx2.await.unwrap().unwrap();
|
||||
// assert that fork block is in the db
|
||||
assert!(client.header(&BlockId::Hash(imported.hash)).unwrap().is_some())
|
||||
|
||||
@@ -18,18 +18,16 @@
|
||||
|
||||
//! RPC interface for the `ManualSeal` Engine.
|
||||
|
||||
use sp_consensus::ImportedAux;
|
||||
use jsonrpc_core::Error;
|
||||
use jsonrpc_derive::rpc;
|
||||
pub use self::gen_client::Client as ManualSealClient;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
TryFutureExt,
|
||||
FutureExt,
|
||||
SinkExt
|
||||
FutureExt, SinkExt, TryFutureExt,
|
||||
};
|
||||
use jsonrpc_core::Error;
|
||||
use jsonrpc_derive::rpc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_consensus::ImportedAux;
|
||||
use sp_runtime::EncodedJustification;
|
||||
pub use self::gen_client::Client as ManualSealClient;
|
||||
|
||||
/// Future's type for jsonrpc
|
||||
type FutureResult<T> = Box<dyn jsonrpc_core::futures::Future<Item = T, Error = Error> + Send>;
|
||||
@@ -63,7 +61,7 @@ pub enum EngineCommand<Hash> {
|
||||
sender: Sender<()>,
|
||||
/// finalization justification
|
||||
justification: Option<EncodedJustification>,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc.
|
||||
@@ -75,7 +73,7 @@ pub trait ManualSealApi<Hash> {
|
||||
&self,
|
||||
create_empty: bool,
|
||||
finalize: bool,
|
||||
parent_hash: Option<Hash>
|
||||
parent_hash: Option<Hash>,
|
||||
) -> FutureResult<CreatedBlock<Hash>>;
|
||||
|
||||
/// Instructs the manual-seal authorship task to finalize a block
|
||||
@@ -83,7 +81,7 @@ pub trait ManualSealApi<Hash> {
|
||||
fn finalize_block(
|
||||
&self,
|
||||
hash: Hash,
|
||||
justification: Option<EncodedJustification>
|
||||
justification: Option<EncodedJustification>,
|
||||
) -> FutureResult<bool>;
|
||||
}
|
||||
|
||||
@@ -98,7 +96,7 @@ pub struct CreatedBlock<Hash> {
|
||||
/// hash of the created block.
|
||||
pub hash: Hash,
|
||||
/// some extra details about the import operation
|
||||
pub aux: ImportedAux
|
||||
pub aux: ImportedAux,
|
||||
}
|
||||
|
||||
impl<Hash> ManualSeal<Hash> {
|
||||
@@ -113,7 +111,7 @@ impl<Hash: Send + 'static> ManualSealApi<Hash> for ManualSeal<Hash> {
|
||||
&self,
|
||||
create_empty: bool,
|
||||
finalize: bool,
|
||||
parent_hash: Option<Hash>
|
||||
parent_hash: Option<Hash>,
|
||||
) -> FutureResult<CreatedBlock<Hash>> {
|
||||
let mut sink = self.import_block_channel.clone();
|
||||
let future = async move {
|
||||
@@ -126,18 +124,22 @@ impl<Hash: Send + 'static> ManualSealApi<Hash> for ManualSeal<Hash> {
|
||||
};
|
||||
sink.send(command).await?;
|
||||
receiver.await?
|
||||
}.boxed();
|
||||
}
|
||||
.boxed();
|
||||
|
||||
Box::new(future.map_err(Error::from).compat())
|
||||
}
|
||||
|
||||
fn finalize_block(&self, hash: Hash, justification: Option<EncodedJustification>) -> FutureResult<bool> {
|
||||
fn finalize_block(
|
||||
&self,
|
||||
hash: Hash,
|
||||
justification: Option<EncodedJustification>,
|
||||
) -> FutureResult<bool> {
|
||||
let mut sink = self.import_block_channel.clone();
|
||||
let future = async move {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
sink.send(
|
||||
EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification }
|
||||
).await?;
|
||||
sink.send(EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification })
|
||||
.await?;
|
||||
|
||||
receiver.await?.map(|_| true)
|
||||
};
|
||||
@@ -150,7 +152,7 @@ impl<Hash: Send + 'static> ManualSealApi<Hash> for ManualSeal<Hash> {
|
||||
/// to the rpc
|
||||
pub fn send_result<T: std::fmt::Debug>(
|
||||
sender: &mut Sender<T>,
|
||||
result: std::result::Result<T, crate::Error>
|
||||
result: std::result::Result<T, crate::Error>,
|
||||
) {
|
||||
if let Some(sender) = sender.take() {
|
||||
if let Err(err) = sender.send(result) {
|
||||
@@ -160,7 +162,7 @@ pub fn send_result<T: std::fmt::Debug>(
|
||||
// instant seal doesn't report errors over rpc, simply log them.
|
||||
match result {
|
||||
Ok(r) => log::info!("Instant Seal success: {:?}", r),
|
||||
Err(e) => log::error!("Instant Seal encountered an error: {}", e)
|
||||
Err(e) => log::error!("Instant Seal encountered an error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,23 +18,21 @@
|
||||
|
||||
//! Block sealing utilities
|
||||
|
||||
use crate::{Error, rpc, CreatedBlock, ConsensusDataProvider};
|
||||
use std::sync::Arc;
|
||||
use sp_runtime::{
|
||||
traits::{Block as BlockT, Header as HeaderT},
|
||||
generic::BlockId,
|
||||
};
|
||||
use crate::{rpc, ConsensusDataProvider, CreatedBlock, Error};
|
||||
use futures::prelude::*;
|
||||
use sp_consensus::{
|
||||
self, BlockImport, Environment, Proposer, ForkChoiceStrategy,
|
||||
BlockImportParams, BlockOrigin, ImportResult, SelectChain, StateAction,
|
||||
};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
|
||||
use sp_api::{ProvideRuntimeApi, TransactionFor};
|
||||
use sc_transaction_pool_api::TransactionPool;
|
||||
use sp_api::{ProvideRuntimeApi, TransactionFor};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_consensus::{
|
||||
self, BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy,
|
||||
ImportResult, Proposer, SelectChain, StateAction,
|
||||
};
|
||||
use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
|
||||
use sp_runtime::{
|
||||
generic::BlockId,
|
||||
traits::{Block as BlockT, Header as HeaderT},
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
|
||||
/// max duration for creating a proposal in secs
|
||||
pub const MAX_PROPOSAL_DURATION: u64 = 10;
|
||||
@@ -59,7 +57,8 @@ pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi<B>, E, TP
|
||||
/// SelectChain object
|
||||
pub select_chain: &'a SC,
|
||||
/// Digest provider for inclusion in blocks.
|
||||
pub consensus_data_provider: Option<&'a dyn ConsensusDataProvider<B, Transaction = TransactionFor<C, B>>>,
|
||||
pub consensus_data_provider:
|
||||
Option<&'a dyn ConsensusDataProvider<B, Transaction = TransactionFor<C, B>>>,
|
||||
/// block import object
|
||||
pub block_import: &'a mut BI,
|
||||
/// Something that can create the inherent data providers.
|
||||
@@ -97,7 +96,7 @@ pub async fn seal_block<B, BI, SC, C, E, TP, CIDP>(
|
||||
{
|
||||
let future = async {
|
||||
if pool.status().ready == 0 && !create_empty {
|
||||
return Err(Error::EmptyTransactionPool);
|
||||
return Err(Error::EmptyTransactionPool)
|
||||
}
|
||||
|
||||
// get the header to build this new block on.
|
||||
@@ -129,12 +128,15 @@ pub async fn seal_block<B, BI, SC, C, E, TP, CIDP>(
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let proposal = proposer.propose(
|
||||
inherent_data.clone(),
|
||||
digest,
|
||||
Duration::from_secs(MAX_PROPOSAL_DURATION),
|
||||
None,
|
||||
).map_err(|err| Error::StringError(format!("{:?}", err))).await?;
|
||||
let proposal = proposer
|
||||
.propose(
|
||||
inherent_data.clone(),
|
||||
digest,
|
||||
Duration::from_secs(MAX_PROPOSAL_DURATION),
|
||||
None,
|
||||
)
|
||||
.map_err(|err| Error::StringError(format!("{:?}", err)))
|
||||
.await?;
|
||||
|
||||
if proposal.block.extrinsics().len() == inherents_len && !create_empty {
|
||||
return Err(Error::EmptyTransactionPool)
|
||||
@@ -145,18 +147,17 @@ pub async fn seal_block<B, BI, SC, C, E, TP, CIDP>(
|
||||
params.body = Some(body);
|
||||
params.finalized = finalize;
|
||||
params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
|
||||
params.state_action = StateAction::ApplyChanges(
|
||||
sp_consensus::StorageChanges::Changes(proposal.storage_changes)
|
||||
);
|
||||
params.state_action = StateAction::ApplyChanges(sp_consensus::StorageChanges::Changes(
|
||||
proposal.storage_changes,
|
||||
));
|
||||
|
||||
if let Some(digest_provider) = digest_provider {
|
||||
digest_provider.append_block_import(&parent, &mut params, &inherent_data)?;
|
||||
}
|
||||
|
||||
match block_import.import_block(params, HashMap::new()).await? {
|
||||
ImportResult::Imported(aux) => {
|
||||
Ok(CreatedBlock { hash: <B as BlockT>::Header::hash(&header), aux })
|
||||
},
|
||||
ImportResult::Imported(aux) =>
|
||||
Ok(CreatedBlock { hash: <B as BlockT>::Header::hash(&header), aux }),
|
||||
other => Err(other.into()),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,34 +41,33 @@
|
||||
|
||||
mod worker;
|
||||
|
||||
pub use crate::worker::{MiningWorker, MiningMetadata, MiningBuild};
|
||||
pub use crate::worker::{MiningBuild, MiningMetadata, MiningWorker};
|
||||
|
||||
use std::{
|
||||
sync::Arc, borrow::Cow, collections::HashMap, marker::PhantomData,
|
||||
cmp::Ordering, time::Duration,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{Future, StreamExt};
|
||||
use log::*;
|
||||
use parking_lot::Mutex;
|
||||
use sc_client_api::{BlockOf, backend::AuxStore, BlockchainEvents};
|
||||
use sp_blockchain::{HeaderBackend, ProvideCache, well_known_cache_keys::Id as CacheKeyId};
|
||||
use sp_block_builder::BlockBuilder as BlockBuilderApi;
|
||||
use sp_runtime::{Justifications, RuntimeString};
|
||||
use sp_runtime::generic::{BlockId, Digest, DigestItem};
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
use prometheus_endpoint::Registry;
|
||||
use sc_client_api::{self, backend::AuxStore, BlockOf, BlockchainEvents};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_block_builder::BlockBuilder as BlockBuilderApi;
|
||||
use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend, ProvideCache};
|
||||
use sp_consensus::{
|
||||
import_queue::{BasicQueue, BoxBlockImport, BoxJustificationImport, Verifier},
|
||||
BlockCheckParams, BlockImport, BlockImportParams, BlockOrigin, CanAuthorWith, Environment,
|
||||
Error as ConsensusError, ForkChoiceStrategy, ImportResult, Proposer, SelectChain, SyncOracle,
|
||||
};
|
||||
use sp_consensus_pow::{Seal, TotalDifficulty, POW_ENGINE_ID};
|
||||
use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
|
||||
use sp_consensus::{
|
||||
BlockImportParams, BlockOrigin, ForkChoiceStrategy, SyncOracle, Environment, Proposer,
|
||||
SelectChain, Error as ConsensusError, CanAuthorWith, BlockImport, BlockCheckParams, ImportResult,
|
||||
use sp_runtime::{
|
||||
generic::{BlockId, Digest, DigestItem},
|
||||
traits::{Block as BlockT, Header as HeaderT},
|
||||
Justifications, RuntimeString,
|
||||
};
|
||||
use sp_consensus::import_queue::{
|
||||
BoxBlockImport, BasicQueue, Verifier, BoxJustificationImport,
|
||||
use std::{
|
||||
borrow::Cow, cmp::Ordering, collections::HashMap, marker::PhantomData, sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use prometheus_endpoint::Registry;
|
||||
use sc_client_api;
|
||||
use log::*;
|
||||
|
||||
use crate::worker::UntilImportedOrTimeout;
|
||||
|
||||
@@ -102,7 +101,7 @@ pub enum Error<B: BlockT> {
|
||||
CheckInherents(sp_inherents::Error),
|
||||
#[display(
|
||||
fmt = "Checking inherents unknown error for identifier: {:?}",
|
||||
"String::from_utf8_lossy(_0)",
|
||||
"String::from_utf8_lossy(_0)"
|
||||
)]
|
||||
CheckInherentsUnknownError(sp_inherents::InherentIdentifier),
|
||||
#[display(fmt = "Multiple pre-runtime digests")]
|
||||
@@ -153,7 +152,8 @@ pub struct PowAux<Difficulty> {
|
||||
pub total_difficulty: Difficulty,
|
||||
}
|
||||
|
||||
impl<Difficulty> PowAux<Difficulty> where
|
||||
impl<Difficulty> PowAux<Difficulty>
|
||||
where
|
||||
Difficulty: Decode + Default,
|
||||
{
|
||||
/// Read the auxiliary from client.
|
||||
@@ -193,11 +193,7 @@ pub trait PowAlgorithm<B: BlockT> {
|
||||
/// breaking algorithms will help to protect against selfish mining.
|
||||
///
|
||||
/// Returns if the new seal should be considered best block.
|
||||
fn break_tie(
|
||||
&self,
|
||||
_own_seal: &Seal,
|
||||
_new_seal: &Seal,
|
||||
) -> bool {
|
||||
fn break_tie(&self, _own_seal: &Seal, _new_seal: &Seal) -> bool {
|
||||
false
|
||||
}
|
||||
/// Verify that the difficulty is valid against given seal.
|
||||
@@ -238,7 +234,8 @@ impl<B: BlockT, I: Clone, C, S: Clone, Algorithm: Clone, CAW: Clone, CIDP> Clone
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, I, C, S, Algorithm, CAW, CIDP> PowBlockImport<B, I, C, S, Algorithm, CAW, CIDP> where
|
||||
impl<B, I, C, S, Algorithm, CAW, CIDP> PowBlockImport<B, I, C, S, Algorithm, CAW, CIDP>
|
||||
where
|
||||
B: BlockT,
|
||||
I: BlockImport<B, Transaction = sp_api::TransactionFor<C, B>> + Send + Sync,
|
||||
I::Error: Into<ConsensusError>,
|
||||
@@ -289,14 +286,15 @@ impl<B, I, C, S, Algorithm, CAW, CIDP> PowBlockImport<B, I, C, S, Algorithm, CAW
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let inherent_data = inherent_data_providers.create_inherent_data()
|
||||
let inherent_data = inherent_data_providers
|
||||
.create_inherent_data()
|
||||
.map_err(|e| Error::CreateInherents(e))?;
|
||||
|
||||
let inherent_res = self.client.runtime_api().check_inherents(
|
||||
&block_id,
|
||||
block,
|
||||
inherent_data,
|
||||
).map_err(|e| Error::Client(e.into()))?;
|
||||
let inherent_res = self
|
||||
.client
|
||||
.runtime_api()
|
||||
.check_inherents(&block_id, block, inherent_data)
|
||||
.map_err(|e| Error::Client(e.into()))?;
|
||||
|
||||
if !inherent_res.ok() {
|
||||
for (identifier, error) in inherent_res.into_errors() {
|
||||
@@ -358,20 +356,19 @@ where
|
||||
self.check_inherents(
|
||||
check_block.clone(),
|
||||
BlockId::Hash(parent_hash),
|
||||
self.create_inherent_data_providers.create_inherent_data_providers(
|
||||
parent_hash,
|
||||
(),
|
||||
).await?,
|
||||
).await?;
|
||||
self.create_inherent_data_providers
|
||||
.create_inherent_data_providers(parent_hash, ())
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
block.body = Some(check_block.deconstruct().1);
|
||||
}
|
||||
|
||||
let inner_seal = fetch_seal::<B>(block.post_digests.last(), block.header.hash())?;
|
||||
|
||||
let intermediate = block.take_intermediate::<PowIntermediate::<Algorithm::Difficulty>>(
|
||||
INTERMEDIATE_KEY
|
||||
)?;
|
||||
let intermediate =
|
||||
block.take_intermediate::<PowIntermediate<Algorithm::Difficulty>>(INTERMEDIATE_KEY)?;
|
||||
|
||||
let difficulty = match intermediate.difficulty {
|
||||
Some(difficulty) => difficulty,
|
||||
@@ -401,14 +398,12 @@ where
|
||||
Ordering::Less => false,
|
||||
Ordering::Greater => true,
|
||||
Ordering::Equal => {
|
||||
let best_inner_seal = fetch_seal::<B>(
|
||||
best_header.digest().logs.last(),
|
||||
best_hash,
|
||||
)?;
|
||||
let best_inner_seal =
|
||||
fetch_seal::<B>(best_header.digest().logs.last(), best_hash)?;
|
||||
|
||||
self.algorithm.break_tie(&best_inner_seal, &inner_seal)
|
||||
},
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -423,35 +418,33 @@ pub struct PowVerifier<B: BlockT, Algorithm> {
|
||||
}
|
||||
|
||||
impl<B: BlockT, Algorithm> PowVerifier<B, Algorithm> {
|
||||
pub fn new(
|
||||
algorithm: Algorithm,
|
||||
) -> Self {
|
||||
pub fn new(algorithm: Algorithm) -> Self {
|
||||
Self { algorithm, _marker: PhantomData }
|
||||
}
|
||||
|
||||
fn check_header(
|
||||
&self,
|
||||
mut header: B::Header,
|
||||
) -> Result<(B::Header, DigestItem<B::Hash>), Error<B>> where
|
||||
) -> Result<(B::Header, DigestItem<B::Hash>), Error<B>>
|
||||
where
|
||||
Algorithm: PowAlgorithm<B>,
|
||||
{
|
||||
let hash = header.hash();
|
||||
|
||||
let (seal, inner_seal) = match header.digest_mut().pop() {
|
||||
Some(DigestItem::Seal(id, seal)) => {
|
||||
Some(DigestItem::Seal(id, seal)) =>
|
||||
if id == POW_ENGINE_ID {
|
||||
(DigestItem::Seal(id, seal.clone()), seal)
|
||||
} else {
|
||||
return Err(Error::WrongEngine(id))
|
||||
}
|
||||
},
|
||||
},
|
||||
_ => return Err(Error::HeaderUnsealed(hash)),
|
||||
};
|
||||
|
||||
let pre_hash = header.hash();
|
||||
|
||||
if !self.algorithm.preliminary_verify(&pre_hash, &inner_seal)?.unwrap_or(true) {
|
||||
return Err(Error::FailedPreliminaryVerify);
|
||||
return Err(Error::FailedPreliminaryVerify)
|
||||
}
|
||||
|
||||
Ok((header, seal))
|
||||
@@ -459,7 +452,8 @@ impl<B: BlockT, Algorithm> PowVerifier<B, Algorithm> {
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm> where
|
||||
impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm>
|
||||
where
|
||||
Algorithm: PowAlgorithm<B> + Send + Sync,
|
||||
Algorithm::Difficulty: 'static + Send,
|
||||
{
|
||||
@@ -473,18 +467,15 @@ impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm> where
|
||||
let hash = header.hash();
|
||||
let (checked_header, seal) = self.check_header(header)?;
|
||||
|
||||
let intermediate = PowIntermediate::<Algorithm::Difficulty> {
|
||||
difficulty: None,
|
||||
};
|
||||
let intermediate = PowIntermediate::<Algorithm::Difficulty> { difficulty: None };
|
||||
|
||||
let mut import_block = BlockImportParams::new(origin, checked_header);
|
||||
import_block.post_digests.push(seal);
|
||||
import_block.body = body;
|
||||
import_block.justifications = justifications;
|
||||
import_block.intermediates.insert(
|
||||
Cow::from(INTERMEDIATE_KEY),
|
||||
Box::new(intermediate) as Box<_>
|
||||
);
|
||||
import_block
|
||||
.intermediates
|
||||
.insert(Cow::from(INTERMEDIATE_KEY), Box::new(intermediate) as Box<_>);
|
||||
import_block.post_hash = Some(hash);
|
||||
|
||||
Ok((import_block, None))
|
||||
@@ -501,10 +492,8 @@ pub fn import_queue<B, Transaction, Algorithm>(
|
||||
algorithm: Algorithm,
|
||||
spawner: &impl sp_core::traits::SpawnEssentialNamed,
|
||||
registry: Option<&Registry>,
|
||||
) -> Result<
|
||||
PowImportQueue<B, Transaction>,
|
||||
sp_consensus::Error
|
||||
> where
|
||||
) -> Result<PowImportQueue<B, Transaction>, sp_consensus::Error>
|
||||
where
|
||||
B: BlockT,
|
||||
Transaction: Send + Sync + 'static,
|
||||
Algorithm: PowAlgorithm<B> + Clone + Send + Sync + 'static,
|
||||
@@ -512,13 +501,7 @@ pub fn import_queue<B, Transaction, Algorithm>(
|
||||
{
|
||||
let verifier = PowVerifier::new(algorithm);
|
||||
|
||||
Ok(BasicQueue::new(
|
||||
verifier,
|
||||
block_import,
|
||||
justification_import,
|
||||
spawner,
|
||||
registry,
|
||||
))
|
||||
Ok(BasicQueue::new(verifier, block_import, justification_import, spawner, registry))
|
||||
}
|
||||
|
||||
/// Start the mining worker for PoW. This function provides the necessary helper functions that can
|
||||
@@ -573,13 +556,13 @@ where
|
||||
let task = async move {
|
||||
loop {
|
||||
if timer.next().await.is_none() {
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
if sync_oracle.is_major_syncing() {
|
||||
debug!(target: "pow", "Skipping proposal due to sync.");
|
||||
worker.lock().on_major_syncing();
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let best_header = match select_chain.best_chain().await {
|
||||
@@ -591,8 +574,8 @@ where
|
||||
Select best chain error: {:?}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
return
|
||||
},
|
||||
};
|
||||
let best_hash = best_header.hash();
|
||||
|
||||
@@ -603,11 +586,11 @@ where
|
||||
Probably a node update is required!",
|
||||
err,
|
||||
);
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if worker.lock().best_hash() == Some(best_hash) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// The worker is locked for the duration of the whole proposing period. Within this period,
|
||||
@@ -622,23 +605,25 @@ where
|
||||
Fetch difficulty failed: {:?}",
|
||||
err,
|
||||
);
|
||||
return;
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
let inherent_data_providers =
|
||||
match create_inherent_data_providers.create_inherent_data_providers(best_hash, ()).await {
|
||||
Ok(x) => x,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
target: "pow",
|
||||
"Unable to propose new block for authoring. \
|
||||
Creating inherent data providers failed: {:?}",
|
||||
err,
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
let inherent_data_providers = match create_inherent_data_providers
|
||||
.create_inherent_data_providers(best_hash, ())
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
target: "pow",
|
||||
"Unable to propose new block for authoring. \
|
||||
Creating inherent data providers failed: {:?}",
|
||||
err,
|
||||
);
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
let inherent_data = match inherent_data_providers.create_inherent_data() {
|
||||
Ok(r) => r,
|
||||
@@ -649,7 +634,7 @@ where
|
||||
Creating inherent data failed: {:?}",
|
||||
e,
|
||||
);
|
||||
return;
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
@@ -673,12 +658,10 @@ where
|
||||
},
|
||||
};
|
||||
|
||||
let proposal = match proposer.propose(
|
||||
inherent_data,
|
||||
inherent_digest,
|
||||
build_time.clone(),
|
||||
None,
|
||||
).await {
|
||||
let proposal = match proposer
|
||||
.propose(inherent_data, inherent_digest, build_time.clone(), None)
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
@@ -714,9 +697,8 @@ fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<Option<Vec<u8>>, Err
|
||||
for log in header.digest().logs() {
|
||||
trace!(target: "pow", "Checking log {:?}, looking for pre runtime digest", log);
|
||||
match (log, pre_digest.is_some()) {
|
||||
(DigestItem::PreRuntime(POW_ENGINE_ID, _), true) => {
|
||||
return Err(Error::MultiplePreRuntimeDigests)
|
||||
},
|
||||
(DigestItem::PreRuntime(POW_ENGINE_ID, _), true) =>
|
||||
return Err(Error::MultiplePreRuntimeDigests),
|
||||
(DigestItem::PreRuntime(POW_ENGINE_ID, v), false) => {
|
||||
pre_digest = Some(v.clone());
|
||||
},
|
||||
@@ -733,13 +715,12 @@ fn fetch_seal<B: BlockT>(
|
||||
hash: B::Hash,
|
||||
) -> Result<Vec<u8>, Error<B>> {
|
||||
match digest {
|
||||
Some(DigestItem::Seal(id, seal)) => {
|
||||
Some(DigestItem::Seal(id, seal)) =>
|
||||
if id == &POW_ENGINE_ID {
|
||||
Ok(seal.clone())
|
||||
} else {
|
||||
return Err(Error::<B>::WrongEngine(*id).into())
|
||||
}
|
||||
},
|
||||
},
|
||||
_ => return Err(Error::<B>::HeaderUnsealed(hash).into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,25 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{pin::Pin, time::Duration, collections::HashMap, borrow::Cow};
|
||||
use futures::{
|
||||
prelude::*,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
use log::*;
|
||||
use sc_client_api::ImportNotifications;
|
||||
use sp_consensus::{Proposal, BlockOrigin, BlockImportParams, StorageChanges,
|
||||
StateAction, import_queue::BoxBlockImport};
|
||||
use sp_consensus::{
|
||||
import_queue::BoxBlockImport, BlockImportParams, BlockOrigin, Proposal, StateAction,
|
||||
StorageChanges,
|
||||
};
|
||||
use sp_runtime::{
|
||||
generic::BlockId,
|
||||
traits::{Block as BlockT, Header as HeaderT},
|
||||
DigestItem,
|
||||
};
|
||||
use futures::{prelude::*, task::{Context, Poll}};
|
||||
use futures_timer::Delay;
|
||||
use log::*;
|
||||
use std::{borrow::Cow, collections::HashMap, pin::Pin, time::Duration};
|
||||
|
||||
use crate::{INTERMEDIATE_KEY, POW_ENGINE_ID, Seal, PowAlgorithm, PowIntermediate};
|
||||
use crate::{PowAlgorithm, PowIntermediate, Seal, INTERMEDIATE_KEY, POW_ENGINE_ID};
|
||||
|
||||
/// Mining metadata. This is the information needed to start an actual mining loop.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
@@ -49,7 +54,7 @@ pub struct MiningBuild<
|
||||
Block: BlockT,
|
||||
Algorithm: PowAlgorithm<Block>,
|
||||
C: sp_api::ProvideRuntimeApi<Block>,
|
||||
Proof
|
||||
Proof,
|
||||
> {
|
||||
/// Mining metadata.
|
||||
pub metadata: MiningMetadata<Block::Hash, Algorithm::Difficulty>,
|
||||
@@ -90,10 +95,7 @@ where
|
||||
self.build = None;
|
||||
}
|
||||
|
||||
pub(crate) fn on_build(
|
||||
&mut self,
|
||||
build: MiningBuild<Block, Algorithm, C, Proof>,
|
||||
) {
|
||||
pub(crate) fn on_build(&mut self, build: MiningBuild<Block, Algorithm, C, Proof>) {
|
||||
self.build = Some(build);
|
||||
}
|
||||
|
||||
@@ -137,23 +139,25 @@ where
|
||||
let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
|
||||
import_block.post_digests.push(seal);
|
||||
import_block.body = Some(body);
|
||||
import_block.state_action = StateAction::ApplyChanges(
|
||||
StorageChanges::Changes(build.proposal.storage_changes)
|
||||
);
|
||||
import_block.state_action =
|
||||
StateAction::ApplyChanges(StorageChanges::Changes(build.proposal.storage_changes));
|
||||
|
||||
let intermediate = PowIntermediate::<Algorithm::Difficulty> {
|
||||
difficulty: Some(build.metadata.difficulty),
|
||||
};
|
||||
|
||||
import_block.intermediates.insert(
|
||||
Cow::from(INTERMEDIATE_KEY),
|
||||
Box::new(intermediate) as Box<_>,
|
||||
);
|
||||
import_block
|
||||
.intermediates
|
||||
.insert(Cow::from(INTERMEDIATE_KEY), Box::new(intermediate) as Box<_>);
|
||||
|
||||
let header = import_block.post_header();
|
||||
match self.block_import.import_block(import_block, HashMap::default()).await {
|
||||
Ok(res) => {
|
||||
res.handle_justification(&header.hash(), *header.number(), &mut self.justification_sync_link);
|
||||
res.handle_justification(
|
||||
&header.hash(),
|
||||
*header.number(),
|
||||
&mut self.justification_sync_link,
|
||||
);
|
||||
|
||||
info!(
|
||||
target: "pow",
|
||||
@@ -190,15 +194,8 @@ pub struct UntilImportedOrTimeout<Block: BlockT> {
|
||||
|
||||
impl<Block: BlockT> UntilImportedOrTimeout<Block> {
|
||||
/// Create a new stream using the given import notification and timeout duration.
|
||||
pub fn new(
|
||||
import_notifications: ImportNotifications<Block>,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
import_notifications,
|
||||
timeout,
|
||||
inner_delay: None,
|
||||
}
|
||||
pub fn new(import_notifications: ImportNotifications<Block>, timeout: Duration) -> Self {
|
||||
Self { import_notifications, timeout, inner_delay: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
|
||||
//! Schema for slots in the aux-db.
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use codec::{Decode, Encode};
|
||||
use sc_client_api::backend::AuxStore;
|
||||
use sp_blockchain::{Result as ClientResult, Error as ClientError};
|
||||
use sp_blockchain::{Error as ClientError, Result as ClientResult};
|
||||
use sp_consensus_slots::{EquivocationProof, Slot};
|
||||
use sp_runtime::traits::Header;
|
||||
|
||||
@@ -33,17 +33,17 @@ pub const MAX_SLOT_CAPACITY: u64 = 1000;
|
||||
pub const PRUNING_BOUND: u64 = 2 * MAX_SLOT_CAPACITY;
|
||||
|
||||
fn load_decode<C, T>(backend: &C, key: &[u8]) -> ClientResult<Option<T>>
|
||||
where
|
||||
C: AuxStore,
|
||||
T: Decode,
|
||||
where
|
||||
C: AuxStore,
|
||||
T: Decode,
|
||||
{
|
||||
match backend.get_aux(key)? {
|
||||
None => Ok(None),
|
||||
Some(t) => T::decode(&mut &t[..])
|
||||
.map_err(
|
||||
|e| ClientError::Backend(format!("Slots DB is corrupted. Decode error: {}", e)),
|
||||
)
|
||||
.map(Some)
|
||||
.map_err(|e| {
|
||||
ClientError::Backend(format!("Slots DB is corrupted. Decode error: {}", e))
|
||||
})
|
||||
.map(Some),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,14 +57,14 @@ pub fn check_equivocation<C, H, P>(
|
||||
header: &H,
|
||||
signer: &P,
|
||||
) -> ClientResult<Option<EquivocationProof<H, P>>>
|
||||
where
|
||||
H: Header,
|
||||
C: AuxStore,
|
||||
P: Clone + Encode + Decode + PartialEq,
|
||||
where
|
||||
H: Header,
|
||||
C: AuxStore,
|
||||
P: Clone + Encode + Decode + PartialEq,
|
||||
{
|
||||
// We don't check equivocations for old headers out of our capacity.
|
||||
if slot_now.saturating_sub(*slot) > Slot::from(MAX_SLOT_CAPACITY) {
|
||||
return Ok(None);
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
// Key for this slot.
|
||||
@@ -72,17 +72,16 @@ pub fn check_equivocation<C, H, P>(
|
||||
slot.using_encoded(|s| curr_slot_key.extend(s));
|
||||
|
||||
// Get headers of this slot.
|
||||
let mut headers_with_sig = load_decode::<_, Vec<(H, P)>>(backend, &curr_slot_key[..])?
|
||||
.unwrap_or_else(Vec::new);
|
||||
let mut headers_with_sig =
|
||||
load_decode::<_, Vec<(H, P)>>(backend, &curr_slot_key[..])?.unwrap_or_else(Vec::new);
|
||||
|
||||
// Get first slot saved.
|
||||
let slot_header_start = SLOT_HEADER_START.to_vec();
|
||||
let first_saved_slot = load_decode::<_, Slot>(backend, &slot_header_start[..])?
|
||||
.unwrap_or(slot);
|
||||
let first_saved_slot = load_decode::<_, Slot>(backend, &slot_header_start[..])?.unwrap_or(slot);
|
||||
|
||||
if slot_now < first_saved_slot {
|
||||
// The code below assumes that slots will be visited sequentially.
|
||||
return Ok(None);
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
for (prev_header, prev_signer) in headers_with_sig.iter() {
|
||||
@@ -96,7 +95,7 @@ pub fn check_equivocation<C, H, P>(
|
||||
offender: signer.clone(),
|
||||
first_header: prev_header.clone(),
|
||||
second_header: header.clone(),
|
||||
}));
|
||||
}))
|
||||
} else {
|
||||
// We don't need to continue in case of duplicated header,
|
||||
// since it's already saved and a possible equivocation
|
||||
@@ -135,12 +134,11 @@ pub fn check_equivocation<C, H, P>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use sp_core::{sr25519, Pair};
|
||||
use sp_core::hash::H256;
|
||||
use sp_runtime::testing::{Header as HeaderTest, Digest as DigestTest};
|
||||
use sp_core::{hash::H256, sr25519, Pair};
|
||||
use sp_runtime::testing::{Digest as DigestTest, Header as HeaderTest};
|
||||
use substrate_test_runtime_client;
|
||||
|
||||
use super::{MAX_SLOT_CAPACITY, PRUNING_BOUND, check_equivocation};
|
||||
use super::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
|
||||
|
||||
fn create_header(number: u64) -> HeaderTest {
|
||||
// so that different headers for the same number get different hashes
|
||||
@@ -151,7 +149,7 @@ mod test {
|
||||
number,
|
||||
state_root: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
digest: DigestTest { logs: vec![], },
|
||||
digest: DigestTest { logs: vec![] },
|
||||
};
|
||||
|
||||
header
|
||||
@@ -171,79 +169,55 @@ mod test {
|
||||
let header6 = create_header(3); // @ slot 4
|
||||
|
||||
// It's ok to sign same headers.
|
||||
assert!(
|
||||
check_equivocation(
|
||||
&client,
|
||||
2.into(),
|
||||
2.into(),
|
||||
&header1,
|
||||
&public,
|
||||
).unwrap().is_none(),
|
||||
);
|
||||
assert!(check_equivocation(&client, 2.into(), 2.into(), &header1, &public,)
|
||||
.unwrap()
|
||||
.is_none(),);
|
||||
|
||||
assert!(
|
||||
check_equivocation(
|
||||
&client,
|
||||
3.into(),
|
||||
2.into(),
|
||||
&header1,
|
||||
&public,
|
||||
).unwrap().is_none(),
|
||||
);
|
||||
assert!(check_equivocation(&client, 3.into(), 2.into(), &header1, &public,)
|
||||
.unwrap()
|
||||
.is_none(),);
|
||||
|
||||
// But not two different headers at the same slot.
|
||||
assert!(
|
||||
check_equivocation(
|
||||
&client,
|
||||
4.into(),
|
||||
2.into(),
|
||||
&header2,
|
||||
&public,
|
||||
).unwrap().is_some(),
|
||||
);
|
||||
assert!(check_equivocation(&client, 4.into(), 2.into(), &header2, &public,)
|
||||
.unwrap()
|
||||
.is_some(),);
|
||||
|
||||
// Different slot is ok.
|
||||
assert!(
|
||||
check_equivocation(
|
||||
&client,
|
||||
5.into(),
|
||||
4.into(),
|
||||
&header3,
|
||||
&public,
|
||||
).unwrap().is_none(),
|
||||
);
|
||||
assert!(check_equivocation(&client, 5.into(), 4.into(), &header3, &public,)
|
||||
.unwrap()
|
||||
.is_none(),);
|
||||
|
||||
// Here we trigger pruning and save header 4.
|
||||
assert!(
|
||||
check_equivocation(
|
||||
&client,
|
||||
(PRUNING_BOUND + 2).into(),
|
||||
(MAX_SLOT_CAPACITY + 4).into(),
|
||||
&header4,
|
||||
&public,
|
||||
).unwrap().is_none(),
|
||||
);
|
||||
assert!(check_equivocation(
|
||||
&client,
|
||||
(PRUNING_BOUND + 2).into(),
|
||||
(MAX_SLOT_CAPACITY + 4).into(),
|
||||
&header4,
|
||||
&public,
|
||||
)
|
||||
.unwrap()
|
||||
.is_none(),);
|
||||
|
||||
// This fails because header 5 is an equivocation of header 4.
|
||||
assert!(
|
||||
check_equivocation(
|
||||
&client,
|
||||
(PRUNING_BOUND + 3).into(),
|
||||
(MAX_SLOT_CAPACITY + 4).into(),
|
||||
&header5,
|
||||
&public,
|
||||
).unwrap().is_some(),
|
||||
);
|
||||
assert!(check_equivocation(
|
||||
&client,
|
||||
(PRUNING_BOUND + 3).into(),
|
||||
(MAX_SLOT_CAPACITY + 4).into(),
|
||||
&header5,
|
||||
&public,
|
||||
)
|
||||
.unwrap()
|
||||
.is_some(),);
|
||||
|
||||
// This is ok because we pruned the corresponding header. Shows that we are pruning.
|
||||
assert!(
|
||||
check_equivocation(
|
||||
&client,
|
||||
(PRUNING_BOUND + 4).into(),
|
||||
4.into(),
|
||||
&header6,
|
||||
&public,
|
||||
).unwrap().is_none(),
|
||||
);
|
||||
assert!(check_equivocation(
|
||||
&client,
|
||||
(PRUNING_BOUND + 4).into(),
|
||||
4.into(),
|
||||
&header6,
|
||||
&public,
|
||||
)
|
||||
.unwrap()
|
||||
.is_none(),);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,19 +25,19 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod slots;
|
||||
mod aux_schema;
|
||||
mod slots;
|
||||
|
||||
pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
|
||||
pub use slots::SlotInfo;
|
||||
use slots::Slots;
|
||||
pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
|
||||
|
||||
use std::{fmt::Debug, ops::Deref, time::Duration};
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{future::Either, Future, TryFutureExt};
|
||||
use futures_timer::Delay;
|
||||
use log::{debug, error, info, warn};
|
||||
use sp_api::{ProvideRuntimeApi, ApiRef};
|
||||
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO, CONSENSUS_WARN};
|
||||
use sp_api::{ApiRef, ProvideRuntimeApi};
|
||||
use sp_arithmetic::traits::BaseArithmetic;
|
||||
use sp_consensus::{
|
||||
BlockImport, CanAuthorWith, JustificationSyncLink, Proposer, SelectChain, SlotData, SyncOracle,
|
||||
@@ -46,10 +46,10 @@ use sp_consensus_slots::Slot;
|
||||
use sp_inherents::CreateInherentDataProviders;
|
||||
use sp_runtime::{
|
||||
generic::BlockId,
|
||||
traits::{Block as BlockT, Header as HeaderT, HashFor, NumberFor}
|
||||
traits::{Block as BlockT, HashFor, Header as HeaderT, NumberFor},
|
||||
};
|
||||
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_WARN, CONSENSUS_INFO};
|
||||
use sp_timestamp::Timestamp;
|
||||
use std::{fmt::Debug, ops::Deref, time::Duration};
|
||||
|
||||
/// The changes that need to applied to the storage to create the state for a block.
|
||||
///
|
||||
@@ -76,10 +76,7 @@ pub trait SlotWorker<B: BlockT, Proof> {
|
||||
///
|
||||
/// Returns a future that resolves to a [`SlotResult`] iff a block was successfully built in
|
||||
/// the slot. Otherwise `None` is returned.
|
||||
async fn on_slot(
|
||||
&mut self,
|
||||
slot_info: SlotInfo<B>,
|
||||
) -> Option<SlotResult<B, Proof>>;
|
||||
async fn on_slot(&mut self, slot_info: SlotInfo<B>) -> Option<SlotResult<B, Proof>>;
|
||||
}
|
||||
|
||||
/// A skeleton implementation for `SlotWorker` which tries to claim a slot at
|
||||
@@ -89,7 +86,8 @@ pub trait SlotWorker<B: BlockT, Proof> {
|
||||
pub trait SimpleSlotWorker<B: BlockT> {
|
||||
/// A handle to a `BlockImport`.
|
||||
type BlockImport: BlockImport<B, Transaction = <Self::Proposer as Proposer<B>>::Transaction>
|
||||
+ Send + 'static;
|
||||
+ Send
|
||||
+ 'static;
|
||||
|
||||
/// A handle to a `SyncOracle`.
|
||||
type SyncOracle: SyncOracle;
|
||||
@@ -100,7 +98,9 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
|
||||
/// The type of future resolving to the proposer.
|
||||
type CreateProposer: Future<Output = Result<Self::Proposer, sp_consensus::Error>>
|
||||
+ Send + Unpin + 'static;
|
||||
+ Send
|
||||
+ Unpin
|
||||
+ 'static;
|
||||
|
||||
/// The type of proposer to use to build blocks.
|
||||
type Proposer: Proposer<B> + Send;
|
||||
@@ -139,12 +139,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
|
||||
/// Notifies the given slot. Similar to `claim_slot`, but will be called no matter whether we
|
||||
/// need to author blocks or not.
|
||||
fn notify_slot(
|
||||
&self,
|
||||
_header: &B::Header,
|
||||
_slot: Slot,
|
||||
_epoch_data: &Self::EpochData,
|
||||
) {}
|
||||
fn notify_slot(&self, _header: &B::Header, _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(
|
||||
@@ -154,18 +149,24 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
) -> Vec<sp_runtime::DigestItem<B::Hash>>;
|
||||
|
||||
/// Returns a function which produces a `BlockImportParams`.
|
||||
fn block_import_params(&self) -> Box<
|
||||
fn block_import_params(
|
||||
&self,
|
||||
) -> Box<
|
||||
dyn Fn(
|
||||
B::Header,
|
||||
&B::Hash,
|
||||
Vec<B::Extrinsic>,
|
||||
StorageChanges<<Self::BlockImport as BlockImport<B>>::Transaction, B>,
|
||||
Self::Claim,
|
||||
Self::EpochData,
|
||||
) -> Result<
|
||||
sp_consensus::BlockImportParams<B, <Self::BlockImport as BlockImport<B>>::Transaction>,
|
||||
sp_consensus::Error
|
||||
> + Send + 'static
|
||||
B::Header,
|
||||
&B::Hash,
|
||||
Vec<B::Extrinsic>,
|
||||
StorageChanges<<Self::BlockImport as BlockImport<B>>::Transaction, B>,
|
||||
Self::Claim,
|
||||
Self::EpochData,
|
||||
) -> Result<
|
||||
sp_consensus::BlockImportParams<
|
||||
B,
|
||||
<Self::BlockImport as BlockImport<B>>::Transaction,
|
||||
>,
|
||||
sp_consensus::Error,
|
||||
> + Send
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
/// Whether to force authoring if offline.
|
||||
@@ -194,10 +195,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
fn telemetry(&self) -> Option<TelemetryHandle>;
|
||||
|
||||
/// Remaining duration for proposing.
|
||||
fn proposing_remaining_duration(
|
||||
&self,
|
||||
slot_info: &SlotInfo<B>,
|
||||
) -> Duration;
|
||||
fn proposing_remaining_duration(&self, slot_info: &SlotInfo<B>) -> Duration;
|
||||
|
||||
/// Implements [`SlotWorker::on_slot`].
|
||||
async fn on_slot(
|
||||
@@ -213,8 +211,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
let proposing_remaining = if proposing_remaining_duration == Duration::default() {
|
||||
debug!(
|
||||
target: logging_target,
|
||||
"Skipping proposal slot {} since there's no time left to propose",
|
||||
slot,
|
||||
"Skipping proposal slot {} since there's no time left to propose", slot,
|
||||
);
|
||||
|
||||
return None
|
||||
@@ -240,8 +237,8 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
"err" => ?err,
|
||||
);
|
||||
|
||||
return None;
|
||||
}
|
||||
return None
|
||||
},
|
||||
};
|
||||
|
||||
self.notify_slot(&slot_info.chain_head, slot, &epoch_data);
|
||||
@@ -260,13 +257,13 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
"authorities_len" => authorities_len,
|
||||
);
|
||||
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
let claim = self.claim_slot(&slot_info.chain_head, slot, &epoch_data)?;
|
||||
|
||||
if self.should_backoff(slot, &slot_info.chain_head) {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
debug!(
|
||||
@@ -289,9 +286,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
target: logging_target,
|
||||
"Unable to author block in slot {:?}: {:?}",
|
||||
slot,
|
||||
err,
|
||||
"Unable to author block in slot {:?}: {:?}", slot, err,
|
||||
);
|
||||
|
||||
telemetry!(
|
||||
@@ -303,7 +298,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
);
|
||||
|
||||
return None
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let logs = self.pre_digest_data(slot, &claim);
|
||||
@@ -311,34 +306,29 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
// deadline our production to 98% of the total time left for proposing. As we deadline
|
||||
// the proposing below to the same total time left, the 2% margin should be enough for
|
||||
// the result to be returned.
|
||||
let proposing = proposer.propose(
|
||||
slot_info.inherent_data,
|
||||
sp_runtime::generic::Digest {
|
||||
logs,
|
||||
},
|
||||
proposing_remaining_duration.mul_f32(0.98),
|
||||
None,
|
||||
).map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)));
|
||||
let proposing = proposer
|
||||
.propose(
|
||||
slot_info.inherent_data,
|
||||
sp_runtime::generic::Digest { logs },
|
||||
proposing_remaining_duration.mul_f32(0.98),
|
||||
None,
|
||||
)
|
||||
.map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)));
|
||||
|
||||
let proposal = match futures::future::select(proposing, proposing_remaining).await {
|
||||
Either::Left((Ok(p), _)) => p,
|
||||
Either::Left((Err(err), _)) => {
|
||||
warn!(
|
||||
target: logging_target,
|
||||
"Proposing failed: {:?}",
|
||||
err,
|
||||
);
|
||||
warn!(target: logging_target, "Proposing failed: {:?}", err,);
|
||||
|
||||
return None
|
||||
},
|
||||
Either::Right(_) => {
|
||||
info!(
|
||||
target: logging_target,
|
||||
"⌛️ Discarding proposal for slot {}; block production took too long",
|
||||
slot,
|
||||
"⌛️ 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")]
|
||||
#[cfg(build_type = "debug")]
|
||||
info!(
|
||||
target: logging_target,
|
||||
"👉 Recompile your node in `--release` mode to mitigate this problem.",
|
||||
@@ -373,14 +363,10 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
) {
|
||||
Ok(bi) => bi,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
target: logging_target,
|
||||
"Failed to create block import params: {:?}",
|
||||
err,
|
||||
);
|
||||
warn!(target: logging_target, "Failed to create block import params: {:?}", err,);
|
||||
|
||||
return None
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
info!(
|
||||
@@ -401,17 +387,14 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
);
|
||||
|
||||
let header = block_import_params.post_header();
|
||||
match block_import
|
||||
.import_block(block_import_params, Default::default())
|
||||
.await
|
||||
{
|
||||
match block_import.import_block(block_import_params, Default::default()).await {
|
||||
Ok(res) => {
|
||||
res.handle_justification(
|
||||
&header.hash(),
|
||||
*header.number(),
|
||||
self.justification_sync_link(),
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
target: logging_target,
|
||||
@@ -425,18 +408,17 @@ pub trait SimpleSlotWorker<B: BlockT> {
|
||||
"hash" => ?parent_hash,
|
||||
"err" => ?err,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Some(SlotResult {
|
||||
block: B::new(header, body),
|
||||
storage_proof,
|
||||
})
|
||||
Some(SlotResult { block: B::new(header, body), storage_proof })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B: BlockT, T: SimpleSlotWorker<B> + Send> SlotWorker<B, <T::Proposer as Proposer<B>>::Proof> for T {
|
||||
impl<B: BlockT, T: SimpleSlotWorker<B> + Send> SlotWorker<B, <T::Proposer as Proposer<B>>::Proof>
|
||||
for T
|
||||
{
|
||||
async fn on_slot(
|
||||
&mut self,
|
||||
slot_info: SlotInfo<B>,
|
||||
@@ -496,8 +478,7 @@ pub async fn start_slot_worker<B, C, W, T, SO, CIDP, CAW, Proof>(
|
||||
mut sync_oracle: SO,
|
||||
create_inherent_data_providers: CIDP,
|
||||
can_author_with: CAW,
|
||||
)
|
||||
where
|
||||
) where
|
||||
B: BlockT,
|
||||
C: SelectChain<B>,
|
||||
W: SlotWorker<B, Proof>,
|
||||
@@ -509,28 +490,25 @@ where
|
||||
{
|
||||
let SlotDuration(slot_duration) = slot_duration;
|
||||
|
||||
let mut slots = Slots::new(
|
||||
slot_duration.slot_duration(),
|
||||
create_inherent_data_providers,
|
||||
client,
|
||||
);
|
||||
let mut slots =
|
||||
Slots::new(slot_duration.slot_duration(), create_inherent_data_providers, client);
|
||||
|
||||
loop {
|
||||
let slot_info = match slots.next_slot().await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
warn!(target: "slots", "Error while polling for next slot: {:?}", e);
|
||||
return;
|
||||
}
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
if sync_oracle.is_major_syncing() {
|
||||
debug!(target: "slots", "Skipping proposal slot due to sync.");
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
if let Err(err) = can_author_with
|
||||
.can_author_with(&BlockId::Hash(slot_info.chain_head.hash()))
|
||||
if let Err(err) =
|
||||
can_author_with.can_author_with(&BlockId::Hash(slot_info.chain_head.hash()))
|
||||
{
|
||||
warn!(
|
||||
target: "slots",
|
||||
@@ -559,7 +537,10 @@ pub enum CheckedHeader<H, S> {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error<T> where T: Debug {
|
||||
pub enum Error<T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
#[error("Slot duration is invalid: {0:?}")]
|
||||
SlotDurationInvalid(SlotDuration<T>),
|
||||
}
|
||||
@@ -591,25 +572,23 @@ impl<T: Clone + Send + Sync + 'static> SlotDuration<T> {
|
||||
///
|
||||
/// `slot_key` is marked as `'static`, as it should really be a
|
||||
/// compile-time constant.
|
||||
pub fn get_or_compute<B: BlockT, C, CB>(client: &C, cb: CB) -> sp_blockchain::Result<Self> where
|
||||
pub fn get_or_compute<B: BlockT, C, CB>(client: &C, cb: CB) -> sp_blockchain::Result<Self>
|
||||
where
|
||||
C: sc_client_api::backend::AuxStore + sc_client_api::UsageProvider<B>,
|
||||
C: ProvideRuntimeApi<B>,
|
||||
CB: FnOnce(ApiRef<C::Api>, &BlockId<B>) -> sp_blockchain::Result<T>,
|
||||
T: SlotData + Encode + Decode + Debug,
|
||||
{
|
||||
let slot_duration = match client.get_aux(T::SLOT_KEY)? {
|
||||
Some(v) => <T as codec::Decode>::decode(&mut &v[..])
|
||||
.map(SlotDuration)
|
||||
.map_err(|_| {
|
||||
sp_blockchain::Error::Backend({
|
||||
error!(target: "slots", "slot duration kept in invalid format");
|
||||
"slot duration kept in invalid format".to_string()
|
||||
})
|
||||
}),
|
||||
Some(v) => <T as codec::Decode>::decode(&mut &v[..]).map(SlotDuration).map_err(|_| {
|
||||
sp_blockchain::Error::Backend({
|
||||
error!(target: "slots", "slot duration kept in invalid format");
|
||||
"slot duration kept in invalid format".to_string()
|
||||
})
|
||||
}),
|
||||
None => {
|
||||
let best_hash = client.usage_info().chain.best_hash;
|
||||
let slot_duration =
|
||||
cb(client.runtime_api(), &BlockId::hash(best_hash))?;
|
||||
let slot_duration = cb(client.runtime_api(), &BlockId::hash(best_hash))?;
|
||||
|
||||
info!(
|
||||
"⏱ Loaded block-time = {:?} from block {:?}",
|
||||
@@ -621,11 +600,13 @@ impl<T: Clone + Send + Sync + 'static> SlotDuration<T> {
|
||||
.using_encoded(|s| client.insert_aux(&[(T::SLOT_KEY, &s[..])], &[]))?;
|
||||
|
||||
Ok(SlotDuration(slot_duration))
|
||||
}
|
||||
},
|
||||
}?;
|
||||
|
||||
if slot_duration.slot_duration() == Default::default() {
|
||||
return Err(sp_blockchain::Error::Application(Box::new(Error::SlotDurationInvalid(slot_duration))))
|
||||
return Err(sp_blockchain::Error::Application(Box::new(Error::SlotDurationInvalid(
|
||||
slot_duration,
|
||||
))))
|
||||
}
|
||||
|
||||
Ok(slot_duration)
|
||||
@@ -687,9 +668,7 @@ pub fn proposing_remaining_duration<Block: BlockT>(
|
||||
) -> Duration {
|
||||
use sp_runtime::traits::Zero;
|
||||
|
||||
let proposing_duration = slot_info
|
||||
.duration
|
||||
.mul_f32(block_proposal_slot_portion.get());
|
||||
let proposing_duration = slot_info.duration.mul_f32(block_proposal_slot_portion.get());
|
||||
|
||||
let slot_remaining = slot_info
|
||||
.ends_at
|
||||
@@ -700,7 +679,7 @@ pub fn proposing_remaining_duration<Block: BlockT>(
|
||||
|
||||
// If parent is genesis block, we don't require any lenience factor.
|
||||
if slot_info.chain_head.number().is_zero() {
|
||||
return proposing_duration;
|
||||
return proposing_duration
|
||||
}
|
||||
|
||||
let parent_slot = match parent_slot {
|
||||
@@ -723,9 +702,7 @@ pub fn proposing_remaining_duration<Block: BlockT>(
|
||||
if let Some(ref max_block_proposal_slot_portion) = max_block_proposal_slot_portion {
|
||||
std::cmp::min(
|
||||
lenient_proposing_duration,
|
||||
slot_info
|
||||
.duration
|
||||
.mul_f32(max_block_proposal_slot_portion.get()),
|
||||
slot_info.duration.mul_f32(max_block_proposal_slot_portion.get()),
|
||||
)
|
||||
} else {
|
||||
lenient_proposing_duration
|
||||
@@ -853,7 +830,7 @@ impl<N: BaseArithmetic> Default for BackoffAuthoringOnFinalizedHeadLagging<N> {
|
||||
|
||||
impl<N> BackoffAuthoringBlocksStrategy<N> for BackoffAuthoringOnFinalizedHeadLagging<N>
|
||||
where
|
||||
N: BaseArithmetic + Copy
|
||||
N: BaseArithmetic + Copy,
|
||||
{
|
||||
fn should_backoff(
|
||||
&self,
|
||||
@@ -865,12 +842,12 @@ where
|
||||
) -> bool {
|
||||
// This should not happen, but we want to keep the previous behaviour if it does.
|
||||
if slot_now <= chain_head_slot {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
let unfinalized_block_length = chain_head_number - finalized_number;
|
||||
let interval = unfinalized_block_length.saturating_sub(self.unfinalized_slack)
|
||||
/ self.authoring_bias;
|
||||
let interval =
|
||||
unfinalized_block_length.saturating_sub(self.unfinalized_slack) / self.authoring_bias;
|
||||
let interval = interval.min(self.max_interval);
|
||||
|
||||
// We're doing arithmetic between block and slot numbers.
|
||||
@@ -906,9 +883,9 @@ impl<N> BackoffAuthoringBlocksStrategy<N> for () {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use sp_api::NumberFor;
|
||||
use std::time::{Duration, Instant};
|
||||
use substrate_test_runtime_client::runtime::{Block, Header};
|
||||
use sp_api::NumberFor;
|
||||
|
||||
const SLOT_DURATION: Duration = Duration::from_millis(6000);
|
||||
|
||||
@@ -945,10 +922,7 @@ mod test {
|
||||
}
|
||||
|
||||
// but we cap it to a maximum of 20 slots
|
||||
assert_eq!(
|
||||
super::slot_lenience_linear(1u64.into(), &slot(23)),
|
||||
Some(SLOT_DURATION * 20),
|
||||
);
|
||||
assert_eq!(super::slot_lenience_linear(1u64.into(), &slot(23)), Some(SLOT_DURATION * 20),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1041,7 +1015,15 @@ mod test {
|
||||
let slot_now = 2;
|
||||
|
||||
let should_backoff: Vec<bool> = (slot_now..1000)
|
||||
.map(|s| strategy.should_backoff(head_number, head_slot.into(), finalized_number, s.into(), "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
|
||||
@@ -1105,7 +1087,15 @@ 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.into(), finalized_number, s.into(), "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
|
||||
@@ -1123,11 +1113,7 @@ mod test {
|
||||
};
|
||||
|
||||
let finalized_number = 2;
|
||||
let mut head_state = HeadState {
|
||||
head_number: 4,
|
||||
head_slot: 10,
|
||||
slot_now: 11,
|
||||
};
|
||||
let mut head_state = HeadState { head_number: 4, head_slot: 10, slot_now: 11 };
|
||||
|
||||
let should_backoff = |head_state: &HeadState| -> bool {
|
||||
<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
|
||||
@@ -1155,32 +1141,27 @@ mod test {
|
||||
// Gradually start to backoff more and more frequently
|
||||
let expected = [
|
||||
false, false, false, false, false, // no effect
|
||||
true, false,
|
||||
true, false, // 1:1
|
||||
true, true, false,
|
||||
true, true, false, // 2:1
|
||||
true, true, true, false,
|
||||
true, true, true, false, // 3:1
|
||||
true, true, true, true, false,
|
||||
true, true, true, true, false, // 4:1
|
||||
true, true, true, true, true, false,
|
||||
true, true, true, true, true, false, // 5:1
|
||||
true, true, true, true, true, true, false,
|
||||
true, true, true, true, true, true, false, // 6:1
|
||||
true, true, true, true, true, true, true, false,
|
||||
true, true, true, true, true, true, true, false, // 7:1
|
||||
true, true, true, true, true, true, true, true, false,
|
||||
true, true, true, true, true, true, true, true, false, // 8:1
|
||||
true, true, true, true, true, true, true, true, true, false,
|
||||
true, true, true, true, true, true, true, true, true, false, // 9:1
|
||||
true, true, true, true, true, true, true, true, true, true, false,
|
||||
true, true, true, true, true, true, true, true, true, true, false, // 10:1
|
||||
true, true, true, true, true, true, true, true, true, true, true, false,
|
||||
true, true, true, true, true, true, true, true, true, true, true, false, // 11:1
|
||||
true, true, true, true, true, true, true, true, true, true, true, true, false,
|
||||
true, true, true, true, true, true, true, true, true, true, true, true, false, // 12:1
|
||||
true, false, true, false, // 1:1
|
||||
true, true, false, true, true, false, // 2:1
|
||||
true, true, true, false, true, true, true, false, // 3:1
|
||||
true, true, true, true, false, true, true, true, true, false, // 4:1
|
||||
true, true, true, true, true, false, true, true, true, true, true, false, // 5:1
|
||||
true, true, true, true, true, true, false, true, true, true, true, true, true,
|
||||
false, // 6:1
|
||||
true, true, true, true, true, true, true, false, true, true, true, true, true, true,
|
||||
true, false, // 7:1
|
||||
true, true, true, true, true, true, true, true, false, true, true, true, true, true,
|
||||
true, true, true, false, // 8:1
|
||||
true, true, true, true, true, true, true, true, true, false, true, true, true, true,
|
||||
true, true, true, true, true, false, // 9:1
|
||||
true, true, true, true, true, true, true, true, true, true, false, true, true, true,
|
||||
true, true, true, true, true, true, true, false, // 10:1
|
||||
true, true, true, true, true, true, true, true, true, true, true, false, true, true,
|
||||
true, true, true, true, true, true, true, true, true, false, // 11:1
|
||||
true, true, true, true, true, true, true, true, true, true, true, true, false, true,
|
||||
true, true, true, true, true, true, true, true, true, true, true, false, // 12:1
|
||||
true, true, true, true,
|
||||
];
|
||||
];
|
||||
|
||||
assert_eq!(backoff.as_slice(), &expected[..]);
|
||||
}
|
||||
@@ -1195,11 +1176,7 @@ mod test {
|
||||
|
||||
let finalized_number = 2;
|
||||
let starting_slot = 11;
|
||||
let mut head_state = HeadState {
|
||||
head_number: 4,
|
||||
head_slot: 10,
|
||||
slot_now: starting_slot,
|
||||
};
|
||||
let mut head_state = HeadState { head_number: 4, head_slot: 10, slot_now: starting_slot };
|
||||
|
||||
let should_backoff = |head_state: &HeadState| -> bool {
|
||||
<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
|
||||
@@ -1240,30 +1217,22 @@ mod test {
|
||||
assert_eq!(last_slot - last_two_claimed.next().unwrap(), 92);
|
||||
assert_eq!(last_slot - last_two_claimed.next().unwrap(), 92 + expected_distance);
|
||||
|
||||
let intervals: Vec<_> = slots_claimed
|
||||
.windows(2)
|
||||
.map(|x| x[1] - x[0])
|
||||
.collect();
|
||||
let intervals: Vec<_> = slots_claimed.windows(2).map(|x| x[1] - x[0]).collect();
|
||||
|
||||
// The key thing is that the distance between claimed slots is capped to `max_interval + 1`
|
||||
// assert_eq!(max_observed_interval, Some(&expected_distance));
|
||||
assert_eq!(intervals.iter().max(), Some(&expected_distance));
|
||||
|
||||
// But lets assert all distances, which we expect to grow linearly until `max_interval + 1`
|
||||
let expected_intervals: Vec<_> = (0..497)
|
||||
.map(|i| (i/2).max(1).min(expected_distance) )
|
||||
.collect();
|
||||
let expected_intervals: Vec<_> =
|
||||
(0..497).map(|i| (i / 2).max(1).min(expected_distance)).collect();
|
||||
|
||||
assert_eq!(intervals, expected_intervals);
|
||||
}
|
||||
|
||||
fn run_until_max_interval(param: BackoffAuthoringOnFinalizedHeadLagging<u64>) -> (u64, u64) {
|
||||
let finalized_number = 0;
|
||||
let mut head_state = HeadState {
|
||||
head_number: 0,
|
||||
head_slot: 0,
|
||||
slot_now: 1,
|
||||
};
|
||||
let mut head_state = HeadState { head_number: 0, head_slot: 0, slot_now: 1 };
|
||||
|
||||
let should_backoff = |head_state: &HeadState| -> bool {
|
||||
<dyn BackoffAuthoringBlocksStrategy<NumberFor<Block>>>::should_backoff(
|
||||
@@ -1277,8 +1246,8 @@ mod test {
|
||||
};
|
||||
|
||||
// Number of blocks until we reach the max interval
|
||||
let block_for_max_interval
|
||||
= param.max_interval * param.authoring_bias + param.unfinalized_slack;
|
||||
let block_for_max_interval =
|
||||
param.max_interval * param.authoring_bias + param.unfinalized_slack;
|
||||
|
||||
while head_state.head_number < block_for_max_interval {
|
||||
if should_backoff(&head_state) {
|
||||
@@ -1294,15 +1263,15 @@ mod test {
|
||||
}
|
||||
|
||||
// Denoting
|
||||
// C: unfinalized_slack
|
||||
// M: authoring_bias
|
||||
// X: max_interval
|
||||
// C: unfinalized_slack
|
||||
// M: authoring_bias
|
||||
// X: max_interval
|
||||
// then the number of slots to reach the max interval can be computed from
|
||||
// (start_slot + C) + M * sum(n, 1, X)
|
||||
// (start_slot + C) + M * sum(n, 1, X)
|
||||
// or
|
||||
// (start_slot + C) + M * X*(X+1)/2
|
||||
// (start_slot + C) + M * X*(X+1)/2
|
||||
fn expected_time_to_reach_max_interval(
|
||||
param: &BackoffAuthoringOnFinalizedHeadLagging<u64>
|
||||
param: &BackoffAuthoringOnFinalizedHeadLagging<u64>,
|
||||
) -> (u64, u64) {
|
||||
let c = param.unfinalized_slack;
|
||||
let m = param.authoring_bias;
|
||||
|
||||
@@ -20,23 +20,21 @@
|
||||
//!
|
||||
//! This is used instead of `futures_timer::Interval` because it was unreliable.
|
||||
|
||||
use super::{Slot, InherentDataProviderExt};
|
||||
use super::{InherentDataProviderExt, Slot};
|
||||
use sp_consensus::{Error, SelectChain};
|
||||
use sp_inherents::{InherentData, CreateInherentDataProviders, InherentDataProvider};
|
||||
use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider};
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
use futures_timer::Delay;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Returns current duration since unix epoch.
|
||||
pub fn duration_now() -> Duration {
|
||||
use std::time::SystemTime;
|
||||
let now = SystemTime::now();
|
||||
now.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| panic!(
|
||||
"Current time {:?} is before unix epoch. Something is wrong: {:?}",
|
||||
now,
|
||||
e,
|
||||
))
|
||||
now.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| {
|
||||
panic!("Current time {:?} is before unix epoch. Something is wrong: {:?}", now, e,)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the duration until the next slot from now.
|
||||
@@ -104,11 +102,7 @@ pub(crate) struct Slots<Block, C, IDP> {
|
||||
|
||||
impl<Block, C, IDP> Slots<Block, C, IDP> {
|
||||
/// Create a new `Slots` stream.
|
||||
pub fn new(
|
||||
slot_duration: Duration,
|
||||
create_inherent_data_providers: IDP,
|
||||
client: C,
|
||||
) -> Self {
|
||||
pub fn new(slot_duration: Duration, create_inherent_data_providers: IDP, client: C) -> Self {
|
||||
Slots {
|
||||
last_slot: 0.into(),
|
||||
slot_duration,
|
||||
@@ -135,7 +129,7 @@ where
|
||||
// schedule wait.
|
||||
let wait_dur = time_until_next_slot(self.slot_duration);
|
||||
Some(Delay::new(wait_dur))
|
||||
}
|
||||
},
|
||||
Some(d) => Some(d),
|
||||
};
|
||||
|
||||
@@ -161,11 +155,12 @@ where
|
||||
);
|
||||
// Let's try at the next slot..
|
||||
self.inner_delay.take();
|
||||
continue;
|
||||
}
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
let inherent_data_providers = self.create_inherent_data_providers
|
||||
let inherent_data_providers = self
|
||||
.create_inherent_data_providers
|
||||
.create_inherent_data_providers(chain_head.hash(), ())
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
//! Uncles functionality for Substrate.
|
||||
|
||||
use sc_client_api::ProvideUncles;
|
||||
use sp_runtime::{traits::Block as BlockT, generic::BlockId};
|
||||
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error<B: BlockT> {
|
||||
@@ -34,7 +34,8 @@ const MAX_UNCLE_GENERATIONS: u32 = 8;
|
||||
pub fn create_uncles_inherent_data_provider<B, C>(
|
||||
client: &C,
|
||||
parent: B::Hash,
|
||||
) -> Result<sp_authorship::InherentDataProvider<B::Header>, sc_client_api::blockchain::Error> where
|
||||
) -> Result<sp_authorship::InherentDataProvider<B::Header>, sc_client_api::blockchain::Error>
|
||||
where
|
||||
B: BlockT,
|
||||
C: ProvideUncles<B>,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user