grandpa: report equivocations (#3868)

* session: runtime api for generating session membership proofs

* grandpa: add runtime api for creating equivocation report txs

* grandpa: submit signed equivocation report transactions

* grandpa: use proper equivocation report type

* grandpa: report equivocations

* grandpa: validate equivocation proof

* grandpa: update to finality-grandpa 0.9.1

* grandpa: fix encoding of session membership proof

* grandpa: initialize set id session mapping for genesis session

* grandpa: fix bug in set_id session validation

* fix compilation

* cleanup from merge conflicts

* cleanup crate tomls

* grandpa: refactor equivocation handling to separate trait

* node-template: fix compilation

* fix test compilation

* bump finality-grandpa to v0.10.2

* rpc: fix runtime version test

* CHERRY-PICK #4200: Add documentation to SubmitSignedTransaction and actually make it work

Squashed commit of the following:

commit 4f2cb0b1c588a06f2f3b478bb4b28b5cb29d54b9
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Tue Dec 3 16:29:33 2019 +0100

    Split the method to avoid confusing type error message.

commit c5bf24eeaaf902add89ed1b046b22c4a4aaeb2cd
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Tue Dec 3 16:19:55 2019 +0100

    Make accounts optional, fix logic.

commit 97db1ef556e023cf6847e5ffdb036c0e3ea6fb0a
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Tue Dec 3 10:06:20 2019 +0100

    Remove warning.

commit 535f5c116d1a2e826eaf90c3f7e6798e443d61d8
Merge: 516257217 0f1a5f651
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Tue Dec 3 07:08:05 2019 +0100

    Merge branch 'master' into td-signed-transactions

commit 516257217bac89fcebd083712f4ea68b7b23b55a
Merge: ac98248c6 2e68c80c2
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Mon Dec 2 13:57:25 2019 +0100

    Merge branch 'master' into td-signed-transactions

commit ac98248c6c56cff381130645a82a13d29933cf83
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Mon Nov 25 17:34:52 2019 +0100

    Forgotten import.

commit 67a3c19031506c28e31c6bc4a90fff62d467dd58
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Mon Nov 25 17:32:10 2019 +0100

    Fix naming and bounds.

commit 93e768ea9df97a4629fca1f9bc4b108fdb33f876
Author: Tomasz Drwięga <tomasz@parity.io>
Date:   Mon Nov 25 17:01:05 2019 +0100

    Add documentation to signed transactions and actually make them work.

* grandpa: skip block initialization on report submission method

* primitives: allow transaction pool access by default for offchain calls

* grandpa: unused parameters

* grandpa: remove unused method

* grandpa: enable equivocation reporting

* grandpa: add workaround for parameter encoding

* grandpa: fix localized_payload calls in tests

* fix submit_report_equivocation_extrinsic in runtimes

* node: fix submit transaction test compilation

* node: bump spec_version

* rpc: fix api version test

* grandpa: allow custom equivocation offence type

* grandpa: add test for authorities::next_change_height

* grandpa: cleanup report_equivocation function

* node: move reporting app crypto to node-primitives

* grandpa: move equivocation traits to own module

* grandpa: rename app-crypto crate import

* grandpa: export equivocation types

* node: bump spec_version

* grandpa: rename EquivocationReport to EquivocationProof

* grandpa: add missing docs to primitives

* grandpa: add missing docs to equivocation

* node: fix compilation

* grandpa: add missing docs to pallet

* node: bump spec_version

* fix whitespace

* grandpa: return error on offence reporting

* grandpa: expose session and validator count in proofs through traits

* grandpa: use strong key in module KeyOwnerProofSystem

* grandpa: move key ownership proof to grandpa runtime api

* grandpa: remove unnecessary cloning when checking equivocation proof

* grandpa: make report_equivocation a method in Environment

* support: implement KeyOwnerProofSystem for ()

* grandpa: move KeyOwnerProofSystem to module trait

* test-utils: fix runtime compilation

* grandpa: fix test compilation

* grandpa: fix test compilation after merge

* grandpa: simplify transaction submission types

* grandpa: validate equivocation report in signed extension

* client: fix test

* node: use ValidateEquivocationReport signed extension

* grandpa: expose key ownership proof under opaque type

* grandpa: better docs on key ownership proofs

* grandpa: add note about signed extension

* grandpa: add ValidateEquivocationReport::new

* grandpa: remove skip_initialize_block from runtime api

* grandpa: use new offchain transaction submission API

* grandpa: take set_id in generate_key_ownership_proof

* grandpa: update to finality-grandpa v0.12.2

* grandpa: cleanup usages of AuthoritySet::current

* grandpa: fix test

* grandpa: add mocking utilities for equivocation reporting

* grandpa: add test for equivocation reporting

* grandpa: move SetIdSession initialization

* grandpa: add more tests

* node: enable historical session manager

* node: bump spec_version

* node: use strong key types in KeyOwnerProofSystem definitions

* grandpa: export GrandpaEquivocationOffence type
This commit is contained in:
André Silva
2020-05-06 17:25:51 +01:00
committed by GitHub
parent a1127f8f9d
commit fbd2ac8f3b
38 changed files with 2249 additions and 296 deletions
@@ -23,11 +23,18 @@ extern crate alloc;
#[cfg(feature = "std")]
use serde::Serialize;
use codec::{Encode, Decode, Input, Codec};
use sp_runtime::{ConsensusEngineId, RuntimeDebug};
use sp_runtime::{ConsensusEngineId, RuntimeDebug, traits::NumberFor};
use sp_std::borrow::Cow;
use sp_std::vec::Vec;
#[cfg(feature = "std")]
use log::debug;
/// Key type for GRANDPA module.
pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::GRANDPA;
mod app {
use sp_application_crypto::{app_crypto, key_types::GRANDPA, ed25519};
app_crypto!(ed25519, GRANDPA);
@@ -157,6 +164,242 @@ impl<N: Codec> ConsensusLog<N> {
}
}
/// Proof of voter misbehavior on a given set id. Misbehavior/equivocation in
/// GRANDPA happens when a voter votes on the same round (either at prevote or
/// precommit stage) for different blocks. Proving is achieved by collecting the
/// signed messages of conflicting votes.
#[derive(Clone, Debug, Decode, Encode, PartialEq)]
pub struct EquivocationProof<H, N> {
set_id: SetId,
equivocation: Equivocation<H, N>,
}
impl<H, N> EquivocationProof<H, N> {
/// Create a new `EquivocationProof` for the given set id and using the
/// given equivocation as proof.
pub fn new(set_id: SetId, equivocation: Equivocation<H, N>) -> Self {
EquivocationProof {
set_id,
equivocation,
}
}
/// Returns the set id at which the equivocation occurred.
pub fn set_id(&self) -> SetId {
self.set_id
}
/// Returns the round number at which the equivocation occurred.
pub fn round(&self) -> RoundNumber {
match self.equivocation {
Equivocation::Prevote(ref equivocation) => equivocation.round_number,
Equivocation::Precommit(ref equivocation) => equivocation.round_number,
}
}
/// Returns the authority id of the equivocator.
pub fn offender(&self) -> &AuthorityId {
self.equivocation.offender()
}
}
/// Wrapper object for GRANDPA equivocation proofs, useful for unifying prevote
/// and precommit equivocations under a common type.
#[derive(Clone, Debug, Decode, Encode, PartialEq)]
pub enum Equivocation<H, N> {
/// Proof of equivocation at prevote stage.
Prevote(grandpa::Equivocation<AuthorityId, grandpa::Prevote<H, N>, AuthoritySignature>),
/// Proof of equivocation at precommit stage.
Precommit(grandpa::Equivocation<AuthorityId, grandpa::Precommit<H, N>, AuthoritySignature>),
}
impl<H, N> From<grandpa::Equivocation<AuthorityId, grandpa::Prevote<H, N>, AuthoritySignature>>
for Equivocation<H, N>
{
fn from(
equivocation: grandpa::Equivocation<
AuthorityId,
grandpa::Prevote<H, N>,
AuthoritySignature,
>,
) -> Self {
Equivocation::Prevote(equivocation)
}
}
impl<H, N> From<grandpa::Equivocation<AuthorityId, grandpa::Precommit<H, N>, AuthoritySignature>>
for Equivocation<H, N>
{
fn from(
equivocation: grandpa::Equivocation<
AuthorityId,
grandpa::Precommit<H, N>,
AuthoritySignature,
>,
) -> Self {
Equivocation::Precommit(equivocation)
}
}
impl<H, N> Equivocation<H, N> {
/// Returns the authority id of the equivocator.
pub fn offender(&self) -> &AuthorityId {
match self {
Equivocation::Prevote(ref equivocation) => &equivocation.identity,
Equivocation::Precommit(ref equivocation) => &equivocation.identity,
}
}
}
/// Verifies the equivocation proof by making sure that both votes target
/// different blocks and that its signatures are valid.
pub fn check_equivocation_proof<H, N>(report: EquivocationProof<H, N>) -> Result<(), ()>
where
H: Clone + Encode + PartialEq,
N: Clone + Encode + PartialEq,
{
// NOTE: the bare `Prevote` and `Precommit` types don't share any trait,
// this is implemented as a macro to avoid duplication.
macro_rules! check {
( $equivocation:expr, $message:expr ) => {
// if both votes have the same target the equivocation is invalid.
if $equivocation.first.0.target_hash == $equivocation.second.0.target_hash &&
$equivocation.first.0.target_number == $equivocation.second.0.target_number
{
return Err(());
}
// check signatures on both votes are valid
check_message_signature(
&$message($equivocation.first.0),
&$equivocation.identity,
&$equivocation.first.1,
$equivocation.round_number,
report.set_id,
)?;
check_message_signature(
&$message($equivocation.second.0),
&$equivocation.identity,
&$equivocation.second.1,
$equivocation.round_number,
report.set_id,
)?;
return Ok(());
};
}
match report.equivocation {
Equivocation::Prevote(equivocation) => {
check!(equivocation, grandpa::Message::Prevote);
}
Equivocation::Precommit(equivocation) => {
check!(equivocation, grandpa::Message::Precommit);
}
}
}
/// Encode round message localized to a given round and set id.
pub fn localized_payload<E: Encode>(round: RoundNumber, set_id: SetId, message: &E) -> Vec<u8> {
let mut buf = Vec::new();
localized_payload_with_buffer(round, set_id, message, &mut buf);
buf
}
/// Encode round message localized to a given round and set id using the given
/// buffer. The given buffer will be cleared and the resulting encoded payload
/// will always be written to the start of the buffer.
pub fn localized_payload_with_buffer<E: Encode>(
round: RoundNumber,
set_id: SetId,
message: &E,
buf: &mut Vec<u8>,
) {
buf.clear();
(message, round, set_id).encode_to(buf)
}
/// Check a message signature by encoding the message as a localized payload and
/// verifying the provided signature using the expected authority id.
pub fn check_message_signature<H, N>(
message: &grandpa::Message<H, N>,
id: &AuthorityId,
signature: &AuthoritySignature,
round: RoundNumber,
set_id: SetId,
) -> Result<(), ()>
where
H: Encode,
N: Encode,
{
check_message_signature_with_buffer(message, id, signature, round, set_id, &mut Vec::new())
}
/// Check a message signature by encoding the message as a localized payload and
/// verifying the provided signature using the expected authority id.
/// The encoding necessary to verify the signature will be done using the given
/// buffer, the original content of the buffer will be cleared.
pub fn check_message_signature_with_buffer<H, N>(
message: &grandpa::Message<H, N>,
id: &AuthorityId,
signature: &AuthoritySignature,
round: RoundNumber,
set_id: SetId,
buf: &mut Vec<u8>,
) -> Result<(), ()>
where
H: Encode,
N: Encode,
{
localized_payload_with_buffer(round, set_id, message, buf);
#[cfg(not(feature = "std"))]
let verify = || {
use sp_application_crypto::RuntimeAppPublic;
id.verify(&buf, signature)
};
#[cfg(feature = "std")]
let verify = || {
use sp_application_crypto::Pair;
AuthorityPair::verify(signature, &buf, &id)
};
if verify() {
Ok(())
} else {
#[cfg(feature = "std")]
debug!(target: "afg", "Bad signature on message from {:?}", id);
Err(())
}
}
/// Localizes the message to the given set and round and signs the payload.
#[cfg(feature = "std")]
pub fn sign_message<H, N>(
message: grandpa::Message<H, N>,
pair: &AuthorityPair,
round: RoundNumber,
set_id: SetId,
) -> grandpa::SignedMessage<H, N, AuthoritySignature, AuthorityId>
where
H: Encode,
N: Encode,
{
use sp_core::Pair;
let encoded = localized_payload(round, set_id, &message);
let signature = pair.sign(&encoded[..]);
grandpa::SignedMessage {
message,
signature,
id: pair.public(),
}
}
/// WASM function call to check for pending changes.
pub const PENDING_CHANGE_CALL: &str = "grandpa_pending_change";
/// WASM function call to get current GRANDPA authorities.
@@ -211,6 +454,29 @@ impl<'a> Decode for VersionedAuthorityList<'a> {
}
}
/// 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> {
codec::Decode::decode(&mut &self.0[..]).ok()
}
}
sp_api::decl_runtime_apis! {
/// APIs for integrating the GRANDPA finality gadget into runtimes.
/// This should be implemented on the runtime side.
@@ -230,5 +496,32 @@ sp_api::decl_runtime_apis! {
/// used to finalize descendants of this block (B+1, B+2, ...). The block B itself
/// is finalized by the authorities from block B-1.
fn grandpa_authorities() -> AuthorityList;
/// Submits an 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`). This method will
/// sign the extrinsic with any reporting keys available in the keystore
/// and will push the transaction to the pool.
/// Only useful in an offchain context.
fn submit_report_equivocation_extrinsic(
equivocation_proof: EquivocationProof<Block::Hash, NumberFor<Block>>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
/// Generates a proof of key ownership for the given authority in the
/// given set. 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 `set_id` as parameter the current
/// implementations ignore this parameter and instead rely on this
/// method being called at the correct block height, i.e. any point at
/// which the given set id 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(
set_id: SetId,
authority_id: AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;
}
}