Files
pezkuwi-subxt/substrate/frame/session/src/historical.rs
T
André Silva fbd2ac8f3b grandpa: report equivocations (#3868)
* session: runtime api for generating session membership proofs

* grandpa: add runtime api for creating equivocation report txs

* grandpa: submit signed equivocation report transactions

* grandpa: use proper equivocation report type

* grandpa: report equivocations

* grandpa: validate equivocation proof

* grandpa: update to finality-grandpa 0.9.1

* grandpa: fix encoding of session membership proof

* grandpa: initialize set id session mapping for genesis session

* grandpa: fix bug in set_id session validation

* fix compilation

* cleanup from merge conflicts

* cleanup crate tomls

* grandpa: refactor equivocation handling to separate trait

* node-template: fix compilation

* fix test compilation

* bump finality-grandpa to v0.10.2

* rpc: fix runtime version test

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

Squashed commit of the following:

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

    Split the method to avoid confusing type error message.

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

    Make accounts optional, fix logic.

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

    Remove warning.

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

    Merge branch 'master' into td-signed-transactions

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

    Merge branch 'master' into td-signed-transactions

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

    Forgotten import.

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

    Fix naming and bounds.

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

    Add documentation to signed transactions and actually make them work.

* grandpa: skip block initialization on report submission method

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

* grandpa: unused parameters

* grandpa: remove unused method

* grandpa: enable equivocation reporting

* grandpa: add workaround for parameter encoding

* grandpa: fix localized_payload calls in tests

* fix submit_report_equivocation_extrinsic in runtimes

* node: fix submit transaction test compilation

* node: bump spec_version

* rpc: fix api version test

* grandpa: allow custom equivocation offence type

* grandpa: add test for authorities::next_change_height

* grandpa: cleanup report_equivocation function

* node: move reporting app crypto to node-primitives

* grandpa: move equivocation traits to own module

* grandpa: rename app-crypto crate import

* grandpa: export equivocation types

* node: bump spec_version

* grandpa: rename EquivocationReport to EquivocationProof

* grandpa: add missing docs to primitives

* grandpa: add missing docs to equivocation

* node: fix compilation

* grandpa: add missing docs to pallet

* node: bump spec_version

* fix whitespace

* grandpa: return error on offence reporting

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

* grandpa: use strong key in module KeyOwnerProofSystem

* grandpa: move key ownership proof to grandpa runtime api

* grandpa: remove unnecessary cloning when checking equivocation proof

* grandpa: make report_equivocation a method in Environment

* support: implement KeyOwnerProofSystem for ()

* grandpa: move KeyOwnerProofSystem to module trait

* test-utils: fix runtime compilation

* grandpa: fix test compilation

* grandpa: fix test compilation after merge

* grandpa: simplify transaction submission types

* grandpa: validate equivocation report in signed extension

* client: fix test

* node: use ValidateEquivocationReport signed extension

* grandpa: expose key ownership proof under opaque type

* grandpa: better docs on key ownership proofs

* grandpa: add note about signed extension

* grandpa: add ValidateEquivocationReport::new

* grandpa: remove skip_initialize_block from runtime api

* grandpa: use new offchain transaction submission API

* grandpa: take set_id in generate_key_ownership_proof

* grandpa: update to finality-grandpa v0.12.2

* grandpa: cleanup usages of AuthoritySet::current

* grandpa: fix test

* grandpa: add mocking utilities for equivocation reporting

* grandpa: add test for equivocation reporting

* grandpa: move SetIdSession initialization

* grandpa: add more tests

* node: enable historical session manager

* node: bump spec_version

* node: use strong key types in KeyOwnerProofSystem definitions

* grandpa: export GrandpaEquivocationOffence type
2020-05-06 17:25:51 +01:00

428 lines
13 KiB
Rust

// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! An opt-in utility for tracking historical sessions in FRAME-session.
//!
//! This is generally useful when implementing blockchains that require accountable
//! safety where validators from some amount f prior sessions must remain slashable.
//!
//! Rather than store the full session data for any given session, we instead commit
//! to the roots of merkle tries containing the session data.
//!
//! These roots and proofs of inclusion can be generated at any time during the current session.
//! Afterwards, the proofs can be fed to a consensus module when reporting misbehavior.
use sp_std::prelude::*;
use codec::{Encode, Decode};
use sp_runtime::KeyTypeId;
use sp_runtime::traits::{Convert, OpaqueKeys};
use sp_session::{MembershipProof, ValidatorCount};
use frame_support::{decl_module, decl_storage};
use frame_support::{Parameter, print};
use sp_trie::{MemoryDB, Trie, TrieMut, Recorder, EMPTY_PREFIX};
use sp_trie::trie_types::{TrieDBMut, TrieDB};
use super::{SessionIndex, Module as SessionModule};
/// Trait necessary for the historical module.
pub trait Trait: super::Trait {
/// Full identification of the validator.
type FullIdentification: Parameter;
/// A conversion from validator ID to full identification.
///
/// This should contain any references to economic actors associated with the
/// validator, since they may be outdated by the time this is queried from a
/// historical trie.
///
/// It must return the identification for the current session index.
type FullIdentificationOf: Convert<Self::ValidatorId, Option<Self::FullIdentification>>;
}
decl_storage! {
trait Store for Module<T: Trait> as Session {
/// Mapping from historical session indices to session-data root hash and validator count.
HistoricalSessions get(fn historical_root):
map hasher(twox_64_concat) SessionIndex => Option<(T::Hash, ValidatorCount)>;
/// The range of historical sessions we store. [first, last)
StoredRange: Option<(SessionIndex, SessionIndex)>;
/// Deprecated.
CachedObsolete:
map hasher(twox_64_concat) SessionIndex
=> Option<Vec<(T::ValidatorId, T::FullIdentification)>>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {}
}
impl<T: Trait> Module<T> {
/// Prune historical stored session roots up to (but not including)
/// `up_to`.
pub fn prune_up_to(up_to: SessionIndex) {
<Self as Store>::StoredRange::mutate(|range| {
let (start, end) = match *range {
Some(range) => range,
None => return, // nothing to prune.
};
let up_to = sp_std::cmp::min(up_to, end);
if up_to < start {
return // out of bounds. harmless.
}
(start..up_to).for_each(<Self as Store>::HistoricalSessions::remove);
let new_start = up_to;
*range = if new_start == end {
None // nothing is stored.
} else {
Some((new_start, end))
}
})
}
}
/// Specialization of the crate-level `SessionManager` which returns the set of full identification
/// when creating a new session.
pub trait SessionManager<ValidatorId, FullIdentification>: crate::SessionManager<ValidatorId> {
/// If there was a validator set change, its returns the set of new validators along with their
/// full identifications.
fn new_session(new_index: SessionIndex) -> Option<Vec<(ValidatorId, FullIdentification)>>;
fn start_session(start_index: SessionIndex);
fn end_session(end_index: SessionIndex);
}
/// An `SessionManager` implementation that wraps an inner `I` and also
/// sets the historical trie root of the ending session.
pub struct NoteHistoricalRoot<T, I>(sp_std::marker::PhantomData<(T, I)>);
impl<T: Trait, I> crate::SessionManager<T::ValidatorId> for NoteHistoricalRoot<T, I>
where I: SessionManager<T::ValidatorId, T::FullIdentification>
{
fn new_session(new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
StoredRange::mutate(|range| {
range.get_or_insert_with(|| (new_index, new_index)).1 = new_index + 1;
});
let new_validators_and_id = <I as SessionManager<_, _>>::new_session(new_index);
let new_validators = new_validators_and_id.as_ref().map(|new_validators| {
new_validators.iter().map(|(v, _id)| v.clone()).collect()
});
if let Some(new_validators) = new_validators_and_id {
let count = new_validators.len() as ValidatorCount;
match ProvingTrie::<T>::generate_for(new_validators) {
Ok(trie) => <HistoricalSessions<T>>::insert(new_index, &(trie.root, count)),
Err(reason) => {
print("Failed to generate historical ancestry-inclusion proof.");
print(reason);
}
};
} else {
let previous_index = new_index.saturating_sub(1);
if let Some(previous_session) = <HistoricalSessions<T>>::get(previous_index) {
<HistoricalSessions<T>>::insert(new_index, previous_session);
}
}
new_validators
}
fn start_session(start_index: SessionIndex) {
<I as SessionManager<_, _>>::start_session(start_index)
}
fn end_session(end_index: SessionIndex) {
<I as SessionManager<_, _>>::end_session(end_index)
}
}
/// A tuple of the validator's ID and their full identification.
pub type IdentificationTuple<T> = (<T as crate::Trait>::ValidatorId, <T as Trait>::FullIdentification);
/// a trie instance for checking and generating proofs.
pub struct ProvingTrie<T: Trait> {
db: MemoryDB<T::Hashing>,
root: T::Hash,
}
impl<T: Trait> ProvingTrie<T> {
fn generate_for<I>(validators: I) -> Result<Self, &'static str>
where I: IntoIterator<Item=(T::ValidatorId, T::FullIdentification)>
{
let mut db = MemoryDB::default();
let mut root = Default::default();
{
let mut trie = TrieDBMut::new(&mut db, &mut root);
for (i, (validator, full_id)) in validators.into_iter().enumerate() {
let i = i as u32;
let keys = match <SessionModule<T>>::load_keys(&validator) {
None => continue,
Some(k) => k,
};
let full_id = (validator, full_id);
// map each key to the owner index.
for key_id in T::Keys::key_ids() {
let key = keys.get_raw(*key_id);
let res = (key_id, key).using_encoded(|k|
i.using_encoded(|v| trie.insert(k, v))
);
let _ = res.map_err(|_| "failed to insert into trie")?;
}
// map each owner index to the full identification.
let _ = i.using_encoded(|k| full_id.using_encoded(|v| trie.insert(k, v)))
.map_err(|_| "failed to insert into trie")?;
}
}
Ok(ProvingTrie {
db,
root,
})
}
fn from_nodes(root: T::Hash, nodes: &[Vec<u8>]) -> Self {
use sp_trie::HashDBT;
let mut memory_db = MemoryDB::default();
for node in nodes {
HashDBT::insert(&mut memory_db, EMPTY_PREFIX, &node[..]);
}
ProvingTrie {
db: memory_db,
root,
}
}
/// Prove the full verification data for a given key and key ID.
pub fn prove(&self, key_id: KeyTypeId, key_data: &[u8]) -> Option<Vec<Vec<u8>>> {
let trie = TrieDB::new(&self.db, &self.root).ok()?;
let mut recorder = Recorder::new();
let val_idx = (key_id, key_data).using_encoded(|s| {
trie.get_with(s, &mut recorder)
.ok()?
.and_then(|raw| u32::decode(&mut &*raw).ok())
})?;
val_idx.using_encoded(|s| {
trie.get_with(s, &mut recorder)
.ok()?
.and_then(|raw| <IdentificationTuple<T>>::decode(&mut &*raw).ok())
})?;
Some(recorder.drain().into_iter().map(|r| r.data).collect())
}
/// Access the underlying trie root.
pub fn root(&self) -> &T::Hash {
&self.root
}
// Check a proof contained within the current memory-db. Returns `None` if the
// nodes within the current `MemoryDB` are insufficient to query the item.
fn query(&self, key_id: KeyTypeId, key_data: &[u8]) -> Option<IdentificationTuple<T>> {
let trie = TrieDB::new(&self.db, &self.root).ok()?;
let val_idx = (key_id, key_data).using_encoded(|s| trie.get(s))
.ok()?
.and_then(|raw| u32::decode(&mut &*raw).ok())?;
val_idx.using_encoded(|s| trie.get(s))
.ok()?
.and_then(|raw| <IdentificationTuple<T>>::decode(&mut &*raw).ok())
}
}
impl<T: Trait, D: AsRef<[u8]>> frame_support::traits::KeyOwnerProofSystem<(KeyTypeId, D)>
for Module<T>
{
type Proof = MembershipProof;
type IdentificationTuple = IdentificationTuple<T>;
fn prove(key: (KeyTypeId, D)) -> Option<Self::Proof> {
let session = <SessionModule<T>>::current_index();
let validators = <SessionModule<T>>::validators()
.into_iter()
.filter_map(|validator| {
T::FullIdentificationOf::convert(validator.clone())
.map(|full_id| (validator, full_id))
})
.collect::<Vec<_>>();
let count = validators.len() as ValidatorCount;
let trie = ProvingTrie::<T>::generate_for(validators).ok()?;
let (id, data) = key;
trie.prove(id, data.as_ref())
.map(|trie_nodes| MembershipProof {
session,
trie_nodes,
validator_count: count,
})
}
fn check_proof(key: (KeyTypeId, D), proof: Self::Proof) -> Option<IdentificationTuple<T>> {
let (id, data) = key;
if proof.session == <SessionModule<T>>::current_index() {
<SessionModule<T>>::key_owner(id, data.as_ref()).and_then(|owner| {
T::FullIdentificationOf::convert(owner.clone()).and_then(move |id| {
let count = <SessionModule<T>>::validators().len() as ValidatorCount;
if count != proof.validator_count {
return None;
}
Some((owner, id))
})
})
} else {
let (root, count) = <HistoricalSessions<T>>::get(&proof.session)?;
if count != proof.validator_count {
return None;
}
let trie = ProvingTrie::<T>::from_nodes(root, &proof.trie_nodes);
trie.query(id, data.as_ref())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::crypto::key_types::DUMMY;
use sp_runtime::testing::UintAuthorityId;
use crate::mock::{
NEXT_VALIDATORS, force_new_session,
set_next_validators, Test, System, Session,
};
use frame_support::traits::{KeyOwnerProofSystem, OnInitialize};
type Historical = Module<Test>;
fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
crate::GenesisConfig::<Test> {
keys: NEXT_VALIDATORS.with(|l|
l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect()
),
}.assimilate_storage(&mut t).unwrap();
sp_io::TestExternalities::new(t)
}
#[test]
fn generated_proof_is_good() {
new_test_ext().execute_with(|| {
set_next_validators(vec![1, 2]);
force_new_session();
System::set_block_number(1);
Session::on_initialize(1);
let encoded_key_1 = UintAuthorityId(1).encode();
let proof = Historical::prove((DUMMY, &encoded_key_1[..])).unwrap();
// proof-checking in the same session is OK.
assert!(Historical::check_proof((DUMMY, &encoded_key_1[..]), proof.clone()).is_some());
set_next_validators(vec![1, 2, 4]);
force_new_session();
System::set_block_number(2);
Session::on_initialize(2);
assert!(Historical::historical_root(proof.session).is_some());
assert!(Session::current_index() > proof.session);
// proof-checking in the next session is also OK.
assert!(Historical::check_proof((DUMMY, &encoded_key_1[..]), proof.clone()).is_some());
set_next_validators(vec![1, 2, 5]);
force_new_session();
System::set_block_number(3);
Session::on_initialize(3);
});
}
#[test]
fn prune_up_to_works() {
new_test_ext().execute_with(|| {
for i in 1..99u64 {
set_next_validators(vec![i]);
force_new_session();
System::set_block_number(i);
Session::on_initialize(i);
}
assert_eq!(StoredRange::get(), Some((0, 100)));
for i in 0..100 {
assert!(Historical::historical_root(i).is_some())
}
Historical::prune_up_to(10);
assert_eq!(StoredRange::get(), Some((10, 100)));
Historical::prune_up_to(9);
assert_eq!(StoredRange::get(), Some((10, 100)));
for i in 10..100 {
assert!(Historical::historical_root(i).is_some())
}
Historical::prune_up_to(99);
assert_eq!(StoredRange::get(), Some((99, 100)));
Historical::prune_up_to(100);
assert_eq!(StoredRange::get(), None);
for i in 99..199u64 {
set_next_validators(vec![i]);
force_new_session();
System::set_block_number(i);
Session::on_initialize(i);
}
assert_eq!(StoredRange::get(), Some((100, 200)));
for i in 100..200 {
assert!(Historical::historical_root(i).is_some())
}
Historical::prune_up_to(9999);
assert_eq!(StoredRange::get(), None);
for i in 100..200 {
assert!(Historical::historical_root(i).is_none())
}
});
}
}