Make BlockImport and Verifier async (#8472)

* Make grandpa work

* Introduce `SharedData`

* Add test and fix bugs

* Switch to `SharedData`

* Make grandpa tests working

* More Babe work

* Make it async

* Fix fix

* Use `async_trait` in sc-consensus-slots

This makes the code a little bit easier to read and also expresses that
there can always only be one call at a time to `on_slot`.

* Make grandpa tests compile

* More Babe tests work

* Fix network test

* Start fixing service test

* Finish service-test

* Fix sc-consensus-aura

* Fix fix fix

* More fixes

* Make everything compile *yeah*

* Fix build when we have Rust 1.51

* Update client/consensus/common/src/shared_data.rs

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

* Update client/consensus/common/src/shared_data.rs

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

* Update client/consensus/common/src/shared_data.rs

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

* Update client/consensus/common/src/shared_data.rs

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

* Update client/consensus/common/src/shared_data.rs

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

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

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

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

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

* Fix warning

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
Bastian Köcher
2021-03-30 11:19:49 +02:00
committed by GitHub
parent 2998b42311
commit 217c4be226
65 changed files with 1241 additions and 715 deletions
+2 -1
View File
@@ -26,7 +26,6 @@ futures = "0.3.9"
futures-timer = "3.0.1"
sp-inherents = { version = "3.0.0", path = "../../../primitives/inherents" }
log = "0.4.8"
parking_lot = "0.11.1"
sp-core = { version = "3.0.0", path = "../../../primitives/core" }
sp-blockchain = { version = "3.0.0", path = "../../../primitives/blockchain" }
sp-io = { version = "3.0.0", path = "../../../primitives/io" }
@@ -38,6 +37,7 @@ sp-timestamp = { version = "3.0.0", path = "../../../primitives/timestamp" }
sp-keystore = { version = "0.9.0", path = "../../../primitives/keystore" }
sc-telemetry = { version = "3.0.0", path = "../../telemetry" }
prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.9.0"}
async-trait = "0.1.42"
# We enable it only for web-wasm check
# See https://docs.rs/getrandom/0.2.1/getrandom/#webassembly-support
getrandom = { version = "0.2", features = ["js"], optional = true }
@@ -52,3 +52,4 @@ sc-network-test = { version = "0.8.0", path = "../../network/test" }
sc-service = { version = "0.9.0", default-features = false, path = "../../service" }
substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" }
tempfile = "3.1.0"
parking_lot = "0.11.1"
@@ -220,6 +220,7 @@ impl<C, P, CAW> AuraVerifier<C, P, CAW> where
}
}
#[async_trait::async_trait]
impl<B: BlockT, C, P, CAW> Verifier<B> for AuraVerifier<C, P, CAW> where
C: ProvideRuntimeApi<B> +
Send +
@@ -234,7 +235,7 @@ impl<B: BlockT, C, P, CAW> Verifier<B> for AuraVerifier<C, P, CAW> where
P::Signature: Encode + Decode,
CAW: CanAuthorWith<B> + Send + Sync + 'static,
{
fn verify(
async fn verify(
&mut self,
origin: BlockOrigin,
header: B::Header,
@@ -405,6 +406,7 @@ impl<Block: BlockT, C, I: BlockImport<Block>, P> AuraBlockImport<Block, C, I, P>
}
}
#[async_trait::async_trait]
impl<Block: BlockT, C, I, P> BlockImport<Block> for AuraBlockImport<Block, C, I, P> where
I: BlockImport<Block, Transaction = sp_api::TransactionFor<C, Block>> + Send + Sync,
I::Error: Into<ConsensusError>,
@@ -412,18 +414,19 @@ impl<Block: BlockT, C, I, P> BlockImport<Block> for AuraBlockImport<Block, C, I,
P: Pair + Send + Sync + 'static,
P::Public: Clone + Eq + Send + Sync + Hash + Debug + Encode + Decode,
P::Signature: Encode + Decode,
sp_api::TransactionFor<C, Block>: Send + 'static,
{
type Error = ConsensusError;
type Transaction = sp_api::TransactionFor<C, Block>;
fn check_block(
async fn check_block(
&mut self,
block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
self.inner.check_block(block).map_err(Into::into)
self.inner.check_block(block).await.map_err(Into::into)
}
fn import_block(
async fn import_block(
&mut self,
block: BlockImportParams<Block, Self::Transaction>,
new_cache: HashMap<CacheKeyId, Vec<u8>>,
@@ -453,7 +456,7 @@ impl<Block: BlockT, C, I, P> BlockImport<Block> for AuraBlockImport<Block, C, I,
);
}
self.inner.import_block(block, new_cache).map_err(Into::into)
self.inner.import_block(block, new_cache).await.map_err(Into::into)
}
}
+18 -5
View File
@@ -580,6 +580,7 @@ mod tests {
use super::*;
use sp_consensus::{
NoNetwork as DummyOracle, Proposal, AlwaysCanAuthor, DisableProofRecording,
import_queue::BoxJustificationImport,
};
use sc_network_test::{Block as TestBlock, *};
use sp_runtime::traits::{Block as BlockT, DigestFor};
@@ -641,13 +642,17 @@ mod tests {
const SLOT_DURATION: u64 = 1000;
type AuraVerifier = import_queue::AuraVerifier<PeersFullClient, AuthorityPair, AlwaysCanAuthor>;
type AuraPeer = Peer<(), PeersClient>;
pub struct AuraTestNet {
peers: Vec<Peer<()>>,
peers: Vec<AuraPeer>,
}
impl TestNetFactory for AuraTestNet {
type Verifier = import_queue::AuraVerifier<PeersFullClient, AuthorityPair, AlwaysCanAuthor>;
type Verifier = AuraVerifier;
type PeerData = ();
type BlockImport = PeersClient;
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
@@ -681,14 +686,22 @@ mod tests {
}
}
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData> {
fn make_block_import(&self, client: PeersClient) -> (
BlockImportAdapter<Self::BlockImport>,
Option<BoxJustificationImport<Block>>,
Self::PeerData,
) {
(client.as_block_import(), None, ())
}
fn peer(&mut self, i: usize) -> &mut AuraPeer {
&mut self.peers[i]
}
fn peers(&self) -> &Vec<Peer<Self::PeerData>> {
fn peers(&self) -> &Vec<AuraPeer> {
&self.peers
}
fn mut_peers<F: FnOnce(&mut Vec<Peer<Self::PeerData>>)>(&mut self, closure: F) {
fn mut_peers<F: FnOnce(&mut Vec<AuraPeer>)>(&mut self, closure: F) {
closure(&mut self.peers);
}
}
@@ -53,6 +53,7 @@ merlin = "2.0"
pdqselect = "0.1.0"
derive_more = "0.99.2"
retain_mut = "0.1.2"
async-trait = "0.1.42"
[dev-dependencies]
sp-keyring = { version = "3.0.0", path = "../../../primitives/keyring" }
@@ -217,7 +217,7 @@ fn epoch_data<B, C, SC>(
SC: SelectChain<B>,
{
let parent = select_chain.best_chain()?;
epoch_changes.lock().epoch_data_for_child_of(
epoch_changes.shared_data().epoch_data_for_child_of(
descendent_query(&**client),
&parent.hash(),
parent.number().clone(),
@@ -18,8 +18,6 @@
//! Schema for BABE epoch changes in the aux-db.
use std::sync::Arc;
use parking_lot::Mutex;
use log::info;
use codec::{Decode, Encode};
@@ -79,18 +77,19 @@ pub fn load_epoch_changes<Block: BlockT, B: AuxStore>(
},
};
let epoch_changes = Arc::new(Mutex::new(maybe_epoch_changes.unwrap_or_else(|| {
info!(target: "babe",
"👶 Creating empty BABE epoch changes on what appears to be first startup."
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
// tree wasn't rebalanced initially it's useful to temporarily leave it here
// to avoid having to wait until an import for rebalancing.
epoch_changes.lock().rebalance();
epoch_changes.shared_data().rebalance();
Ok(epoch_changes)
}
@@ -189,7 +188,7 @@ mod test {
).unwrap();
assert!(
epoch_changes.lock()
epoch_changes.shared_data()
.tree()
.iter()
.map(|(_, _, epoch)| epoch.clone())
@@ -201,7 +200,7 @@ mod test {
); // PersistedEpochHeader does not implement Debug, so we use assert! directly.
write_epoch_changes::<TestBlock, _, _>(
&epoch_changes.lock(),
&epoch_changes.shared_data(),
|values| {
client.insert_aux(values, &[]).unwrap();
},
+184 -173
View File
@@ -76,8 +76,8 @@ pub use sp_consensus_babe::{
pub use sp_consensus::SyncOracle;
pub use sc_consensus_slots::SlotProportion;
use std::{
collections::HashMap, sync::Arc, u64, pin::Pin, time::{Instant, Duration},
any::Any, borrow::Cow, convert::TryInto,
collections::HashMap, sync::Arc, u64, pin::Pin, borrow::Cow, convert::TryInto,
time::{Duration, Instant},
};
use sp_consensus::{ImportResult, CanAuthorWith, import_queue::BoxJustificationImport};
use sp_core::crypto::Public;
@@ -502,7 +502,7 @@ async fn answer_requests<B: BlockT, C>(
match request {
BabeRequest::EpochForChild(parent_hash, parent_number, slot_number, response) => {
let lookup = || {
let epoch_changes = epoch_changes.lock();
let epoch_changes = epoch_changes.shared_data();
let epoch_descriptor = epoch_changes.epoch_descriptor_for_child_of(
descendent_query(&*client),
&parent_hash,
@@ -656,7 +656,7 @@ where
parent: &B::Header,
slot: Slot,
) -> Result<Self::EpochData, ConsensusError> {
self.epoch_changes.lock().epoch_descriptor_for_child_of(
self.epoch_changes.shared_data().epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent.hash(),
parent.number().clone(),
@@ -667,7 +667,8 @@ where
}
fn authorities_len(&self, epoch_descriptor: &Self::EpochData) -> Option<usize> {
self.epoch_changes.lock()
self.epoch_changes
.shared_data()
.viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot))
.map(|epoch| epoch.as_ref().authorities.len())
}
@@ -681,7 +682,7 @@ where
debug!(target: "babe", "Attempting to claim slot {}", slot);
let s = authorship::claim_slot(
slot,
self.epoch_changes.lock().viable_epoch(
self.epoch_changes.shared_data().viable_epoch(
&epoch_descriptor,
|slot| Epoch::genesis(&self.config, slot)
)?.as_ref(),
@@ -768,7 +769,7 @@ where
import_block.storage_changes = Some(storage_changes);
import_block.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(BabeIntermediate::<B> { epoch_descriptor }) as Box<dyn Any>,
Box::new(BabeIntermediate::<B> { epoch_descriptor }) as Box<_>,
);
Ok(import_block)
@@ -1083,6 +1084,7 @@ where
}
}
#[async_trait::async_trait]
impl<Block, Client, SelectChain, CAW> Verifier<Block>
for BabeVerifier<Block, Client, SelectChain, CAW>
where
@@ -1093,7 +1095,7 @@ where
SelectChain: sp_consensus::SelectChain<Block>,
CAW: CanAuthorWith<Block> + Send + Sync,
{
fn verify(
async fn verify(
&mut self,
origin: BlockOrigin,
header: Block::Header,
@@ -1125,7 +1127,7 @@ where
.map_err(Error::<Block>::FetchParentHeader)?;
let pre_digest = find_pre_digest::<Block>(&header)?;
let epoch_changes = self.epoch_changes.lock();
let epoch_changes = self.epoch_changes.shared_data();
let epoch_descriptor = epoch_changes.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
&parent_hash,
@@ -1189,7 +1191,8 @@ where
self.telemetry;
CONSENSUS_TRACE;
"babe.checked_and_importing";
"pre_header" => ?pre_header);
"pre_header" => ?pre_header,
);
let mut import_block = BlockImportParams::new(origin, pre_header);
import_block.post_digests.push(verified_info.seal);
@@ -1197,7 +1200,7 @@ where
import_block.justifications = justifications;
import_block.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<dyn Any>,
Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<_>,
);
import_block.post_hash = Some(hash);
@@ -1275,6 +1278,7 @@ impl<Block: BlockT, Client, I> BabeBlockImport<Block, Client, I> {
}
}
#[async_trait::async_trait]
impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client, Inner> where
Block: BlockT,
Inner: BlockImport<Block, Transaction = sp_api::TransactionFor<Client, Block>> + Send + Sync,
@@ -1286,7 +1290,7 @@ impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client,
type Error = ConsensusError;
type Transaction = sp_api::TransactionFor<Client, Block>;
fn import_block(
async fn import_block(
&mut self,
mut block: BlockImportParams<Block, Self::Transaction>,
new_cache: HashMap<CacheKeyId, Vec<u8>>,
@@ -1328,202 +1332,209 @@ impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client,
);
}
let mut epoch_changes = self.epoch_changes.lock();
// check if there's any epoch change expected to happen at this slot.
// `epoch` is the epoch to verify the block under, and `first_in_epoch` is true
// if this is the first block in its chain for that epoch.
//
// also provides the total weight of the chain, including the imported block.
let (epoch_descriptor, first_in_epoch, parent_weight) = {
let parent_weight = if *parent_header.number() == Zero::zero() {
0
} else {
aux_schema::load_block_weight(&*self.client, parent_hash)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?
.ok_or_else(|| ConsensusError::ClientImport(
babe_err(Error::<Block>::ParentBlockNoAssociatedWeight(hash)).into()
))?
};
let intermediate = block.take_intermediate::<BabeIntermediate<Block>>(
INTERMEDIATE_KEY
)?;
let epoch_descriptor = intermediate.epoch_descriptor;
let first_in_epoch = parent_slot < epoch_descriptor.start_slot();
(epoch_descriptor, first_in_epoch, parent_weight)
};
let total_weight = parent_weight + pre_digest.added_weight();
// search for this all the time so we can reject unexpected announcements.
let next_epoch_digest = find_next_epoch_digest::<Block>(&block.header)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
let next_config_digest = find_next_config_digest::<Block>(&block.header)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
match (first_in_epoch, next_epoch_digest.is_some(), next_config_digest.is_some()) {
(true, true, _) => {},
(false, false, false) => {},
(false, false, true) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::UnexpectedConfigChange).into(),
)
)
},
(true, false, _) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::ExpectedEpochChange(hash, slot)).into(),
)
)
},
(false, true, _) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::UnexpectedEpochChange).into(),
)
)
},
}
// if there's a pending epoch we'll save the previous epoch changes here
// this way we can revert it if there's any error
let mut old_epoch_changes = None;
let info = self.client.info();
// Use an extra scope to make the compiler happy, because otherwise he complains about the
// mutex, even if we dropped it...
let mut epoch_changes = {
let mut epoch_changes = self.epoch_changes.shared_data_locked();
if let Some(next_epoch_descriptor) = next_epoch_digest {
old_epoch_changes = Some(epoch_changes.clone());
// check if there's any epoch change expected to happen at this slot.
// `epoch` is the epoch to verify the block under, and `first_in_epoch` is true
// if this is the first block in its chain for that epoch.
//
// also provides the total weight of the chain, including the imported block.
let (epoch_descriptor, first_in_epoch, parent_weight) = {
let parent_weight = if *parent_header.number() == Zero::zero() {
0
} else {
aux_schema::load_block_weight(&*self.client, parent_hash)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?
.ok_or_else(|| ConsensusError::ClientImport(
babe_err(Error::<Block>::ParentBlockNoAssociatedWeight(hash)).into()
))?
};
let viable_epoch = epoch_changes.viable_epoch(
&epoch_descriptor,
|slot| Epoch::genesis(&self.config, slot)
).ok_or_else(|| {
ConsensusError::ClientImport(Error::<Block>::FetchEpoch(parent_hash).into())
})?;
let epoch_config = next_config_digest.map(Into::into).unwrap_or_else(
|| viable_epoch.as_ref().config.clone()
);
// restrict info logging during initial sync to avoid spam
let log_level = if block.origin == BlockOrigin::NetworkInitialSync {
log::Level::Debug
} else {
log::Level::Info
};
log!(target: "babe",
log_level,
"👶 New epoch {} launching at block {} (block slot {} >= start slot {}).",
viable_epoch.as_ref().epoch_index,
hash,
slot,
viable_epoch.as_ref().start_slot,
);
let next_epoch = viable_epoch.increment((next_epoch_descriptor, epoch_config));
log!(target: "babe",
log_level,
"👶 Next epoch starts at slot {}",
next_epoch.as_ref().start_slot,
);
// prune the tree of epochs not part of the finalized chain or
// that are not live anymore, and then track the given epoch change
// in the tree.
// NOTE: it is important that these operations are done in this
// order, otherwise if pruning after import the `is_descendent_of`
// used by pruning may not know about the block that is being
// imported.
let prune_and_import = || {
prune_finalized(
self.client.clone(),
&mut epoch_changes,
let intermediate = block.take_intermediate::<BabeIntermediate<Block>>(
INTERMEDIATE_KEY
)?;
epoch_changes.import(
descendent_query(&*self.client),
hash,
number,
*block.header.parent_hash(),
next_epoch,
).map_err(|e| ConsensusError::ClientImport(format!("{:?}", e)))?;
Ok(())
let epoch_descriptor = intermediate.epoch_descriptor;
let first_in_epoch = parent_slot < epoch_descriptor.start_slot();
(epoch_descriptor, first_in_epoch, parent_weight)
};
if let Err(e) = prune_and_import() {
debug!(target: "babe", "Failed to launch next epoch: {:?}", e);
*epoch_changes = old_epoch_changes.expect("set `Some` above and not taken; qed");
return Err(e);
let total_weight = parent_weight + pre_digest.added_weight();
// search for this all the time so we can reject unexpected announcements.
let next_epoch_digest = find_next_epoch_digest::<Block>(&block.header)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
let next_config_digest = find_next_config_digest::<Block>(&block.header)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
match (first_in_epoch, next_epoch_digest.is_some(), next_config_digest.is_some()) {
(true, true, _) => {},
(false, false, false) => {},
(false, false, true) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::UnexpectedConfigChange).into(),
)
)
},
(true, false, _) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::ExpectedEpochChange(hash, slot)).into(),
)
)
},
(false, true, _) => {
return Err(
ConsensusError::ClientImport(
babe_err(Error::<Block>::UnexpectedEpochChange).into(),
)
)
},
}
crate::aux_schema::write_epoch_changes::<Block, _, _>(
&*epoch_changes,
|insert| block.auxiliary.extend(
insert.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec())))
)
let info = self.client.info();
if let Some(next_epoch_descriptor) = next_epoch_digest {
old_epoch_changes = Some((*epoch_changes).clone());
let viable_epoch = epoch_changes.viable_epoch(
&epoch_descriptor,
|slot| Epoch::genesis(&self.config, slot)
).ok_or_else(|| {
ConsensusError::ClientImport(Error::<Block>::FetchEpoch(parent_hash).into())
})?;
let epoch_config = next_config_digest.map(Into::into).unwrap_or_else(
|| viable_epoch.as_ref().config.clone()
);
// restrict info logging during initial sync to avoid spam
let log_level = if block.origin == BlockOrigin::NetworkInitialSync {
log::Level::Debug
} else {
log::Level::Info
};
log!(target: "babe",
log_level,
"👶 New epoch {} launching at block {} (block slot {} >= start slot {}).",
viable_epoch.as_ref().epoch_index,
hash,
slot,
viable_epoch.as_ref().start_slot,
);
let next_epoch = viable_epoch.increment((next_epoch_descriptor, epoch_config));
log!(target: "babe",
log_level,
"👶 Next epoch starts at slot {}",
next_epoch.as_ref().start_slot,
);
// prune the tree of epochs not part of the finalized chain or
// that are not live anymore, and then track the given epoch change
// in the tree.
// NOTE: it is important that these operations are done in this
// order, otherwise if pruning after import the `is_descendent_of`
// used by pruning may not know about the block that is being
// imported.
let prune_and_import = || {
prune_finalized(
self.client.clone(),
&mut epoch_changes,
)?;
epoch_changes.import(
descendent_query(&*self.client),
hash,
number,
*block.header.parent_hash(),
next_epoch,
).map_err(|e| ConsensusError::ClientImport(format!("{:?}", e)))?;
Ok(())
};
if let Err(e) = prune_and_import() {
debug!(target: "babe", "Failed to launch next epoch: {:?}", e);
*epoch_changes = old_epoch_changes.expect("set `Some` above and not taken; qed");
return Err(e);
}
crate::aux_schema::write_epoch_changes::<Block, _, _>(
&*epoch_changes,
|insert| block.auxiliary.extend(
insert.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec())))
)
);
}
aux_schema::write_block_weight(
hash,
total_weight,
|values| block.auxiliary.extend(
values.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec())))
),
);
}
aux_schema::write_block_weight(
hash,
total_weight,
|values| block.auxiliary.extend(
values.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec())))
),
);
// The fork choice rule is that we pick the heaviest chain (i.e.
// more primary blocks), if there's a tie we go with the longest
// chain.
block.fork_choice = {
let (last_best, last_best_number) = (info.best_hash, info.best_number);
// The fork choice rule is that we pick the heaviest chain (i.e.
// more primary blocks), if there's a tie we go with the longest
// chain.
block.fork_choice = {
let (last_best, last_best_number) = (info.best_hash, info.best_number);
let last_best_weight = if &last_best == block.header.parent_hash() {
// the parent=genesis case is already covered for loading parent weight,
// so we don't need to cover again here.
parent_weight
} else {
aux_schema::load_block_weight(&*self.client, last_best)
.map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?
let last_best_weight = if &last_best == block.header.parent_hash() {
// the parent=genesis case is already covered for loading parent weight,
// so we don't need to cover again here.
parent_weight
} else {
aux_schema::load_block_weight(&*self.client, last_best)
.map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?
.ok_or_else(
|| ConsensusError::ChainLookup("No block weight for parent header.".to_string())
)?
};
Some(ForkChoiceStrategy::Custom(if total_weight > last_best_weight {
true
} else if total_weight == last_best_weight {
number > last_best_number
} else {
false
}))
};
Some(ForkChoiceStrategy::Custom(if total_weight > last_best_weight {
true
} else if total_weight == last_best_weight {
number > last_best_number
} else {
false
}))
// Release the mutex, but it stays locked
epoch_changes.release_mutex()
};
let import_result = self.inner.import_block(block, new_cache);
let import_result = self.inner.import_block(block, new_cache).await;
// revert to the original epoch changes in case there's an error
// importing the block
if import_result.is_err() {
if let Some(old_epoch_changes) = old_epoch_changes {
*epoch_changes = old_epoch_changes;
*epoch_changes.upgrade() = old_epoch_changes;
}
}
import_result.map_err(Into::into)
}
fn check_block(
async fn check_block(
&mut self,
block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
self.inner.check_block(block).map_err(Into::into)
self.inner.check_block(block).await.map_err(Into::into)
}
}
@@ -1583,7 +1594,7 @@ pub fn block_import<Client, Block: BlockT, I>(
// startup rather than waiting until importing the next epoch change block.
prune_finalized(
client.clone(),
&mut epoch_changes.lock(),
&mut epoch_changes.shared_data(),
)?;
let import = BabeBlockImport::new(
+42 -29
View File
@@ -47,6 +47,7 @@ use rand_chacha::{
};
use sc_keystore::LocalKeystore;
use sp_application_crypto::key_types::BABE;
use futures::executor::block_on;
type Item = DigestItem<Hash>;
@@ -67,6 +68,9 @@ enum Stage {
type Mutator = Arc<dyn Fn(&mut TestHeader, Stage) + Send + Sync>;
type BabeBlockImport =
PanickingBlockImport<crate::BabeBlockImport<TestBlock, TestClient, Arc<TestClient>>>;
#[derive(Clone)]
struct DummyFactory {
client: Arc<TestClient>,
@@ -134,7 +138,7 @@ impl DummyProposer {
// figure out if we should add a consensus digest, since the test runtime
// doesn't.
let epoch_changes = self.factory.epoch_changes.lock();
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,
@@ -188,30 +192,37 @@ thread_local! {
}
#[derive(Clone)]
struct PanickingBlockImport<B>(B);
pub struct PanickingBlockImport<B>(B);
impl<B: BlockImport<TestBlock>> BlockImport<TestBlock> for PanickingBlockImport<B> {
#[async_trait::async_trait]
impl<B: BlockImport<TestBlock>> BlockImport<TestBlock> for PanickingBlockImport<B>
where
B::Transaction: Send,
B: Send,
{
type Error = B::Error;
type Transaction = B::Transaction;
fn import_block(
async fn import_block(
&mut self,
block: BlockImportParams<TestBlock, Self::Transaction>,
new_cache: HashMap<CacheKeyId, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
Ok(self.0.import_block(block, new_cache).expect("importing block failed"))
Ok(self.0.import_block(block, new_cache).await.expect("importing block failed"))
}
fn check_block(
async fn check_block(
&mut self,
block: BlockCheckParams<TestBlock>,
) -> Result<ImportResult, Self::Error> {
Ok(self.0.check_block(block).expect("checking block failed"))
Ok(self.0.check_block(block).await.expect("checking block failed"))
}
}
type BabePeer = Peer<Option<PeerData>, BabeBlockImport>;
pub struct BabeTestNet {
peers: Vec<Peer<Option<PeerData>>>,
peers: Vec<BabePeer>,
}
type TestHeader = <TestBlock as BlockT>::Header;
@@ -227,11 +238,12 @@ pub struct TestVerifier {
mutator: Mutator,
}
#[async_trait::async_trait]
impl Verifier<TestBlock> for TestVerifier {
/// Verify the given data and return the BlockImportParams and an optional
/// new set of validators to import. If not, err with an Error-Message
/// presented to the User in the logs.
fn verify(
async fn verify(
&mut self,
origin: BlockOrigin,
mut header: TestHeader,
@@ -240,7 +252,7 @@ impl Verifier<TestBlock> for TestVerifier {
) -> Result<(BlockImportParams<TestBlock, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
// apply post-sealing mutations (i.e. stripping seal, if desired).
(self.mutator)(&mut header, Stage::PostSeal);
self.inner.verify(origin, header, justifications, body)
self.inner.verify(dbg!(origin), header, justifications, body).await
}
}
@@ -255,6 +267,7 @@ pub struct PeerData {
impl TestNetFactory for BabeTestNet {
type Verifier = TestVerifier;
type PeerData = Option<PeerData>;
type BlockImport = BabeBlockImport;
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
@@ -264,9 +277,9 @@ impl TestNetFactory for BabeTestNet {
}
}
fn make_block_import<Transaction>(&self, client: PeersClient)
fn make_block_import(&self, client: PeersClient)
-> (
BlockImportAdapter<Transaction>,
BlockImportAdapter<Self::BlockImport>,
Option<BoxJustificationImport<Block>>,
Option<PeerData>,
)
@@ -287,7 +300,7 @@ impl TestNetFactory for BabeTestNet {
Some(Box::new(block_import.clone()) as BoxBlockImport<_, _>)
);
(
BlockImportAdapter::new_full(block_import),
BlockImportAdapter::new(block_import),
None,
Some(PeerData { link, inherent_data_providers, block_import: data_block_import }),
)
@@ -326,17 +339,17 @@ impl TestNetFactory for BabeTestNet {
}
}
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData> {
fn peer(&mut self, i: usize) -> &mut BabePeer {
trace!(target: "babe", "Retrieving a peer");
&mut self.peers[i]
}
fn peers(&self) -> &Vec<Peer<Self::PeerData>> {
fn peers(&self) -> &Vec<BabePeer> {
trace!(target: "babe", "Retrieving peers");
&self.peers
}
fn mut_peers<F: FnOnce(&mut Vec<Peer<Self::PeerData>>)>(
fn mut_peers<F: FnOnce(&mut Vec<BabePeer>)>(
&mut self,
closure: F,
) {
@@ -436,7 +449,7 @@ fn run_one_test(
telemetry: None,
}).expect("Starts babe"));
}
futures::executor::block_on(future::select(
block_on(future::select(
futures::future::poll_fn(move |cx| {
let mut net = net.lock();
net.poll(cx);
@@ -567,7 +580,7 @@ fn can_author_block() {
}
// Propose and import a new BABE block on top of the given parent.
fn propose_and_import_block<Transaction>(
fn propose_and_import_block<Transaction: Send + 'static>(
parent: &TestHeader,
slot: Option<Slot>,
proposer_factory: &mut DummyFactory,
@@ -595,7 +608,7 @@ fn propose_and_import_block<Transaction>(
let mut block = futures::executor::block_on(proposer.propose_with(pre_digest)).unwrap().block;
let epoch_descriptor = proposer_factory.epoch_changes.lock().epoch_descriptor_for_child_of(
let epoch_descriptor = proposer_factory.epoch_changes.shared_data().epoch_descriptor_for_child_of(
descendent_query(&*proposer_factory.client),
&parent_hash,
*parent.number(),
@@ -623,10 +636,10 @@ fn propose_and_import_block<Transaction>(
import.body = Some(block.extrinsics);
import.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(BabeIntermediate::<TestBlock> { epoch_descriptor }) as Box<dyn Any>,
Box::new(BabeIntermediate::<TestBlock> { epoch_descriptor }) as Box<_>,
);
import.fork_choice = Some(ForkChoiceStrategy::LongestChain);
let import_result = block_import.import_block(import, Default::default()).unwrap();
let import_result = block_on(block_import.import_block(import, Default::default())).unwrap();
match import_result {
ImportResult::Imported(_) => {},
@@ -664,7 +677,7 @@ fn importing_block_one_sets_genesis_epoch() {
let genesis_epoch = Epoch::genesis(&data.link.config, 999.into());
let epoch_changes = data.link.epoch_changes.lock();
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,
@@ -739,13 +752,13 @@ fn importing_epoch_change_block_prunes_tree() {
// We should be tracking a total of 9 epochs in the fork tree
assert_eq!(
epoch_changes.lock().tree().iter().count(),
epoch_changes.shared_data().tree().iter().count(),
9,
);
// And only one root
assert_eq!(
epoch_changes.lock().tree().roots().count(),
epoch_changes.shared_data().tree().roots().count(),
1,
);
@@ -756,16 +769,16 @@ fn importing_epoch_change_block_prunes_tree() {
// at this point no hashes from the first fork must exist on the tree
assert!(
!epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_1.contains(h)),
!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.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_2.contains(h))
epoch_changes.shared_data().tree().iter().map(|(h, _, _)| h).any(|h| fork_2.contains(h))
);
assert!(
epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_3.contains(h)),
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
@@ -774,12 +787,12 @@ fn importing_epoch_change_block_prunes_tree() {
// at this point no hashes from the second fork must exist on the tree
assert!(
!epoch_changes.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_2.contains(h)),
!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.lock().tree().iter().map(|(h, _, _)| h).any(|h| fork_3.contains(h)),
epoch_changes.shared_data().tree().iter().map(|(h, _, _)| h).any(|h| fork_3.contains(h)),
);
}
@@ -17,3 +17,4 @@ sc-client-api = { version = "3.0.0", path = "../../api" }
sp-blockchain = { version = "3.0.0", path = "../../../primitives/blockchain" }
sp-runtime = { version = "3.0.0", path = "../../../primitives/runtime" }
sp-consensus = { version = "0.9.0", path = "../../../primitives/consensus/common" }
parking_lot = "0.11.1"
@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Collection of common consensus specific implementations
mod longest_chain;
pub mod shared_data;
pub use longest_chain::LongestChain;
@@ -0,0 +1,271 @@
// This file is part of Substrate.
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Provides a generic wrapper around shared data. See [`SharedData`] for more information.
use std::sync::Arc;
use parking_lot::{Mutex, MappedMutexGuard, Condvar, MutexGuard};
/// Created by [`SharedDataLocked::release_mutex`].
///
/// As long as the object isn't dropped, the shared data is locked. It is advised to drop this
/// object when the shared data doesn't need to be locked anymore. To get access to the shared data
/// [`Self::upgrade`] is provided.
#[must_use = "Shared data will be unlocked on drop!"]
pub struct SharedDataLockedUpgradable<T> {
shared_data: SharedData<T>,
}
impl<T> SharedDataLockedUpgradable<T> {
/// Upgrade to a *real* mutex guard that will give access to the inner data.
///
/// Every call to this function will reaquire the mutex again.
pub fn upgrade(&mut self) -> MappedMutexGuard<T> {
MutexGuard::map(self.shared_data.inner.lock(), |i| &mut i.shared_data)
}
}
impl<T> Drop for SharedDataLockedUpgradable<T> {
fn drop(&mut self) {
let mut inner = self.shared_data.inner.lock();
// It should not be locked anymore
inner.locked = false;
// Notify all waiting threads.
self.shared_data.cond_var.notify_all();
}
}
/// Created by [`SharedData::shared_data_locked`].
///
/// As long as this object isn't dropped, the shared data is held in a mutex guard and the shared
/// data is tagged as locked. Access to the shared data is provided through [`Deref`] and
/// [`DerefMut`]. The trick is to use [`Self::release_mutex`] to release the mutex, but still keep
/// the shared data locked. This means every other thread trying to access the shared data in this
/// time will need to wait until this lock is freed.
///
/// If this object is dropped without calling [`Self::release_mutex`], the lock will be dropped
/// immediately.
#[must_use = "Shared data will be unlocked on drop!"]
pub struct SharedDataLocked<'a, T> {
/// The current active mutex guard holding the inner data.
inner: MutexGuard<'a, SharedDataInner<T>>,
/// The [`SharedData`] instance that created this instance.
///
/// This instance is only taken on drop or when calling [`Self::release_mutex`].
shared_data: Option<SharedData<T>>,
}
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"),
}
}
}
impl<'a, T> Drop for SharedDataLocked<'a, T> {
fn drop(&mut self) {
if let Some(shared_data) = self.shared_data.take() {
// If the `shared_data` is still set, it means [`Self::release_mutex`] wasn't
// called and the lock should be released.
self.inner.locked = false;
// Notify all waiting threads about the released lock.
shared_data.cond_var.notify_all();
}
}
}
impl<'a, T> std::ops::Deref for SharedDataLocked<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner.shared_data
}
}
impl<'a, T> std::ops::DerefMut for SharedDataLocked<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner.shared_data
}
}
/// Holds the shared data and if the shared data is currently locked.
///
/// For more information see [`SharedData`].
struct SharedDataInner<T> {
/// The actual shared data that is protected here against concurrent access.
shared_data: T,
/// Is `shared_data` currently locked and can not be accessed?
locked: bool,
}
/// Some shared data that provides support for locking this shared data for some time.
///
/// When working with consensus engines there is often data that needs to be shared between multiple
/// parts of the system, like block production and block import. This struct provides an abstraction
/// for this shared data in a generic way.
///
/// The pain point when sharing this data is often the usage of mutex guards in an async context as
/// this doesn't work for most of them as these guards don't implement `Send`. This abstraction
/// provides a way to lock the shared data, while not having the mutex locked. So, the data stays
/// locked and we are still able to hold this lock over an `await` call.
///
/// # Example
///
/// ```
///# use sc_consensus::shared_data::SharedData;
///
/// let shared_data = SharedData::new(String::from("hello world"));
///
/// let lock = shared_data.shared_data_locked();
///
/// let shared_data2 = shared_data.clone();
/// let join_handle1 = std::thread::spawn(move || {
/// // This will need to wait for the outer lock to be released before it can access the data.
/// shared_data2.shared_data().push_str("1");
/// });
///
/// assert_eq!(*lock, "hello world");
///
/// // Let us release the mutex, but we still keep it locked.
/// // Now we could call `await` for example.
/// let mut lock = lock.release_mutex();
///
/// let shared_data2 = shared_data.clone();
/// let join_handle2 = std::thread::spawn(move || {
/// shared_data2.shared_data().push_str("2");
/// });
///
/// // We still have the lock and can upgrade it to access the data.
/// assert_eq!(*lock.upgrade(), "hello world");
/// lock.upgrade().push_str("3");
///
/// drop(lock);
/// join_handle1.join().unwrap();
/// join_handle2.join().unwrap();
///
/// let data = shared_data.shared_data();
/// // As we don't know the order of the threads, we need to check for both combinations
/// assert!(*data == "hello world321" || *data == "hello world312");
/// ```
pub struct SharedData<T> {
inner: Arc<Mutex<SharedDataInner<T>>>,
cond_var: Arc<Condvar>,
}
impl<T> Clone for SharedData<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
cond_var: self.cond_var.clone(),
}
}
}
impl<T> SharedData<T> {
/// Create a new instance of [`SharedData`] to share the given `shared_data`.
pub fn new(shared_data: T) -> Self {
Self {
inner: Arc::new(Mutex::new(SharedDataInner { shared_data, locked: false })),
cond_var: Default::default(),
}
}
/// Acquire access to the shared data.
///
/// This will give mutable access to the shared data. After the returned mutex guard is dropped,
/// the shared data is accessible by other threads. So, this function should be used when
/// reading/writing of the shared data in a local context is required.
///
/// When requiring to lock shared data for some longer time, even with temporarily releasing the
/// lock, [`Self::shared_data_locked`] should be used.
pub fn shared_data(&self) -> MappedMutexGuard<T> {
let mut guard = self.inner.lock();
while guard.locked {
self.cond_var.wait(&mut guard);
}
debug_assert!(!guard.locked);
MutexGuard::map(guard, |i| &mut i.shared_data)
}
/// Acquire access to the shared data and lock it.
///
/// This will give mutable access to the shared data. The returned [`SharedDataLocked`]
/// provides the function [`SharedDataLocked::release_mutex`] to release the mutex, but
/// keeping the data locked. This is useful in async contexts for example where the data needs to
/// be locked, but a mutex guard can not be held.
///
/// For an example see [`SharedData`].
pub fn shared_data_locked(&self) -> SharedDataLocked<T> {
let mut guard = self.inner.lock();
while guard.locked {
self.cond_var.wait(&mut guard);
}
debug_assert!(!guard.locked);
guard.locked = true;
SharedDataLocked {
inner: guard,
shared_data: Some(self.clone()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shared_data_locking_works() {
const THREADS: u32 = 100;
let shared_data = SharedData::new(0u32);
let lock = shared_data.shared_data_locked();
for i in 0..THREADS {
let data = shared_data.clone();
std::thread::spawn(move || {
if i % 2 == 1 {
*data.shared_data() += 1;
} else {
let mut lock = data.shared_data_locked().release_mutex();
// Give the other threads some time to wake up
std::thread::sleep(std::time::Duration::from_millis(10));
*lock.upgrade() += 1;
}
});
}
let lock = lock.release_mutex();
std::thread::sleep(std::time::Duration::from_millis(100));
drop(lock);
while *shared_data.shared_data() < THREADS {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
+1 -1
View File
@@ -14,8 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] }
parking_lot = "0.11.1"
fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" }
sp-runtime = { path = "../../../primitives/runtime" , version = "3.0.0"}
sp-blockchain = { version = "3.0.0", path = "../../../primitives/blockchain" }
sc-client-api = { path = "../../api" , version = "3.0.0"}
sc-consensus = { path = "../common" , version = "0.9.0"}
+5 -4
View File
@@ -20,8 +20,7 @@
pub mod migration;
use std::{sync::Arc, ops::Add, collections::BTreeMap, borrow::{Borrow, BorrowMut}};
use parking_lot::Mutex;
use std::{ops::Add, collections::BTreeMap, borrow::{Borrow, BorrowMut}};
use codec::{Encode, Decode};
use fork_tree::ForkTree;
use sc_client_api::utils::is_descendent_of;
@@ -645,10 +644,12 @@ impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E> where
}
/// Type alias to produce the epoch-changes tree from a block type.
pub type EpochChangesFor<Block, Epoch> = EpochChanges<<Block as BlockT>::Hash, NumberFor<Block>, Epoch>;
pub type EpochChangesFor<Block, Epoch> =
EpochChanges<<Block as BlockT>::Hash, NumberFor<Block>, Epoch>;
/// A shared epoch changes tree.
pub type SharedEpochChanges<Block, Epoch> = Arc<Mutex<EpochChangesFor<Block, Epoch>>>;
pub type SharedEpochChanges<Block, Epoch> =
sc_consensus::shared_data::SharedData<EpochChangesFor<Block, Epoch>>;
#[cfg(test)]
mod tests {
@@ -23,6 +23,7 @@ parking_lot = "0.11.1"
codec = { package = "parity-scale-codec", version = "2.0.0" }
serde = { version = "1.0", features=["derive"] }
assert_matches = "1.3.0"
async-trait = "0.1.42"
sc-client-api = { path = "../../api", version = "3.0.0"}
sc-consensus-babe = { path = "../../consensus/babe", version = "0.9.0"}
@@ -21,12 +21,7 @@
use super::ConsensusDataProvider;
use crate::Error;
use codec::Encode;
use std::{
any::Any,
borrow::Cow,
sync::{Arc, atomic},
time::SystemTime,
};
use std::{borrow::Cow, sync::{Arc, atomic}, time::SystemTime};
use sc_client_api::AuxStore;
use sc_consensus_babe::{
Config, Epoch, authorship, CompatibleDigestItem, BabeIntermediate,
@@ -102,7 +97,7 @@ impl<B, C> BabeConsensusDataProvider<B, C>
}
fn epoch(&self, parent: &B::Header, slot: Slot) -> Result<Epoch, Error> {
let epoch_changes = self.epoch_changes.lock();
let epoch_changes = self.epoch_changes.shared_data();
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
@@ -156,7 +151,7 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
authority_index: 0_u32,
});
let mut epoch_changes = self.epoch_changes.lock();
let mut epoch_changes = self.epoch_changes.shared_data();
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
@@ -200,7 +195,7 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
inherents: &InherentData
) -> Result<(), Error> {
let slot = inherents.babe_inherent_data()?;
let epoch_changes = self.epoch_changes.lock();
let epoch_changes = self.epoch_changes.shared_data();
let mut epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(
descendent_query(&*self.client),
@@ -239,7 +234,7 @@ impl<B, C> ConsensusDataProvider<B> for BabeConsensusDataProvider<B, C>
params.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(BabeIntermediate::<B> { epoch_descriptor }) as Box<dyn Any>,
Box::new(BabeIntermediate::<B> { epoch_descriptor }) as Box<_>,
);
Ok(())
@@ -55,8 +55,9 @@ pub const MANUAL_SEAL_ENGINE_ID: ConsensusEngineId = [b'm', b'a', b'n', b'l'];
/// The verifier for the manual seal engine; instantly finalizes.
struct ManualSealVerifier;
#[async_trait::async_trait]
impl<B: BlockT> Verifier<B> for ManualSealVerifier {
fn verify(
async fn verify(
&mut self,
origin: BlockOrigin,
header: B::Header,
@@ -144,7 +144,7 @@ pub async fn seal_block<B, BI, SC, C, E, P>(
digest_provider.append_block_import(&parent, &mut params, &id)?;
}
match block_import.import_block(params, HashMap::new())? {
match block_import.import_block(params, HashMap::new()).await? {
ImportResult::Imported(aux) => {
Ok(CreatedBlock { hash: <B as BlockT>::Header::hash(&header), aux })
},
@@ -30,3 +30,4 @@ parking_lot = "0.11.1"
sp-timestamp = { version = "3.0.0", path = "../../../primitives/timestamp" }
derive_more = "0.99.2"
prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.9.0"}
async-trait = "0.1.42"
+15 -12
View File
@@ -36,7 +36,7 @@ mod worker;
pub use crate::worker::{MiningWorker, MiningMetadata, MiningBuild};
use std::{
sync::Arc, any::Any, borrow::Cow, collections::HashMap, marker::PhantomData,
sync::Arc, borrow::Cow, collections::HashMap, marker::PhantomData,
cmp::Ordering, time::Duration,
};
use futures::{prelude::*, future::Either};
@@ -307,6 +307,7 @@ impl<B, I, C, S, Algorithm, CAW> PowBlockImport<B, I, C, S, Algorithm, CAW> wher
}
}
#[async_trait::async_trait]
impl<B, I, C, S, Algorithm, CAW> BlockImport<B> for PowBlockImport<B, I, C, S, Algorithm, CAW> where
B: BlockT,
I: BlockImport<B, Transaction = sp_api::TransactionFor<C, B>> + Send + Sync,
@@ -314,21 +315,21 @@ impl<B, I, C, S, Algorithm, CAW> BlockImport<B> for PowBlockImport<B, I, C, S, A
S: SelectChain<B>,
C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + ProvideCache<B> + BlockOf,
C::Api: BlockBuilderApi<B>,
Algorithm: PowAlgorithm<B>,
Algorithm::Difficulty: 'static,
CAW: CanAuthorWith<B>,
Algorithm: PowAlgorithm<B> + Send,
Algorithm::Difficulty: 'static + Send,
CAW: CanAuthorWith<B> + Send,
{
type Error = ConsensusError;
type Transaction = sp_api::TransactionFor<C, B>;
fn check_block(
async fn check_block(
&mut self,
block: BlockCheckParams<B>,
) -> Result<ImportResult, Self::Error> {
self.inner.check_block(block).map_err(Into::into)
self.inner.check_block(block).await.map_err(Into::into)
}
fn import_block(
async fn import_block(
&mut self,
mut block: BlockImportParams<B, Self::Transaction>,
new_cache: HashMap<CacheKeyId, Vec<u8>>,
@@ -403,7 +404,7 @@ impl<B, I, C, S, Algorithm, CAW> BlockImport<B> for PowBlockImport<B, I, C, S, A
));
}
self.inner.import_block(block, new_cache).map_err(Into::into)
self.inner.import_block(block, new_cache).await.map_err(Into::into)
}
}
@@ -449,11 +450,12 @@ impl<B: BlockT, Algorithm> PowVerifier<B, Algorithm> {
}
}
#[async_trait::async_trait]
impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm> where
Algorithm: PowAlgorithm<B> + Send + Sync,
Algorithm::Difficulty: 'static,
Algorithm::Difficulty: 'static + Send,
{
fn verify(
async fn verify(
&mut self,
origin: BlockOrigin,
header: B::Header,
@@ -473,7 +475,7 @@ impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm> where
import_block.justifications = justifications;
import_block.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(intermediate) as Box<dyn Any>
Box::new(intermediate) as Box<_>,
);
import_block.post_hash = Some(hash);
@@ -513,6 +515,7 @@ pub fn import_queue<B, Transaction, Algorithm>(
B: BlockT,
Transaction: Send + Sync + 'static,
Algorithm: PowAlgorithm<B> + Clone + Send + Sync + 'static,
Algorithm::Difficulty: Send,
{
register_pow_inherent_data_provider(&inherent_data_providers)?;
@@ -556,7 +559,7 @@ pub fn start_mining_worker<Block, C, S, Algorithm, E, SO, CAW>(
C: ProvideRuntimeApi<Block> + BlockchainEvents<Block> + 'static,
S: SelectChain<Block> + 'static,
Algorithm: PowAlgorithm<Block> + Clone,
Algorithm::Difficulty: 'static,
Algorithm::Difficulty: Send + 'static,
E: Environment<Block> + Send + Sync + 'static,
E::Error: std::fmt::Debug,
E::Proposer: Proposer<Block, Transaction = sp_api::TransactionFor<C, Block>>,
+6 -5
View File
@@ -16,7 +16,7 @@
// 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, any::Any, borrow::Cow};
use std::{pin::Pin, time::Duration, collections::HashMap, borrow::Cow};
use sc_client_api::ImportNotifications;
use sp_runtime::{DigestItem, traits::Block as BlockT, generic::BlockId};
use sp_consensus::{Proposal, BlockOrigin, BlockImportParams, import_queue::BoxBlockImport};
@@ -68,7 +68,8 @@ impl<Block, Algorithm, C, Proof> MiningWorker<Block, Algorithm, C, Proof> where
Block: BlockT,
C: sp_api::ProvideRuntimeApi<Block>,
Algorithm: PowAlgorithm<Block>,
Algorithm::Difficulty: 'static,
Algorithm::Difficulty: 'static + Send,
sp_api::TransactionFor<C, Block>: Send + 'static,
{
/// Get the current best hash. `None` if the worker has just started or the client is doing
/// major syncing.
@@ -94,7 +95,7 @@ impl<Block, Algorithm, C, Proof> MiningWorker<Block, Algorithm, C, Proof> where
/// Submit a mined seal. The seal will be validated again. Returns true if the submission is
/// successful.
pub fn submit(&mut self, seal: Seal) -> bool {
pub async fn submit(&mut self, seal: Seal) -> bool {
if let Some(build) = self.build.take() {
match self.algorithm.verify(
&BlockId::Hash(build.metadata.best_hash),
@@ -135,10 +136,10 @@ impl<Block, Algorithm, C, Proof> MiningWorker<Block, Algorithm, C, Proof> where
import_block.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(intermediate) as Box<dyn Any>
Box::new(intermediate) as Box<_>,
);
match self.block_import.import_block(import_block, HashMap::default()) {
match self.block_import.import_block(import_block, HashMap::default()).await {
Ok(_) => {
info!(
target: "pow",
@@ -398,6 +398,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
let header = block_import_params.post_header();
if let Err(err) = block_import
.import_block(block_import_params, Default::default())
.await
{
warn!(
target: logging_target,