babe: report equivocations (#6362)

* slots: create primitives crate for consensus slots

* offences: add method to check if an offence is unknown

* babe: initial equivocation reporting implementation

* babe: organize imports

* babe: working equivocation reporting

* babe: add slot number to equivocation proof

* session: move duplicate traits to session primitives

* babe: move equivocation stuff to its own file

* offences: fix test

* session: don't have primitives depend on frame_support

* babe: use opaque type for key owner proof

* babe: cleanup client equivocation reporting

* babe: cleanup equivocation code in pallet

* babe: allow sending signed equivocation reports

* node: fix compilation

* fix test compilation

* babe: return bool on check_equivocation_proof

* babe: add test for equivocation reporting

* babe: add more tests

* babe: add test for validate unsigned

* babe: take slot number in generate_key_ownership_proof API

* babe: add benchmark for equivocation proof checking

* session: add benchmark for membership proof checking

* offences: fix babe benchmark

* babe: add weights based on benchmark results

* babe: adjust weights after benchmarking on reference hardware

* babe: reorder checks in check_and_report_equivocation
This commit is contained in:
André Silva
2020-07-04 11:18:13 +01:00
committed by GitHub
parent 61635e75c1
commit a9c21b8b84
34 changed files with 2031 additions and 275 deletions
+105 -28
View File
@@ -720,27 +720,29 @@ impl<Block: BlockT> BabeLink<Block> {
}
/// A verifier for Babe blocks.
pub struct BabeVerifier<Block: BlockT, Client> {
pub struct BabeVerifier<Block: BlockT, Client, SelectChain> {
client: Arc<Client>,
select_chain: SelectChain,
inherent_data_providers: sp_inherents::InherentDataProviders,
config: Config,
epoch_changes: SharedEpochChanges<Block, Epoch>,
time_source: TimeSource,
}
impl<Block, Client> BabeVerifier<Block, Client>
where
Block: BlockT,
Client: HeaderBackend<Block> + HeaderMetadata<Block> + ProvideRuntimeApi<Block>,
Client::Api: BlockBuilderApi<Block, Error = sp_blockchain::Error>,
impl<Block, Client, SelectChain> BabeVerifier<Block, Client, SelectChain>
where
Block: BlockT,
Client: AuxStore + HeaderBackend<Block> + HeaderMetadata<Block> + ProvideRuntimeApi<Block>,
Client::Api: BlockBuilderApi<Block, Error = sp_blockchain::Error>
+ BabeApi<Block, Error = sp_blockchain::Error>,
SelectChain: sp_consensus::SelectChain<Block>,
{
fn check_inherents(
&self,
block: Block,
block_id: BlockId<Block>,
inherent_data: InherentData,
) -> Result<(), Error<Block>>
{
) -> Result<(), Error<Block>> {
let inherent_res = self.client.runtime_api().check_inherents(
&block_id,
block,
@@ -757,13 +759,95 @@ impl<Block, Client> BabeVerifier<Block, Client>
Ok(())
}
}
fn check_and_report_equivocation(
&self,
slot_now: SlotNumber,
slot: SlotNumber,
header: &Block::Header,
author: &AuthorityId,
origin: &BlockOrigin,
) -> Result<(), Error<Block>> {
// don't report any equivocations during initial sync
// as they are most likely stale.
if *origin == BlockOrigin::NetworkInitialSync {
return Ok(());
}
// check if authorship of this header is an equivocation and return a proof if so.
let equivocation_proof =
match check_equivocation(&*self.client, slot_now, slot, header, author)
.map_err(Error::Client)?
{
Some(proof) => proof,
None => return Ok(()),
};
info!(
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
author,
slot,
equivocation_proof.first_header.hash(),
equivocation_proof.second_header.hash(),
);
// get the best block on which we will build and send the equivocation report.
let best_id = self
.select_chain
.best_chain()
.map(|h| BlockId::Hash(h.hash()))
.map_err(|e| Error::Client(e.into()))?;
// generate a key ownership proof. we start by trying to generate the
// key owernship proof at the parent of the equivocating header, this
// will make sure that proof generation is successful since it happens
// during the on-going session (i.e. session keys are available in the
// state to be able to generate the proof). this might fail if the
// equivocation happens on the first block of the session, in which case
// its parent would be on the previous session. if generation on the
// parent header fails we try with best block as well.
let generate_key_owner_proof = |block_id: &BlockId<Block>| {
self.client
.runtime_api()
.generate_key_ownership_proof(block_id, slot, equivocation_proof.offender.clone())
.map_err(Error::Client)
};
let parent_id = BlockId::Hash(*header.parent_hash());
let key_owner_proof = match generate_key_owner_proof(&parent_id)? {
Some(proof) => proof,
None => match generate_key_owner_proof(&best_id)? {
Some(proof) => proof,
None => {
debug!(target: "babe", "Equivocation offender is not part of the authority set.");
return Ok(());
}
},
};
// submit equivocation report at best block.
self.client
.runtime_api()
.submit_report_equivocation_unsigned_extrinsic(
&best_id,
equivocation_proof,
key_owner_proof,
)
.map_err(Error::Client)?;
info!(target: "babe", "Submitted equivocation report for author {:?}", author);
Ok(())
}
}
impl<Block, Client> Verifier<Block> for BabeVerifier<Block, Client> where
impl<Block, Client, SelectChain> Verifier<Block> for BabeVerifier<Block, Client, SelectChain>
where
Block: BlockT,
Client: HeaderMetadata<Block, Error = sp_blockchain::Error> + HeaderBackend<Block> + ProvideRuntimeApi<Block>
+ Send + Sync + AuxStore + ProvideCache<Block>,
+ Send + Sync + AuxStore + ProvideCache<Block>,
Client::Api: BlockBuilderApi<Block, Error = sp_blockchain::Error> + BabeApi<Block, Error = sp_blockchain::Error>,
SelectChain: sp_consensus::SelectChain<Block>,
{
fn verify(
&mut self,
@@ -824,28 +908,18 @@ impl<Block, Client> Verifier<Block> for BabeVerifier<Block, Client> where
CheckedHeader::Checked(pre_header, verified_info) => {
let babe_pre_digest = verified_info.pre_digest.as_babe_pre_digest()
.expect("check_header always returns a pre-digest digest item; qed");
let slot_number = babe_pre_digest.slot_number();
let author = verified_info.author;
// the header is valid but let's check if there was something else already
// proposed at the same slot by the given author
if let Some(equivocation_proof) = check_equivocation(
&*self.client,
// proposed at the same slot by the given author. if there was, we will
// report the equivocation to the runtime.
self.check_and_report_equivocation(
slot_now,
babe_pre_digest.slot_number(),
slot_number,
&header,
&author,
).map_err(|e| e.to_string())? {
info!(
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
author,
babe_pre_digest.slot_number(),
equivocation_proof.fst_header().hash(),
equivocation_proof.snd_header().hash(),
);
}
&verified_info.author,
&origin,
)?;
// if the body is passed through, we need to use the runtime
// to check that the internally-set timestamp in the inherents
@@ -1284,12 +1358,13 @@ pub fn block_import<Client, Block: BlockT, I>(
///
/// The block import object provided must be the `BabeBlockImport` or a wrapper
/// of it, otherwise crucial import logic will be omitted.
pub fn import_queue<Block: BlockT, Client, Inner>(
pub fn import_queue<Block: BlockT, Client, SelectChain, Inner>(
babe_link: BabeLink<Block>,
block_import: Inner,
justification_import: Option<BoxJustificationImport<Block>>,
finality_proof_import: Option<BoxFinalityProofImport<Block>>,
client: Arc<Client>,
select_chain: SelectChain,
inherent_data_providers: InherentDataProviders,
spawner: &impl sp_core::traits::SpawnNamed,
registry: Option<&Registry>,
@@ -1299,11 +1374,13 @@ pub fn import_queue<Block: BlockT, Client, Inner>(
Client: ProvideRuntimeApi<Block> + ProvideCache<Block> + Send + Sync + AuxStore + 'static,
Client: HeaderBackend<Block> + HeaderMetadata<Block, Error = sp_blockchain::Error>,
Client::Api: BlockBuilderApi<Block> + BabeApi<Block> + ApiExt<Block, Error = sp_blockchain::Error>,
SelectChain: sp_consensus::SelectChain<Block> + 'static,
{
register_babe_inherent_data_provider(&inherent_data_providers, babe_link.config.slot_duration)?;
let verifier = BabeVerifier {
client,
select_chain,
inherent_data_providers,
config: babe_link.config,
epoch_changes: babe_link.epoch_changes,
+11 -1
View File
@@ -214,8 +214,13 @@ 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,
>;
pub struct TestVerifier {
inner: BabeVerifier<TestBlock, PeersFullClient>,
inner: BabeVerifier<TestBlock, PeersFullClient, TestSelectChain>,
mutator: Mutator,
}
@@ -297,15 +302,20 @@ impl TestNetFactory for BabeTestNet {
)
-> 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 (_, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
TestVerifier {
inner: BabeVerifier {
client: client.clone(),
select_chain: longest_chain,
inherent_data_providers: data.inherent_data_providers.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),