mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 18:11:10 +00:00
Generalize the Consensus Infrastructure (#883)
* Split out Consensus * Supply ImportQueue through network-service - simplify ImportQueue.import_blocks - remove Deadlock on import_block - Adding Verifier-Trait - Implement import_queue provisioning in service; allow cli to import * Allow to actually customize import queue * Consensus Gossip: Cache Message hash per Topic
This commit is contained in:
committed by
GitHub
parent
a24e61cb29
commit
ac4bcf879f
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "substrate-consensus-common"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Common utilities for substrate consensus"
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-primitives = { path= "../../primitives"}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate Consensus Common.
|
||||
|
||||
// Substrate Demo 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 Consensus Common 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 Consensus Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tracks offline validators.
|
||||
#![allow(dead_code)]
|
||||
|
||||
#![cfg(feature="rhd")]
|
||||
|
||||
extern crate substrate_primitives as primitives;
|
||||
|
||||
use primitives::{generic::BlockId, Justification};
|
||||
use primitives::traits::{Block, Header};
|
||||
|
||||
/// Block import trait.
|
||||
pub trait BlockImport<B: Block> {
|
||||
/// Import a block alongside its corresponding justification.
|
||||
fn import_block(&self, block: B, justification: Justification, authorities: &[AuthorityId]) -> bool;
|
||||
}
|
||||
|
||||
pub mod offline_tracker;
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2018 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/>.
|
||||
|
||||
//! Tracks offline validators.
|
||||
|
||||
use node_primitives::AccountId;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
// time before we report a validator.
|
||||
const REPORT_TIME: Duration = Duration::from_secs(60 * 5);
|
||||
|
||||
struct Observed {
|
||||
last_round_end: Instant,
|
||||
offline_since: Instant,
|
||||
}
|
||||
|
||||
impl Observed {
|
||||
fn new() -> Observed {
|
||||
let now = Instant::now();
|
||||
Observed {
|
||||
last_round_end: now,
|
||||
offline_since: now,
|
||||
}
|
||||
}
|
||||
|
||||
fn note_round_end(&mut self, was_online: bool) {
|
||||
let now = Instant::now();
|
||||
|
||||
self.last_round_end = now;
|
||||
if was_online {
|
||||
self.offline_since = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
// can happen if clocks are not monotonic
|
||||
if self.offline_since > self.last_round_end { return true }
|
||||
self.last_round_end.duration_since(self.offline_since) < REPORT_TIME
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks offline validators and can issue a report for those offline.
|
||||
pub struct OfflineTracker {
|
||||
observed: HashMap<AccountId, Observed>,
|
||||
}
|
||||
|
||||
impl OfflineTracker {
|
||||
/// Create a new tracker.
|
||||
pub fn new() -> Self {
|
||||
OfflineTracker { observed: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Note new consensus is starting with the given set of validators.
|
||||
pub fn note_new_block(&mut self, validators: &[AccountId]) {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let set: HashSet<_> = validators.iter().cloned().collect();
|
||||
self.observed.retain(|k, _| set.contains(k));
|
||||
}
|
||||
|
||||
/// Note that a round has ended.
|
||||
pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) {
|
||||
self.observed.entry(validator)
|
||||
.or_insert_with(Observed::new)
|
||||
.note_round_end(was_online);
|
||||
}
|
||||
|
||||
/// Generate a vector of indices for offline account IDs.
|
||||
pub fn reports(&self, validators: &[AccountId]) -> Vec<u32> {
|
||||
validators.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| if self.is_online(v) {
|
||||
None
|
||||
} else {
|
||||
Some(i as u32)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether reports on a validator set are consistent with our view of things.
|
||||
pub fn check_consistency(&self, validators: &[AccountId], reports: &[u32]) -> bool {
|
||||
reports.iter().cloned().all(|r| {
|
||||
let v = match validators.get(r as usize) {
|
||||
Some(v) => v,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// we must think all validators reported externally are offline.
|
||||
let thinks_online = self.is_online(v);
|
||||
!thinks_online
|
||||
})
|
||||
}
|
||||
|
||||
fn is_online(&self, v: &AccountId) -> bool {
|
||||
self.observed.get(v).map(Observed::is_active).unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn validator_offline() {
|
||||
let mut tracker = OfflineTracker::new();
|
||||
let v = [0; 32].into();
|
||||
let v2 = [1; 32].into();
|
||||
let v3 = [2; 32].into();
|
||||
tracker.note_round_end(v, true);
|
||||
tracker.note_round_end(v2, true);
|
||||
tracker.note_round_end(v3, true);
|
||||
|
||||
let slash_time = REPORT_TIME + Duration::from_secs(5);
|
||||
tracker.observed.get_mut(&v).unwrap().offline_since -= slash_time;
|
||||
tracker.observed.get_mut(&v2).unwrap().offline_since -= slash_time;
|
||||
|
||||
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]);
|
||||
|
||||
tracker.note_new_block(&[v, v3]);
|
||||
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "substrate-consensus-rhd"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Rhododendron Round-Based consensus-algorithm for substrate"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.17"
|
||||
parity-codec = { version = "1.1" }
|
||||
parity-codec-derive = { version = "2.0" }
|
||||
substrate-primitives = { path = "../../primitives" }
|
||||
srml-support = { path = "../../../srml/support" }
|
||||
sr-primitives = { path = "../../sr-primitives" }
|
||||
sr-version = { path = "../../sr-version" }
|
||||
sr-io = { path = "../../sr-io" }
|
||||
srml-consensus = { path = "../../../srml/consensus" }
|
||||
tokio = "0.1.7"
|
||||
parking_lot = "0.4"
|
||||
error-chain = "0.12"
|
||||
log = "0.3"
|
||||
rhododendron = { git = "https://github.com/paritytech/rhododendron.git", features = ["codec"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-keyring = { path = "../../keyring" }
|
||||
substrate-executor = { path = "../../executor" }
|
||||
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde/std",
|
||||
"substrate-primitives/std",
|
||||
"srml-support/std",
|
||||
"sr-primitives/std",
|
||||
"sr-version/std",
|
||||
]
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright 2017-2018 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/>.
|
||||
|
||||
//! Error types in the BFT service.
|
||||
use runtime_version::RuntimeVersion;
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
/// Missing state at block with given descriptor.
|
||||
StateUnavailable(b: String) {
|
||||
description("State missing at given block."),
|
||||
display("State unavailable at block {}", b),
|
||||
}
|
||||
|
||||
/// I/O terminated unexpectedly
|
||||
IoTerminated {
|
||||
description("I/O terminated unexpectedly."),
|
||||
display("I/O terminated unexpectedly."),
|
||||
}
|
||||
|
||||
/// Unable to schedule wakeup.
|
||||
FaultyTimer(e: ::tokio::timer::Error) {
|
||||
description("Timer error"),
|
||||
display("Timer error: {}", e),
|
||||
}
|
||||
|
||||
/// Unable to propose a block.
|
||||
CannotPropose {
|
||||
description("Unable to create block proposal."),
|
||||
display("Unable to create block proposal."),
|
||||
}
|
||||
|
||||
/// Error checking signature
|
||||
InvalidSignature(s: ::primitives::ed25519::Signature, a: ::primitives::AuthorityId) {
|
||||
description("Message signature is invalid"),
|
||||
display("Message signature {:?} by {:?} is invalid.", s, a),
|
||||
}
|
||||
|
||||
/// Account is not an authority.
|
||||
InvalidAuthority(a: ::primitives::AuthorityId) {
|
||||
description("Message sender is not a valid authority"),
|
||||
display("Message sender {:?} is not a valid authority.", a),
|
||||
}
|
||||
|
||||
/// Authoring interface does not match the runtime.
|
||||
IncompatibleAuthoringRuntime(native: RuntimeVersion, on_chain: RuntimeVersion) {
|
||||
description("Authoring for current runtime is not supported"),
|
||||
display("Authoring for current runtime is not supported. Native ({}) cannot author for on-chain ({}).", native, on_chain),
|
||||
}
|
||||
|
||||
/// Authoring interface does not match the runtime.
|
||||
RuntimeVersionMissing {
|
||||
description("Current runtime has no version"),
|
||||
display("Authoring for current runtime is not supported since it has no version."),
|
||||
}
|
||||
|
||||
/// Authoring interface does not match the runtime.
|
||||
NativeRuntimeMissing {
|
||||
description("This build has no native runtime"),
|
||||
display("Authoring in current build is not supported since it has no runtime."),
|
||||
}
|
||||
|
||||
/// Justification requirements not met.
|
||||
InvalidJustification {
|
||||
description("Invalid justification"),
|
||||
display("Invalid justification."),
|
||||
}
|
||||
|
||||
/// Some other error.
|
||||
Other(e: Box<::std::error::Error + Send>) {
|
||||
description("Other error")
|
||||
display("Other error: {}", e.description())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::rhododendron::InputStreamConcluded> for Error {
|
||||
fn from(_: ::rhododendron::InputStreamConcluded) -> Error {
|
||||
ErrorKind::IoTerminated.into()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
// Copyright 2017-2018 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/>.
|
||||
|
||||
//! Utility for substrate-based runtimes that want to check misbehavior reports.
|
||||
|
||||
use codec::{Codec, Encode};
|
||||
use primitives::{AuthorityId, Signature};
|
||||
|
||||
use rhododendron::messages::{Action, Message, MisbehaviorKind};
|
||||
use runtime_io;
|
||||
|
||||
// check a message signature. returns true if signed by that authority.
|
||||
fn check_message_sig<B: Codec, H: Codec>(
|
||||
message: Message<B, H>,
|
||||
signature: &Signature,
|
||||
from: &AuthorityId
|
||||
) -> bool {
|
||||
let msg: Vec<u8> = message.encode();
|
||||
runtime_io::ed25519_verify(&signature.0, &msg, from)
|
||||
}
|
||||
|
||||
fn prepare<B, H>(parent: H, round_number: u32, hash: H) -> Message<B, H> {
|
||||
Message {
|
||||
parent,
|
||||
action: Action::Prepare(round_number, hash),
|
||||
}
|
||||
}
|
||||
|
||||
fn commit<B, H>(parent: H, round_number: u32, hash: H) -> Message<B, H> {
|
||||
Message {
|
||||
parent,
|
||||
action: Action::Commit(round_number, hash),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate misbehavior.
|
||||
///
|
||||
/// Doesn't check that the header hash in question is
|
||||
/// valid or whether the misbehaving authority was part of
|
||||
/// the set at that block.
|
||||
pub fn evaluate_misbehavior<B: Codec, H: Codec + Copy>(
|
||||
misbehaved: &AuthorityId,
|
||||
parent_hash: H,
|
||||
kind: &MisbehaviorKind<H>,
|
||||
) -> bool {
|
||||
match *kind {
|
||||
MisbehaviorKind::BftDoublePrepare(round, (h_1, ref s_1), (h_2, ref s_2)) => {
|
||||
s_1 != s_2 &&
|
||||
check_message_sig::<B, H>(prepare::<B, H>(parent_hash, round, h_1), s_1, misbehaved) &&
|
||||
check_message_sig::<B, H>(prepare::<B, H>(parent_hash, round, h_2), s_2, misbehaved)
|
||||
}
|
||||
MisbehaviorKind::BftDoubleCommit(round, (h_1, ref s_1), (h_2, ref s_2)) => {
|
||||
s_1 != s_2 &&
|
||||
check_message_sig::<B, H>(commit::<B, H>(parent_hash, round, h_1), s_1, misbehaved) &&
|
||||
check_message_sig::<B, H>(commit::<B, H>(parent_hash, round, h_2), s_2, misbehaved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use keyring::ed25519;
|
||||
use keyring::Keyring;
|
||||
use rhododendron;
|
||||
|
||||
use runtime_primitives::testing::{H256, Block as RawBlock};
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
fn sign_prepare(key: &ed25519::Pair, round: u32, hash: H256, parent_hash: H256) -> (H256, Signature) {
|
||||
let msg = ::sign_message::<Block>(
|
||||
rhododendron::Message::Vote(rhododendron::Vote::Prepare(round as _, hash)),
|
||||
key,
|
||||
parent_hash
|
||||
);
|
||||
|
||||
match msg {
|
||||
rhododendron::LocalizedMessage::Vote(vote) => (hash, vote.signature.signature),
|
||||
_ => panic!("signing vote leads to signed vote"),
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_commit(key: &ed25519::Pair, round: u32, hash: H256, parent_hash: H256) -> (H256, Signature) {
|
||||
let msg = ::sign_message::<Block>(
|
||||
rhododendron::Message::Vote(rhododendron::Vote::Commit(round as _, hash)),
|
||||
key,
|
||||
parent_hash
|
||||
);
|
||||
|
||||
match msg {
|
||||
rhododendron::LocalizedMessage::Vote(vote) => (hash, vote.signature.signature),
|
||||
_ => panic!("signing vote leads to signed vote"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluates_double_prepare() {
|
||||
let key: ed25519::Pair = Keyring::One.into();
|
||||
let parent_hash = [0xff; 32].into();
|
||||
let hash_1 = [0; 32].into();
|
||||
let hash_2 = [1; 32].into();
|
||||
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoublePrepare(
|
||||
1,
|
||||
sign_prepare(&key, 1, hash_1, parent_hash),
|
||||
sign_prepare(&key, 1, hash_2, parent_hash)
|
||||
)
|
||||
));
|
||||
|
||||
// same signature twice is not misbehavior.
|
||||
let signed = sign_prepare(&key, 1, hash_1, parent_hash);
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoublePrepare(
|
||||
1,
|
||||
signed,
|
||||
signed,
|
||||
)
|
||||
) == false);
|
||||
|
||||
// misbehavior has wrong target.
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&Keyring::Two.to_raw_public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoublePrepare(
|
||||
1,
|
||||
sign_prepare(&key, 1, hash_1, parent_hash),
|
||||
sign_prepare(&key, 1, hash_2, parent_hash),
|
||||
)
|
||||
) == false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluates_double_commit() {
|
||||
let key: ed25519::Pair = Keyring::One.into();
|
||||
let parent_hash = [0xff; 32].into();
|
||||
let hash_1 = [0; 32].into();
|
||||
let hash_2 = [1; 32].into();
|
||||
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoubleCommit(
|
||||
1,
|
||||
sign_commit(&key, 1, hash_1, parent_hash),
|
||||
sign_commit(&key, 1, hash_2, parent_hash)
|
||||
)
|
||||
));
|
||||
|
||||
// same signature twice is not misbehavior.
|
||||
let signed = sign_commit(&key, 1, hash_1, parent_hash);
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoubleCommit(
|
||||
1,
|
||||
signed,
|
||||
signed,
|
||||
)
|
||||
) == false);
|
||||
|
||||
// misbehavior has wrong target.
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&Keyring::Two.to_raw_public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoubleCommit(
|
||||
1,
|
||||
sign_commit(&key, 1, hash_1, parent_hash),
|
||||
sign_commit(&key, 1, hash_2, parent_hash),
|
||||
)
|
||||
) == false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user