make SelectChain async (#9128)

* make SelectChain async

* make JustificationImport async
This commit is contained in:
André Silva
2021-06-20 12:01:09 +01:00
committed by GitHub
parent 650fc2f9c9
commit a26ec52450
18 changed files with 800 additions and 454 deletions
@@ -34,12 +34,12 @@ use parking_lot::RwLock;
use prometheus_endpoint::{register, Counter, Gauge, PrometheusError, U64};
use sc_client_api::{
backend::{apply_aux, Backend},
backend::{apply_aux, Backend as BackendT},
utils::is_descendent_of,
};
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO};
use sp_blockchain::HeaderMetadata;
use sp_consensus::SelectChain;
use sp_consensus::SelectChain as SelectChainT;
use sp_finality_grandpa::{
AuthorityId, AuthoritySignature, Equivocation, EquivocationProof, GrandpaApi, RoundNumber,
SetId, GRANDPA_ENGINE_ID,
@@ -54,7 +54,7 @@ use crate::{
local_authority_id,
notification::GrandpaJustificationSender,
until_imported::UntilVoteTargetImported,
voting_rule::VotingRule,
voting_rule::VotingRule as VotingRuleT,
ClientForGrandpa, CommandOrError, Commit, Config, Error, NewAuthoritySet, Precommit, Prevote,
PrimaryPropose, SignedMessage, VoterCommand,
};
@@ -478,11 +478,11 @@ impl<BE, Block: BlockT, C, N: NetworkT<Block>, SC, VR> Environment<BE, Block, C,
impl<BE, Block, C, N, SC, VR> Environment<BE, Block, C, N, SC, VR>
where
Block: BlockT,
BE: Backend<Block>,
BE: BackendT<Block>,
C: ClientForGrandpa<Block, BE>,
C::Api: GrandpaApi<Block>,
N: NetworkT<Block>,
SC: SelectChain<Block>,
SC: SelectChainT<Block>,
{
/// Report the given equivocation to the GRANDPA runtime module. This method
/// generates a session membership proof of the offender and then submits an
@@ -503,9 +503,12 @@ where
let is_descendent_of = is_descendent_of(&*self.client, None);
let best_header = self.select_chain
.best_chain()
.map_err(|e| Error::Blockchain(e.to_string()))?;
// TODO: add proper async support here
let best_header = futures::executor::block_on(
self.select_chain
.best_chain()
.map_err(|e| Error::Blockchain(e.to_string())),
)?;
let authority_set = self.authority_set.inner();
@@ -581,11 +584,11 @@ impl<BE, Block, C, N, SC, VR> finality_grandpa::Chain<Block::Hash, NumberFor<Blo
for Environment<BE, Block, C, N, SC, VR>
where
Block: BlockT,
BE: Backend<Block>,
BE: BackendT<Block>,
C: ClientForGrandpa<Block, BE>,
N: NetworkT<Block>,
SC: SelectChain<Block>,
VR: VotingRule<Block, C>,
SC: SelectChainT<Block>,
VR: VotingRuleT<Block, C>,
NumberFor<Block>: BlockNumberOps,
{
fn ancestry(
@@ -637,12 +640,12 @@ impl<B, Block, C, N, SC, VR> voter::Environment<Block::Hash, NumberFor<Block>>
for Environment<B, Block, C, N, SC, VR>
where
Block: BlockT,
B: Backend<Block>,
B: BackendT<Block>,
C: ClientForGrandpa<Block, B> + 'static,
C::Api: GrandpaApi<Block>,
N: NetworkT<Block>,
SC: SelectChain<Block>,
VR: VotingRule<Block, C>,
SC: SelectChainT<Block> + 'static,
VR: VotingRuleT<Block, C> + Clone + 'static,
NumberFor<Block>: BlockNumberOps,
{
type Timer = Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send>>;
@@ -684,116 +687,25 @@ where
type Error = CommandOrError<Block::Hash, NumberFor<Block>>;
fn best_chain_containing(&self, block: Block::Hash) -> Self::BestChain {
let find_best_chain = || {
let client = self.client.clone();
let authority_set = self.authority_set.clone();
let select_chain = self.select_chain.clone();
let voting_rule = self.voting_rule.clone();
let set_id = self.set_id;
Box::pin(async move {
// NOTE: when we finalize an authority set change through the sync protocol the voter is
// signaled asynchronously. therefore the voter could still vote in the next round
// before activating the new set. the `authority_set` is updated immediately thus we
// restrict the voter based on that.
if self.set_id != self.authority_set.set_id() {
return None;
// before activating the new set. the `authority_set` is updated immediately thus
// we restrict the voter based on that.
if set_id != authority_set.set_id() {
return Ok(None);
}
let base_header = match self.client.header(BlockId::Hash(block)).ok()? {
Some(h) => h,
None => {
debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find base block", block);
return None;
}
};
// we refuse to vote beyond the current limit number where transitions are scheduled to
// occur.
// once blocks are finalized that make that transition irrelevant or activate it,
// we will proceed onwards. most of the time there will be no pending transition.
// the limit, if any, is guaranteed to be higher than or equal to the given base number.
let limit = self.authority_set.current_limit(*base_header.number());
debug!(target: "afg", "Finding best chain containing block {:?} with number limit {:?}", block, limit);
match self.select_chain.finality_target(block, None) {
Ok(Some(best_hash)) => {
let best_header = self
.client
.header(BlockId::Hash(best_hash))
.ok()?
.expect("Header known to exist after `finality_target` call; qed");
// check if our vote is currently being limited due to a pending change
let limit = limit.filter(|limit| limit < best_header.number());
if let Some(target_number) = limit {
let mut target_header = best_header.clone();
// walk backwards until we find the target block
loop {
if *target_header.number() < target_number {
unreachable!(
"we are traversing backwards from a known block; \
blocks are stored contiguously; \
qed"
);
}
if *target_header.number() == target_number {
break;
}
target_header = self
.client
.header(BlockId::Hash(*target_header.parent_hash()))
.ok()?
.expect("Header known to exist after `finality_target` call; qed");
}
Some((base_header, best_header, target_header))
} else {
// otherwise just use the given best as the target
Some((base_header, best_header.clone(), best_header))
}
}
Ok(None) => {
debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find target block", block);
None
}
Err(e) => {
debug!(target: "afg", "Encountered error finding best chain containing {:?}: {:?}", block, e);
None
}
}
};
if let Some((base_header, best_header, target_header)) = find_best_chain() {
// restrict vote according to the given voting rule, if the
// voting rule doesn't restrict the vote then we keep the
// previous target.
//
// note that we pass the original `best_header`, i.e. before the
// authority set limit filter, which can be considered a
// mandatory/implicit voting rule.
//
// we also make sure that the restricted vote is higher than the
// round base (i.e. last finalized), otherwise the value
// returned by the given voting rule is ignored and the original
// target is used instead.
let rule_fut = self.voting_rule.restrict_vote(
self.client.clone(),
&base_header,
&best_header,
&target_header,
);
Box::pin(async move {
Ok(rule_fut
.await
.filter(|(_, restricted_number)| {
// we can only restrict votes within the interval [base, target]
restricted_number >= base_header.number()
&& restricted_number < target_header.number()
})
.or_else(|| Some((target_header.hash(), *target_header.number()))))
})
} else {
Box::pin(future::ok(None))
}
best_chain_containing(block, client, authority_set, select_chain, voting_rule)
.await
.map_err(|e| e.into())
})
}
fn round_data(
@@ -1227,6 +1139,111 @@ impl<Block: BlockT> From<GrandpaJustification<Block>> for JustificationOrCommit<
}
}
async fn best_chain_containing<Block, Backend, Client, SelectChain, VotingRule>(
block: Block::Hash,
client: Arc<Client>,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
select_chain: SelectChain,
voting_rule: VotingRule,
) -> Result<Option<(Block::Hash, NumberFor<Block>)>, Error>
where
Backend: BackendT<Block>,
Block: BlockT,
Client: ClientForGrandpa<Block, Backend>,
SelectChain: SelectChainT<Block> + 'static,
VotingRule: VotingRuleT<Block, Client>,
{
let base_header = match client.header(BlockId::Hash(block))? {
Some(h) => h,
None => {
debug!(target: "afg",
"Encountered error finding best chain containing {:?}: couldn't find base block",
block,
);
return Ok(None);
}
};
// we refuse to vote beyond the current limit number where transitions are scheduled to occur.
// once blocks are finalized that make that transition irrelevant or activate it, we will
// proceed onwards. most of the time there will be no pending transition. the limit, if any, is
// guaranteed to be higher than or equal to the given base number.
let limit = authority_set.current_limit(*base_header.number());
debug!(target: "afg", "Finding best chain containing block {:?} with number limit {:?}", block, limit);
let result = match select_chain.finality_target(block, None).await {
Ok(Some(best_hash)) => {
let best_header = client
.header(BlockId::Hash(best_hash))?
.expect("Header known to exist after `finality_target` call; qed");
// check if our vote is currently being limited due to a pending change
let limit = limit.filter(|limit| limit < best_header.number());
let (base_header, best_header, target_header) = if let Some(target_number) = limit {
let mut target_header = best_header.clone();
// walk backwards until we find the target block
loop {
if *target_header.number() < target_number {
unreachable!(
"we are traversing backwards from a known block; \
blocks are stored contiguously; \
qed"
);
}
if *target_header.number() == target_number {
break;
}
target_header = client
.header(BlockId::Hash(*target_header.parent_hash()))?
.expect("Header known to exist after `finality_target` call; qed");
}
(base_header, best_header, target_header)
} else {
// otherwise just use the given best as the target
(base_header, best_header.clone(), best_header)
};
// restrict vote according to the given voting rule, if the
// voting rule doesn't restrict the vote then we keep the
// previous target.
//
// note that we pass the original `best_header`, i.e. before the
// authority set limit filter, which can be considered a
// mandatory/implicit voting rule.
//
// we also make sure that the restricted vote is higher than the
// round base (i.e. last finalized), otherwise the value
// returned by the given voting rule is ignored and the original
// target is used instead.
voting_rule
.restrict_vote(client.clone(), &base_header, &best_header, &target_header)
.await
.filter(|(_, restricted_number)| {
// we can only restrict votes within the interval [base, target]
restricted_number >= base_header.number() &&
restricted_number < target_header.number()
})
.or_else(|| Some((target_header.hash(), *target_header.number())))
}
Ok(None) => {
debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find target block", block);
None
}
Err(e) => {
debug!(target: "afg", "Encountered error finding best chain containing {:?}: {:?}", block, e);
None
}
};
Ok(result)
}
/// Finalize the given block and apply any authority set changes. If an
/// authority set change is enacted then a justification is created (if not
/// given) and stored with the block when finalizing it.
@@ -1244,7 +1261,7 @@ pub(crate) fn finalize_block<BE, Block, Client>(
) -> Result<(), CommandOrError<Block::Hash, NumberFor<Block>>>
where
Block: BlockT,
BE: Backend<Block>,
BE: BackendT<Block>,
Client: ClientForGrandpa<Block, BE>,
{
// NOTE: lock must be held through writing to DB to avoid race. this lock