From df8a6d4a2ba283bd867c803ad664290a994ac062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Sat, 17 Aug 2019 17:16:09 +0200 Subject: [PATCH 01/78] session: runtime api for generating session membership proofs --- Cargo.lock | 3 +++ core/session/Cargo.toml | 17 +++++++++++++---- core/session/src/lib.rs | 27 +++++++++++++++++++++++++-- node/runtime/src/lib.rs | 14 +++++++++++++- srml/session/Cargo.toml | 4 +++- srml/session/src/historical.rs | 14 ++++---------- 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cf8b42bd41d6..d876dcc129373 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4319,6 +4319,7 @@ dependencies = [ "srml-timestamp 2.0.0", "substrate-application-crypto 2.0.0", "substrate-primitives 2.0.0", + "substrate-session 2.0.0", "substrate-trie 2.0.0", ] @@ -5509,7 +5510,9 @@ dependencies = [ name = "substrate-session" version = "2.0.0" dependencies = [ + "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "sr-primitives 2.0.0", + "sr-staking-primitives 2.0.0", "sr-std 2.0.0", "substrate-client 2.0.0", "substrate-primitives 2.0.0", diff --git a/core/session/Cargo.toml b/core/session/Cargo.toml index 5d8cb3f0001ba..352460e5696c6 100644 --- a/core/session/Cargo.toml +++ b/core/session/Cargo.toml @@ -6,10 +6,19 @@ edition = "2018" [dependencies] client = { package = "substrate-client", path = "../client", default-features = false } -rstd = { package = "sr-std", path = "../sr-std", default-features = false } -sr-primitives = { path = "../sr-primitives", optional = true } +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } primitives = { package = "substrate-primitives", path = "../primitives", optional = true } +rstd = { package = "sr-std", path = "../sr-std", default-features = false } +sr-primitives = { path = "../sr-primitives", default-features = false } +sr-staking-primitives = { path = "../sr-staking-primitives", default-features = false } [features] -default = [ "std" ] -std = [ "client/std", "rstd/std", "sr-primitives", "primitives" ] +default = ["std"] +std = [ + "client/std", + "codec/std", + "primitives/std", + "rstd/std", + "sr-primitives/std", + "sr-staking-primitives/std", +] diff --git a/core/session/src/lib.rs b/core/session/src/lib.rs index 1b40d2d9ba815..8023bc2a1861b 100644 --- a/core/session/src/lib.rs +++ b/core/session/src/lib.rs @@ -18,12 +18,15 @@ #![cfg_attr(not(feature = "std"), no_std)] +use codec::{Encode, Decode}; use rstd::vec::Vec; +use sr_primitives::KeyTypeId; +use sr_staking_primitives::SessionIndex; -#[cfg(feature = "std")] -use sr_primitives::traits::{ProvideRuntimeApi, Block as BlockT}; #[cfg(feature = "std")] use primitives::{H256, Blake2Hasher}; +#[cfg(feature = "std")] +use sr_primitives::traits::{ProvideRuntimeApi, Block as BlockT}; client::decl_runtime_apis! { /// Session keys runtime api. @@ -37,6 +40,26 @@ client::decl_runtime_apis! { /// Returns the concatenated SCALE encoded public keys. fn generate_session_keys(seed: Option>) -> Vec; } + + /// Historical session membership runtime api. + pub trait SessionMembership { + /// Generates a proof that the given session key is a part of the + /// current session. The generated proof can later on be validated with + /// the historical session module. Proofs of membership are useful e.g. + /// for validating misbehavior reports. + fn generate_session_membership_proof( + session_key: (KeyTypeId, Vec), + ) -> Option; + } +} + +/// Proof of membership of a specific key in a given session. +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, Debug)] +pub struct MembershipProof { + /// The session index on which the specific key is a member. + pub session: SessionIndex, + /// Trie nodes of a merkle proof of session membership. + pub trie_nodes: Vec>, } /// Generate the initial session keys with the given seeds and store them in diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index fdf0bfdc144bb..7d8b406206a82 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -36,7 +36,7 @@ use client::{ runtime_api as client_api, impl_runtime_apis }; use sr_primitives::{ - Permill, Perbill, ApplyResult, impl_opaque_keys, generic, create_runtime_str, key_types + ApplyResult, KeyTypeId, Perbill, Permill, impl_opaque_keys, generic, create_runtime_str, key_types, }; use sr_primitives::curve::PiecewiseLinear; use sr_primitives::transaction_validity::TransactionValidity; @@ -49,6 +49,7 @@ use elections::VoteIndex; #[cfg(any(feature = "std", test))] use version::NativeVersion; use primitives::OpaqueMetadata; +use session::historical; use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; use im_online::sr25519::{AuthorityId as ImOnlineId}; use authority_discovery_primitives::{AuthorityId as EncodedAuthorityId, Signature as EncodedSignature}; @@ -527,6 +528,7 @@ construct_runtime!( ImOnline: im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, AuthorityDiscovery: authority_discovery::{Module, Call, Config}, Offences: offences::{Module, Call, Storage, Event}, + Historical: historical::{Module}, RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage}, } ); @@ -707,6 +709,16 @@ impl_runtime_apis! { SessionKeys::generate(seed) } } + + impl substrate_session::SessionMembership for Runtime { + fn generate_session_membership_proof( + session_key: (KeyTypeId, Vec), + ) -> Option { + use support::traits::KeyOwnerProofSystem; + + Historical::prove(session_key) + } + } } #[cfg(test)] diff --git a/srml/session/Cargo.toml b/srml/session/Cargo.toml index b114755ad91c2..bbf47f7b95e82 100644 --- a/srml/session/Cargo.toml +++ b/srml/session/Cargo.toml @@ -9,6 +9,7 @@ serde = { version = "1.0.101", optional = true } safe-mix = { version = "1.0.0", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } +session-primitives = { package = "substrate-session", path = "../../core/session", default-features = false } sr-primitives = { path = "../../core/sr-primitives", default-features = false } sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } support = { package = "srml-support", path = "../support", default-features = false } @@ -31,9 +32,10 @@ std = [ "safe-mix/std", "codec/std", "rstd/std", - "support/std", + "session-primitives/std", "sr-primitives/std", "sr-staking-primitives/std", + "support/std", "timestamp/std", "substrate-trie/std", "runtime-io/std", diff --git a/srml/session/src/historical.rs b/srml/session/src/historical.rs index 08e4a6ce3175f..0427cd260e5d2 100644 --- a/srml/session/src/historical.rs +++ b/srml/session/src/historical.rs @@ -27,6 +27,7 @@ use rstd::prelude::*; use codec::{Encode, Decode}; +use session_primitives::MembershipProof; use sr_primitives::KeyTypeId; use sr_primitives::traits::{Convert, OpaqueKeys, Hash as HashT}; use support::{decl_module, decl_storage}; @@ -268,17 +269,10 @@ impl ProvingTrie { } -/// Proof of ownership of a specific key. -#[derive(Encode, Decode, Clone)] -pub struct Proof { - session: SessionIndex, - trie_nodes: Vec>, -} - impl> support::traits::KeyOwnerProofSystem<(KeyTypeId, D)> for Module { - type Proof = Proof; + type Proof = MembershipProof; type IdentificationTuple = IdentificationTuple; fn prove(key: (KeyTypeId, D)) -> Option { @@ -287,13 +281,13 @@ impl> support::traits::KeyOwnerProofSystem<(KeyTypeId, let (id, data) = key; - trie.prove(id, data.as_ref()).map(|trie_nodes| Proof { + trie.prove(id, data.as_ref()).map(|trie_nodes| MembershipProof { session, trie_nodes, }) } - fn check_proof(key: (KeyTypeId, D), proof: Proof) -> Option> { + fn check_proof(key: (KeyTypeId, D), proof: Self::Proof) -> Option> { let (id, data) = key; if proof.session == >::current_index() { From a94834eaef4d101cc844a26779a64505601a7188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Sat, 17 Aug 2019 20:07:26 +0200 Subject: [PATCH 02/78] grandpa: add runtime api for creating equivocation report txs --- Cargo.lock | 1 + core/finality-grandpa/primitives/src/lib.rs | 7 ++- node/runtime/src/lib.rs | 18 ++++++ srml/grandpa/Cargo.toml | 1 + srml/grandpa/src/lib.rs | 64 +++++++++++++++++++-- srml/support/src/traits.rs | 4 +- 6 files changed, 87 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d876dcc129373..b349a2f098b76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4193,6 +4193,7 @@ dependencies = [ "srml-session 2.0.0", "srml-support 2.0.0", "srml-system 2.0.0", + "substrate-application-crypto 2.0.0", "substrate-finality-grandpa-primitives 2.0.0", "substrate-primitives 2.0.0", ] diff --git a/core/finality-grandpa/primitives/src/lib.rs b/core/finality-grandpa/primitives/src/lib.rs index a439953899cc7..647fe48c0f28a 100644 --- a/core/finality-grandpa/primitives/src/lib.rs +++ b/core/finality-grandpa/primitives/src/lib.rs @@ -28,7 +28,7 @@ use sr_primitives::ConsensusEngineId; use client::decl_runtime_apis; use rstd::vec::Vec; -mod app { +pub mod app { use app_crypto::{app_crypto, key_types::GRANDPA, ed25519}; app_crypto!(ed25519, GRANDPA); } @@ -173,5 +173,10 @@ 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() -> Vec<(AuthorityId, AuthorityWeight)>; + + fn construct_equivocation_report_extrinsic( + equivocation: (), + key_owner_proof: Vec, + ) -> Option>; } } diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 7d8b406206a82..dbfc495f0a0a2 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -458,6 +458,9 @@ impl authority_discovery::Trait for Runtime { impl grandpa::Trait for Runtime { type Event = Event; + type Call = Call; + type UncheckedExtrinsic = UncheckedExtrinsic; + type KeyOwnerProofSystem = Historical; } parameter_types! { @@ -621,6 +624,21 @@ impl_runtime_apis! { fn grandpa_authorities() -> Vec<(GrandpaId, GrandpaWeight)> { Grandpa::grandpa_authorities() } + + fn construct_equivocation_report_extrinsic( + equivocation: (), + key_owner_proof: Vec, + ) -> Option> { + use codec::{Decode, Encode}; + + let key_owner_proof = Decode::decode(&mut &key_owner_proof[..]).ok()?; + + Grandpa::construct_equivocation_report_extrinsic( + equivocation, + key_owner_proof, + |_call, _key| None, // FIXME: actually create xt here + ).map(|xt| xt.encode()) + } } impl babe_primitives::BabeApi for Runtime { diff --git a/srml/grandpa/Cargo.toml b/srml/grandpa/Cargo.toml index 4b494cfeff8d1..f8087f7fe114d 100644 --- a/srml/grandpa/Cargo.toml +++ b/srml/grandpa/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] +app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false } diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index 610bd18fb3f8a..5a64a0500c2b6 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -34,10 +34,12 @@ use rstd::prelude::*; use codec::{self as codec, Encode, Decode, Error}; use support::{ decl_event, decl_storage, decl_module, dispatch::Result, + storage::StorageValue, storage::StorageMap, traits::KeyOwnerProofSystem, }; use sr_primitives::{ - generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, - Perbill, + generic::{DigestItem, OpaqueDigestItemId}, + traits::{Extrinsic as ExtrinsicT, Zero}, + KeyTypeId, Perbill, }; use sr_staking_primitives::{ SessionIndex, @@ -53,8 +55,24 @@ mod tests; pub trait Trait: system::Trait { /// The event type of this module. type Event: From + Into<::Event>; + + /// The function call. + type Call: From>; + + /// A system for proving ownership of keys, i.e. that a given key was part + /// of a validator set. Needed for validating equivocation reports. + type KeyOwnerProofSystem: KeyOwnerProofSystem< + (KeyTypeId, Vec), + >; + + /// A extrinsic right from the external world. This is unchecked and so + /// can contain a signature. + type UncheckedExtrinsic: ExtrinsicT::Call> + Encode + Decode; } +type KeyOwnerProof = + <::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, Vec)>>::Proof; + /// A stored pending change, old format. // TODO: remove shim // https://github.com/paritytech/substrate/issues/1614 @@ -169,10 +187,19 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; - /// Report some misbehavior. - fn report_misbehavior(origin, _report: Vec) { + // Report some misbehavior. + fn report_equivocation(origin, equivocation: (), proof: KeyOwnerProof) { ensure_signed(origin)?; - // FIXME: https://github.com/paritytech/substrate/issues/1112 + + let id = T::KeyOwnerProofSystem::check_proof( + unimplemented!(), + proof, + ).ok_or("Invalid key ownership proof.")?; + + // TODO: + // - use proper equivocation type + // - validate equivocation + // - report to offences module } fn on_finalize(block_number: T::BlockNumber) { @@ -336,6 +363,33 @@ impl Module { Authorities::put(authorities); } } + + pub fn construct_equivocation_report_extrinsic( + equivocation: (), + key_owner_proof: KeyOwnerProof, + create_extrinsic: F, + ) -> Option where + F: Fn(Call, fg_primitives::app::Public) -> Option, + { + use app_crypto::RuntimeAppPublic; + let local_keys = fg_primitives::app::Public::all(); + + for key in local_keys { + let call = Call::report_equivocation::( + equivocation, + key_owner_proof.clone(), + ); + + let xt = create_extrinsic(call, key); + + // early exit after successful extrinsic creation + if xt.is_some() { + return xt; + } + } + + None + } } impl Module { diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index cd11b56612e74..f7c9967de1195 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -23,7 +23,7 @@ use codec::{FullCodec, Codec, Encode, Decode}; use primitives::u32_trait::Value as U32; use sr_primitives::{ ConsensusEngineId, - traits::{MaybeSerializeDebug, SimpleArithmetic, Saturating}, + traits::{MaybeDebug, MaybeSerializeDebug, SimpleArithmetic, Saturating}, }; /// Anything that can have a `::len()` method. @@ -115,7 +115,7 @@ pub trait VerifySeal { /// key owner. pub trait KeyOwnerProofSystem { /// The proof of membership itself. - type Proof: Codec; + type Proof: Clone + Codec + MaybeDebug + PartialEq; /// The full identification of a key owner and the stash account. type IdentificationTuple: Codec; From 30213e4fd293c6f3977b508d0785f9b842a2af81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Fri, 18 Oct 2019 17:10:11 +0100 Subject: [PATCH 03/78] grandpa: submit signed equivocation report transactions --- Cargo.lock | 1 + core/finality-grandpa/primitives/src/lib.rs | 4 +- node/runtime/Cargo.toml | 2 + node/runtime/src/lib.rs | 48 +++++++++---- srml/grandpa/src/lib.rs | 76 ++++++++++++++------- srml/support/src/traits.rs | 3 +- 6 files changed, 93 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b349a2f098b76..a1536281ec274 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2493,6 +2493,7 @@ dependencies = [ "srml-transaction-payment 2.0.0", "srml-treasury 2.0.0", "srml-utility 2.0.0", + "substrate-application-crypto 2.0.0", "substrate-authority-discovery-primitives 2.0.0", "substrate-client 2.0.0", "substrate-consensus-babe-primitives 2.0.0", diff --git a/core/finality-grandpa/primitives/src/lib.rs b/core/finality-grandpa/primitives/src/lib.rs index 647fe48c0f28a..3a0e318adcc1e 100644 --- a/core/finality-grandpa/primitives/src/lib.rs +++ b/core/finality-grandpa/primitives/src/lib.rs @@ -174,9 +174,9 @@ decl_runtime_apis! { /// is finalized by the authorities from block B-1. fn grandpa_authorities() -> Vec<(AuthorityId, AuthorityWeight)>; - fn construct_equivocation_report_extrinsic( + fn submit_report_equivocation_extrinsic( equivocation: (), key_owner_proof: Vec, - ) -> Option>; + ) -> Option<()>; } } diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index af66c0432f33d..314a86e5e8c67 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -12,6 +12,7 @@ rustc-hex = { version = "2.0", optional = true } safe-mix = { version = "1.0", default-features = false } serde = { version = "1.0.101", optional = true } +app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", default-features = false } authority-discovery-primitives = { package = "substrate-authority-discovery-primitives", path = "../../core/authority-discovery/primitives", default-features = false } babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../core/consensus/babe/primitives", default-features = false } client = { package = "substrate-client", path = "../../core/client", default-features = false } @@ -60,6 +61,7 @@ wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1. [features] default = ["std"] std = [ + "app-crypto/std", "authority-discovery-primitives/std", "authority-discovery/std", "authorship/std", diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index dbfc495f0a0a2..34dfe48f5b8c1 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -436,13 +436,13 @@ impl sudo::Trait for Runtime { type Proposal = Call; } -type SubmitTransaction = TransactionSubmitter; +type SubmitImOnlineTransaction = TransactionSubmitter; impl im_online::Trait for Runtime { type AuthorityId = ImOnlineId; type Call = Call; type Event = Event; - type SubmitTransaction = SubmitTransaction; + type SubmitTransaction = SubmitImOnlineTransaction; type ReportUnresponsiveness = Offences; } @@ -453,14 +453,37 @@ impl offences::Trait for Runtime { } impl authority_discovery::Trait for Runtime { - type AuthorityId = BabeId; + type AuthorityId = BabeId; } +pub mod report { + pub mod app { + use app_crypto::{app_crypto, sr25519, KeyTypeId}; + + pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"rprt"); + app_crypto!(sr25519, KEY_TYPE); + + impl From for node_primitives::Signature { + fn from(sig: Signature) -> Self { + sr25519::Signature::from(sig).into() + } + } + } + + pub type ReporterId = app::Public; +} + +use report::ReporterId; + +type SubmitGrandpaTransaction = TransactionSubmitter; + impl grandpa::Trait for Runtime { type Event = Event; type Call = Call; - type UncheckedExtrinsic = UncheckedExtrinsic; type KeyOwnerProofSystem = Historical; + type SubmitTransaction = SubmitGrandpaTransaction; + type ReportEquivocation = Offences; + type ReportKeyType = ReporterId; } parameter_types! { @@ -625,19 +648,18 @@ impl_runtime_apis! { Grandpa::grandpa_authorities() } - fn construct_equivocation_report_extrinsic( + fn submit_report_equivocation_extrinsic( equivocation: (), key_owner_proof: Vec, - ) -> Option> { - use codec::{Decode, Encode}; + ) -> Option<()> { + use codec::Decode; let key_owner_proof = Decode::decode(&mut &key_owner_proof[..]).ok()?; - Grandpa::construct_equivocation_report_extrinsic( + Grandpa::submit_report_equivocation_extrinsic( equivocation, key_owner_proof, - |_call, _key| None, // FIXME: actually create xt here - ).map(|xt| xt.encode()) + ) } } @@ -668,9 +690,9 @@ impl_runtime_apis! { } fn sign(payload: &Vec) -> Option<(EncodedSignature, EncodedAuthorityId)> { - AuthorityDiscovery::sign(payload).map(|(sig, id)| { - (EncodedSignature(sig.encode()), EncodedAuthorityId(id.encode())) - }) + AuthorityDiscovery::sign(payload).map(|(sig, id)| { + (EncodedSignature(sig.encode()), EncodedAuthorityId(id.encode())) + }) } fn verify(payload: &Vec, signature: &EncodedSignature, authority_id: &EncodedAuthorityId) -> bool { diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index 5a64a0500c2b6..f2f05c76ae1a7 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -31,6 +31,8 @@ pub use substrate_finality_grandpa_primitives as fg_primitives; use rstd::prelude::*; + +use app_crypto::RuntimeAppPublic; use codec::{self as codec, Encode, Decode, Error}; use support::{ decl_event, decl_storage, decl_module, dispatch::Result, @@ -43,11 +45,14 @@ use sr_primitives::{ }; use sr_staking_primitives::{ SessionIndex, - offence::{Offence, Kind}, + offence::{Kind, Offence, ReportOffence}, }; use fg_primitives::{GRANDPA_ENGINE_ID, ScheduledChange, ConsensusLog, SetId, RoundNumber}; pub use fg_primitives::{AuthorityId, AuthorityWeight}; -use system::{ensure_signed, DigestOf}; +use system::{ + ensure_signed, DigestOf, + offchain::SubmitSignedTransaction, +}; mod mock; mod tests; @@ -65,14 +70,29 @@ pub trait Trait: system::Trait { (KeyTypeId, Vec), >; - /// A extrinsic right from the external world. This is unchecked and so - /// can contain a signature. - type UncheckedExtrinsic: ExtrinsicT::Call> + Encode + Decode; + /// A transaction submitter. Used for submitting equivocation reports. + type SubmitTransaction: SubmitSignedTransaction::Call>; + + type ReportEquivocation: + ReportOffence< + Self::AccountId, + KeyOwnerIdentification, + GrandpaEquivocationOffence>, + >; + + /// Key type to use when signing equivocation report transactions, must be + /// convertible to and from an account id since that's what we need to use + /// to sign transactions. + type ReportKeyType: RuntimeAppPublic + From + Into + Clone; } type KeyOwnerProof = <::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, Vec)>>::Proof; +type KeyOwnerIdentification = + <::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, Vec)>> + ::IdentificationTuple; + /// A stored pending change, old format. // TODO: remove shim // https://github.com/paritytech/substrate/issues/1614 @@ -189,7 +209,7 @@ decl_module! { // Report some misbehavior. fn report_equivocation(origin, equivocation: (), proof: KeyOwnerProof) { - ensure_signed(origin)?; + let account_id = ensure_signed(origin)?; let id = T::KeyOwnerProofSystem::check_proof( unimplemented!(), @@ -199,7 +219,19 @@ decl_module! { // TODO: // - use proper equivocation type // - validate equivocation - // - report to offences module + + T::ReportEquivocation::report_offence( + vec![account_id], + GrandpaEquivocationOffence { + time_slot: GrandpaTimeSlot { + set_id: 0, + round: 0, + }, + session_index: SessionIndex::default(), + validator_set_count: 1, + offender: id, + }, + ); } fn on_finalize(block_number: T::BlockNumber) { @@ -364,27 +396,22 @@ impl Module { } } - pub fn construct_equivocation_report_extrinsic( + pub fn submit_report_equivocation_extrinsic( equivocation: (), key_owner_proof: KeyOwnerProof, - create_extrinsic: F, - ) -> Option where - F: Fn(Call, fg_primitives::app::Public) -> Option, - { - use app_crypto::RuntimeAppPublic; - let local_keys = fg_primitives::app::Public::all(); + ) -> Option<()> { + let local_keys = T::ReportKeyType::all(); for key in local_keys { - let call = Call::report_equivocation::( - equivocation, - key_owner_proof.clone(), - ); - - let xt = create_extrinsic(call, key); + let call = Call::report_equivocation(equivocation, key_owner_proof.clone()); + let ext = T::SubmitTransaction::sign_and_submit( + call, + key.into(), + ).ok(); // early exit after successful extrinsic creation - if xt.is_some() { - return xt; + if ext.is_some() { + return Some(()); } } @@ -482,16 +509,15 @@ impl finality_tracker::OnFinalizationStalled for Modul /// A round number and set id which point on the time of an offence. #[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] -struct GrandpaTimeSlot { +pub struct GrandpaTimeSlot { // The order of these matters for `derive(Ord)`. set_id: SetId, round: RoundNumber, } -// TODO [slashing]: Integrate this. /// A grandpa equivocation offence report. #[allow(dead_code)] -struct GrandpaEquivocationOffence { +pub struct GrandpaEquivocationOffence { /// Time slot at which this incident happened. time_slot: GrandpaTimeSlot, /// The session index in which the incident happened. diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index f7c9967de1195..8df0aa828754a 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -116,8 +116,9 @@ pub trait VerifySeal { pub trait KeyOwnerProofSystem { /// The proof of membership itself. type Proof: Clone + Codec + MaybeDebug + PartialEq; + // type Proof: Codec + Clone + PartialEq; /// The full identification of a key owner and the stash account. - type IdentificationTuple: Codec; + type IdentificationTuple: Clone + Codec; /// Prove membership of a key owner in the current block-state. /// From 071b3f40cd2059a29d21e3de96173524c832d42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Fri, 18 Oct 2019 23:23:50 +0100 Subject: [PATCH 04/78] grandpa: use proper equivocation report type --- Cargo.lock | 13 +--- Cargo.toml | 3 + core/finality-grandpa/primitives/Cargo.toml | 10 ++- core/finality-grandpa/primitives/src/lib.rs | 56 +++++++++++++- node/runtime/src/lib.rs | 8 +- srml/grandpa/src/lib.rs | 85 +++++++++++++++------ srml/session/src/historical.rs | 21 +++-- srml/support/src/traits.rs | 13 +++- 8 files changed, 155 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1536281ec274..57f5096329381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -868,10 +868,9 @@ dependencies = [ [[package]] name = "finality-grandpa" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "hashmap_core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1181,11 +1180,6 @@ dependencies = [ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "hashmap_core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "heapsize" version = "0.4.2" @@ -5096,7 +5090,7 @@ name = "substrate-finality-grandpa" version = "2.0.0" dependencies = [ "env_logger 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "finality-grandpa 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "finality-grandpa 0.9.0", "fork-tree 2.0.0", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "futures-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5129,6 +5123,7 @@ dependencies = [ name = "substrate-finality-grandpa-primitives" version = "2.0.0" dependencies = [ + "finality-grandpa 0.9.0", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "sr-primitives 2.0.0", @@ -6791,7 +6786,6 @@ dependencies = [ "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" -"checksum finality-grandpa 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9681c1f75941ea47584573dd2bc10558b2067d460612945887e00744e43393be" "checksum fixed-hash 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "516877b7b9a1cc2d0293cbce23cd6203f0edbfd4090e6ca4489fecb5aa73050e" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3" @@ -6827,7 +6821,6 @@ dependencies = [ "checksum hash256-std-hasher 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" "checksum hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6587d09be37fb98a11cb08b9000a3f592451c1b1b613ca69d949160e313a430a" -"checksum hashmap_core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6852e5a86250521973b0c1d39677166d8a9c0047c908d7e04f1aa04177973c" "checksum heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" diff --git a/Cargo.toml b/Cargo.toml index 5b012be0e98b3..ad3b6a0dee240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,3 +129,6 @@ is-it-maintained-open-issues = { repository = "paritytech/substrate" } [profile.release] # Substrate runtime requires unwinding. panic = "unwind" + +[patch.crates-io] +finality-grandpa = { path = "../finality-grandpa" } diff --git a/core/finality-grandpa/primitives/Cargo.toml b/core/finality-grandpa/primitives/Cargo.toml index 02439d4150d81..97981284046cd 100644 --- a/core/finality-grandpa/primitives/Cargo.toml +++ b/core/finality-grandpa/primitives/Cargo.toml @@ -5,20 +5,22 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -client = { package = "substrate-client", path = "../../client", default-features = false } app-crypto = { package = "substrate-application-crypto", path = "../../application-crypto", default-features = false } +client = { package = "substrate-client", path = "../../client", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -sr-primitives = { path = "../../sr-primitives", default-features = false } +grandpa = { package = "finality-grandpa", version = "0.9.0", default-features = false, features = ["derive-codec"] } rstd = { package = "sr-std", path = "../../sr-std", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } +sr-primitives = { path = "../../sr-primitives", default-features = false } [features] default = ["std"] std = [ + "app-crypto/std", "client/std", "codec/std", - "sr-primitives/std", + "grandpa/std", "rstd/std", "serde", - "app-crypto/std", + "sr-primitives/std", ] diff --git a/core/finality-grandpa/primitives/src/lib.rs b/core/finality-grandpa/primitives/src/lib.rs index 3a0e318adcc1e..11e0e594bf2e9 100644 --- a/core/finality-grandpa/primitives/src/lib.rs +++ b/core/finality-grandpa/primitives/src/lib.rs @@ -23,11 +23,13 @@ extern crate alloc; #[cfg(feature = "std")] use serde::Serialize; -use codec::{Encode, Decode, Codec}; -use sr_primitives::ConsensusEngineId; -use client::decl_runtime_apis; + use rstd::vec::Vec; +use client::decl_runtime_apis; +use codec::{Encode, Decode, Codec}; +use sr_primitives::{ConsensusEngineId, traits::NumberFor}; + pub mod app { use app_crypto::{app_crypto, key_types::GRANDPA, ed25519}; app_crypto!(ed25519, GRANDPA); @@ -149,6 +151,52 @@ impl ConsensusLog { } } +#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Clone, Decode, Encode, PartialEq)] +pub struct EquivocationReport { + set_id: SetId, + equivocation: Equivocation, +} + +impl EquivocationReport { + pub fn set_id(&self) -> SetId { + self.set_id + } + + pub fn round(&self) -> RoundNumber { + match self.equivocation { + Equivocation::Prevote(ref equivocation) => equivocation.round_number, + Equivocation::Precommit(ref equivocation) => equivocation.round_number, + } + } + + pub fn offender(&self) -> &AuthorityId { + match self.equivocation { + Equivocation::Prevote(ref equivocation) => &equivocation.identity, + Equivocation::Precommit(ref equivocation) => &equivocation.identity, + } + } +} + +#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Clone, Decode, Encode, PartialEq)] +pub enum Equivocation { + Prevote( + grandpa::Equivocation< + AuthorityId, + grandpa::Prevote, + AuthoritySignature, + >, + ), + Precommit( + grandpa::Equivocation< + AuthorityId, + grandpa::Precommit, + AuthoritySignature, + >, + ), +} + /// 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. @@ -175,7 +223,7 @@ decl_runtime_apis! { fn grandpa_authorities() -> Vec<(AuthorityId, AuthorityWeight)>; fn submit_report_equivocation_extrinsic( - equivocation: (), + equivocation_report: EquivocationReport>, key_owner_proof: Vec, ) -> Option<()>; } diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 34dfe48f5b8c1..04b3e5c5e9abb 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -649,15 +649,13 @@ impl_runtime_apis! { } fn submit_report_equivocation_extrinsic( - equivocation: (), + equivocation_report: fg_primitives::EquivocationReport, key_owner_proof: Vec, ) -> Option<()> { - use codec::Decode; - - let key_owner_proof = Decode::decode(&mut &key_owner_proof[..]).ok()?; + let key_owner_proof = codec::Decode::decode(&mut &key_owner_proof[..]).ok()?; Grandpa::submit_report_equivocation_extrinsic( - equivocation, + equivocation_report, key_owner_proof, ) } diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index f2f05c76ae1a7..7a1d977ec7083 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -32,22 +32,26 @@ pub use substrate_finality_grandpa_primitives as fg_primitives; use rstd::prelude::*; -use app_crypto::RuntimeAppPublic; +use app_crypto::{key_types::GRANDPA, RuntimeAppPublic}; use codec::{self as codec, Encode, Decode, Error}; use support::{ - decl_event, decl_storage, decl_module, dispatch::Result, - storage::StorageValue, storage::StorageMap, traits::KeyOwnerProofSystem, + decl_event, decl_storage, decl_module, + dispatch::Result, + traits::KeyOwnerProofSystem, }; use sr_primitives::{ generic::{DigestItem, OpaqueDigestItemId}, - traits::{Extrinsic as ExtrinsicT, Zero}, + traits::Zero, KeyTypeId, Perbill, }; use sr_staking_primitives::{ SessionIndex, offence::{Kind, Offence, ReportOffence}, }; -use fg_primitives::{GRANDPA_ENGINE_ID, ScheduledChange, ConsensusLog, SetId, RoundNumber}; +use fg_primitives::{ + GRANDPA_ENGINE_ID, + ConsensusLog, EquivocationReport, RoundNumber, SetId, ScheduledChange, +}; pub use fg_primitives::{AuthorityId, AuthorityWeight}; use system::{ ensure_signed, DigestOf, @@ -65,9 +69,12 @@ pub trait Trait: system::Trait { type Call: From>; /// A system for proving ownership of keys, i.e. that a given key was part - /// of a validator set. Needed for validating equivocation reports. + /// of a validator set, needed for validating equivocation reports. The + /// session index and validator count of the session are part of the proof + /// as extra data. type KeyOwnerProofSystem: KeyOwnerProofSystem< (KeyTypeId, Vec), + ExtraData = (SessionIndex, u32), >; /// A transaction submitter. Used for submitting equivocation reports. @@ -78,7 +85,7 @@ pub trait Trait: system::Trait { Self::AccountId, KeyOwnerIdentification, GrandpaEquivocationOffence>, - >; + >; /// Key type to use when signing equivocation report transactions, must be /// convertible to and from an account id since that's what we need to use @@ -208,28 +215,54 @@ decl_module! { fn deposit_event() = default; // Report some misbehavior. - fn report_equivocation(origin, equivocation: (), proof: KeyOwnerProof) { - let account_id = ensure_signed(origin)?; - - let id = T::KeyOwnerProofSystem::check_proof( - unimplemented!(), - proof, - ).ok_or("Invalid key ownership proof.")?; + fn report_equivocation( + origin, + equivocation_report: EquivocationReport, + key_owner_proof: KeyOwnerProof, + ) { + let reporter_id = ensure_signed(origin)?; + + // validate the membership proof and extract session index and + // validator set count of the session that we're proving membership + // of + let (offender, (session_index, validator_set_count)) = + T::KeyOwnerProofSystem::check_proof( + (GRANDPA, equivocation_report.offender().encode()), + key_owner_proof, + ).ok_or("Invalid key ownership proof.")?; // TODO: - // - use proper equivocation type - // - validate equivocation + // - validate equivocation proof + + // we check the equivocation within the context of its set id (and + // associated session). + let set_id = equivocation_report.set_id(); + + let previous_set_id_session_index = SetIdSession::get(set_id.saturating_sub(1)); + let set_id_session_index = SetIdSession::get(set_id) + .ok_or("Invalid set id provided.")?; + + // check that the session id for the membership proof is within the + // bounds of the set id reported in the equivocation. + if session_index > set_id_session_index || + previous_set_id_session_index + .map(|previous_index| session_index <= previous_index) + .unwrap_or(false) + { + return Err("Invalid set id provided."); + } + // report to the offences module rewarding the sender. T::ReportEquivocation::report_offence( - vec![account_id], + vec![reporter_id], GrandpaEquivocationOffence { + session_index, + validator_set_count, + offender, time_slot: GrandpaTimeSlot { - set_id: 0, - round: 0, + set_id, + round: equivocation_report.round(), }, - session_index: SessionIndex::default(), - validator_set_count: 1, - offender: id, }, ); } @@ -397,13 +430,17 @@ impl Module { } pub fn submit_report_equivocation_extrinsic( - equivocation: (), + equivocation_report: EquivocationReport, key_owner_proof: KeyOwnerProof, ) -> Option<()> { let local_keys = T::ReportKeyType::all(); for key in local_keys { - let call = Call::report_equivocation(equivocation, key_owner_proof.clone()); + let call = Call::report_equivocation( + equivocation_report.clone(), + key_owner_proof.clone(), + ); + let ext = T::SubmitTransaction::sign_and_submit( call, key.into(), diff --git a/srml/session/src/historical.rs b/srml/session/src/historical.rs index 0427cd260e5d2..5a67441606d14 100644 --- a/srml/session/src/historical.rs +++ b/srml/session/src/historical.rs @@ -275,6 +275,10 @@ impl> support::traits::KeyOwnerProofSystem<(KeyTypeId, type Proof = MembershipProof; type IdentificationTuple = IdentificationTuple; + // The session index of a given membership proof and the validator count for + // that session. + type ExtraData = (SessionIndex, ValidatorCount); + fn prove(key: (KeyTypeId, D)) -> Option { let session = >::current_index(); let trie = ProvingTrie::::generate_for(session).ok()?; @@ -287,18 +291,25 @@ impl> support::traits::KeyOwnerProofSystem<(KeyTypeId, }) } - fn check_proof(key: (KeyTypeId, D), proof: Self::Proof) -> Option> { + fn check_proof( + key: (KeyTypeId, D), + proof: Self::Proof, + ) -> Option<(IdentificationTuple, Self::ExtraData)> { let (id, data) = key; if proof.session == >::current_index() { - >::key_owner(id, data.as_ref()).and_then(|owner| - T::FullIdentificationOf::convert(owner.clone()).map(move |id| (owner, id)) - ) + >::key_owner(id, data.as_ref()).and_then(|owner| { + T::FullIdentificationOf::convert(owner.clone()).map(move |id| { + let count = >::validators().len() as u32; + ((owner, id), (proof.session, count)) + }) + }) } else { - let (root, _) = >::get(&proof.session)?; + let (root, count) = >::get(&proof.session)?; let trie = ProvingTrie::::from_nodes(root, &proof.trie_nodes); trie.query(id, data.as_ref()) + .map(|id| (id, (proof.session, count))) } } } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 8df0aa828754a..745dee03a0fb5 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -116,10 +116,16 @@ pub trait VerifySeal { pub trait KeyOwnerProofSystem { /// The proof of membership itself. type Proof: Clone + Codec + MaybeDebug + PartialEq; - // type Proof: Codec + Clone + PartialEq; /// The full identification of a key owner and the stash account. type IdentificationTuple: Clone + Codec; + /// Arbitrary data associated with a given key ownership proof, useful for + /// providing context to the proof. For example, if the proof is proving + /// ownership of a key in a **numbered** validator set, then the context of + /// the proof could be the number associated with that validator set (e.g. a + /// session index). + type ExtraData; + /// Prove membership of a key owner in the current block-state. /// /// This should typically only be called off-chain, since it may be @@ -131,7 +137,10 @@ pub trait KeyOwnerProofSystem { /// Check a proof of membership on-chain. Return `Some` iff the proof is /// valid and recent enough to check. - fn check_proof(key: Key, proof: Self::Proof) -> Option; + fn check_proof( + key: Key, + proof: Self::Proof, + ) -> Option<(Self::IdentificationTuple, Self::ExtraData)>; } /// Handler for when some currency "account" decreased in balance for From 6d98987711f7601527adb7c7e8809fa6e2c70331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Sat, 19 Oct 2019 16:48:17 +0100 Subject: [PATCH 05/78] grandpa: report equivocations --- Cargo.lock | 1 + core/finality-grandpa/Cargo.toml | 1 + core/finality-grandpa/primitives/src/lib.rs | 53 +++++++- core/finality-grandpa/src/authorities.rs | 41 ++++++ core/finality-grandpa/src/environment.rs | 137 +++++++++++++++++--- core/finality-grandpa/src/lib.rs | 46 ++++--- core/finality-grandpa/src/observer.rs | 5 +- node/cli/src/service.rs | 2 +- node/runtime/src/lib.rs | 4 +- srml/grandpa/src/lib.rs | 16 ++- 10 files changed, 260 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57f5096329381..5c88cedb25cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5111,6 +5111,7 @@ dependencies = [ "substrate-keystore 2.0.0", "substrate-network 2.0.0", "substrate-primitives 2.0.0", + "substrate-session 2.0.0", "substrate-telemetry 2.0.0", "substrate-test-runtime-client 2.0.0", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/core/finality-grandpa/Cargo.toml b/core/finality-grandpa/Cargo.toml index 84178c8a78fd5..dfeb88a357a0d 100644 --- a/core/finality-grandpa/Cargo.toml +++ b/core/finality-grandpa/Cargo.toml @@ -18,6 +18,7 @@ sr-primitives = { path = "../sr-primitives" } consensus_common = { package = "substrate-consensus-common", path = "../consensus/common" } primitives = { package = "substrate-primitives", path = "../primitives" } substrate-telemetry = { path = "../telemetry" } +substrate-session = { path = "../../core/session", default-features = false } keystore = { package = "substrate-keystore", path = "../keystore" } serde_json = "1.0.41" client = { package = "substrate-client", path = "../client" } diff --git a/core/finality-grandpa/primitives/src/lib.rs b/core/finality-grandpa/primitives/src/lib.rs index 11e0e594bf2e9..096ed21fa1710 100644 --- a/core/finality-grandpa/primitives/src/lib.rs +++ b/core/finality-grandpa/primitives/src/lib.rs @@ -35,6 +35,9 @@ pub mod app { app_crypto!(ed25519, GRANDPA); } +pub const KEY_TYPE: app_crypto::KeyTypeId = + app_crypto::key_types::GRANDPA; + /// The grandpa crypto scheme defined via the keypair type. #[cfg(feature = "std")] pub type AuthorityPair = app::Pair; @@ -151,6 +154,7 @@ impl ConsensusLog { } } +// FIXME: rename to equivocation proof ? #[cfg_attr(feature = "std", derive(Debug))] #[derive(Clone, Decode, Encode, PartialEq)] pub struct EquivocationReport { @@ -159,6 +163,13 @@ pub struct EquivocationReport { } impl EquivocationReport { + pub fn new(set_id: SetId, equivocation: Equivocation) -> Self { + EquivocationReport { + set_id, + equivocation, + } + } + pub fn set_id(&self) -> SetId { self.set_id } @@ -171,10 +182,7 @@ impl EquivocationReport { } pub fn offender(&self) -> &AuthorityId { - match self.equivocation { - Equivocation::Prevote(ref equivocation) => &equivocation.identity, - Equivocation::Precommit(ref equivocation) => &equivocation.identity, - } + self.equivocation.offender() } } @@ -197,6 +205,43 @@ pub enum Equivocation { ), } +impl From, AuthoritySignature>> + for Equivocation +{ + fn from( + equivocation: grandpa::Equivocation< + AuthorityId, + grandpa::Prevote, + AuthoritySignature, + >, + ) -> Self { + Equivocation::Prevote(equivocation) + } +} + +impl From, AuthoritySignature>> + for Equivocation +{ + fn from( + equivocation: grandpa::Equivocation< + AuthorityId, + grandpa::Precommit, + AuthoritySignature, + >, + ) -> Self { + Equivocation::Precommit(equivocation) + } +} + +impl Equivocation { + pub fn offender(&self) -> &AuthorityId { + match self { + Equivocation::Prevote(ref equivocation) => &equivocation.identity, + Equivocation::Precommit(ref equivocation) => &equivocation.identity, + } + } +} + /// 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. diff --git a/core/finality-grandpa/src/authorities.rs b/core/finality-grandpa/src/authorities.rs index 9b83c9feb6871..4cf7ba9086aaf 100644 --- a/core/finality-grandpa/src/authorities.rs +++ b/core/finality-grandpa/src/authorities.rs @@ -123,6 +123,47 @@ where N: Add + Ord + Clone + Debug, H: Clone + Debug { + /// Returns the block height at which the next pending change in the given + /// chain (i.e. it includes `best_hash`) was signalled, `None` if there are + /// no pending changes for the given chain. + /// + /// This is useful since we know that when a change is signalled the + /// underlying runtime authority set management module (e.g. session module) + /// has updated its internal state (e.g. a new session started). + pub(crate) fn next_change_height( + &self, + best_hash: &H, + is_descendent_of: &F, + ) -> Result, fork_tree::Error> where + F: Fn(&H, &H) -> Result, + E: std::error::Error, + { + let mut forced = None; + for change in &self.pending_forced_changes { + if is_descendent_of(&change.canon_hash, best_hash)? { + forced = Some(change.canon_height.clone()); + break; + } + } + + let mut standard = None; + for (_, _, change) in self.pending_standard_changes.roots() { + if is_descendent_of(&change.canon_hash, best_hash)? { + standard = Some(change.canon_height.clone()); + break; + } + } + + let n = match (standard, forced) { + (Some(standard), Some(forced)) => Some(standard.min(forced)), + (Some(standard), None) => Some(standard), + (None, Some(forced)) => Some(forced), + (None, None) => None, + }; + + Ok(n) + } + fn add_standard_change( &mut self, pending: PendingChange, diff --git a/core/finality-grandpa/src/environment.rs b/core/finality-grandpa/src/environment.rs index 70e45848be1dd..256ad6b7f7d66 100644 --- a/core/finality-grandpa/src/environment.rs +++ b/core/finality-grandpa/src/environment.rs @@ -31,15 +31,17 @@ use client::{ blockchain::HeaderBackend, backend::Finalizer, }; use grandpa::{ - BlockNumberOps, Equivocation, Error as GrandpaError, round::State as RoundState, - voter, voter_set::VoterSet, + BlockNumberOps, Error as GrandpaError, round::State as RoundState, + self, voter, voter_set::VoterSet, }; +use header_metadata::HeaderMetadata; use primitives::{Blake2Hasher, H256, Pair}; use sr_primitives::generic::BlockId; use sr_primitives::traits::{ - Block as BlockT, Header as HeaderT, NumberFor, One, Zero, + Block as BlockT, Header as HeaderT, NumberFor, One, ProvideRuntimeApi, Zero, }; use substrate_telemetry::{telemetry, CONSENSUS_INFO}; +use substrate_session::SessionMembership; use crate::{ CommandOrError, Commit, Config, Error, Network, Precommit, Prevote, @@ -52,7 +54,10 @@ use crate::authorities::{AuthoritySet, SharedAuthoritySet}; use crate::consensus_changes::SharedConsensusChanges; use crate::justification::GrandpaJustification; use crate::until_imported::UntilVoteTargetImported; -use fg_primitives::{AuthorityId, AuthoritySignature, SetId, RoundNumber}; +use fg_primitives::{ + AuthorityId, AuthoritySignature, Equivocation, EquivocationReport, + GrandpaApi, RoundNumber, SetId, +}; type HistoricalVotes = grandpa::HistoricalVotes< ::Hash, @@ -368,8 +373,9 @@ impl SharedVoterSetState { } /// The environment we run GRANDPA in. -pub(crate) struct Environment, RA, SC> { +pub(crate) struct Environment, RA, PRA, SC> { pub(crate) inner: Arc>, + pub(crate) api: Arc, pub(crate) select_chain: SC, pub(crate) voters: Arc>, pub(crate) config: Config, @@ -380,7 +386,7 @@ pub(crate) struct Environment, RA, SC> { pub(crate) voter_set_state: SharedVoterSetState, } -impl, RA, SC> Environment { +impl, PRA, RA, SC> Environment { /// Updates the voter set state using the given closure. The write lock is /// held during evaluation of the closure and the environment's voter set /// state is set to its result if successful. @@ -396,9 +402,9 @@ impl, RA, SC> Environment, B, E, N, RA, SC> +impl, B, E, N, RA, PRA, SC> grandpa::Chain> -for Environment +for Environment where Block: 'static, B: Backend + 'static, @@ -519,9 +525,9 @@ pub(crate) fn ancestry, E, RA>( Ok(tree_route.retracted().iter().skip(1).map(|e| e.hash).collect()) } -impl, N, RA, SC> +impl, N, RA, PRA, SC> voter::Environment> -for Environment +for Environment where Block: 'static, B: Backend + 'static, @@ -529,6 +535,8 @@ where N: Network + 'static + Send, N::In: 'static + Send, RA: 'static + Send + Sync, + PRA: ProvideRuntimeApi, + PRA::Api: GrandpaApi + SessionMembership, SC: SelectChain + 'static, NumberFor: BlockNumberOps, { @@ -538,11 +546,11 @@ where // regular round message streams type In = Box, Self::Signature, Self::Id>, + Item = grandpa::SignedMessage, Self::Signature, Self::Id>, Error = Self::Error, > + Send>; type Out = Box>, + SinkItem = grandpa::Message>, SinkError = Self::Error, > + Send>; @@ -818,22 +826,119 @@ where fn prevote_equivocation( &self, _round: RoundNumber, - equivocation: ::grandpa::Equivocation, Self::Signature> + equivocation: grandpa::Equivocation, Self::Signature> ) { warn!(target: "afg", "Detected prevote equivocation in the finality worker: {:?}", equivocation); - // nothing yet; this could craft misbehavior reports of some kind. + report_equivocation( + &*self.inner, + &*self.api, + &self.authority_set.inner().read(), + &self.select_chain, + equivocation.into(), + ); } fn precommit_equivocation( &self, _round: RoundNumber, - equivocation: Equivocation, Self::Signature> + equivocation: grandpa::Equivocation, Self::Signature> ) { warn!(target: "afg", "Detected precommit equivocation in the finality worker: {:?}", equivocation); - // nothing yet + report_equivocation( + &*self.inner, + &*self.api, + &self.authority_set.inner().read(), + &self.select_chain, + equivocation.into(), + ); } } +// TODO: how to make this optional? + +fn report_equivocation( + backend: &B, + api: &PRA, + authority_set: &AuthoritySet>, + select_chain: &SC, + equivocation: Equivocation>, +) -> Result<(), String> where + Block: BlockT, + B: HeaderBackend + HeaderMetadata, + PRA: ProvideRuntimeApi, + PRA::Api: GrandpaApi + SessionMembership, + SC: SelectChain + 'static, +{ + let is_descendent_of = is_descendent_of::<_, _, Block::Hash>(backend, None); + + let best_header = select_chain.best_chain().unwrap(); + let next_change_height = authority_set.next_change_height( + &best_header.hash(), + &is_descendent_of, + ).unwrap(); + + let current_set_latest_height = match next_change_height { + Some(n) if n.is_zero() => + return Err("Authority set change signalled at genesis.".to_string()), + // the next set starts at `n` so the current one lasts until `n - 1`. if + // `n` is later than the best block, then the current set is still live + // at best block. + Some(n) if n > *best_header.number() => *best_header.number(), + Some(n) => n - One::one(), + // there is no pending change, the latest block for the current set is + // the best block. + None => *best_header.number(), + }; + + // FIXME: clean up + // find the header of the latest block in the current set + let current_set_latest_header = { + if current_set_latest_height == *best_header.number() { + best_header.clone() + } else { + let h = backend.header(BlockId::Number(current_set_latest_height)).unwrap().unwrap(); + + // make sure that the given block is in the same chain as "best" + if is_descendent_of(&h.hash(), &best_header.hash()).unwrap() { + h + } else { + let mut current = best_header.clone(); + loop { + if *current.number() == current_set_latest_height { + break; + } + current = backend.header(BlockId::Hash(*current.parent_hash())).unwrap().unwrap(); + } + current + } + } + }; + + // generate membership proof at that block + let membership_proof = api.runtime_api() + .generate_session_membership_proof( + &BlockId::Hash(current_set_latest_header.hash()), + (fg_primitives::KEY_TYPE, equivocation.offender().encode()), + ) + .unwrap(); + + // submit equivocation report at best block + let equivocation_report = EquivocationReport::new( + authority_set.set_id, + equivocation, + ); + + api.runtime_api() + .submit_report_equivocation_extrinsic( + &BlockId::Hash(best_header.hash()), + equivocation_report, + membership_proof.encode(), + ) + .unwrap(); + + Ok(()) +} + pub(crate) enum JustificationOrCommit { Justification(GrandpaJustification), Commit((RoundNumber, Commit)), diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index e4b71acbec463..bbe2902042cef 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -70,6 +70,7 @@ use inherents::InherentDataProviders; use consensus_common::SelectChain; use primitives::{H256, Blake2Hasher, Pair}; use substrate_telemetry::{telemetry, CONSENSUS_INFO, CONSENSUS_DEBUG, CONSENSUS_WARN}; +use substrate_session::SessionMembership; use serde_json; use srml_finality_tracker; @@ -328,8 +329,9 @@ impl fmt::Display for CommandOrError { } } -pub struct LinkHalf, RA, SC> { +pub struct LinkHalf, RA, PRA, SC> { client: Arc>, + api: Arc, select_chain: SC, persistent_data: PersistentData, voter_commands_rx: mpsc::UnboundedReceiver>>, @@ -339,11 +341,11 @@ pub struct LinkHalf, RA, SC> { /// to it. pub fn block_import, RA, PRA, SC>( client: Arc>, - api: &PRA, + api: Arc, select_chain: SC, ) -> Result<( GrandpaBlockImport, - LinkHalf + LinkHalf ), ClientError> where B: Backend + 'static, @@ -384,6 +386,7 @@ where ), LinkHalf { client, + api, select_chain, persistent_data, voter_commands_rx, @@ -466,11 +469,11 @@ fn register_finality_tracker_inherent_data_provider, N, RA, SC, X> { +pub struct GrandpaParams, N, RA, PRA, SC, X> { /// Configuration for the GRANDPA service. pub config: Config, /// A link to the block import worker. - pub link: LinkHalf, + pub link: LinkHalf, /// The Network instance. pub network: N, /// The inherent data providers. @@ -483,8 +486,8 @@ pub struct GrandpaParams, N, RA, SC, X> { /// Run a GRANDPA voter as a task. Provide configuration and a link to a /// block import worker that has already been instantiated with `block_import`. -pub fn run_grandpa_voter, N, RA, SC, X>( - grandpa_params: GrandpaParams, +pub fn run_grandpa_voter, N, RA, PRA, SC, X>( + grandpa_params: GrandpaParams, ) -> client::error::Result + Send + 'static> where Block::Hash: Ord, B: Backend + 'static, @@ -495,6 +498,8 @@ pub fn run_grandpa_voter, N, RA, SC, X>( NumberFor: BlockNumberOps, DigestFor: Encode, RA: Send + Sync + 'static, + PRA: ProvideRuntimeApi + Send + Sync + 'static, + PRA::Api: GrandpaApi + SessionMembership, X: Future + Clone + Send + 'static, { let GrandpaParams { @@ -508,6 +513,7 @@ pub fn run_grandpa_voter, N, RA, SC, X>( let LinkHalf { client, + api, select_chain, persistent_data, voter_commands_rx, @@ -553,6 +559,7 @@ pub fn run_grandpa_voter, N, RA, SC, X>( let voter_work = VoterWork::new( client, + api, config, network, select_chain, @@ -578,25 +585,28 @@ pub fn run_grandpa_voter, N, RA, SC, X>( /// Future that powers the voter. #[must_use] -struct VoterWork, RA, SC> { +struct VoterWork, RA, PRA, SC> { voter: Box>> + Send>, - env: Arc>, + env: Arc>, voter_commands_rx: mpsc::UnboundedReceiver>>, } -impl VoterWork +impl VoterWork where Block: BlockT, N: Network + Sync, N::In: Send + 'static, NumberFor: BlockNumberOps, - RA: 'static + Send + Sync, + RA: Send + Sync + 'static, + PRA: ProvideRuntimeApi + Send + Sync + 'static, + PRA::Api: GrandpaApi + SessionMembership, E: CallExecutor + Send + Sync + 'static, B: Backend + 'static, SC: SelectChain + 'static, { fn new( client: Arc>, + api: Arc, config: Config, network: NetworkBridge, select_chain: SC, @@ -607,6 +617,7 @@ where let voters = persistent_data.authority_set.current_authorities(); let env = Arc::new(Environment { inner: client, + api, select_chain, voters: Arc::new(voters), config, @@ -726,6 +737,7 @@ where voter_set_state: self.env.voter_set_state.clone(), // Fields below are simply transferred and not updated. inner: self.env.inner.clone(), + api: self.env.api.clone(), select_chain: self.env.select_chain.clone(), config: self.env.config.clone(), authority_set: self.env.authority_set.clone(), @@ -755,13 +767,15 @@ where } } -impl Future for VoterWork +impl Future for VoterWork where Block: BlockT, N: Network + Sync, N::In: Send + 'static, NumberFor: BlockNumberOps, - RA: 'static + Send + Sync, + RA: Send + Sync + 'static, + PRA: ProvideRuntimeApi + Send + Sync + 'static, + PRA::Api: GrandpaApi + SessionMembership, E: CallExecutor + Send + Sync + 'static, B: Backend + 'static, SC: SelectChain + 'static, @@ -809,8 +823,8 @@ where } #[deprecated(since = "1.1", note = "Please switch to run_grandpa_voter.")] -pub fn run_grandpa, N, RA, SC, X>( - grandpa_params: GrandpaParams, +pub fn run_grandpa, N, RA, PRA, SC, X>( + grandpa_params: GrandpaParams, ) -> ::client::error::Result + Send + 'static> where Block::Hash: Ord, B: Backend + 'static, @@ -821,6 +835,8 @@ pub fn run_grandpa, N, RA, SC, X>( NumberFor: BlockNumberOps, DigestFor: Encode, RA: Send + Sync + 'static, + PRA: ProvideRuntimeApi + Send + Sync + 'static, + PRA::Api: GrandpaApi + SessionMembership, X: Future + Clone + Send + 'static, { run_grandpa_voter(grandpa_params) diff --git a/core/finality-grandpa/src/observer.rs b/core/finality-grandpa/src/observer.rs index 39eeafcb1b141..25834a3a18260 100644 --- a/core/finality-grandpa/src/observer.rs +++ b/core/finality-grandpa/src/observer.rs @@ -150,9 +150,9 @@ fn grandpa_observer, RA, S, F>( /// listening for and validating GRANDPA commits instead of following the full /// protocol. Provide configuration and a link to a block import worker that has /// already been instantiated with `block_import`. -pub fn run_grandpa_observer, N, RA, SC>( +pub fn run_grandpa_observer, N, RA, PRA, SC>( config: Config, - link: LinkHalf, + link: LinkHalf, network: N, on_exit: impl Future + Clone + Send + 'static, ) -> ::client::error::Result + Send + 'static> where @@ -169,6 +169,7 @@ pub fn run_grandpa_observer, N, RA, SC>( select_chain: _, persistent_data, voter_commands_rx, + .. } = link; let (network, network_startup) = NetworkBridge::new( diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 485cd325b0ec1..37db4537299cf 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -71,7 +71,7 @@ macro_rules! new_full_start { .ok_or_else(|| substrate_service::Error::SelectChainRequired)?; let (grandpa_block_import, grandpa_link) = grandpa::block_import::<_, _, _, node_runtime::RuntimeApi, _, _>( - client.clone(), &*client, select_chain + client.clone(), client.clone(), select_chain )?; let justification_import = grandpa_block_import.clone(); diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 04b3e5c5e9abb..638e58302c772 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -475,13 +475,13 @@ pub mod report { use report::ReporterId; -type SubmitGrandpaTransaction = TransactionSubmitter; +type SubmitReportTransaction = TransactionSubmitter; impl grandpa::Trait for Runtime { type Event = Event; type Call = Call; type KeyOwnerProofSystem = Historical; - type SubmitTransaction = SubmitGrandpaTransaction; + type SubmitTransaction = SubmitReportTransaction; type ReportEquivocation = Offences; type ReportKeyType = ReporterId; } diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index 7a1d977ec7083..8494f2bd3f6e8 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -42,6 +42,7 @@ use support::{ use sr_primitives::{ generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, + weights::SimpleDispatchInfo, KeyTypeId, Perbill, }; use sr_staking_primitives::{ @@ -214,7 +215,11 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; - // Report some misbehavior. + /// Report some misbehavior. + /// + /// FIXME: I have no clue about the weight (but we're checking two + /// ed25519 signatures). + #[weight = SimpleDispatchInfo::FixedOperational(10_000_000)] fn report_equivocation( origin, equivocation_report: EquivocationReport, @@ -223,13 +228,12 @@ decl_module! { let reporter_id = ensure_signed(origin)?; // validate the membership proof and extract session index and - // validator set count of the session that we're proving membership - // of + // validator set count of the session that we're proving membership of let (offender, (session_index, validator_set_count)) = T::KeyOwnerProofSystem::check_proof( (GRANDPA, equivocation_report.offender().encode()), key_owner_proof, - ).ok_or("Invalid key ownership proof.")?; + ).ok_or("Invalid/outdated key ownership proof.")?; // TODO: // - validate equivocation proof @@ -240,7 +244,7 @@ decl_module! { let previous_set_id_session_index = SetIdSession::get(set_id.saturating_sub(1)); let set_id_session_index = SetIdSession::get(set_id) - .ok_or("Invalid set id provided.")?; + .ok_or("Invalid equivocation set id.")?; // check that the session id for the membership proof is within the // bounds of the set id reported in the equivocation. @@ -249,7 +253,7 @@ decl_module! { .map(|previous_index| session_index <= previous_index) .unwrap_or(false) { - return Err("Invalid set id provided."); + return Err("Invalid equivocation set id provided."); } // report to the offences module rewarding the sender. From 9df0d4da628fde6c1d66822df10334a3f819f56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 21 Oct 2019 16:10:47 +0100 Subject: [PATCH 06/78] grandpa: validate equivocation proof --- Cargo.lock | 2 + core/finality-grandpa/primitives/Cargo.toml | 4 + core/finality-grandpa/primitives/src/lib.rs | 104 ++++++++++++++++++ .../src/communication/gossip.rs | 4 +- .../finality-grandpa/src/communication/mod.rs | 43 ++------ core/finality-grandpa/src/justification.rs | 5 +- core/finality-grandpa/src/lib.rs | 1 + srml/grandpa/src/lib.rs | 6 +- 8 files changed, 129 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c88cedb25cb2..08c679a47e028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5125,12 +5125,14 @@ name = "substrate-finality-grandpa-primitives" version = "2.0.0" dependencies = [ "finality-grandpa 0.9.0", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "sr-primitives 2.0.0", "sr-std 2.0.0", "substrate-application-crypto 2.0.0", "substrate-client 2.0.0", + "substrate-primitives 2.0.0", ] [[package]] diff --git a/core/finality-grandpa/primitives/Cargo.toml b/core/finality-grandpa/primitives/Cargo.toml index 97981284046cd..0a40eba8a7415 100644 --- a/core/finality-grandpa/primitives/Cargo.toml +++ b/core/finality-grandpa/primitives/Cargo.toml @@ -9,6 +9,8 @@ app-crypto = { package = "substrate-application-crypto", path = "../../applicati client = { package = "substrate-client", path = "../../client", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.9.0", default-features = false, features = ["derive-codec"] } +log = { version = "0.4.8", optional = true } +primitives = { package = "substrate-primitives", path = "../../primitives", optional = true } rstd = { package = "sr-std", path = "../../sr-std", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } sr-primitives = { path = "../../sr-primitives", default-features = false } @@ -20,6 +22,8 @@ std = [ "client/std", "codec/std", "grandpa/std", + "log", + "primitives", "rstd/std", "serde", "sr-primitives/std", diff --git a/core/finality-grandpa/primitives/src/lib.rs b/core/finality-grandpa/primitives/src/lib.rs index 096ed21fa1710..11f5dc4acd5a0 100644 --- a/core/finality-grandpa/primitives/src/lib.rs +++ b/core/finality-grandpa/primitives/src/lib.rs @@ -26,6 +26,9 @@ use serde::Serialize; use rstd::vec::Vec; +#[cfg(feature = "std")] +use log::debug; + use client::decl_runtime_apis; use codec::{Encode, Decode, Codec}; use sr_primitives::{ConsensusEngineId, traits::NumberFor}; @@ -242,6 +245,107 @@ impl Equivocation { } } +pub fn check_equivocation_report( + report: &EquivocationReport, +) -> 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.clone()), + &$equivocation.identity, + &$equivocation.first.1, + $equivocation.round_number, + report.set_id, + )?; + + check_message_signature( + &$message($equivocation.second.0.clone()), + &$equivocation.identity, + &$equivocation.second.1, + $equivocation.round_number, + report.set_id, + )?; + + return Ok(()); + } + } + + match report.equivocation { + Equivocation::Prevote(ref equivocation) => { + check!(equivocation, grandpa::Message::Prevote); + }, + Equivocation::Precommit(ref equivocation) => { + check!(equivocation, grandpa::Message::Precommit); + }, + } +} + +fn localized_payload( + round: RoundNumber, + set_id: SetId, + message: &E, +) -> Vec { + (message, round, set_id).encode() +} + +// check a message. +pub fn check_message_signature( + message: &grandpa::Message, + id: &AuthorityId, + signature: &AuthoritySignature, + round: RoundNumber, + set_id: SetId, +) -> Result<(), ()> where + H: Encode, + N: Encode, +{ + use app_crypto::RuntimeAppPublic; + + let encoded_raw = localized_payload(round, set_id, message); + if id.verify(&encoded_raw, signature) { + Ok(()) + } else { + #[cfg(feature = "std")] + debug!(target: "afg", "Bad signature on message from {:?}", id); + + Err(()) + } +} + +#[cfg(feature = "std")] +pub fn sign_message( + message: grandpa::Message, + pair: &AuthorityPair, + round: RoundNumber, + set_id: SetId, +) -> grandpa::SignedMessage where + H: Encode, + N: Encode, +{ + use primitives::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. diff --git a/core/finality-grandpa/src/communication/gossip.rs b/core/finality-grandpa/src/communication/gossip.rs index 24402c8a02d3e..ab8528aa3bea7 100644 --- a/core/finality-grandpa/src/communication/gossip.rs +++ b/core/finality-grandpa/src/communication/gossip.rs @@ -86,7 +86,7 @@ use sr_primitives::traits::{NumberFor, Block as BlockT, Zero}; use network::consensus_gossip::{self as network_gossip, MessageIntent, ValidatorContext}; use network::{config::Roles, PeerId}; use codec::{Encode, Decode}; -use fg_primitives::AuthorityId; +use fg_primitives::{check_message_signature, AuthorityId}; use substrate_telemetry::{telemetry, CONSENSUS_DEBUG}; use log::{trace, debug, warn}; @@ -635,7 +635,7 @@ impl Inner { return Action::Discard(cost::UNKNOWN_VOTER); } - if let Err(()) = super::check_message_sig::( + if let Err(()) = check_message_signature( &full.message.message, &full.message.id, &full.message.signature, diff --git a/core/finality-grandpa/src/communication/mod.rs b/core/finality-grandpa/src/communication/mod.rs index ba7bdce336258..5a6c8881f49b5 100644 --- a/core/finality-grandpa/src/communication/mod.rs +++ b/core/finality-grandpa/src/communication/mod.rs @@ -51,6 +51,7 @@ use gossip::{ GossipMessage, FullCatchUpMessage, FullCommitMessage, VoteMessage, GossipValidator }; use fg_primitives::{ + check_message_signature, sign_message, AuthorityPair, AuthorityId, AuthoritySignature, SetId as SetIdNumber, RoundNumber, }; @@ -648,10 +649,6 @@ impl> Clone for NetworkBridge { } } -fn localized_payload(round: RoundNumber, set_id: SetIdNumber, message: &E) -> Vec { - (message, round, set_id).encode() -} - /// Type-safe wrapper around a round number. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)] pub struct Round(pub RoundNumber); @@ -660,24 +657,6 @@ pub struct Round(pub RoundNumber); #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)] pub struct SetId(pub SetIdNumber); -// check a message. -pub(crate) fn check_message_sig( - message: &Message, - id: &AuthorityId, - signature: &AuthoritySignature, - round: RoundNumber, - set_id: SetIdNumber, -) -> Result<(), ()> { - let as_public = id.clone(); - let encoded_raw = localized_payload(round, set_id, message); - if AuthorityPair::verify(signature, &encoded_raw, &as_public) { - Ok(()) - } else { - debug!(target: "afg", "Bad signature on message from {:?}", id); - Err(()) - } -} - /// A sink for outgoing messages to the network. Any messages that are sent will /// be replaced, as appropriate, according to the given `HasVoted`. /// NOTE: The votes are stored unsigned, which means that the signatures need to @@ -718,16 +697,14 @@ impl> Sink for OutgoingMessages } // when locals exist, sign messages on import - if let Some((ref pair, ref local_id)) = self.locals { - let encoded = localized_payload(self.round, self.set_id, &msg); - let signature = pair.sign(&encoded[..]); - + if let Some((ref pair, _)) = self.locals { let target_hash = msg.target().0.clone(); - let signed = SignedMessage:: { - message: msg, - signature, - id: local_id.clone(), - }; + let signed = sign_message( + msg, + pair, + self.round, + self.set_id, + ); let message = GossipMessage::Vote(VoteMessage:: { message: signed.clone(), @@ -808,7 +785,7 @@ fn check_compact_commit( use crate::communication::gossip::Misbehavior; use grandpa::Message as GrandpaMessage; - if let Err(()) = check_message_sig::( + if let Err(()) = check_message_signature( &GrandpaMessage::Precommit(precommit.clone()), id, sig, @@ -894,7 +871,7 @@ fn check_catch_up( for (msg, id, sig) in messages { signatures_checked += 1; - if let Err(()) = check_message_sig::( + if let Err(()) = check_message_signature( &msg, id, sig, diff --git a/core/finality-grandpa/src/justification.rs b/core/finality-grandpa/src/justification.rs index b4de8ff058684..49a29f61e80bd 100644 --- a/core/finality-grandpa/src/justification.rs +++ b/core/finality-grandpa/src/justification.rs @@ -25,10 +25,9 @@ use grandpa::{Error as GrandpaError}; use sr_primitives::generic::BlockId; use sr_primitives::traits::{NumberFor, Block as BlockT, Header as HeaderT}; use primitives::{H256, Blake2Hasher}; -use fg_primitives::AuthorityId; +use fg_primitives::{check_message_signature, AuthorityId}; use crate::{Commit, Error}; -use crate::communication; /// A GRANDPA justification for block finality, it includes a commit message and /// an ancestry proof including all headers routing all precommit target blocks @@ -135,7 +134,7 @@ impl> GrandpaJustification { let mut visited_hashes = HashSet::new(); for signed in self.commit.precommits.iter() { - if let Err(_) = communication::check_message_sig::( + if let Err(_) = check_message_signature( &grandpa::Message::Precommit(signed.precommit.clone()), &signed.id, &signed.signature, diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index bbe2902042cef..10aee92f7fa94 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -114,6 +114,7 @@ mod tests; /// A GRANDPA message for a substrate chain. pub type Message = grandpa::Message<::Hash, NumberFor>; + /// A signed message. pub type SignedMessage = grandpa::SignedMessage< ::Hash, diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index 8494f2bd3f6e8..bc8e3f029844b 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -235,8 +235,10 @@ decl_module! { key_owner_proof, ).ok_or("Invalid/outdated key ownership proof.")?; - // TODO: - // - validate equivocation proof + // validate equivocation proof (check votes are different and + // signatures are valid). + fg_primitives::check_equivocation_report(&equivocation_report) + .map_err(|_| "Invalid equivocation proof.")?; // we check the equivocation within the context of its set id (and // associated session). From e4435d7858e1f890be42339e41164366431ca7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 21 Oct 2019 21:47:00 +0100 Subject: [PATCH 07/78] grandpa: update to finality-grandpa 0.9.1 --- Cargo.lock | 8 +++++--- Cargo.toml | 3 --- core/finality-grandpa/Cargo.toml | 4 ++-- core/finality-grandpa/primitives/Cargo.toml | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08c679a47e028..b42397a32eb0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -867,7 +867,8 @@ dependencies = [ [[package]] name = "finality-grandpa" -version = "0.9.0" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5090,7 +5091,7 @@ name = "substrate-finality-grandpa" version = "2.0.0" dependencies = [ "env_logger 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "finality-grandpa 0.9.0", + "finality-grandpa 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "fork-tree 2.0.0", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "futures-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5124,7 +5125,7 @@ dependencies = [ name = "substrate-finality-grandpa-primitives" version = "2.0.0" dependencies = [ - "finality-grandpa 0.9.0", + "finality-grandpa 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6789,6 +6790,7 @@ dependencies = [ "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" +"checksum finality-grandpa 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34754852da8d86bc509715292c73140a5b678656d0b16132acd6737bdb5fd5f8" "checksum fixed-hash 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "516877b7b9a1cc2d0293cbce23cd6203f0edbfd4090e6ca4489fecb5aa73050e" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3" diff --git a/Cargo.toml b/Cargo.toml index ad3b6a0dee240..5b012be0e98b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,6 +129,3 @@ is-it-maintained-open-issues = { repository = "paritytech/substrate" } [profile.release] # Substrate runtime requires unwinding. panic = "unwind" - -[patch.crates-io] -finality-grandpa = { path = "../finality-grandpa" } diff --git a/core/finality-grandpa/Cargo.toml b/core/finality-grandpa/Cargo.toml index dfeb88a357a0d..68699c50aee77 100644 --- a/core/finality-grandpa/Cargo.toml +++ b/core/finality-grandpa/Cargo.toml @@ -27,10 +27,10 @@ inherents = { package = "substrate-inherents", path = "../../core/inherents" } network = { package = "substrate-network", path = "../network" } srml-finality-tracker = { path = "../../srml/finality-tracker" } fg_primitives = { package = "substrate-finality-grandpa-primitives", path = "primitives" } -grandpa = { package = "finality-grandpa", version = "0.9.0", features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.9.1", features = ["derive-codec"] } [dev-dependencies] -grandpa = { package = "finality-grandpa", version = "0.9.0", features = ["derive-codec", "test-helpers"] } +grandpa = { package = "finality-grandpa", version = "0.9.1", features = ["derive-codec", "test-helpers"] } network = { package = "substrate-network", path = "../network", features = ["test-helpers"] } keyring = { package = "substrate-keyring", path = "../keyring" } test-client = { package = "substrate-test-runtime-client", path = "../test-runtime/client"} diff --git a/core/finality-grandpa/primitives/Cargo.toml b/core/finality-grandpa/primitives/Cargo.toml index 0a40eba8a7415..ac7c9e33d3667 100644 --- a/core/finality-grandpa/primitives/Cargo.toml +++ b/core/finality-grandpa/primitives/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" app-crypto = { package = "substrate-application-crypto", path = "../../application-crypto", default-features = false } client = { package = "substrate-client", path = "../../client", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.9.0", default-features = false, features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.9.1", default-features = false, features = ["derive-codec"] } log = { version = "0.4.8", optional = true } primitives = { package = "substrate-primitives", path = "../../primitives", optional = true } rstd = { package = "sr-std", path = "../../sr-std", default-features = false } From 0497a8a0d3646040c0c5caa55e7e29c17193ff24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 22 Oct 2019 12:02:31 +0100 Subject: [PATCH 08/78] grandpa: fix encoding of session membership proof --- core/finality-grandpa/src/environment.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/finality-grandpa/src/environment.rs b/core/finality-grandpa/src/environment.rs index 256ad6b7f7d66..406441a6c997a 100644 --- a/core/finality-grandpa/src/environment.rs +++ b/core/finality-grandpa/src/environment.rs @@ -920,6 +920,7 @@ fn report_equivocation( &BlockId::Hash(current_set_latest_header.hash()), (fg_primitives::KEY_TYPE, equivocation.offender().encode()), ) + .unwrap() .unwrap(); // submit equivocation report at best block From b8c1c99d1eeda9de8edf2134e4caff00c9af8afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 22 Oct 2019 12:05:01 +0100 Subject: [PATCH 09/78] grandpa: initialize set id session mapping for genesis session --- srml/grandpa/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index bc8e3f029844b..3489444b50126 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -207,7 +207,14 @@ decl_storage! { } add_extra_genesis { config(authorities): Vec<(AuthorityId, AuthorityWeight)>; - build(|config| Module::::initialize_authorities(&config.authorities)) + build(|config| { + // NOTE: initialize first session of first set. this is necessary + // because we only update this `on_new_session` which isn't called + // for the genesis session. + SetIdSession::insert(0, 0); + + Module::::initialize_authorities(&config.authorities) + }) } } From 18b0d9d7da2c202a9bb7cac2362cb99c6819ad31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 22 Oct 2019 12:22:55 +0100 Subject: [PATCH 10/78] grandpa: fix bug in set_id session validation --- srml/grandpa/src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/srml/grandpa/src/lib.rs b/srml/grandpa/src/lib.rs index 3489444b50126..e909946256205 100644 --- a/srml/grandpa/src/lib.rs +++ b/srml/grandpa/src/lib.rs @@ -251,7 +251,17 @@ decl_module! { // associated session). let set_id = equivocation_report.set_id(); - let previous_set_id_session_index = SetIdSession::get(set_id.saturating_sub(1)); + // fetch the current and previous sets last session index. on the + // genesis set there's no previous set. + let previous_set_id_session_index = if set_id == 0 { + None + } else { + let session_index = SetIdSession::get(set_id - 1) + .ok_or("Invalid equivocation set id.")?; + + Some(session_index) + }; + let set_id_session_index = SetIdSession::get(set_id) .ok_or("Invalid equivocation set id.")?; From c0d5453de7aad52f38e546b1bcc2d28e6f380d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 27 Nov 2019 18:55:37 +0000 Subject: [PATCH 11/78] fix compilation --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 2 + bin/node/runtime/src/lib.rs | 82 ++++++++------------------ frame/grandpa/src/lib.rs | 7 +-- primitives/finality-grandpa/src/lib.rs | 6 +- 5 files changed, 34 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccd5c3c6037f8..eb57b7adbe83b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3158,6 +3158,7 @@ dependencies = [ "sr-staking-primitives 2.0.0", "sr-std 2.0.0", "sr-version 2.0.0", + "substrate-application-crypto 2.0.0", "substrate-authority-discovery-primitives 2.0.0", "substrate-block-builder-runtime-api 2.0.0", "substrate-consensus-babe-primitives 2.0.0", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 3844fa336fd69..76fc908a83c76 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -14,6 +14,7 @@ rustc-hex = { version = "2.0", optional = true } serde = { version = "1.0.102", optional = true } # primitives +app-crypto = { package = "substrate-application-crypto", path = "../../../primitives/application-crypto", default-features = false } authority-discovery-primitives = { package = "substrate-authority-discovery-primitives", path = "../../../primitives/authority-discovery", default-features = false } babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../../primitives/consensus/babe", default-features = false } block-builder-api = { package = "substrate-block-builder-runtime-api", path = "../../../primitives/block-builder/runtime-api", default-features = false} @@ -71,6 +72,7 @@ runtime_io = { package = "sr-io", path = "../../../primitives/sr-io" } [features] default = ["std"] std = [ + "app-crypto/std", "authority-discovery-primitives/std", "authority-discovery/std", "authorship/std", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 02f55d230b207..7c1a6782d11fc 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -27,25 +27,26 @@ use support::{ traits::{SplitTwoWays, Currency, Randomness}, }; use primitives::u32_trait::{_1, _2, _3, _4}; -<<<<<<< HEAD:node/runtime/src/lib.rs -use node_primitives::{ - AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, - Moment, Signature, -}; -use babe_primitives::{AuthorityId as BabeId, AuthoritySignature as BabeSignature}; -use grandpa::fg_primitives; -use client::{ - block_builder::api::{self as block_builder_api, InherentData, CheckInherentsResult}, - runtime_api as client_api, impl_runtime_apis -}; +// <<<<<<< HEAD:node/runtime/src/lib.rs +// use node_primitives::{ +// AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, +// Moment, Signature, +// }; +// use babe_primitives::{AuthorityId as BabeId, AuthoritySignature as BabeSignature}; +// use grandpa::fg_primitives; +// use client::{ +// block_builder::api::{self as block_builder_api, InherentData, CheckInherentsResult}, +// runtime_api as client_api, impl_runtime_apis +// }; use sr_primitives::{ - ApplyResult, KeyTypeId, Perbill, Permill, impl_opaque_keys, generic, create_runtime_str, key_types, + create_runtime_str, impl_opaque_keys, generic, + ApplyExtrinsicResult, KeyTypeId, Perbill, Permill, }; -======= +// ======= use node_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, Moment, Signature}; use sr_api::impl_runtime_apis; -use sr_primitives::{Permill, Perbill, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str}; ->>>>>>> master:bin/node/runtime/src/lib.rs +// use sr_primitives::{Permill, Perbill, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str}; +// >>>>>>> master:bin/node/runtime/src/lib.rs use sr_primitives::curve::PiecewiseLinear; use sr_primitives::transaction_validity::TransactionValidity; use sr_primitives::traits::{ @@ -56,13 +57,13 @@ use version::RuntimeVersion; #[cfg(any(feature = "std", test))] use version::NativeVersion; use primitives::OpaqueMetadata; -<<<<<<< HEAD:node/runtime/src/lib.rs +// <<<<<<< HEAD:node/runtime/src/lib.rs use session::historical; -use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; -======= +// use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; +// ======= use grandpa::AuthorityList as GrandpaAuthorityList; use grandpa::fg_primitives; ->>>>>>> master:bin/node/runtime/src/lib.rs +// >>>>>>> master:bin/node/runtime/src/lib.rs use im_online::sr25519::{AuthorityId as ImOnlineId}; use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId; use transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; @@ -457,14 +458,9 @@ impl offences::Trait for Runtime { type OnOffenceHandler = Staking; } -<<<<<<< HEAD:node/runtime/src/lib.rs -impl authority_discovery::Trait for Runtime { - type AuthorityId = BabeId; -} -======= impl authority_discovery::Trait for Runtime {} ->>>>>>> master:bin/node/runtime/src/lib.rs +// move to own crate pub mod report { pub mod app { use app_crypto::{app_crypto, sr25519, KeyTypeId}; @@ -472,9 +468,11 @@ pub mod report { pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"rprt"); app_crypto!(sr25519, KEY_TYPE); - impl From for node_primitives::Signature { - fn from(sig: Signature) -> Self { - sr25519::Signature::from(sig).into() + impl sr_primitives::traits::IdentifyAccount for Public { + type AccountId = sr_primitives::AccountId32; + + fn into_account(self) -> Self::AccountId { + sr_primitives::MultiSigner::from(self.0).into_account() } } } @@ -708,36 +706,8 @@ impl_runtime_apis! { } impl authority_discovery_primitives::AuthorityDiscoveryApi for Runtime { -<<<<<<< HEAD:node/runtime/src/lib.rs - fn authorities() -> Vec { - AuthorityDiscovery::authorities().into_iter() - .map(|id| id.encode()) - .map(EncodedAuthorityId) - .collect() - } - - fn sign(payload: &Vec) -> Option<(EncodedSignature, EncodedAuthorityId)> { - AuthorityDiscovery::sign(payload).map(|(sig, id)| { - (EncodedSignature(sig.encode()), EncodedAuthorityId(id.encode())) - }) - } - - fn verify(payload: &Vec, signature: &EncodedSignature, authority_id: &EncodedAuthorityId) -> bool { - let signature = match BabeSignature::decode(&mut &signature.0[..]) { - Ok(s) => s, - _ => return false, - }; - - let authority_id = match BabeId::decode(&mut &authority_id.0[..]) { - Ok(id) => id, - _ => return false, - }; - - AuthorityDiscovery::verify(payload, signature, authority_id) -======= fn authorities() -> Vec { AuthorityDiscovery::authorities() ->>>>>>> master:bin/node/runtime/src/lib.rs } } diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index fe1b8b2ec811c..37281061b7abb 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -43,7 +43,7 @@ use support::{ }; use sr_primitives::{ generic::{DigestItem, OpaqueDigestItemId}, - traits::Zero, + traits::{IdentifyAccount, Zero}, KeyTypeId, Perbill, // ======= // use support::{decl_event, decl_storage, decl_module, dispatch::Result, storage}; @@ -105,7 +105,7 @@ pub trait Trait: system::Trait { /// Key type to use when signing equivocation report transactions, must be /// convertible to and from an account id since that's what we need to use /// to sign transactions. - type ReportKeyType: RuntimeAppPublic + From + Into + Clone; + type ReportKeyType: RuntimeAppPublic + IdentifyAccount; } type KeyOwnerProof = @@ -506,10 +506,9 @@ impl Module { ); // FIXME: placeholder to reduce unused warnings - let key = std::mem::MaybeUninit::uninit(); let ext = T::SubmitTransaction::sign_and_submit( call, - unsafe { key.assume_init() }, + unimplemented!(), ).ok(); // early exit after successful extrinsic creation diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 8aeed3dbdc87f..9c22cd92c6a75 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -167,8 +167,7 @@ impl ConsensusLog { } // FIXME: rename to equivocation proof ? -#[cfg_attr(feature = "std", derive(Debug))] -#[derive(Clone, Decode, Encode, PartialEq)] +#[derive(Clone, Decode, Debug, Encode, PartialEq)] pub struct EquivocationReport { set_id: SetId, equivocation: Equivocation, @@ -198,8 +197,7 @@ impl EquivocationReport { } } -#[cfg_attr(feature = "std", derive(Debug))] -#[derive(Clone, Decode, Encode, PartialEq)] +#[derive(Clone, Debug, Decode, Encode, PartialEq)] pub enum Equivocation { Prevote( grandpa::Equivocation< From 97770a59fa4990ece36405362c5a40049b21cf1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 27 Nov 2019 19:21:27 +0000 Subject: [PATCH 12/78] cleanup from merge conflicts --- bin/node/cli/src/service.rs | 18 +-- bin/node/runtime/src/lib.rs | 18 --- .../finality-grandpa/src/communication/mod.rs | 3 - client/finality-grandpa/src/environment.rs | 24 ---- core/finality-grandpa/Cargo.toml | 40 ------- frame/grandpa/src/lib.rs | 14 --- frame/session/Cargo.toml | 12 -- frame/support/src/traits.rs | 4 - node/runtime/Cargo.toml | 108 ------------------ primitives/finality-grandpa/Cargo.toml | 17 --- primitives/finality-grandpa/src/lib.rs | 12 -- primitives/session/Cargo.toml | 18 --- primitives/session/src/lib.rs | 6 - srml/grandpa/Cargo.toml | 38 ------ 14 files changed, 6 insertions(+), 326 deletions(-) delete mode 100644 core/finality-grandpa/Cargo.toml delete mode 100644 node/runtime/Cargo.toml delete mode 100644 srml/grandpa/Cargo.toml diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 65b60ab9f26c0..3e825e5377d12 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -69,18 +69,12 @@ macro_rules! new_full_start { .with_import_queue(|_config, client, mut select_chain, _transaction_pool| { let select_chain = select_chain.take() .ok_or_else(|| substrate_service::Error::SelectChainRequired)?; -// <<<<<<< HEAD:node/cli/src/service.rs - let (grandpa_block_import, grandpa_link) = - grandpa::block_import::<_, _, _, node_runtime::RuntimeApi, _, _>( - client.clone(), client.clone(), select_chain - )?; -// ======= -// let (grandpa_block_import, grandpa_link) = grandpa::block_import( -// client.clone(), -// &*client, -// select_chain, -// )?; -// >>>>>>> master:bin/node/cli/src/service.rs + let (grandpa_block_import, grandpa_link) = grandpa::block_import( + client.clone(), + client.clone(), + &*client, + select_chain, + )?; let justification_import = grandpa_block_import.clone(); let (block_import, babe_link) = babe::block_import( diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 7c1a6782d11fc..fa4317a6d8531 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -27,26 +27,12 @@ use support::{ traits::{SplitTwoWays, Currency, Randomness}, }; use primitives::u32_trait::{_1, _2, _3, _4}; -// <<<<<<< HEAD:node/runtime/src/lib.rs -// use node_primitives::{ -// AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, -// Moment, Signature, -// }; -// use babe_primitives::{AuthorityId as BabeId, AuthoritySignature as BabeSignature}; -// use grandpa::fg_primitives; -// use client::{ -// block_builder::api::{self as block_builder_api, InherentData, CheckInherentsResult}, -// runtime_api as client_api, impl_runtime_apis -// }; use sr_primitives::{ create_runtime_str, impl_opaque_keys, generic, ApplyExtrinsicResult, KeyTypeId, Perbill, Permill, }; -// ======= use node_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, Moment, Signature}; use sr_api::impl_runtime_apis; -// use sr_primitives::{Permill, Perbill, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str}; -// >>>>>>> master:bin/node/runtime/src/lib.rs use sr_primitives::curve::PiecewiseLinear; use sr_primitives::transaction_validity::TransactionValidity; use sr_primitives::traits::{ @@ -57,13 +43,9 @@ use version::RuntimeVersion; #[cfg(any(feature = "std", test))] use version::NativeVersion; use primitives::OpaqueMetadata; -// <<<<<<< HEAD:node/runtime/src/lib.rs use session::historical; -// use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; -// ======= use grandpa::AuthorityList as GrandpaAuthorityList; use grandpa::fg_primitives; -// >>>>>>> master:bin/node/runtime/src/lib.rs use im_online::sr25519::{AuthorityId as ImOnlineId}; use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId; use transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/finality-grandpa/src/communication/mod.rs index 51efe2eb1c7f2..6ee228e27edb4 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -676,13 +676,10 @@ impl> Clone for NetworkBridge { } } -// <<<<<<< HEAD:core/finality-grandpa/src/communication/mod.rs -// ======= pub(crate) fn localized_payload(round: RoundNumber, set_id: SetIdNumber, message: &E) -> Vec { (message, round, set_id).encode() } -// >>>>>>> master:client/finality-grandpa/src/communication/mod.rs /// Type-safe wrapper around a round number. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)] pub struct Round(pub RoundNumber); diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 3128b7918d68b..7ab831c337614 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -61,13 +61,10 @@ use crate::consensus_changes::SharedConsensusChanges; use crate::justification::GrandpaJustification; use crate::until_imported::UntilVoteTargetImported; use crate::voting_rule::VotingRule; -// <<<<<<< HEAD:core/finality-grandpa/src/environment.rs use fg_primitives::{ AuthorityId, AuthoritySignature, Equivocation, EquivocationReport, GrandpaApi, RoundNumber, SetId, }; -// use fg_primitives::{AuthorityId, AuthoritySignature, SetId, RoundNumber}; -// >>>>>>> master:client/finality-grandpa/src/environment.rs type HistoricalVotes = grandpa::HistoricalVotes< ::Hash, @@ -383,14 +380,9 @@ impl SharedVoterSetState { } /// The environment we run GRANDPA in. -// <<<<<<< HEAD:core/finality-grandpa/src/environment.rs pub(crate) struct Environment, RA, PRA, SC, VR> { pub(crate) client: Arc>, pub(crate) api: Arc, -// ======= -// pub(crate) struct Environment, RA, SC, VR> { -// pub(crate) client: Arc>, -// >>>>>>> master:client/finality-grandpa/src/environment.rs pub(crate) select_chain: SC, pub(crate) voters: Arc>, pub(crate) config: Config, @@ -402,11 +394,7 @@ pub(crate) struct Environment, RA, PRA, S pub(crate) voting_rule: VR, } -// <<<<<<< HEAD:core/finality-grandpa/src/environment.rs impl, PRA, RA, SC, VR> Environment { -// ======= -// impl, RA, SC, VR> Environment { -// >>>>>>> master:client/finality-grandpa/src/environment.rs /// Updates the voter set state using the given closure. The write lock is /// held during evaluation of the closure and the environment's voter set /// state is set to its result if successful. @@ -422,15 +410,9 @@ impl, PRA, RA, SC, VR> Environment, B, E, N, RA, PRA, SC, VR> grandpa::Chain> for Environment -// ======= -// impl, B, E, N, RA, SC, VR> -// grandpa::Chain> -// for Environment -// >>>>>>> master:client/finality-grandpa/src/environment.rs where Block: 'static, B: Backend + 'static, @@ -579,15 +561,9 @@ pub(crate) fn ancestry, E, RA>( Ok(tree_route.retracted().iter().skip(1).map(|e| e.hash).collect()) } -// <<<<<<< HEAD:core/finality-grandpa/src/environment.rs impl, N, RA, PRA, SC, VR> voter::Environment> for Environment -// ======= -// impl, N, RA, SC, VR> -// voter::Environment> -// for Environment -// >>>>>>> master:client/finality-grandpa/src/environment.rs where Block: 'static, B: Backend + 'static, diff --git a/core/finality-grandpa/Cargo.toml b/core/finality-grandpa/Cargo.toml deleted file mode 100644 index 68699c50aee77..0000000000000 --- a/core/finality-grandpa/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "substrate-finality-grandpa" -version = "2.0.0" -authors = ["Parity Technologies "] -edition = "2018" - -[dependencies] -fork-tree = { path = "../../core/utils/fork-tree" } -futures = "0.1.29" -futures03 = { package = "futures-preview", version = "0.3.0-alpha.19", features = ["compat"] } -log = "0.4.8" -parking_lot = "0.9.0" -tokio-executor = "0.1.8" -tokio-timer = "0.2.11" -rand = "0.7.2" -codec = { package = "parity-scale-codec", version = "1.0.0", features = ["derive"] } -sr-primitives = { path = "../sr-primitives" } -consensus_common = { package = "substrate-consensus-common", path = "../consensus/common" } -primitives = { package = "substrate-primitives", path = "../primitives" } -substrate-telemetry = { path = "../telemetry" } -substrate-session = { path = "../../core/session", default-features = false } -keystore = { package = "substrate-keystore", path = "../keystore" } -serde_json = "1.0.41" -client = { package = "substrate-client", path = "../client" } -header-metadata = { package = "substrate-header-metadata", path = "../client/header-metadata" } -inherents = { package = "substrate-inherents", path = "../../core/inherents" } -network = { package = "substrate-network", path = "../network" } -srml-finality-tracker = { path = "../../srml/finality-tracker" } -fg_primitives = { package = "substrate-finality-grandpa-primitives", path = "primitives" } -grandpa = { package = "finality-grandpa", version = "0.9.1", features = ["derive-codec"] } - -[dev-dependencies] -grandpa = { package = "finality-grandpa", version = "0.9.1", features = ["derive-codec", "test-helpers"] } -network = { package = "substrate-network", path = "../network", features = ["test-helpers"] } -keyring = { package = "substrate-keyring", path = "../keyring" } -test-client = { package = "substrate-test-runtime-client", path = "../test-runtime/client"} -babe_primitives = { package = "substrate-consensus-babe-primitives", path = "../consensus/babe/primitives" } -env_logger = "0.7.0" -tokio = "0.1.22" -tempfile = "3.1.0" diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 37281061b7abb..6835a4a0957f6 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -34,7 +34,6 @@ use rstd::prelude::*; use app_crypto::{key_types::GRANDPA, RuntimeAppPublic}; use codec::{self as codec, Encode, Decode, Error}; -// <<<<<<< HEAD:srml/grandpa/src/lib.rs use support::{ decl_event, decl_storage, decl_module, storage, dispatch::Result, @@ -45,33 +44,20 @@ use sr_primitives::{ generic::{DigestItem, OpaqueDigestItemId}, traits::{IdentifyAccount, Zero}, KeyTypeId, Perbill, -// ======= -// use support::{decl_event, decl_storage, decl_module, dispatch::Result, storage}; -// use sr_primitives::{ -// generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, Perbill, -// >>>>>>> master:frame/grandpa/src/lib.rs }; use sr_staking_primitives::{ SessionIndex, offence::{Kind, Offence, ReportOffence}, }; -// use fg_primitives::{ -// GRANDPA_ENGINE_ID, -// }; -// <<<<<<< HEAD:srml/grandpa/src/lib.rs -// pub use fg_primitives::{AuthorityId, AuthorityWeight}; use system::{ ensure_signed, DigestOf, offchain::SubmitSignedTransaction, }; -// ======= use fg_primitives::{ GRANDPA_AUTHORITIES_KEY, GRANDPA_ENGINE_ID, ConsensusLog, EquivocationReport, RoundNumber, SetId, ScheduledChange, }; pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; -// use system::{ensure_signed, DigestOf}; -// >>>>>>> master:frame/grandpa/src/lib.rs mod mock; mod tests; diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 8571563880aec..60a43a7802935 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -8,18 +8,7 @@ edition = "2018" serde = { version = "1.0.101", optional = true } safe-mix = { version = "1.0.0", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -# <<<<<<< HEAD:srml/session/Cargo.toml -# rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } session-primitives = { package = "substrate-session", path = "../../primitives/session", default-features = false } -# sr-primitives = { path = "../../core/sr-primitives", default-features = false } -# sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } -# support = { package = "srml-support", path = "../support", default-features = false } -# system = { package = "srml-system", path = "../system", default-features = false } -# timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false } -# substrate-trie = { path = "../../core/trie", default-features = false, optional = true } -# runtime-io ={ package = "sr-io", path = "../../core/sr-io", default-features = false } -# impl-trait-for-tuples = "0.1.2" -# ======= rstd = { package = "sr-std", path = "../../primitives/sr-std", default-features = false } sr-primitives = { path = "../../primitives/sr-primitives", default-features = false } sr-staking-primitives = { path = "../../primitives/sr-staking-primitives", default-features = false } @@ -29,7 +18,6 @@ timestamp = { package = "pallet-timestamp", path = "../timestamp", default-featu substrate-trie = { path = "../../primitives/trie", default-features = false, optional = true } runtime-io ={ package = "sr-io", path = "../../primitives/sr-io", default-features = false } impl-trait-for-tuples = "0.1.3" -# >>>>>>> master:frame/session/Cargo.toml [dev-dependencies] primitives = { package = "substrate-primitives", path = "../../primitives/core" } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 147f2df7f2057..b39b570d967ef 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -23,11 +23,7 @@ use codec::{FullCodec, Codec, Encode, Decode}; use primitives::u32_trait::Value as U32; use sr_primitives::{ ConsensusEngineId, -// <<<<<<< HEAD:srml/support/src/traits.rs -// traits::{MaybeDebug, MaybeSerializeDebug, SimpleArithmetic, Saturating}, -// ======= traits::{MaybeSerializeDeserialize, SimpleArithmetic, Saturating}, -// >>>>>>> master:frame/support/src/traits.rs }; /// Anything that can have a `::len()` method. diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml deleted file mode 100644 index 314a86e5e8c67..0000000000000 --- a/node/runtime/Cargo.toml +++ /dev/null @@ -1,108 +0,0 @@ -[package] -name = "node-runtime" -version = "2.0.0" -authors = ["Parity Technologies "] -edition = "2018" -build = "build.rs" - -[dependencies] -codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -integer-sqrt = { version = "0.1.2" } -rustc-hex = { version = "2.0", optional = true } -safe-mix = { version = "1.0", default-features = false } -serde = { version = "1.0.101", optional = true } - -app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", default-features = false } -authority-discovery-primitives = { package = "substrate-authority-discovery-primitives", path = "../../core/authority-discovery/primitives", default-features = false } -babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../core/consensus/babe/primitives", default-features = false } -client = { package = "substrate-client", path = "../../core/client", default-features = false } -node-primitives = { path = "../primitives", default-features = false } -offchain-primitives = { package = "substrate-offchain-primitives", path = "../../core/offchain/primitives", default-features = false } -primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false } -rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } -sr-primitives = { path = "../../core/sr-primitives", default-features = false } -sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } -substrate-keyring = { path = "../../core/keyring", optional = true } -substrate-session = { path = "../../core/session", default-features = false } -version = { package = "sr-version", path = "../../core/sr-version", default-features = false } - -authority-discovery = { package = "srml-authority-discovery", path = "../../srml/authority-discovery", default-features = false } -authorship = { package = "srml-authorship", path = "../../srml/authorship", default-features = false } -babe = { package = "srml-babe", path = "../../srml/babe", default-features = false } -balances = { package = "srml-balances", path = "../../srml/balances", default-features = false } -collective = { package = "srml-collective", path = "../../srml/collective", default-features = false } -contracts = { package = "srml-contracts", path = "../../srml/contracts", default-features = false } -contracts-rpc-runtime-api = { package = "srml-contracts-rpc-runtime-api", path = "../../srml/contracts/rpc/runtime-api/", default-features = false } -democracy = { package = "srml-democracy", path = "../../srml/democracy", default-features = false } -elections = { package = "srml-elections", path = "../../srml/elections", default-features = false } -executive = { package = "srml-executive", path = "../../srml/executive", default-features = false } -finality-tracker = { package = "srml-finality-tracker", path = "../../srml/finality-tracker", default-features = false } -grandpa = { package = "srml-grandpa", path = "../../srml/grandpa", default-features = false } -im-online = { package = "srml-im-online", path = "../../srml/im-online", default-features = false } -indices = { package = "srml-indices", path = "../../srml/indices", default-features = false } -membership = { package = "srml-membership", path = "../../srml/membership", default-features = false } -offences = { package = "srml-offences", path = "../../srml/offences", default-features = false } -randomness-collective-flip = { package = "srml-randomness-collective-flip", path = "../../srml/randomness-collective-flip", default-features = false } -session = { package = "srml-session", path = "../../srml/session", default-features = false, features = ["historical"] } -staking = { package = "srml-staking", path = "../../srml/staking", default-features = false } -srml-staking-reward-curve = { path = "../../srml/staking/reward-curve"} -sudo = { package = "srml-sudo", path = "../../srml/sudo", default-features = false } -support = { package = "srml-support", path = "../../srml/support", default-features = false } -system = { package = "srml-system", path = "../../srml/system", default-features = false } -system-rpc-runtime-api = { package = "srml-system-rpc-runtime-api", path = "../../srml/system/rpc/runtime-api/", default-features = false } -timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default-features = false } -treasury = { package = "srml-treasury", path = "../../srml/treasury", default-features = false } -utility = { package = "srml-utility", path = "../../srml/utility", default-features = false } -transaction-payment = { package = "srml-transaction-payment", path = "../../srml/transaction-payment", default-features = false } - -[build-dependencies] -wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.2", path = "../../core/utils/wasm-builder-runner" } - -[features] -default = ["std"] -std = [ - "app-crypto/std", - "authority-discovery-primitives/std", - "authority-discovery/std", - "authorship/std", - "babe-primitives/std", - "babe/std", - "balances/std", - "client/std", - "codec/std", - "collective/std", - "contracts/std", - "contracts-rpc-runtime-api/std", - "democracy/std", - "elections/std", - "executive/std", - "finality-tracker/std", - "grandpa/std", - "im-online/std", - "indices/std", - "membership/std", - "node-primitives/std", - "offchain-primitives/std", - "offences/std", - "primitives/std", - "randomness-collective-flip/std", - "rstd/std", - "rustc-hex", - "safe-mix/std", - "serde", - "session/std", - "sr-primitives/std", - "sr-staking-primitives/std", - "staking/std", - "substrate-keyring", - "substrate-session/std", - "sudo/std", - "support/std", - "system/std", - "system-rpc-runtime-api/std", - "timestamp/std", - "treasury/std", - "utility/std", - "transaction-payment/std", - "version/std", -] diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index 4bf15bcb31dc2..577b8c1fe1b81 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -5,43 +5,26 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -# <<<<<<< HEAD:core/finality-grandpa/primitives/Cargo.toml -# app-crypto = { package = "substrate-application-crypto", path = "../../application-crypto", default-features = false } -# codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.10.1", default-features = false, features = ["derive-codec"] } -# log = { version = "0.4.8", optional = true } primitives = { package = "substrate-primitives", path = "../../primitives/core", optional = true } -# rstd = { package = "sr-std", path = "../../sr-std", default-features = false } -# serde = { version = "1.0.101", optional = true, features = ["derive"] } -# sr-primitives = { path = "../../sr-primitives", default-features = false } -# ======= app-crypto = { package = "substrate-application-crypto", path = "../application-crypto", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } rstd = { package = "sr-std", path = "../sr-std", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } sr-api = { path = "../sr-api", default-features = false } sr-primitives = { path = "../sr-primitives", default-features = false } -# >>>>>>> master:primitives/finality-grandpa/Cargo.toml log = { version = "0.4.8", optional = true } [features] default = ["std"] std = [ "app-crypto/std", -# <<<<<<< HEAD:core/finality-grandpa/primitives/Cargo.toml - # "client/std", -# "codec/std", "grandpa/std", -# "log", "primitives", -# "rstd/std", -# "serde", -# ======= "codec/std", "rstd/std", "serde", "sr-api/std", -# >>>>>>> master:primitives/finality-grandpa/Cargo.toml "sr-primitives/std", "log", ] diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 9c22cd92c6a75..b2f6f7fbccf55 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -23,13 +23,10 @@ extern crate alloc; #[cfg(feature = "std")] use serde::Serialize; -// <<<<<<< HEAD:core/finality-grandpa/primitives/src/lib.rs -// ======= use codec::{Encode, Decode, Input, Codec}; use sr_primitives::{ConsensusEngineId, RuntimeDebug, traits::NumberFor}; use rstd::borrow::Cow; -// >>>>>>> master:primitives/finality-grandpa/src/lib.rs use rstd::vec::Vec; #[cfg(feature = "std")] @@ -425,16 +422,7 @@ sr_api::decl_runtime_apis! { /// When called at block B, it will return the set of authorities that should be /// used to finalize descendants of this block (B+1, B+2, ...). The block B itself /// is finalized by the authorities from block B-1. -// <<<<<<< HEAD:core/finality-grandpa/primitives/src/lib.rs -// fn grandpa_authorities() -> Vec<(AuthorityId, AuthorityWeight)>; - -// fn submit_report_equivocation_extrinsic( -// equivocation_report: EquivocationReport>, -// key_owner_proof: Vec, -// ) -> Option<()>; -// ======= fn grandpa_authorities() -> AuthorityList; -// >>>>>>> master:primitives/finality-grandpa/src/lib.rs fn submit_report_equivocation_extrinsic( equivocation_report: EquivocationReport>, diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index 03a149e1aff94..c3ab4d7276ebe 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -5,25 +5,8 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -# <<<<<<< HEAD:core/session/Cargo.toml -# client = { package = "substrate-client", path = "../client", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false, features = ["derive"] } -# primitives = { package = "substrate-primitives", path = "../primitives", optional = true } -# rstd = { package = "sr-std", path = "../sr-std", default-features = false } -# sr-primitives = { path = "../sr-primitives", default-features = false } sr-staking-primitives = { path = "../sr-staking-primitives", default-features = false } - -# [features] -# default = ["std"] -# std = [ -# "client/std", -# "codec/std", -# "primitives/std", -# "rstd/std", -# "sr-primitives/std", -# , -# ] -# ======= sr-api = { path = "../sr-api", default-features = false } rstd = { package = "sr-std", path = "../sr-std", default-features = false } sr-primitives = { path = "../sr-primitives", default-features = false } @@ -31,4 +14,3 @@ sr-primitives = { path = "../sr-primitives", default-features = false } [features] default = [ "std" ] std = [ "sr-api/std", "rstd/std", "sr-primitives/std", "sr-staking-primitives/std" ] -# >>>>>>> master:primitives/session/Cargo.toml diff --git a/primitives/session/src/lib.rs b/primitives/session/src/lib.rs index cf170f61bed83..1b417876f1381 100644 --- a/primitives/session/src/lib.rs +++ b/primitives/session/src/lib.rs @@ -23,14 +23,8 @@ use rstd::vec::Vec; use sr_primitives::KeyTypeId; use sr_staking_primitives::SessionIndex; -// #[cfg(feature = "std")] -// <<<<<<< HEAD:core/session/src/lib.rs -// use primitives::{H256, Blake2Hasher}; #[cfg(feature = "std")] -// use sr_primitives::traits::{ProvideRuntimeApi, Block as BlockT}; -// ======= use sr_primitives::{generic::BlockId, traits::{ProvideRuntimeApi, Block as BlockT}}; -// >>>>>>> master:primitives/session/src/lib.rs sr_api::decl_runtime_apis! { /// Session keys runtime api. diff --git a/srml/grandpa/Cargo.toml b/srml/grandpa/Cargo.toml deleted file mode 100644 index f8087f7fe114d..0000000000000 --- a/srml/grandpa/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "srml-grandpa" -version = "2.0.0" -authors = ["Parity Technologies "] -edition = "2018" - -[dependencies] -app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", default-features = false } -serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false } -substrate-finality-grandpa-primitives = { path = "../../core/finality-grandpa/primitives", default-features = false } -rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } -sr-primitives = { path = "../../core/sr-primitives", default-features = false } -sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } -support = { package = "srml-support", path = "../support", default-features = false } -system = { package = "srml-system", path = "../system", default-features = false } -session = { package = "srml-session", path = "../session", default-features = false } -finality-tracker = { package = "srml-finality-tracker", path = "../finality-tracker", default-features = false } - -[dev-dependencies] -runtime-io ={ package = "sr-io", path = "../../core/sr-io" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "primitives/std", - "substrate-finality-grandpa-primitives/std", - "rstd/std", - "support/std", - "sr-primitives/std", - "sr-staking-primitives/std", - "system/std", - "session/std", - "finality-tracker/std", -] From d395ff6a18aaede301ffc9973a3bb031fb00da46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 27 Nov 2019 19:26:04 +0000 Subject: [PATCH 13/78] cleanup crate tomls --- frame/session/Cargo.toml | 2 +- primitives/finality-grandpa/Cargo.toml | 10 +++++----- primitives/session/Cargo.toml | 12 +++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 60a43a7802935..d30d13ca9337a 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" serde = { version = "1.0.101", optional = true } safe-mix = { version = "1.0.0", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -session-primitives = { package = "substrate-session", path = "../../primitives/session", default-features = false } rstd = { package = "sr-std", path = "../../primitives/sr-std", default-features = false } +session-primitives = { package = "substrate-session", path = "../../primitives/session", default-features = false } sr-primitives = { path = "../../primitives/sr-primitives", default-features = false } sr-staking-primitives = { path = "../../primitives/sr-staking-primitives", default-features = false } support = { package = "frame-support", path = "../support", default-features = false } diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index 577b8c1fe1b81..7250117f23663 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -5,26 +5,26 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -grandpa = { package = "finality-grandpa", version = "0.10.1", default-features = false, features = ["derive-codec"] } -primitives = { package = "substrate-primitives", path = "../../primitives/core", optional = true } app-crypto = { package = "substrate-application-crypto", path = "../application-crypto", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } +grandpa = { package = "finality-grandpa", version = "0.10.1", default-features = false, features = ["derive-codec"] } +log = { version = "0.4.8", optional = true } +primitives = { package = "substrate-primitives", path = "../../primitives/core", optional = true } rstd = { package = "sr-std", path = "../sr-std", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } sr-api = { path = "../sr-api", default-features = false } sr-primitives = { path = "../sr-primitives", default-features = false } -log = { version = "0.4.8", optional = true } [features] default = ["std"] std = [ "app-crypto/std", + "codec/std", "grandpa/std", + "log", "primitives", - "codec/std", "rstd/std", "serde", "sr-api/std", "sr-primitives/std", - "log", ] diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index c3ab4d7276ebe..3d25bf3de0630 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -6,11 +6,17 @@ edition = "2018" [dependencies] codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false, features = ["derive"] } -sr-staking-primitives = { path = "../sr-staking-primitives", default-features = false } -sr-api = { path = "../sr-api", default-features = false } rstd = { package = "sr-std", path = "../sr-std", default-features = false } +sr-api = { path = "../sr-api", default-features = false } sr-primitives = { path = "../sr-primitives", default-features = false } +sr-staking-primitives = { path = "../sr-staking-primitives", default-features = false } [features] default = [ "std" ] -std = [ "sr-api/std", "rstd/std", "sr-primitives/std", "sr-staking-primitives/std" ] +std = [ + "codec/std", + "rstd/std", + "sr-api/std", + "sr-primitives/std", + "sr-staking-primitives/std", +] From a31208c939ca884dddc16bb8029125fbaa963169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 28 Nov 2019 22:23:42 +0000 Subject: [PATCH 14/78] grandpa: refactor equivocation handling to separate trait --- bin/node/runtime/src/lib.rs | 13 +- client/finality-grandpa/src/lib.rs | 46 ------ frame/grandpa/src/lib.rs | 189 ++++++++++++++++++------- primitives/finality-grandpa/src/lib.rs | 2 +- 4 files changed, 144 insertions(+), 106 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 177c4afef7a3a..abbadbe3131be 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -468,15 +468,16 @@ pub mod report { use report::ReporterId; -type SubmitReportTransaction = TransactionSubmitter; - impl grandpa::Trait for Runtime { type Event = Event; type Call = Call; - type KeyOwnerProofSystem = Historical; - type SubmitTransaction = SubmitReportTransaction; - type ReportEquivocation = Offences; - type ReportKeyType = ReporterId; + // type HandleEquivocation = (); + type HandleEquivocation = grandpa::EquivocationHandler< + Historical, + TransactionSubmitter, + Offences, + ReporterId, + >; } parameter_types! { diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 5db6c5d33a48f..a8f284a5479dd 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -403,11 +403,8 @@ impl, RA> GenesisAuthoritySetProvider for /// to it. pub fn block_import, RA, PRA, SC>( client: Arc>, -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs api: Arc, -// ======= genesis_authorities_provider: &dyn GenesisAuthoritySetProvider, -// >>>>>>> master:client/finality-grandpa/src/lib.rs select_chain: SC, ) -> Result<( GrandpaBlockImport, @@ -531,11 +528,7 @@ fn register_finality_tracker_inherent_data_provider, N, RA, PRA, SC, VR, X> { -// ======= -// pub struct GrandpaParams, N, RA, SC, VR, X> { -// >>>>>>> master:client/finality-grandpa/src/lib.rs /// Configuration for the GRANDPA service. pub config: Config, /// A link to the block import worker. @@ -554,15 +547,9 @@ pub struct GrandpaParams, N, RA, PRA, SC, VR, X> /// Run a GRANDPA voter as a task. Provide configuration and a link to a /// block import worker that has already been instantiated with `block_import`. -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs pub fn run_grandpa_voter, N, RA, PRA, SC, VR, X>( grandpa_params: GrandpaParams, ) -> sp_blockchain::Result + Send + 'static> where -// ======= -// pub fn run_grandpa_voter, N, RA, SC, VR, X>( -// grandpa_params: GrandpaParams, -// ) -> sp_blockchain::Result + Send + 'static> where -// >>>>>>> master:client/finality-grandpa/src/lib.rs Block::Hash: Ord, B: Backend + 'static, E: CallExecutor + Send + Sync + 'static, @@ -661,7 +648,6 @@ pub fn run_grandpa_voter, N, RA, PRA, SC, VR, X>( /// Future that powers the voter. #[must_use] -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs struct VoterWork, RA, PRA, SC, VR> { voter: Box>> + Send>, env: Arc>, @@ -669,15 +655,6 @@ struct VoterWork, RA, PRA, SC, VR> { } impl VoterWork -// ======= -// struct VoterWork, RA, SC, VR> { -// voter: Box>> + Send>, -// env: Arc>, -// voter_commands_rx: mpsc::UnboundedReceiver>>, -// } - -// impl VoterWork -// >>>>>>> master:client/finality-grandpa/src/lib.rs where Block: BlockT, N: Network + Sync, @@ -704,12 +681,8 @@ where let voters = persistent_data.authority_set.current_authorities(); let env = Arc::new(Environment { -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs -// inner: client, api, -// ======= client, -// >>>>>>> master:client/finality-grandpa/src/lib.rs select_chain, voting_rule, voters: Arc::new(voters), @@ -829,13 +802,8 @@ where voters: Arc::new(new.authorities.into_iter().collect()), set_id: new.set_id, voter_set_state: self.env.voter_set_state.clone(), - // Fields below are simply transferred and not updated. -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs -// inner: self.env.inner.clone(), -// ======= client: self.env.client.clone(), api: self.env.api.clone(), -// >>>>>>> master:client/finality-grandpa/src/lib.rs select_chain: self.env.select_chain.clone(), config: self.env.config.clone(), authority_set: self.env.authority_set.clone(), @@ -866,11 +834,7 @@ where } } -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs impl Future for VoterWork -// ======= -// impl Future for VoterWork -// >>>>>>> master:client/finality-grandpa/src/lib.rs where Block: BlockT, N: Network + Sync, @@ -926,17 +890,10 @@ where } } -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs #[deprecated(since = "1.1.0", note = "Please switch to run_grandpa_voter.")] pub fn run_grandpa, N, RA, PRA, SC, VR, X>( grandpa_params: GrandpaParams, ) -> sp_blockchain::Result + Send + 'static> where -// ======= -// #[deprecated(since = "1.1.0", note = "Please switch to run_grandpa_voter.")] -// pub fn run_grandpa, N, RA, SC, VR, X>( -// grandpa_params: GrandpaParams, -// ) -> ::sp_blockchain::Result + Send + 'static> where -// >>>>>>> master:client/finality-grandpa/src/lib.rs Block::Hash: Ord, B: Backend + 'static, E: CallExecutor + Send + Sync + 'static, @@ -946,12 +903,9 @@ pub fn run_grandpa, N, RA, PRA, SC, VR, X>( NumberFor: BlockNumberOps, DigestFor: Encode, RA: Send + Sync + 'static, -// <<<<<<< HEAD:core/finality-grandpa/src/lib.rs PRA: ProvideRuntimeApi + Send + Sync + 'static, PRA::Api: GrandpaApi + SessionMembership, -// ======= VR: VotingRule> + Clone + 'static, -// >>>>>>> master:client/finality-grandpa/src/lib.rs X: Future + Clone + Send + 'static, { run_grandpa_voter(grandpa_params) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 6835a4a0957f6..292be8968e9b8 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -69,37 +69,135 @@ pub trait Trait: system::Trait { /// The function call. type Call: From>; - /// A system for proving ownership of keys, i.e. that a given key was part - /// of a validator set, needed for validating equivocation reports. The - /// session index and validator count of the session are part of the proof - /// as extra data. - type KeyOwnerProofSystem: KeyOwnerProofSystem< + type HandleEquivocation: HandleEquivocation; +} + +use rstd::fmt::Debug; + +pub trait HandleEquivocation { + type KeyOwnerProof: Clone + Debug + Decode + Encode + PartialEq; + type KeyOwnerIdentification; + + fn check_proof( + equivocation_report: &EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; + + fn report_offence( + reporters: Vec, + offence: GrandpaEquivocationOffence, + ); + + fn submit_equivocation_report( + equivocation_report: EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Result; +} + +impl HandleEquivocation for () { + type KeyOwnerProof = (); + type KeyOwnerIdentification = (); + + fn check_proof( + equivocation_report: &EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { + None + } + + fn report_offence( + reporters: Vec, + offence: GrandpaEquivocationOffence, + ) { + + } + + fn submit_equivocation_report( + equivocation_report: EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Result { + Ok(()) + } +} + +pub struct EquivocationHandler { + _phantom: rstd::marker::PhantomData<(P, S, R, K)>, +} + +impl Default for EquivocationHandler { + fn default() -> Self { + Self { + _phantom: Default::default(), + } + } +} + +impl HandleEquivocation for EquivocationHandler where + T: Trait, + // A system for proving ownership of keys, i.e. that a given key was part + // of a validator set, needed for validating equivocation reports. The + // session index and validator count of the session are part of the proof + // as extra data. + P: KeyOwnerProofSystem< (KeyTypeId, Vec), ExtraData = (SessionIndex, u32), - >; - - /// A transaction submitter. Used for submitting equivocation reports. - type SubmitTransaction: SubmitSignedTransaction::Call>; - - type ReportEquivocation: - ReportOffence< - Self::AccountId, - KeyOwnerIdentification, - GrandpaEquivocationOffence>, - >; - - /// Key type to use when signing equivocation report transactions, must be - /// convertible to and from an account id since that's what we need to use - /// to sign transactions. - type ReportKeyType: RuntimeAppPublic + IdentifyAccount; -} + >, + // A transaction submitter. Used for submitting equivocation reports. + S: SubmitSignedTransaction::Call>, + R: ReportOffence< + T::AccountId, + P::IdentificationTuple, + GrandpaEquivocationOffence, + >, + // Key type to use when signing equivocation report transactions, must be + // convertible to and from an account id since that's what we need to use + // to sign transactions. + K: RuntimeAppPublic + IdentifyAccount, +{ + type KeyOwnerProof = P::Proof; + type KeyOwnerIdentification = P::IdentificationTuple; + + fn check_proof( + equivocation_report: &EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { + let (offender, (session_index, validator_set_count)) = + P::check_proof( + (GRANDPA, equivocation_report.offender().encode()), + key_owner_proof, + )?; + + Some((offender, session_index, validator_set_count)) + } -type KeyOwnerProof = - <::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, Vec)>>::Proof; + fn report_offence( + reporters: Vec, + offence: GrandpaEquivocationOffence, + ) { + R::report_offence( + reporters, + offence, + ); + } -type KeyOwnerIdentification = - <::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, Vec)>> - ::IdentificationTuple; + fn submit_equivocation_report( + equivocation_report: EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Result { + // let call = Call::report_equivocation( + // equivocation_report.clone(), + // key_owner_proof.clone(), + // ); + + // // FIXME: add key, depends on: https://github.com/paritytech/substrate/pull/4200 + // let ext = T::SubmitTransaction::sign_and_submit( + // call, + // unimplemented!(), + // ).ok(); + + Ok(()) + } +} /// A stored pending change, old format. // TODO: remove shim @@ -234,15 +332,15 @@ decl_module! { fn report_equivocation( origin, equivocation_report: EquivocationReport, - key_owner_proof: KeyOwnerProof, + key_owner_proof: >::KeyOwnerProof, ) { let reporter_id = ensure_signed(origin)?; // validate the membership proof and extract session index and // validator set count of the session that we're proving membership of - let (offender, (session_index, validator_set_count)) = - T::KeyOwnerProofSystem::check_proof( - (GRANDPA, equivocation_report.offender().encode()), + let (offender, session_index, validator_set_count) = + T::HandleEquivocation::check_proof( + &equivocation_report, key_owner_proof, ).ok_or("Invalid/outdated key ownership proof.")?; @@ -280,7 +378,7 @@ decl_module! { } // report to the offences module rewarding the sender. - T::ReportEquivocation::report_offence( + T::HandleEquivocation::report_offence( vec![reporter_id], GrandpaEquivocationOffence { session_index, @@ -481,29 +579,14 @@ impl Module { pub fn submit_report_equivocation_extrinsic( equivocation_report: EquivocationReport, - key_owner_proof: KeyOwnerProof, + key_owner_proof: >::KeyOwnerProof, ) -> Option<()> { - let local_keys = T::ReportKeyType::all(); - - for key in local_keys { - let call = Call::report_equivocation( - equivocation_report.clone(), - key_owner_proof.clone(), - ); + T::HandleEquivocation::submit_equivocation_report( + equivocation_report, + key_owner_proof, + ).ok()?; - // FIXME: placeholder to reduce unused warnings - let ext = T::SubmitTransaction::sign_and_submit( - call, - unimplemented!(), - ).ok(); - - // early exit after successful extrinsic creation - if ext.is_some() { - return Some(()); - } - } - - None + Some(()) } } diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index b2f6f7fbccf55..e794316fdedff 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -164,7 +164,7 @@ impl ConsensusLog { } // FIXME: rename to equivocation proof ? -#[derive(Clone, Decode, Debug, Encode, PartialEq)] +#[derive(Clone, Debug, Decode, Encode, PartialEq)] pub struct EquivocationReport { set_id: SetId, equivocation: Equivocation, From 4d0b2275bbf8df432ecda7040bf4762a1399ce46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 10 Dec 2019 16:25:06 +0000 Subject: [PATCH 15/78] node-template: fix compilation --- bin/node-template/runtime/src/lib.rs | 19 ++++++++++++++++++- bin/node-template/src/service.rs | 7 +++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 7abe43c066b66..c74e4c1eddc58 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -12,7 +12,7 @@ use sp_std::prelude::*; use primitives::OpaqueMetadata; use sp_runtime::{ ApplyExtrinsicResult, transaction_validity::TransactionValidity, generic, create_runtime_str, - impl_opaque_keys, MultiSignature + impl_opaque_keys, MultiSignature, KeyTypeId, }; use sp_runtime::traits::{ NumberFor, BlakeTwo256, Block as BlockT, StaticLookup, Verify, ConvertInto, IdentifyAccount @@ -165,6 +165,8 @@ impl aura::Trait for Runtime { impl grandpa::Trait for Runtime { type Event = Event; + type Call = Call; + type HandleEquivocation = (); } impl indices::Trait for Runtime { @@ -356,9 +358,24 @@ impl_runtime_apis! { } } + impl sp_session::SessionMembership for Runtime { + fn generate_session_membership_proof( + _session_key: (KeyTypeId, Vec), + ) -> Option { + None + } + } + impl fg_primitives::GrandpaApi for Runtime { fn grandpa_authorities() -> GrandpaAuthorityList { Grandpa::grandpa_authorities() } + + fn submit_report_equivocation_extrinsic( + _equivocation_report: fg_primitives::EquivocationReport, + _key_owner_proof: Vec, + ) -> Option<()> { + None + } } } diff --git a/bin/node-template/src/service.rs b/bin/node-template/src/service.rs index 600ae2c5b2db1..58251008fa001 100644 --- a/bin/node-template/src/service.rs +++ b/bin/node-template/src/service.rs @@ -52,8 +52,11 @@ macro_rules! new_full_start { .ok_or_else(|| sc_service::Error::SelectChainRequired)?; let (grandpa_block_import, grandpa_link) = - grandpa::block_import::<_, _, _, runtime::RuntimeApi, _>( - client.clone(), &*client, select_chain + grandpa::block_import::<_, _, _, runtime::RuntimeApi, _, _>( + client.clone(), + client.clone(), + &*client, + select_chain, )?; let import_queue = aura::import_queue::<_, _, AuraPair, _>( From 8e0cbf64e5ff123e3d4d7f97b339a38b796f36ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 10 Dec 2019 18:30:24 +0000 Subject: [PATCH 16/78] fix test compilation --- Cargo.lock | 1 + bin/node/runtime/src/lib.rs | 2 +- client/finality-grandpa/src/tests.rs | 16 ++++++++++++++ frame/grandpa/src/mock.rs | 4 +++- primitives/finality-grandpa/src/lib.rs | 17 ++++++++++++--- test/utils/runtime/Cargo.toml | 2 ++ test/utils/runtime/src/lib.rs | 29 ++++++++++++++++++++++++++ 7 files changed, 66 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e2ed28a155bc..f7776798746d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6703,6 +6703,7 @@ dependencies = [ "sp-consensus-aura 2.0.0", "sp-consensus-babe 2.0.0", "sp-core 2.0.0", + "sp-finality-grandpa 2.0.0", "sp-inherents 2.0.0", "sp-io 2.0.0", "sp-keyring 2.0.0", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b0d6e80b82d7e..190b623096b02 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -792,7 +792,7 @@ mod tests { #[test] fn validate_bounds() { - let x = SubmitTransaction::default(); + let x = SubmitImOnlineTransaction::default(); is_submit_signed_transaction(x); } diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index d9010bd02b490..d4645a3250e09 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -53,6 +53,7 @@ type PeerData = test_client::Executor, Block, test_client::runtime::RuntimeApi, + test_client::Client, LongestChain > > @@ -119,6 +120,7 @@ impl TestNetFactory for GrandpaTestNet { match client { PeersClient::Full(ref client, ref backend) => { let (import, link) = block_import( + client.clone(), client.clone(), &self.test_config, LongestChain::new(backend.clone()), @@ -274,6 +276,19 @@ impl GrandpaApi for RuntimeApi { ) -> Result> { Ok(self.inner.genesis_authorities.clone()).map(NativeOrEncoded::Native) } + + fn GrandpaApi_submit_report_equivocation_extrinsic_runtime_api_impl( + &self, + _: &BlockId, + _: ExecutionContext, + _: Option<( + fg_primitives::EquivocationReport<::Hash, NumberFor>, + Vec, + )>, + _: Vec, + ) -> Result>> { + Ok(NativeOrEncoded::Native(None)) + } } impl GenesisAuthoritySetProvider for TestApi { @@ -1593,6 +1608,7 @@ fn grandpa_environment_respects_voting_rules() { config: config.clone(), consensus_changes: consensus_changes.clone(), client: link.client.clone(), + api: link.client.clone(), select_chain: link.select_chain.clone(), set_id: authority_set.set_id(), voter_set_state: set_state.clone(), diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index fb153d71cf602..63682494d715d 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -23,7 +23,7 @@ use runtime_io; use support::{impl_outer_origin, impl_outer_event, parameter_types, weights::Weight}; use primitives::H256; use codec::{Encode, Decode}; -use crate::{AuthorityId, AuthorityList, GenesisConfig, Trait, Module, ConsensusLog}; +use crate::{AuthorityId, AuthorityList, Call, GenesisConfig, Trait, Module, ConsensusLog}; use sp_finality_grandpa::GRANDPA_ENGINE_ID; impl_outer_origin!{ @@ -40,6 +40,8 @@ pub struct Test; impl Trait for Test { type Event = TestEvent; + type Call = Call; + type HandleEquivocation = (); } parameter_types! { pub const BlockHashCount: u64 = 250; diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index a688c9eca62bb..59105db176be3 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -315,10 +315,21 @@ pub fn check_message_signature( H: Encode, N: Encode, { - use app_crypto::RuntimeAppPublic; - let encoded_raw = localized_payload(round, set_id, message); - if id.verify(&encoded_raw, signature) { + + #[cfg(not(feature = "std"))] + let verify = || { + use app_crypto::RuntimeAppPublic; + id.verify(&encoded_raw, signature) + }; + + #[cfg(feature = "std")] + let verify = || { + use app_crypto::Pair; + AuthorityPair::verify(signature, &encoded_raw, &id) + }; + + if verify() { Ok(()) } else { #[cfg(feature = "std")] diff --git a/test/utils/runtime/Cargo.toml b/test/utils/runtime/Cargo.toml index 40a5f17837c0a..062f370aaeca6 100644 --- a/test/utils/runtime/Cargo.toml +++ b/test/utils/runtime/Cargo.toml @@ -33,6 +33,7 @@ frame-system = { path = "../../../frame/system", default-features = false } frame-system-rpc-runtime-api = { path = "../../../frame/system/rpc/runtime-api", default-features = false } pallet-timestamp = { path = "../../../frame/timestamp", default-features = false } sc-client = { path = "../../../client", optional = true } +sp-finality-grandpa = { path = "../../../primitives/finality-grandpa", default-features = false } sp-trie = { path = "../../../primitives/trie", default-features = false } sp-transaction-pool = { package = "sp-transaction-pool-api", path = "../../../primitives/transaction-pool", default-features = false } trie-db = { version = "0.16.0", default-features = false } @@ -77,6 +78,7 @@ std = [ "frame-system/std", "pallet-timestamp/std", "sc-client", + "sp-finality-grandpa/std", "sp-trie/std", "sp-transaction-pool/std", "trie-db/std", diff --git a/test/utils/runtime/src/lib.rs b/test/utils/runtime/src/lib.rs index 4a0a5859c40b8..41c8658e158a3 100644 --- a/test/utils/runtime/src/lib.rs +++ b/test/utils/runtime/src/lib.rs @@ -634,6 +634,27 @@ cfg_if! { } } + impl session::SessionMembership for Runtime { + fn generate_session_membership_proof( + _session_key: (primitives::crypto::KeyTypeId, Vec), + ) -> Option { + None + } + } + + impl sp_finality_grandpa::GrandpaApi for Runtime { + fn grandpa_authorities() -> sp_finality_grandpa::AuthorityList { + Vec::new() + } + + fn submit_report_equivocation_extrinsic( + _equivocation_report: sp_finality_grandpa::EquivocationReport, + _key_owner_proof: Vec, + ) -> Option<()> { + None + } + } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(_account: AccountId) -> Index { 0 @@ -850,6 +871,14 @@ cfg_if! { } } + impl session::SessionMembership for Runtime { + fn generate_session_membership_proof( + _session_key: (primitives::crypto::KeyTypeId, Vec), + ) -> Option { + None + } + } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(_account: AccountId) -> Index { 0 From 2cc3d7fde61754f8fc10f59fe518725e028bec33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 10 Dec 2019 18:48:46 +0000 Subject: [PATCH 17/78] bump finality-grandpa to v0.10.2 --- Cargo.lock | 8 ++++---- client/finality-grandpa/Cargo.toml | 4 ++-- primitives/finality-grandpa/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7776798746d3..696cddc5c8b0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,7 +1208,7 @@ dependencies = [ [[package]] name = "finality-grandpa" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5305,7 +5305,7 @@ name = "sc-finality-grandpa" version = "2.0.0" dependencies = [ "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "finality-grandpa 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "finality-grandpa 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "fork-tree 2.0.0", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6208,7 +6208,7 @@ dependencies = [ name = "sp-finality-grandpa" version = "2.0.0" dependencies = [ - "finality-grandpa 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "finality-grandpa 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "sc-application-crypto 2.0.0", @@ -8164,7 +8164,7 @@ dependencies = [ "checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum file-per-thread-logger 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8505b75b31ef7285168dd237c4a7db3c1f3e0927e7d314e670bc98e854272fe9" -"checksum finality-grandpa 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bd555755b04f83d6ed3041f5da26c0123a417ae2b96a826c1171b3f6fb804803" +"checksum finality-grandpa 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4106eb29c7e092f4a6ce6e7632abbbfdf85d94e63035d3790d2d16eeae83d3f4" "checksum fixed-hash 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72fe7539e2c5692c6989f2f9c0457e42f1e5768f96b85c87d273574670ae459f" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index a297412fa3b14..45d9f40a8f9b3 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -28,10 +28,10 @@ sp-blockchain = { path = "../../primitives/blockchain" } network = { package = "sc-network", path = "../network" } sp-finality-tracker = { path = "../../primitives/finality-tracker" } fg_primitives = { package = "sp-finality-grandpa", path = "../../primitives/finality-grandpa" } -grandpa = { package = "finality-grandpa", version = "0.10.1", features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.10.2", features = ["derive-codec"] } [dev-dependencies] -grandpa = { package = "finality-grandpa", version = "0.10.1", features = ["derive-codec", "test-helpers"] } +grandpa = { package = "finality-grandpa", version = "0.10.2", features = ["derive-codec", "test-helpers"] } network = { package = "sc-network", path = "../network", features = ["test-helpers"] } keyring = { package = "sp-keyring", path = "../../primitives/keyring" } test-client = { package = "substrate-test-runtime-client", path = "../../test/utils/runtime/client"} diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index c9f60c0e6dbf6..a66a7175e37d4 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] app-crypto = { package = "sc-application-crypto", path = "../application-crypto", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.10.1", default-features = false, features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.10.2", default-features = false, features = ["derive-codec"] } log = { version = "0.4.8", optional = true } sp-std = { path = "../std", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } From beb09d579cef29813e4baae92e5d4121af456172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 10:51:23 +0000 Subject: [PATCH 18/78] rpc: fix runtime version test --- client/rpc/src/state/tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 2ae22df1a0058..4d1ff99655878 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -335,7 +335,7 @@ fn should_query_storage() { // Both hashes invalid. let result = api.query_storage( - keys.clone(), + keys.clone(), random_hash1, Some(random_hash2), ); @@ -375,7 +375,8 @@ fn should_return_runtime_version() { \"specVersion\":1,\"implVersion\":1,\"apis\":[[\"0xdf6acb689907609b\",2],\ [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",1],[\"0x40fe3ad401f8959a\",4],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",1],\ - [\"0xf78b278be53f454c\",1],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]]}"; + [\"0xf78b278be53f454c\",1],[\"0xab3c0572291feb8b\",1],[\"0xdd8e34c0eb9b2e5a\",1],\ + [\"0xbc9d89904f5b923f\",1]]}"; let runtime_version = api.runtime_version(None.into()).wait().unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); From d2f0a7feac4d7fbb2ad2b5154ff81d1168a6b69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 12:17:55 +0000 Subject: [PATCH 19/78] CHERRY-PICK #4200: Add documentation to SubmitSignedTransaction and actually make it work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit dc8d71c3f0967e9721181e1c0d0d2b3cedbfc306 Author: Tomasz Drwięga Date: Tue Dec 3 16:29:33 2019 +0100 Split the method to avoid confusing type error message. commit 0c4c0378ff3857603f38aad3ba6ca9ea85eb8da2 Author: Tomasz Drwięga Date: Tue Dec 3 16:19:55 2019 +0100 Make accounts optional, fix logic. commit d715f645ab03dd63dc61901956b3802d6d39db5d Author: Tomasz Drwięga Date: Tue Dec 3 10:06:20 2019 +0100 Remove warning. commit 3f38218aa88a8a49557f26f77842fb8a46b5358b Merge: f85b89032 368318c9b Author: Tomasz Drwięga Date: Tue Dec 3 07:08:05 2019 +0100 Merge branch 'master' into td-signed-transactions commit f85b89032481aa4e3a791255dbd1c2672e2caf29 Merge: f8c954038 d8d5da2c6 Author: Tomasz Drwięga Date: Mon Dec 2 13:57:25 2019 +0100 Merge branch 'master' into td-signed-transactions commit f8c9540383e6e7481576476d341fea8e4937d1c7 Author: Tomasz Drwięga Date: Mon Nov 25 17:34:52 2019 +0100 Forgotten import. commit a645b90d6b785b36df52524d4c935a598da33402 Author: Tomasz Drwięga Date: Mon Nov 25 17:32:10 2019 +0100 Fix naming and bounds. commit bc28c60593c30c4f53a76df92f0c4546a29bc901 Author: Tomasz Drwięga Date: Mon Nov 25 17:01:05 2019 +0100 Add documentation to signed transactions and actually make them work. --- bin/node/runtime/src/lib.rs | 33 +++-- frame/system/src/offchain.rs | 248 +++++++++++++++++++++++++++++------ 2 files changed, 226 insertions(+), 55 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 190b623096b02..780dcaafdca74 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -778,22 +778,29 @@ impl_runtime_apis! { #[cfg(test)] mod tests { use super::*; - use system::offchain::SubmitSignedTransaction; - - fn is_submit_signed_transaction(_arg: T) where - T: SubmitSignedTransaction< - Runtime, - Call, - Extrinsic=UncheckedExtrinsic, - CreateTransaction=Runtime, - Signer=ImOnlineId, - >, - {} + use system::offchain::{SignAndSubmitTransaction, SubmitSignedTransaction}; #[test] fn validate_bounds() { - let x = SubmitImOnlineTransaction::default(); - is_submit_signed_transaction(x); + fn is_submit_signed_transaction() where + T: SubmitSignedTransaction< + Runtime, + Call, + >, + {} + + fn is_sign_and_submit_transaction() where + T: SignAndSubmitTransaction< + Runtime, + Call, + Extrinsic=UncheckedExtrinsic, + CreateTransaction=Runtime, + Signer=ImOnlineId, + >, + {} + + is_submit_signed_transaction::(); + is_sign_and_submit_transaction::(); } #[test] diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index b6f260d9e7351..8c8e15021d470 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -17,14 +17,52 @@ //! Module helpers for offchain calls. use codec::Encode; -use sp_runtime::app_crypto::{self, RuntimeAppPublic}; +use sp_std::convert::TryInto; +use sp_std::prelude::Vec; +use sp_runtime::app_crypto::{RuntimeAppPublic, AppPublic, AppSignature}; use sp_runtime::traits::{Extrinsic as ExtrinsicT, IdentifyAccount}; +use support::debug; + +/// Creates runtime-specific signed transaction. +/// +/// This trait should be implemented by your `Runtime` to be able +/// to submit `SignedTransaction`s` to the pool from offchain code. +pub trait CreateTransaction { + /// A `Public` key representing a particular `AccountId`. + type Public: Clone + IdentifyAccount; + /// A `Signature` generated by the `Signer`. + type Signature; + + /// Attempt to create signed extrinsic data that encodes call from given account. + /// + /// Runtime implementation is free to construct the payload to sign and the signature + /// in any way it wants. + /// Returns `None` if signed extrinsic could not be created (either because signing failed + /// or because of any other runtime-specific reason). + fn create_transaction>( + call: Extrinsic::Call, + public: Self::Public, + account: T::AccountId, + nonce: T::Index, + ) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>; +} /// A trait responsible for signing a payload using given account. +/// +/// This trait is usually going to represent a local public key +/// that has ability to sign arbitrary `Payloads`. +/// +/// NOTE: Most likely you don't need to implement this trait manually. +/// It has a blanket implementation for all `RuntimeAppPublic` types, +/// so it's enough to pass an application-specific crypto type. +/// +/// To easily create `SignedTransaction`s have a look at the +/// `TransactionSubmitter` type. pub trait Signer { /// Sign any encodable payload with given account and produce a signature. /// - /// Returns `Some` if signing succeeded and `None` in case the `account` couldn't be used. + /// Returns `Some` if signing succeeded and `None` in case the `account` couldn't + /// be used (for instance we couldn't convert it to required application specific crypto). fn sign(public: Public, payload: &Payload) -> Option; } @@ -33,22 +71,22 @@ pub trait Signer { /// This implementation additionaly supports conversion to/from multi-signature/multi-signer /// wrappers. /// If the wrapped crypto doesn't match `AppPublic`s crypto `None` is returned. -impl Signer for AppPublic where - AppPublic: RuntimeAppPublic - + app_crypto::AppPublic - + From<::Generic>, - ::Signature: app_crypto::AppSignature, +impl Signer for TAnyAppPublic where + TAnyAppPublic: RuntimeAppPublic + + AppPublic + + From<::Generic>, + ::Signature: AppSignature, Signature: From< - <::Signature as app_crypto::AppSignature>::Generic + <::Signature as AppSignature>::Generic >, - Public: sp_std::convert::TryInto<::Generic> + Public: TryInto<::Generic> { fn sign(public: Public, raw_payload: &Payload) -> Option { raw_payload.using_encoded(|payload| { let public = public.try_into().ok()?; - AppPublic::from(public).sign(&payload) + TAnyAppPublic::from(public).sign(&payload) .map( - <::Signature as app_crypto::AppSignature> + <::Signature as AppSignature> ::Generic::from ) .map(Signature::from) @@ -56,36 +94,20 @@ impl Signer for AppPublic where } } -/// Creates runtime-specific signed transaction. -pub trait CreateTransaction { - /// A `Public` key representing a particular `AccountId`. - type Public: IdentifyAccount + Clone; - /// A `Signature` generated by the `Signer`. - type Signature; - - /// Attempt to create signed extrinsic data that encodes call from given account. - /// - /// Runtime implementation is free to construct the payload to sign and the signature - /// in any way it wants. - /// Returns `None` if signed extrinsic could not be created (either because signing failed - /// or because of any other runtime-specific reason). - fn create_transaction>( - call: Extrinsic::Call, - public: Self::Public, - account: T::AccountId, - nonce: T::Index, - ) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>; -} - -type PublicOf = < - >::CreateTransaction as CreateTransaction< - T, - >::Extrinsic, - > +/// Retrieves a public key type for given `SignAndSubmitTransaction`. +pub type PublicOf = +< + >::CreateTransaction + as + CreateTransaction>::Extrinsic> >::Public; /// A trait to sign and submit transactions in offchain calls. -pub trait SubmitSignedTransaction { +/// +/// NOTE: Most likely you should not implement this trait yourself. +/// There is an implementation for `TransactionSubmitter` type, which +/// you should use. +pub trait SignAndSubmitTransaction { /// Unchecked extrinsic type. type Extrinsic: ExtrinsicT + codec::Encode; @@ -107,6 +129,12 @@ pub trait SubmitSignedTransaction { let call = call.into(); let id = public.clone().into_account(); let expected = >::account_nonce(&id); + debug::native::debug!( + target: "offchain", + "Creating signed transaction from account: {:?} (nonce: {:?})", + id, + expected, + ); let (call, signature_data) = Self::CreateTransaction ::create_transaction::(call, public, id, expected) .ok_or(())?; @@ -116,6 +144,10 @@ pub trait SubmitSignedTransaction { } /// A trait to submit unsigned transactions in offchain calls. +/// +/// NOTE: Most likely you should not implement this trait yourself. +/// There is an implementation for `TransactionSubmitter` type, which +/// you should use. pub trait SubmitUnsignedTransaction { /// Unchecked extrinsic type. type Extrinsic: ExtrinsicT + codec::Encode; @@ -130,9 +162,91 @@ pub trait SubmitUnsignedTransaction { } } +/// A utility trait to easily create signed transactions +/// from accounts in node's local keystore. +/// +/// NOTE: Most likely you should not implement this trait yourself. +/// There is an implementation for `TransactionSubmitter` type, which +/// you should use. +pub trait SubmitSignedTransaction { + /// A `SignAndSubmitTransaction` implementation. + type SignAndSubmit: SignAndSubmitTransaction; + + /// Find local keys that match given list of accounts. + /// + /// Technically it finds an intersection between given list of `AccountId`s + /// and accounts that are represented by public keys in local keystore. + /// If `None` is passed it returns all accounts in the keystore. + /// + /// Returns both public keys and `AccountId`s of accounts that are available. + /// Such accounts can later be used to sign a payload or send signed transactions. + fn find_local_keys(accounts: Option>) -> Vec<( + T::AccountId, + PublicOf, + )>; + + /// Create and submit signed transactions from supported accounts. + /// + /// This method should intersect given list of accounts with the ones + /// supported locally and submit signed transaction containing given `Call` + /// with every of them. + /// + /// Returns a vector of results and account ids that were supported. + #[must_use] + fn submit_signed_from( + call: impl Into + Clone, + accounts: impl IntoIterator, + ) -> Vec<(T::AccountId, Result<(), ()>)> { + let keys = Self::find_local_keys(Some(accounts)); + keys.into_iter().map(|(account, pub_key)| { + let call = call.clone().into(); + ( + account, + Self::SignAndSubmit::sign_and_submit(call, pub_key) + ) + }).collect() + } + + /// Create and submit signed transactions from all local accounts. + /// + /// This method submits a signed transaction from all local accounts + /// for given application crypto. + /// + /// Returns a vector of results and account ids that were supported. + #[must_use] + fn submit_signed( + call: impl Into + Clone, + ) -> Vec<(T::AccountId, Result<(), ()>)> { + let keys = Self::find_local_keys(None as Option>); + keys.into_iter().map(|(account, pub_key)| { + let call = call.clone().into(); + ( + account, + Self::SignAndSubmit::sign_and_submit(call, pub_key) + ) + }).collect() + } +} + + /// A default type used to submit transactions to the pool. -pub struct TransactionSubmitter { - _signer: sp_std::marker::PhantomData<(S, C, E)>, +/// +/// This is passed into each runtime as an opaque associated type that can have either of: +/// - [`SignAndSubmitTransaction`] +/// - [`SubmitUnsignedTransaction`] +/// - [`SubmitSignedTransaction`] +/// and used accordingly. +/// +/// This struct should be constructed by providing the following generic parameters: +/// * `Signer` - Usually the application specific key type (see `app_crypto`). +/// * `CreateTransaction` - A type that is able to produce signed transactions, +/// usually it's going to be the entire `Runtime` object. +/// * `Extrinsic` - A runtime-specific type for in-block extrinsics. +/// +/// If you only need the ability to submit unsigned transactions, +/// you may substitute both `Signer` and `CreateTransaction` with any type. +pub struct TransactionSubmitter { + _signer: sp_std::marker::PhantomData<(Signer, CreateTransaction, Extrinsic)>, } impl Default for TransactionSubmitter { @@ -144,7 +258,7 @@ impl Default for TransactionSubmitter { } /// A blanket implementation to simplify creation of transaction signer & submitter in the runtime. -impl SubmitSignedTransaction for TransactionSubmitter where +impl SignAndSubmitTransaction for TransactionSubmitter where T: crate::Trait, C: CreateTransaction, S: Signer<>::Public, >::Signature>, @@ -155,10 +269,60 @@ impl SubmitSignedTransaction for TransactionSubmitter type Signer = S; } -/// A blanket impl to use the same submitter for usigned transactions as well. +/// A blanket implementation to use the same submitter for unsigned transactions as well. impl SubmitUnsignedTransaction for TransactionSubmitter where T: crate::Trait, E: ExtrinsicT + codec::Encode, { type Extrinsic = E; } + +/// A blanket implementation to support local keystore of application-crypto types. +impl SubmitSignedTransaction for TransactionSubmitter where + T: crate::Trait, + C: CreateTransaction, + E: ExtrinsicT + codec::Encode, + S: Signer<>::Public, >::Signature>, + // Make sure we can unwrap the app crypto key. + S: RuntimeAppPublic + AppPublic + Into<::Generic>, + // Make sure we can convert from wrapped crypto to public key (e.g. `MultiSigner`) + S::Generic: Into>, + // For simplicity we require the same trait to implement `SignAndSubmitTransaction` too. + Self: SignAndSubmitTransaction, +{ + type SignAndSubmit = Self; + + fn find_local_keys(accounts: Option>) -> Vec<( + T::AccountId, + PublicOf, + )> { + // Convert app-specific keys into generic ones. + let local_accounts_and_keys = S::all() + .into_iter() + .map(|app_key| { + // unwrap app-crypto + let generic_pub_key: ::Generic = app_key.into(); + // convert to expected public key type (might be MultiSigner) + let signer_pub_key: PublicOf = generic_pub_key.into(); + // lookup accountid for that pubkey + let account = signer_pub_key.clone().into_account(); + (account, signer_pub_key) + }).collect::>(); + + if let Some(accounts) = accounts { + let mut local_accounts_and_keys = local_accounts_and_keys; + // sort by accountId to allow bin-search. + local_accounts_and_keys.sort_by(|a, b| a.0.cmp(&b.0)); + + // get all the matching accounts + accounts.into_iter().filter_map(|acc| { + let idx = local_accounts_and_keys.binary_search_by(|a| a.0.cmp(&acc)).ok()?; + local_accounts_and_keys.get(idx).cloned() + }).collect() + } else { + // just return all account ids and keys + local_accounts_and_keys + } + } +} + From 044c7f061e210ca9d0c14aa1a09618195148ae21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 17:05:08 +0000 Subject: [PATCH 20/78] grandpa: skip block initialization on report submission method --- primitives/finality-grandpa/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 59105db176be3..a621329f58920 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -435,6 +435,7 @@ sp_api::decl_runtime_apis! { /// is finalized by the authorities from block B-1. fn grandpa_authorities() -> AuthorityList; + #[skip_initialize_block] fn submit_report_equivocation_extrinsic( equivocation_report: EquivocationReport>, key_owner_proof: Vec, From 17af714297c2865cb589ccc7bee8a5a81385a52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 17:06:03 +0000 Subject: [PATCH 21/78] primitives: allow transaction pool access by default for offchain calls --- primitives/core/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 3241f9b4ff3ef..a1157da091a71 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -111,8 +111,11 @@ impl ExecutionContext { match self { Importing | Syncing | BlockConstruction => offchain::Capabilities::none(), - // Enable keystore by default for offchain calls. CC @bkchr - OffchainCall(None) => [offchain::Capability::Keystore][..].into(), + // Enable keystore and transaction pool by default for offchain calls. CC @bkchr + OffchainCall(None) => [ + offchain::Capability::Keystore, + offchain::Capability::TransactionPool, + ][..].into(), OffchainCall(Some((_, capabilities))) => *capabilities, } } From 29e4f9456d823759ca16283d5fc29f4e04d26524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 17:06:24 +0000 Subject: [PATCH 22/78] grandpa: unused parameters --- frame/grandpa/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index de9eb025ac461..0c14291b379c8 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -100,22 +100,22 @@ impl HandleEquivocation for () { type KeyOwnerIdentification = (); fn check_proof( - equivocation_report: &EquivocationReport, - key_owner_proof: Self::KeyOwnerProof, + _equivocation_report: &EquivocationReport, + _key_owner_proof: Self::KeyOwnerProof, ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { None } fn report_offence( - reporters: Vec, - offence: GrandpaEquivocationOffence, + _reporters: Vec, + _offence: GrandpaEquivocationOffence, ) { } fn submit_equivocation_report( - equivocation_report: EquivocationReport, - key_owner_proof: Self::KeyOwnerProof, + _equivocation_report: EquivocationReport, + _key_owner_proof: Self::KeyOwnerProof, ) -> Result { Ok(()) } From e69c86ea16e517e9c001b707f286db81b9c29d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 17:06:35 +0000 Subject: [PATCH 23/78] grandpa: remove unused method --- client/finality-grandpa/src/communication/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/finality-grandpa/src/communication/mod.rs index 00b412435e90b..34a16f114bb07 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -689,10 +689,6 @@ impl> Clone for NetworkBridge { } } -pub(crate) fn localized_payload(round: RoundNumber, set_id: SetIdNumber, message: &E) -> Vec { - (message, round, set_id).encode() -} - /// Type-safe wrapper around a round number. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)] pub struct Round(pub RoundNumber); From 856087d17dad2f8a0ce70ab64b40460e5894777c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 17:20:56 +0000 Subject: [PATCH 24/78] grandpa: enable equivocation reporting --- bin/node/runtime/src/lib.rs | 13 ++++++------- frame/grandpa/src/lib.rs | 25 ++++++++++++++----------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 780dcaafdca74..475bfe93559b1 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -475,13 +475,12 @@ use report::ReporterId; impl grandpa::Trait for Runtime { type Event = Event; type Call = Call; - type HandleEquivocation = (); - // type HandleEquivocation = grandpa::EquivocationHandler< - // Historical, - // TransactionSubmitter, - // Offences, - // ReporterId, - // >; + type HandleEquivocation = grandpa::EquivocationHandler< + Historical, + TransactionSubmitter, + Offences, + ReporterId, + >; } parameter_types! { diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 0c14291b379c8..ef672c65588b2 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -134,7 +134,7 @@ impl Default for EquivocationHandler { } impl HandleEquivocation for EquivocationHandler where - T: Trait, + T: Trait, // A system for proving ownership of keys, i.e. that a given key was part // of a validator set, needed for validating equivocation reports. The // session index and validator count of the session are part of the proof @@ -185,18 +185,21 @@ impl HandleEquivocation for EquivocationHandler wh equivocation_report: EquivocationReport, key_owner_proof: Self::KeyOwnerProof, ) -> Result { - // let call = Call::report_equivocation( - // equivocation_report.clone(), - // key_owner_proof.clone(), - // ); + let call = Call::report_equivocation( + equivocation_report.clone(), + key_owner_proof, + ); - // // FIXME: add key, depends on: https://github.com/paritytech/substrate/pull/4200 - // let ext = T::SubmitTransaction::sign_and_submit( - // call, - // unimplemented!(), - // ).ok(); + let res = S::submit_signed_from( + call, + K::all().into_iter().map(|k| k.into_account()), + ); - Ok(()) + if res.iter().any(|(_, r)| r.is_ok()) { + Ok(()) + } else { + Err("Error submitting equivocation report.") + } } } From 4e4a27bd53de377fd5e0f619ddd4a718bead9793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 17:24:12 +0000 Subject: [PATCH 25/78] grandpa: add workaround for parameter encoding --- frame/grandpa/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index ef672c65588b2..61a66c5e00389 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -187,7 +187,7 @@ impl HandleEquivocation for EquivocationHandler wh ) -> Result { let call = Call::report_equivocation( equivocation_report.clone(), - key_owner_proof, + key_owner_proof.encode(), ); let res = S::submit_signed_from( @@ -336,10 +336,16 @@ decl_module! { fn report_equivocation( origin, equivocation_report: EquivocationReport, - key_owner_proof: >::KeyOwnerProof, + // key_owner_proof: >::KeyOwnerProof, + key_owner_proof: Vec, ) { let reporter_id = ensure_signed(origin)?; + // FIXME: this is a hack needed because the typed argument version above fails to + // compile (due to missing codec implementation) + let key_owner_proof = Decode::decode(&mut &key_owner_proof[..]) + .map_err(|_| "Key owner proof decoding failed.")?; + // validate the membership proof and extract session index and // validator set count of the session that we're proving membership of let (offender, session_index, validator_set_count) = From 47aab63fbcb707d4b6df531ba31c593b92a183b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 11 Dec 2019 17:43:41 +0000 Subject: [PATCH 26/78] grandpa: fix localized_payload calls in tests --- client/finality-grandpa/src/communication/tests.rs | 4 ++-- client/finality-grandpa/src/tests.rs | 2 +- primitives/finality-grandpa/src/lib.rs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/finality-grandpa/src/communication/tests.rs b/client/finality-grandpa/src/communication/tests.rs index 86bbdb8f64f40..c8426b394a635 100644 --- a/client/finality-grandpa/src/communication/tests.rs +++ b/client/finality-grandpa/src/communication/tests.rs @@ -230,7 +230,7 @@ fn good_commit_leads_to_relay() { let target_number = 500; let precommit = grandpa::Precommit { target_hash: target_hash.clone(), target_number }; - let payload = super::localized_payload( + let payload = fg_primitives::localized_payload( round, set_id, &grandpa::Message::Precommit(precommit.clone()) ); @@ -345,7 +345,7 @@ fn bad_commit_leads_to_report() { let target_number = 500; let precommit = grandpa::Precommit { target_hash: target_hash.clone(), target_number }; - let payload = super::localized_payload( + let payload = fg_primitives::localized_payload( round, set_id, &grandpa::Message::Precommit(precommit.clone()) ); diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 33672ba56f55a..e4621cd7fbd10 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -1723,7 +1723,7 @@ fn imports_justification_for_regular_blocks_on_import() { }; let msg = grandpa::Message::Precommit(precommit.clone()); - let encoded = communication::localized_payload(round, set_id, &msg); + let encoded = fg_primitives::localized_payload(round, set_id, &msg); let signature = peers[0].sign(&encoded[..]).into(); let precommit = grandpa::SignedPrecommit { diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index a621329f58920..83c30aa035eaa 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -296,7 +296,8 @@ pub fn check_equivocation_report( } } -fn localized_payload( +// round message localized to a given round and set id. +pub fn localized_payload( round: RoundNumber, set_id: SetId, message: &E, From 9e1586e903ceeb58b32fbde74cecc8d828f0f81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 28 Jan 2020 16:48:02 +0000 Subject: [PATCH 27/78] fix submit_report_equivocation_extrinsic in runtimes --- bin/node-template/runtime/src/lib.rs | 8 ++++++-- test-utils/runtime/src/lib.rs | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index e7a95f15f9575..7b7dc71c17d75 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -15,7 +15,8 @@ use sp_runtime::{ impl_opaque_keys, MultiSignature, KeyTypeId, }; use sp_runtime::traits::{ - BlakeTwo256, Block as BlockT, StaticLookup, Verify, ConvertInto, IdentifyAccount + BlakeTwo256, Block as BlockT, StaticLookup, Verify, ConvertInto, + IdentifyAccount, NumberFor, }; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -384,7 +385,10 @@ impl_runtime_apis! { } fn submit_report_equivocation_extrinsic( - _equivocation_report: fg_primitives::EquivocationReport, + _equivocation_report: fg_primitives::EquivocationReport< + ::Hash, + NumberFor, + >, _key_owner_proof: Vec, ) -> Option<()> { None diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index a72670febab98..fb2cd2c91eb06 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -40,7 +40,7 @@ use sp_runtime::{ }, traits::{ BlindCheckable, BlakeTwo256, Block as BlockT, Extrinsic as ExtrinsicT, - GetNodeBlockType, GetRuntimeBlockType, Verify, IdentityLookup, + GetNodeBlockType, GetRuntimeBlockType, NumberFor, Verify, IdentityLookup, }, }; use sp_version::RuntimeVersion; @@ -654,7 +654,10 @@ cfg_if! { } fn submit_report_equivocation_extrinsic( - _equivocation_report: sp_finality_grandpa::EquivocationReport, + _equivocation_report: sp_finality_grandpa::EquivocationReport< + ::Hash, + NumberFor, + >, _key_owner_proof: Vec, ) -> Option<()> { None From a3c62ea57ec72c9fd2078c4b0664668ed331a7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 28 Jan 2020 17:13:55 +0000 Subject: [PATCH 28/78] node: fix submit transaction test compilation --- bin/node/executor/tests/submit_transaction.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs index 9e91ffc76b38f..3fb4237db18fd 100644 --- a/bin/node/executor/tests/submit_transaction.rs +++ b/bin/node/executor/tests/submit_transaction.rs @@ -15,7 +15,7 @@ // along with Substrate. If not, see . use node_runtime::{ - Call, Executive, Indices, Runtime, SubmitTransaction, UncheckedExtrinsic, + Call, Executive, Indices, Runtime, SubmitImOnlineTransaction, UncheckedExtrinsic, }; use sp_application_crypto::AppKey; use sp_core::testing::KeyStore; @@ -47,7 +47,7 @@ fn should_submit_unsigned_transaction() { }; let call = pallet_im_online::Call::heartbeat(heartbeat_data, signature); - > + > ::submit_unsigned(call) .unwrap(); @@ -70,17 +70,17 @@ fn should_submit_signed_transaction() { t.register_extension(KeystoreExt(keystore)); t.execute_with(|| { - let keys = > + let keys = > ::find_all_local_keys(); assert_eq!(keys.len(), 3, "Missing keys: {:?}", keys); - let can_sign = > + let can_sign = > ::can_sign(); assert!(can_sign, "Since there are keys, `can_sign` should return true"); let call = pallet_balances::Call::transfer(Default::default(), Default::default()); let results = - >::submit_signed(call); + >::submit_signed(call); let len = results.len(); assert_eq!(len, 3); @@ -102,7 +102,7 @@ fn should_submit_signed_twice_from_the_same_account() { t.execute_with(|| { let call = pallet_balances::Call::transfer(Default::default(), Default::default()); let results = - >::submit_signed(call); + >::submit_signed(call); let len = results.len(); assert_eq!(len, 1); @@ -112,7 +112,7 @@ fn should_submit_signed_twice_from_the_same_account() { // submit another one from the same account. The nonce should be incremented. let call = pallet_balances::Call::transfer(Default::default(), Default::default()); let results = - >::submit_signed(call); + >::submit_signed(call); let len = results.len(); assert_eq!(len, 1); @@ -152,7 +152,7 @@ fn submitted_transaction_should_be_valid() { t.execute_with(|| { let call = pallet_balances::Call::transfer(Default::default(), Default::default()); let results = - >::submit_signed(call); + >::submit_signed(call); let len = results.len(); assert_eq!(len, 1); assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); From bd814e7d78e2ccf93142fa7b3301898ac6f4b1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 28 Jan 2020 17:16:37 +0000 Subject: [PATCH 29/78] node: bump spec_version --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8d52d72725002..0c8f10b88a956 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -84,7 +84,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 205, + spec_version: 206, impl_version: 206, apis: RUNTIME_API_VERSIONS, }; From 9ac2c45811eeb95bf8fecd66d8e11493ed11b34f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 29 Jan 2020 12:32:52 +0000 Subject: [PATCH 30/78] rpc: fix api version test --- client/rpc/src/state/tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index a0ab11e977204..62b2f72612f73 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -404,7 +404,8 @@ fn should_return_runtime_version() { \"specVersion\":1,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",2],\ [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",1],[\"0x40fe3ad401f8959a\",4],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",1],\ - [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]]}"; + [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xdd8e34c0eb9b2e5a\",1],\ + [\"0xbc9d89904f5b923f\",1]]}"; let runtime_version = api.runtime_version(None.into()).wait().unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); From ee3f6b05b143c3647dcb7c826567d6d05ed9dada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 24 Feb 2020 18:09:55 +0000 Subject: [PATCH 31/78] grandpa: allow custom equivocation offence type --- frame/grandpa/src/lib.rs | 92 ++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index c0ac3f9971397..a5c81c029aed5 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -78,16 +78,14 @@ pub trait Trait: frame_system::Trait { pub trait HandleEquivocation { type KeyOwnerProof: Clone + Debug + Decode + Encode + PartialEq; type KeyOwnerIdentification; + type Offence: GrandpaOffence; fn check_proof( equivocation_report: &EquivocationReport, key_owner_proof: Self::KeyOwnerProof, ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; - fn report_offence( - reporters: Vec, - offence: GrandpaEquivocationOffence, - ); + fn report_offence(reporters: Vec, offence: Self::Offence); fn submit_equivocation_report( equivocation_report: EquivocationReport, @@ -98,6 +96,7 @@ pub trait HandleEquivocation { impl HandleEquivocation for () { type KeyOwnerProof = (); type KeyOwnerIdentification = (); + type Offence = GrandpaEquivocationOffence; fn check_proof( _equivocation_report: &EquivocationReport, @@ -121,11 +120,19 @@ impl HandleEquivocation for () { } } -pub struct EquivocationHandler { - _phantom: sp_std::marker::PhantomData<(P, S, R, K)>, +pub struct EquivocationHandler< + P, + S, + R, + K, + O = GrandpaEquivocationOffence< +

)>>::IdentificationTuple, + >, +> { + _phantom: sp_std::marker::PhantomData<(P, S, O, R, K)>, } -impl Default for EquivocationHandler { +impl Default for EquivocationHandler { fn default() -> Self { Self { _phantom: Default::default(), @@ -133,23 +140,18 @@ impl Default for EquivocationHandler { } } -impl HandleEquivocation for EquivocationHandler where - T: Trait, +impl HandleEquivocation for EquivocationHandler +where + T: Trait, // A system for proving ownership of keys, i.e. that a given key was part // of a validator set, needed for validating equivocation reports. The // session index and validator count of the session are part of the proof // as extra data. - P: KeyOwnerProofSystem< - (KeyTypeId, Vec), - ExtraData = (SessionIndex, u32), - >, + P: KeyOwnerProofSystem<(KeyTypeId, Vec), ExtraData = (SessionIndex, u32)>, // A transaction submitter. Used for submitting equivocation reports. S: SubmitSignedTransaction::Call>, - R: ReportOffence< - T::AccountId, - P::IdentificationTuple, - GrandpaEquivocationOffence, - >, + O: GrandpaOffence, + R: ReportOffence, // Key type to use when signing equivocation report transactions, must be // convertible to and from an account id since that's what we need to use // to sign transactions. @@ -157,6 +159,7 @@ impl HandleEquivocation for EquivocationHandler wh { type KeyOwnerProof = P::Proof; type KeyOwnerIdentification = P::IdentificationTuple; + type Offence = O; fn check_proof( equivocation_report: &EquivocationReport, @@ -171,14 +174,8 @@ impl HandleEquivocation for EquivocationHandler wh Some((offender, session_index, validator_set_count)) } - fn report_offence( - reporters: Vec, - offence: GrandpaEquivocationOffence, - ) { - R::report_offence( - reporters, - offence, - ); + fn report_offence(reporters: Vec, offence: O) { + R::report_offence(reporters, offence); } fn submit_equivocation_report( @@ -407,15 +404,13 @@ decl_module! { // report to the offences module rewarding the sender. T::HandleEquivocation::report_offence( vec![reporter_id], - GrandpaEquivocationOffence { + >::Offence::new( session_index, validator_set_count, offender, - time_slot: GrandpaTimeSlot { - set_id, - round: equivocation_report.round(), - }, - }, + set_id, + equivocation_report.round(), + ), ); } @@ -728,7 +723,38 @@ pub struct GrandpaEquivocationOffence { offender: FullIdentification, } -impl Offence for GrandpaEquivocationOffence { +pub trait GrandpaOffence: Offence { + fn new( + session_index: SessionIndex, + validator_set_count: u32, + offender: FullIdentification, + set_id: SetId, + round: RoundNumber, + ) -> Self; +} + +impl GrandpaOffence + for GrandpaEquivocationOffence +{ + fn new( + session_index: SessionIndex, + validator_set_count: u32, + offender: FullIdentification, + set_id: SetId, + round: RoundNumber, + ) -> Self { + GrandpaEquivocationOffence { + session_index, + validator_set_count, + offender, + time_slot: GrandpaTimeSlot { set_id, round }, + } + } +} + +impl Offence + for GrandpaEquivocationOffence +{ const ID: Kind = *b"grandpa:equivoca"; type TimeSlot = GrandpaTimeSlot; From 45d2550f5c56499407e4f713b692d890fc320a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 25 Feb 2020 15:18:07 +0000 Subject: [PATCH 32/78] grandpa: add test for authorities::next_change_height --- client/finality-grandpa/src/authorities.rs | 120 +++++++++++++++++++++ client/finality-grandpa/src/environment.rs | 17 +-- 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index d3fa77a90f019..8b6ecb5bcc03c 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -874,4 +874,124 @@ mod tests { }) ); } + + #[test] + fn next_change_height_works() { + let mut authorities = AuthoritySet { + current_authorities: Vec::new(), + set_id: 0, + pending_standard_changes: ForkTree::new(), + pending_forced_changes: Vec::new(), + }; + + let new_set = Vec::new(); + + // We have three pending changes with 2 possible roots that are enacted + // immediately on finality (i.e. standard changes). + let change_a0 = PendingChange { + next_authorities: new_set.clone(), + delay: 0, + canon_height: 5, + canon_hash: "hash_a0", + delay_kind: DelayKind::Finalized, + }; + + let change_a1 = PendingChange { + next_authorities: new_set.clone(), + delay: 0, + canon_height: 10, + canon_hash: "hash_a1", + delay_kind: DelayKind::Finalized, + }; + + let change_b = PendingChange { + next_authorities: new_set.clone(), + delay: 0, + canon_height: 4, + canon_hash: "hash_b", + delay_kind: DelayKind::Finalized, + }; + + // A0 (#5) <- A10 (#8) <- A1 (#10) <- best_a + // B (#4) <- best_b + let is_descendent_of = is_descendent_of(|base, hash| match (*base, *hash) { + ("hash_a0", "hash_a1") => true, + ("hash_a0", "best_a") => true, + ("hash_a1", "best_a") => true, + ("hash_a10", "best_a") => true, + ("hash_b", "best_b") => true, + _ => false, + }); + + // add the three pending changes + authorities + .add_pending_change(change_b, &is_descendent_of) + .unwrap(); + authorities + .add_pending_change(change_a0, &is_descendent_of) + .unwrap(); + authorities + .add_pending_change(change_a1, &is_descendent_of) + .unwrap(); + + // the earliest change at block `best_a` should be the change at A0 (#5) + assert_eq!( + authorities + .next_change_height(&"best_a", &is_descendent_of) + .unwrap(), + Some(5), + ); + + // the earliest change at block `best_b` should be the change at B (#4) + assert_eq!( + authorities + .next_change_height(&"best_b", &is_descendent_of) + .unwrap(), + Some(4), + ); + + // we apply the change at A0 which should prune it and the fork at B + authorities + .apply_standard_changes("hash_a0", 5, &is_descendent_of) + .unwrap(); + + // the next change is now at A1 (#10) + assert_eq!( + authorities + .next_change_height(&"best_a", &is_descendent_of) + .unwrap(), + Some(10), + ); + + // there's no longer any pending change at `best_b` fork + assert_eq!( + authorities + .next_change_height(&"best_b", &is_descendent_of) + .unwrap(), + None, + ); + + // we a forced change at A10 (#8) + let change_a10 = PendingChange { + next_authorities: new_set.clone(), + delay: 0, + canon_height: 8, + canon_hash: "hash_a10", + delay_kind: DelayKind::Best { + median_last_finalized: 0, + }, + }; + + authorities + .add_pending_change(change_a10, &static_is_descendent_of(false)) + .unwrap(); + + // it should take precedence over the change at A1 (#10) + assert_eq!( + authorities + .next_change_height(&"best_a", &is_descendent_of) + .unwrap(), + Some(8), + ); + } } diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 72ea31fdfa0dd..1366d9e1e95ea 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -947,7 +947,8 @@ fn report_equivocation( authority_set: &AuthoritySet>, select_chain: &SC, equivocation: Equivocation>, -) -> Result<(), String> where +) -> Result<(), String> +where Block: BlockT, B: HeaderBackend + HeaderMetadata, PRA: ProvideRuntimeApi, @@ -1014,13 +1015,13 @@ fn report_equivocation( equivocation, ); - // api.runtime_api() - // .submit_report_equivocation_extrinsic( - // &BlockId::Hash(best_header.hash()), - // equivocation_report, - // membership_proof.encode(), - // ) - // .unwrap(); + api.runtime_api() + .submit_report_equivocation_extrinsic( + &BlockId::Hash(best_header.hash()), + equivocation_report, + membership_proof.encode(), + ) + .unwrap(); Ok(()) } From 3b4c910f2f4410c146e22e52c3ec71666b4bb837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 25 Feb 2020 17:46:55 +0000 Subject: [PATCH 33/78] grandpa: cleanup report_equivocation function --- client/finality-grandpa/src/authorities.rs | 66 ++++++----- client/finality-grandpa/src/environment.rs | 127 ++++++++++++--------- client/finality-grandpa/src/lib.rs | 12 +- 3 files changed, 113 insertions(+), 92 deletions(-) diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 8b6ecb5bcc03c..102f448a2bb0e 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -121,28 +121,29 @@ where H: PartialEq, impl AuthoritySet where - N: Add + Ord + Clone + Debug, - H: Clone + Debug + N: Add + Ord + Clone + Debug, + H: Clone + Debug, { - /// Returns the block height at which the next pending change in the given - /// chain (i.e. it includes `best_hash`) was signalled, `None` if there are - /// no pending changes for the given chain. + /// Returns the block hash and height at which the next pending change in + /// the given chain (i.e. it includes `best_hash`) was signalled, `None` if + /// there are no pending changes for the given chain. /// /// This is useful since we know that when a change is signalled the /// underlying runtime authority set management module (e.g. session module) /// has updated its internal state (e.g. a new session started). - pub(crate) fn next_change_height( + pub(crate) fn next_change( &self, best_hash: &H, is_descendent_of: &F, - ) -> Result, fork_tree::Error> where + ) -> Result, fork_tree::Error> + where F: Fn(&H, &H) -> Result, - E: std::error::Error, + E: std::error::Error, { let mut forced = None; for change in &self.pending_forced_changes { if is_descendent_of(&change.canon_hash, best_hash)? { - forced = Some(change.canon_height.clone()); + forced = Some((change.canon_hash.clone(), change.canon_height.clone())); break; } } @@ -150,28 +151,33 @@ where let mut standard = None; for (_, _, change) in self.pending_standard_changes.roots() { if is_descendent_of(&change.canon_hash, best_hash)? { - standard = Some(change.canon_height.clone()); + standard = Some((change.canon_hash.clone(), change.canon_height.clone())); break; } } - let n = match (standard, forced) { - (Some(standard), Some(forced)) => Some(standard.min(forced)), - (Some(standard), None) => Some(standard), - (None, Some(forced)) => Some(forced), + let earliest = match (forced, standard) { + (Some(forced), Some(standard)) => Some(if forced.1 < standard.1 { + forced + } else { + standard + }), + (Some(forced), None) => Some(forced), + (None, Some(standard)) => Some(standard), (None, None) => None, }; - Ok(n) + Ok(earliest) } fn add_standard_change( &mut self, pending: PendingChange, is_descendent_of: &F, - ) -> Result<(), fork_tree::Error> where + ) -> Result<(), fork_tree::Error> + where F: Fn(&H, &H) -> Result, - E: std::error::Error, + E: std::error::Error, { let hash = pending.canon_hash.clone(); let number = pending.canon_height.clone(); @@ -876,7 +882,7 @@ mod tests { } #[test] - fn next_change_height_works() { + fn next_change_works() { let mut authorities = AuthoritySet { current_authorities: Vec::new(), set_id: 0, @@ -937,20 +943,20 @@ mod tests { // the earliest change at block `best_a` should be the change at A0 (#5) assert_eq!( authorities - .next_change_height(&"best_a", &is_descendent_of) + .next_change(&"best_a", &is_descendent_of) .unwrap(), - Some(5), + Some(("hash_a0", 5)), ); - // the earliest change at block `best_b` should be the change at B (#4) + // the earliest change at block `best_b` should be the change at B (#4) assert_eq!( authorities - .next_change_height(&"best_b", &is_descendent_of) + .next_change(&"best_b", &is_descendent_of) .unwrap(), - Some(4), + Some(("hash_b", 4)), ); - // we apply the change at A0 which should prune it and the fork at B + // we apply the change at A0 which should prune it and the fork at B authorities .apply_standard_changes("hash_a0", 5, &is_descendent_of) .unwrap(); @@ -958,15 +964,15 @@ mod tests { // the next change is now at A1 (#10) assert_eq!( authorities - .next_change_height(&"best_a", &is_descendent_of) + .next_change(&"best_a", &is_descendent_of) .unwrap(), - Some(10), + Some(("hash_a1", 10)), ); - // there's no longer any pending change at `best_b` fork + // there's no longer any pending change at `best_b` fork assert_eq!( authorities - .next_change_height(&"best_b", &is_descendent_of) + .next_change(&"best_b", &is_descendent_of) .unwrap(), None, ); @@ -989,9 +995,9 @@ mod tests { // it should take precedence over the change at A1 (#10) assert_eq!( authorities - .next_change_height(&"best_a", &is_descendent_of) + .next_change(&"best_a", &is_descendent_of) .unwrap(), - Some(8), + Some(("hash_a10", 8)), ); } } diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 1366d9e1e95ea..9094a5ce247ed 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -563,10 +563,10 @@ where Block: 'static, B: Backend + 'static, E: CallExecutor + 'static + Send + Sync, - N: NetworkT + 'static + Send, + N: NetworkT + 'static + Send, RA: 'static + Send + Sync, PRA: ProvideRuntimeApi, - PRA::Api: GrandpaApi + SessionMembership, + PRA::Api: GrandpaApi + SessionMembership, SC: SelectChain + 'static, VR: VotingRule>, NumberFor: BlockNumberOps, @@ -911,105 +911,121 @@ where fn prevote_equivocation( &self, _round: RoundNumber, - equivocation: finality_grandpa::Equivocation, Self::Signature> + equivocation: finality_grandpa::Equivocation, Self::Signature>, ) { warn!(target: "afg", "Detected prevote equivocation in the finality worker: {:?}", equivocation); - report_equivocation( + match report_equivocation( &*self.client, &*self.api, &self.authority_set.inner().read(), &self.select_chain, equivocation.into(), - ); + ) { + Ok(_) => {} + Err(err) => { + warn!(target: "afg", "Error reporting prevote equivocation: {:?}", err); + } + }; } fn precommit_equivocation( &self, _round: RoundNumber, - equivocation: finality_grandpa::Equivocation, Self::Signature> + equivocation: finality_grandpa::Equivocation, Self::Signature>, ) { warn!(target: "afg", "Detected precommit equivocation in the finality worker: {:?}", equivocation); - report_equivocation( + match report_equivocation( &*self.client, &*self.api, &self.authority_set.inner().read(), &self.select_chain, equivocation.into(), - ); + ) { + Ok(_) => {} + Err(err) => { + warn!(target: "afg", "Error reporting precommit equivocation: {:?}", err); + } + }; } } -// TODO: how to make this optional? - +/// Report the given equivocation to the GRANDPA runtime module. This method +/// generates a session membership proof of the offender and then submits an +/// extrinsic to report the equivocation. In particular, the session membership +/// proof must be generated at the block at which the given set was active which +/// isn't necessarily the best block if there are pending authority set changes. fn report_equivocation( backend: &B, api: &PRA, authority_set: &AuthoritySet>, select_chain: &SC, equivocation: Equivocation>, -) -> Result<(), String> +) -> Result<(), Error> where Block: BlockT, B: HeaderBackend + HeaderMetadata, PRA: ProvideRuntimeApi, - PRA::Api: GrandpaApi + SessionMembership, + PRA::Api: GrandpaApi + SessionMembership, SC: SelectChain + 'static, { let is_descendent_of = is_descendent_of(backend, None); - let best_header = select_chain.best_chain().unwrap(); - let next_change_height = authority_set.next_change_height( - &best_header.hash(), - &is_descendent_of, - ).unwrap(); - - let current_set_latest_height = match next_change_height { - Some(n) if n.is_zero() => - return Err("Authority set change signalled at genesis.".to_string()), + let best_header = select_chain + .best_chain() + .map_err(|e| Error::Blockchain(e.to_string()))?; + + // block hash and number of the next pending authority set change in the + // given best chain. + let next_change = authority_set + .next_change(&best_header.hash(), &is_descendent_of) + .map_err(|e| Error::Safety(e.to_string()))?; + + // find the hash of the latest block in the current set + let current_set_latest_hash = match next_change { + Some((_, n)) if n.is_zero() => { + return Err(Error::Safety( + "Authority set change signalled at genesis.".to_string(), + )) + } // the next set starts at `n` so the current one lasts until `n - 1`. if // `n` is later than the best block, then the current set is still live // at best block. - Some(n) if n > *best_header.number() => *best_header.number(), - Some(n) => n - One::one(), - // there is no pending change, the latest block for the current set is - // the best block. - None => *best_header.number(), - }; - - // FIXME: clean up - // find the header of the latest block in the current set - let current_set_latest_header = { - if current_set_latest_height == *best_header.number() { - best_header.clone() - } else { - let h = backend.header(BlockId::Number(current_set_latest_height)).unwrap().unwrap(); + Some((_, n)) if n > *best_header.number() => best_header.hash(), + Some((h, _)) => { + // this is the header at which the new set will start + let header = backend.header(BlockId::Hash(h))?.expect( + "got block hash from registered pending change; \ + pending changes are only registered on block import; qed.", + ); - // make sure that the given block is in the same chain as "best" - if is_descendent_of(&h.hash(), &best_header.hash()).unwrap() { - h - } else { - let mut current = best_header.clone(); - loop { - if *current.number() == current_set_latest_height { - break; - } - current = backend.header(BlockId::Hash(*current.parent_hash())).unwrap().unwrap(); - } - current - } + // its parent block is the last block in the current set + *header.parent_hash() } + // there is no pending change, the latest block for the current set is + // the best block. + None => best_header.hash(), }; // generate membership proof at that block - let membership_proof = api.runtime_api() + let membership_proof = match api + .runtime_api() .generate_session_membership_proof( - &BlockId::Hash(current_set_latest_header.hash()), - (sp_finality_grandpa::KEY_TYPE, equivocation.offender().encode()), + &BlockId::Hash(current_set_latest_hash), + ( + sp_finality_grandpa::KEY_TYPE, + equivocation.offender().encode(), + ), ) - .unwrap() - .unwrap(); + .map_err(Error::Client)? + { + Some(proof) => proof, + None => { + debug!(target: "afg", "Equivocation offender is not part of the authority set."); + return Ok(()); + } + }; - // submit equivocation report at best block + // submit equivocation report at **best** block let equivocation_report = EquivocationReport::new( authority_set.set_id, equivocation, @@ -1020,8 +1036,7 @@ where &BlockId::Hash(best_header.hash()), equivocation_report, membership_proof.encode(), - ) - .unwrap(); + ).map_err(Error::Client)?; Ok(()) } diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 5ad9815bf49c5..14a06ffcaf69f 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -554,8 +554,8 @@ pub fn run_grandpa_voter( DigestFor: Encode, RA: Send + Sync + 'static, PRA: ProvideRuntimeApi + Send + Sync + 'static, - PRA::Api: GrandpaApi + SessionMembership, - X: futures::Future + Clone + Send + Unpin + 'static, + PRA::Api: GrandpaApi + SessionMembership, + X: futures::Future + Clone + Send + Unpin + 'static, Client: AuxStore, { let GrandpaParams { @@ -648,7 +648,7 @@ where NumberFor: BlockNumberOps, RA: Send + Sync + 'static, PRA: ProvideRuntimeApi + Send + Sync + 'static, - PRA::Api: GrandpaApi + SessionMembership, + PRA::Api: GrandpaApi + SessionMembership, E: CallExecutor + Send + Sync + 'static, B: Backend + 'static, SC: SelectChain + 'static, @@ -829,7 +829,7 @@ where NumberFor: BlockNumberOps, RA: Send + Sync + 'static, PRA: ProvideRuntimeApi + Send + Sync + 'static, - PRA::Api: GrandpaApi + SessionMembership, + PRA::Api: GrandpaApi + SessionMembership, E: CallExecutor + Send + Sync + 'static, B: Backend + 'static, SC: SelectChain + 'static, @@ -886,9 +886,9 @@ pub fn run_grandpa( DigestFor: Encode, RA: Send + Sync + 'static, PRA: ProvideRuntimeApi + Send + Sync + 'static, - PRA::Api: GrandpaApi + SessionMembership, + PRA::Api: GrandpaApi + SessionMembership, VR: VotingRule> + Clone + 'static, - X: futures::Future + Clone + Send + Unpin + 'static, + X: futures::Future + Clone + Send + Unpin + 'static, Client: AuxStore, { run_grandpa_voter(grandpa_params) From aced659571e05cf166ca5ae108c80a1864d241be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 25 Feb 2020 23:00:50 +0000 Subject: [PATCH 34/78] node: move reporting app crypto to node-primitives --- Cargo.lock | 3 ++- bin/node/primitives/Cargo.toml | 4 ++++ bin/node/primitives/src/lib.rs | 29 +++++++++++++++++++++++++++++ bin/node/runtime/Cargo.toml | 2 -- bin/node/runtime/src/lib.rs | 26 ++------------------------ primitives/core/src/lib.rs | 2 +- 6 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a594aa1045053..e719a6b143b29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3464,7 +3464,9 @@ dependencies = [ name = "node-primitives" version = "2.0.0" dependencies = [ + "parity-scale-codec", "pretty_assertions", + "sp-application-crypto", "sp-core", "sp-runtime", "sp-serializer", @@ -3543,7 +3545,6 @@ dependencies = [ "rustc-hex", "serde", "sp-api", - "sp-application-crypto", "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index 5fc6ce8f101bd..afb5ddd59045e 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -6,6 +6,8 @@ edition = "2018" license = "GPL-3.0" [dependencies] +codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false, features = ["derive"] } +sp-application-crypto = { version = "2.0.0", default-features = false, path = "../../../primitives/application-crypto" } sp-core = { version = "2.0.0", default-features = false, path = "../../../primitives/core" } sp-runtime = { version = "2.0.0", default-features = false, path = "../../../primitives/runtime" } @@ -16,6 +18,8 @@ pretty_assertions = "0.6.1" [features] default = ["std"] std = [ + "codec/std", + "sp-application-crypto/std", "sp-core/std", "sp-runtime/std", ] diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index 97e8f50c27145..f67ac7bc0a971 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -62,3 +62,32 @@ pub type Header = generic::Header; pub type Block = generic::Block; /// Block ID. pub type BlockId = generic::BlockId; + +/// App-specific crypto used for reporting equivocation/misbehavior in BABE and +/// GRANDPA. The crypto used is sr25519 and the account must be minimally funded +/// in order to pay for transaction fees. Any rewards for misbehavior reporting +/// will be paid out to this account. +pub mod report { + use sp_core::crypto::KeyTypeId; + + /// Key type for the reporting module. Used for reporting BABE and GRANDPA + /// equivocations. + pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"fish"); + + mod app { + use sp_application_crypto::{app_crypto, sr25519}; + + app_crypto!(sr25519, super::KEY_TYPE); + + impl sp_runtime::traits::IdentifyAccount for Public { + type AccountId = sp_runtime::AccountId32; + + fn into_account(self) -> Self::AccountId { + sp_runtime::MultiSigner::from(self.0).into_account() + } + } + } + + /// Identity of the equivocation/misbehavior reporter. + pub type ReporterId = app::Public; +} diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 589024dd79ba2..3f8e8b6731477 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -15,7 +15,6 @@ rustc-hex = { version = "2.0", optional = true } serde = { version = "1.0.102", optional = true } # primitives -sp-application-crypto = { version = "2.0.0", default-features = false, path = "../../../primitives/application-crypto" } sp-authority-discovery = { version = "2.0.0", default-features = false, path = "../../../primitives/authority-discovery" } sp-consensus-babe = { version = "0.8", default-features = false, path = "../../../primitives/consensus/babe" } sp-block-builder = { path = "../../../primitives/block-builder", default-features = false} @@ -77,7 +76,6 @@ sp-io = { version = "2.0.0", path = "../../../primitives/io" } [features] default = ["std"] std = [ - "sp-application-crypto/std", "sp-authority-discovery/std", "pallet-authority-discovery/std", "pallet-authorship/std", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index cff7d579500c3..a067326cb7dfe 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -470,36 +470,14 @@ impl pallet_offences::Trait for Runtime { impl pallet_authority_discovery::Trait for Runtime {} -// move to own crate -pub mod report { - pub mod app { - use sp_application_crypto::{app_crypto, sr25519, KeyTypeId}; - - pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"rprt"); - app_crypto!(sr25519, KEY_TYPE); - - impl sp_runtime::traits::IdentifyAccount for Public { - type AccountId = sp_runtime::AccountId32; - - fn into_account(self) -> Self::AccountId { - sp_runtime::MultiSigner::from(self.0).into_account() - } - } - } - - pub type ReporterId = app::Public; -} - -use report::ReporterId; - impl pallet_grandpa::Trait for Runtime { type Event = Event; type Call = Call; type HandleEquivocation = pallet_grandpa::EquivocationHandler< Historical, - TransactionSubmitter, + TransactionSubmitter, Offences, - ReporterId, + node_primitives::report::ReporterId, >; } diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index df2cd6889bcba..4d8148e4f30cd 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -108,7 +108,7 @@ impl ExecutionContext { match self { Importing | Syncing | BlockConstruction => offchain::Capabilities::none(), - // Enable keystore and transaction pool by default for offchain calls. CC @bkchr + // Enable keystore and transaction pool by default for offchain calls. OffchainCall(None) => [ offchain::Capability::Keystore, offchain::Capability::TransactionPool, From 1c3d8a04fb8a270afa95380a75e9faaa609d7109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 25 Feb 2020 23:29:31 +0000 Subject: [PATCH 35/78] grandpa: move equivocation traits to own module --- bin/node/primitives/src/lib.rs | 2 +- frame/grandpa/src/equivocation.rs | 242 +++++++++++++++++++++++++++ frame/grandpa/src/lib.rs | 266 +++--------------------------- 3 files changed, 268 insertions(+), 242 deletions(-) create mode 100644 frame/grandpa/src/equivocation.rs diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index f67ac7bc0a971..914b9e36956e6 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -88,6 +88,6 @@ pub mod report { } } - /// Identity of the equivocation/misbehavior reporter. + /// Identity of the equivocation/misbehavior reporter. pub type ReporterId = app::Public; } diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs new file mode 100644 index 0000000000000..df53864ac9e76 --- /dev/null +++ b/frame/grandpa/src/equivocation.rs @@ -0,0 +1,242 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! +//! An opt-in utility module for reporting equivocations. +//! +//! This module defines an offence type for GRANDPA equivocations +//! and some utility traits to wire together: +//! - a key ownership proof system (e.g. to prove that a given authority was +//! part of a session); +//! - a system for reporting offences; +//! - a system for signing and submitting transactions; +//! +//! These can be used in an offchain context in order to submit equivocation +//! reporting extrinsics (from the client that's running the GRANDPA protocol). +//! And in a runtime context, so that the GRANDPA module can validate the +//! equivocation proofs in the extrinsic and report the offences. +//! + +use sp_std::{fmt::Debug, prelude::*}; + +use app_crypto::{key_types::GRANDPA, RuntimeAppPublic}; +use codec::{self as codec, Decode, Encode}; +use frame_support::traits::KeyOwnerProofSystem; +use frame_system::offchain::SubmitSignedTransaction; +use sp_finality_grandpa::{EquivocationReport, RoundNumber, SetId}; +use sp_runtime::{traits::IdentifyAccount, DispatchResult, KeyTypeId, PerThing, Perbill}; +use sp_staking::{ + offence::{Kind, Offence, ReportOffence}, + SessionIndex, +}; + +pub trait HandleEquivocation { + type KeyOwnerProof: Clone + Debug + Decode + Encode + PartialEq; + type KeyOwnerIdentification; + type Offence: GrandpaOffence; + + fn check_proof( + equivocation_report: &EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; + + fn report_offence(reporters: Vec, offence: Self::Offence); + + fn submit_equivocation_report( + equivocation_report: EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> DispatchResult; +} + +impl HandleEquivocation for () { + type KeyOwnerProof = (); + type KeyOwnerIdentification = (); + type Offence = GrandpaEquivocationOffence; + + fn check_proof( + _equivocation_report: &EquivocationReport, + _key_owner_proof: Self::KeyOwnerProof, + ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { + None + } + + fn report_offence( + _reporters: Vec, + _offence: GrandpaEquivocationOffence, + ) { + } + + fn submit_equivocation_report( + _equivocation_report: EquivocationReport, + _key_owner_proof: Self::KeyOwnerProof, + ) -> DispatchResult { + Ok(()) + } +} + +pub struct EquivocationHandler< + P, + S, + R, + K, + O = GrandpaEquivocationOffence< +

)>>::IdentificationTuple, + >, +> { + _phantom: sp_std::marker::PhantomData<(P, S, O, R, K)>, +} + +impl Default for EquivocationHandler { + fn default() -> Self { + Self { + _phantom: Default::default(), + } + } +} + +impl HandleEquivocation for EquivocationHandler +where + T: super::Trait, + // A system for proving ownership of keys, i.e. that a given key was part + // of a validator set, needed for validating equivocation reports. The + // session index and validator count of the session are part of the proof + // as extra data. + P: KeyOwnerProofSystem<(KeyTypeId, Vec), ExtraData = (SessionIndex, u32)>, + // A transaction submitter. Used for submitting equivocation reports. + S: SubmitSignedTransaction::Call>, + O: GrandpaOffence, + R: ReportOffence, + // Key type to use when signing equivocation report transactions, must be + // convertible to and from an account id since that's what we need to use + // to sign transactions. + K: RuntimeAppPublic + IdentifyAccount, +{ + type KeyOwnerProof = P::Proof; + type KeyOwnerIdentification = P::IdentificationTuple; + type Offence = O; + + fn check_proof( + equivocation_report: &EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { + let (offender, (session_index, validator_set_count)) = P::check_proof( + (GRANDPA, equivocation_report.offender().encode()), + key_owner_proof, + )?; + + Some((offender, session_index, validator_set_count)) + } + + fn report_offence(reporters: Vec, offence: O) { + R::report_offence(reporters, offence); + } + + fn submit_equivocation_report( + equivocation_report: EquivocationReport, + key_owner_proof: Self::KeyOwnerProof, + ) -> DispatchResult { + let call = + super::Call::report_equivocation(equivocation_report.clone(), key_owner_proof.encode()); + + let res = S::submit_signed_from(call, K::all().into_iter().map(|k| k.into_account())); + + if res.iter().any(|(_, r)| r.is_ok()) { + Ok(()) + } else { + Err("Error submitting equivocation report.".into()) + } + } +} + +/// A round number and set id which point on the time of an offence. +#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] +pub struct GrandpaTimeSlot { + // The order of these matters for `derive(Ord)`. + set_id: SetId, + round: RoundNumber, +} + +/// A grandpa equivocation offence report. +#[allow(dead_code)] +pub struct GrandpaEquivocationOffence { + /// Time slot at which this incident happened. + time_slot: GrandpaTimeSlot, + /// The session index in which the incident happened. + session_index: SessionIndex, + /// The size of the validator set at the time of the offence. + validator_set_count: u32, + /// The authority which produced this equivocation. + offender: FullIdentification, +} + +pub trait GrandpaOffence: Offence { + fn new( + session_index: SessionIndex, + validator_set_count: u32, + offender: FullIdentification, + set_id: SetId, + round: RoundNumber, + ) -> Self; +} + +impl GrandpaOffence + for GrandpaEquivocationOffence +{ + fn new( + session_index: SessionIndex, + validator_set_count: u32, + offender: FullIdentification, + set_id: SetId, + round: RoundNumber, + ) -> Self { + GrandpaEquivocationOffence { + session_index, + validator_set_count, + offender, + time_slot: GrandpaTimeSlot { set_id, round }, + } + } +} + +impl Offence + for GrandpaEquivocationOffence +{ + const ID: Kind = *b"grandpa:equivoca"; + type TimeSlot = GrandpaTimeSlot; + + fn offenders(&self) -> Vec { + vec![self.offender.clone()] + } + + fn session_index(&self) -> SessionIndex { + self.session_index + } + + fn validator_set_count(&self) -> u32 { + self.validator_set_count + } + + fn time_slot(&self) -> Self::TimeSlot { + self.time_slot + } + + fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { + // the formula is min((3k / n)^2, 1) + let x = Perbill::from_rational_approximation(3 * offenders_count, validator_set_count); + // _ ^ 2 + x.square() + } +} diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index a5c81c029aed5..3ecdcd5f4001b 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -30,41 +30,31 @@ // re-export since this is necessary for `impl_apis` in runtime. pub use sp_finality_grandpa as fg_primitives; -use sp_std::{ - fmt::Debug, - prelude::*, -}; +use sp_std::prelude::*; -use app_crypto::{key_types::GRANDPA, RuntimeAppPublic}; -use codec::{self as codec, Encode, Decode}; +use codec::{self as codec, Decode, Encode}; +pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; +use fg_primitives::{ + ConsensusLog, EquivocationReport, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, + GRANDPA_ENGINE_ID, +}; use frame_support::{ - decl_event, decl_storage, decl_module, decl_error, storage, - traits::KeyOwnerProofSystem, - weights::SimpleDispatchInfo, + decl_error, decl_event, decl_module, decl_storage, storage, weights::SimpleDispatchInfo, }; +use frame_system::{self as system, ensure_signed, DigestOf}; use sp_runtime::{ generic::{DigestItem, OpaqueDigestItemId}, - traits::{IdentifyAccount, Zero}, - DispatchResult, KeyTypeId, Perbill, PerThing, -}; -use sp_staking::{ - SessionIndex, - offence::{Kind, Offence, ReportOffence}, -}; -use fg_primitives::{ - GRANDPA_AUTHORITIES_KEY, GRANDPA_ENGINE_ID, - ConsensusLog, EquivocationReport, RoundNumber, SetId, ScheduledChange, -}; -pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; -use frame_system::{ - self as system, ensure_signed, - DigestOf, - offchain::SubmitSignedTransaction, + traits::Zero, + DispatchResult, }; +use sp_staking::SessionIndex; +mod equivocation; mod mock; mod tests; +pub use equivocation::EquivocationHandler; + pub trait Trait: frame_system::Trait { /// The event type of this module. type Event: From + Into<::Event>; @@ -72,132 +62,7 @@ pub trait Trait: frame_system::Trait { /// The function call. type Call: From>; - type HandleEquivocation: HandleEquivocation; -} - -pub trait HandleEquivocation { - type KeyOwnerProof: Clone + Debug + Decode + Encode + PartialEq; - type KeyOwnerIdentification; - type Offence: GrandpaOffence; - - fn check_proof( - equivocation_report: &EquivocationReport, - key_owner_proof: Self::KeyOwnerProof, - ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; - - fn report_offence(reporters: Vec, offence: Self::Offence); - - fn submit_equivocation_report( - equivocation_report: EquivocationReport, - key_owner_proof: Self::KeyOwnerProof, - ) -> DispatchResult; -} - -impl HandleEquivocation for () { - type KeyOwnerProof = (); - type KeyOwnerIdentification = (); - type Offence = GrandpaEquivocationOffence; - - fn check_proof( - _equivocation_report: &EquivocationReport, - _key_owner_proof: Self::KeyOwnerProof, - ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { - None - } - - fn report_offence( - _reporters: Vec, - _offence: GrandpaEquivocationOffence, - ) { - - } - - fn submit_equivocation_report( - _equivocation_report: EquivocationReport, - _key_owner_proof: Self::KeyOwnerProof, - ) -> DispatchResult { - Ok(()) - } -} - -pub struct EquivocationHandler< - P, - S, - R, - K, - O = GrandpaEquivocationOffence< -

)>>::IdentificationTuple, - >, -> { - _phantom: sp_std::marker::PhantomData<(P, S, O, R, K)>, -} - -impl Default for EquivocationHandler { - fn default() -> Self { - Self { - _phantom: Default::default(), - } - } -} - -impl HandleEquivocation for EquivocationHandler -where - T: Trait, - // A system for proving ownership of keys, i.e. that a given key was part - // of a validator set, needed for validating equivocation reports. The - // session index and validator count of the session are part of the proof - // as extra data. - P: KeyOwnerProofSystem<(KeyTypeId, Vec), ExtraData = (SessionIndex, u32)>, - // A transaction submitter. Used for submitting equivocation reports. - S: SubmitSignedTransaction::Call>, - O: GrandpaOffence, - R: ReportOffence, - // Key type to use when signing equivocation report transactions, must be - // convertible to and from an account id since that's what we need to use - // to sign transactions. - K: RuntimeAppPublic + IdentifyAccount, -{ - type KeyOwnerProof = P::Proof; - type KeyOwnerIdentification = P::IdentificationTuple; - type Offence = O; - - fn check_proof( - equivocation_report: &EquivocationReport, - key_owner_proof: Self::KeyOwnerProof, - ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { - let (offender, (session_index, validator_set_count)) = - P::check_proof( - (GRANDPA, equivocation_report.offender().encode()), - key_owner_proof, - )?; - - Some((offender, session_index, validator_set_count)) - } - - fn report_offence(reporters: Vec, offence: O) { - R::report_offence(reporters, offence); - } - - fn submit_equivocation_report( - equivocation_report: EquivocationReport, - key_owner_proof: Self::KeyOwnerProof, - ) -> DispatchResult { - let call = Call::report_equivocation( - equivocation_report.clone(), - key_owner_proof.encode(), - ); - - let res = S::submit_signed_from( - call, - K::all().into_iter().map(|k| k.into_account()), - ); - - if res.iter().any(|(_, r)| r.is_ok()) { - Ok(()) - } else { - Err("Error submitting equivocation report.".into()) - } - } + type HandleEquivocation: equivocation::HandleEquivocation; } /// A stored pending change, old format. @@ -350,9 +215,11 @@ decl_module! { fn report_equivocation( origin, equivocation_report: EquivocationReport, - // key_owner_proof: >::KeyOwnerProof, + // key_owner_proof: >::KeyOwnerProof, key_owner_proof: Vec, ) { + use equivocation::{GrandpaOffence, HandleEquivocation}; + let reporter_id = ensure_signed(origin)?; // FIXME: this is a hack needed because the typed argument version above fails to @@ -404,7 +271,7 @@ decl_module! { // report to the offences module rewarding the sender. T::HandleEquivocation::report_offence( vec![reporter_id], - >::Offence::new( + >::Offence::new( session_index, validator_set_count, offender, @@ -599,12 +466,12 @@ impl Module { pub fn submit_report_equivocation_extrinsic( equivocation_report: EquivocationReport, - key_owner_proof: >::KeyOwnerProof, + key_owner_proof: >::KeyOwnerProof, ) -> Option<()> { - T::HandleEquivocation::submit_equivocation_report( - equivocation_report, - key_owner_proof, - ).ok()?; + use equivocation::HandleEquivocation; + + T::HandleEquivocation::submit_equivocation_report(equivocation_report, key_owner_proof) + .ok()?; Some(()) } @@ -701,86 +568,3 @@ impl pallet_finality_tracker::OnFinalizationStalled fo >::put((further_wait, median)); } } - -/// A round number and set id which point on the time of an offence. -#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] -pub struct GrandpaTimeSlot { - // The order of these matters for `derive(Ord)`. - set_id: SetId, - round: RoundNumber, -} - -/// A grandpa equivocation offence report. -#[allow(dead_code)] -pub struct GrandpaEquivocationOffence { - /// Time slot at which this incident happened. - time_slot: GrandpaTimeSlot, - /// The session index in which the incident happened. - session_index: SessionIndex, - /// The size of the validator set at the time of the offence. - validator_set_count: u32, - /// The authority which produced this equivocation. - offender: FullIdentification, -} - -pub trait GrandpaOffence: Offence { - fn new( - session_index: SessionIndex, - validator_set_count: u32, - offender: FullIdentification, - set_id: SetId, - round: RoundNumber, - ) -> Self; -} - -impl GrandpaOffence - for GrandpaEquivocationOffence -{ - fn new( - session_index: SessionIndex, - validator_set_count: u32, - offender: FullIdentification, - set_id: SetId, - round: RoundNumber, - ) -> Self { - GrandpaEquivocationOffence { - session_index, - validator_set_count, - offender, - time_slot: GrandpaTimeSlot { set_id, round }, - } - } -} - -impl Offence - for GrandpaEquivocationOffence -{ - const ID: Kind = *b"grandpa:equivoca"; - type TimeSlot = GrandpaTimeSlot; - - fn offenders(&self) -> Vec { - vec![self.offender.clone()] - } - - fn session_index(&self) -> SessionIndex { - self.session_index - } - - fn validator_set_count(&self) -> u32 { - self.validator_set_count - } - - fn time_slot(&self) -> Self::TimeSlot { - self.time_slot - } - - fn slash_fraction( - offenders_count: u32, - validator_set_count: u32, - ) -> Perbill { - // the formula is min((3k / n)^2, 1) - let x = Perbill::from_rational_approximation(3 * offenders_count, validator_set_count); - // _ ^ 2 - x.square() - } -} From 45d6d9d059c8f1b872eee2666570f830c629b656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 25 Feb 2020 23:47:12 +0000 Subject: [PATCH 36/78] grandpa: rename app-crypto crate import --- frame/grandpa/Cargo.toml | 4 ++-- frame/grandpa/src/equivocation.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index a5b38cb175520..089791f631ca9 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -6,9 +6,9 @@ edition = "2018" license = "GPL-3.0" [dependencies] -app-crypto = { package = "sp-application-crypto", path = "../../primitives/application-crypto", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } +sp-application-crypto = { version = "2.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-core = { version = "2.0.0", default-features = false, path = "../../primitives/core" } sp-finality-grandpa = { version = "2.0.0", default-features = false, path = "../../primitives/finality-grandpa" } sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" } @@ -25,9 +25,9 @@ sp-io ={ version = "2.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "app-crypto/std", "serde", "codec/std", + "sp-application-crypto/std", "sp-core/std", "sp-finality-grandpa/std", "sp-std/std", diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index df53864ac9e76..62a7b7e3a0280 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -32,10 +32,10 @@ use sp_std::{fmt::Debug, prelude::*}; -use app_crypto::{key_types::GRANDPA, RuntimeAppPublic}; use codec::{self as codec, Decode, Encode}; use frame_support::traits::KeyOwnerProofSystem; use frame_system::offchain::SubmitSignedTransaction; +use sp_application_crypto::{key_types::GRANDPA, RuntimeAppPublic}; use sp_finality_grandpa::{EquivocationReport, RoundNumber, SetId}; use sp_runtime::{traits::IdentifyAccount, DispatchResult, KeyTypeId, PerThing, Perbill}; use sp_staking::{ From 34a89c2cf8ea96763934557d0b8f297e7aa8631a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 01:54:08 +0000 Subject: [PATCH 37/78] grandpa: export equivocation types --- frame/grandpa/src/equivocation.rs | 12 ++++++------ frame/grandpa/src/lib.rs | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 62a7b7e3a0280..be7274f495df0 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -165,21 +165,21 @@ where #[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] pub struct GrandpaTimeSlot { // The order of these matters for `derive(Ord)`. - set_id: SetId, - round: RoundNumber, + pub set_id: SetId, + pub round: RoundNumber, } /// A grandpa equivocation offence report. #[allow(dead_code)] pub struct GrandpaEquivocationOffence { /// Time slot at which this incident happened. - time_slot: GrandpaTimeSlot, + pub time_slot: GrandpaTimeSlot, /// The session index in which the incident happened. - session_index: SessionIndex, + pub session_index: SessionIndex, /// The size of the validator set at the time of the offence. - validator_set_count: u32, + pub validator_set_count: u32, /// The authority which produced this equivocation. - offender: FullIdentification, + pub offender: FullIdentification, } pub trait GrandpaOffence: Offence { diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index fc488878398b7..b04c2f130a75f 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -53,7 +53,9 @@ mod equivocation; mod mock; mod tests; -pub use equivocation::EquivocationHandler; +pub use equivocation::{ + EquivocationHandler, GrandpaEquivocationOffence, GrandpaTimeSlot, GrandpaOffence +}; pub trait Trait: frame_system::Trait { /// The event type of this module. From bcc04970e157db7fe5dbf2ad93fde5bea4a6cd2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 01:58:27 +0000 Subject: [PATCH 38/78] node: bump spec_version --- bin/node/runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 93553ea2092d9..29b81777486a0 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -82,8 +82,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 225, - impl_version: 1, + spec_version: 226, + impl_version: 0, apis: RUNTIME_API_VERSIONS, }; From 2c8e808e4821b0fd5ef5414dc54a24a129a91e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 15:05:27 +0000 Subject: [PATCH 39/78] grandpa: rename EquivocationReport to EquivocationProof --- frame/grandpa/src/equivocation.rs | 18 +++++++++--------- frame/grandpa/src/lib.rs | 16 ++++++++-------- primitives/finality-grandpa/src/lib.rs | 16 +++++++--------- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index be7274f495df0..4be652a31fd1a 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -36,7 +36,7 @@ use codec::{self as codec, Decode, Encode}; use frame_support::traits::KeyOwnerProofSystem; use frame_system::offchain::SubmitSignedTransaction; use sp_application_crypto::{key_types::GRANDPA, RuntimeAppPublic}; -use sp_finality_grandpa::{EquivocationReport, RoundNumber, SetId}; +use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; use sp_runtime::{traits::IdentifyAccount, DispatchResult, KeyTypeId, PerThing, Perbill}; use sp_staking::{ offence::{Kind, Offence, ReportOffence}, @@ -49,14 +49,14 @@ pub trait HandleEquivocation { type Offence: GrandpaOffence; fn check_proof( - equivocation_report: &EquivocationReport, + equivocation_proof: &EquivocationProof, key_owner_proof: Self::KeyOwnerProof, ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; fn report_offence(reporters: Vec, offence: Self::Offence); fn submit_equivocation_report( - equivocation_report: EquivocationReport, + equivocation_proof: EquivocationProof, key_owner_proof: Self::KeyOwnerProof, ) -> DispatchResult; } @@ -67,7 +67,7 @@ impl HandleEquivocation for () { type Offence = GrandpaEquivocationOffence; fn check_proof( - _equivocation_report: &EquivocationReport, + _equivocation_proof: &EquivocationProof, _key_owner_proof: Self::KeyOwnerProof, ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { None @@ -80,7 +80,7 @@ impl HandleEquivocation for () { } fn submit_equivocation_report( - _equivocation_report: EquivocationReport, + _equivocation_proof: EquivocationProof, _key_owner_proof: Self::KeyOwnerProof, ) -> DispatchResult { Ok(()) @@ -129,11 +129,11 @@ where type Offence = O; fn check_proof( - equivocation_report: &EquivocationReport, + equivocation_proof: &EquivocationProof, key_owner_proof: Self::KeyOwnerProof, ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { let (offender, (session_index, validator_set_count)) = P::check_proof( - (GRANDPA, equivocation_report.offender().encode()), + (GRANDPA, equivocation_proof.offender().encode()), key_owner_proof, )?; @@ -145,11 +145,11 @@ where } fn submit_equivocation_report( - equivocation_report: EquivocationReport, + equivocation_proof: EquivocationProof, key_owner_proof: Self::KeyOwnerProof, ) -> DispatchResult { let call = - super::Call::report_equivocation(equivocation_report.clone(), key_owner_proof.encode()); + super::Call::report_equivocation(equivocation_proof.clone(), key_owner_proof.encode()); let res = S::submit_signed_from(call, K::all().into_iter().map(|k| k.into_account())); diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index b04c2f130a75f..23ab2781f9cae 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -35,7 +35,7 @@ use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; use fg_primitives::{ - ConsensusLog, EquivocationReport, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, + ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, GRANDPA_ENGINE_ID, }; use frame_support::{ @@ -216,7 +216,7 @@ decl_module! { #[weight = SimpleDispatchInfo::FixedOperational(10_000_000)] fn report_equivocation( origin, - equivocation_report: EquivocationReport, + equivocation_proof: EquivocationProof, // key_owner_proof: >::KeyOwnerProof, key_owner_proof: Vec, ) { @@ -233,18 +233,18 @@ decl_module! { // validator set count of the session that we're proving membership of let (offender, session_index, validator_set_count) = T::HandleEquivocation::check_proof( - &equivocation_report, + &equivocation_proof, key_owner_proof, ).ok_or("Invalid/outdated key ownership proof.")?; // validate equivocation proof (check votes are different and // signatures are valid). - fg_primitives::check_equivocation_report(&equivocation_report) + fg_primitives::check_equivocation_proof(&equivocation_proof) .map_err(|_| "Invalid equivocation proof.")?; // we check the equivocation within the context of its set id (and // associated session). - let set_id = equivocation_report.set_id(); + let set_id = equivocation_proof.set_id(); // fetch the current and previous sets last session index. on the // genesis set there's no previous set. @@ -278,7 +278,7 @@ decl_module! { validator_set_count, offender, set_id, - equivocation_report.round(), + equivocation_proof.round(), ), ); } @@ -467,12 +467,12 @@ impl Module { } pub fn submit_report_equivocation_extrinsic( - equivocation_report: EquivocationReport, + equivocation_proof: EquivocationProof, key_owner_proof: >::KeyOwnerProof, ) -> Option<()> { use equivocation::HandleEquivocation; - T::HandleEquivocation::submit_equivocation_report(equivocation_report, key_owner_proof) + T::HandleEquivocation::submit_equivocation_report(equivocation_proof, key_owner_proof) .ok()?; Some(()) diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 74fd69584dc34..b0b4a8346263a 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -32,7 +32,7 @@ use sp_std::vec::Vec; #[cfg(feature = "std")] use log::debug; -pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::key_types::GRANDPA; +pub use sp_application_crypto::key_types::GRANDPA; mod app { use sp_application_crypto::{app_crypto, key_types::GRANDPA, ed25519}; @@ -163,16 +163,15 @@ impl ConsensusLog { } } -// FIXME: rename to equivocation proof ? #[derive(Clone, Debug, Decode, Encode, PartialEq)] -pub struct EquivocationReport { +pub struct EquivocationProof { set_id: SetId, equivocation: Equivocation, } -impl EquivocationReport { +impl EquivocationProof { pub fn new(set_id: SetId, equivocation: Equivocation) -> Self { - EquivocationReport { + EquivocationProof { set_id, equivocation, } @@ -249,9 +248,8 @@ impl Equivocation { } } -pub fn check_equivocation_report( - report: &EquivocationReport, -) -> Result<(), ()> where +pub fn check_equivocation_proof(report: &EquivocationProof) -> Result<(), ()> +where H: Clone + Encode + PartialEq, N: Clone + Encode + PartialEq, { @@ -479,7 +477,7 @@ sp_api::decl_runtime_apis! { #[skip_initialize_block] fn submit_report_equivocation_extrinsic( - equivocation_report: EquivocationReport>, + equivocation_proof: EquivocationProof>, key_owner_proof: Vec, ) -> Option<()>; } From bea38e722fd6c91d85829e148db9e56a54c2fc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 15:42:21 +0000 Subject: [PATCH 40/78] grandpa: add missing docs to primitives --- primitives/finality-grandpa/src/lib.rs | 69 ++++++++++++++------------ 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index b0b4a8346263a..824bea0a7ad13 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -163,6 +163,10 @@ impl ConsensusLog { } } +/// 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 { set_id: SetId, @@ -170,6 +174,8 @@ pub struct EquivocationProof { } impl EquivocationProof { + /// Create a new `EquivocationProof` for the given set id and using the + /// given equivocation as proof. pub fn new(set_id: SetId, equivocation: Equivocation) -> Self { EquivocationProof { set_id, @@ -177,10 +183,12 @@ impl EquivocationProof { } } + /// 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, @@ -188,27 +196,20 @@ impl EquivocationProof { } } + /// 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 { - Prevote( - grandpa::Equivocation< - AuthorityId, - grandpa::Prevote, - AuthoritySignature, - >, - ), - Precommit( - grandpa::Equivocation< - AuthorityId, - grandpa::Precommit, - AuthoritySignature, - >, - ), + /// Proof of equivocation at prevote stage. + Prevote(grandpa::Equivocation, AuthoritySignature>), + /// Proof of equivocation at precommit stage. + Precommit(grandpa::Equivocation, AuthoritySignature>), } impl From, AuthoritySignature>> @@ -240,6 +241,7 @@ impl From, Aut } impl Equivocation { + /// Returns the authority id of the equivocator. pub fn offender(&self) -> &AuthorityId { match self { Equivocation::Prevote(ref equivocation) => &equivocation.identity, @@ -248,6 +250,8 @@ impl Equivocation { } } +/// Verifies the equivocation proof by making sure that both votes target +/// different blocks and that its signatures are valid. pub fn check_equivocation_proof(report: &EquivocationProof) -> Result<(), ()> where H: Clone + Encode + PartialEq, @@ -281,25 +285,21 @@ where )?; return Ok(()); - } + }; } match report.equivocation { Equivocation::Prevote(ref equivocation) => { check!(equivocation, grandpa::Message::Prevote); - }, + } Equivocation::Precommit(ref equivocation) => { check!(equivocation, grandpa::Message::Precommit); - }, + } } } /// Encode round message localized to a given round and set id. -pub fn localized_payload( - round: RoundNumber, - set_id: SetId, - message: &E, -) -> Vec { +pub fn localized_payload(round: RoundNumber, set_id: SetId, message: &E) -> Vec { let mut buf = Vec::new(); localized_payload_with_buffer(round, set_id, message, &mut buf); buf @@ -326,18 +326,12 @@ pub fn check_message_signature( signature: &AuthoritySignature, round: RoundNumber, set_id: SetId, -) -> Result<(), ()> where +) -> Result<(), ()> +where H: Encode, N: Encode, { - check_message_signature_with_buffer( - message, - id, - signature, - round, - set_id, - &mut Vec::new(), - ) + 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 @@ -351,7 +345,8 @@ pub fn check_message_signature_with_buffer( round: RoundNumber, set_id: SetId, buf: &mut Vec, -) -> Result<(), ()> where +) -> Result<(), ()> +where H: Encode, N: Encode, { @@ -379,13 +374,15 @@ pub fn check_message_signature_with_buffer( } } +/// Localizes the message to the given set and round and signs the payload. #[cfg(feature = "std")] pub fn sign_message( message: grandpa::Message, pair: &AuthorityPair, round: RoundNumber, set_id: SetId, -) -> grandpa::SignedMessage where +) -> grandpa::SignedMessage +where H: Encode, N: Encode, { @@ -475,6 +472,12 @@ sp_api::decl_runtime_apis! { /// 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 an encoded key ownership proof + /// (the key ownership proof is generic). 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. #[skip_initialize_block] fn submit_report_equivocation_extrinsic( equivocation_proof: EquivocationProof>, From c18278140f7fd2b61fa79c1a7d0569457edd4931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 16:02:24 +0000 Subject: [PATCH 41/78] grandpa: add missing docs to equivocation --- frame/grandpa/src/equivocation.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 4be652a31fd1a..3e839488a25c9 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -43,18 +43,31 @@ use sp_staking::{ SessionIndex, }; +/// A trait with utility methods for handling equivocation reports in GRANDPA. +/// The key owner proof and offence types are generic, and the trait provides +/// methods to check an equivocation proof (i.e. the equivocation itself and the +/// key ownership proof), reporting an offence triggered by a valid equivocation +/// report, and also for creating and submitting equivocation report extrinsics +/// (useful only in offchain context). pub trait HandleEquivocation { + /// The proof of key ownership. type KeyOwnerProof: Clone + Debug + Decode + Encode + PartialEq; + /// The identification of a key owner. type KeyOwnerIdentification; + /// The offence type used for reporting offences on valid equivocation reports. type Offence: GrandpaOffence; + /// Verifies the key ownership proof and the equivocation proof against the + /// offending key owner. fn check_proof( equivocation_proof: &EquivocationProof, key_owner_proof: Self::KeyOwnerProof, ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; + /// Report an offence proved by the given reporters. fn report_offence(reporters: Vec, offence: Self::Offence); + /// Create and dispatch an equivocation report extrinsic. fn submit_equivocation_report( equivocation_proof: EquivocationProof, key_owner_proof: Self::KeyOwnerProof, @@ -87,6 +100,10 @@ impl HandleEquivocation for () { } } +/// Generic equivocation handler. This type implements `HandleEquivocation` +/// using existing subsystems that are part of frame (type bounds described +/// below) and will dispatch to them directly, it's only purpose is to wire all +/// subsystems together. pub struct EquivocationHandler< P, S, @@ -117,7 +134,10 @@ where P: KeyOwnerProofSystem<(KeyTypeId, Vec), ExtraData = (SessionIndex, u32)>, // A transaction submitter. Used for submitting equivocation reports. S: SubmitSignedTransaction::Call>, + // The offence type that should be used when reporting. O: GrandpaOffence, + // A system for reporting offences after valid equivocation reports are + // processed. R: ReportOffence, // Key type to use when signing equivocation report transactions, must be // convertible to and from an account id since that's what we need to use @@ -182,7 +202,11 @@ pub struct GrandpaEquivocationOffence { pub offender: FullIdentification, } +/// An interface for types that will be used as GRANDPA offences and must also +/// implement the `Offence` trait. This trait provides a constructor that is +/// provided all available data during processing of GRANDPA equivocations. pub trait GrandpaOffence: Offence { + /// Create a new GRANDPA offence using the given equivocation details. fn new( session_index: SessionIndex, validator_set_count: u32, From 0ee7249d44a65b84aa0b1ad33b72e45cf9adf09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 16:08:25 +0000 Subject: [PATCH 42/78] node: fix compilation --- bin/node-template/runtime/src/lib.rs | 2 +- bin/node/runtime/src/lib.rs | 4 ++-- client/finality-grandpa/src/environment.rs | 6 +++--- client/finality-grandpa/src/tests.rs | 2 +- primitives/finality-grandpa/src/lib.rs | 3 ++- test-utils/runtime/src/lib.rs | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 9a6312ee73fa5..2dd0acd630278 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -371,7 +371,7 @@ impl_runtime_apis! { } fn submit_report_equivocation_extrinsic( - _equivocation_report: fg_primitives::EquivocationReport< + _equivocation_proof: fg_primitives::EquivocationProof< ::Hash, NumberFor, >, diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 29b81777486a0..d4c79bbb540dc 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -733,7 +733,7 @@ impl_runtime_apis! { } fn submit_report_equivocation_extrinsic( - equivocation_report: fg_primitives::EquivocationReport< + equivocation_proof: fg_primitives::EquivocationProof< ::Hash, NumberFor, >, @@ -742,7 +742,7 @@ impl_runtime_apis! { let key_owner_proof = codec::Decode::decode(&mut &key_owner_proof[..]).ok()?; Grandpa::submit_report_equivocation_extrinsic( - equivocation_report, + equivocation_proof, key_owner_proof, ) } diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 9094a5ce247ed..76fbfd25bc98c 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -64,7 +64,7 @@ use crate::justification::GrandpaJustification; use crate::until_imported::UntilVoteTargetImported; use crate::voting_rule::VotingRule; use sp_finality_grandpa::{ - AuthorityId, AuthoritySignature, Equivocation, EquivocationReport, + AuthorityId, AuthoritySignature, Equivocation, EquivocationProof, GrandpaApi, RoundNumber, SetId, }; @@ -1026,7 +1026,7 @@ where }; // submit equivocation report at **best** block - let equivocation_report = EquivocationReport::new( + let equivocation_proof = EquivocationProof::new( authority_set.set_id, equivocation, ); @@ -1034,7 +1034,7 @@ where api.runtime_api() .submit_report_equivocation_extrinsic( &BlockId::Hash(best_header.hash()), - equivocation_report, + equivocation_proof, membership_proof.encode(), ).map_err(Error::Client)?; diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index f71532cb91c46..8ad9c7560b708 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -319,7 +319,7 @@ impl GrandpaApi for RuntimeApi { _: &BlockId, _: ExecutionContext, _: Option<( - sp_finality_grandpa::EquivocationReport<::Hash, NumberFor>, + sp_finality_grandpa::EquivocationProof<::Hash, NumberFor>, Vec, )>, _: Vec, diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 824bea0a7ad13..56df3d3f0abd5 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -32,7 +32,8 @@ use sp_std::vec::Vec; #[cfg(feature = "std")] use log::debug; -pub use sp_application_crypto::key_types::GRANDPA; +/// 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}; diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 6dff2f1226814..e7f020526124c 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -651,7 +651,7 @@ cfg_if! { } fn submit_report_equivocation_extrinsic( - _equivocation_report: sp_finality_grandpa::EquivocationReport< + _equivocation_proof: sp_finality_grandpa::EquivocationProof< ::Hash, NumberFor, >, From 237f516c59e4fa0fe665957511d9668129d35f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 16:42:34 +0000 Subject: [PATCH 43/78] grandpa: add missing docs to pallet --- frame/grandpa/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 23ab2781f9cae..9f3d0b2f2844e 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -64,6 +64,8 @@ pub trait Trait: frame_system::Trait { /// The function call. type Call: From>; + /// The equivocation handling subsystem, equivocation report validation and + /// offence reporting will be defined based on this type. type HandleEquivocation: equivocation::HandleEquivocation; } @@ -209,7 +211,10 @@ decl_module! { fn deposit_event() = default; - /// Report some misbehavior. + /// Report voter equivocation/misbehavior. This method will verify the + /// equivocation proof and validate the given key ownership proof + /// against the extracted offender. If both are valid, the offence + /// will be reported. /// /// FIXME: I have no clue about the weight (but we're checking two /// ed25519 signatures). @@ -466,7 +471,11 @@ impl Module { } } - pub fn submit_report_equivocation_extrinsic( + /// Submits an extrinsic to report an equivocation. This method will sign an + /// extrinsic with a call to `report_equivocation` with any reporting keys + /// available in the keystore and will push the transaction to the pool. + /// Only useful in an offchain context. + pub fn submit_report_equivocation_extrinsic( equivocation_proof: EquivocationProof, key_owner_proof: >::KeyOwnerProof, ) -> Option<()> { From a99bea6760a91750fbfc2978326dad5d92361f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 26 Feb 2020 16:44:32 +0000 Subject: [PATCH 44/78] node: bump spec_version --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d4c79bbb540dc..12690b58f381f 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -82,7 +82,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 226, + spec_version: 227, impl_version: 0, apis: RUNTIME_API_VERSIONS, }; From 443856bb8a87386a3188be265a79701e5bcbcf73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 2 Mar 2020 15:21:02 +0000 Subject: [PATCH 45/78] fix whitespace --- bin/node/runtime/src/lib.rs | 2 +- client/finality-grandpa/src/authorities.rs | 18 +++++++++--------- client/finality-grandpa/src/environment.rs | 6 +++--- frame/grandpa/src/lib.rs | 16 ++++++++-------- primitives/finality-grandpa/src/lib.rs | 12 ++++++------ 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 12690b58f381f..47e13e29b20f2 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -630,7 +630,7 @@ construct_runtime!( ImOnline: pallet_im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, AuthorityDiscovery: pallet_authority_discovery::{Module, Call, Config}, Offences: pallet_offences::{Module, Call, Storage, Event}, - Historical: pallet_session_historical::{Module}, + Historical: pallet_session_historical::{Module}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage}, Identity: pallet_identity::{Module, Call, Storage, Event}, Society: pallet_society::{Module, Call, Storage, Event, Config}, diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 102f448a2bb0e..4639b9658916d 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -892,8 +892,8 @@ mod tests { let new_set = Vec::new(); - // We have three pending changes with 2 possible roots that are enacted - // immediately on finality (i.e. standard changes). + // We have three pending changes with 2 possible roots that are enacted + // immediately on finality (i.e. standard changes). let change_a0 = PendingChange { next_authorities: new_set.clone(), delay: 0, @@ -918,8 +918,8 @@ mod tests { delay_kind: DelayKind::Finalized, }; - // A0 (#5) <- A10 (#8) <- A1 (#10) <- best_a - // B (#4) <- best_b + // A0 (#5) <- A10 (#8) <- A1 (#10) <- best_a + // B (#4) <- best_b let is_descendent_of = is_descendent_of(|base, hash| match (*base, *hash) { ("hash_a0", "hash_a1") => true, ("hash_a0", "best_a") => true, @@ -929,7 +929,7 @@ mod tests { _ => false, }); - // add the three pending changes + // add the three pending changes authorities .add_pending_change(change_b, &is_descendent_of) .unwrap(); @@ -940,7 +940,7 @@ mod tests { .add_pending_change(change_a1, &is_descendent_of) .unwrap(); - // the earliest change at block `best_a` should be the change at A0 (#5) + // the earliest change at block `best_a` should be the change at A0 (#5) assert_eq!( authorities .next_change(&"best_a", &is_descendent_of) @@ -961,7 +961,7 @@ mod tests { .apply_standard_changes("hash_a0", 5, &is_descendent_of) .unwrap(); - // the next change is now at A1 (#10) + // the next change is now at A1 (#10) assert_eq!( authorities .next_change(&"best_a", &is_descendent_of) @@ -977,7 +977,7 @@ mod tests { None, ); - // we a forced change at A10 (#8) + // we a forced change at A10 (#8) let change_a10 = PendingChange { next_authorities: new_set.clone(), delay: 0, @@ -992,7 +992,7 @@ mod tests { .add_pending_change(change_a10, &static_is_descendent_of(false)) .unwrap(); - // it should take precedence over the change at A1 (#10) + // it should take precedence over the change at A1 (#10) assert_eq!( authorities .next_change(&"best_a", &is_descendent_of) diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 76fbfd25bc98c..00c130f8baa7c 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -419,7 +419,7 @@ where Block: 'static, B: Backend + 'static, E: CallExecutor + Send + Sync, - N: NetworkT + 'static + Send, + N: NetworkT + 'static + Send, SC: SelectChain + 'static, VR: VotingRule>, RA: Send + Sync, @@ -992,13 +992,13 @@ where // at best block. Some((_, n)) if n > *best_header.number() => best_header.hash(), Some((h, _)) => { - // this is the header at which the new set will start + // this is the header at which the new set will start let header = backend.header(BlockId::Hash(h))?.expect( "got block hash from registered pending change; \ pending changes are only registered on block import; qed.", ); - // its parent block is the last block in the current set + // its parent block is the last block in the current set *header.parent_hash() } // there is no pending change, the latest block for the current set is diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 9f3d0b2f2844e..b9475ec6d4f5f 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -54,7 +54,7 @@ mod mock; mod tests; pub use equivocation::{ - EquivocationHandler, GrandpaEquivocationOffence, GrandpaTimeSlot, GrandpaOffence + EquivocationHandler, GrandpaEquivocationOffence, GrandpaTimeSlot, GrandpaOffence }; pub trait Trait: frame_system::Trait { @@ -64,8 +64,8 @@ pub trait Trait: frame_system::Trait { /// The function call. type Call: From>; - /// The equivocation handling subsystem, equivocation report validation and - /// offence reporting will be defined based on this type. + /// The equivocation handling subsystem, equivocation report validation and + /// offence reporting will be defined based on this type. type HandleEquivocation: equivocation::HandleEquivocation; } @@ -471,11 +471,11 @@ impl Module { } } - /// Submits an extrinsic to report an equivocation. This method will sign an - /// extrinsic with a call to `report_equivocation` with any reporting keys - /// available in the keystore and will push the transaction to the pool. - /// Only useful in an offchain context. - pub fn submit_report_equivocation_extrinsic( + /// Submits an extrinsic to report an equivocation. This method will sign an + /// extrinsic with a call to `report_equivocation` with any reporting keys + /// available in the keystore and will push the transaction to the pool. + /// Only useful in an offchain context. + pub fn submit_report_equivocation_extrinsic( equivocation_proof: EquivocationProof, key_owner_proof: >::KeyOwnerProof, ) -> Option<()> { diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 56df3d3f0abd5..24605d0cd8bbd 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -473,12 +473,12 @@ sp_api::decl_runtime_apis! { /// 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 an encoded key ownership proof - /// (the key ownership proof is generic). 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. + /// Submits an extrinsic to report an equivocation. The caller must + /// provide the equivocation proof and an encoded key ownership proof + /// (the key ownership proof is generic). 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. #[skip_initialize_block] fn submit_report_equivocation_extrinsic( equivocation_proof: EquivocationProof>, From 637273679be263a66d3990e4b4fb9357d2fe73ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Fri, 13 Mar 2020 18:27:23 +0000 Subject: [PATCH 46/78] grandpa: return error on offence reporting --- frame/grandpa/src/equivocation.rs | 14 +++++++++----- frame/grandpa/src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 3e839488a25c9..d2bcfabfb18b6 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -39,7 +39,7 @@ use sp_application_crypto::{key_types::GRANDPA, RuntimeAppPublic}; use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; use sp_runtime::{traits::IdentifyAccount, DispatchResult, KeyTypeId, PerThing, Perbill}; use sp_staking::{ - offence::{Kind, Offence, ReportOffence}, + offence::{Kind, Offence, OffenceError, ReportOffence}, SessionIndex, }; @@ -65,7 +65,10 @@ pub trait HandleEquivocation { ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; /// Report an offence proved by the given reporters. - fn report_offence(reporters: Vec, offence: Self::Offence); + fn report_offence( + reporters: Vec, + offence: Self::Offence, + ) -> Result<(), OffenceError>; /// Create and dispatch an equivocation report extrinsic. fn submit_equivocation_report( @@ -89,7 +92,8 @@ impl HandleEquivocation for () { fn report_offence( _reporters: Vec, _offence: GrandpaEquivocationOffence, - ) { + ) -> Result<(), OffenceError> { + Ok(()) } fn submit_equivocation_report( @@ -160,8 +164,8 @@ where Some((offender, session_index, validator_set_count)) } - fn report_offence(reporters: Vec, offence: O) { - R::report_offence(reporters, offence); + fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { + R::report_offence(reporters, offence) } fn submit_equivocation_report( diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index b9475ec6d4f5f..c08578c967c86 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -285,7 +285,7 @@ decl_module! { set_id, equivocation_proof.round(), ), - ); + ).map_err(|_| "Duplicate offence report.")?; } fn on_initialize() { From 18eb5601632faba5678f12bde9df3178fb7627db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 1 Apr 2020 20:17:40 +0100 Subject: [PATCH 47/78] grandpa: expose session and validator count in proofs through traits --- Cargo.lock | 1 + frame/grandpa/Cargo.toml | 2 ++ frame/grandpa/src/equivocation.rs | 52 +++++++++++++++++++++++++----- frame/grandpa/src/lib.rs | 16 +++++++--- frame/session/src/historical.rs | 53 +++++++++++++++++-------------- frame/support/src/traits.rs | 16 ++-------- primitives/session/src/lib.rs | 10 ++++++ 7 files changed, 101 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39f9a8ab6c283..53ee6acd02e67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4327,6 +4327,7 @@ dependencies = [ "sp-finality-grandpa", "sp-io", "sp-runtime", + "sp-session", "sp-staking", "sp-std", ] diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index b2915253005ec..2e86a2ea20fc3 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -14,6 +14,7 @@ codec = { package = "parity-scale-codec", version = "1.3.0", default-features = sp-application-crypto = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/application-crypto" } sp-core = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/core" } sp-finality-grandpa = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/finality-grandpa" } +sp-session = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/session" } sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } sp-staking = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/staking" } @@ -33,6 +34,7 @@ std = [ "sp-application-crypto/std", "sp-core/std", "sp-finality-grandpa/std", + "sp-session/std", "sp-std/std", "frame-support/std", "sp-runtime/std", diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 2b7a31d2dd319..8afc7281a6088 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -30,7 +30,7 @@ //! equivocation proofs in the extrinsic and report the offences. //! -use sp_std::{fmt::Debug, prelude::*}; +use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; use frame_support::traits::KeyOwnerProofSystem; @@ -51,7 +51,7 @@ use sp_staking::{ /// (useful only in offchain context). pub trait HandleEquivocation { /// The proof of key ownership. - type KeyOwnerProof: Clone + Debug + Decode + Encode + PartialEq; + type KeyOwnerProof: Decode + Encode + GetSessionNumber + GetValidatorCount; /// The identification of a key owner. type KeyOwnerIdentification; /// The offence type used for reporting offences on valid equivocation reports. @@ -62,7 +62,7 @@ pub trait HandleEquivocation { fn check_proof( equivocation_proof: &EquivocationProof, key_owner_proof: Self::KeyOwnerProof, - ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)>; + ) -> Option; /// Report an offence proved by the given reporters. fn report_offence( @@ -85,7 +85,7 @@ impl HandleEquivocation for () { fn check_proof( _equivocation_proof: &EquivocationProof, _key_owner_proof: Self::KeyOwnerProof, - ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { + ) -> Option { None } @@ -104,6 +104,41 @@ impl HandleEquivocation for () { } } +/// A trait to get a session number the `MembershipProof` belongs to. +pub trait GetSessionNumber { + fn session(&self) -> SessionIndex; +} + +/// A trait to get the validator count at the session the `MembershipProof` +/// belongs to. +pub trait GetValidatorCount { + fn validator_count(&self) -> sp_session::ValidatorCount; +} + +impl GetSessionNumber for () { + fn session(&self) -> SessionIndex { + Default::default() + } +} + +impl GetValidatorCount for () { + fn validator_count(&self) -> sp_session::ValidatorCount { + Default::default() + } +} + +impl GetSessionNumber for sp_session::MembershipProof { + fn session(&self) -> SessionIndex { + self.session() + } +} + +impl GetValidatorCount for sp_session::MembershipProof { + fn validator_count(&self) -> sp_session::ValidatorCount { + self.validator_count() + } +} + /// Generic equivocation handler. This type implements `HandleEquivocation` /// using existing subsystems that are part of frame (type bounds described /// below) and will dispatch to them directly, it's only purpose is to wire all @@ -135,7 +170,8 @@ where // of a validator set, needed for validating equivocation reports. The // session index and validator count of the session are part of the proof // as extra data. - P: KeyOwnerProofSystem<(KeyTypeId, Vec), ExtraData = (SessionIndex, u32)>, + P: KeyOwnerProofSystem<(KeyTypeId, Vec)>, + P::Proof: GetSessionNumber + GetValidatorCount, // A transaction submitter. Used for submitting equivocation reports. S: SubmitSignedTransaction::Call>, // The offence type that should be used when reporting. @@ -155,13 +191,13 @@ where fn check_proof( equivocation_proof: &EquivocationProof, key_owner_proof: Self::KeyOwnerProof, - ) -> Option<(Self::KeyOwnerIdentification, SessionIndex, u32)> { - let (offender, (session_index, validator_set_count)) = P::check_proof( + ) -> Option { + let offender = P::check_proof( (GRANDPA, equivocation_proof.offender().encode()), key_owner_proof, )?; - Some((offender, session_index, validator_set_count)) + Some(offender) } fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index d6434c735fb41..e63c68c6d4e60 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -219,18 +219,26 @@ decl_module! { // key_owner_proof: >::KeyOwnerProof, key_owner_proof: Vec, ) { - use equivocation::{GrandpaOffence, HandleEquivocation}; + use equivocation::{ + GetSessionNumber, GetValidatorCount, GrandpaOffence, HandleEquivocation, + }; let reporter_id = ensure_signed(origin)?; // FIXME: this is a hack needed because the typed argument version above fails to // compile (due to missing codec implementation) - let key_owner_proof = Decode::decode(&mut &key_owner_proof[..]) - .map_err(|_| "Key owner proof decoding failed.")?; + let key_owner_proof: >::KeyOwnerProof = + Decode::decode(&mut &key_owner_proof[..]) + .map_err(|_| "Key owner proof decoding failed.")?; + + let (session_index, validator_set_count) = ( + key_owner_proof.session(), + key_owner_proof.validator_count(), + ); // validate the membership proof and extract session index and // validator set count of the session that we're proving membership of - let (offender, session_index, validator_set_count) = + let offender = T::HandleEquivocation::check_proof( &equivocation_proof, key_owner_proof, diff --git a/frame/session/src/historical.rs b/frame/session/src/historical.rs index b1e753f9fbbe7..d9711ecf9d3bd 100644 --- a/frame/session/src/historical.rs +++ b/frame/session/src/historical.rs @@ -29,15 +29,13 @@ use sp_std::prelude::*; use codec::{Encode, Decode}; use sp_runtime::KeyTypeId; use sp_runtime::traits::{Convert, OpaqueKeys}; -use sp_session::MembershipProof; +use sp_session::{MembershipProof, ValidatorCount}; use frame_support::{decl_module, decl_storage}; use frame_support::{Parameter, print}; use sp_trie::{MemoryDB, Trie, TrieMut, Recorder, EMPTY_PREFIX}; use sp_trie::trie_types::{TrieDBMut, TrieDB}; use super::{SessionIndex, Module as SessionModule}; -type ValidatorCount = u32; - /// Trait necessary for the historical module. pub trait Trait: super::Trait { /// Full identification of the validator. @@ -127,7 +125,7 @@ impl crate::SessionManager for NoteHistoricalRoot::generate_for(new_validators) { Ok(trie) => >::insert(new_index, &(trie.root, count)), Err(reason) => { @@ -260,46 +258,53 @@ impl> frame_support::traits::KeyOwnerProofSystem<(KeyTy type Proof = MembershipProof; type IdentificationTuple = IdentificationTuple; - // The session index of a given membership proof and the validator count for - // that session. - type ExtraData = (SessionIndex, ValidatorCount); - fn prove(key: (KeyTypeId, D)) -> Option { let session = >::current_index(); - let validators = >::validators().into_iter() + let validators = >::validators() + .into_iter() .filter_map(|validator| { T::FullIdentificationOf::convert(validator.clone()) .map(|full_id| (validator, full_id)) - }); + }) + .collect::>(); + + let count = validators.len() as ValidatorCount; + let trie = ProvingTrie::::generate_for(validators).ok()?; let (id, data) = key; - - trie.prove(id, data.as_ref()).map(|trie_nodes| MembershipProof { - session, - trie_nodes, - }) + trie.prove(id, data.as_ref()) + .map(|trie_nodes| MembershipProof { + session, + trie_nodes, + validator_count: count, + }) } - fn check_proof( - key: (KeyTypeId, D), - proof: Self::Proof, - ) -> Option<(IdentificationTuple, Self::ExtraData)> { + fn check_proof(key: (KeyTypeId, D), proof: Self::Proof) -> Option> { let (id, data) = key; if proof.session == >::current_index() { >::key_owner(id, data.as_ref()).and_then(|owner| { - T::FullIdentificationOf::convert(owner.clone()).map(move |id| { - let count = >::validators().len() as u32; - ((owner, id), (proof.session, count)) + T::FullIdentificationOf::convert(owner.clone()).and_then(move |id| { + let count = >::validators().len() as ValidatorCount; + + if count != proof.validator_count { + return None; + } + + Some((owner, id)) }) }) } else { let (root, count) = >::get(&proof.session)?; - let trie = ProvingTrie::::from_nodes(root, &proof.trie_nodes); + if count != proof.validator_count { + return None; + } + + let trie = ProvingTrie::::from_nodes(root, &proof.trie_nodes); trie.query(id, data.as_ref()) - .map(|id| (id, (proof.session, count))) } } } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index d01ad140ff610..77e4c679f4e68 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -263,16 +263,9 @@ pub trait VerifySeal { /// key owner. pub trait KeyOwnerProofSystem { /// The proof of membership itself. - type Proof: Clone + Codec + Debug + PartialEq; + type Proof: Codec; /// The full identification of a key owner and the stash account. - type IdentificationTuple: Clone + Codec; - - /// Arbitrary data associated with a given key ownership proof, useful for - /// providing context to the proof. For example, if the proof is proving - /// ownership of a key in a **numbered** validator set, then the context of - /// the proof could be the number associated with that validator set (e.g. a - /// session index). - type ExtraData; + type IdentificationTuple: Codec; /// Prove membership of a key owner in the current block-state. /// @@ -285,10 +278,7 @@ pub trait KeyOwnerProofSystem { /// Check a proof of membership on-chain. Return `Some` iff the proof is /// valid and recent enough to check. - fn check_proof( - key: Key, - proof: Self::Proof, - ) -> Option<(Self::IdentificationTuple, Self::ExtraData)>; + fn check_proof(key: Key, proof: Self::Proof) -> Option; } /// Handler for when some currency "account" decreased in balance for diff --git a/primitives/session/src/lib.rs b/primitives/session/src/lib.rs index 7c74d4ed25bf5..d0fa8f8aee992 100644 --- a/primitives/session/src/lib.rs +++ b/primitives/session/src/lib.rs @@ -60,6 +60,9 @@ sp_api::decl_runtime_apis! { } } +/// Number of validators in a given session. +pub type ValidatorCount = u32; + /// Proof of membership of a specific key in a given session. #[derive(Encode, Decode, Clone, Eq, PartialEq, Default, RuntimeDebug)] pub struct MembershipProof { @@ -67,6 +70,8 @@ pub struct MembershipProof { pub session: SessionIndex, /// Trie nodes of a merkle proof of session membership. pub trie_nodes: Vec>, + /// The validator count of the session on which the specific key is a member. + pub validator_count: ValidatorCount, } impl MembershipProof { @@ -74,6 +79,11 @@ impl MembershipProof { pub fn session(&self) -> SessionIndex { self.session } + + /// Returns the validator count of the session this proof was generated for. + pub 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 From 9a0412030a1f8d9ebe84ef13f684538fccae62c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 2 Apr 2020 00:07:59 +0100 Subject: [PATCH 48/78] grandpa: use strong key in module KeyOwnerProofSystem --- frame/grandpa/src/equivocation.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 8afc7281a6088..e5509e1b4987d 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -36,7 +36,7 @@ use codec::{self as codec, Decode, Encode}; use frame_support::traits::KeyOwnerProofSystem; use frame_system::offchain::SubmitSignedTransaction; use sp_application_crypto::{key_types::GRANDPA, RuntimeAppPublic}; -use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; +use sp_finality_grandpa::{AuthorityId, EquivocationProof, RoundNumber, SetId}; use sp_runtime::{traits::IdentifyAccount, DispatchResult, KeyTypeId, Perbill}; use sp_staking::{ offence::{Kind, Offence, OffenceError, ReportOffence}, @@ -149,7 +149,7 @@ pub struct EquivocationHandler< R, K, O = GrandpaEquivocationOffence< -

)>>::IdentificationTuple, +

>::IdentificationTuple, >, > { _phantom: sp_std::marker::PhantomData<(P, S, O, R, K)>, @@ -170,7 +170,7 @@ where // of a validator set, needed for validating equivocation reports. The // session index and validator count of the session are part of the proof // as extra data. - P: KeyOwnerProofSystem<(KeyTypeId, Vec)>, + P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId)>, P::Proof: GetSessionNumber + GetValidatorCount, // A transaction submitter. Used for submitting equivocation reports. S: SubmitSignedTransaction::Call>, @@ -193,7 +193,7 @@ where key_owner_proof: Self::KeyOwnerProof, ) -> Option { let offender = P::check_proof( - (GRANDPA, equivocation_proof.offender().encode()), + (GRANDPA, equivocation_proof.offender().clone()), key_owner_proof, )?; From 5be01efe2c052193cb78694d3807b37354073967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 2 Apr 2020 00:18:40 +0100 Subject: [PATCH 49/78] grandpa: move key ownership proof to grandpa runtime api --- bin/node-template/runtime/src/lib.rs | 14 ++++++-------- bin/node/runtime/src/lib.rs | 20 ++++++++++---------- client/finality-grandpa/src/environment.rs | 18 +++++++----------- client/finality-grandpa/src/lib.rs | 7 +++---- primitives/finality-grandpa/src/lib.rs | 8 ++++++++ primitives/session/src/lib.rs | 11 ----------- 6 files changed, 34 insertions(+), 44 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index e6ed0bf52305b..4456373d571a5 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -356,14 +356,6 @@ impl_runtime_apis! { } } - impl sp_session::SessionMembership for Runtime { - fn generate_session_membership_proof( - _session_key: (sp_core::crypto::KeyTypeId, Vec), - ) -> Option { - None - } - } - impl fg_primitives::GrandpaApi for Runtime { fn grandpa_authorities() -> GrandpaAuthorityList { Grandpa::grandpa_authorities() @@ -378,5 +370,11 @@ impl_runtime_apis! { ) -> Option<()> { None } + + fn generate_key_ownership_proof( + _authority_key: fg_primitives::AuthorityId, + ) -> Option> { + None + } } } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ac0b74069e10a..ea54a7cfa514b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -786,6 +786,16 @@ impl_runtime_apis! { key_owner_proof, ) } + + fn generate_key_ownership_proof( + session_key: fg_primitives::AuthorityId, + ) -> Option> { + use codec::Encode; + use frame_support::traits::KeyOwnerProofSystem; + + Historical::prove((fg_primitives::KEY_TYPE, session_key)) + .map(|p| p.encode()) + } } impl sp_consensus_babe::BabeApi for Runtime { @@ -879,16 +889,6 @@ impl_runtime_apis! { } } - impl sp_session::SessionMembership for Runtime { - fn generate_session_membership_proof( - session_key: (sp_core::crypto::KeyTypeId, Vec), - ) -> Option { - use frame_support::traits::KeyOwnerProofSystem; - - Historical::prove(session_key) - } - } - #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn dispatch_benchmark( diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 2e6ee9d958014..5c1de633bfb77 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -39,7 +39,6 @@ use sp_runtime::generic::BlockId; use sp_runtime::traits::{ Block as BlockT, Header as HeaderT, NumberFor, One, Zero, }; -use sp_session::SessionMembership; use sc_telemetry::{telemetry, CONSENSUS_INFO}; use crate::{ @@ -584,7 +583,7 @@ where Block: 'static, B: Backend, C: crate::ClientForGrandpa + 'static, - C::Api: GrandpaApi+ SessionMembership, + C::Api: GrandpaApi, N: NetworkT + 'static + Send, SC: SelectChain + 'static, VR: VotingRule, @@ -980,7 +979,7 @@ where Block: BlockT, BE: Backend, Client: crate::ClientForGrandpa, - Client::Api: GrandpaApi+ SessionMembership, + Client::Api: GrandpaApi, SC: SelectChain + 'static, { let is_descendent_of = is_descendent_of(&**client, None); @@ -1021,15 +1020,12 @@ where None => best_header.hash(), }; - // generate membership proof at that block - let membership_proof = match client + // generate key ownership proof at that block + let key_owner_proof = match client .runtime_api() - .generate_session_membership_proof( + .generate_key_ownership_proof( &BlockId::Hash(current_set_latest_hash), - ( - sp_finality_grandpa::KEY_TYPE, - equivocation.offender().encode(), - ), + equivocation.offender().clone(), ) .map_err(Error::Client)? { @@ -1050,7 +1046,7 @@ where .submit_report_equivocation_extrinsic( &BlockId::Hash(best_header.hash()), equivocation_proof, - membership_proof.encode(), + key_owner_proof, ).map_err(Error::Client)?; Ok(()) diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 79d86a850fa8e..3708a44ab46e8 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -68,7 +68,6 @@ use sp_runtime::generic::BlockId; use sp_runtime::traits::{NumberFor, Block as BlockT, DigestFor, Zero}; use sc_keystore::KeyStorePtr; use sp_inherents::InherentDataProviders; -use sp_session::SessionMembership; use sp_consensus::{SelectChain, BlockImport}; use sp_core::Pair; use sc_telemetry::{telemetry, CONSENSUS_INFO, CONSENSUS_DEBUG}; @@ -617,7 +616,7 @@ pub fn run_grandpa_voter( NumberFor: BlockNumberOps, DigestFor: Encode, C: ClientForGrandpa + 'static, - C::Api: GrandpaApi+ SessionMembership, + C::Api: GrandpaApi, { let GrandpaParams { mut config, @@ -713,7 +712,7 @@ where Block: BlockT, B: Backend + 'static, C: ClientForGrandpa + 'static, - C::Api: GrandpaApi+ SessionMembership, + C::Api: GrandpaApi, N: NetworkT + Sync, NumberFor: BlockNumberOps, SC: SelectChain + 'static, @@ -899,7 +898,7 @@ where NumberFor: BlockNumberOps, SC: SelectChain + 'static, C: ClientForGrandpa + 'static, - C::Api: GrandpaApi+ SessionMembership, + C::Api: GrandpaApi, VR: VotingRule + Clone + 'static, { type Output = Result<(), Error>; diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 24605d0cd8bbd..fb331edf8e117 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -484,5 +484,13 @@ sp_api::decl_runtime_apis! { equivocation_proof: EquivocationProof>, key_owner_proof: Vec, ) -> Option<()>; + + /// Generates a proof that the given session key is a part of the + /// current session. The generated proof can later on be validated with + /// the historical session module. Proofs of membership are useful e.g. + /// for validating misbehavior reports. + fn generate_key_ownership_proof( + authority_key: AuthorityId, + ) -> Option>; } } diff --git a/primitives/session/src/lib.rs b/primitives/session/src/lib.rs index d0fa8f8aee992..720dfbfcaddfb 100644 --- a/primitives/session/src/lib.rs +++ b/primitives/session/src/lib.rs @@ -47,17 +47,6 @@ sp_api::decl_runtime_apis! { /// Returns the list of public raw public keys + key type. fn decode_session_keys(encoded: Vec) -> Option, KeyTypeId)>>; } - - /// Historical session membership runtime api. - pub trait SessionMembership { - /// Generates a proof that the given session key is a part of the - /// current session. The generated proof can later on be validated with - /// the historical session module. Proofs of membership are useful e.g. - /// for validating misbehavior reports. - fn generate_session_membership_proof( - session_key: (KeyTypeId, Vec), - ) -> Option; - } } /// Number of validators in a given session. From d3304fefa3fa462a7acf24f3655d4b20356c6fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 2 Apr 2020 19:35:02 +0100 Subject: [PATCH 50/78] grandpa: remove unnecessary cloning when checking equivocation proof --- frame/grandpa/src/lib.rs | 15 +++++++++------ primitives/finality-grandpa/src/lib.rs | 13 +++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index e63c68c6d4e60..e6042e6fa3aa5 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -244,15 +244,18 @@ decl_module! { key_owner_proof, ).ok_or("Invalid/outdated key ownership proof.")?; - // validate equivocation proof (check votes are different and - // signatures are valid). - fg_primitives::check_equivocation_proof(&equivocation_proof) - .map_err(|_| "Invalid equivocation proof.")?; - // we check the equivocation within the context of its set id (and // associated session). let set_id = equivocation_proof.set_id(); + // the GRANDPA round when the offence happened + let round = equivocation_proof.round(); + + // validate equivocation proof (check votes are different and + // signatures are valid). + fg_primitives::check_equivocation_proof(equivocation_proof) + .map_err(|_| "Invalid equivocation proof.")?; + // fetch the current and previous sets last session index. on the // genesis set there's no previous set. let previous_set_id_session_index = if set_id == 0 { @@ -285,7 +288,7 @@ decl_module! { validator_set_count, offender, set_id, - equivocation_proof.round(), + round, ), ).map_err(|_| "Duplicate offence report.")?; } diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index fb331edf8e117..6c73e31a49c61 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -253,7 +253,7 @@ impl Equivocation { /// Verifies the equivocation proof by making sure that both votes target /// different blocks and that its signatures are valid. -pub fn check_equivocation_proof(report: &EquivocationProof) -> Result<(), ()> +pub fn check_equivocation_proof(report: EquivocationProof) -> Result<(), ()> where H: Clone + Encode + PartialEq, N: Clone + Encode + PartialEq, @@ -264,13 +264,14 @@ where ( $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 { + $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.clone()), + &$message($equivocation.first.0), &$equivocation.identity, &$equivocation.first.1, $equivocation.round_number, @@ -278,7 +279,7 @@ where )?; check_message_signature( - &$message($equivocation.second.0.clone()), + &$message($equivocation.second.0), &$equivocation.identity, &$equivocation.second.1, $equivocation.round_number, @@ -290,10 +291,10 @@ where } match report.equivocation { - Equivocation::Prevote(ref equivocation) => { + Equivocation::Prevote(equivocation) => { check!(equivocation, grandpa::Message::Prevote); } - Equivocation::Precommit(ref equivocation) => { + Equivocation::Precommit(equivocation) => { check!(equivocation, grandpa::Message::Precommit); } } From 71d60de31a66a55e0576d894e64e9d0320d952ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 2 Apr 2020 20:14:36 +0100 Subject: [PATCH 51/78] grandpa: make report_equivocation a method in Environment --- client/finality-grandpa/src/environment.rs | 209 ++++++++++----------- 1 file changed, 98 insertions(+), 111 deletions(-) diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 5f3d0f50ff392..bdc7618d6f4d8 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -407,7 +407,7 @@ pub(crate) struct Environment, SC, pub(crate) _phantom: PhantomData, } -impl, SC, VR> Environment { +impl, SC, VR> Environment { /// Updates the voter set state using the given closure. The write lock is /// held during evaluation of the closure and the environment's voter set /// state is set to its result if successful. @@ -434,6 +434,97 @@ impl, SC, VR> Environment Environment +where + Block: BlockT, + BE: Backend, + C: crate::ClientForGrandpa, + C::Api: GrandpaApi, + N: NetworkT, + SC: SelectChain + 'static, +{ + /// Report the given equivocation to the GRANDPA runtime module. This method + /// generates a session membership proof of the offender and then submits an + /// extrinsic to report the equivocation. In particular, the session membership + /// proof must be generated at the block at which the given set was active which + /// isn't necessarily the best block if there are pending authority set changes. + fn report_equivocation( + &self, + equivocation: Equivocation>, + ) -> Result<(), Error> { + 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()))?; + + let authority_set = self.authority_set.inner().read(); + + // block hash and number of the next pending authority set change in the + // given best chain. + let next_change = authority_set + .next_change(&best_header.hash(), &is_descendent_of) + .map_err(|e| Error::Safety(e.to_string()))?; + + // find the hash of the latest block in the current set + let current_set_latest_hash = match next_change { + Some((_, n)) if n.is_zero() => { + return Err(Error::Safety( + "Authority set change signalled at genesis.".to_string(), + )) + } + // the next set starts at `n` so the current one lasts until `n - 1`. if + // `n` is later than the best block, then the current set is still live + // at best block. + Some((_, n)) if n > *best_header.number() => best_header.hash(), + Some((h, _)) => { + // this is the header at which the new set will start + let header = self.client.header(BlockId::Hash(h))?.expect( + "got block hash from registered pending change; \ + pending changes are only registered on block import; qed.", + ); + + // its parent block is the last block in the current set + *header.parent_hash() + } + // there is no pending change, the latest block for the current set is + // the best block. + None => best_header.hash(), + }; + + // generate key ownership proof at that block + let key_owner_proof = match self.client + .runtime_api() + .generate_key_ownership_proof( + &BlockId::Hash(current_set_latest_hash), + equivocation.offender().clone(), + ) + .map_err(Error::Client)? + { + Some(proof) => proof, + None => { + debug!(target: "afg", "Equivocation offender is not part of the authority set."); + return Ok(()); + } + }; + + // submit equivocation report at **best** block + let equivocation_proof = EquivocationProof::new( + authority_set.set_id, + equivocation, + ); + + self.client.runtime_api() + .submit_report_equivocation_extrinsic( + &BlockId::Hash(best_header.hash()), + equivocation_proof, + key_owner_proof, + ).map_err(Error::Client)?; + + Ok(()) + } +} + impl finality_grandpa::Chain> for Environment @@ -932,17 +1023,9 @@ where equivocation: finality_grandpa::Equivocation, Self::Signature>, ) { warn!(target: "afg", "Detected prevote equivocation in the finality worker: {:?}", equivocation); - match report_equivocation( - &self.client.clone(), - &self.authority_set.inner().read(), - &self.select_chain, - equivocation.into(), - ) { - Ok(_) => {} - Err(err) => { - warn!(target: "afg", "Error reporting prevote equivocation: {:?}", err); - } - }; + if let Err(err) = self.report_equivocation(equivocation.into()) { + warn!(target: "afg", "Error reporting prevote equivocation: {:?}", err); + } } fn precommit_equivocation( @@ -951,106 +1034,10 @@ where equivocation: finality_grandpa::Equivocation, Self::Signature>, ) { warn!(target: "afg", "Detected precommit equivocation in the finality worker: {:?}", equivocation); - match report_equivocation( - &self.client.clone(), - &self.authority_set.inner().read(), - &self.select_chain, - equivocation.into(), - ) { - Ok(_) => {} - Err(err) => { - warn!(target: "afg", "Error reporting precommit equivocation: {:?}", err); - } - }; - } -} - -/// Report the given equivocation to the GRANDPA runtime module. This method -/// generates a session membership proof of the offender and then submits an -/// extrinsic to report the equivocation. In particular, the session membership -/// proof must be generated at the block at which the given set was active which -/// isn't necessarily the best block if there are pending authority set changes. -fn report_equivocation( - client: &Arc, - authority_set: &AuthoritySet>, - select_chain: &SC, - equivocation: Equivocation>, -) -> Result<(), Error> -where - Block: BlockT, - BE: Backend, - Client: crate::ClientForGrandpa, - Client::Api: GrandpaApi, - SC: SelectChain + 'static, -{ - let is_descendent_of = is_descendent_of(&**client, None); - - let best_header = select_chain - .best_chain() - .map_err(|e| Error::Blockchain(e.to_string()))?; - - // block hash and number of the next pending authority set change in the - // given best chain. - let next_change = authority_set - .next_change(&best_header.hash(), &is_descendent_of) - .map_err(|e| Error::Safety(e.to_string()))?; - - // find the hash of the latest block in the current set - let current_set_latest_hash = match next_change { - Some((_, n)) if n.is_zero() => { - return Err(Error::Safety( - "Authority set change signalled at genesis.".to_string(), - )) + if let Err(err) = self.report_equivocation(equivocation.into()) { + warn!(target: "afg", "Error reporting precommit equivocation: {:?}", err); } - // the next set starts at `n` so the current one lasts until `n - 1`. if - // `n` is later than the best block, then the current set is still live - // at best block. - Some((_, n)) if n > *best_header.number() => best_header.hash(), - Some((h, _)) => { - // this is the header at which the new set will start - let header = client.header(BlockId::Hash(h))?.expect( - "got block hash from registered pending change; \ - pending changes are only registered on block import; qed.", - ); - - // its parent block is the last block in the current set - *header.parent_hash() - } - // there is no pending change, the latest block for the current set is - // the best block. - None => best_header.hash(), - }; - - // generate key ownership proof at that block - let key_owner_proof = match client - .runtime_api() - .generate_key_ownership_proof( - &BlockId::Hash(current_set_latest_hash), - equivocation.offender().clone(), - ) - .map_err(Error::Client)? - { - Some(proof) => proof, - None => { - debug!(target: "afg", "Equivocation offender is not part of the authority set."); - return Ok(()); - } - }; - - // submit equivocation report at **best** block - let equivocation_proof = EquivocationProof::new( - authority_set.set_id, - equivocation, - ); - - client.runtime_api() - .submit_report_equivocation_extrinsic( - &BlockId::Hash(best_header.hash()), - equivocation_proof, - key_owner_proof, - ).map_err(Error::Client)?; - - Ok(()) + } } pub(crate) enum JustificationOrCommit { From bd4c74f75b67bfb299364c5fe447e890c628e0fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 8 Apr 2020 19:20:03 +0100 Subject: [PATCH 52/78] support: implement KeyOwnerProofSystem for () --- frame/support/src/lib.rs | 4 +++- frame/support/src/traits.rs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index f242efecc401e..eb1bd240db352 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -216,9 +216,11 @@ macro_rules! assert_ok { } } +use codec::{Decode, Encode}; + /// The void type - it cannot exist. // Oh rust, you crack me up... -#[derive(Clone, Eq, PartialEq, RuntimeDebug)] +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug)] pub enum Void {} #[cfg(feature = "std")] diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index f19d3995eaf13..c2642c8debfbd 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -281,6 +281,21 @@ pub trait KeyOwnerProofSystem { fn check_proof(key: Key, proof: Self::Proof) -> Option; } +impl KeyOwnerProofSystem for () { + // The proof and identification tuples is any bottom type to guarantee that the methods of this + // implementation can never be called or return anything other than `None`. + type Proof = crate::Void; + type IdentificationTuple = crate::Void; + + fn prove(_key: Key) -> Option { + None + } + + fn check_proof(_key: Key, _proof: Self::Proof) -> Option { + None + } +} + /// Handler for when some currency "account" decreased in balance for /// some reason. /// From 668363f33174f756deb40452852723f0753fb863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 8 Apr 2020 19:39:20 +0100 Subject: [PATCH 53/78] grandpa: move KeyOwnerProofSystem to module trait --- bin/node-template/runtime/src/lib.rs | 20 ++++++- bin/node/runtime/src/lib.rs | 25 ++++++-- frame/grandpa/src/equivocation.rs | 89 +++++++--------------------- frame/grandpa/src/lib.rs | 54 +++++++++-------- 4 files changed, 86 insertions(+), 102 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 4456373d571a5..c78c7fcd69db7 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -9,7 +9,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use sp_std::prelude::*; -use sp_core::OpaqueMetadata; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ ApplyExtrinsicResult, generic, create_runtime_str, impl_opaque_keys, MultiSignature, transaction_validity::{TransactionValidity, TransactionSource}, @@ -33,7 +33,7 @@ pub use balances::Call as BalancesCall; pub use sp_runtime::{Permill, Perbill}; pub use frame_support::{ StorageValue, construct_runtime, parameter_types, - traits::Randomness, + traits::{KeyOwnerProofSystem, Randomness}, weights::Weight, }; @@ -176,6 +176,17 @@ impl aura::Trait for Runtime { impl grandpa::Trait for Runtime { type Event = Event; type Call = Call; + + type KeyOwnerProofSystem = (); + + type KeyOwnerProof = + )>>::Proof; + + type KeyOwnerIdentification = , + )>>::IdentificationTuple; + type HandleEquivocation = (); } @@ -351,7 +362,7 @@ impl_runtime_apis! { fn decode_session_keys( encoded: Vec, - ) -> Option, sp_core::crypto::KeyTypeId)>> { + ) -> Option, KeyTypeId)>> { opaque::SessionKeys::decode_into_raw_public_keys(&encoded) } } @@ -374,6 +385,9 @@ impl_runtime_apis! { fn generate_key_ownership_proof( _authority_key: fg_primitives::AuthorityId, ) -> Option> { + // NOTE: this is the only implementation possible since we've + // defined our key owner proof type as a bottom type (i.e. a type + // with no values). None } } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index fbeb9717a19c7..4ca91fce1c313 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -24,9 +24,13 @@ use sp_std::prelude::*; use frame_support::{ construct_runtime, parameter_types, debug, weights::Weight, - traits::{Currency, Randomness, OnUnbalanced, Imbalance}, + traits::{Currency, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness}, +}; +use sp_core::{ + crypto::KeyTypeId, + u32_trait::{_1, _2, _3, _4}, + OpaqueMetadata, }; -use sp_core::u32_trait::{_1, _2, _3, _4}; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; use sp_api::impl_runtime_apis; @@ -43,7 +47,6 @@ use sp_runtime::traits::{ use sp_version::RuntimeVersion; #[cfg(any(feature = "std", test))] use sp_version::NativeVersion; -use sp_core::OpaqueMetadata; use pallet_grandpa::AuthorityList as GrandpaAuthorityList; use pallet_grandpa::fg_primitives; use pallet_im_online::sr25519::{AuthorityId as ImOnlineId}; @@ -563,8 +566,19 @@ impl pallet_authority_discovery::Trait for Runtime {} impl pallet_grandpa::Trait for Runtime { type Event = Event; type Call = Call; + + type KeyOwnerProofSystem = Historical; + + type KeyOwnerProof = + )>>::Proof; + + type KeyOwnerIdentification = , + )>>::IdentificationTuple; + type HandleEquivocation = pallet_grandpa::EquivocationHandler< - Historical, + Self::KeyOwnerIdentification, TransactionSubmitterOf, Offences, node_primitives::report::ReporterId, @@ -808,7 +822,6 @@ impl_runtime_apis! { session_key: fg_primitives::AuthorityId, ) -> Option> { use codec::Encode; - use frame_support::traits::KeyOwnerProofSystem; Historical::prove((fg_primitives::KEY_TYPE, session_key)) .map(|p| p.encode()) @@ -901,7 +914,7 @@ impl_runtime_apis! { fn decode_session_keys( encoded: Vec, - ) -> Option, sp_core::crypto::KeyTypeId)>> { + ) -> Option, KeyTypeId)>> { SessionKeys::decode_into_raw_public_keys(&encoded) } } diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index e5509e1b4987d..8e09aeb9f6d28 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -33,11 +33,10 @@ use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; -use frame_support::traits::KeyOwnerProofSystem; use frame_system::offchain::SubmitSignedTransaction; -use sp_application_crypto::{key_types::GRANDPA, RuntimeAppPublic}; -use sp_finality_grandpa::{AuthorityId, EquivocationProof, RoundNumber, SetId}; -use sp_runtime::{traits::IdentifyAccount, DispatchResult, KeyTypeId, Perbill}; +use sp_application_crypto::RuntimeAppPublic; +use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; +use sp_runtime::{traits::IdentifyAccount, DispatchResult, Perbill}; use sp_staking::{ offence::{Kind, Offence, OffenceError, ReportOffence}, SessionIndex, @@ -50,19 +49,8 @@ use sp_staking::{ /// report, and also for creating and submitting equivocation report extrinsics /// (useful only in offchain context). pub trait HandleEquivocation { - /// The proof of key ownership. - type KeyOwnerProof: Decode + Encode + GetSessionNumber + GetValidatorCount; - /// The identification of a key owner. - type KeyOwnerIdentification; /// The offence type used for reporting offences on valid equivocation reports. - type Offence: GrandpaOffence; - - /// Verifies the key ownership proof and the equivocation proof against the - /// offending key owner. - fn check_proof( - equivocation_proof: &EquivocationProof, - key_owner_proof: Self::KeyOwnerProof, - ) -> Option; + type Offence: GrandpaOffence; /// Report an offence proved by the given reporters. fn report_offence( @@ -73,32 +61,23 @@ pub trait HandleEquivocation { /// Create and dispatch an equivocation report extrinsic. fn submit_equivocation_report( equivocation_proof: EquivocationProof, - key_owner_proof: Self::KeyOwnerProof, + key_owner_proof: T::KeyOwnerProof, ) -> DispatchResult; } impl HandleEquivocation for () { - type KeyOwnerProof = (); - type KeyOwnerIdentification = (); - type Offence = GrandpaEquivocationOffence; - - fn check_proof( - _equivocation_proof: &EquivocationProof, - _key_owner_proof: Self::KeyOwnerProof, - ) -> Option { - None - } + type Offence = GrandpaEquivocationOffence; fn report_offence( _reporters: Vec, - _offence: GrandpaEquivocationOffence, + _offence: GrandpaEquivocationOffence, ) -> Result<(), OffenceError> { Ok(()) } fn submit_equivocation_report( _equivocation_proof: EquivocationProof, - _key_owner_proof: Self::KeyOwnerProof, + _key_owner_proof: T::KeyOwnerProof, ) -> DispatchResult { Ok(()) } @@ -115,13 +94,13 @@ pub trait GetValidatorCount { fn validator_count(&self) -> sp_session::ValidatorCount; } -impl GetSessionNumber for () { +impl GetSessionNumber for frame_support::Void { fn session(&self) -> SessionIndex { Default::default() } } -impl GetValidatorCount for () { +impl GetValidatorCount for frame_support::Void { fn validator_count(&self) -> sp_session::ValidatorCount { Default::default() } @@ -143,19 +122,11 @@ impl GetValidatorCount for sp_session::MembershipProof { /// using existing subsystems that are part of frame (type bounds described /// below) and will dispatch to them directly, it's only purpose is to wire all /// subsystems together. -pub struct EquivocationHandler< - P, - S, - R, - K, - O = GrandpaEquivocationOffence< -

>::IdentificationTuple, - >, -> { - _phantom: sp_std::marker::PhantomData<(P, S, O, R, K)>, +pub struct EquivocationHandler> { + _phantom: sp_std::marker::PhantomData<(I, S, O, R, K)>, } -impl Default for EquivocationHandler { +impl Default for EquivocationHandler { fn default() -> Self { Self { _phantom: Default::default(), @@ -163,53 +134,33 @@ impl Default for EquivocationHandler { } } -impl HandleEquivocation for EquivocationHandler +impl HandleEquivocation + for EquivocationHandler where - T: super::Trait, - // A system for proving ownership of keys, i.e. that a given key was part - // of a validator set, needed for validating equivocation reports. The - // session index and validator count of the session are part of the proof - // as extra data. - P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId)>, - P::Proof: GetSessionNumber + GetValidatorCount, + T: super::Trait, // A transaction submitter. Used for submitting equivocation reports. S: SubmitSignedTransaction::Call>, // The offence type that should be used when reporting. - O: GrandpaOffence, + O: GrandpaOffence, // A system for reporting offences after valid equivocation reports are // processed. - R: ReportOffence, + R: ReportOffence, // Key type to use when signing equivocation report transactions, must be // convertible to and from an account id since that's what we need to use // to sign transactions. K: RuntimeAppPublic + IdentifyAccount, { - type KeyOwnerProof = P::Proof; - type KeyOwnerIdentification = P::IdentificationTuple; type Offence = O; - fn check_proof( - equivocation_proof: &EquivocationProof, - key_owner_proof: Self::KeyOwnerProof, - ) -> Option { - let offender = P::check_proof( - (GRANDPA, equivocation_proof.offender().clone()), - key_owner_proof, - )?; - - Some(offender) - } - fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { R::report_offence(reporters, offence) } fn submit_equivocation_report( equivocation_proof: EquivocationProof, - key_owner_proof: Self::KeyOwnerProof, + key_owner_proof: T::KeyOwnerProof, ) -> DispatchResult { - let call = - super::Call::report_equivocation(equivocation_proof.clone(), key_owner_proof.encode()); + let call = super::Call::report_equivocation(equivocation_proof, key_owner_proof); let res = S::submit_signed_from(call, K::all().into_iter().map(|k| k.into_account())); diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index e6042e6fa3aa5..6bd4835845bc6 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -39,13 +39,14 @@ use fg_primitives::{ GRANDPA_ENGINE_ID, }; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, storage, weights::SimpleDispatchInfo, + decl_error, decl_event, decl_module, decl_storage, storage, traits::KeyOwnerProofSystem, + weights::SimpleDispatchInfo, Parameter, }; use frame_system::{self as system, ensure_signed, DigestOf}; use sp_runtime::{ generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, - DispatchResult, + DispatchResult, KeyTypeId, }; use sp_staking::SessionIndex; @@ -54,7 +55,8 @@ mod mock; mod tests; pub use equivocation::{ - EquivocationHandler, GrandpaEquivocationOffence, GrandpaTimeSlot, GrandpaOffence + EquivocationHandler, GetSessionNumber, GetValidatorCount, GrandpaOffence, GrandpaTimeSlot, + HandleEquivocation, }; pub trait Trait: frame_system::Trait { @@ -64,9 +66,26 @@ pub trait Trait: frame_system::Trait { /// The function call. type Call: From>; - /// The equivocation handling subsystem, equivocation report validation and - /// offence reporting will be defined based on this type. - type HandleEquivocation: equivocation::HandleEquivocation; + /// The proof of key ownership, used for validating equivocation reports. + /// The proof must include the session index and validator count of the + /// session at which the equivocation occurred. + type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount; + + /// The identification of a key owner, used when reporting equivocations. + type KeyOwnerIdentification: Parameter; + + /// A system for proving ownership of keys, i.e. that a given key was part + /// of a validator set, needed for validating equivocation reports. + type KeyOwnerProofSystem: KeyOwnerProofSystem< + (KeyTypeId, AuthorityId), + Proof = Self::KeyOwnerProof, + IdentificationTuple = Self::KeyOwnerIdentification, + >; + + /// The equivocation handling subsystem, defines methods to report an + /// offence (after the equivocation has been validated) and for submitting a + /// transaction to report an equivocation (from an offchain context). + type HandleEquivocation: HandleEquivocation; } /// A stored pending change, old format. @@ -216,21 +235,10 @@ decl_module! { fn report_equivocation( origin, equivocation_proof: EquivocationProof, - // key_owner_proof: >::KeyOwnerProof, - key_owner_proof: Vec, + key_owner_proof: T::KeyOwnerProof, ) { - use equivocation::{ - GetSessionNumber, GetValidatorCount, GrandpaOffence, HandleEquivocation, - }; - let reporter_id = ensure_signed(origin)?; - // FIXME: this is a hack needed because the typed argument version above fails to - // compile (due to missing codec implementation) - let key_owner_proof: >::KeyOwnerProof = - Decode::decode(&mut &key_owner_proof[..]) - .map_err(|_| "Key owner proof decoding failed.")?; - let (session_index, validator_set_count) = ( key_owner_proof.session(), key_owner_proof.validator_count(), @@ -239,8 +247,8 @@ decl_module! { // validate the membership proof and extract session index and // validator set count of the session that we're proving membership of let offender = - T::HandleEquivocation::check_proof( - &equivocation_proof, + T::KeyOwnerProofSystem::check_proof( + (fg_primitives::KEY_TYPE, equivocation_proof.offender().clone()), key_owner_proof, ).ok_or("Invalid/outdated key ownership proof.")?; @@ -283,7 +291,7 @@ decl_module! { // report to the offences module rewarding the sender. T::HandleEquivocation::report_offence( vec![reporter_id], - >::Offence::new( + >::Offence::new( session_index, validator_set_count, offender, @@ -470,10 +478,8 @@ impl Module { /// Only useful in an offchain context. pub fn submit_report_equivocation_extrinsic( equivocation_proof: EquivocationProof, - key_owner_proof: >::KeyOwnerProof, + key_owner_proof: T::KeyOwnerProof, ) -> Option<()> { - use equivocation::HandleEquivocation; - T::HandleEquivocation::submit_equivocation_report(equivocation_proof, key_owner_proof) .ok()?; From 7658f31766e4706c5af01353a473a429dbb28a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 8 Apr 2020 19:52:26 +0100 Subject: [PATCH 54/78] test-utils: fix runtime compilation --- test-utils/runtime/src/lib.rs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 05d8d5a76242b..7888c118096e9 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -34,7 +34,7 @@ use sp_trie::trie_types::{TrieDB, TrieDBMut}; use sp_api::{decl_runtime_apis, impl_runtime_apis}; use sp_runtime::{ create_runtime_str, impl_opaque_keys, - ApplyExtrinsicResult, KeyTypeId, Perbill, + ApplyExtrinsicResult, Perbill, transaction_validity::{ TransactionValidity, ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionSource, @@ -655,14 +655,6 @@ cfg_if! { } } - impl sp_session::SessionMembership for Runtime { - fn generate_session_membership_proof( - _session_key: (KeyTypeId, Vec), - ) -> Option { - None - } - } - impl sp_finality_grandpa::GrandpaApi for Runtime { fn grandpa_authorities() -> sp_finality_grandpa::AuthorityList { Vec::new() @@ -677,6 +669,12 @@ cfg_if! { ) -> Option<()> { None } + + fn generate_key_ownership_proof( + _authority_key: sp_finality_grandpa::AuthorityId, + ) -> Option> { + None + } } impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { @@ -873,14 +871,6 @@ cfg_if! { } } - impl sp_session::SessionMembership for Runtime { - fn generate_session_membership_proof( - _session_key: (KeyTypeId, Vec), - ) -> Option { - None - } - } - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(_account: AccountId) -> Index { 0 From 97d8fb82f3773be35ddcfef6de251c923be600ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 8 Apr 2020 20:07:34 +0100 Subject: [PATCH 55/78] grandpa: fix test compilation --- client/finality-grandpa/src/tests.rs | 26 +++++++++++++------------- frame/grandpa/src/mock.rs | 18 ++++++++++++++++-- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 35a592d863250..aa13dd230e9b7 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -41,7 +41,7 @@ use parity_scale_codec::Decode; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, HashFor}; use sp_runtime::generic::{BlockId, DigestItem}; use sp_core::{H256, crypto::Public}; -use sp_finality_grandpa::{GRANDPA_ENGINE_ID, AuthorityList, GrandpaApi}; +use sp_finality_grandpa::{GRANDPA_ENGINE_ID, AuthorityList, EquivocationProof, GrandpaApi}; use sp_state_machine::{InMemoryBackend, prove_read, read_proof_check}; use authorities::AuthoritySet; @@ -217,19 +217,19 @@ sp_api::mock_impl_runtime_apis! { fn grandpa_authorities(&self) -> AuthorityList { self.inner.genesis_authorities.clone() } - } - fn GrandpaApi_submit_report_equivocation_extrinsic_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<( - sp_finality_grandpa::EquivocationProof<::Hash, NumberFor>, - Vec, - )>, - _: Vec, - ) -> Result>> { - Ok(NativeOrEncoded::Native(None)) + fn submit_report_equivocation_extrinsic( + _equivocation_proof: EquivocationProof, + _key_owner_proof: Vec, + ) -> Option<()> { + None + } + + fn generate_key_ownership_proof( + _authority_key: AuthorityId, + ) -> Option> { + None + } } } diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index cc0a1390df8a7..cad33450a97c4 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -20,8 +20,11 @@ use sp_runtime::{Perbill, DigestItem, traits::IdentityLookup, testing::{Header, UintAuthorityId}}; use sp_io; -use frame_support::{impl_outer_origin, impl_outer_event, parameter_types, weights::Weight}; -use sp_core::H256; +use frame_support::{ + impl_outer_origin, impl_outer_event, parameter_types, + traits::KeyOwnerProofSystem, weights::Weight, +}; +use sp_core::{crypto::KeyTypeId, H256}; use codec::{Encode, Decode}; use crate::{AuthorityId, AuthorityList, Call, GenesisConfig, Trait, Module, ConsensusLog}; use sp_finality_grandpa::GRANDPA_ENGINE_ID; @@ -42,6 +45,17 @@ pub struct Test; impl Trait for Test { type Event = TestEvent; type Call = Call; + + type KeyOwnerProofSystem = (); + + type KeyOwnerProof = + )>>::Proof; + + type KeyOwnerIdentification = , + )>>::IdentificationTuple; + type HandleEquivocation = (); } parameter_types! { From 712c78d4cb60faf85520bd4378bcb7e8d189e993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 8 Apr 2020 20:18:59 +0100 Subject: [PATCH 56/78] grandpa: fix test compilation after merge --- client/finality-grandpa/src/authorities.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 1880f55d7d252..119471c0dc354 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -996,7 +996,7 @@ mod tests { // we apply the change at A0 which should prune it and the fork at B authorities - .apply_standard_changes("hash_a0", 5, &is_descendent_of) + .apply_standard_changes("hash_a0", 5, &is_descendent_of, false) .unwrap(); // the next change is now at A1 (#10) From ae2f6bf4111c5caee3fb2a1d06932733c51a1b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 8 Apr 2020 20:33:56 +0100 Subject: [PATCH 57/78] grandpa: simplify transaction submission types --- bin/node/primitives/src/lib.rs | 14 ++------------ bin/node/runtime/src/lib.rs | 1 - frame/grandpa/src/equivocation.rs | 18 ++++++------------ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index 914b9e36956e6..5cd8c4c02490c 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -64,9 +64,8 @@ pub type Block = generic::Block; pub type BlockId = generic::BlockId; /// App-specific crypto used for reporting equivocation/misbehavior in BABE and -/// GRANDPA. The crypto used is sr25519 and the account must be minimally funded -/// in order to pay for transaction fees. Any rewards for misbehavior reporting -/// will be paid out to this account. +/// GRANDPA. Any rewards for misbehavior reporting will be paid out to this +/// account. pub mod report { use sp_core::crypto::KeyTypeId; @@ -76,16 +75,7 @@ pub mod report { mod app { use sp_application_crypto::{app_crypto, sr25519}; - app_crypto!(sr25519, super::KEY_TYPE); - - impl sp_runtime::traits::IdentifyAccount for Public { - type AccountId = sp_runtime::AccountId32; - - fn into_account(self) -> Self::AccountId { - sp_runtime::MultiSigner::from(self.0).into_account() - } - } } /// Identity of the equivocation/misbehavior reporter. diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e409e92c95664..ff7b5f804fb4a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -586,7 +586,6 @@ impl pallet_grandpa::Trait for Runtime { Self::KeyOwnerIdentification, TransactionSubmitterOf, Offences, - node_primitives::report::ReporterId, >; } diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 8e09aeb9f6d28..e7661c801f272 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -34,9 +34,8 @@ use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; use frame_system::offchain::SubmitSignedTransaction; -use sp_application_crypto::RuntimeAppPublic; use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; -use sp_runtime::{traits::IdentifyAccount, DispatchResult, Perbill}; +use sp_runtime::{DispatchResult, Perbill}; use sp_staking::{ offence::{Kind, Offence, OffenceError, ReportOffence}, SessionIndex, @@ -122,11 +121,11 @@ impl GetValidatorCount for sp_session::MembershipProof { /// using existing subsystems that are part of frame (type bounds described /// below) and will dispatch to them directly, it's only purpose is to wire all /// subsystems together. -pub struct EquivocationHandler> { - _phantom: sp_std::marker::PhantomData<(I, S, O, R, K)>, +pub struct EquivocationHandler> { + _phantom: sp_std::marker::PhantomData<(I, S, R, O)>, } -impl Default for EquivocationHandler { +impl Default for EquivocationHandler { fn default() -> Self { Self { _phantom: Default::default(), @@ -134,8 +133,7 @@ impl Default for EquivocationHandler { } } -impl HandleEquivocation - for EquivocationHandler +impl HandleEquivocation for EquivocationHandler where T: super::Trait, // A transaction submitter. Used for submitting equivocation reports. @@ -145,10 +143,6 @@ where // A system for reporting offences after valid equivocation reports are // processed. R: ReportOffence, - // Key type to use when signing equivocation report transactions, must be - // convertible to and from an account id since that's what we need to use - // to sign transactions. - K: RuntimeAppPublic + IdentifyAccount, { type Offence = O; @@ -162,7 +156,7 @@ where ) -> DispatchResult { let call = super::Call::report_equivocation(equivocation_proof, key_owner_proof); - let res = S::submit_signed_from(call, K::all().into_iter().map(|k| k.into_account())); + let res = S::submit_signed(call); if res.iter().any(|(_, r)| r.is_ok()) { Ok(()) From b6ef6e3c4cb98ddb5127b3856707923e26ebb024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 9 Apr 2020 00:09:17 +0100 Subject: [PATCH 58/78] grandpa: validate equivocation report in signed extension --- frame/grandpa/src/equivocation.rs | 205 ++++++++++++++++++++++++------ frame/grandpa/src/lib.rs | 46 +------ 2 files changed, 171 insertions(+), 80 deletions(-) diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index e7661c801f272..8eea143d9be83 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -33,20 +33,143 @@ use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; +use frame_support::{dispatch::IsSubType, traits::KeyOwnerProofSystem}; use frame_system::offchain::SubmitSignedTransaction; use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; -use sp_runtime::{DispatchResult, Perbill}; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::{ + InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, + }, + DispatchResult, Perbill, +}; use sp_staking::{ offence::{Kind, Offence, OffenceError, ReportOffence}, SessionIndex, }; +/// Ensure that equivocation reports are only processed if valid. +#[derive(Encode, Decode, Clone, Eq, PartialEq)] +pub struct ValidateEquivocationReport(sp_std::marker::PhantomData); + +impl sp_std::fmt::Debug for ValidateEquivocationReport { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "ValidateEquivocationReport") + } +} + +/// Custom validity error used when validating equivocation reports. +#[derive(Debug)] +#[repr(u8)] +pub enum ReportEquivocationValidityError { + /// The proof provided in the report is not valid. + InvalidEquivocationProof = 1, + /// The proof provided in the report is not valid. + InvalidKeyOwnershipProof = 2, + /// The set id provided in the report is not valid. + InvalidSetId = 3, + /// The session index provided in the report is not valid. + InvalidSession = 4, +} + +impl From for TransactionValidityError { + fn from(e: ReportEquivocationValidityError) -> TransactionValidityError { + TransactionValidityError::from(InvalidTransaction::Custom(e as u8)) + } +} + +impl SignedExtension for ValidateEquivocationReport +where + ::Call: IsSubType, T>, +{ + const IDENTIFIER: &'static str = "ValidateEquivocationReport"; + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed( + &self, + ) -> sp_std::result::Result { + Ok(()) + } + + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + let (equivocation_proof, key_owner_proof) = match call.is_sub_type() { + Some(super::Call::report_equivocation(equivocation_proof, key_owner_proof)) => { + (equivocation_proof, key_owner_proof) + } + _ => return Ok(ValidTransaction::default()), + }; + + // validate the key ownership proof extracting the id of the offender. + if let None = T::KeyOwnerProofSystem::check_proof( + ( + sp_finality_grandpa::KEY_TYPE, + equivocation_proof.offender().clone(), + ), + key_owner_proof.clone(), + ) { + return Err(ReportEquivocationValidityError::InvalidKeyOwnershipProof.into()); + } + + // we check the equivocation within the context of its set id (and + // associated session). + let set_id = equivocation_proof.set_id(); + let session_index = key_owner_proof.session(); + + // validate equivocation proof (check votes are different and + // signatures are valid). + if let Err(_) = sp_finality_grandpa::check_equivocation_proof(equivocation_proof.clone()) { + return Err(ReportEquivocationValidityError::InvalidEquivocationProof.into()); + } + + // fetch the current and previous sets last session index. on the + // genesis set there's no previous set. + let previous_set_id_session_index = if set_id == 0 { + None + } else { + let session_index = + if let Some(session_id) = >::session_for_set(set_id - 1) { + session_id + } else { + return Err(ReportEquivocationValidityError::InvalidSetId.into()); + }; + + Some(session_index) + }; + + let set_id_session_index = + if let Some(session_id) = >::session_for_set(set_id) { + session_id + } else { + return Err(ReportEquivocationValidityError::InvalidSetId.into()); + }; + + // check that the session id for the membership proof is within the + // bounds of the set id reported in the equivocation. + if session_index > set_id_session_index || + previous_set_id_session_index + .map(|previous_index| session_index <= previous_index) + .unwrap_or(false) + { + return Err(ReportEquivocationValidityError::InvalidSession.into()); + } + + Ok(ValidTransaction::default()) + } +} + /// A trait with utility methods for handling equivocation reports in GRANDPA. -/// The key owner proof and offence types are generic, and the trait provides -/// methods to check an equivocation proof (i.e. the equivocation itself and the -/// key ownership proof), reporting an offence triggered by a valid equivocation -/// report, and also for creating and submitting equivocation report extrinsics -/// (useful only in offchain context). +/// The offence type is generic, and the trait provides , reporting an offence +/// triggered by a valid equivocation report, and also for creating and +/// submitting equivocation report extrinsics (useful only in offchain context). pub trait HandleEquivocation { /// The offence type used for reporting offences on valid equivocation reports. type Offence: GrandpaOffence; @@ -82,41 +205,6 @@ impl HandleEquivocation for () { } } -/// A trait to get a session number the `MembershipProof` belongs to. -pub trait GetSessionNumber { - fn session(&self) -> SessionIndex; -} - -/// A trait to get the validator count at the session the `MembershipProof` -/// belongs to. -pub trait GetValidatorCount { - fn validator_count(&self) -> sp_session::ValidatorCount; -} - -impl GetSessionNumber for frame_support::Void { - fn session(&self) -> SessionIndex { - Default::default() - } -} - -impl GetValidatorCount for frame_support::Void { - fn validator_count(&self) -> sp_session::ValidatorCount { - Default::default() - } -} - -impl GetSessionNumber for sp_session::MembershipProof { - fn session(&self) -> SessionIndex { - self.session() - } -} - -impl GetValidatorCount for sp_session::MembershipProof { - fn validator_count(&self) -> sp_session::ValidatorCount { - self.validator_count() - } -} - /// Generic equivocation handler. This type implements `HandleEquivocation` /// using existing subsystems that are part of frame (type bounds described /// below) and will dispatch to them directly, it's only purpose is to wire all @@ -249,3 +337,38 @@ impl Offence x.square() } } + +/// A trait to get a session number the `MembershipProof` belongs to. +pub trait GetSessionNumber { + fn session(&self) -> SessionIndex; +} + +/// A trait to get the validator count at the session the `MembershipProof` +/// belongs to. +pub trait GetValidatorCount { + fn validator_count(&self) -> sp_session::ValidatorCount; +} + +impl GetSessionNumber for frame_support::Void { + fn session(&self) -> SessionIndex { + Default::default() + } +} + +impl GetValidatorCount for frame_support::Void { + fn validator_count(&self) -> sp_session::ValidatorCount { + Default::default() + } +} + +impl GetSessionNumber for sp_session::MembershipProof { + fn session(&self) -> SessionIndex { + self.session() + } +} + +impl GetValidatorCount for sp_session::MembershipProof { + fn validator_count(&self) -> sp_session::ValidatorCount { + self.validator_count() + } +} diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 6bd4835845bc6..204f6887a5ebe 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -229,9 +229,9 @@ decl_module! { /// against the extracted offender. If both are valid, the offence /// will be reported. /// - /// FIXME: I have no clue about the weight (but we're checking two - /// ed25519 signatures). - #[weight = SimpleDispatchInfo::FixedOperational(10_000_000)] + /// Since the weight is 0 in order to avoid DoS pre-validation is implemented in a + /// `SignedExtension`. + #[weight = SimpleDispatchInfo::FixedNormal(0)] fn report_equivocation( origin, equivocation_proof: EquivocationProof, @@ -244,50 +244,18 @@ decl_module! { key_owner_proof.validator_count(), ); - // validate the membership proof and extract session index and - // validator set count of the session that we're proving membership of + // we have already checked this proof in `SignedExtension`, we to + // check it again to get the full identification of the offender. let offender = T::KeyOwnerProofSystem::check_proof( (fg_primitives::KEY_TYPE, equivocation_proof.offender().clone()), key_owner_proof, - ).ok_or("Invalid/outdated key ownership proof.")?; + ).ok_or("Invalid key ownership proof.")?; - // we check the equivocation within the context of its set id (and - // associated session). + // the set id and round when the offence happened let set_id = equivocation_proof.set_id(); - - // the GRANDPA round when the offence happened let round = equivocation_proof.round(); - // validate equivocation proof (check votes are different and - // signatures are valid). - fg_primitives::check_equivocation_proof(equivocation_proof) - .map_err(|_| "Invalid equivocation proof.")?; - - // fetch the current and previous sets last session index. on the - // genesis set there's no previous set. - let previous_set_id_session_index = if set_id == 0 { - None - } else { - let session_index = SetIdSession::get(set_id - 1) - .ok_or("Invalid equivocation set id.")?; - - Some(session_index) - }; - - let set_id_session_index = SetIdSession::get(set_id) - .ok_or("Invalid equivocation set id.")?; - - // check that the session id for the membership proof is within the - // bounds of the set id reported in the equivocation. - if session_index > set_id_session_index || - previous_set_id_session_index - .map(|previous_index| session_index <= previous_index) - .unwrap_or(false) - { - return Err("Invalid equivocation set id provided.".into()); - } - // report to the offences module rewarding the sender. T::HandleEquivocation::report_offence( vec![reporter_id], From 7d3cfd7c69258733e82f17d6fe6d2e7a06131b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 9 Apr 2020 00:12:28 +0100 Subject: [PATCH 59/78] client: fix test --- client/rpc/src/state/tests.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 4cf374549daeb..4a9b701959c8c 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -440,8 +440,7 @@ fn should_return_runtime_version() { \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",2],\ [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",2],[\"0x40fe3ad401f8959a\",4],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",1],\ - [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xdd8e34c0eb9b2e5a\",1],\ - [\"0xbc9d89904f5b923f\",1]]}"; + [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]]}"; let runtime_version = api.runtime_version(None.into()).wait().unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); From a15f4837a5ac8adc2920009a2f4e97a3773bfd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 9 Apr 2020 00:28:36 +0100 Subject: [PATCH 60/78] node: use ValidateEquivocationReport signed extension --- bin/node/cli/src/factory_impl.rs | 3 ++- bin/node/cli/src/service.rs | 3 ++- bin/node/runtime/src/lib.rs | 2 ++ bin/node/testing/src/keyring.rs | 1 + bin/utils/subkey/src/main.rs | 2 ++ frame/grandpa/src/equivocation.rs | 6 ++++++ frame/grandpa/src/lib.rs | 2 +- 7 files changed, 16 insertions(+), 3 deletions(-) diff --git a/bin/node/cli/src/factory_impl.rs b/bin/node/cli/src/factory_impl.rs index cd7e3022e0b9a..03fd1a72e7d8b 100644 --- a/bin/node/cli/src/factory_impl.rs +++ b/bin/node/cli/src/factory_impl.rs @@ -59,6 +59,7 @@ impl FactoryState { pallet_transaction_payment::ChargeTransactionPayment::from(0), Default::default(), Default::default(), + Default::default(), ) } } @@ -123,7 +124,7 @@ impl RuntimeAdapter for FactoryState { (*amount).into() ) ) - }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), (), ())) + }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), (), (), ())) } fn inherent_extrinsics(&self) -> InherentData { diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 92d62364a27f9..432ca8073f36a 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -618,11 +618,12 @@ mod tests { payment, Default::default(), Default::default(), + Default::default(), ); let raw_payload = SignedPayload::from_raw( function, extra, - (version, genesis_hash, genesis_hash, (), (), (), (), ()) + (version, genesis_hash, genesis_hash, (), (), (), (), (), ()) ); let signature = raw_payload.using_encoded(|payload| { signer.sign(payload) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ff7b5f804fb4a..4f574d1bbbcae 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -112,6 +112,7 @@ impl frame_system::offchain::CreateTransaction for pallet_transaction_payment::ChargeTransactionPayment::::from(tip), Default::default(), Default::default(), + Default::default(), ); let raw_payload = SignedPayload::new(call, extra).map_err(|e| { debug::warn!("Unable to create signed payload: {:?}", e); @@ -734,6 +735,7 @@ pub type SignedExtra = ( pallet_transaction_payment::ChargeTransactionPayment, pallet_contracts::CheckBlockGasLimit, pallet_staking::LockStakingStatus, + pallet_grandpa::ValidateEquivocationReport, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 5fa1e48b03218..5156862a38a0a 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -76,6 +76,7 @@ pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra { pallet_transaction_payment::ChargeTransactionPayment::from(extra_fee), Default::default(), Default::default(), + Default::default(), ) } diff --git a/bin/utils/subkey/src/main.rs b/bin/utils/subkey/src/main.rs index 237cc68df2f46..475dee7484434 100644 --- a/bin/utils/subkey/src/main.rs +++ b/bin/utils/subkey/src/main.rs @@ -691,6 +691,7 @@ fn create_extrinsic( pallet_transaction_payment::ChargeTransactionPayment::::from(f), Default::default(), Default::default(), + Default::default(), ) }; let raw_payload = SignedPayload::from_raw( @@ -705,6 +706,7 @@ fn create_extrinsic( (), (), (), + (), ), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)).into_runtime(); diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 8eea143d9be83..789eba2a87f73 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -52,6 +52,12 @@ use sp_staking::{ #[derive(Encode, Decode, Clone, Eq, PartialEq)] pub struct ValidateEquivocationReport(sp_std::marker::PhantomData); +impl Default for ValidateEquivocationReport { + fn default() -> ValidateEquivocationReport { + ValidateEquivocationReport(Default::default()) + } +} + impl sp_std::fmt::Debug for ValidateEquivocationReport { fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { write!(f, "ValidateEquivocationReport") diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 204f6887a5ebe..8fd852b099bf6 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -56,7 +56,7 @@ mod tests; pub use equivocation::{ EquivocationHandler, GetSessionNumber, GetValidatorCount, GrandpaOffence, GrandpaTimeSlot, - HandleEquivocation, + HandleEquivocation, ValidateEquivocationReport, }; pub trait Trait: frame_system::Trait { From dca1a80e8835b1e949c9040e000e31cfe0b988b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 9 Apr 2020 00:58:44 +0100 Subject: [PATCH 61/78] grandpa: expose key ownership proof under opaque type --- Cargo.lock | 1 - bin/node-template/runtime/src/lib.rs | 4 ++-- bin/node/runtime/src/lib.rs | 7 ++++--- client/finality-grandpa/Cargo.toml | 1 - client/finality-grandpa/src/tests.rs | 6 +++--- primitives/finality-grandpa/src/lib.rs | 27 ++++++++++++++++++++++++-- test-utils/runtime/src/lib.rs | 4 ++-- 7 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d50801804cf3..16e8a65944c88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6336,7 +6336,6 @@ dependencies = [ "sp-inherents", "sp-keyring", "sp-runtime", - "sp-session", "sp-state-machine", "sp-utils", "substrate-prometheus-endpoint", diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index c78c7fcd69db7..d93e351f22079 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -377,14 +377,14 @@ impl_runtime_apis! { ::Hash, NumberFor, >, - _key_owner_proof: Vec, + _key_owner_proof: fg_primitives::OpaqueKeyOwnershipProof, ) -> Option<()> { None } fn generate_key_ownership_proof( _authority_key: fg_primitives::AuthorityId, - ) -> Option> { + ) -> Option { // NOTE: this is the only implementation possible since we've // defined our key owner proof type as a bottom type (i.e. a type // with no values). diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 4f574d1bbbcae..9930f33be03de 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -814,9 +814,9 @@ impl_runtime_apis! { ::Hash, NumberFor, >, - key_owner_proof: Vec, + key_owner_proof: fg_primitives::OpaqueKeyOwnershipProof, ) -> Option<()> { - let key_owner_proof = codec::Decode::decode(&mut &key_owner_proof[..]).ok()?; + let key_owner_proof = key_owner_proof.decode()?; Grandpa::submit_report_equivocation_extrinsic( equivocation_proof, @@ -826,11 +826,12 @@ impl_runtime_apis! { fn generate_key_ownership_proof( session_key: fg_primitives::AuthorityId, - ) -> Option> { + ) -> Option { use codec::Encode; Historical::prove((fg_primitives::KEY_TYPE, session_key)) .map(|p| p.encode()) + .map(fg_primitives::OpaqueKeyOwnershipProof::new) } } diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 6b5aa6ac96adb..b684c814d1b93 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -21,7 +21,6 @@ assert_matches = "1.3.0" parity-scale-codec = { version = "1.3.0", features = ["derive"] } sp-arithmetic = { version = "2.0.0-alpha.5", path = "../../primitives/arithmetic" } sp-runtime = { version = "2.0.0-alpha.5", path = "../../primitives/runtime" } -sp-session = { version = "2.0.0-alpha.5", path = "../../primitives/session" } sp-utils = { version = "2.0.0-alpha.5", path = "../../primitives/utils" } sp-consensus = { version = "0.8.0-alpha.5", path = "../../primitives/consensus/common" } sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 7d3039d443f4d..e16287c3999e0 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -41,7 +41,7 @@ use parity_scale_codec::Decode; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, HashFor}; use sp_runtime::generic::{BlockId, DigestItem}; use sp_core::{H256, crypto::Public}; -use sp_finality_grandpa::{GRANDPA_ENGINE_ID, AuthorityList, EquivocationProof, GrandpaApi}; +use sp_finality_grandpa::{GRANDPA_ENGINE_ID, AuthorityList, EquivocationProof, GrandpaApi, OpaqueKeyOwnershipProof}; use sp_state_machine::{InMemoryBackend, prove_read, read_proof_check}; use authorities::AuthoritySet; @@ -217,14 +217,14 @@ sp_api::mock_impl_runtime_apis! { fn submit_report_equivocation_extrinsic( _equivocation_proof: EquivocationProof, - _key_owner_proof: Vec, + _key_owner_proof: OpaqueKeyOwnershipProof, ) -> Option<()> { None } fn generate_key_ownership_proof( _authority_key: AuthorityId, - ) -> Option> { + ) -> Option { None } } diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 6c73e31a49c61..bf4f839d0c620 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -454,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); + +impl OpaqueKeyOwnershipProof { + /// Create a new `OpaqueKeyOwnershipProof` using the given encoded + /// representation. + pub fn new(inner: Vec) -> OpaqueKeyOwnershipProof { + OpaqueKeyOwnershipProof(inner) + } + + /// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key + /// ownership proof type. + pub fn decode(self) -> Option { + 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. @@ -483,7 +506,7 @@ sp_api::decl_runtime_apis! { #[skip_initialize_block] fn submit_report_equivocation_extrinsic( equivocation_proof: EquivocationProof>, - key_owner_proof: Vec, + key_owner_proof: OpaqueKeyOwnershipProof, ) -> Option<()>; /// Generates a proof that the given session key is a part of the @@ -492,6 +515,6 @@ sp_api::decl_runtime_apis! { /// for validating misbehavior reports. fn generate_key_ownership_proof( authority_key: AuthorityId, - ) -> Option>; + ) -> Option; } } diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index ae42895be8faa..f44c8f310f5d2 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -675,14 +675,14 @@ cfg_if! { ::Hash, NumberFor, >, - _key_owner_proof: Vec, + _key_owner_proof: sp_finality_grandpa::OpaqueKeyOwnershipProof, ) -> Option<()> { None } fn generate_key_ownership_proof( _authority_key: sp_finality_grandpa::AuthorityId, - ) -> Option> { + ) -> Option { None } } From f686072dc7b68bdf902826cf06bec98109870c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 9 Apr 2020 01:13:55 +0100 Subject: [PATCH 62/78] grandpa: better docs on key ownership proofs --- bin/node-template/runtime/src/lib.rs | 2 +- bin/node/runtime/src/lib.rs | 4 ++-- client/finality-grandpa/src/tests.rs | 2 +- primitives/finality-grandpa/src/lib.rs | 19 ++++++++++--------- test-utils/runtime/src/lib.rs | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index d93e351f22079..5b31b1e34128f 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -383,7 +383,7 @@ impl_runtime_apis! { } fn generate_key_ownership_proof( - _authority_key: fg_primitives::AuthorityId, + _authority_id: fg_primitives::AuthorityId, ) -> Option { // NOTE: this is the only implementation possible since we've // defined our key owner proof type as a bottom type (i.e. a type diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9930f33be03de..1b6ed3eef02c4 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -825,11 +825,11 @@ impl_runtime_apis! { } fn generate_key_ownership_proof( - session_key: fg_primitives::AuthorityId, + authority_id: fg_primitives::AuthorityId, ) -> Option { use codec::Encode; - Historical::prove((fg_primitives::KEY_TYPE, session_key)) + Historical::prove((fg_primitives::KEY_TYPE, authority_id)) .map(|p| p.encode()) .map(fg_primitives::OpaqueKeyOwnershipProof::new) } diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index e16287c3999e0..12c5fcda9cc95 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -223,7 +223,7 @@ sp_api::mock_impl_runtime_apis! { } fn generate_key_ownership_proof( - _authority_key: AuthorityId, + _authority_id: AuthorityId, ) -> Option { None } diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index bf4f839d0c620..448e4baffd49f 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -498,10 +498,10 @@ sp_api::decl_runtime_apis! { fn grandpa_authorities() -> AuthorityList; /// Submits an extrinsic to report an equivocation. The caller must - /// provide the equivocation proof and an encoded key ownership proof - /// (the key ownership proof is generic). This method will sign the - /// extrinsic with any reporting keys available in the keystore and will - /// push the transaction to the pool. + /// 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. #[skip_initialize_block] fn submit_report_equivocation_extrinsic( @@ -509,12 +509,13 @@ sp_api::decl_runtime_apis! { key_owner_proof: OpaqueKeyOwnershipProof, ) -> Option<()>; - /// Generates a proof that the given session key is a part of the - /// current session. The generated proof can later on be validated with - /// the historical session module. Proofs of membership are useful e.g. - /// for validating misbehavior reports. + /// Generates a proof of key ownership for the given authority. 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. fn generate_key_ownership_proof( - authority_key: AuthorityId, + authority_id: AuthorityId, ) -> Option; } } diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index f44c8f310f5d2..3f4014115a3fa 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -681,7 +681,7 @@ cfg_if! { } fn generate_key_ownership_proof( - _authority_key: sp_finality_grandpa::AuthorityId, + _authority_id: sp_finality_grandpa::AuthorityId, ) -> Option { None } From a91c2eb431a3ac210e9b1a1f23c9514498b6fa43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 9 Apr 2020 01:22:44 +0100 Subject: [PATCH 63/78] grandpa: add note about signed extension --- frame/grandpa/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 8fd852b099bf6..64e272844ad34 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -85,6 +85,10 @@ pub trait Trait: frame_system::Trait { /// The equivocation handling subsystem, defines methods to report an /// offence (after the equivocation has been validated) and for submitting a /// transaction to report an equivocation (from an offchain context). + /// NOTE: when enabling equivocation handling (i.e. this type isn't set to + /// `()`) you must add the `equivocation::ValidateEquivocationReport` signed + /// extension to the runtime's `SignedExtra` definition, otherwise + /// equivocation reports won't be properly validated. type HandleEquivocation: HandleEquivocation; } From ca43f21327ba78ceda6447a140c5ef38baf00e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Fri, 17 Apr 2020 22:52:31 +0100 Subject: [PATCH 64/78] grandpa: add ValidateEquivocationReport::new --- frame/grandpa/src/equivocation.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 789eba2a87f73..73049e03d1c56 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -54,6 +54,12 @@ pub struct ValidateEquivocationReport(sp_std::marker::PhantomData); impl Default for ValidateEquivocationReport { fn default() -> ValidateEquivocationReport { + ValidateEquivocationReport::new() + } +} + +impl ValidateEquivocationReport { + pub fn new() -> ValidateEquivocationReport { ValidateEquivocationReport(Default::default()) } } From 75ec8e893a79c3132381661919dd1af864b54de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 20 Apr 2020 21:28:57 +0100 Subject: [PATCH 65/78] grandpa: remove skip_initialize_block from runtime api --- primitives/finality-grandpa/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 448e4baffd49f..e81749e3c3d9d 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -503,7 +503,6 @@ sp_api::decl_runtime_apis! { /// 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. - #[skip_initialize_block] fn submit_report_equivocation_extrinsic( equivocation_proof: EquivocationProof>, key_owner_proof: OpaqueKeyOwnershipProof, From ea73e51ef865757ef25f8272e745169a3cb0be41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 27 Apr 2020 20:08:35 +0100 Subject: [PATCH 66/78] grandpa: use new offchain transaction submission API --- Cargo.lock | 1 + bin/node/primitives/Cargo.toml | 2 ++ bin/node/primitives/src/lib.rs | 12 ++++++++ bin/node/runtime/src/lib.rs | 20 ++++--------- frame/grandpa/src/equivocation.rs | 49 +++++++++++++++++++++---------- frame/grandpa/src/lib.rs | 12 -------- 6 files changed, 54 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0685e6b055f58..61319b4de8693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3524,6 +3524,7 @@ dependencies = [ name = "node-primitives" version = "2.0.0-dev" dependencies = [ + "frame-system", "parity-scale-codec", "pretty_assertions", "sp-application-crypto", diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index a125c1e573b23..e69b626b5420f 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -12,6 +12,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +frame-system = { version = "2.0.0-dev", default-features = false, path = "../../../frame/system" } sp-application-crypto = { version = "2.0.0-dev", default-features = false, path = "../../../primitives/application-crypto" } sp-core = { version = "2.0.0-dev", default-features = false, path = "../../../primitives/core" } sp-runtime = { version = "2.0.0-dev", default-features = false, path = "../../../primitives/runtime" } @@ -24,6 +25,7 @@ pretty_assertions = "0.6.1" default = ["std"] std = [ "codec/std", + "frame-system/std", "sp-application-crypto/std", "sp-core/std", "sp-runtime/std", diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index 5cd8c4c02490c..c6180b31f7e05 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -67,6 +67,8 @@ pub type BlockId = generic::BlockId; /// GRANDPA. Any rewards for misbehavior reporting will be paid out to this /// account. pub mod report { + use super::{Signature, Verify}; + use frame_system::offchain::AppCrypto; use sp_core::crypto::KeyTypeId; /// Key type for the reporting module. Used for reporting BABE and GRANDPA @@ -80,4 +82,14 @@ pub mod report { /// Identity of the equivocation/misbehavior reporter. pub type ReporterId = app::Public; + + /// An `AppCrypto` type to allow submitting signed transactions using the reporting + /// application key as signer. + pub struct ReporterAppCrypto; + + impl AppCrypto<::Signer, Signature> for ReporterAppCrypto { + type RuntimeAppPublic = ReporterId; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 36c810261a4c5..66ab069b31531 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -23,7 +23,6 @@ use sp_std::prelude::*; use frame_support::{ construct_runtime, parameter_types, debug, -// <<<<<<< HEAD weights::{Weight, RuntimeDbWeight}, traits::{Currency, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness, LockIdentifier}, }; @@ -31,10 +30,6 @@ use sp_core::{ crypto::KeyTypeId, u32_trait::{_1, _2, _3, _4}, OpaqueMetadata, -// ======= -// weights::{Weight, RuntimeDbWeight}, -// traits::{Currency, Randomness, OnUnbalanced, Imbalance, LockIdentifier}, -// >>>>>>> master }; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; @@ -58,10 +53,7 @@ use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use pallet_contracts_rpc_runtime_api::ContractExecResult; -// <<<<<<< HEAD use pallet_session::{historical as pallet_session_historical}; -// ======= -// >>>>>>> master use sp_inherents::{InherentData, CheckInherentsResult}; #[cfg(any(feature = "std", test))] @@ -596,12 +588,12 @@ impl pallet_grandpa::Trait for Runtime { Vec, )>>::IdentificationTuple; - type HandleEquivocation = (); - // type HandleEquivocation = pallet_grandpa::EquivocationHandler< - // Self::KeyOwnerIdentification, - // TransactionSubmitterOf, - // Offences, - // >; + type HandleEquivocation = pallet_grandpa::EquivocationHandler< + Self::KeyOwnerIdentification, + node_primitives::report::ReporterAppCrypto, + Runtime, + Offences, + >; } parameter_types! { diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 1112c38d8c9ec..ed7478dd58835 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -33,8 +33,8 @@ use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; -use frame_support::{dispatch::IsSubType, traits::KeyOwnerProofSystem}; -// use frame_system::offchain::SubmitSignedTransaction; +use frame_support::{debug, dispatch::IsSubType, traits::KeyOwnerProofSystem}; +use frame_system::offchain::{AppCrypto, CreateSignedTransaction, Signer}; use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; use sp_runtime::{ traits::{DispatchInfoOf, SignedExtension}, @@ -221,11 +221,11 @@ impl HandleEquivocation for () { /// using existing subsystems that are part of frame (type bounds described /// below) and will dispatch to them directly, it's only purpose is to wire all /// subsystems together. -pub struct EquivocationHandler> { - _phantom: sp_std::marker::PhantomData<(I, S, R, O)>, +pub struct EquivocationHandler> { + _phantom: sp_std::marker::PhantomData<(I, C, S, R, O)>, } -impl Default for EquivocationHandler { +impl Default for EquivocationHandler { fn default() -> Self { Self { _phantom: Default::default(), @@ -233,11 +233,13 @@ impl Default for EquivocationHandler { } } -impl HandleEquivocation for EquivocationHandler +impl HandleEquivocation + for EquivocationHandler where - T: super::Trait, - // A transaction submitter. Used for submitting equivocation reports. - // S: SubmitSignedTransaction::Call>, + // A signed transaction creator. Used for signing and submitting equivocation reports. + T: super::Trait + CreateSignedTransaction>, + // Application-specific crypto bindings. + C: AppCrypto, // The offence type that should be used when reporting. O: GrandpaOffence, // A system for reporting offences after valid equivocation reports are @@ -254,15 +256,30 @@ where equivocation_proof: EquivocationProof, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResult { - // let call = super::Call::report_equivocation(equivocation_proof, key_owner_proof); + use frame_system::offchain::SendSignedTransaction; - // let res = S::submit_signed(call); + let signer = Signer::::all_accounts(); + if !signer.can_sign() { + return Err( + "No local accounts available. Consider adding one via `author_insertKey` RPC.", + )?; + } + + let results = signer.send_signed_transaction(|_account| { + super::Call::report_equivocation(equivocation_proof.clone(), key_owner_proof.clone()) + }); + + for (acc, res) in &results { + match res { + Ok(()) => debug::info!("[{:?}] Submitted GRANDPA equivocation report.", acc.id), + Err(e) => debug::error!( + "[{:?}] Error submitting equivocation report: {:?}", + acc.id, + e + ), + } + } - // if res.iter().any(|(_, r)| r.is_ok()) { - // Ok(()) - // } else { - // Err("Error submitting equivocation report.".into()) - // } Ok(()) } } diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 84526470380f7..d418b13d2eae8 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -31,21 +31,9 @@ pub use sp_finality_grandpa as fg_primitives; use sp_std::prelude::*; -// <<<<<<< HEAD use codec::{self as codec, Decode, Encode}; pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; -// ======= -// use codec::{self as codec, Encode, Decode}; -// use frame_support::{decl_event, decl_storage, decl_module, decl_error, storage}; -// use sp_runtime::{ -// DispatchResult, generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, Perbill, -// }; -// use sp_staking::{ -// SessionIndex, -// offence::{Offence, Kind}, -// }; -// >>>>>>> master use fg_primitives::{ ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, GRANDPA_ENGINE_ID, From 4468c00fb44506a0fdba64f679d2e88818de4152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 27 Apr 2020 20:27:31 +0100 Subject: [PATCH 67/78] grandpa: take set_id in generate_key_ownership_proof --- bin/node-template/runtime/src/lib.rs | 1 + bin/node/runtime/src/lib.rs | 1 + client/finality-grandpa/src/environment.rs | 1 + client/finality-grandpa/src/tests.rs | 1 + primitives/finality-grandpa/src/lib.rs | 17 ++++++++++++----- test-utils/runtime/src/lib.rs | 1 + 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 8220ceca41921..df7cf9fa86a2d 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -398,6 +398,7 @@ impl_runtime_apis! { } fn generate_key_ownership_proof( + _set_id: fg_primitives::SetId, _authority_id: fg_primitives::AuthorityId, ) -> Option { // NOTE: this is the only implementation possible since we've diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 66ab069b31531..14cde3215af15 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -833,6 +833,7 @@ impl_runtime_apis! { } fn generate_key_ownership_proof( + _set_id: fg_primitives::SetId, authority_id: fg_primitives::AuthorityId, ) -> Option { use codec::Encode; diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index c67c9d65dae9d..b53ac5ca502f7 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -497,6 +497,7 @@ where .runtime_api() .generate_key_ownership_proof( &BlockId::Hash(current_set_latest_hash), + authority_set.set_id, equivocation.offender().clone(), ) .map_err(Error::Client)? diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 12c5fcda9cc95..e64d8f3349d04 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -223,6 +223,7 @@ sp_api::mock_impl_runtime_apis! { } fn generate_key_ownership_proof( + _set_id: SetId, _authority_id: AuthorityId, ) -> Option { None diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index e81749e3c3d9d..a84ce57c8d1f6 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -508,12 +508,19 @@ sp_api::decl_runtime_apis! { key_owner_proof: OpaqueKeyOwnershipProof, ) -> Option<()>; - /// Generates a proof of key ownership for the given authority. 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. + /// 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; } diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 2104569968270..1003a0c593d5d 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -688,6 +688,7 @@ cfg_if! { } fn generate_key_ownership_proof( + _set_id: sp_finality_grandpa::SetId, _authority_id: sp_finality_grandpa::AuthorityId, ) -> Option { None From 873fe1996d2aedf86f33e884aa24521923a7312a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 12:31:26 +0100 Subject: [PATCH 68/78] grandpa: update to finality-grandpa v0.12.2 --- Cargo.lock | 4 ++-- client/finality-grandpa/Cargo.toml | 4 ++-- client/finality-grandpa/rpc/Cargo.toml | 2 +- primitives/finality-grandpa/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 197a376d8f0d2..766af10cbbcaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1352,9 +1352,9 @@ dependencies = [ [[package]] name = "finality-grandpa" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab32971efbe776e46bfbc34d5b662d8e1de51fd14e26a2eba522c0f3470fc0f" +checksum = "1f4682570188cd105606e621b9992e580f717c15f8cd1b7d106b59f1c6e54680" dependencies = [ "either", "futures 0.3.4", diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 97dafc3d46b8b..9b5d787f889a8 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -42,11 +42,11 @@ sp-finality-tracker = { version = "2.0.0-dev", path = "../../primitives/finality sp-finality-grandpa = { version = "2.0.0-dev", path = "../../primitives/finality-grandpa" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.8.0-dev"} sc-block-builder = { version = "0.8.0-dev", path = "../block-builder" } -finality-grandpa = { version = "0.12.1", features = ["derive-codec"] } +finality-grandpa = { version = "0.12.2", features = ["derive-codec"] } pin-project = "0.4.6" [dev-dependencies] -finality-grandpa = { version = "0.12.1", features = ["derive-codec", "test-helpers"] } +finality-grandpa = { version = "0.12.2", features = ["derive-codec", "test-helpers"] } sc-network = { version = "0.8.0-dev", path = "../network" } sc-network-test = { version = "0.8.0-dev", path = "../network/test" } sp-keyring = { version = "2.0.0-dev", path = "../../primitives/keyring" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index f0c3580fe6e45..61636ff9f0305 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -8,7 +8,7 @@ license = "GPL-3.0" [dependencies] sc-finality-grandpa = { version = "0.8.0-dev", path = "../" } -finality-grandpa = { version = "0.12.1", features = ["derive-codec"] } +finality-grandpa = { version = "0.12.2", features = ["derive-codec"] } jsonrpc-core = "14.0.3" jsonrpc-core-client = "14.0.3" jsonrpc-derive = "14.0.3" diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index 914df16859420..378f715eccae9 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-application-crypto = { version = "2.0.0-dev", default-features = false, path = "../application-crypto" } codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.12.1", default-features = false, features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.12.2", default-features = false, features = ["derive-codec"] } log = { version = "0.4.8", optional = true } serde = { version = "1.0.101", optional = true, features = ["derive"] } sp-api = { version = "2.0.0-dev", default-features = false, path = "../api" } From 207e0dc9715b891dfc379e4acd9d140ec0337189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 12:46:32 +0100 Subject: [PATCH 69/78] grandpa: cleanup usages of AuthoritySet::current --- client/finality-grandpa/src/authorities.rs | 16 +++++++++------- client/finality-grandpa/src/aux_schema.rs | 6 +++--- client/finality-grandpa/src/environment.rs | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 0098e59c78d31..3c2ea14ff5723 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -100,15 +100,17 @@ pub(crate) struct Status { /// A set of authorities. #[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct AuthoritySet { + /// The current active authorities. pub(crate) current_authorities: AuthorityList, - set_id: u64, - // Tree of pending standard changes across forks. Standard changes are - // enacted on finality and must be enacted (i.e. finalized) in-order across - // a given branch + /// The current set id. + pub(crate) set_id: u64, + /// Tree of pending standard changes across forks. Standard changes are + /// enacted on finality and must be enacted (i.e. finalized) in-order across + /// a given branch pub(crate) pending_standard_changes: ForkTree>, - // Pending forced changes across different forks (at most one per fork). - // Forced changes are enacted on block depth (not finality), for this reason - // only one forced change should exist per fork. + /// Pending forced changes across different forks (at most one per fork). + /// Forced changes are enacted on block depth (not finality), for this reason + /// only one forced change should exist per fork. pending_forced_changes: Vec>, } diff --git a/client/finality-grandpa/src/aux_schema.rs b/client/finality-grandpa/src/aux_schema.rs index c217bc328a9a1..e4e8a980420d8 100644 --- a/client/finality-grandpa/src/aux_schema.rs +++ b/client/finality-grandpa/src/aux_schema.rs @@ -154,7 +154,7 @@ fn migrate_from_version0( None => (0, genesis_round()), }; - let set_id = new_set.current().0; + let set_id = new_set.set_id; let base = last_round_state.prevote_ghost .expect("state is for completed round; completed rounds must have a prevote ghost; qed."); @@ -201,7 +201,7 @@ fn migrate_from_version1( backend, AUTHORITY_SET_KEY, )? { - let set_id = set.current().0; + let set_id = set.set_id; let completed_rounds = |number, state, base| CompletedRounds::new( CompletedRound { @@ -312,7 +312,7 @@ pub(crate) fn load_persistent( .expect("state is for completed round; completed rounds must have a prevote ghost; qed."); VoterSetState::live( - set.current().0, + set.set_id, &set, base, ) diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index d7a87e40ca6cc..1db1bcbb8d4a5 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -127,7 +127,7 @@ impl CompletedRounds { let mut rounds = Vec::with_capacity(NUM_LAST_COMPLETED_ROUNDS); rounds.push(genesis); - let voters = voters.current().1.iter().map(|(a, _)| a.clone()).collect(); + let voters = voters.current_authorities.iter().map(|(a, _)| a.clone()).collect(); CompletedRounds { rounds, set_id, voters } } @@ -546,7 +546,7 @@ where // 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.inner().read().current().0 { + if self.set_id != self.authority_set.set_id() { return None; } From a2370613da18c66ff34f23120abf6e6083febe3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 16:04:35 +0100 Subject: [PATCH 70/78] grandpa: fix test --- client/finality-grandpa/src/authorities.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 3c2ea14ff5723..80c1f4ad3fe38 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -972,14 +972,16 @@ mod tests { #[test] fn next_change_works() { + let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]), 1)]; + let mut authorities = AuthoritySet { - current_authorities: Vec::new(), + current_authorities: current_authorities.clone(), set_id: 0, pending_standard_changes: ForkTree::new(), pending_forced_changes: Vec::new(), }; - let new_set = Vec::new(); + let new_set = current_authorities.clone(); // We have three pending changes with 2 possible roots that are enacted // immediately on finality (i.e. standard changes). From 769d1d54a8b71b61ba49fcc1b1b9e519b7d37657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 21:55:24 +0100 Subject: [PATCH 71/78] grandpa: add mocking utilities for equivocation reporting --- Cargo.lock | 7 + frame/grandpa/Cargo.toml | 9 +- frame/grandpa/src/mock.rs | 464 ++++++++++++++++++++++++++++++++++---- 3 files changed, 437 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28916b79bd56b..4ef041542e055 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4279,16 +4279,23 @@ dependencies = [ name = "pallet-grandpa" version = "2.0.0-dev" dependencies = [ + "finality-grandpa", "frame-support", "frame-system", + "pallet-balances", "pallet-finality-tracker", + "pallet-offences", "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "serde", "sp-application-crypto", "sp-core", "sp-finality-grandpa", "sp-io", + "sp-keyring", "sp-runtime", "sp-session", "sp-staking", diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index 289fd8401715b..649b4053d0536 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -27,7 +27,14 @@ pallet-session = { version = "2.0.0-dev", default-features = false, path = "../s pallet-finality-tracker = { version = "2.0.0-dev", default-features = false, path = "../finality-tracker" } [dev-dependencies] -sp-io ={ version = "2.0.0-dev", path = "../../primitives/io" } +grandpa = { package = "finality-grandpa", version = "0.12.2", features = ["derive-codec"] } +sp-io = { version = "2.0.0-dev", path = "../../primitives/io" } +sp-keyring = { version = "2.0.0-dev", path = "../../primitives/keyring" } +pallet-balances = { version = "2.0.0-dev", path = "../balances" } +pallet-offences = { version = "2.0.0-dev", path = "../offences" } +pallet-staking = { version = "2.0.0-dev", path = "../staking" } +pallet-staking-reward-curve = { version = "2.0.0-dev", path = "../staking/reward-curve" } +pallet-timestamp = { version = "2.0.0-dev", path = "../timestamp" } [features] default = ["std"] diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 3320291a75b2d..e303562f90810 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -18,57 +18,85 @@ #![cfg(test)] -use sp_runtime::{Perbill, DigestItem, traits::IdentityLookup, testing::{Header, UintAuthorityId}}; -use sp_io; +use crate::{ + equivocation::ValidateEquivocationReport, AuthorityId, AuthorityList, Call as GrandpaCall, + ConsensusLog, Module, Trait, +}; +use ::grandpa as finality_grandpa; +use codec::Encode; use frame_support::{ - impl_outer_origin, impl_outer_event, parameter_types, - traits::KeyOwnerProofSystem, weights::Weight, + impl_outer_dispatch, impl_outer_event, impl_outer_origin, parameter_types, + traits::{KeyOwnerProofSystem, OnFinalize, OnInitialize}, + weights::{DispatchInfo, Weight}, }; +use pallet_staking::EraIndex; use sp_core::{crypto::KeyTypeId, H256}; -use codec::{Encode, Decode}; -use crate::{AuthorityId, AuthorityList, Call, GenesisConfig, Trait, Module, ConsensusLog}; -use sp_finality_grandpa::GRANDPA_ENGINE_ID; +use sp_finality_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID}; +use sp_io; +use sp_keyring::Ed25519Keyring; +use sp_runtime::{ + curve::PiecewiseLinear, + impl_opaque_keys, + testing::{Header, TestXt, UintAuthorityId}, + traits::{ + Convert, Extrinsic as ExtrinsicT, Header as _, IdentityLookup, OpaqueKeys, + SaturatedConversion, SignedExtension, + }, + transaction_validity::TransactionValidityError, + DigestItem, Perbill, +}; +use sp_staking::SessionIndex; use frame_system as system; -impl_outer_origin!{ - pub enum Origin for Test where system = frame_system {} -} +use pallet_balances as balances; +use pallet_offences as offences; +use pallet_session as session; +use pallet_staking as staking; +use pallet_timestamp as timestamp; -pub fn grandpa_log(log: ConsensusLog) -> DigestItem { - DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()) +impl_outer_origin! { + pub enum Origin for Test {} } -// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. -#[derive(Clone, PartialEq, Eq, Debug, Decode, Encode)] -pub struct Test; - -impl Trait for Test { - type Event = TestEvent; - type Call = Call; +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + grandpa::Grandpa, + staking::Staking, + } +} - type KeyOwnerProofSystem = (); +impl_opaque_keys! { + pub struct TestSessionKeys { + pub grandpa_authority: super::Module, + } +} - type KeyOwnerProof = - )>>::Proof; +impl_outer_event! { + pub enum TestEvent for Test { + system, + balances, + grandpa, + offences, + session, + staking, + } +} - type KeyOwnerIdentification = , - )>>::IdentificationTuple; +#[derive(Clone, Eq, PartialEq)] +pub struct Test; - type HandleEquivocation = (); -} parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: Weight = 1024; pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } + impl frame_system::Trait for Test { type Origin = Origin; type Index = u64; type BlockNumber = u64; - type Call = (); + type Call = Call; type Hash = H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; @@ -84,20 +112,220 @@ impl frame_system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); - type AccountData = (); + type AccountData = balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); } +impl system::offchain::SendTransactionTypes for Test +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = TestXt; +} + +parameter_types! { + pub const Period: u64 = 1; + pub const Offset: u64 = 0; + pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); +} + +/// Custom `SessionHandler` since we use `TestSessionKeys` as `Keys`. +impl session::Trait for Test { + type Event = TestEvent; + type ValidatorId = u64; + type ValidatorIdOf = staking::StashOf; + type ShouldEndSession = session::PeriodicSessions; + type NextSessionRotation = session::PeriodicSessions; + type SessionManager = Staking; + type SessionHandler = ::KeyTypeIdProviders; + type Keys = TestSessionKeys; + type DisabledValidatorsThreshold = DisabledValidatorsThreshold; +} + +impl session::historical::Trait for Test { + type FullIdentification = staking::Exposure; + type FullIdentificationOf = staking::ExposureOf; +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl balances::Trait for Test { + type Balance = u128; + type DustRemoval = (); + type Event = TestEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 3; +} + +impl timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} + +pallet_staking_reward_curve::build! { + const REWARD_CURVE: PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000u64, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +parameter_types! { + pub const SessionsPerEra: SessionIndex = 3; + pub const BondingDuration: EraIndex = 3; + pub const SlashDeferDuration: EraIndex = 0; + pub const AttestationPeriod: u64 = 100; + pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; + pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const ElectionLookahead: u64 = 0; + pub const StakingUnsignedPriority: u64 = u64::max_value() / 2; +} + +pub struct CurrencyToVoteHandler; + +impl Convert for CurrencyToVoteHandler { + fn convert(x: u128) -> u128 { + x + } +} + +impl Convert for CurrencyToVoteHandler { + fn convert(x: u128) -> u64 { + x.saturated_into() + } +} + +impl staking::Trait for Test { + type RewardRemainder = (); + type CurrencyToVote = CurrencyToVoteHandler; + type Event = TestEvent; + type Currency = Balances; + type Slash = (); + type Reward = (); + type SessionsPerEra = SessionsPerEra; + type BondingDuration = BondingDuration; + type SlashDeferDuration = SlashDeferDuration; + type SlashCancelOrigin = system::EnsureRoot; + type SessionInterface = Self; + type UnixTime = timestamp::Module; + type RewardCurve = RewardCurve; + type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type NextNewSession = Session; + type ElectionLookahead = ElectionLookahead; + type Call = Call; + type UnsignedPriority = StakingUnsignedPriority; + type MaxIterations = (); +} + +impl offences::Trait for Test { + type Event = TestEvent; + type IdentificationTuple = session::historical::IdentificationTuple; + type OnOffenceHandler = Staking; +} + +impl Trait for Test { + type Event = TestEvent; + type Call = Call; + + type KeyOwnerProofSystem = Historical; + + type KeyOwnerProof = + )>>::Proof; + + type KeyOwnerIdentification = , + )>>::IdentificationTuple; + + type HandleEquivocation = super::EquivocationHandler< + Self::KeyOwnerIdentification, + reporting_keys::ReporterAppCrypto, + Test, + Offences, + >; +} + +pub mod reporting_keys { + use sp_core::crypto::KeyTypeId; + + pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); + + mod app { + use sp_application_crypto::{app_crypto, ed25519}; + app_crypto!(ed25519, super::KEY_TYPE); + + impl sp_runtime::traits::IdentifyAccount for Public { + type AccountId = u64; + fn into_account(self) -> Self::AccountId { + super::super::Grandpa::grandpa_authorities() + .iter() + .map(|(k, _)| k) + .position(|b| *b == self.0.clone().into()) + .unwrap() as u64 + } + } + } + + pub type ReporterId = app::Public; + + pub struct ReporterAppCrypto; + impl frame_system::offchain::AppCrypto + for ReporterAppCrypto + { + type RuntimeAppPublic = ReporterId; + type GenericSignature = sp_core::ed25519::Signature; + type GenericPublic = sp_core::ed25519::Public; + } +} + +type Extrinsic = TestXt; + +impl system::offchain::CreateSignedTransaction for Test +where + Call: From, +{ + fn create_transaction>( + call: Call, + _public: reporting_keys::ReporterId, + _account: ::AccountId, + nonce: ::Index, + ) -> Option<(Call, ::SignaturePayload)> { + Some((call, (nonce, ()))) + } +} + +impl frame_system::offchain::SigningTypes for Test { + type Public = reporting_keys::ReporterId; + type Signature = sp_core::ed25519::Signature; +} + mod grandpa { pub use crate::Event; } -impl_outer_event!{ - pub enum TestEvent for Test { - system, - grandpa, - } +pub type Balances = pallet_balances::Module; +pub type Historical = pallet_session::historical::Module; +pub type Offences = pallet_offences::Module; +pub type Session = pallet_session::Module; +pub type Staking = pallet_staking::Module; +pub type System = frame_system::Module; +pub type Timestamp = pallet_timestamp::Module; +pub type Grandpa = Module; + +pub fn grandpa_log(log: ConsensusLog) -> DigestItem { + DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()) } pub fn to_authorities(vec: Vec<(u64, u64)>) -> AuthorityList { @@ -106,13 +334,165 @@ pub fn to_authorities(vec: Vec<(u64, u64)>) -> AuthorityList { .collect() } -pub fn new_test_ext(authorities: Vec<(u64, u64)>) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - GenesisConfig { - authorities: to_authorities(authorities), - }.assimilate_storage::(&mut t).unwrap(); +pub fn extract_keyring(id: &AuthorityId) -> Ed25519Keyring { + let mut raw_public = [0; 32]; + raw_public.copy_from_slice(id.as_ref()); + Ed25519Keyring::from_raw_public(raw_public).unwrap() +} + +pub fn new_test_ext(vec: Vec<(u64, u64)>) -> sp_io::TestExternalities { + new_test_ext_raw_authorities(to_authorities(vec)) +} + +pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + // stashes are the index. + let session_keys: Vec<_> = authorities + .iter() + .enumerate() + .map(|(i, (k, _))| { + ( + i as u64, + i as u64, + TestSessionKeys { + grandpa_authority: AuthorityId::from(k.clone()), + }, + ) + }) + .collect(); + + // controllers are the index + 1000 + let stakers: Vec<_> = (0..authorities.len()) + .map(|i| { + ( + i as u64, + i as u64 + 1000, + 10_000, + staking::StakerStatus::::Validator, + ) + }) + .collect(); + + let balances: Vec<_> = (0..authorities.len()) + .map(|i| (i as u64, 10_000_000)) + .collect(); + + // NOTE: this will initialize the grandpa authorities + // through OneSessionHandler::on_genesis_session + session::GenesisConfig:: { keys: session_keys } + .assimilate_storage(&mut t) + .unwrap(); + + balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); + + let staking_config = staking::GenesisConfig:: { + stakers, + validator_count: 8, + force_era: staking::Forcing::ForceNew, + minimum_validator_count: 0, + invulnerables: vec![], + ..Default::default() + }; + + staking_config.assimilate_storage(&mut t).unwrap(); + t.into() } -pub type System = frame_system::Module; -pub type Grandpa = Module; +pub fn start_session(session_index: SessionIndex) { + let mut parent_hash = System::parent_hash(); + + for i in Session::current_index()..session_index { + println!("session index {}", i); + Staking::on_finalize(System::block_number()); + System::set_block_number((i + 1).into()); + Timestamp::set_timestamp(System::block_number() * 6000); + + // In order to be able to use `System::parent_hash()` in the tests + // we need to first get it via `System::finalize` and then set it + // the `System::initialize`. However, it is needed to be taken into + // consideration that finalizing will prune some data in `System` + // storage including old values `BlockHash` if that reaches above + // `BlockHashCount` capacity. + if System::block_number() > 1 { + let hdr = System::finalize(); + parent_hash = hdr.hash(); + } + + System::initialize( + &(i as u64 + 1), + &parent_hash, + &Default::default(), + &Default::default(), + Default::default(), + ); + + Session::on_initialize(System::block_number()); + System::on_initialize(System::block_number()); + } + + assert_eq!(Session::current_index(), session_index); +} + +pub fn start_era(era_index: EraIndex) { + start_session((era_index * 3).into()); + assert_eq!(Staking::current_era(), Some(era_index)); +} + +pub fn initialize_block(number: u64, parent_hash: H256) { + System::initialize( + &number, + &parent_hash, + &Default::default(), + &Default::default(), + Default::default(), + ); +} + +pub fn report_equivocation( + equivocation_proof: sp_finality_grandpa::EquivocationProof, + key_owner_proof: sp_session::MembershipProof, +) -> Result, TransactionValidityError> { + let inner = GrandpaCall::report_equivocation(equivocation_proof, key_owner_proof); + let call = Call::Grandpa(inner.clone()); + + ValidateEquivocationReport::::new().validate(&0, &call, &DispatchInfo::default(), 0)?; + + Ok(inner) +} + +pub fn generate_equivocation_proof( + set_id: SetId, + vote1: (RoundNumber, H256, u64, &Ed25519Keyring), + vote2: (RoundNumber, H256, u64, &Ed25519Keyring), +) -> sp_finality_grandpa::EquivocationProof { + let signed_prevote = |round, hash, number, keyring: &Ed25519Keyring| { + let prevote = finality_grandpa::Prevote { + target_hash: hash, + target_number: number, + }; + + let prevote_msg = finality_grandpa::Message::Prevote(prevote.clone()); + let payload = sp_finality_grandpa::localized_payload(round, set_id, &prevote_msg); + let signed = keyring.sign(&payload).into(); + (prevote, signed) + }; + + let (prevote1, signed1) = signed_prevote(vote1.0, vote1.1, vote1.2, vote1.3); + let (prevote2, signed2) = signed_prevote(vote2.0, vote2.1, vote2.2, vote2.3); + + sp_finality_grandpa::EquivocationProof::new( + set_id, + sp_finality_grandpa::Equivocation::Prevote(finality_grandpa::Equivocation { + round_number: vote1.0, + identity: vote1.3.public().into(), + first: (prevote1, signed1), + second: (prevote2, signed2), + }), + ) +} From f66ba8ba2e0639a48878a7077f68a53c49914abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 22:01:29 +0100 Subject: [PATCH 72/78] grandpa: add test for equivocation reporting --- frame/grandpa/src/mock.rs | 1 - frame/grandpa/src/tests.rs | 114 ++++++++++++++++++++++++++++++++----- 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index e303562f90810..1d223a01be1df 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -408,7 +408,6 @@ pub fn start_session(session_index: SessionIndex) { let mut parent_hash = System::parent_hash(); for i in Session::current_index()..session_index { - println!("session index {}", i); Staking::on_finalize(System::block_number()); System::set_block_number((i + 1).into()); Timestamp::set_timestamp(System::block_number() * 6000); diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index b583c31968d89..0712016ecf2dd 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -18,23 +18,17 @@ #![cfg(test)] -use sp_runtime::{testing::{H256, Digest}, traits::Header}; -use frame_support::traits::OnFinalize; +use super::*; use crate::mock::*; -use frame_system::{EventRecord, Phase}; use codec::{Decode, Encode}; use fg_primitives::ScheduledChange; -use super::*; - -fn initialize_block(number: u64, parent_hash: H256) { - System::initialize( - &number, - &parent_hash, - &Default::default(), - &Default::default(), - Default::default(), - ); -} +use frame_support::{ + assert_ok, + traits::{Currency, OnFinalize}, +}; +use frame_system::{EventRecord, Phase}; +use sp_core::H256; +use sp_runtime::{testing::Digest, traits::Header}; #[test] fn authorities_change_logged() { @@ -319,3 +313,95 @@ fn time_slot_have_sane_ord() { ]; assert!(FIXTURE.windows(2).all(|f| f[0] < f[1])); } + +#[test] +fn equivocation_report_works() { + use sp_keyring::Ed25519Keyring; + + let authorities = vec![ + Ed25519Keyring::Alice, + Ed25519Keyring::Bob, + Ed25519Keyring::Charlie, + ]; + + let authorities = authorities + .into_iter() + .map(|id| (id.public().into(), 1u64)) + .collect(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + assert_eq!(Staking::current_era(), Some(0)); + assert_eq!(Session::current_index(), 0); + + start_era(1); + + let authorities = Grandpa::grandpa_authorities(); + + // make sure that all authorities have the same balance + for i in 0..authorities.len() { + assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); + assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); + + assert_eq!( + Staking::eras_stakers(1, i as u64), + pallet_staking::Exposure { + total: 10_000, + own: 10_000, + others: vec![], + }, + ); + } + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index].0; + let equivocation_keyring = extract_keyring(equivocation_key); + + let set_id = Grandpa::current_set_id(); + + // generate an equivocation proof, with two votes in the same round for + // different block hashes signed by the same key + let equivocation_proof = generate_equivocation_proof( + set_id, + (1, H256::random(), 10, &equivocation_keyring), + (1, H256::random(), 10, &equivocation_keyring), + ); + + // create the key ownership proof + let key_owner_proof = + Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + + // report the equivocation and the tx should be dispatched successfully + let inner = report_equivocation(equivocation_proof, key_owner_proof).unwrap(); + assert_ok!(Grandpa::dispatch(inner, Origin::signed(1))); + + start_era(2); + + // check that the balance of 0-th validator is slashed 100%. + assert_eq!(Balances::total_balance(&0), 10_000_000 - 10_000); + assert_eq!(Staking::slashable_balance_of(&0), 0); + + assert_eq!( + Staking::eras_stakers(2, 0), + pallet_staking::Exposure { + total: 0, + own: 0, + others: vec![], + }, + ); + + // check that the balances of all other validators are left intact. + for i in 1..authorities.len() { + assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); + assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); + + assert_eq!( + Staking::eras_stakers(2, i as u64), + pallet_staking::Exposure { + total: 10_000, + own: 10_000, + others: vec![], + }, + ); + } + }); +} From 76eeee1b1b3db3e4ebca51102099bf8ad6fd8fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 22:04:29 +0100 Subject: [PATCH 73/78] grandpa: move SetIdSession initialization --- frame/grandpa/src/lib.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index d418b13d2eae8..58a36a7bb9370 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -212,12 +212,7 @@ decl_storage! { add_extra_genesis { config(authorities): AuthorityList; build(|config| { - // NOTE: initialize first session of first set. this is necessary - // because we only update this `on_new_session` which isn't called - // for the genesis session. - SetIdSession::insert(0, 0); - - Module::::initialize_authorities(&config.authorities) + Module::::initialize(&config.authorities) }) } } @@ -434,7 +429,9 @@ impl Module { >::deposit_log(log.into()); } - fn initialize_authorities(authorities: &AuthorityList) { + // Perform module initialization, abstracted so that it can be called either through genesis + // config builder or through `on_genesis_session`. + fn initialize(authorities: &AuthorityList) { if !authorities.is_empty() { assert!( Self::grandpa_authorities().is_empty(), @@ -442,6 +439,11 @@ impl Module { ); Self::set_grandpa_authorities(authorities); } + + // NOTE: initialize first session of first set. this is necessary + // because we only update this `on_new_session` which isn't called + // for the genesis session. + SetIdSession::insert(0, 0); } /// Submits an extrinsic to report an equivocation. This method will sign an @@ -508,7 +510,7 @@ impl pallet_session::OneSessionHandler for Module where I: Iterator { let authorities = validators.map(|(_, k)| (k, 1)).collect::>(); - Self::initialize_authorities(&authorities); + Self::initialize(&authorities); } fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I) From 34e4040385e7894e71d3c3f19fa5efb4e5c110aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 23:38:23 +0100 Subject: [PATCH 74/78] grandpa: add more tests --- frame/grandpa/src/mock.rs | 2 +- frame/grandpa/src/tests.rs | 268 ++++++++++++++++++++++++++++++++++++- 2 files changed, 262 insertions(+), 8 deletions(-) diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 1d223a01be1df..4a2e2a496132d 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -138,7 +138,7 @@ impl session::Trait for Test { type ValidatorIdOf = staking::StashOf; type ShouldEndSession = session::PeriodicSessions; type NextSessionRotation = session::PeriodicSessions; - type SessionManager = Staking; + type SessionManager = session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = TestSessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index 0712016ecf2dd..898c67583565f 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -23,11 +23,12 @@ use crate::mock::*; use codec::{Decode, Encode}; use fg_primitives::ScheduledChange; use frame_support::{ - assert_ok, + assert_err, assert_ok, traits::{Currency, OnFinalize}, }; use frame_system::{EventRecord, Phase}; use sp_core::H256; +use sp_keyring::Ed25519Keyring; use sp_runtime::{testing::Digest, traits::Header}; #[test] @@ -314,20 +315,22 @@ fn time_slot_have_sane_ord() { assert!(FIXTURE.windows(2).all(|f| f[0] < f[1])); } -#[test] -fn equivocation_report_works() { - use sp_keyring::Ed25519Keyring; - +fn test_authorities() -> AuthorityList { let authorities = vec![ Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie, ]; - let authorities = authorities + authorities .into_iter() .map(|id| (id.public().into(), 1u64)) - .collect(); + .collect() +} + +#[test] +fn report_equivocation_current_set_works() { + let authorities = test_authorities(); new_test_ext_raw_authorities(authorities).execute_with(|| { assert_eq!(Staking::current_era(), Some(0)); @@ -405,3 +408,254 @@ fn equivocation_report_works() { } }); } + +#[test] +fn report_equivocation_old_set_works() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let authorities = Grandpa::grandpa_authorities(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index].0; + + // create the key ownership proof in the "old" set + let key_owner_proof = + Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + + start_era(2); + + // make sure that all authorities have the same balance + for i in 0..authorities.len() { + assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); + assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); + + assert_eq!( + Staking::eras_stakers(2, i as u64), + pallet_staking::Exposure { + total: 10_000, + own: 10_000, + others: vec![], + }, + ); + } + + let equivocation_keyring = extract_keyring(equivocation_key); + + let set_id = Grandpa::current_set_id(); + + // generate an equivocation proof for the old set, + let equivocation_proof = generate_equivocation_proof( + set_id - 1, + (1, H256::random(), 10, &equivocation_keyring), + (1, H256::random(), 10, &equivocation_keyring), + ); + + // report the equivocation using the key ownership proof generated on + // the old set, the tx should be dispatched successfully + let inner = report_equivocation(equivocation_proof, key_owner_proof).unwrap(); + assert_ok!(Grandpa::dispatch(inner, Origin::signed(1))); + + start_era(3); + + // check that the balance of 0-th validator is slashed 100%. + assert_eq!(Balances::total_balance(&0), 10_000_000 - 10_000); + assert_eq!(Staking::slashable_balance_of(&0), 0); + + assert_eq!( + Staking::eras_stakers(3, 0), + pallet_staking::Exposure { + total: 0, + own: 0, + others: vec![], + }, + ); + + // check that the balances of all other validators are left intact. + for i in 1..authorities.len() { + assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); + assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); + + assert_eq!( + Staking::eras_stakers(3, i as u64), + pallet_staking::Exposure { + total: 10_000, + own: 10_000, + others: vec![], + }, + ); + } + }); +} + +#[test] +fn report_equivocation_invalid_set_id() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let authorities = Grandpa::grandpa_authorities(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index].0; + let equivocation_keyring = extract_keyring(equivocation_key); + + let key_owner_proof = + Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + + let set_id = Grandpa::current_set_id(); + + // generate an equivocation for a future set + let equivocation_proof = generate_equivocation_proof( + set_id + 1, + (1, H256::random(), 10, &equivocation_keyring), + (1, H256::random(), 10, &equivocation_keyring), + ); + + // it should be filtered by the signed extension validation + assert_err!( + report_equivocation(equivocation_proof, key_owner_proof), + equivocation::ReportEquivocationValidityError::InvalidSetId, + ); + }); +} + +#[test] +fn report_equivocation_invalid_session() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let authorities = Grandpa::grandpa_authorities(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index].0; + let equivocation_keyring = extract_keyring(equivocation_key); + + // generate a key ownership proof at set id = 1 + let key_owner_proof = + Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + + start_era(2); + + let set_id = Grandpa::current_set_id(); + + // generate an equivocation proof at set id = 2 + let equivocation_proof = generate_equivocation_proof( + set_id, + (1, H256::random(), 10, &equivocation_keyring), + (1, H256::random(), 10, &equivocation_keyring), + ); + + // report an equivocation for the current set using an key ownership + // proof from the previous set, the session should be invalid. + assert_err!( + report_equivocation(equivocation_proof, key_owner_proof), + equivocation::ReportEquivocationValidityError::InvalidSession, + ); + }); +} + +#[test] +fn report_equivocation_invalid_key_owner_proof() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + let authorities = Grandpa::grandpa_authorities(); + + let invalid_owner_authority_index = 1; + let invalid_owner_key = &authorities[invalid_owner_authority_index].0; + + // generate a key ownership proof for the authority at index 1 + let invalid_key_owner_proof = + Historical::prove((sp_finality_grandpa::KEY_TYPE, &invalid_owner_key)).unwrap(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index].0; + let equivocation_keyring = extract_keyring(equivocation_key); + + let set_id = Grandpa::current_set_id(); + + // generate an equivocation proof for the authority at index 0 + let equivocation_proof = generate_equivocation_proof( + set_id, + (1, H256::random(), 10, &equivocation_keyring), + (1, H256::random(), 10, &equivocation_keyring), + ); + + // we need to start a new era otherwise the key ownership proof won't be + // checked since the authorities are part of the current session + start_era(2); + + // report an equivocation for the current set using a key ownership + // proof for a different key than the one in the equivocation proof. + assert_err!( + report_equivocation(equivocation_proof, invalid_key_owner_proof), + equivocation::ReportEquivocationValidityError::InvalidKeyOwnershipProof, + ); + }); +} + +#[test] +fn report_equivocation_invalid_equivocation_proof() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let authorities = Grandpa::grandpa_authorities(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index].0; + let equivocation_keyring = extract_keyring(equivocation_key); + + // generate a key ownership proof at set id = 1 + let key_owner_proof = + Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + + let set_id = Grandpa::current_set_id(); + + let assert_invalid_equivocation_proof = |equivocation_proof| { + assert_err!( + report_equivocation(equivocation_proof, key_owner_proof.clone()), + equivocation::ReportEquivocationValidityError::InvalidEquivocationProof, + ); + }; + + start_era(2); + + // both votes target the same block number and hash, + // there is no equivocation. + assert_invalid_equivocation_proof(generate_equivocation_proof( + set_id, + (1, H256::zero(), 10, &equivocation_keyring), + (1, H256::zero(), 10, &equivocation_keyring), + )); + + // votes targetting different rounds, there is no equivocation. + assert_invalid_equivocation_proof(generate_equivocation_proof( + set_id, + (1, H256::random(), 10, &equivocation_keyring), + (2, H256::random(), 10, &equivocation_keyring), + )); + + // votes signed with different authority keys + assert_invalid_equivocation_proof(generate_equivocation_proof( + set_id, + (1, H256::random(), 10, &equivocation_keyring), + (1, H256::random(), 10, &Ed25519Keyring::Charlie), + )); + + // votes signed with a key that isn't part of the authority set + assert_invalid_equivocation_proof(generate_equivocation_proof( + set_id, + (1, H256::random(), 10, &equivocation_keyring), + (1, H256::random(), 10, &Ed25519Keyring::Dave), + )); + }); +} From b34ebdeb98d29e7b18d0749a6728dc1e3a02c497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 23:41:19 +0100 Subject: [PATCH 75/78] node: enable historical session manager --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8a8090ecdf44e..31c133e94e7e7 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -275,7 +275,7 @@ impl pallet_session::Trait for Runtime { type ValidatorId = ::AccountId; type ValidatorIdOf = pallet_staking::StashOf; type ShouldEndSession = Babe; - type SessionManager = Staking; + type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; From 999b6714b1f104f2f1085a94253c9fc840bb5a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 5 May 2020 23:42:48 +0100 Subject: [PATCH 76/78] node: bump spec_version --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 31c133e94e7e7..ab437ea8c5587 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -90,7 +90,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 246, + spec_version: 247, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 7f08cc10d48b9bcd65267f0a730d040fb5932a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 6 May 2020 13:13:17 +0100 Subject: [PATCH 77/78] node: use strong key types in KeyOwnerProofSystem definitions --- bin/node-template/runtime/src/lib.rs | 8 ++++---- bin/node/runtime/src/lib.rs | 8 ++++---- frame/grandpa/src/mock.rs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 434c0e2a149ee..8261577586e47 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -19,7 +19,7 @@ use sp_runtime::traits::{ }; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use grandpa::AuthorityList as GrandpaAuthorityList; +use grandpa::{AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList}; use grandpa::fg_primitives; use sp_version::RuntimeVersion; #[cfg(feature = "std")] @@ -193,11 +193,11 @@ impl grandpa::Trait for Runtime { type KeyOwnerProofSystem = (); type KeyOwnerProof = - )>>::Proof; + >::Proof; type KeyOwnerIdentification = , + GrandpaId, )>>::IdentificationTuple; type HandleEquivocation = (); @@ -395,7 +395,7 @@ impl_runtime_apis! { fn generate_key_ownership_proof( _set_id: fg_primitives::SetId, - _authority_id: fg_primitives::AuthorityId, + _authority_id: GrandpaId, ) -> Option { // NOTE: this is the only implementation possible since we've // defined our key owner proof type as a bottom type (i.e. a type diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ab437ea8c5587..294aac8ae3957 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -50,7 +50,7 @@ use sp_runtime::traits::{ use sp_version::RuntimeVersion; #[cfg(any(feature = "std", test))] use sp_version::NativeVersion; -use pallet_grandpa::AuthorityList as GrandpaAuthorityList; +use pallet_grandpa::{AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList}; use pallet_grandpa::fg_primitives; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; @@ -582,11 +582,11 @@ impl pallet_grandpa::Trait for Runtime { type KeyOwnerProofSystem = Historical; type KeyOwnerProof = - )>>::Proof; + >::Proof; type KeyOwnerIdentification = , + GrandpaId, )>>::IdentificationTuple; type HandleEquivocation = pallet_grandpa::EquivocationHandler< @@ -835,7 +835,7 @@ impl_runtime_apis! { fn generate_key_ownership_proof( _set_id: fg_primitives::SetId, - authority_id: fg_primitives::AuthorityId, + authority_id: GrandpaId, ) -> Option { use codec::Encode; diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 4a2e2a496132d..f307f17fd4d53 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -242,11 +242,11 @@ impl Trait for Test { type KeyOwnerProofSystem = Historical; type KeyOwnerProof = - )>>::Proof; + >::Proof; type KeyOwnerIdentification = , + AuthorityId, )>>::IdentificationTuple; type HandleEquivocation = super::EquivocationHandler< From d390b741b76c4c483161ce17408d7b973ed90df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Wed, 6 May 2020 14:55:51 +0100 Subject: [PATCH 78/78] grandpa: export GrandpaEquivocationOffence type --- frame/grandpa/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 58a36a7bb9370..16aebe335f9ae 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -55,8 +55,8 @@ mod mock; mod tests; pub use equivocation::{ - EquivocationHandler, GetSessionNumber, GetValidatorCount, GrandpaOffence, GrandpaTimeSlot, - HandleEquivocation, ValidateEquivocationReport, + EquivocationHandler, GetSessionNumber, GetValidatorCount, GrandpaEquivocationOffence, + GrandpaOffence, GrandpaTimeSlot, HandleEquivocation, ValidateEquivocationReport, }; pub trait Trait: frame_system::Trait {