mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-19 22:45:40 +00:00
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:
@@ -17,9 +17,10 @@ codec = { package = "parity-scale-codec", version = "1.3.1", default-features =
|
||||
merlin = { version = "2.0", default-features = false }
|
||||
sp-std = { version = "2.0.0-rc4", default-features = false, path = "../../std" }
|
||||
sp-api = { version = "2.0.0-rc4", default-features = false, path = "../../api" }
|
||||
sp-core = { version = "2.0.0-rc4", default-features = false, path = "../../core" }
|
||||
sp-consensus = { version = "0.8.0-rc4", optional = true, path = "../common" }
|
||||
sp-consensus-slots = { version = "0.8.0-rc4", default-features = false, path = "../slots" }
|
||||
sp-consensus-vrf = { version = "0.8.0-rc4", path = "../vrf", default-features = false }
|
||||
sp-core = { version = "2.0.0-rc4", default-features = false, path = "../../core" }
|
||||
sp-inherents = { version = "2.0.0-rc4", default-features = false, path = "../../inherents" }
|
||||
sp-runtime = { version = "2.0.0-rc4", default-features = false, path = "../../runtime" }
|
||||
sp-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../../timestamp" }
|
||||
@@ -27,14 +28,15 @@ sp-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../../
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"sp-core/std",
|
||||
"sp-application-crypto/std",
|
||||
"codec/std",
|
||||
"merlin/std",
|
||||
"sp-std/std",
|
||||
"sp-api/std",
|
||||
"sp-consensus",
|
||||
"sp-consensus-slots/std",
|
||||
"sp-consensus-vrf/std",
|
||||
"sp-core/std",
|
||||
"sp-inherents/std",
|
||||
"sp-runtime/std",
|
||||
"sp-timestamp/std",
|
||||
|
||||
@@ -17,18 +17,14 @@
|
||||
|
||||
//! Private implementation details of BABE digests.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use super::{BABE_ENGINE_ID, AuthoritySignature};
|
||||
use super::{AuthorityId, AuthorityIndex, SlotNumber, BabeAuthorityWeight, BabeEpochConfiguration, AllowedSlots};
|
||||
#[cfg(feature = "std")]
|
||||
use sp_runtime::{DigestItem, generic::OpaqueDigestItemId};
|
||||
#[cfg(feature = "std")]
|
||||
use std::fmt::Debug;
|
||||
use codec::{Decode, Encode};
|
||||
#[cfg(feature = "std")]
|
||||
use codec::Codec;
|
||||
use super::{
|
||||
AllowedSlots, AuthorityId, AuthorityIndex, AuthoritySignature, BabeAuthorityWeight,
|
||||
BabeEpochConfiguration, SlotNumber, BABE_ENGINE_ID,
|
||||
};
|
||||
use codec::{Codec, Decode, Encode};
|
||||
use sp_std::vec::Vec;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_runtime::{generic::OpaqueDigestItemId, DigestItem, RuntimeDebug};
|
||||
|
||||
use sp_consensus_vrf::schnorrkel::{Randomness, VRFOutput, VRFProof};
|
||||
|
||||
/// Raw BABE primary slot assignment pre-digest.
|
||||
@@ -151,7 +147,6 @@ impl From<NextConfigDescriptor> for BabeEpochConfiguration {
|
||||
}
|
||||
|
||||
/// A digest item which is usable with BABE consensus.
|
||||
#[cfg(feature = "std")]
|
||||
pub trait CompatibleDigestItem: Sized {
|
||||
/// Construct a digest item which contains a BABE pre-digest.
|
||||
fn babe_pre_digest(seal: PreDigest) -> Self;
|
||||
@@ -172,9 +167,8 @@ pub trait CompatibleDigestItem: Sized {
|
||||
fn as_next_config_descriptor(&self) -> Option<NextConfigDescriptor>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<Hash> CompatibleDigestItem for DigestItem<Hash> where
|
||||
Hash: Debug + Send + Sync + Eq + Clone + Codec + 'static
|
||||
Hash: Send + Sync + Eq + Clone + Codec + 'static
|
||||
{
|
||||
fn babe_pre_digest(digest: PreDigest) -> Self {
|
||||
DigestItem::PreRuntime(BABE_ENGINE_ID, digest.encode())
|
||||
|
||||
@@ -23,17 +23,21 @@
|
||||
pub mod digests;
|
||||
pub mod inherents;
|
||||
|
||||
pub use sp_consensus_vrf::schnorrkel::{
|
||||
Randomness, VRF_PROOF_LENGTH, VRF_OUTPUT_LENGTH, RANDOMNESS_LENGTH
|
||||
};
|
||||
pub use merlin::Transcript;
|
||||
pub use sp_consensus_vrf::schnorrkel::{
|
||||
Randomness, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH,
|
||||
};
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use sp_std::vec::Vec;
|
||||
use sp_runtime::{ConsensusEngineId, RuntimeDebug};
|
||||
use codec::{Decode, Encode};
|
||||
#[cfg(feature = "std")]
|
||||
use sp_core::vrf::{VRFTranscriptData, VRFTranscriptValue};
|
||||
use crate::digests::{NextEpochDescriptor, NextConfigDescriptor};
|
||||
use sp_runtime::{traits::Header, ConsensusEngineId, RuntimeDebug};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
use crate::digests::{NextConfigDescriptor, NextEpochDescriptor};
|
||||
|
||||
/// Key type for BABE module.
|
||||
pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::BABE;
|
||||
|
||||
mod app {
|
||||
use sp_application_crypto::{app_crypto, key_types::BABE, sr25519};
|
||||
@@ -73,7 +77,10 @@ pub const MEDIAN_ALGORITHM_CARDINALITY: usize = 1200; // arbitrary suggestion by
|
||||
pub type AuthorityIndex = u32;
|
||||
|
||||
/// A slot number.
|
||||
pub type SlotNumber = u64;
|
||||
pub use sp_consensus_slots::SlotNumber;
|
||||
|
||||
/// An equivocation proof for multiple block authorships on the same slot (i.e. double vote).
|
||||
pub type EquivocationProof<H> = sp_consensus_slots::EquivocationProof<H, AuthorityId>;
|
||||
|
||||
/// The weight of an authority.
|
||||
// NOTE: we use a unique name for the weight to avoid conflicts with other
|
||||
@@ -256,6 +263,93 @@ pub struct BabeEpochConfiguration {
|
||||
pub allowed_slots: AllowedSlots,
|
||||
}
|
||||
|
||||
/// Verifies the equivocation proof by making sure that: both headers have
|
||||
/// different hashes, are targetting the same slot, and have valid signatures by
|
||||
/// the same authority.
|
||||
pub fn check_equivocation_proof<H>(proof: EquivocationProof<H>) -> bool
|
||||
where
|
||||
H: Header,
|
||||
{
|
||||
use digests::*;
|
||||
use sp_application_crypto::RuntimeAppPublic;
|
||||
|
||||
let find_pre_digest = |header: &H| {
|
||||
header
|
||||
.digest()
|
||||
.logs()
|
||||
.iter()
|
||||
.find_map(|log| log.as_babe_pre_digest())
|
||||
};
|
||||
|
||||
let verify_seal_signature = |mut header: H, offender: &AuthorityId| {
|
||||
let seal = header.digest_mut().pop()?.as_babe_seal()?;
|
||||
let pre_hash = header.hash();
|
||||
|
||||
if !offender.verify(&pre_hash.as_ref(), &seal) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(())
|
||||
};
|
||||
|
||||
let verify_proof = || {
|
||||
// we must have different headers for the equivocation to be valid
|
||||
if proof.first_header.hash() == proof.second_header.hash() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let first_pre_digest = find_pre_digest(&proof.first_header)?;
|
||||
let second_pre_digest = find_pre_digest(&proof.second_header)?;
|
||||
|
||||
// both headers must be targetting the same slot and it must
|
||||
// be the same as the one in the proof.
|
||||
if proof.slot_number != first_pre_digest.slot_number() ||
|
||||
first_pre_digest.slot_number() != second_pre_digest.slot_number()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// both headers must have been authored by the same authority
|
||||
if first_pre_digest.authority_index() != second_pre_digest.authority_index() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// we finally verify that the expected authority has signed both headers and
|
||||
// that the signature is valid.
|
||||
verify_seal_signature(proof.first_header, &proof.offender)?;
|
||||
verify_seal_signature(proof.second_header, &proof.offender)?;
|
||||
|
||||
Some(())
|
||||
};
|
||||
|
||||
// NOTE: we isolate the verification code into an helper function that
|
||||
// returns `Option<()>` so that we can use `?` to deal with any intermediate
|
||||
// errors and discard the proof as invalid.
|
||||
verify_proof().is_some()
|
||||
}
|
||||
|
||||
/// An opaque type used to represent the key ownership proof at the runtime API
|
||||
/// boundary. The inner value is an encoded representation of the actual key
|
||||
/// ownership proof which will be parameterized when defining the runtime. At
|
||||
/// the runtime API boundary this type is unknown and as such we keep this
|
||||
/// opaque representation, implementors of the runtime API will have to make
|
||||
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
|
||||
#[derive(Decode, Encode, PartialEq)]
|
||||
pub struct OpaqueKeyOwnershipProof(Vec<u8>);
|
||||
impl OpaqueKeyOwnershipProof {
|
||||
/// Create a new `OpaqueKeyOwnershipProof` using the given encoded
|
||||
/// representation.
|
||||
pub fn new(inner: Vec<u8>) -> OpaqueKeyOwnershipProof {
|
||||
OpaqueKeyOwnershipProof(inner)
|
||||
}
|
||||
|
||||
/// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key
|
||||
/// ownership proof type.
|
||||
pub fn decode<T: Decode>(self) -> Option<T> {
|
||||
Decode::decode(&mut &self.0[..]).ok()
|
||||
}
|
||||
}
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// API necessary for block authorship with BABE.
|
||||
#[api_version(2)]
|
||||
@@ -269,5 +363,34 @@ sp_api::decl_runtime_apis! {
|
||||
|
||||
/// Returns the slot number that started the current epoch.
|
||||
fn current_epoch_start() -> SlotNumber;
|
||||
|
||||
/// Generates a proof of key ownership for the given authority in the
|
||||
/// current epoch. An example usage of this module is coupled with the
|
||||
/// session historical module to prove that a given authority key is
|
||||
/// tied to a given staking identity during a specific session. Proofs
|
||||
/// of key ownership are necessary for submitting equivocation reports.
|
||||
/// NOTE: even though the API takes a `slot_number` as parameter the current
|
||||
/// implementations ignores this parameter and instead relies on this
|
||||
/// method being called at the correct block height, i.e. any point at
|
||||
/// which the epoch for the given slot is live on-chain. Future
|
||||
/// implementations will instead use indexed data through an offchain
|
||||
/// worker, not requiring older states to be available.
|
||||
fn generate_key_ownership_proof(
|
||||
slot_number: SlotNumber,
|
||||
authority_id: AuthorityId,
|
||||
) -> Option<OpaqueKeyOwnershipProof>;
|
||||
|
||||
/// Submits an unsigned extrinsic to report an equivocation. The caller
|
||||
/// must provide the equivocation proof and a key ownership proof
|
||||
/// (should be obtained using `generate_key_ownership_proof`). The
|
||||
/// extrinsic will be unsigned and should only be accepted for local
|
||||
/// authorship (not to be broadcast to the network). This method returns
|
||||
/// `None` when creation of the extrinsic fails, e.g. if equivocation
|
||||
/// reporting is disabled for the given runtime (i.e. this method is
|
||||
/// hardcoded to return `None`). Only useful in an offchain context.
|
||||
fn submit_report_equivocation_unsigned_extrinsic(
|
||||
equivocation_proof: EquivocationProof<Block::Header>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> Option<()>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "sp-consensus-slots"
|
||||
version = "0.8.0-rc4"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Primitives for slots-based consensus"
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.dev"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
|
||||
sp-runtime = { version = "2.0.0-rc2", default-features = false, path = "../../runtime" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"sp-runtime/std",
|
||||
]
|
||||
@@ -0,0 +1,41 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Primitives for slots-based consensus engines.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// A slot number.
|
||||
pub type SlotNumber = u64;
|
||||
|
||||
/// Represents an equivocation proof. An equivocation happens when a validator
|
||||
/// produces more than one block on the same slot. The proof of equivocation
|
||||
/// are the given distinct headers that were signed by the validator and which
|
||||
/// include the slot number.
|
||||
#[derive(Clone, Debug, Decode, Encode, PartialEq)]
|
||||
pub struct EquivocationProof<Header, Id> {
|
||||
/// Returns the authority id of the equivocator.
|
||||
pub offender: Id,
|
||||
/// The slot number at which the equivocation happened.
|
||||
pub slot_number: SlotNumber,
|
||||
/// The first header involved in the equivocation.
|
||||
pub first_header: Header,
|
||||
/// The second header involved in the equivocation.
|
||||
pub second_header: Header,
|
||||
}
|
||||
@@ -333,6 +333,11 @@ pub fn to_substrate_wasm_fn_return_value(value: &impl Encode) -> u64 {
|
||||
res
|
||||
}
|
||||
|
||||
/// The void type - it cannot exist.
|
||||
// Oh rust, you crack me up...
|
||||
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug)]
|
||||
pub enum Void {}
|
||||
|
||||
/// Macro for creating `Maybe*` marker traits.
|
||||
///
|
||||
/// Such a maybe-marker trait requires the given bound when `feature = std` and doesn't require
|
||||
|
||||
@@ -64,6 +64,48 @@ pub struct MembershipProof {
|
||||
pub validator_count: ValidatorCount,
|
||||
}
|
||||
|
||||
/// A utility trait to get a session number. This is implemented for
|
||||
/// `MembershipProof` below to fetch the session number the given session
|
||||
/// membership proof is for. It is useful when we need to deal with key owner
|
||||
/// proofs generically (i.e. just typing against the `KeyOwnerProofSystem`
|
||||
/// trait) but still restrict their capabilities.
|
||||
pub trait GetSessionNumber {
|
||||
fn session(&self) -> SessionIndex;
|
||||
}
|
||||
|
||||
/// A utility trait to get the validator count of a given session. This is
|
||||
/// implemented for `MembershipProof` below and fetches the number of validators
|
||||
/// in the session the membership proof is for. It is useful when we need to
|
||||
/// deal with key owner proofs generically (i.e. just typing against the
|
||||
/// `KeyOwnerProofSystem` trait) but still restrict their capabilities.
|
||||
pub trait GetValidatorCount {
|
||||
fn validator_count(&self) -> ValidatorCount;
|
||||
}
|
||||
|
||||
impl GetSessionNumber for sp_core::Void {
|
||||
fn session(&self) -> SessionIndex {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl GetValidatorCount for sp_core::Void {
|
||||
fn validator_count(&self) -> ValidatorCount {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSessionNumber for MembershipProof {
|
||||
fn session(&self) -> SessionIndex {
|
||||
self.session
|
||||
}
|
||||
}
|
||||
|
||||
impl GetValidatorCount for MembershipProof {
|
||||
fn validator_count(&self) -> ValidatorCount {
|
||||
self.validator_count
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the initial session keys with the given seeds, at the given block and store them in
|
||||
/// the client's keystore.
|
||||
#[cfg(feature = "std")]
|
||||
|
||||
@@ -117,10 +117,21 @@ impl sp_runtime::traits::Printable for OffenceError {
|
||||
pub trait ReportOffence<Reporter, Offender, O: Offence<Offender>> {
|
||||
/// Report an `offence` and reward given `reporters`.
|
||||
fn report_offence(reporters: Vec<Reporter>, offence: O) -> Result<(), OffenceError>;
|
||||
|
||||
/// Returns true iff all of the given offenders have been previously reported
|
||||
/// at the given time slot. This function is useful to prevent the sending of
|
||||
/// duplicate offence reports.
|
||||
fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool;
|
||||
}
|
||||
|
||||
impl<Reporter, Offender, O: Offence<Offender>> ReportOffence<Reporter, Offender, O> for () {
|
||||
fn report_offence(_reporters: Vec<Reporter>, _offence: O) -> Result<(), OffenceError> { Ok(()) }
|
||||
fn report_offence(_reporters: Vec<Reporter>, _offence: O) -> Result<(), OffenceError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_known_offence(_offenders: &[Offender], _time_slot: &O::TimeSlot) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to take action on an offence.
|
||||
|
||||
Reference in New Issue
Block a user