babe: report equivocations (#6362)

* slots: create primitives crate for consensus slots

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

* babe: initial equivocation reporting implementation

* babe: organize imports

* babe: working equivocation reporting

* babe: add slot number to equivocation proof

* session: move duplicate traits to session primitives

* babe: move equivocation stuff to its own file

* offences: fix test

* session: don't have primitives depend on frame_support

* babe: use opaque type for key owner proof

* babe: cleanup client equivocation reporting

* babe: cleanup equivocation code in pallet

* babe: allow sending signed equivocation reports

* node: fix compilation

* fix test compilation

* babe: return bool on check_equivocation_proof

* babe: add test for equivocation reporting

* babe: add more tests

* babe: add test for validate unsigned

* babe: take slot number in generate_key_ownership_proof API

* babe: add benchmark for equivocation proof checking

* session: add benchmark for membership proof checking

* offences: fix babe benchmark

* babe: add weights based on benchmark results

* babe: adjust weights after benchmarking on reference hardware

* babe: reorder checks in check_and_report_equivocation
This commit is contained in:
André Silva
2020-07-04 11:18:13 +01:00
committed by GitHub
parent 61635e75c1
commit a9c21b8b84
34 changed files with 2031 additions and 275 deletions
+19
View File
@@ -4009,9 +4009,15 @@ dependencies = [
name = "pallet-babe"
version = "2.0.0-rc4"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"pallet-authorship",
"pallet-balances",
"pallet-offences",
"pallet-session",
"pallet-staking",
"pallet-staking-reward-curve",
"pallet-timestamp",
"parity-scale-codec",
"serde",
@@ -4022,6 +4028,7 @@ dependencies = [
"sp-inherents",
"sp-io",
"sp-runtime",
"sp-session",
"sp-staking",
"sp-std",
"sp-timestamp",
@@ -4554,10 +4561,12 @@ dependencies = [
"pallet-staking-reward-curve",
"pallet-timestamp",
"parity-scale-codec",
"rand 0.7.3",
"serde",
"sp-core",
"sp-io",
"sp-runtime",
"sp-session",
"sp-std",
]
@@ -6343,6 +6352,7 @@ dependencies = [
"sp-application-crypto",
"sp-blockchain",
"sp-consensus",
"sp-consensus-slots",
"sp-core",
"sp-inherents",
"sp-runtime",
@@ -7568,6 +7578,7 @@ dependencies = [
"sp-api",
"sp-application-crypto",
"sp-consensus",
"sp-consensus-slots",
"sp-consensus-vrf",
"sp-core",
"sp-inherents",
@@ -7587,6 +7598,14 @@ dependencies = [
"sp-std",
]
[[package]]
name = "sp-consensus-slots"
version = "0.8.0-rc4"
dependencies = [
"parity-scale-codec",
"sp-runtime",
]
[[package]]
name = "sp-consensus-vrf"
version = "0.8.0-rc4"
+8 -2
View File
@@ -83,7 +83,7 @@ macro_rules! new_full_start {
let (grandpa_block_import, grandpa_link) = grandpa::block_import(
client.clone(),
&(client.clone() as Arc<_>),
select_chain,
select_chain.clone(),
)?;
let justification_import = grandpa_block_import.clone();
@@ -99,6 +99,7 @@ macro_rules! new_full_start {
Some(Box::new(justification_import)),
None,
client,
select_chain,
inherent_data_providers.clone(),
spawn_task_handle,
prometheus_registry,
@@ -367,14 +368,18 @@ pub fn new_light_base(config: Configuration) -> Result<(
client,
backend,
fetcher,
_select_chain,
mut select_chain,
_tx_pool,
spawn_task_handle,
registry,
| {
let select_chain = select_chain.take()
.ok_or_else(|| sc_service::Error::SelectChainRequired)?;
let fetch_checker = fetcher
.map(|fetcher| fetcher.checker().clone())
.ok_or_else(|| "Trying to start light import queue without active fetch checker")?;
let grandpa_block_import = grandpa::light_block_import(
client.clone(),
backend,
@@ -398,6 +403,7 @@ pub fn new_light_base(config: Configuration) -> Result<(
None,
Some(Box::new(finality_proof_import)),
client.clone(),
select_chain,
inherent_data_providers.clone(),
spawn_task_handle,
registry,
+1
View File
@@ -149,6 +149,7 @@ runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-collective/runtime-benchmarks",
"pallet-democracy/runtime-benchmarks",
+40 -1
View File
@@ -270,6 +270,21 @@ impl pallet_babe::Trait for Runtime {
type EpochDuration = EpochDuration;
type ExpectedBlockTime = ExpectedBlockTime;
type EpochChangeTrigger = pallet_babe::ExternalTrigger;
type KeyOwnerProofSystem = Historical;
type KeyOwnerProof = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(
KeyTypeId,
pallet_babe::AuthorityId,
)>>::Proof;
type KeyOwnerIdentification = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(
KeyTypeId,
pallet_babe::AuthorityId,
)>>::IdentificationTuple;
type HandleEquivocation =
pallet_babe::EquivocationHandler<Self::KeyOwnerIdentification, Offences>;
}
parameter_types! {
@@ -808,7 +823,7 @@ construct_runtime!(
{
System: frame_system::{Module, Call, Config, Storage, Event<T>},
Utility: pallet_utility::{Module, Call, Event},
Babe: pallet_babe::{Module, Call, Storage, Config, Inherent(Timestamp)},
Babe: pallet_babe::{Module, Call, Storage, Config, Inherent(Timestamp), ValidateUnsigned},
Timestamp: pallet_timestamp::{Module, Call, Storage, Inherent},
Authorship: pallet_authorship::{Module, Call, Storage, Inherent},
Indices: pallet_indices::{Module, Call, Storage, Config<T>, Event<T>},
@@ -985,6 +1000,29 @@ impl_runtime_apis! {
fn current_epoch_start() -> sp_consensus_babe::SlotNumber {
Babe::current_epoch_start()
}
fn generate_key_ownership_proof(
_slot_number: sp_consensus_babe::SlotNumber,
authority_id: sp_consensus_babe::AuthorityId,
) -> Option<sp_consensus_babe::OpaqueKeyOwnershipProof> {
use codec::Encode;
Historical::prove((sp_consensus_babe::KEY_TYPE, authority_id))
.map(|p| p.encode())
.map(sp_consensus_babe::OpaqueKeyOwnershipProof::new)
}
fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof: sp_consensus_babe::EquivocationProof<<Block as BlockT>::Header>,
key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof,
) -> Option<()> {
let key_owner_proof = key_owner_proof.decode()?;
Babe::submit_unsigned_equivocation_report(
equivocation_proof,
key_owner_proof,
)
}
}
impl sp_authority_discovery::AuthorityDiscoveryApi<Block> for Runtime {
@@ -1099,6 +1137,7 @@ impl_runtime_apis! {
let mut batches = Vec::<BenchmarkBatch>::new();
let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat, &whitelist);
add_benchmark!(params, batches, b"babe", Babe);
add_benchmark!(params, batches, b"balances", Balances);
add_benchmark!(params, batches, b"collective", Council);
add_benchmark!(params, batches, b"democracy", Democracy);
+2 -2
View File
@@ -479,8 +479,8 @@ fn check_header<C, B: BlockT, P: Pair>(
info!(
"Slot author is equivocating at slot {} with headers {:?} and {:?}",
slot_num,
equivocation_proof.fst_header().hash(),
equivocation_proof.snd_header().hash(),
equivocation_proof.first_header.hash(),
equivocation_proof.second_header.hash(),
);
}
+105 -28
View File
@@ -720,27 +720,29 @@ impl<Block: BlockT> BabeLink<Block> {
}
/// A verifier for Babe blocks.
pub struct BabeVerifier<Block: BlockT, Client> {
pub struct BabeVerifier<Block: BlockT, Client, SelectChain> {
client: Arc<Client>,
select_chain: SelectChain,
inherent_data_providers: sp_inherents::InherentDataProviders,
config: Config,
epoch_changes: SharedEpochChanges<Block, Epoch>,
time_source: TimeSource,
}
impl<Block, Client> BabeVerifier<Block, Client>
where
Block: BlockT,
Client: HeaderBackend<Block> + HeaderMetadata<Block> + ProvideRuntimeApi<Block>,
Client::Api: BlockBuilderApi<Block, Error = sp_blockchain::Error>,
impl<Block, Client, SelectChain> BabeVerifier<Block, Client, SelectChain>
where
Block: BlockT,
Client: AuxStore + HeaderBackend<Block> + HeaderMetadata<Block> + ProvideRuntimeApi<Block>,
Client::Api: BlockBuilderApi<Block, Error = sp_blockchain::Error>
+ BabeApi<Block, Error = sp_blockchain::Error>,
SelectChain: sp_consensus::SelectChain<Block>,
{
fn check_inherents(
&self,
block: Block,
block_id: BlockId<Block>,
inherent_data: InherentData,
) -> Result<(), Error<Block>>
{
) -> Result<(), Error<Block>> {
let inherent_res = self.client.runtime_api().check_inherents(
&block_id,
block,
@@ -757,13 +759,95 @@ impl<Block, Client> BabeVerifier<Block, Client>
Ok(())
}
}
fn check_and_report_equivocation(
&self,
slot_now: SlotNumber,
slot: SlotNumber,
header: &Block::Header,
author: &AuthorityId,
origin: &BlockOrigin,
) -> Result<(), Error<Block>> {
// don't report any equivocations during initial sync
// as they are most likely stale.
if *origin == BlockOrigin::NetworkInitialSync {
return Ok(());
}
// check if authorship of this header is an equivocation and return a proof if so.
let equivocation_proof =
match check_equivocation(&*self.client, slot_now, slot, header, author)
.map_err(Error::Client)?
{
Some(proof) => proof,
None => return Ok(()),
};
info!(
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
author,
slot,
equivocation_proof.first_header.hash(),
equivocation_proof.second_header.hash(),
);
// get the best block on which we will build and send the equivocation report.
let best_id = self
.select_chain
.best_chain()
.map(|h| BlockId::Hash(h.hash()))
.map_err(|e| Error::Client(e.into()))?;
// generate a key ownership proof. we start by trying to generate the
// key owernship proof at the parent of the equivocating header, this
// will make sure that proof generation is successful since it happens
// during the on-going session (i.e. session keys are available in the
// state to be able to generate the proof). this might fail if the
// equivocation happens on the first block of the session, in which case
// its parent would be on the previous session. if generation on the
// parent header fails we try with best block as well.
let generate_key_owner_proof = |block_id: &BlockId<Block>| {
self.client
.runtime_api()
.generate_key_ownership_proof(block_id, slot, equivocation_proof.offender.clone())
.map_err(Error::Client)
};
let parent_id = BlockId::Hash(*header.parent_hash());
let key_owner_proof = match generate_key_owner_proof(&parent_id)? {
Some(proof) => proof,
None => match generate_key_owner_proof(&best_id)? {
Some(proof) => proof,
None => {
debug!(target: "babe", "Equivocation offender is not part of the authority set.");
return Ok(());
}
},
};
// submit equivocation report at best block.
self.client
.runtime_api()
.submit_report_equivocation_unsigned_extrinsic(
&best_id,
equivocation_proof,
key_owner_proof,
)
.map_err(Error::Client)?;
info!(target: "babe", "Submitted equivocation report for author {:?}", author);
Ok(())
}
}
impl<Block, Client> Verifier<Block> for BabeVerifier<Block, Client> where
impl<Block, Client, SelectChain> Verifier<Block> for BabeVerifier<Block, Client, SelectChain>
where
Block: BlockT,
Client: HeaderMetadata<Block, Error = sp_blockchain::Error> + HeaderBackend<Block> + ProvideRuntimeApi<Block>
+ Send + Sync + AuxStore + ProvideCache<Block>,
+ Send + Sync + AuxStore + ProvideCache<Block>,
Client::Api: BlockBuilderApi<Block, Error = sp_blockchain::Error> + BabeApi<Block, Error = sp_blockchain::Error>,
SelectChain: sp_consensus::SelectChain<Block>,
{
fn verify(
&mut self,
@@ -824,28 +908,18 @@ impl<Block, Client> Verifier<Block> for BabeVerifier<Block, Client> where
CheckedHeader::Checked(pre_header, verified_info) => {
let babe_pre_digest = verified_info.pre_digest.as_babe_pre_digest()
.expect("check_header always returns a pre-digest digest item; qed");
let slot_number = babe_pre_digest.slot_number();
let author = verified_info.author;
// the header is valid but let's check if there was something else already
// proposed at the same slot by the given author
if let Some(equivocation_proof) = check_equivocation(
&*self.client,
// proposed at the same slot by the given author. if there was, we will
// report the equivocation to the runtime.
self.check_and_report_equivocation(
slot_now,
babe_pre_digest.slot_number(),
slot_number,
&header,
&author,
).map_err(|e| e.to_string())? {
info!(
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
author,
babe_pre_digest.slot_number(),
equivocation_proof.fst_header().hash(),
equivocation_proof.snd_header().hash(),
);
}
&verified_info.author,
&origin,
)?;
// if the body is passed through, we need to use the runtime
// to check that the internally-set timestamp in the inherents
@@ -1284,12 +1358,13 @@ pub fn block_import<Client, Block: BlockT, I>(
///
/// The block import object provided must be the `BabeBlockImport` or a wrapper
/// of it, otherwise crucial import logic will be omitted.
pub fn import_queue<Block: BlockT, Client, Inner>(
pub fn import_queue<Block: BlockT, Client, SelectChain, Inner>(
babe_link: BabeLink<Block>,
block_import: Inner,
justification_import: Option<BoxJustificationImport<Block>>,
finality_proof_import: Option<BoxFinalityProofImport<Block>>,
client: Arc<Client>,
select_chain: SelectChain,
inherent_data_providers: InherentDataProviders,
spawner: &impl sp_core::traits::SpawnNamed,
registry: Option<&Registry>,
@@ -1299,11 +1374,13 @@ pub fn import_queue<Block: BlockT, Client, Inner>(
Client: ProvideRuntimeApi<Block> + ProvideCache<Block> + Send + Sync + AuxStore + 'static,
Client: HeaderBackend<Block> + HeaderMetadata<Block, Error = sp_blockchain::Error>,
Client::Api: BlockBuilderApi<Block> + BabeApi<Block> + ApiExt<Block, Error = sp_blockchain::Error>,
SelectChain: sp_consensus::SelectChain<Block> + 'static,
{
register_babe_inherent_data_provider(&inherent_data_providers, babe_link.config.slot_duration)?;
let verifier = BabeVerifier {
client,
select_chain,
inherent_data_providers,
config: babe_link.config,
epoch_changes: babe_link.epoch_changes,
+11 -1
View File
@@ -214,8 +214,13 @@ pub struct BabeTestNet {
type TestHeader = <TestBlock as BlockT>::Header;
type TestExtrinsic = <TestBlock as BlockT>::Extrinsic;
type TestSelectChain = substrate_test_runtime_client::LongestChain<
substrate_test_runtime_client::Backend,
TestBlock,
>;
pub struct TestVerifier {
inner: BabeVerifier<TestBlock, PeersFullClient>,
inner: BabeVerifier<TestBlock, PeersFullClient, TestSelectChain>,
mutator: Mutator,
}
@@ -297,15 +302,20 @@ impl TestNetFactory for BabeTestNet {
)
-> Self::Verifier
{
use substrate_test_runtime_client::DefaultTestClientBuilderExt;
let client = client.as_full().expect("only full clients are used in test");
trace!(target: "babe", "Creating a verifier");
// ensure block import and verifier are linked correctly.
let data = maybe_link.as_ref().expect("babe link always provided to verifier instantiation");
let (_, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
TestVerifier {
inner: BabeVerifier {
client: client.clone(),
select_chain: longest_chain,
inherent_data_providers: data.inherent_data_providers.clone(),
config: data.link.config.clone(),
epoch_changes: data.link.epoch_changes.clone(),
@@ -18,6 +18,7 @@ sc-client-api = { version = "2.0.0-rc4", path = "../../api" }
sp-core = { version = "2.0.0-rc4", path = "../../../primitives/core" }
sp-application-crypto = { version = "2.0.0-rc4", path = "../../../primitives/application-crypto" }
sp-blockchain = { version = "2.0.0-rc4", path = "../../../primitives/blockchain" }
sp-consensus-slots = { version = "0.8.0-rc4", path = "../../../primitives/consensus/slots" }
sp-runtime = { version = "2.0.0-rc4", path = "../../../primitives/runtime" }
sp-state-machine = { version = "0.8.0-rc4", path = "../../../primitives/state-machine" }
sp-api = { version = "2.0.0-rc4", path = "../../../primitives/api" }
@@ -19,6 +19,7 @@
use codec::{Encode, Decode};
use sc_client_api::backend::AuxStore;
use sp_blockchain::{Result as ClientResult, Error as ClientError};
use sp_consensus_slots::EquivocationProof;
use sp_runtime::traits::Header;
const SLOT_HEADER_MAP_KEY: &[u8] = b"slot_header_map";
@@ -44,31 +45,6 @@ fn load_decode<C, T>(backend: &C, key: &[u8]) -> ClientResult<Option<T>>
}
}
/// Represents an equivocation proof.
#[derive(Debug, Clone)]
pub struct EquivocationProof<H> {
slot: u64,
fst_header: H,
snd_header: H,
}
impl<H> EquivocationProof<H> {
/// Get the slot number where the equivocation happened.
pub fn slot(&self) -> u64 {
self.slot
}
/// Get the first header involved in the equivocation.
pub fn fst_header(&self) -> &H {
&self.fst_header
}
/// Get the second header involved in the equivocation.
pub fn snd_header(&self) -> &H {
&self.snd_header
}
}
/// Checks if the header is an equivocation and returns the proof in that case.
///
/// Note: it detects equivocations only when slot_now - slot <= MAX_SLOT_CAPACITY.
@@ -78,7 +54,7 @@ pub fn check_equivocation<C, H, P>(
slot: u64,
header: &H,
signer: &P,
) -> ClientResult<Option<EquivocationProof<H>>>
) -> ClientResult<Option<EquivocationProof<H, P>>>
where
H: Header,
C: AuxStore,
@@ -114,9 +90,10 @@ pub fn check_equivocation<C, H, P>(
// 2) with different hash
if header.hash() != prev_header.hash() {
return Ok(Some(EquivocationProof {
slot, // 3) and mentioning the same slot.
fst_header: prev_header.clone(),
snd_header: header.clone(),
slot_number: slot,
offender: signer.clone(),
first_header: prev_header.clone(),
second_header: header.clone(),
}));
} else {
// We don't need to continue in case of duplicated header,
+28 -16
View File
@@ -13,40 +13,52 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] }
serde = { version = "1.0.101", optional = true }
sp-inherents = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/inherents" }
sp-application-crypto = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/application-crypto" }
sp-std = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/std" }
sp-runtime = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/runtime" }
sp-staking = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/staking" }
frame-benchmarking = { version = "2.0.0-rc4", default-features = false, path = "../benchmarking", optional = true }
frame-support = { version = "2.0.0-rc4", default-features = false, path = "../support" }
frame-system = { version = "2.0.0-rc4", default-features = false, path = "../system" }
pallet-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../timestamp" }
sp-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/timestamp" }
pallet-authorship = { version = "2.0.0-rc4", default-features = false, path = "../authorship" }
pallet-session = { version = "2.0.0-rc4", default-features = false, path = "../session" }
pallet-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../timestamp" }
serde = { version = "1.0.101", optional = true }
sp-application-crypto = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/application-crypto" }
sp-consensus-babe = { version = "0.8.0-rc4", default-features = false, path = "../../primitives/consensus/babe" }
sp-consensus-vrf = { version = "0.8.0-rc4", default-features = false, path = "../../primitives/consensus/vrf" }
sp-inherents = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/inherents" }
sp-io = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/io" }
sp-runtime = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/runtime" }
sp-session = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/session" }
sp-staking = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/staking" }
sp-std = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/std" }
sp-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../../primitives/timestamp" }
[dev-dependencies]
frame-benchmarking = { version = "2.0.0-rc4", path = "../benchmarking" }
pallet-balances = { version = "2.0.0-rc4", path = "../balances" }
pallet-offences = { version = "2.0.0-rc4", path = "../offences" }
pallet-staking = { version = "2.0.0-rc4", path = "../staking" }
pallet-staking-reward-curve = { version = "2.0.0-rc4", path = "../staking/reward-curve" }
sp-core = { version = "2.0.0-rc4", path = "../../primitives/core" }
[features]
default = ["std"]
std = [
"codec/std",
"serde",
"sp-std/std",
"sp-application-crypto/std",
"frame-benchmarking/std",
"frame-support/std",
"sp-runtime/std",
"sp-staking/std",
"frame-system/std",
"pallet-authorship/std",
"pallet-session/std",
"pallet-timestamp/std",
"sp-timestamp/std",
"sp-inherents/std",
"serde",
"sp-application-crypto/std",
"sp-consensus-babe/std",
"sp-consensus-vrf/std",
"pallet-session/std",
"sp-inherents/std",
"sp-io/std",
"sp-runtime/std",
"sp-session/std",
"sp-staking/std",
"sp-std/std",
"sp-timestamp/std",
]
runtime-benchmarks = ["frame-benchmarking"]
+108
View File
@@ -0,0 +1,108 @@
// This file is part of Substrate.
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Benchmarks for the BABE Pallet.
#![cfg_attr(not(feature = "std"), no_std)]
use super::*;
use frame_benchmarking::benchmarks;
type Header = sp_runtime::generic::Header<u64, sp_runtime::traits::BlakeTwo256>;
benchmarks! {
_ { }
check_equivocation_proof {
let x in 0 .. 1;
// NOTE: generated with the test below `test_generate_equivocation_report_blob`.
// the output is not deterministic since keys are generated randomly (and therefore
// signature content changes). it should not affect the benchmark.
// with the current benchmark setup it is not possible to generate this programatically
// from the benchmark setup.
const EQUIVOCATION_PROOF_BLOB: [u8; 416] = [
222, 241, 46, 66, 243, 228, 135, 233, 177, 64, 149, 170, 141, 92, 193, 106, 51, 73, 31,
27, 80, 218, 220, 248, 129, 29, 20, 128, 243, 250, 134, 39, 11, 0, 0, 0, 0, 0, 0, 0,
158, 4, 7, 240, 67, 153, 134, 190, 251, 196, 229, 95, 136, 165, 234, 228, 255, 18, 2,
187, 76, 125, 108, 50, 67, 33, 196, 108, 38, 115, 179, 86, 40, 36, 27, 5, 105, 58, 228,
94, 198, 65, 212, 218, 213, 61, 170, 21, 51, 249, 182, 121, 101, 91, 204, 25, 31, 87,
219, 208, 43, 119, 211, 185, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 66, 65, 66, 69, 52, 2, 0, 0, 0, 0, 11,
0, 0, 0, 0, 0, 0, 0, 5, 66, 65, 66, 69, 1, 1, 188, 192, 217, 91, 138, 78, 217, 80, 8,
29, 140, 55, 242, 210, 170, 184, 73, 98, 135, 212, 236, 209, 115, 52, 200, 79, 175,
172, 242, 161, 199, 47, 236, 93, 101, 95, 43, 34, 141, 16, 247, 220, 33, 59, 31, 197,
27, 7, 196, 62, 12, 238, 236, 124, 136, 191, 29, 36, 22, 238, 242, 202, 57, 139, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 40, 23, 175, 153, 83, 6, 33, 65, 123, 51, 80, 223, 126, 186, 226, 225, 240, 105, 28,
169, 9, 54, 11, 138, 46, 194, 201, 250, 48, 242, 125, 117, 116, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 66, 65,
66, 69, 52, 2, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 5, 66, 65, 66, 69, 1, 1, 142, 12,
124, 11, 167, 227, 103, 88, 78, 23, 228, 33, 96, 41, 207, 183, 227, 189, 114, 70, 254,
30, 128, 243, 233, 83, 214, 45, 74, 182, 120, 119, 64, 243, 219, 119, 63, 240, 205,
123, 231, 82, 205, 174, 143, 70, 2, 86, 182, 20, 16, 141, 145, 91, 116, 195, 58, 223,
175, 145, 255, 7, 121, 133
];
let equivocation_proof1: sp_consensus_babe::EquivocationProof<Header> =
Decode::decode(&mut &EQUIVOCATION_PROOF_BLOB[..]).unwrap();
let equivocation_proof2 = equivocation_proof1.clone();
}: {
sp_consensus_babe::check_equivocation_proof::<Header>(equivocation_proof1);
} verify {
assert!(sp_consensus_babe::check_equivocation_proof::<Header>(equivocation_proof2));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
use frame_support::assert_ok;
#[test]
fn test_benchmarks() {
new_test_ext(3).execute_with(|| {
assert_ok!(test_benchmark_check_equivocation_proof::<Test>());
})
}
#[test]
fn test_generate_equivocation_report_blob() {
let (pairs, mut ext) = new_test_ext_with_pairs(3);
let offending_authority_index = 0;
let offending_authority_pair = &pairs[0];
ext.execute_with(|| {
start_era(1);
let equivocation_proof = generate_equivocation_proof(
offending_authority_index,
offending_authority_pair,
CurrentSlot::get() + 1,
);
println!("equivocation_proof: {:?}", equivocation_proof);
println!(
"equivocation_proof.encode(): {:?}",
equivocation_proof.encode()
);
});
}
}
+271
View File
@@ -0,0 +1,271 @@
// This file is part of Substrate.
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//!
//! An opt-in utility module for reporting equivocations.
//!
//! This module defines an offence type for BABE equivocations
//! and some utility traits to wire together:
//! - a system for reporting offences;
//! - a system for submitting unsigned transactions;
//! - a way to get the current block author;
//!
//! These can be used in an offchain context in order to submit equivocation
//! reporting extrinsics (from the client that's import BABE blocks).
//! And in a runtime context, so that the BABE pallet can validate the
//! equivocation proofs in the extrinsic and report the offences.
//!
//! IMPORTANT:
//! When using this module for enabling equivocation reporting it is required
//! that the `ValidateUnsigned` for the BABE pallet is used in the runtime
//! definition.
//!
use frame_support::{debug, traits::KeyOwnerProofSystem};
use sp_consensus_babe::{EquivocationProof, SlotNumber};
use sp_runtime::transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
TransactionValidityError, ValidTransaction,
};
use sp_runtime::{DispatchResult, Perbill};
use sp_staking::{
offence::{Kind, Offence, OffenceError, ReportOffence},
SessionIndex,
};
use sp_std::prelude::*;
use crate::{Call, Module, Trait};
/// A trait with utility methods for handling equivocation reports in BABE.
/// The trait provides methods for reporting an offence triggered by a valid
/// equivocation report, checking the current block author (to declare as the
/// reporter), and also for creating and submitting equivocation report
/// extrinsics (useful only in offchain context).
pub trait HandleEquivocation<T: Trait> {
/// Report an offence proved by the given reporters.
fn report_offence(
reporters: Vec<T::AccountId>,
offence: BabeEquivocationOffence<T::KeyOwnerIdentification>,
) -> Result<(), OffenceError>;
/// Returns true if all of the offenders at the given time slot have already been reported.
fn is_known_offence(offenders: &[T::KeyOwnerIdentification], time_slot: &SlotNumber) -> bool;
/// Create and dispatch an equivocation report extrinsic.
fn submit_unsigned_equivocation_report(
equivocation_proof: EquivocationProof<T::Header>,
key_owner_proof: T::KeyOwnerProof,
) -> DispatchResult;
/// Fetch the current block author id, if defined.
fn block_author() -> Option<T::AccountId>;
}
impl<T: Trait> HandleEquivocation<T> for () {
fn report_offence(
_reporters: Vec<T::AccountId>,
_offence: BabeEquivocationOffence<T::KeyOwnerIdentification>,
) -> Result<(), OffenceError> {
Ok(())
}
fn is_known_offence(_offenders: &[T::KeyOwnerIdentification], _time_slot: &SlotNumber) -> bool {
true
}
fn submit_unsigned_equivocation_report(
_equivocation_proof: EquivocationProof<T::Header>,
_key_owner_proof: T::KeyOwnerProof,
) -> DispatchResult {
Ok(())
}
fn block_author() -> Option<T::AccountId> {
None
}
}
/// 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<I, R> {
_phantom: sp_std::marker::PhantomData<(I, R)>,
}
impl<I, R> Default for EquivocationHandler<I, R> {
fn default() -> Self {
Self {
_phantom: Default::default(),
}
}
}
impl<T, R> HandleEquivocation<T> for EquivocationHandler<T::KeyOwnerIdentification, R>
where
// We use the authorship pallet to fetch the current block author and use
// `offchain::SendTransactionTypes` for unsigned extrinsic creation and
// submission.
T: Trait + pallet_authorship::Trait + frame_system::offchain::SendTransactionTypes<Call<T>>,
// A system for reporting offences after valid equivocation reports are
// processed.
R: ReportOffence<
T::AccountId,
T::KeyOwnerIdentification,
BabeEquivocationOffence<T::KeyOwnerIdentification>,
>,
{
fn report_offence(
reporters: Vec<T::AccountId>,
offence: BabeEquivocationOffence<T::KeyOwnerIdentification>,
) -> Result<(), OffenceError> {
R::report_offence(reporters, offence)
}
fn is_known_offence(offenders: &[T::KeyOwnerIdentification], time_slot: &SlotNumber) -> bool {
R::is_known_offence(offenders, time_slot)
}
fn submit_unsigned_equivocation_report(
equivocation_proof: EquivocationProof<T::Header>,
key_owner_proof: T::KeyOwnerProof,
) -> DispatchResult {
use frame_system::offchain::SubmitTransaction;
let call = Call::report_equivocation_unsigned(equivocation_proof, key_owner_proof);
match SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
Ok(()) => debug::info!("Submitted BABE equivocation report."),
Err(e) => debug::error!("Error submitting equivocation report: {:?}", e),
}
Ok(())
}
fn block_author() -> Option<T::AccountId> {
Some(<pallet_authorship::Module<T>>::author())
}
}
/// A `ValidateUnsigned` implementation that restricts calls to `report_equivocation_unsigned`
/// to local calls (i.e. extrinsics generated on this node) or that already in a block. This
/// guarantees that only block authors can include unsigned equivocation reports.
impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if let Call::report_equivocation_unsigned(equivocation_proof, _) = call {
// discard equivocation report not coming from the local node
match source {
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }
_ => {
debug::warn!(
target: "babe",
"rejecting unsigned report equivocation transaction because it is not local/in-block."
);
return InvalidTransaction::Call.into();
}
}
ValidTransaction::with_tag_prefix("BabeEquivocation")
// We assign the maximum priority for any equivocation report.
.priority(TransactionPriority::max_value())
// Only one equivocation report for the same offender at the same slot.
.and_provides((
equivocation_proof.offender.clone(),
equivocation_proof.slot_number,
))
// We don't propagate this. This can never be included on a remote node.
.propagate(false)
.build()
} else {
InvalidTransaction::Call.into()
}
}
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
if let Call::report_equivocation_unsigned(equivocation_proof, key_owner_proof) = call {
// check the membership proof to extract the offender's id
let key = (
sp_consensus_babe::KEY_TYPE,
equivocation_proof.offender.clone(),
);
let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof.clone())
.ok_or(InvalidTransaction::BadProof)?;
// check if the offence has already been reported,
// and if so then we can discard the report.
let is_known_offence = T::HandleEquivocation::is_known_offence(
&[offender],
&equivocation_proof.slot_number,
);
if is_known_offence {
Err(InvalidTransaction::Stale.into())
} else {
Ok(())
}
} else {
Err(InvalidTransaction::Call.into())
}
}
}
/// A BABE equivocation offence report.
///
/// When a validator released two or more blocks at the same slot.
pub struct BabeEquivocationOffence<FullIdentification> {
/// A babe slot number in which this incident happened.
pub slot: SlotNumber,
/// The session index in which the incident happened.
pub session_index: SessionIndex,
/// The size of the validator set at the time of the offence.
pub validator_set_count: u32,
/// The authority that produced the equivocation.
pub offender: FullIdentification,
}
impl<FullIdentification: Clone> Offence<FullIdentification>
for BabeEquivocationOffence<FullIdentification>
{
const ID: Kind = *b"babe:equivocatio";
type TimeSlot = SlotNumber;
fn offenders(&self) -> Vec<FullIdentification> {
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.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()
}
}
+187 -64
View File
@@ -21,37 +21,44 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(unused_must_use, unsafe_code, unused_variables, unused_must_use)]
use pallet_timestamp;
use sp_std::{result, prelude::*};
use codec::{Decode, Encode};
use frame_support::{
decl_storage, decl_module, traits::{FindAuthor, Get, Randomness as RandomnessT},
decl_error, decl_module, decl_storage,
traits::{FindAuthor, Get, KeyOwnerProofSystem, Randomness as RandomnessT},
weights::Weight,
Parameter,
};
use sp_timestamp::OnTimestampSet;
use sp_runtime::{generic::DigestItem, ConsensusEngineId, Perbill};
use sp_runtime::traits::{IsMember, SaturatedConversion, Saturating, Hash, One};
use sp_staking::{
SessionIndex,
offence::{Offence, Kind},
};
use frame_system::{ensure_none, ensure_signed};
use sp_application_crypto::Public;
use sp_runtime::{
generic::DigestItem,
traits::{Hash, IsMember, One, SaturatedConversion, Saturating},
ConsensusEngineId, KeyTypeId,
};
use sp_session::{GetSessionNumber, GetValidatorCount};
use sp_std::{prelude::*, result};
use sp_timestamp::OnTimestampSet;
use codec::{Encode, Decode};
use sp_inherents::{InherentIdentifier, InherentData, ProvideInherent, MakeFatalError};
use sp_consensus_babe::{
BABE_ENGINE_ID, ConsensusLog, BabeAuthorityWeight, SlotNumber,
inherents::{INHERENT_IDENTIFIER, BabeInherentData},
digests::{NextEpochDescriptor, NextConfigDescriptor, PreDigest},
digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest},
inherents::{BabeInherentData, INHERENT_IDENTIFIER},
BabeAuthorityWeight, ConsensusLog, EquivocationProof, SlotNumber, BABE_ENGINE_ID,
};
use sp_consensus_vrf::schnorrkel;
pub use sp_consensus_babe::{AuthorityId, VRF_OUTPUT_LENGTH, RANDOMNESS_LENGTH, PUBLIC_KEY_LENGTH};
use sp_inherents::{InherentData, InherentIdentifier, MakeFatalError, ProvideInherent};
pub use sp_consensus_babe::{AuthorityId, PUBLIC_KEY_LENGTH, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH};
mod equivocation;
#[cfg(any(feature = "runtime-benchmarks", test))]
mod benchmarking;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;
#[cfg(all(feature = "std", test))]
mod mock;
pub use equivocation::{BabeEquivocationOffence, EquivocationHandler, HandleEquivocation};
pub trait Trait: pallet_timestamp::Trait {
/// The amount of time, in slots, that each epoch should last.
@@ -70,6 +77,30 @@ pub trait Trait: pallet_timestamp::Trait {
/// Typically, the `ExternalTrigger` type should be used. An internal trigger should only be used
/// when no other module is responsible for changing authority set.
type EpochChangeTrigger: EpochChangeTrigger;
/// 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).
/// NOTE: when enabling equivocation handling (i.e. this type isn't set to
/// `()`) you must use this pallet's `ValidateUnsigned` in the runtime
/// definition.
type HandleEquivocation: HandleEquivocation<Self>;
}
/// Trigger an epoch change, if any should take place.
@@ -106,6 +137,17 @@ const UNDER_CONSTRUCTION_SEGMENT_LENGTH: usize = 256;
type MaybeRandomness = Option<schnorrkel::Randomness>;
decl_error! {
pub enum Error for Module<T: Trait> {
/// An equivocation proof provided as part of an equivocation report is invalid.
InvalidEquivocationProof,
/// A key ownership proof provided as part of an equivocation report is invalid.
InvalidKeyOwnershipProof,
/// A given equivocation report is valid but already previously reported.
DuplicateOffenceReport,
}
}
decl_storage! {
trait Store for Module<T: Trait> as Babe {
/// Current epoch index.
@@ -208,6 +250,69 @@ decl_module! {
// remove temporary "environment" entry from storage
Lateness::<T>::kill();
}
/// Report authority 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.
#[weight = weight::weight_for_report_equivocation::<T>()]
fn report_equivocation(
origin,
equivocation_proof: EquivocationProof<T::Header>,
key_owner_proof: T::KeyOwnerProof,
) {
let reporter = ensure_signed(origin)?;
Self::do_report_equivocation(
Some(reporter),
equivocation_proof,
key_owner_proof,
)?;
}
/// Report authority 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.
/// This extrinsic must be called unsigned and it is expected that only
/// block authors will call it (validated in `ValidateUnsigned`), as such
/// if the block author is defined it will be defined as the equivocation
/// reporter.
#[weight = weight::weight_for_report_equivocation::<T>()]
fn report_equivocation_unsigned(
origin,
equivocation_proof: EquivocationProof<T::Header>,
key_owner_proof: T::KeyOwnerProof,
) {
ensure_none(origin)?;
Self::do_report_equivocation(
T::HandleEquivocation::block_author(),
equivocation_proof,
key_owner_proof,
)?;
}
}
}
mod weight {
use frame_support::{
traits::Get,
weights::{constants::WEIGHT_PER_MICROS, Weight},
};
pub fn weight_for_report_equivocation<T: super::Trait>() -> Weight {
// checking membership proof
(35 * WEIGHT_PER_MICROS)
.saturating_add(T::DbWeight::get().reads(5))
// check equivocation proof
.saturating_add(110 * WEIGHT_PER_MICROS)
// report offence
.saturating_add(110 * WEIGHT_PER_MICROS)
// worst case we are considering is that the given offender
// is backed by 200 nominators
.saturating_add(T::DbWeight::get().reads(14 + 3 * 200))
.saturating_add(T::DbWeight::get().writes(10 + 3 * 200))
}
}
@@ -274,51 +379,6 @@ impl<T: Trait> pallet_session::ShouldEndSession<T::BlockNumber> for Module<T> {
}
}
/// A BABE equivocation offence report.
///
/// When a validator released two or more blocks at the same slot.
pub struct BabeEquivocationOffence<FullIdentification> {
/// A babe slot number in which this incident happened.
pub slot: u64,
/// The session index in which the incident happened.
pub session_index: SessionIndex,
/// The size of the validator set at the time of the offence.
pub validator_set_count: u32,
/// The authority that produced the equivocation.
pub offender: FullIdentification,
}
impl<FullIdentification: Clone> Offence<FullIdentification> for BabeEquivocationOffence<FullIdentification> {
const ID: Kind = *b"babe:equivocatio";
type TimeSlot = u64;
fn offenders(&self) -> Vec<FullIdentification> {
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.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()
}
}
impl<T: Trait> Module<T> {
/// Determine the BABE slot duration based on the Timestamp module configuration.
pub fn slot_duration() -> T::Moment {
@@ -561,6 +621,69 @@ impl<T: Trait> Module<T> {
Authorities::put(authorities);
}
}
fn do_report_equivocation(
reporter: Option<T::AccountId>,
equivocation_proof: EquivocationProof<T::Header>,
key_owner_proof: T::KeyOwnerProof,
) -> Result<(), Error<T>> {
let offender = equivocation_proof.offender.clone();
let slot_number = equivocation_proof.slot_number;
// validate the equivocation proof
if !sp_consensus_babe::check_equivocation_proof(equivocation_proof) {
return Err(Error::InvalidEquivocationProof.into());
}
let validator_set_count = key_owner_proof.validator_count();
let session_index = key_owner_proof.session();
let epoch_index = (slot_number.saturating_sub(GenesisSlot::get()) / T::EpochDuration::get())
.saturated_into::<u32>();
// check that the slot number is consistent with the session index
// in the key ownership proof (i.e. slot is for that epoch)
if epoch_index != session_index {
return Err(Error::InvalidKeyOwnershipProof.into());
}
// check the membership proof and extract the offender's id
let key = (sp_consensus_babe::KEY_TYPE, offender);
let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof)
.ok_or(Error::InvalidKeyOwnershipProof)?;
let offence = BabeEquivocationOffence {
slot: slot_number,
validator_set_count,
offender,
session_index,
};
let reporters = match reporter {
Some(id) => vec![id],
None => vec![],
};
T::HandleEquivocation::report_offence(reporters, offence)
.map_err(|_| Error::DuplicateOffenceReport)?;
Ok(())
}
/// Submits an extrinsic to report an equivocation. This method will create
/// an unsigned extrinsic with a call to `report_equivocation_unsigned` and
/// will push the transaction to the pool. Only useful in an offchain
/// context.
pub fn submit_unsigned_equivocation_report(
equivocation_proof: EquivocationProof<T::Header>,
key_owner_proof: T::KeyOwnerProof,
) -> Option<()> {
T::HandleEquivocation::submit_unsigned_equivocation_report(
equivocation_proof,
key_owner_proof,
)
.ok()
}
}
impl<T: Trait> OnTimestampSet<T::Moment> for Module<T> {
+308 -31
View File
@@ -18,27 +18,37 @@
//! Test utilities
use codec::Encode;
use super::{Trait, Module, GenesisConfig, CurrentSlot};
use super::{Trait, Module, CurrentSlot};
use sp_runtime::{
Perbill, impl_opaque_keys,
testing::{Header, UintAuthorityId, Digest, DigestItem},
traits::IdentityLookup,
curve::PiecewiseLinear,
testing::{Digest, DigestItem, Header, TestXt,},
traits::{Convert, Header as _, IdentityLookup, OpaqueKeys, SaturatedConversion},
};
use frame_system::InitKind;
use frame_support::{
impl_outer_origin, parameter_types, StorageValue,
traits::OnInitialize,
impl_outer_dispatch, impl_outer_origin, parameter_types, StorageValue,
traits::{KeyOwnerProofSystem, OnInitialize},
weights::Weight,
};
use sp_io;
use sp_core::{H256, U256, crypto::Pair};
use sp_consensus_babe::AuthorityPair;
use sp_core::{H256, U256, crypto::{KeyTypeId, Pair}};
use sp_consensus_babe::{AuthorityId, AuthorityPair, SlotNumber};
use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof};
use sp_staking::SessionIndex;
use pallet_staking::EraIndex;
impl_outer_origin!{
pub enum Origin for Test where system = frame_system {}
}
impl_outer_dispatch! {
pub enum Call for Test where origin: Origin {
babe::Babe,
staking::Staking,
}
}
type DummyValidatorId = u64;
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
@@ -50,7 +60,6 @@ parameter_types! {
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
pub const MinimumPeriod: u64 = 1;
pub const EpochDuration: u64 = 3;
pub const ExpectedBlockTime: u64 = 1;
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(16);
@@ -61,7 +70,7 @@ impl frame_system::Trait for Test {
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Call = Call;
type Hash = H256;
type Version = ();
type Hashing = sp_runtime::traits::BlakeTwo256;
@@ -78,27 +87,55 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type MaximumBlockLength = MaximumBlockLength;
type ModuleToIndex = ();
type AccountData = ();
type AccountData = pallet_balances::AccountData<u128>;
type OnNewAccount = ();
type OnKilledAccount = ();
}
impl<C> frame_system::offchain::SendTransactionTypes<C> for Test
where
Call: From<C>,
{
type OverarchingCall = Call;
type Extrinsic = TestXt<Call, ()>;
}
impl_opaque_keys! {
pub struct MockSessionKeys {
pub dummy: UintAuthorityId,
pub babe_authority: super::Module<Test>,
}
}
impl pallet_session::Trait for Test {
type Event = ();
type ValidatorId = <Self as frame_system::Trait>::AccountId;
type ValidatorIdOf = pallet_staking::StashOf<Self>;
type ShouldEndSession = Babe;
type SessionHandler = (Babe,);
type SessionManager = ();
type ValidatorIdOf = ();
type NextSessionRotation = Babe;
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, Staking>;
type SessionHandler = <MockSessionKeys as OpaqueKeys>::KeyTypeIdProviders;
type Keys = MockSessionKeys;
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
type NextSessionRotation = Babe;
}
impl pallet_session::historical::Trait for Test {
type FullIdentification = pallet_staking::Exposure<u64, u128>;
type FullIdentificationOf = pallet_staking::ExposureOf<Self>;
}
parameter_types! {
pub const UncleGenerations: u64 = 0;
}
impl pallet_authorship::Trait for Test {
type FindAuthor = pallet_session::FindAccountFromAuthorIndex<Self, Babe>;
type UncleGenerations = UncleGenerations;
type FilterUncle = ();
type EventHandler = ();
}
parameter_types! {
pub const MinimumPeriod: u64 = 1;
}
impl pallet_timestamp::Trait for Test {
@@ -107,33 +144,142 @@ impl pallet_timestamp::Trait for Test {
type MinimumPeriod = MinimumPeriod;
}
parameter_types! {
pub const ExistentialDeposit: u128 = 1;
}
impl pallet_balances::Trait for Test {
type Balance = u128;
type DustRemoval = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
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<u128, u128> for CurrencyToVoteHandler {
fn convert(x: u128) -> u128 {
x
}
}
impl Convert<u128, u64> for CurrencyToVoteHandler {
fn convert(x: u128) -> u64 {
x.saturated_into()
}
}
impl pallet_staking::Trait for Test {
type RewardRemainder = ();
type CurrencyToVote = CurrencyToVoteHandler;
type Event = ();
type Currency = Balances;
type Slash = ();
type Reward = ();
type SessionsPerEra = SessionsPerEra;
type BondingDuration = BondingDuration;
type SlashDeferDuration = SlashDeferDuration;
type SlashCancelOrigin = frame_system::EnsureRoot<Self::AccountId>;
type SessionInterface = Self;
type UnixTime = pallet_timestamp::Module<Test>;
type RewardCurve = RewardCurve;
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
type NextNewSession = Session;
type ElectionLookahead = ElectionLookahead;
type Call = Call;
type UnsignedPriority = StakingUnsignedPriority;
type MaxIterations = ();
type MinSolutionScoreBump = ();
}
parameter_types! {
pub OffencesWeightSoftLimit: Weight = Perbill::from_percent(60) * MaximumBlockWeight::get();
}
impl pallet_offences::Trait for Test {
type Event = ();
type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>;
type OnOffenceHandler = Staking;
type WeightSoftLimit = OffencesWeightSoftLimit;
}
impl Trait for Test {
type EpochDuration = EpochDuration;
type ExpectedBlockTime = ExpectedBlockTime;
type EpochChangeTrigger = crate::ExternalTrigger;
type KeyOwnerProofSystem = Historical;
type KeyOwnerProof =
<Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, AuthorityId)>>::Proof;
type KeyOwnerIdentification = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(
KeyTypeId,
AuthorityId,
)>>::IdentificationTuple;
type HandleEquivocation = super::EquivocationHandler<Self::KeyOwnerIdentification, Offences>;
}
pub fn new_test_ext(authorities_len: usize) -> (Vec<AuthorityPair>, sp_io::TestExternalities) {
let pairs = (0..authorities_len).map(|i| {
AuthorityPair::from_seed(&U256::from(i).into())
}).collect::<Vec<_>>();
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
GenesisConfig {
authorities: pairs.iter().map(|a| (a.public(), 1)).collect(),
}.assimilate_storage::<Test>(&mut t).unwrap();
(pairs, t.into())
}
pub type Balances = pallet_balances::Module<Test>;
pub type Historical = pallet_session::historical::Module<Test>;
pub type Offences = pallet_offences::Module<Test>;
pub type Session = pallet_session::Module<Test>;
pub type Staking = pallet_staking::Module<Test>;
pub type System = frame_system::Module<Test>;
pub type Timestamp = pallet_timestamp::Module<Test>;
pub type Babe = Module<Test>;
pub fn go_to_block(n: u64, s: u64) {
use frame_support::traits::OnFinalize;
System::on_finalize(System::block_number());
Session::on_finalize(System::block_number());
Staking::on_finalize(System::block_number());
let parent_hash = if System::block_number() > 1 {
let hdr = System::finalize();
hdr.hash()
} else {
System::parent_hash()
};
let pre_digest = make_secondary_plain_pre_digest(0, s);
System::initialize(&n, &Default::default(), &Default::default(), &pre_digest, InitKind::Full);
System::initialize(&n, &parent_hash, &Default::default(), &pre_digest, InitKind::Full);
System::set_block_number(n);
Timestamp::set_timestamp(n);
if s > 1 {
CurrentSlot::put(s);
}
// includes a call into `Babe::do_initialize`.
System::on_initialize(n);
Session::on_initialize(n);
Staking::on_initialize(n);
}
/// Slots will grow accordingly to blocks
@@ -145,6 +291,19 @@ pub fn progress_to_block(n: u64) {
}
}
/// Progress to the first block at the given session
pub fn start_session(session_index: SessionIndex) {
let missing = (session_index - Session::current_index()) * 3;
progress_to_block(System::block_number() + missing as u64 + 1);
assert_eq!(Session::current_index(), session_index);
}
/// Progress to the first block at the given era
pub fn start_era(era_index: EraIndex) {
start_session((era_index * 3).into());
assert_eq!(Staking::current_era(), Some(era_index));
}
pub fn make_pre_digest(
authority_index: sp_consensus_babe::AuthorityIndex,
slot_number: sp_consensus_babe::SlotNumber,
@@ -177,6 +336,124 @@ pub fn make_secondary_plain_pre_digest(
Digest { logs: vec![log] }
}
pub type System = frame_system::Module<Test>;
pub type Babe = Module<Test>;
pub type Session = pallet_session::Module<Test>;
pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities {
new_test_ext_with_pairs(authorities_len).1
}
pub fn new_test_ext_with_pairs(authorities_len: usize) -> (Vec<AuthorityPair>, sp_io::TestExternalities) {
let pairs = (0..authorities_len).map(|i| {
AuthorityPair::from_seed(&U256::from(i).into())
}).collect::<Vec<_>>();
let public = pairs.iter().map(|p| p.public()).collect();
(pairs, new_test_ext_raw_authorities(public))
}
pub fn new_test_ext_raw_authorities(authorities: Vec<AuthorityId>) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default()
.build_storage::<Test>()
.unwrap();
// stashes are the index.
let session_keys: Vec<_> = authorities
.iter()
.enumerate()
.map(|(i, k)| {
(
i as u64,
i as u64,
MockSessionKeys {
babe_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,
pallet_staking::StakerStatus::<u64>::Validator,
)
})
.collect();
let balances: Vec<_> = (0..authorities.len())
.map(|i| (i as u64, 10_000_000))
.collect();
// NOTE: this will initialize the babe authorities
// through OneSessionHandler::on_genesis_session
pallet_session::GenesisConfig::<Test> { keys: session_keys }
.assimilate_storage(&mut t)
.unwrap();
pallet_balances::GenesisConfig::<Test> { balances }
.assimilate_storage(&mut t)
.unwrap();
let staking_config = pallet_staking::GenesisConfig::<Test> {
stakers,
validator_count: 8,
force_era: pallet_staking::Forcing::ForceNew,
minimum_validator_count: 0,
invulnerables: vec![],
..Default::default()
};
staking_config.assimilate_storage(&mut t).unwrap();
t.into()
}
/// Creates an equivocation at the current block, by generating two headers.
pub fn generate_equivocation_proof(
offender_authority_index: u32,
offender_authority_pair: &AuthorityPair,
slot_number: SlotNumber,
) -> sp_consensus_babe::EquivocationProof<Header> {
use sp_consensus_babe::digests::CompatibleDigestItem;
let current_block = System::block_number();
let current_slot = CurrentSlot::get();
let make_header = || {
let parent_hash = System::parent_hash();
let pre_digest = make_secondary_plain_pre_digest(offender_authority_index, slot_number);
System::initialize(&current_block, &parent_hash, &Default::default(), &pre_digest, InitKind::Full);
System::set_block_number(current_block);
Timestamp::set_timestamp(current_block);
System::finalize()
};
// sign the header prehash and sign it, adding it to the block as the seal
// digest item
let seal_header = |header: &mut Header| {
let prehash = header.hash();
let seal = <DigestItem as CompatibleDigestItem>::babe_seal(
offender_authority_pair.sign(prehash.as_ref()),
);
header.digest_mut().push(seal);
};
// generate two headers at the current block
let mut h1 = make_header();
let mut h2 = make_header();
seal_header(&mut h1);
seal_header(&mut h2);
// restore previous runtime state
go_to_block(current_block, current_slot);
sp_consensus_babe::EquivocationProof {
slot_number,
offender: offender_authority_pair.public(),
first_header: h1,
second_header: h2,
}
}
+411 -9
View File
@@ -17,13 +17,16 @@
//! Consensus extension module tests for BABE consensus.
use super::*;
use super::{Call, *};
use frame_support::{
assert_err, assert_ok,
traits::{Currency, OnFinalize},
};
use mock::*;
use frame_support::traits::OnFinalize;
use pallet_session::ShouldEndSession;
use sp_core::crypto::IsWrappedBy;
use sp_consensus_babe::AllowedSlots;
use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof};
use sp_core::crypto::{IsWrappedBy, Pair};
const EMPTY_RANDOMNESS: [u8; 32] = [
74, 25, 49, 128, 53, 97, 244, 49,
@@ -40,14 +43,14 @@ fn empty_randomness_is_correct() {
#[test]
fn initial_values() {
new_test_ext(4).1.execute_with(|| {
new_test_ext(4).execute_with(|| {
assert_eq!(Babe::authorities().len(), 4)
})
}
#[test]
fn check_module() {
new_test_ext(4).1.execute_with(|| {
new_test_ext(4).execute_with(|| {
assert!(!Babe::should_end_session(0), "Genesis does not change sessions");
assert!(!Babe::should_end_session(200000),
"BABE does not include the block number in epoch calculations");
@@ -56,7 +59,7 @@ fn check_module() {
#[test]
fn first_block_epoch_zero_start() {
let (pairs, mut ext) = new_test_ext(4);
let (pairs, mut ext) = new_test_ext_with_pairs(4);
ext.execute_with(|| {
let genesis_slot = 100;
@@ -124,7 +127,7 @@ fn first_block_epoch_zero_start() {
#[test]
fn authority_index() {
new_test_ext(4).1.execute_with(|| {
new_test_ext(4).execute_with(|| {
assert_eq!(
Babe::find_author((&[(BABE_ENGINE_ID, &[][..])]).into_iter().cloned()), None,
"Trivially invalid authorities are ignored")
@@ -133,7 +136,7 @@ fn authority_index() {
#[test]
fn can_predict_next_epoch_change() {
new_test_ext(0).1.execute_with(|| {
new_test_ext(1).execute_with(|| {
assert_eq!(<Test as Trait>::EpochDuration::get(), 3);
// this sets the genesis slot to 6;
go_to_block(1, 6);
@@ -154,7 +157,7 @@ fn can_predict_next_epoch_change() {
#[test]
fn can_enact_next_config() {
new_test_ext(0).1.execute_with(|| {
new_test_ext(1).execute_with(|| {
assert_eq!(<Test as Trait>::EpochDuration::get(), 3);
// this sets the genesis slot to 6;
go_to_block(1, 6);
@@ -183,3 +186,402 @@ fn can_enact_next_config() {
assert_eq!(header.digest.logs[2], consensus_digest.clone())
});
}
#[test]
fn report_equivocation_current_session_works() {
let (pairs, mut ext) = new_test_ext_with_pairs(3);
ext.execute_with(|| {
start_era(1);
let authorities = Babe::authorities();
let validators = Session::validators();
// make sure that all authorities have the same balance
for validator in &validators {
assert_eq!(Balances::total_balance(validator), 10_000_000);
assert_eq!(Staking::slashable_balance_of(validator), 10_000);
assert_eq!(
Staking::eras_stakers(1, validator),
pallet_staking::Exposure {
total: 10_000,
own: 10_000,
others: vec![],
},
);
}
// we will use the validator at index 0 as the offending authority
let offending_validator_index = 0;
let offending_validator_id = Session::validators()[offending_validator_index];
let offending_authority_pair = pairs
.into_iter()
.find(|p| p.public() == authorities[offending_validator_index].0)
.unwrap();
// generate an equivocation proof. it creates two headers at the given
// slot with different block hashes and signed by the given key
let equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
// create the key ownership proof
let key = (
sp_consensus_babe::KEY_TYPE,
&offending_authority_pair.public(),
);
let key_owner_proof = Historical::prove(key).unwrap();
// report the equivocation
Babe::report_equivocation_unsigned(Origin::none(), equivocation_proof, key_owner_proof)
.unwrap();
// start a new era so that the results of the offence report
// are applied at era end
start_era(2);
// check that the balance of offending validator is slashed 100%.
assert_eq!(
Balances::total_balance(&offending_validator_id),
10_000_000 - 10_000
);
assert_eq!(Staking::slashable_balance_of(&offending_validator_id), 0);
assert_eq!(
Staking::eras_stakers(2, offending_validator_id),
pallet_staking::Exposure {
total: 0,
own: 0,
others: vec![],
},
);
// check that the balances of all other validators are left intact.
for validator in &validators {
if *validator == offending_validator_id {
continue;
}
assert_eq!(Balances::total_balance(validator), 10_000_000);
assert_eq!(Staking::slashable_balance_of(validator), 10_000);
assert_eq!(
Staking::eras_stakers(2, validator),
pallet_staking::Exposure {
total: 10_000,
own: 10_000,
others: vec![],
},
);
}
})
}
#[test]
fn report_equivocation_old_session_works() {
let (pairs, mut ext) = new_test_ext_with_pairs(3);
ext.execute_with(|| {
start_era(1);
let authorities = Babe::authorities();
// we will use the validator at index 0 as the offending authority
let offending_validator_index = 0;
let offending_validator_id = Session::validators()[offending_validator_index];
let offending_authority_pair = pairs
.into_iter()
.find(|p| p.public() == authorities[offending_validator_index].0)
.unwrap();
// generate an equivocation proof at the current slot
let equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
// create the key ownership proof
let key = (
sp_consensus_babe::KEY_TYPE,
&offending_authority_pair.public(),
);
let key_owner_proof = Historical::prove(key).unwrap();
// start a new era and report the equivocation
// from the previous era
start_era(2);
// check the balance of the offending validator
assert_eq!(Balances::total_balance(&offending_validator_id), 10_000_000);
assert_eq!(
Staking::slashable_balance_of(&offending_validator_id),
10_000
);
// report the equivocation
Babe::report_equivocation_unsigned(Origin::none(), equivocation_proof, key_owner_proof)
.unwrap();
// start a new era so that the results of the offence report
// are applied at era end
start_era(3);
// check that the balance of offending validator is slashed 100%.
assert_eq!(
Balances::total_balance(&offending_validator_id),
10_000_000 - 10_000
);
assert_eq!(Staking::slashable_balance_of(&offending_validator_id), 0);
assert_eq!(
Staking::eras_stakers(3, offending_validator_id),
pallet_staking::Exposure {
total: 0,
own: 0,
others: vec![],
},
);
})
}
#[test]
fn report_equivocation_invalid_key_owner_proof() {
let (pairs, mut ext) = new_test_ext_with_pairs(3);
ext.execute_with(|| {
start_era(1);
let authorities = Babe::authorities();
// we will use the validator at index 0 as the offending authority
let offending_validator_index = 0;
let offending_authority_pair = pairs
.into_iter()
.find(|p| p.public() == authorities[offending_validator_index].0)
.unwrap();
// generate an equivocation proof at the current slot
let equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
// create the key ownership proof
let key = (
sp_consensus_babe::KEY_TYPE,
&offending_authority_pair.public(),
);
let mut key_owner_proof = Historical::prove(key).unwrap();
// we change the session index in the key ownership proof
// which should make it invalid
key_owner_proof.session = 0;
assert_err!(
Babe::report_equivocation_unsigned(
Origin::none(),
equivocation_proof.clone(),
key_owner_proof
),
Error::<Test>::InvalidKeyOwnershipProof,
);
// it should fail as well if we create a key owner proof
// for a different authority than the offender
let key = (sp_consensus_babe::KEY_TYPE, &authorities[1].0);
let key_owner_proof = Historical::prove(key).unwrap();
// we need to progress to a new era to make sure that the key
// ownership proof is properly checked, otherwise since the state
// is still available the historical module will just check
// against current session data.
start_era(2);
assert_err!(
Babe::report_equivocation_unsigned(Origin::none(), equivocation_proof, key_owner_proof),
Error::<Test>::InvalidKeyOwnershipProof,
);
})
}
#[test]
fn report_equivocation_invalid_equivocation_proof() {
use sp_runtime::traits::Header;
let (pairs, mut ext) = new_test_ext_with_pairs(3);
ext.execute_with(|| {
start_era(1);
let authorities = Babe::authorities();
// we will use the validator at index 0 as the offending authority
let offending_validator_index = 0;
let offending_authority_pair = pairs
.into_iter()
.find(|p| p.public() == authorities[offending_validator_index].0)
.unwrap();
// create the key ownership proof
let key = (
sp_consensus_babe::KEY_TYPE,
&offending_authority_pair.public(),
);
let key_owner_proof = Historical::prove(key).unwrap();
let assert_invalid_equivocation = |equivocation_proof| {
assert_err!(
Babe::report_equivocation_unsigned(
Origin::none(),
equivocation_proof,
key_owner_proof.clone(),
),
Error::<Test>::InvalidEquivocationProof,
)
};
// both headers have the same hash, no equivocation.
let mut equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
equivocation_proof.second_header = equivocation_proof.first_header.clone();
assert_invalid_equivocation(equivocation_proof);
// missing preruntime digest from one header
let mut equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
equivocation_proof.first_header.digest_mut().logs.remove(0);
assert_invalid_equivocation(equivocation_proof);
// missing seal from one header
let mut equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
equivocation_proof.first_header.digest_mut().logs.remove(1);
assert_invalid_equivocation(equivocation_proof);
// invalid slot number in proof compared to runtime digest
let mut equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
equivocation_proof.slot_number = 0;
assert_invalid_equivocation(equivocation_proof.clone());
// different slot numbers in headers
let h1 = equivocation_proof.first_header;
let mut equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get() + 1,
);
// use the header from the previous equivocation generated
// at the previous slot
equivocation_proof.first_header = h1.clone();
assert_invalid_equivocation(equivocation_proof.clone());
// invalid seal signature
let mut equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get() + 1,
);
// replace the seal digest with the digest from the
// previous header at the previous slot
equivocation_proof.first_header.digest_mut().pop();
equivocation_proof
.first_header
.digest_mut()
.push(h1.digest().logs().last().unwrap().clone());
assert_invalid_equivocation(equivocation_proof.clone());
})
}
#[test]
fn report_equivocation_validate_unsigned_prevents_duplicates() {
use sp_runtime::transaction_validity::{
InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionSource,
TransactionValidity, ValidTransaction,
};
let (pairs, mut ext) = new_test_ext_with_pairs(3);
ext.execute_with(|| {
start_era(1);
let authorities = Babe::authorities();
// generate and report an equivocation for the validator at index 0
let offending_validator_index = 0;
let offending_authority_pair = pairs
.into_iter()
.find(|p| p.public() == authorities[offending_validator_index].0)
.unwrap();
let equivocation_proof = generate_equivocation_proof(
offending_validator_index as u32,
&offending_authority_pair,
CurrentSlot::get(),
);
let key = (
sp_consensus_babe::KEY_TYPE,
&offending_authority_pair.public(),
);
let key_owner_proof = Historical::prove(key).unwrap();
let inner =
Call::report_equivocation_unsigned(equivocation_proof.clone(), key_owner_proof.clone());
// only local/inblock reports are allowed
assert_eq!(
<Babe as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::External,
&inner,
),
InvalidTransaction::Call.into(),
);
// the transaction is valid when passed as local
let tx_tag = (offending_authority_pair.public(), CurrentSlot::get());
assert_eq!(
<Babe as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::Local,
&inner,
),
TransactionValidity::Ok(ValidTransaction {
priority: TransactionPriority::max_value(),
requires: vec![],
provides: vec![("BabeEquivocation", tx_tag).encode()],
longevity: TransactionLongevity::max_value(),
propagate: false,
})
);
// the pre dispatch checks should also pass
assert_ok!(<Babe as sp_runtime::traits::ValidateUnsigned>::pre_dispatch(&inner));
// we submit the report
Babe::report_equivocation_unsigned(Origin::none(), equivocation_proof, key_owner_proof)
.unwrap();
// the report should now be considered stale and the transaction is invalid
assert_err!(
<Babe as sp_runtime::traits::ValidateUnsigned>::pre_dispatch(&inner),
InvalidTransaction::Stale,
);
});
}
+1 -35
View File
@@ -50,6 +50,7 @@ use sp_runtime::{
},
DispatchResult, Perbill,
};
use sp_session::GetSessionNumber;
use sp_staking::{
offence::{Kind, Offence, OffenceError, ReportOffence},
SessionIndex,
@@ -376,38 +377,3 @@ impl<FullIdentification: Clone> Offence<FullIdentification>
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
}
}
+5 -2
View File
@@ -49,15 +49,18 @@ use sp_runtime::{
traits::Zero,
DispatchResult, KeyTypeId,
};
use sp_session::{GetSessionNumber, GetValidatorCount};
use sp_staking::SessionIndex;
mod equivocation;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;
pub use equivocation::{
EquivocationHandler, GetSessionNumber, GetValidatorCount, GrandpaEquivocationOffence,
GrandpaOffence, GrandpaTimeSlot, HandleEquivocation, ValidateEquivocationReport,
EquivocationHandler, GrandpaEquivocationOffence, GrandpaOffence, GrandpaTimeSlot,
HandleEquivocation, ValidateEquivocationReport,
};
pub trait Trait: frame_system::Trait {
+7 -1
View File
@@ -86,10 +86,16 @@ impl ReportOffence<u64, IdentificationTuple, Offence> for OffenceHandler {
OFFENCES.with(|l| l.borrow_mut().push((reporters, offence)));
Ok(())
}
fn is_known_offence(_offenders: &[IdentificationTuple], _time_slot: &SessionIndex) -> bool {
false
}
}
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
let t = frame_system::GenesisConfig::default()
.build_storage::<Runtime>()
.unwrap();
t.into()
}
@@ -323,21 +323,16 @@ benchmarks! {
}
report_offence_babe {
let r in 1 .. MAX_REPORTERS;
let n in 0 .. MAX_NOMINATORS.min(MAX_NOMINATIONS as u32);
let o = 1;
// Make r reporters
let mut reporters = vec![];
for i in 0 .. r {
let reporter = account("reporter", i, SEED);
reporters.push(reporter);
}
// for babe equivocation reports the number of reporters
// and offenders is always 1
let reporters = vec![account("reporter", 1, SEED)];
// make sure reporters actually get rewarded
Staking::<T>::set_slash_reward_fraction(Perbill::one());
let (mut offenders, raw_offenders) = make_offenders::<T>(o, n)?;
let (mut offenders, raw_offenders) = make_offenders::<T>(1, n)?;
let keys = ImOnline::<T>::keys();
let offence = BabeEquivocationOffence {
@@ -357,9 +352,9 @@ benchmarks! {
assert_eq!(
System::<T>::event_count(), 0
+ 1 // offence
+ 2 * r // reporter (reward + endowment)
+ o // offenders slashed
+ o * n // nominators slashed
+ 2 // reporter (reward + endowment)
+ 1 // offenders slashed
+ n // nominators slashed
);
}
+9
View File
@@ -185,6 +185,15 @@ where
Ok(())
}
fn is_known_offence(offenders: &[T::IdentificationTuple], time_slot: &O::TimeSlot) -> bool {
let any_unknown = offenders.iter().any(|offender| {
let report_id = Self::report_id::<O>(time_slot, offender);
!<Reports<T>>::contains_key(&report_id)
});
!any_unknown
}
}
impl<T: Trait> Module<T> {
+71
View File
@@ -174,6 +174,77 @@ fn doesnt_deposit_event_for_dups() {
});
}
#[test]
fn reports_if_an_offence_is_dup() {
type TestOffence = Offence<u64>;
new_test_ext().execute_with(|| {
let time_slot = 42;
assert_eq!(offence_reports(KIND, time_slot), vec![]);
let offence = |time_slot, offenders| TestOffence {
validator_set_count: 5,
time_slot,
offenders,
};
let mut test_offence = offence(time_slot, vec![0]);
// the report for authority 0 at time slot 42 should not be a known
// offence
assert!(
!<Offences as ReportOffence<_, _, TestOffence>>::is_known_offence(
&test_offence.offenders,
&test_offence.time_slot
)
);
// we report an offence for authority 0 at time slot 42
Offences::report_offence(vec![], test_offence.clone()).unwrap();
// the same report should be a known offence now
assert!(
<Offences as ReportOffence<_, _, TestOffence>>::is_known_offence(
&test_offence.offenders,
&test_offence.time_slot
)
);
// and reporting it again should yield a duplicate report error
assert_eq!(
Offences::report_offence(vec![], test_offence.clone()),
Err(OffenceError::DuplicateReport)
);
// after adding a new offender to the offence report
test_offence.offenders.push(1);
// it should not be a known offence anymore
assert!(
!<Offences as ReportOffence<_, _, TestOffence>>::is_known_offence(
&test_offence.offenders,
&test_offence.time_slot
)
);
// and reporting it again should work without any error
assert_eq!(
Offences::report_offence(vec![], test_offence.clone()),
Ok(())
);
// creating a new offence for the same authorities on the next slot
// should be considered a new offence and thefore not known
let test_offence_next_slot = offence(time_slot + 1, vec![0, 1]);
assert!(
!<Offences as ReportOffence<_, _, TestOffence>>::is_known_offence(
&test_offence_next_slot.offenders,
&test_offence_next_slot.time_slot
)
);
});
}
#[test]
fn should_properly_count_offences() {
// We report two different authorities for the same issue. Ultimately, the 1st authority
@@ -13,12 +13,14 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
sp-std = { version = "2.0.0-rc4", default-features = false, path = "../../../primitives/std" }
sp-session = { version = "2.0.0-rc4", default-features = false, path = "../../../primitives/session" }
sp-runtime = { version = "2.0.0-rc4", default-features = false, path = "../../../primitives/runtime" }
frame-system = { version = "2.0.0-rc4", default-features = false, path = "../../system" }
frame-benchmarking = { version = "2.0.0-rc4", default-features = false, path = "../../benchmarking" }
frame-support = { version = "2.0.0-rc4", default-features = false, path = "../../support" }
pallet-staking = { version = "2.0.0-rc4", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" }
pallet-session = { version = "2.0.0-rc4", default-features = false, path = "../../session" }
rand = { version = "0.7.2", default-features = false }
[dev-dependencies]
serde = { version = "1.0.101" }
@@ -33,6 +35,7 @@ pallet-balances = { version = "2.0.0-rc4", path = "../../balances" }
default = ["std"]
std = [
"sp-std/std",
"sp-session/std",
"sp-runtime/std",
"frame-system/std",
"frame-benchmarking/std",
+101 -9
View File
@@ -25,20 +25,30 @@ mod mock;
use sp_std::prelude::*;
use sp_std::vec;
use frame_system::RawOrigin;
use frame_benchmarking::benchmarks;
use pallet_session::*;
use pallet_session::Module as Session;
use pallet_staking::{
MAX_NOMINATIONS,
benchmarking::create_validator_with_nominators,
use frame_support::{
codec::Decode,
storage::StorageValue,
traits::{KeyOwnerProofSystem, OnInitialize},
};
use frame_system::RawOrigin;
use pallet_session::{historical::Module as Historical, Module as Session, *};
use pallet_staking::{
benchmarking::create_validator_with_nominators, testing_utils::create_validators,
MAX_NOMINATIONS,
};
use sp_runtime::traits::{One, StaticLookup};
const MAX_VALIDATORS: u32 = 1000;
pub struct Module<T: Trait>(pallet_session::Module<T>);
pub trait Trait: pallet_session::Trait + pallet_session::historical::Trait + pallet_staking::Trait {}
pub trait Trait: pallet_session::Trait + pallet_staking::Trait {}
impl<T: Trait> OnInitialize<T::BlockNumber> for Module<T> {
fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight {
pallet_session::Module::<T>::on_initialize(n)
}
}
benchmarks! {
_ { }
@@ -59,6 +69,88 @@ benchmarks! {
let proof: Vec<u8> = vec![0,1,2,3];
Session::<T>::set_keys(RawOrigin::Signed(v_controller.clone()).into(), keys, proof)?;
}: _(RawOrigin::Signed(v_controller))
check_membership_proof_current_session {
let n in 2 .. MAX_VALIDATORS as u32;
let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n);
let key_owner_proof2 = key_owner_proof1.clone();
}: {
Historical::<T>::check_proof(key, key_owner_proof1);
}
verify {
assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some());
}
check_membership_proof_historical_session {
let n in 2 .. MAX_VALIDATORS as u32;
let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n);
// skip to the next session so that the session is historical
// and the membership merkle proof must be checked.
Session::<T>::rotate_session();
let key_owner_proof2 = key_owner_proof1.clone();
}: {
Historical::<T>::check_proof(key, key_owner_proof1);
}
verify {
assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some());
}
}
/// Sets up the benchmark for checking a membership proof. It creates the given
/// number of validators, sets random session keys and then creates a membership
/// proof for the first authority and returns its key and the proof.
fn check_membership_proof_setup<T: Trait>(
n: u32,
) -> (
(sp_runtime::KeyTypeId, &'static [u8; 32]),
sp_session::MembershipProof,
) {
pallet_staking::ValidatorCount::put(n);
// create validators and set random session keys
for (n, who) in create_validators::<T>(n, 1000)
.unwrap()
.into_iter()
.enumerate()
{
use rand::RngCore;
use rand::SeedableRng;
let validator = T::Lookup::lookup(who).unwrap();
let controller = pallet_staking::Module::<T>::bonded(validator).unwrap();
let keys = {
let mut keys = [0u8; 128];
// we keep the keys for the first validator as 0x00000...
if n > 0 {
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
rng.fill_bytes(&mut keys);
}
keys
};
let keys: T::Keys = Decode::decode(&mut &keys[..]).unwrap();
let proof: Vec<u8> = vec![];
Session::<T>::set_keys(RawOrigin::Signed(controller).into(), keys, proof).unwrap();
}
Module::<T>::on_initialize(T::BlockNumber::one());
// skip sessions until the new validator set is enacted
while Session::<T>::validators().len() < n as usize {
Session::<T>::rotate_session();
}
let key = (sp_runtime::KeyTypeId(*b"babe"), &[0u8; 32]);
(key, Historical::<T>::prove(key).unwrap())
}
#[cfg(test)]
+4
View File
@@ -3355,6 +3355,10 @@ impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
Ok(())
}
}
fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool {
R::is_known_offence(offenders, time_slot)
}
}
#[allow(deprecated)]
+1 -6
View File
@@ -30,11 +30,11 @@ pub use sp_tracing;
#[cfg(feature = "std")]
pub use serde;
pub use sp_core::Void;
#[doc(hidden)]
pub use sp_std;
#[doc(hidden)]
pub use codec;
use codec::{Decode, Encode};
#[cfg(feature = "std")]
#[doc(hidden)]
pub use once_cell;
@@ -364,11 +364,6 @@ macro_rules! assert_ok {
}
}
/// The void type - it cannot exist.
// Oh rust, you crack me up...
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug)]
pub enum Void {}
#[cfg(feature = "std")]
#[doc(hidden)]
pub use serde::{Serialize, Deserialize};
@@ -17,9 +17,10 @@ codec = { package = "parity-scale-codec", version = "1.3.1", default-features =
merlin = { version = "2.0", default-features = false }
sp-std = { version = "2.0.0-rc4", default-features = false, path = "../../std" }
sp-api = { version = "2.0.0-rc4", default-features = false, path = "../../api" }
sp-core = { version = "2.0.0-rc4", default-features = false, path = "../../core" }
sp-consensus = { version = "0.8.0-rc4", optional = true, path = "../common" }
sp-consensus-slots = { version = "0.8.0-rc4", default-features = false, path = "../slots" }
sp-consensus-vrf = { version = "0.8.0-rc4", path = "../vrf", default-features = false }
sp-core = { version = "2.0.0-rc4", default-features = false, path = "../../core" }
sp-inherents = { version = "2.0.0-rc4", default-features = false, path = "../../inherents" }
sp-runtime = { version = "2.0.0-rc4", default-features = false, path = "../../runtime" }
sp-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../../timestamp" }
@@ -27,14 +28,15 @@ sp-timestamp = { version = "2.0.0-rc4", default-features = false, path = "../../
[features]
default = ["std"]
std = [
"sp-core/std",
"sp-application-crypto/std",
"codec/std",
"merlin/std",
"sp-std/std",
"sp-api/std",
"sp-consensus",
"sp-consensus-slots/std",
"sp-consensus-vrf/std",
"sp-core/std",
"sp-inherents/std",
"sp-runtime/std",
"sp-timestamp/std",
@@ -17,18 +17,14 @@
//! Private implementation details of BABE digests.
#[cfg(feature = "std")]
use super::{BABE_ENGINE_ID, AuthoritySignature};
use super::{AuthorityId, AuthorityIndex, SlotNumber, BabeAuthorityWeight, BabeEpochConfiguration, AllowedSlots};
#[cfg(feature = "std")]
use sp_runtime::{DigestItem, generic::OpaqueDigestItemId};
#[cfg(feature = "std")]
use std::fmt::Debug;
use codec::{Decode, Encode};
#[cfg(feature = "std")]
use codec::Codec;
use super::{
AllowedSlots, AuthorityId, AuthorityIndex, AuthoritySignature, BabeAuthorityWeight,
BabeEpochConfiguration, SlotNumber, BABE_ENGINE_ID,
};
use codec::{Codec, Decode, Encode};
use sp_std::vec::Vec;
use sp_runtime::RuntimeDebug;
use sp_runtime::{generic::OpaqueDigestItemId, DigestItem, RuntimeDebug};
use sp_consensus_vrf::schnorrkel::{Randomness, VRFOutput, VRFProof};
/// Raw BABE primary slot assignment pre-digest.
@@ -151,7 +147,6 @@ impl From<NextConfigDescriptor> for BabeEpochConfiguration {
}
/// A digest item which is usable with BABE consensus.
#[cfg(feature = "std")]
pub trait CompatibleDigestItem: Sized {
/// Construct a digest item which contains a BABE pre-digest.
fn babe_pre_digest(seal: PreDigest) -> Self;
@@ -172,9 +167,8 @@ pub trait CompatibleDigestItem: Sized {
fn as_next_config_descriptor(&self) -> Option<NextConfigDescriptor>;
}
#[cfg(feature = "std")]
impl<Hash> CompatibleDigestItem for DigestItem<Hash> where
Hash: Debug + Send + Sync + Eq + Clone + Codec + 'static
Hash: Send + Sync + Eq + Clone + Codec + 'static
{
fn babe_pre_digest(digest: PreDigest) -> Self {
DigestItem::PreRuntime(BABE_ENGINE_ID, digest.encode())
+131 -8
View File
@@ -23,17 +23,21 @@
pub mod digests;
pub mod inherents;
pub use sp_consensus_vrf::schnorrkel::{
Randomness, VRF_PROOF_LENGTH, VRF_OUTPUT_LENGTH, RANDOMNESS_LENGTH
};
pub use merlin::Transcript;
pub use sp_consensus_vrf::schnorrkel::{
Randomness, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH,
};
use codec::{Encode, Decode};
use sp_std::vec::Vec;
use sp_runtime::{ConsensusEngineId, RuntimeDebug};
use codec::{Decode, Encode};
#[cfg(feature = "std")]
use sp_core::vrf::{VRFTranscriptData, VRFTranscriptValue};
use crate::digests::{NextEpochDescriptor, NextConfigDescriptor};
use sp_runtime::{traits::Header, ConsensusEngineId, RuntimeDebug};
use sp_std::vec::Vec;
use crate::digests::{NextConfigDescriptor, NextEpochDescriptor};
/// Key type for BABE module.
pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::BABE;
mod app {
use sp_application_crypto::{app_crypto, key_types::BABE, sr25519};
@@ -73,7 +77,10 @@ pub const MEDIAN_ALGORITHM_CARDINALITY: usize = 1200; // arbitrary suggestion by
pub type AuthorityIndex = u32;
/// A slot number.
pub type SlotNumber = u64;
pub use sp_consensus_slots::SlotNumber;
/// An equivocation proof for multiple block authorships on the same slot (i.e. double vote).
pub type EquivocationProof<H> = sp_consensus_slots::EquivocationProof<H, AuthorityId>;
/// The weight of an authority.
// NOTE: we use a unique name for the weight to avoid conflicts with other
@@ -256,6 +263,93 @@ pub struct BabeEpochConfiguration {
pub allowed_slots: AllowedSlots,
}
/// Verifies the equivocation proof by making sure that: both headers have
/// different hashes, are targetting the same slot, and have valid signatures by
/// the same authority.
pub fn check_equivocation_proof<H>(proof: EquivocationProof<H>) -> bool
where
H: Header,
{
use digests::*;
use sp_application_crypto::RuntimeAppPublic;
let find_pre_digest = |header: &H| {
header
.digest()
.logs()
.iter()
.find_map(|log| log.as_babe_pre_digest())
};
let verify_seal_signature = |mut header: H, offender: &AuthorityId| {
let seal = header.digest_mut().pop()?.as_babe_seal()?;
let pre_hash = header.hash();
if !offender.verify(&pre_hash.as_ref(), &seal) {
return None;
}
Some(())
};
let verify_proof = || {
// we must have different headers for the equivocation to be valid
if proof.first_header.hash() == proof.second_header.hash() {
return None;
}
let first_pre_digest = find_pre_digest(&proof.first_header)?;
let second_pre_digest = find_pre_digest(&proof.second_header)?;
// both headers must be targetting the same slot and it must
// be the same as the one in the proof.
if proof.slot_number != first_pre_digest.slot_number() ||
first_pre_digest.slot_number() != second_pre_digest.slot_number()
{
return None;
}
// both headers must have been authored by the same authority
if first_pre_digest.authority_index() != second_pre_digest.authority_index() {
return None;
}
// we finally verify that the expected authority has signed both headers and
// that the signature is valid.
verify_seal_signature(proof.first_header, &proof.offender)?;
verify_seal_signature(proof.second_header, &proof.offender)?;
Some(())
};
// NOTE: we isolate the verification code into an helper function that
// returns `Option<()>` so that we can use `?` to deal with any intermediate
// errors and discard the proof as invalid.
verify_proof().is_some()
}
/// An opaque type used to represent the key ownership proof at the runtime API
/// boundary. The inner value is an encoded representation of the actual key
/// ownership proof which will be parameterized when defining the runtime. At
/// the runtime API boundary this type is unknown and as such we keep this
/// opaque representation, implementors of the runtime API will have to make
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
#[derive(Decode, Encode, PartialEq)]
pub struct OpaqueKeyOwnershipProof(Vec<u8>);
impl OpaqueKeyOwnershipProof {
/// Create a new `OpaqueKeyOwnershipProof` using the given encoded
/// representation.
pub fn new(inner: Vec<u8>) -> OpaqueKeyOwnershipProof {
OpaqueKeyOwnershipProof(inner)
}
/// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key
/// ownership proof type.
pub fn decode<T: Decode>(self) -> Option<T> {
Decode::decode(&mut &self.0[..]).ok()
}
}
sp_api::decl_runtime_apis! {
/// API necessary for block authorship with BABE.
#[api_version(2)]
@@ -269,5 +363,34 @@ sp_api::decl_runtime_apis! {
/// Returns the slot number that started the current epoch.
fn current_epoch_start() -> SlotNumber;
/// Generates a proof of key ownership for the given authority in the
/// current epoch. An example usage of this module is coupled with the
/// session historical module to prove that a given authority key is
/// tied to a given staking identity during a specific session. Proofs
/// of key ownership are necessary for submitting equivocation reports.
/// NOTE: even though the API takes a `slot_number` as parameter the current
/// implementations ignores this parameter and instead relies on this
/// method being called at the correct block height, i.e. any point at
/// which the epoch for the given slot is live on-chain. Future
/// implementations will instead use indexed data through an offchain
/// worker, not requiring older states to be available.
fn generate_key_ownership_proof(
slot_number: SlotNumber,
authority_id: AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;
/// Submits an unsigned extrinsic to report an equivocation. The caller
/// must provide the equivocation proof and a key ownership proof
/// (should be obtained using `generate_key_ownership_proof`). The
/// extrinsic will be unsigned and should only be accepted for local
/// authorship (not to be broadcast to the network). This method returns
/// `None` when creation of the extrinsic fails, e.g. if equivocation
/// reporting is disabled for the given runtime (i.e. this method is
/// hardcoded to return `None`). Only useful in an offchain context.
fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof: EquivocationProof<Block::Header>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
}
}
@@ -0,0 +1,23 @@
[package]
name = "sp-consensus-slots"
version = "0.8.0-rc4"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Primitives for slots-based consensus"
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
sp-runtime = { version = "2.0.0-rc2", default-features = false, path = "../../runtime" }
[features]
default = ["std"]
std = [
"codec/std",
"sp-runtime/std",
]
@@ -0,0 +1,41 @@
// This file is part of Substrate.
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Primitives for slots-based consensus engines.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
/// A slot number.
pub type SlotNumber = u64;
/// Represents an equivocation proof. An equivocation happens when a validator
/// produces more than one block on the same slot. The proof of equivocation
/// are the given distinct headers that were signed by the validator and which
/// include the slot number.
#[derive(Clone, Debug, Decode, Encode, PartialEq)]
pub struct EquivocationProof<Header, Id> {
/// Returns the authority id of the equivocator.
pub offender: Id,
/// The slot number at which the equivocation happened.
pub slot_number: SlotNumber,
/// The first header involved in the equivocation.
pub first_header: Header,
/// The second header involved in the equivocation.
pub second_header: Header,
}
+5
View File
@@ -333,6 +333,11 @@ pub fn to_substrate_wasm_fn_return_value(value: &impl Encode) -> u64 {
res
}
/// The void type - it cannot exist.
// Oh rust, you crack me up...
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug)]
pub enum Void {}
/// Macro for creating `Maybe*` marker traits.
///
/// Such a maybe-marker trait requires the given bound when `feature = std` and doesn't require
+42
View File
@@ -64,6 +64,48 @@ pub struct MembershipProof {
pub validator_count: ValidatorCount,
}
/// A utility trait to get a session number. This is implemented for
/// `MembershipProof` below to fetch the session number the given session
/// membership proof is for. It is useful when we need to deal with key owner
/// proofs generically (i.e. just typing against the `KeyOwnerProofSystem`
/// trait) but still restrict their capabilities.
pub trait GetSessionNumber {
fn session(&self) -> SessionIndex;
}
/// A utility trait to get the validator count of a given session. This is
/// implemented for `MembershipProof` below and fetches the number of validators
/// in the session the membership proof is for. It is useful when we need to
/// deal with key owner proofs generically (i.e. just typing against the
/// `KeyOwnerProofSystem` trait) but still restrict their capabilities.
pub trait GetValidatorCount {
fn validator_count(&self) -> ValidatorCount;
}
impl GetSessionNumber for sp_core::Void {
fn session(&self) -> SessionIndex {
Default::default()
}
}
impl GetValidatorCount for sp_core::Void {
fn validator_count(&self) -> ValidatorCount {
Default::default()
}
}
impl GetSessionNumber for MembershipProof {
fn session(&self) -> SessionIndex {
self.session
}
}
impl GetValidatorCount for MembershipProof {
fn validator_count(&self) -> ValidatorCount {
self.validator_count
}
}
/// Generate the initial session keys with the given seeds, at the given block and store them in
/// the client's keystore.
#[cfg(feature = "std")]
+12 -1
View File
@@ -117,10 +117,21 @@ impl sp_runtime::traits::Printable for OffenceError {
pub trait ReportOffence<Reporter, Offender, O: Offence<Offender>> {
/// Report an `offence` and reward given `reporters`.
fn report_offence(reporters: Vec<Reporter>, offence: O) -> Result<(), OffenceError>;
/// Returns true iff all of the given offenders have been previously reported
/// at the given time slot. This function is useful to prevent the sending of
/// duplicate offence reports.
fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool;
}
impl<Reporter, Offender, O: Offence<Offender>> ReportOffence<Reporter, Offender, O> for () {
fn report_offence(_reporters: Vec<Reporter>, _offence: O) -> Result<(), OffenceError> { Ok(()) }
fn report_offence(_reporters: Vec<Reporter>, _offence: O) -> Result<(), OffenceError> {
Ok(())
}
fn is_known_offence(_offenders: &[Offender], _time_slot: &O::TimeSlot) -> bool {
true
}
}
/// A trait to take action on an offence.
+50 -2
View File
@@ -26,7 +26,7 @@ pub mod system;
use sp_std::{prelude::*, marker::PhantomData};
use codec::{Encode, Decode, Input, Error};
use sp_core::{OpaqueMetadata, RuntimeDebug, ChangesTrieConfiguration};
use sp_core::{offchain::KeyTypeId, ChangesTrieConfiguration, OpaqueMetadata, RuntimeDebug};
use sp_application_crypto::{ed25519, sr25519, ecdsa, RuntimeAppPublic};
use trie_db::{TrieMut, Trie};
use sp_trie::PrefixedMemoryDB;
@@ -49,7 +49,11 @@ use sp_version::RuntimeVersion;
pub use sp_core::hash::H256;
#[cfg(any(feature = "std", test))]
use sp_version::NativeVersion;
use frame_support::{impl_outer_origin, parameter_types, weights::{Weight, RuntimeDbWeight}};
use frame_support::{
impl_outer_origin, parameter_types,
traits::KeyOwnerProofSystem,
weights::{RuntimeDbWeight, Weight},
};
use sp_inherents::{CheckInherentsResult, InherentData};
use cfg_if::cfg_if;
@@ -462,6 +466,18 @@ impl pallet_babe::Trait for Runtime {
// are manually adding the digests. normally in this situation you'd use
// pallet_babe::SameAuthoritiesForever.
type EpochChangeTrigger = pallet_babe::ExternalTrigger;
type KeyOwnerProofSystem = ();
type KeyOwnerProof =
<Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, AuthorityId)>>::Proof;
type KeyOwnerIdentification = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(
KeyTypeId,
AuthorityId,
)>>::IdentificationTuple;
type HandleEquivocation = ();
}
/// Adds one to the given input and returns the final result.
@@ -690,6 +706,22 @@ cfg_if! {
fn current_epoch_start() -> SlotNumber {
<pallet_babe::Module<Runtime>>::current_epoch_start()
}
fn submit_report_equivocation_unsigned_extrinsic(
_equivocation_proof: sp_consensus_babe::EquivocationProof<
<Block as BlockT>::Header,
>,
_key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof,
) -> Option<()> {
None
}
fn generate_key_ownership_proof(
_slot_number: sp_consensus_babe::SlotNumber,
_authority_id: sp_consensus_babe::AuthorityId,
) -> Option<sp_consensus_babe::OpaqueKeyOwnershipProof> {
None
}
}
impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
@@ -916,6 +948,22 @@ cfg_if! {
fn current_epoch_start() -> SlotNumber {
<pallet_babe::Module<Runtime>>::current_epoch_start()
}
fn submit_report_equivocation_unsigned_extrinsic(
_equivocation_proof: sp_consensus_babe::EquivocationProof<
<Block as BlockT>::Header,
>,
_key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof,
) -> Option<()> {
None
}
fn generate_key_ownership_proof(
_slot_number: sp_consensus_babe::SlotNumber,
_authority_id: sp_consensus_babe::AuthorityId,
) -> Option<sp_consensus_babe::OpaqueKeyOwnershipProof> {
None
}
}
impl sp_offchain::OffchainWorkerApi<Block> for Runtime {