mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 04:28:01 +00:00
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: 5162572170f1a5f651Author: 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: ac98248c62e68c80c2Author: 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:
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user