mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 12:11:02 +00:00
Slash Authorities for irrefutable misbehavior (#84)
* double-commit and prepare misbehavior * get misbehavior on completion * collect misbehavior on drop, not only on success * kill unused transaction_index field * add primitive misbehavior report type * add misbehavior report transaction * store prior session * fix set_items * basic checks for misbehavior reports * crate for substrate bft misbehavior checking * integrate misbehavior check crate * fix comment * new wasm binaries * fix hash in test * import misbehavior transactions into queue * fix test build * sign on digest and full proposal when proposing * detect proposal misbehavior * fix fallout * restore balance/bondage types
This commit is contained in:
committed by
GitHub
parent
de6e7e9136
commit
27c9e6de9a
Generated
+13
@@ -1044,6 +1044,7 @@ dependencies = [
|
|||||||
"ed25519 0.1.0",
|
"ed25519 0.1.0",
|
||||||
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"polkadot-api 0.1.0",
|
"polkadot-api 0.1.0",
|
||||||
"polkadot-collator 0.1.0",
|
"polkadot-collator 0.1.0",
|
||||||
@@ -1097,6 +1098,7 @@ dependencies = [
|
|||||||
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"substrate-codec 0.1.0",
|
"substrate-codec 0.1.0",
|
||||||
"substrate-keyring 0.1.0",
|
"substrate-keyring 0.1.0",
|
||||||
|
"substrate-misbehavior-check 0.1.0",
|
||||||
"substrate-primitives 0.1.0",
|
"substrate-primitives 0.1.0",
|
||||||
"substrate-runtime-io 0.1.0",
|
"substrate-runtime-io 0.1.0",
|
||||||
"substrate-runtime-std 0.1.0",
|
"substrate-runtime-std 0.1.0",
|
||||||
@@ -1524,6 +1526,17 @@ dependencies = [
|
|||||||
"hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "substrate-misbehavior-check"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"substrate-bft 0.1.0",
|
||||||
|
"substrate-codec 0.1.0",
|
||||||
|
"substrate-keyring 0.1.0",
|
||||||
|
"substrate-primitives 0.1.0",
|
||||||
|
"substrate-runtime-io 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "substrate-network"
|
name = "substrate-network"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ members = [
|
|||||||
"substrate/executor",
|
"substrate/executor",
|
||||||
"substrate/keyring",
|
"substrate/keyring",
|
||||||
"substrate/network",
|
"substrate/network",
|
||||||
|
"substrate/misbehavior-check",
|
||||||
"substrate/primitives",
|
"substrate/primitives",
|
||||||
"substrate/rpc-servers",
|
"substrate/rpc-servers",
|
||||||
"substrate/rpc",
|
"substrate/rpc",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ parking_lot = "0.4"
|
|||||||
tokio-timer = "0.1.2"
|
tokio-timer = "0.1.2"
|
||||||
ed25519 = { path = "../../substrate/ed25519" }
|
ed25519 = { path = "../../substrate/ed25519" }
|
||||||
error-chain = "0.11"
|
error-chain = "0.11"
|
||||||
|
log = "0.4"
|
||||||
polkadot-api = { path = "../api" }
|
polkadot-api = { path = "../api" }
|
||||||
polkadot-collator = { path = "../collator" }
|
polkadot-collator = { path = "../collator" }
|
||||||
polkadot-primitives = { path = "../primitives" }
|
polkadot-primitives = { path = "../primitives" }
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ extern crate substrate_primitives as primitives;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate error_chain;
|
extern crate error_chain;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -55,9 +58,9 @@ use polkadot_api::{PolkadotApi, BlockBuilder};
|
|||||||
use polkadot_primitives::{Hash, Timestamp};
|
use polkadot_primitives::{Hash, Timestamp};
|
||||||
use polkadot_primitives::block::Block as PolkadotBlock;
|
use polkadot_primitives::block::Block as PolkadotBlock;
|
||||||
use polkadot_primitives::parachain::{Id as ParaId, DutyRoster, BlockData, Extrinsic, CandidateReceipt};
|
use polkadot_primitives::parachain::{Id as ParaId, DutyRoster, BlockData, Extrinsic, CandidateReceipt};
|
||||||
use primitives::block::{Block as SubstrateBlock, Header as SubstrateHeader, HeaderHash, Id as BlockId};
|
use primitives::block::{Block as SubstrateBlock, Header as SubstrateHeader, HeaderHash, Id as BlockId, Number as BlockNumber};
|
||||||
use primitives::AuthorityId;
|
use primitives::AuthorityId;
|
||||||
use transaction_pool::TransactionPool;
|
use transaction_pool::{Ready, TransactionPool};
|
||||||
|
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
@@ -477,17 +480,19 @@ impl<C: PolkadotApi, N: Network> bft::ProposerFactory for ProposerFactory<C, N>
|
|||||||
let duty_roster = self.client.duty_roster(&checked_id)?;
|
let duty_roster = self.client.duty_roster(&checked_id)?;
|
||||||
|
|
||||||
let group_info = make_group_info(duty_roster, authorities)?;
|
let group_info = make_group_info(duty_roster, authorities)?;
|
||||||
let table = Arc::new(SharedTable::new(group_info, sign_with, parent_hash));
|
let table = Arc::new(SharedTable::new(group_info, sign_with.clone(), parent_hash));
|
||||||
let router = self.network.table_router(table.clone());
|
let router = self.network.table_router(table.clone());
|
||||||
|
|
||||||
// TODO [PoC-2]: kick off collation process.
|
// TODO [PoC-2]: kick off collation process.
|
||||||
Ok(Proposer {
|
Ok(Proposer {
|
||||||
parent_hash,
|
parent_hash,
|
||||||
|
parent_number: parent_header.number,
|
||||||
parent_id: checked_id,
|
parent_id: checked_id,
|
||||||
_table: table,
|
local_key: sign_with,
|
||||||
_router: router,
|
|
||||||
client: self.client.clone(),
|
client: self.client.clone(),
|
||||||
transaction_pool: self.transaction_pool.clone(),
|
transaction_pool: self.transaction_pool.clone(),
|
||||||
|
_table: table,
|
||||||
|
_router: router,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,8 +508,10 @@ fn current_timestamp() -> Timestamp {
|
|||||||
/// The Polkadot proposer logic.
|
/// The Polkadot proposer logic.
|
||||||
pub struct Proposer<C: PolkadotApi, R> {
|
pub struct Proposer<C: PolkadotApi, R> {
|
||||||
parent_hash: HeaderHash,
|
parent_hash: HeaderHash,
|
||||||
|
parent_number: BlockNumber,
|
||||||
parent_id: C::CheckedBlockId,
|
parent_id: C::CheckedBlockId,
|
||||||
client: Arc<C>,
|
client: Arc<C>,
|
||||||
|
local_key: Arc<ed25519::Pair>,
|
||||||
transaction_pool: Arc<Mutex<TransactionPool>>,
|
transaction_pool: Arc<Mutex<TransactionPool>>,
|
||||||
_table: Arc<SharedTable>,
|
_table: Arc<SharedTable>,
|
||||||
_router: R,
|
_router: R,
|
||||||
@@ -516,8 +523,6 @@ impl<C: PolkadotApi, R: TableRouter> bft::Proposer for Proposer<C, R> {
|
|||||||
type Evaluate = Result<bool, Error>;
|
type Evaluate = Result<bool, Error>;
|
||||||
|
|
||||||
fn propose(&self) -> Result<SubstrateBlock, Error> {
|
fn propose(&self) -> Result<SubstrateBlock, Error> {
|
||||||
use transaction_pool::Ready;
|
|
||||||
|
|
||||||
// TODO: handle case when current timestamp behind that in state.
|
// TODO: handle case when current timestamp behind that in state.
|
||||||
let mut block_builder = self.client.build_block(
|
let mut block_builder = self.client.build_block(
|
||||||
&self.parent_id,
|
&self.parent_id,
|
||||||
@@ -565,6 +570,65 @@ impl<C: PolkadotApi, R: TableRouter> bft::Proposer for Proposer<C, R> {
|
|||||||
fn evaluate(&self, proposal: &SubstrateBlock) -> Result<bool, Error> {
|
fn evaluate(&self, proposal: &SubstrateBlock) -> Result<bool, Error> {
|
||||||
evaluate_proposal(proposal, &*self.client, current_timestamp(), &self.parent_hash, &self.parent_id)
|
evaluate_proposal(proposal, &*self.client, current_timestamp(), &self.parent_hash, &self.parent_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, bft::Misbehavior)>) {
|
||||||
|
use bft::generic::Misbehavior as GenericMisbehavior;
|
||||||
|
use primitives::bft::{MisbehaviorKind, MisbehaviorReport};
|
||||||
|
use polkadot_primitives::transaction::{Function, Transaction, UncheckedTransaction};
|
||||||
|
|
||||||
|
let local_id = self.local_key.public().0;
|
||||||
|
let mut pool = self.transaction_pool.lock();
|
||||||
|
let mut next_nonce = {
|
||||||
|
let readiness_evaluator = Ready::create(self.parent_id.clone(), &*self.client);
|
||||||
|
|
||||||
|
let cur_nonce = pool.pending(readiness_evaluator)
|
||||||
|
.filter(|tx| tx.as_transaction().transaction.signed == local_id)
|
||||||
|
.last()
|
||||||
|
.map(|tx| Ok(tx.as_transaction().transaction.nonce))
|
||||||
|
.unwrap_or_else(|| self.client.nonce(&self.parent_id, local_id));
|
||||||
|
|
||||||
|
match cur_nonce {
|
||||||
|
Ok(cur_nonce) => cur_nonce + 1,
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target: "consensus", "Error computing next transaction nonce: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (target, misbehavior) in misbehavior {
|
||||||
|
let report = MisbehaviorReport {
|
||||||
|
parent_hash: self.parent_hash,
|
||||||
|
parent_number: self.parent_number,
|
||||||
|
target,
|
||||||
|
misbehavior: match misbehavior {
|
||||||
|
GenericMisbehavior::ProposeOutOfTurn(_, _, _) => continue,
|
||||||
|
GenericMisbehavior::DoublePropose(_, _, _) => continue,
|
||||||
|
GenericMisbehavior::DoublePrepare(round, (h1, s1), (h2, s2))
|
||||||
|
=> MisbehaviorKind::BftDoublePrepare(round as u32, (h1, s1.signature), (h2, s2.signature)),
|
||||||
|
GenericMisbehavior::DoubleCommit(round, (h1, s1), (h2, s2))
|
||||||
|
=> MisbehaviorKind::BftDoubleCommit(round as u32, (h1, s1.signature), (h2, s2.signature)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx = Transaction {
|
||||||
|
signed: local_id,
|
||||||
|
nonce: next_nonce,
|
||||||
|
function: Function::ReportMisbehavior(report),
|
||||||
|
};
|
||||||
|
|
||||||
|
next_nonce += 1;
|
||||||
|
|
||||||
|
let message = tx.encode();
|
||||||
|
let signature = self.local_key.sign(&message);
|
||||||
|
let tx = UncheckedTransaction {
|
||||||
|
transaction: tx,
|
||||||
|
signature,
|
||||||
|
};
|
||||||
|
|
||||||
|
pool.import(tx).expect("locally signed transaction is valid; qed");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_proposal<C: PolkadotApi>(
|
fn evaluate_proposal<C: PolkadotApi>(
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ mod tests {
|
|||||||
construct_block(
|
construct_block(
|
||||||
2,
|
2,
|
||||||
block1().1,
|
block1().1,
|
||||||
hex!("c8776c92e8012bf6b3f206448eda3f00bca26d77f220f4714c81cbc92a30e1e2").into(),
|
hex!("5604fe023cd6effd93aec9b4a008398abdd32afb3fec988a19aa853ab0424a7c").into(),
|
||||||
200_000,
|
200_000,
|
||||||
vec![
|
vec![
|
||||||
Transaction {
|
Transaction {
|
||||||
|
|||||||
@@ -80,3 +80,9 @@ pub type Signature = primitives::hash::H512;
|
|||||||
|
|
||||||
/// A timestamp: seconds since the unix epoch.
|
/// A timestamp: seconds since the unix epoch.
|
||||||
pub type Timestamp = u64;
|
pub type Timestamp = u64;
|
||||||
|
|
||||||
|
/// The balance of an account.
|
||||||
|
pub type Balance = u64;
|
||||||
|
|
||||||
|
/// The amount of bonding period left in an account. Measured in eras.
|
||||||
|
pub type Bondage = u64;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
use rstd::vec::Vec;
|
use rstd::vec::Vec;
|
||||||
use codec::{Input, Slicable};
|
use codec::{Input, Slicable};
|
||||||
|
use primitives::bft::MisbehaviorReport;
|
||||||
use ::Signature;
|
use ::Signature;
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
@@ -168,6 +169,8 @@ enum FunctionId {
|
|||||||
StakingUnstake = 0x21,
|
StakingUnstake = 0x21,
|
||||||
/// Staking subsystem: transfer stake.
|
/// Staking subsystem: transfer stake.
|
||||||
StakingTransfer = 0x22,
|
StakingTransfer = 0x22,
|
||||||
|
/// Report misbehavior.
|
||||||
|
StakingReportMisbehavior = 0x23,
|
||||||
/// Make a proposal for the governance system.
|
/// Make a proposal for the governance system.
|
||||||
GovernancePropose = 0x30,
|
GovernancePropose = 0x30,
|
||||||
/// Approve a proposal for the governance system.
|
/// Approve a proposal for the governance system.
|
||||||
@@ -178,9 +181,16 @@ impl FunctionId {
|
|||||||
/// Derive `Some` value from a `u8`, or `None` if it's invalid.
|
/// Derive `Some` value from a `u8`, or `None` if it's invalid.
|
||||||
fn from_u8(value: u8) -> Option<FunctionId> {
|
fn from_u8(value: u8) -> Option<FunctionId> {
|
||||||
use self::*;
|
use self::*;
|
||||||
let functions = [FunctionId::StakingStake, FunctionId::StakingUnstake,
|
let functions = [
|
||||||
FunctionId::StakingTransfer, FunctionId::SessionSetKey, FunctionId::TimestampSet,
|
FunctionId::StakingStake,
|
||||||
FunctionId::GovernancePropose, FunctionId::GovernanceApprove];
|
FunctionId::StakingUnstake,
|
||||||
|
FunctionId::StakingTransfer,
|
||||||
|
FunctionId::StakingReportMisbehavior,
|
||||||
|
FunctionId::SessionSetKey,
|
||||||
|
FunctionId::TimestampSet,
|
||||||
|
FunctionId::GovernancePropose,
|
||||||
|
FunctionId::GovernanceApprove,
|
||||||
|
];
|
||||||
functions.iter().map(|&f| f).find(|&f| value == f as u8)
|
functions.iter().map(|&f| f).find(|&f| value == f as u8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,6 +232,8 @@ pub enum Function {
|
|||||||
StakingUnstake,
|
StakingUnstake,
|
||||||
/// Staking subsystem: transfer stake.
|
/// Staking subsystem: transfer stake.
|
||||||
StakingTransfer(::AccountId, u64),
|
StakingTransfer(::AccountId, u64),
|
||||||
|
/// Staking subsystem: report misbehavior of a validator.
|
||||||
|
ReportMisbehavior(MisbehaviorReport),
|
||||||
/// Make a proposal for the governance system.
|
/// Make a proposal for the governance system.
|
||||||
GovernancePropose(Proposal),
|
GovernancePropose(Proposal),
|
||||||
/// Approve a proposal for the governance system.
|
/// Approve a proposal for the governance system.
|
||||||
@@ -269,6 +281,7 @@ impl Slicable for Function {
|
|||||||
|
|
||||||
Function::StakingTransfer(to, amount)
|
Function::StakingTransfer(to, amount)
|
||||||
}
|
}
|
||||||
|
FunctionId::StakingReportMisbehavior => Function::ReportMisbehavior(MisbehaviorReport::decode(input)?),
|
||||||
FunctionId::GovernancePropose =>
|
FunctionId::GovernancePropose =>
|
||||||
Function::GovernancePropose(try_opt!(Slicable::decode(input))),
|
Function::GovernancePropose(try_opt!(Slicable::decode(input))),
|
||||||
FunctionId::GovernanceApprove =>
|
FunctionId::GovernanceApprove =>
|
||||||
@@ -293,6 +306,10 @@ impl Slicable for Function {
|
|||||||
Function::StakingUnstake => {
|
Function::StakingUnstake => {
|
||||||
(FunctionId::StakingUnstake as u8).using_encoded(|s| v.extend(s));
|
(FunctionId::StakingUnstake as u8).using_encoded(|s| v.extend(s));
|
||||||
}
|
}
|
||||||
|
Function::ReportMisbehavior(ref report) => {
|
||||||
|
(FunctionId::StakingReportMisbehavior as u8).using_encoded(|s| v.extend(s));
|
||||||
|
report.using_encoded(|s| v.extend(s));
|
||||||
|
}
|
||||||
Function::StakingTransfer(ref to, ref amount) => {
|
Function::StakingTransfer(ref to, ref amount) => {
|
||||||
(FunctionId::StakingTransfer as u8).using_encoded(|s| v.extend(s));
|
(FunctionId::StakingTransfer as u8).using_encoded(|s| v.extend(s));
|
||||||
to.using_encoded(|s| v.extend(s));
|
to.using_encoded(|s| v.extend(s));
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ substrate-runtime-std = { path = "../../substrate/runtime-std" }
|
|||||||
substrate-runtime-io = { path = "../../substrate/runtime-io" }
|
substrate-runtime-io = { path = "../../substrate/runtime-io" }
|
||||||
substrate-runtime-support = { path = "../../substrate/runtime-support" }
|
substrate-runtime-support = { path = "../../substrate/runtime-support" }
|
||||||
substrate-primitives = { path = "../../substrate/primitives" }
|
substrate-primitives = { path = "../../substrate/primitives" }
|
||||||
|
substrate-misbehavior-check = { path = "../../substrate/misbehavior-check" }
|
||||||
polkadot-primitives = { path = "../primitives" }
|
polkadot-primitives = { path = "../primitives" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@@ -25,6 +26,7 @@ std = [
|
|||||||
"substrate-runtime-io/std",
|
"substrate-runtime-io/std",
|
||||||
"substrate-runtime-support/std",
|
"substrate-runtime-support/std",
|
||||||
"substrate-primitives/std",
|
"substrate-primitives/std",
|
||||||
|
"substrate-misbehavior-check/std",
|
||||||
"polkadot-primitives/std",
|
"polkadot-primitives/std",
|
||||||
"log"
|
"log"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ pub struct Environment {
|
|||||||
pub parent_hash: Hash,
|
pub parent_hash: Hash,
|
||||||
/// The current block digest.
|
/// The current block digest.
|
||||||
pub digest: Digest,
|
pub digest: Digest,
|
||||||
/// The current transaction index
|
|
||||||
pub transaction_index: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do something with the environment and return its value. Keep the function short.
|
/// Do something with the environment and return its value. Keep the function short.
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ use std::collections::HashMap;
|
|||||||
use runtime_io::twox_128;
|
use runtime_io::twox_128;
|
||||||
use runtime_support::Hashable;
|
use runtime_support::Hashable;
|
||||||
use primitives::Block;
|
use primitives::Block;
|
||||||
use polkadot_primitives::{BlockNumber, AccountId};
|
use polkadot_primitives::{Balance, BlockNumber, AccountId};
|
||||||
use runtime::staking::Balance;
|
|
||||||
|
|
||||||
/// Configuration of a general Polkadot genesis block.
|
/// Configuration of a general Polkadot genesis block.
|
||||||
pub struct GenesisConfig {
|
pub struct GenesisConfig {
|
||||||
|
|||||||
@@ -19,23 +19,33 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
extern crate substrate_runtime_std as rstd;
|
extern crate substrate_runtime_std as rstd;
|
||||||
#[macro_use] extern crate substrate_runtime_io as runtime_io;
|
|
||||||
extern crate substrate_runtime_support as runtime_support;
|
extern crate substrate_runtime_support as runtime_support;
|
||||||
#[cfg(all(feature = "std", test))] extern crate substrate_keyring as keyring;
|
|
||||||
|
|
||||||
#[cfg(feature = "std")] extern crate rustc_hex;
|
|
||||||
|
|
||||||
extern crate substrate_codec as codec;
|
extern crate substrate_codec as codec;
|
||||||
#[cfg(feature = "std")] #[macro_use] extern crate substrate_primitives as primitives;
|
extern crate substrate_misbehavior_check as misbehavior_check;
|
||||||
extern crate polkadot_primitives;
|
extern crate polkadot_primitives;
|
||||||
|
|
||||||
#[cfg(test)] #[macro_use] extern crate hex_literal;
|
#[cfg(all(feature = "std", test))]
|
||||||
|
extern crate substrate_keyring as keyring;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
extern crate rustc_hex;
|
||||||
|
|
||||||
|
#[cfg_attr(any(test, feature = "std"), macro_use)]
|
||||||
|
extern crate substrate_primitives as primitives;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate substrate_runtime_io as runtime_io;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate hex_literal;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod environment;
|
pub mod environment;
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
|
||||||
#[cfg(feature = "std")] pub mod genesismap;
|
#[cfg(feature = "std")]
|
||||||
|
pub mod genesismap;
|
||||||
|
|
||||||
/// Type definitions and helpers for transactions.
|
/// Type definitions and helpers for transactions.
|
||||||
pub mod transaction {
|
pub mod transaction {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use polkadot_primitives::SessionKey;
|
|||||||
struct AuthorityStorageVec {}
|
struct AuthorityStorageVec {}
|
||||||
impl StorageVec for AuthorityStorageVec {
|
impl StorageVec for AuthorityStorageVec {
|
||||||
type Item = SessionKey;
|
type Item = SessionKey;
|
||||||
const PREFIX: &'static[u8] = b":auth:";
|
const PREFIX: &'static [u8] = b":auth:";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current set of authorities. These are the session keys.
|
/// Get the current set of authorities. These are the session keys.
|
||||||
@@ -37,7 +37,7 @@ pub mod internal {
|
|||||||
/// Set the current set of authorities' session keys.
|
/// Set the current set of authorities' session keys.
|
||||||
///
|
///
|
||||||
/// Called by `next_session` only.
|
/// Called by `next_session` only.
|
||||||
pub fn set_authorities(authorities: &[SessionKey]) {
|
pub fn set_authorities<'a, I: IntoIterator<Item=&'a SessionKey>>(authorities: I) {
|
||||||
AuthorityStorageVec::set_items(authorities);
|
AuthorityStorageVec::set_items(authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,23 @@ use runtime::{system, staking, consensus};
|
|||||||
|
|
||||||
const SESSION_LENGTH: &[u8] = b"ses:len";
|
const SESSION_LENGTH: &[u8] = b"ses:len";
|
||||||
const CURRENT_INDEX: &[u8] = b"ses:ind";
|
const CURRENT_INDEX: &[u8] = b"ses:ind";
|
||||||
|
const CURRENT_SESSION_START: &[u8] = b"ses:sta";
|
||||||
|
const LAST_SESSION_START: &[u8] = b"ses:lst";
|
||||||
const LAST_LENGTH_CHANGE: &[u8] = b"ses:llc";
|
const LAST_LENGTH_CHANGE: &[u8] = b"ses:llc";
|
||||||
const NEXT_KEY_FOR: &[u8] = b"ses:nxt:";
|
const NEXT_KEY_FOR: &[u8] = b"ses:nxt:";
|
||||||
const NEXT_SESSION_LENGTH: &[u8] = b"ses:nln";
|
const NEXT_SESSION_LENGTH: &[u8] = b"ses:nln";
|
||||||
|
|
||||||
struct ValidatorStorageVec {}
|
struct ValidatorStorageVec;
|
||||||
impl StorageVec for ValidatorStorageVec {
|
impl StorageVec for ValidatorStorageVec {
|
||||||
type Item = AccountId;
|
type Item = AccountId;
|
||||||
const PREFIX: &'static[u8] = b"ses:val:";
|
const PREFIX: &'static [u8] = b"ses:val:";
|
||||||
|
}
|
||||||
|
|
||||||
|
// the session keys before the previous.
|
||||||
|
struct LastValidators;
|
||||||
|
impl StorageVec for LastValidators {
|
||||||
|
type Item = (AccountId, SessionKey);
|
||||||
|
const PREFIX: &'static [u8] = b"ses:old:";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current set of validators.
|
/// Get the current set of validators.
|
||||||
@@ -50,11 +59,31 @@ pub fn validator_count() -> u32 {
|
|||||||
ValidatorStorageVec::count() as u32
|
ValidatorStorageVec::count() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current era index.
|
/// The current session index.
|
||||||
pub fn current_index() -> BlockNumber {
|
pub fn current_index() -> BlockNumber {
|
||||||
storage::get_or(CURRENT_INDEX, 0)
|
storage::get_or(CURRENT_INDEX, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the starting block of the current session.
|
||||||
|
pub fn current_start_block() -> BlockNumber {
|
||||||
|
// this seems like it's computable just by examining the current block number, session length,
|
||||||
|
// and last length change, but it's not simple to tell whether we are before or after
|
||||||
|
// a session rotation on a block which will have one.
|
||||||
|
storage::get_or(CURRENT_SESSION_START, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the last session's validators, paired with their authority keys.
|
||||||
|
pub fn last_session_keys() -> Vec<(AccountId, SessionKey)> {
|
||||||
|
LastValidators::items()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the start block of the last session.
|
||||||
|
/// In general this is computable from the session length,
|
||||||
|
/// but when the current session is the first with a new length it is uncomputable.
|
||||||
|
pub fn last_session_start() -> Option<BlockNumber> {
|
||||||
|
storage::get(LAST_SESSION_START)
|
||||||
|
}
|
||||||
|
|
||||||
/// The block number at which the era length last changed.
|
/// The block number at which the era length last changed.
|
||||||
pub fn last_length_change() -> BlockNumber {
|
pub fn last_length_change() -> BlockNumber {
|
||||||
storage::get_or(LAST_LENGTH_CHANGE, 0)
|
storage::get_or(LAST_LENGTH_CHANGE, 0)
|
||||||
@@ -90,11 +119,14 @@ pub mod privileged {
|
|||||||
pub mod internal {
|
pub mod internal {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Set the current set of validators.
|
/// Transition to a new era, with a new set of valiators.
|
||||||
///
|
///
|
||||||
/// Called by staking::next_era() only. `next_session` should be called after this in order to
|
/// Called by staking::next_era() only. `next_session` should be called after this in order to
|
||||||
/// update the session keys to the next validator set.
|
/// update the session keys to the next validator set.
|
||||||
pub fn set_validators(new: &[AccountId]) {
|
pub fn set_validators(new: &[AccountId]) {
|
||||||
|
LastValidators::set_items(
|
||||||
|
new.iter().cloned().zip(consensus::authorities())
|
||||||
|
);
|
||||||
ValidatorStorageVec::set_items(new);
|
ValidatorStorageVec::set_items(new);
|
||||||
consensus::internal::set_authorities(new);
|
consensus::internal::set_authorities(new);
|
||||||
}
|
}
|
||||||
@@ -114,7 +146,6 @@ pub mod internal {
|
|||||||
fn rotate_session() {
|
fn rotate_session() {
|
||||||
// Increment current session index.
|
// Increment current session index.
|
||||||
storage::put(CURRENT_INDEX, &(current_index() + 1));
|
storage::put(CURRENT_INDEX, &(current_index() + 1));
|
||||||
|
|
||||||
// Enact era length change.
|
// Enact era length change.
|
||||||
if let Some(next_len) = storage::get::<u64>(NEXT_SESSION_LENGTH) {
|
if let Some(next_len) = storage::get::<u64>(NEXT_SESSION_LENGTH) {
|
||||||
storage::put(SESSION_LENGTH, &next_len);
|
storage::put(SESSION_LENGTH, &next_len);
|
||||||
@@ -122,10 +153,23 @@ fn rotate_session() {
|
|||||||
storage::kill(NEXT_SESSION_LENGTH);
|
storage::kill(NEXT_SESSION_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let validators = validators();
|
||||||
|
|
||||||
|
storage::put(LAST_SESSION_START, ¤t_start_block());
|
||||||
|
storage::put(CURRENT_SESSION_START, &system::block_number());
|
||||||
|
LastValidators::set_items(
|
||||||
|
validators.iter()
|
||||||
|
.cloned()
|
||||||
|
.zip(consensus::authorities())
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
// Update any changes in session keys.
|
// Update any changes in session keys.
|
||||||
validators().iter().enumerate().for_each(|(i, v)| {
|
validators.iter().enumerate().for_each(|(i, v)| {
|
||||||
let k = v.to_keyed_vec(NEXT_KEY_FOR);
|
let k = v.to_keyed_vec(NEXT_KEY_FOR);
|
||||||
if let Some(n) = storage::take(&k) {
|
if let Some(n) = storage::take(&k) {
|
||||||
|
// this is fine because the authorities vector currently
|
||||||
|
// matches the validators length perfectly.
|
||||||
consensus::internal::set_authority(i as u32, &n);
|
consensus::internal::set_authority(i as u32, &n);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,18 +22,16 @@ use runtime_io::print;
|
|||||||
use codec::KeyedVec;
|
use codec::KeyedVec;
|
||||||
use runtime_support::{storage, StorageVec};
|
use runtime_support::{storage, StorageVec};
|
||||||
use polkadot_primitives::{BlockNumber, AccountId};
|
use polkadot_primitives::{BlockNumber, AccountId};
|
||||||
use runtime::{system, session, governance};
|
use primitives::bft::{MisbehaviorReport, MisbehaviorKind};
|
||||||
|
use runtime::{system, session, governance, consensus};
|
||||||
|
|
||||||
/// The balance of an account.
|
type Balance = u64;
|
||||||
pub type Balance = u64;
|
type Bondage = u64;
|
||||||
|
|
||||||
/// The amount of bonding period left in an account. Measured in eras.
|
|
||||||
pub type Bondage = u64;
|
|
||||||
|
|
||||||
struct IntentionStorageVec {}
|
struct IntentionStorageVec {}
|
||||||
impl StorageVec for IntentionStorageVec {
|
impl StorageVec for IntentionStorageVec {
|
||||||
type Item = AccountId;
|
type Item = AccountId;
|
||||||
const PREFIX: &'static[u8] = b"sta:wil:";
|
const PREFIX: &'static [u8] = b"sta:wil:";
|
||||||
}
|
}
|
||||||
|
|
||||||
const BONDING_DURATION: &[u8] = b"sta:loc";
|
const BONDING_DURATION: &[u8] = b"sta:loc";
|
||||||
@@ -80,11 +78,16 @@ pub fn balance(who: &AccountId) -> Balance {
|
|||||||
storage::get_or_default(&who.to_keyed_vec(BALANCE_OF))
|
storage::get_or_default(&who.to_keyed_vec(BALANCE_OF))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The liquidity-state of a given account.
|
/// Gives the index of the era where the account's balance will no longer
|
||||||
|
/// be bonded.
|
||||||
pub fn bondage(who: &AccountId) -> Bondage {
|
pub fn bondage(who: &AccountId) -> Bondage {
|
||||||
storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF))
|
storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_balance(who: &AccountId, amount: Balance) {
|
||||||
|
storage::put(&who.to_keyed_vec(BALANCE_OF), &amount)
|
||||||
|
}
|
||||||
|
|
||||||
// Each identity's stake may be in one of three bondage states, given by an integer:
|
// Each identity's stake may be in one of three bondage states, given by an integer:
|
||||||
// - n | n <= current_era(): inactive: free to be transferred.
|
// - n | n <= current_era(): inactive: free to be transferred.
|
||||||
// - ~0: active: currently representing a validator.
|
// - ~0: active: currently representing a validator.
|
||||||
@@ -113,7 +116,7 @@ pub mod public {
|
|||||||
pub fn stake(transactor: &AccountId) {
|
pub fn stake(transactor: &AccountId) {
|
||||||
let mut intentions = IntentionStorageVec::items();
|
let mut intentions = IntentionStorageVec::items();
|
||||||
// can't be in the list twice.
|
// can't be in the list twice.
|
||||||
assert!(intentions.iter().find(|t| *t == transactor).is_none(), "Cannot stake if already staked.");
|
assert!(intentions.iter().find(|t| t == &transactor).is_none(), "Cannot stake if already staked.");
|
||||||
intentions.push(transactor.clone());
|
intentions.push(transactor.clone());
|
||||||
IntentionStorageVec::set_items(&intentions);
|
IntentionStorageVec::set_items(&intentions);
|
||||||
storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &u64::max_value());
|
storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &u64::max_value());
|
||||||
@@ -132,6 +135,46 @@ pub mod public {
|
|||||||
IntentionStorageVec::set_items(&intentions);
|
IntentionStorageVec::set_items(&intentions);
|
||||||
storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &(current_era() + bonding_duration()));
|
storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &(current_era() + bonding_duration()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Report misbehavior. Only validators may do this, signing under
|
||||||
|
/// the authority key of the session the report corresponds to.
|
||||||
|
///
|
||||||
|
/// Reports older than one session in the past will be ignored.
|
||||||
|
pub fn report_misbehavior(transactor: &AccountId, report: &MisbehaviorReport) {
|
||||||
|
let (validators, authorities) = if report.parent_number < session::last_session_start().unwrap_or(0) {
|
||||||
|
panic!("report is too old");
|
||||||
|
} else if report.parent_number < session::current_start_block() {
|
||||||
|
session::last_session_keys().into_iter().unzip()
|
||||||
|
} else {
|
||||||
|
(session::validators(), consensus::authorities())
|
||||||
|
};
|
||||||
|
|
||||||
|
if report.parent_hash != system::block_hash(report.parent_number) {
|
||||||
|
// report out of chain.
|
||||||
|
panic!("report not from this blockchain");
|
||||||
|
}
|
||||||
|
|
||||||
|
let reporting_validator = match authorities.iter().position(|x| x == transactor) {
|
||||||
|
None => panic!("only validators may report"),
|
||||||
|
Some(pos) => validators.get(pos).expect("validators and authorities have same cardinality; qed"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// any invalidity beyond this point is actually its own misbehavior.
|
||||||
|
let target = match authorities.iter().position(|x| x == &report.target) {
|
||||||
|
None => {
|
||||||
|
slash(reporting_validator, None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(pos) => validators.get(pos).expect("validators and authorities have same cardinality; qed"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let misbehaved = ::misbehavior_check::evaluate_misbehavior(&report.target, report.parent_hash, &report.misbehavior);
|
||||||
|
if misbehaved {
|
||||||
|
slash(target, Some(reporting_validator))
|
||||||
|
} else {
|
||||||
|
slash(reporting_validator, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod privileged {
|
pub mod privileged {
|
||||||
@@ -171,6 +214,23 @@ pub mod internal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Slash a validator, with an optional benefactor.
|
||||||
|
fn slash(who: &AccountId, benefactor: Option<&AccountId>) {
|
||||||
|
// the reciprocal of the proportion of the amount slashed to give
|
||||||
|
// to the benefactor.
|
||||||
|
const SLASH_REWARD_DENOMINATOR: Balance = 10;
|
||||||
|
|
||||||
|
let slashed = balance(who);
|
||||||
|
set_balance(who, 0);
|
||||||
|
|
||||||
|
if let Some(benefactor) = benefactor {
|
||||||
|
let reward = slashed / SLASH_REWARD_DENOMINATOR;
|
||||||
|
|
||||||
|
let prior = balance(benefactor);
|
||||||
|
set_balance(benefactor, prior + reward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The era has changed - enact new staking set.
|
/// The era has changed - enact new staking set.
|
||||||
///
|
///
|
||||||
/// NOTE: This always happens immediately before a session change to ensure that new validators
|
/// NOTE: This always happens immediately before a session change to ensure that new validators
|
||||||
@@ -404,4 +464,31 @@ mod tests {
|
|||||||
transfer(&one, &two, 69);
|
transfer(&one, &two, 69);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn misbehavior_report_by_non_validator_panics() {
|
||||||
|
let one = Keyring::One.to_raw_public();
|
||||||
|
let two = Keyring::Two.to_raw_public();
|
||||||
|
|
||||||
|
let mut t: TestExternalities = map![
|
||||||
|
twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&111u64)
|
||||||
|
];
|
||||||
|
|
||||||
|
with_externalities(&mut t, || {
|
||||||
|
// the misbehavior report here is invalid, but that
|
||||||
|
// actually doesn't panic; instead it would slash the bad
|
||||||
|
// reporter.
|
||||||
|
report_misbehavior(&one, &MisbehaviorReport {
|
||||||
|
parent_hash: [0; 32].into(),
|
||||||
|
parent_number: 0,
|
||||||
|
target: two,
|
||||||
|
misbehavior: MisbehaviorKind::BftDoubleCommit(
|
||||||
|
2,
|
||||||
|
([1; 32].into(), [2; 64].into()),
|
||||||
|
([3; 32].into(), [4; 64].into()),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ fn dispatch_function(function: &Function, transactor: &AccountId) {
|
|||||||
Function::StakingTransfer(dest, value) => {
|
Function::StakingTransfer(dest, value) => {
|
||||||
::runtime::staking::public::transfer(transactor, &dest, value);
|
::runtime::staking::public::transfer(transactor, &dest, value);
|
||||||
}
|
}
|
||||||
|
Function::ReportMisbehavior(ref report) => {
|
||||||
|
::runtime::staking::public::report_misbehavior(transactor, report)
|
||||||
|
}
|
||||||
Function::SessionSetKey(session) => {
|
Function::SessionSetKey(session) => {
|
||||||
::runtime::session::public::set_key(transactor, &session);
|
::runtime::session::public::set_key(transactor, &session);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -392,6 +392,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"polkadot-primitives 0.1.0",
|
"polkadot-primitives 0.1.0",
|
||||||
"substrate-codec 0.1.0",
|
"substrate-codec 0.1.0",
|
||||||
|
"substrate-misbehavior-check 0.1.0",
|
||||||
"substrate-primitives 0.1.0",
|
"substrate-primitives 0.1.0",
|
||||||
"substrate-runtime-io 0.1.0",
|
"substrate-runtime-io 0.1.0",
|
||||||
"substrate-runtime-std 0.1.0",
|
"substrate-runtime-std 0.1.0",
|
||||||
@@ -599,6 +600,15 @@ dependencies = [
|
|||||||
"substrate-runtime-std 0.1.0",
|
"substrate-runtime-std 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "substrate-misbehavior-check"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"substrate-codec 0.1.0",
|
||||||
|
"substrate-primitives 0.1.0",
|
||||||
|
"substrate-runtime-io 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "substrate-primitives"
|
name = "substrate-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ substrate-runtime-std = { path = "../../../substrate/runtime-std", default-featu
|
|||||||
substrate-runtime-io = { path = "../../../substrate/runtime-io", default-features = false }
|
substrate-runtime-io = { path = "../../../substrate/runtime-io", default-features = false }
|
||||||
substrate-runtime-support = { path = "../../../substrate/runtime-support", default-features = false }
|
substrate-runtime-support = { path = "../../../substrate/runtime-support", default-features = false }
|
||||||
substrate-primitives = { path = "../../../substrate/primitives", default-features = false }
|
substrate-primitives = { path = "../../../substrate/primitives", default-features = false }
|
||||||
|
substrate-misbehavior-check = { path = "../../../substrate/misbehavior-check", default-features = false }
|
||||||
polkadot-primitives = { path = "../../primitives", default-features = false }
|
polkadot-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@@ -22,6 +23,7 @@ std = [
|
|||||||
"substrate-runtime-std/std",
|
"substrate-runtime-std/std",
|
||||||
"substrate-runtime-support/std",
|
"substrate-runtime-support/std",
|
||||||
"substrate-primitives/std",
|
"substrate-primitives/std",
|
||||||
|
"substrate-misbehavior-check/std",
|
||||||
"polkadot-primitives/std",
|
"polkadot-primitives/std",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -14,13 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Message accumulator for each round of BFT consensus.
|
//! Vote accumulator for each round of BFT consensus.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use generic::{Message, LocalizedMessage};
|
use generic::{Vote, LocalizedMessage, LocalizedProposal};
|
||||||
|
|
||||||
/// Justification for some state at a given round.
|
/// Justification for some state at a given round.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -122,6 +122,26 @@ struct VoteCounts {
|
|||||||
committed: usize,
|
committed: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Proposal<Candidate, Digest, Signature> {
|
||||||
|
proposal: Candidate,
|
||||||
|
digest: Digest,
|
||||||
|
digest_signature: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Misbehavior which can occur.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Misbehavior<Digest, Signature> {
|
||||||
|
/// Proposed out-of-turn.
|
||||||
|
ProposeOutOfTurn(usize, Digest, Signature),
|
||||||
|
/// Issued two conflicting proposals.
|
||||||
|
DoublePropose(usize, (Digest, Signature), (Digest, Signature)),
|
||||||
|
/// Issued two conflicting prepare messages.
|
||||||
|
DoublePrepare(usize, (Digest, Signature), (Digest, Signature)),
|
||||||
|
/// Issued two conflicting commit messages.
|
||||||
|
DoubleCommit(usize, (Digest, Signature), (Digest, Signature)),
|
||||||
|
}
|
||||||
|
|
||||||
/// Accumulates messages for a given round of BFT consensus.
|
/// Accumulates messages for a given round of BFT consensus.
|
||||||
///
|
///
|
||||||
/// This isn't tied to the "view" of a single authority. It
|
/// This isn't tied to the "view" of a single authority. It
|
||||||
@@ -132,13 +152,13 @@ pub struct Accumulator<Candidate, Digest, AuthorityId, Signature>
|
|||||||
where
|
where
|
||||||
Candidate: Eq + Clone,
|
Candidate: Eq + Clone,
|
||||||
Digest: Hash + Eq + Clone,
|
Digest: Hash + Eq + Clone,
|
||||||
AuthorityId: Hash + Eq,
|
AuthorityId: Hash + Eq + Clone,
|
||||||
Signature: Eq + Clone,
|
Signature: Eq + Clone,
|
||||||
{
|
{
|
||||||
round_number: usize,
|
round_number: usize,
|
||||||
threshold: usize,
|
threshold: usize,
|
||||||
round_proposer: AuthorityId,
|
round_proposer: AuthorityId,
|
||||||
proposal: Option<Candidate>,
|
proposal: Option<Proposal<Candidate, Digest, Signature>>,
|
||||||
prepares: HashMap<AuthorityId, (Digest, Signature)>,
|
prepares: HashMap<AuthorityId, (Digest, Signature)>,
|
||||||
commits: HashMap<AuthorityId, (Digest, Signature)>,
|
commits: HashMap<AuthorityId, (Digest, Signature)>,
|
||||||
vote_counts: HashMap<Digest, VoteCounts>,
|
vote_counts: HashMap<Digest, VoteCounts>,
|
||||||
@@ -150,7 +170,7 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
where
|
where
|
||||||
Candidate: Eq + Clone,
|
Candidate: Eq + Clone,
|
||||||
Digest: Hash + Eq + Clone,
|
Digest: Hash + Eq + Clone,
|
||||||
AuthorityId: Hash + Eq,
|
AuthorityId: Hash + Eq + Clone,
|
||||||
Signature: Eq + Clone,
|
Signature: Eq + Clone,
|
||||||
{
|
{
|
||||||
/// Create a new state accumulator.
|
/// Create a new state accumulator.
|
||||||
@@ -179,7 +199,7 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn proposal(&self) -> Option<&Candidate> {
|
pub fn proposal(&self) -> Option<&Candidate> {
|
||||||
self.proposal.as_ref()
|
self.proposal.as_ref().map(|p| &p.proposal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspect the current consensus state.
|
/// Inspect the current consensus state.
|
||||||
@@ -192,32 +212,61 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
pub fn import_message(
|
pub fn import_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
message: LocalizedMessage<Candidate, Digest, AuthorityId, Signature>,
|
message: LocalizedMessage<Candidate, Digest, AuthorityId, Signature>,
|
||||||
)
|
) -> Result<(), Misbehavior<Digest, Signature>> {
|
||||||
{
|
|
||||||
// message from different round.
|
// message from different round.
|
||||||
if message.message.round_number() != self.round_number {
|
if message.round_number() != self.round_number {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (sender, signature) = (message.sender, message.signature);
|
match message {
|
||||||
|
LocalizedMessage::Propose(proposal) => self.import_proposal(proposal),
|
||||||
match message.message {
|
LocalizedMessage::Vote(vote) => {
|
||||||
Message::Propose(_, p) => self.import_proposal(p, sender),
|
let (sender, signature) = (vote.sender, vote.signature);
|
||||||
Message::Prepare(_, d) => self.import_prepare(d, sender, signature),
|
match vote.vote {
|
||||||
Message::Commit(_, d) => self.import_commit(d, sender, signature),
|
Vote::Prepare(_, d) => self.import_prepare(d, sender, signature),
|
||||||
Message::AdvanceRound(_) => self.import_advance_round(sender),
|
Vote::Commit(_, d) => self.import_commit(d, sender, signature),
|
||||||
|
Vote::AdvanceRound(_) => self.import_advance_round(sender),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_proposal(
|
fn import_proposal(
|
||||||
&mut self,
|
&mut self,
|
||||||
proposal: Candidate,
|
proposal: LocalizedProposal<Candidate, Digest, AuthorityId, Signature>,
|
||||||
sender: AuthorityId,
|
) -> Result<(), Misbehavior<Digest, Signature>> {
|
||||||
) {
|
let sender = proposal.sender;
|
||||||
if sender != self.round_proposer || self.proposal.is_some() { return }
|
|
||||||
|
|
||||||
self.proposal = Some(proposal.clone());
|
if sender != self.round_proposer {
|
||||||
self.state = State::Proposed(proposal);
|
return Err(Misbehavior::ProposeOutOfTurn(
|
||||||
|
self.round_number,
|
||||||
|
proposal.digest,
|
||||||
|
proposal.digest_signature)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.proposal {
|
||||||
|
Some(ref p) if &p.digest != &proposal.digest => {
|
||||||
|
return Err(Misbehavior::DoublePropose(
|
||||||
|
self.round_number,
|
||||||
|
{
|
||||||
|
let old = self.proposal.as_ref().expect("just checked to be Some; qed");
|
||||||
|
(old.digest.clone(), old.digest_signature.clone())
|
||||||
|
},
|
||||||
|
(proposal.digest.clone(), proposal.digest_signature.clone())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.proposal = Some(Proposal {
|
||||||
|
proposal: proposal.proposal.clone(),
|
||||||
|
digest: proposal.digest,
|
||||||
|
digest_signature: proposal.digest_signature,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.state = State::Proposed(proposal.proposal);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_prepare(
|
fn import_prepare(
|
||||||
@@ -225,21 +274,32 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
digest: Digest,
|
digest: Digest,
|
||||||
sender: AuthorityId,
|
sender: AuthorityId,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
) {
|
) -> Result<(), Misbehavior<Digest, Signature>> {
|
||||||
// ignore any subsequent prepares by the same sender.
|
// ignore any subsequent prepares by the same sender.
|
||||||
// TODO: if digest is different, that's misbehavior.
|
let threshold_prepared = match self.prepares.entry(sender.clone()) {
|
||||||
let threshold_prepared = if let Entry::Vacant(vacant) = self.prepares.entry(sender) {
|
Entry::Vacant(vacant) => {
|
||||||
vacant.insert((digest.clone(), signature));
|
vacant.insert((digest.clone(), signature));
|
||||||
let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default);
|
let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default);
|
||||||
count.prepared += 1;
|
count.prepared += 1;
|
||||||
|
|
||||||
|
if count.prepared >= self.threshold {
|
||||||
|
Some(digest)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Occupied(occupied) => {
|
||||||
|
// if digest is different, that's misbehavior.
|
||||||
|
if occupied.get().0 != digest {
|
||||||
|
return Err(Misbehavior::DoublePrepare(
|
||||||
|
self.round_number,
|
||||||
|
occupied.get().clone(),
|
||||||
|
(digest, signature)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if count.prepared >= self.threshold {
|
|
||||||
Some(digest)
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// only allow transition to prepare from begin or proposed state.
|
// only allow transition to prepare from begin or proposed state.
|
||||||
@@ -261,6 +321,8 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
signatures: signatures,
|
signatures: signatures,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_commit(
|
fn import_commit(
|
||||||
@@ -268,21 +330,32 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
digest: Digest,
|
digest: Digest,
|
||||||
sender: AuthorityId,
|
sender: AuthorityId,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
) {
|
) -> Result<(), Misbehavior<Digest, Signature>> {
|
||||||
// ignore any subsequent commits by the same sender.
|
// ignore any subsequent commits by the same sender.
|
||||||
// TODO: if digest is different, that's misbehavior.
|
let threshold_committed = match self.commits.entry(sender.clone()) {
|
||||||
let threshold_committed = if let Entry::Vacant(vacant) = self.commits.entry(sender) {
|
Entry::Vacant(vacant) => {
|
||||||
vacant.insert((digest.clone(), signature));
|
vacant.insert((digest.clone(), signature));
|
||||||
let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default);
|
let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default);
|
||||||
count.committed += 1;
|
count.committed += 1;
|
||||||
|
|
||||||
|
if count.committed >= self.threshold {
|
||||||
|
Some(digest)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Occupied(occupied) => {
|
||||||
|
// if digest is different, that's misbehavior.
|
||||||
|
if occupied.get().0 != digest {
|
||||||
|
return Err(Misbehavior::DoubleCommit(
|
||||||
|
self.round_number,
|
||||||
|
occupied.get().clone(),
|
||||||
|
(digest, signature)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if count.committed >= self.threshold {
|
|
||||||
Some(digest)
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// transition to concluded state always valid.
|
// transition to concluded state always valid.
|
||||||
@@ -302,15 +375,17 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
signatures: signatures,
|
signatures: signatures,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_advance_round(
|
fn import_advance_round(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender: AuthorityId,
|
sender: AuthorityId,
|
||||||
) {
|
) -> Result<(), Misbehavior<Digest, Signature>> {
|
||||||
self.advance_round.insert(sender);
|
self.advance_round.insert(sender);
|
||||||
|
|
||||||
if self.advance_round.len() < self.threshold { return }
|
if self.advance_round.len() < self.threshold { return Ok(()) }
|
||||||
|
|
||||||
// allow transition to new round only if we haven't produced a justification
|
// allow transition to new round only if we haven't produced a justification
|
||||||
// yet.
|
// yet.
|
||||||
@@ -319,13 +394,16 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A
|
|||||||
State::Prepared(j) => State::Advanced(Some(j)),
|
State::Prepared(j) => State::Advanced(Some(j)),
|
||||||
State::Advanced(j) => State::Advanced(j),
|
State::Advanced(j) => State::Advanced(j),
|
||||||
State::Begin | State::Proposed(_) => State::Advanced(None),
|
State::Begin | State::Proposed(_) => State::Advanced(None),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use generic::{LocalizedMessage, LocalizedProposal, LocalizedVote};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Candidate(usize);
|
pub struct Candidate(usize);
|
||||||
@@ -333,7 +411,7 @@ mod tests {
|
|||||||
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
|
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
|
||||||
pub struct Digest(usize);
|
pub struct Digest(usize);
|
||||||
|
|
||||||
#[derive(Hash, PartialEq, Eq, Debug)]
|
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||||
pub struct AuthorityId(usize);
|
pub struct AuthorityId(usize);
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
@@ -375,19 +453,27 @@ mod tests {
|
|||||||
let mut accumulator = Accumulator::<_, Digest, _, _>::new(1, 7, AuthorityId(8));
|
let mut accumulator = Accumulator::<_, Digest, _, _>::new(1, 7, AuthorityId(8));
|
||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
let res = accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal {
|
||||||
sender: AuthorityId(5),
|
sender: AuthorityId(5),
|
||||||
signature: Signature(999, 5),
|
full_signature: Signature(999, 5),
|
||||||
message: Message::Propose(1, Candidate(999)),
|
digest_signature: Signature(999, 5),
|
||||||
});
|
proposal: Candidate(999),
|
||||||
|
digest: Digest(999),
|
||||||
|
round_number: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal {
|
||||||
sender: AuthorityId(8),
|
sender: AuthorityId(8),
|
||||||
signature: Signature(999, 8),
|
full_signature: Signature(999, 8),
|
||||||
message: Message::Propose(1, Candidate(999)),
|
digest_signature: Signature(999, 8),
|
||||||
});
|
proposal: Candidate(999),
|
||||||
|
digest: Digest(999),
|
||||||
|
round_number: 1,
|
||||||
|
})).unwrap();
|
||||||
|
|
||||||
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
||||||
}
|
}
|
||||||
@@ -397,29 +483,32 @@ mod tests {
|
|||||||
let mut accumulator = Accumulator::new(1, 7, AuthorityId(8));
|
let mut accumulator = Accumulator::new(1, 7, AuthorityId(8));
|
||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal {
|
||||||
sender: AuthorityId(8),
|
sender: AuthorityId(8),
|
||||||
signature: Signature(999, 8),
|
full_signature: Signature(999, 8),
|
||||||
message: Message::Propose(1, Candidate(999)),
|
digest_signature: Signature(999, 8),
|
||||||
});
|
round_number: 1,
|
||||||
|
proposal: Candidate(999),
|
||||||
|
digest: Digest(999),
|
||||||
|
})).unwrap();
|
||||||
|
|
||||||
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
||||||
|
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::Prepare(1, Digest(999)),
|
vote: Vote::Prepare(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(7),
|
sender: AuthorityId(7),
|
||||||
signature: Signature(999, 7),
|
signature: Signature(999, 7),
|
||||||
message: Message::Prepare(1, Digest(999)),
|
vote: Vote::Prepare(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
&State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)),
|
&State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)),
|
||||||
@@ -432,29 +521,32 @@ mod tests {
|
|||||||
let mut accumulator = Accumulator::new(1, 7, AuthorityId(8));
|
let mut accumulator = Accumulator::new(1, 7, AuthorityId(8));
|
||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal {
|
||||||
sender: AuthorityId(8),
|
sender: AuthorityId(8),
|
||||||
signature: Signature(999, 8),
|
full_signature: Signature(999, 8),
|
||||||
message: Message::Propose(1, Candidate(999)),
|
digest_signature: Signature(999, 8),
|
||||||
});
|
round_number: 1,
|
||||||
|
proposal: Candidate(999),
|
||||||
|
digest: Digest(999),
|
||||||
|
})).unwrap();
|
||||||
|
|
||||||
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
||||||
|
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::Prepare(1, Digest(999)),
|
vote: Vote::Prepare(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(7),
|
sender: AuthorityId(7),
|
||||||
signature: Signature(999, 7),
|
signature: Signature(999, 7),
|
||||||
message: Message::Prepare(1, Digest(999)),
|
vote: Vote::Prepare(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
&State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)),
|
&State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)),
|
||||||
@@ -462,11 +554,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::Commit(1, Digest(999)),
|
vote: Vote::Commit(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
&State::Prepared(_) => {},
|
&State::Prepared(_) => {},
|
||||||
@@ -474,11 +566,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(7),
|
sender: AuthorityId(7),
|
||||||
signature: Signature(999, 7),
|
signature: Signature(999, 7),
|
||||||
message: Message::Commit(1, Digest(999)),
|
vote: Vote::Commit(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
&State::Committed(ref j) => assert_eq!(j.digest, Digest(999)),
|
&State::Committed(ref j) => assert_eq!(j.digest, Digest(999)),
|
||||||
@@ -491,20 +583,23 @@ mod tests {
|
|||||||
let mut accumulator = Accumulator::new(1, 7, AuthorityId(8));
|
let mut accumulator = Accumulator::new(1, 7, AuthorityId(8));
|
||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal {
|
||||||
sender: AuthorityId(8),
|
sender: AuthorityId(8),
|
||||||
signature: Signature(999, 8),
|
full_signature: Signature(999, 8),
|
||||||
message: Message::Propose(1, Candidate(999)),
|
digest_signature: Signature(999, 8),
|
||||||
});
|
round_number: 1,
|
||||||
|
proposal: Candidate(999),
|
||||||
|
digest: Digest(999),
|
||||||
|
})).unwrap();
|
||||||
|
|
||||||
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
assert_eq!(accumulator.state(), &State::Proposed(Candidate(999)));
|
||||||
|
|
||||||
for i in 0..7 {
|
for i in 0..7 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::Prepare(1, Digest(999)),
|
vote: Vote::Prepare(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
@@ -513,11 +608,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::AdvanceRound(1),
|
vote: Vote::AdvanceRound(1),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
&State::Prepared(_) => {},
|
&State::Prepared(_) => {},
|
||||||
@@ -525,11 +620,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(7),
|
sender: AuthorityId(7),
|
||||||
signature: Signature(999, 7),
|
signature: Signature(999, 7),
|
||||||
message: Message::AdvanceRound(1),
|
vote: Vote::AdvanceRound(1),
|
||||||
});
|
}.into()).unwrap();
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
&State::Advanced(Some(_)) => {},
|
&State::Advanced(Some(_)) => {},
|
||||||
@@ -543,11 +638,11 @@ mod tests {
|
|||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
for i in 0..7 {
|
for i in 0..7 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::Prepare(1, Digest(999)),
|
vote: Vote::Prepare(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
@@ -556,11 +651,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..7 {
|
for i in 0..7 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::Commit(1, Digest(999)),
|
vote: Vote::Commit(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
@@ -575,11 +670,11 @@ mod tests {
|
|||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
for i in 0..7 {
|
for i in 0..7 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(1, i),
|
signature: Signature(1, i),
|
||||||
message: Message::AdvanceRound(1),
|
vote: Vote::AdvanceRound(1),
|
||||||
});
|
}.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
@@ -594,11 +689,11 @@ mod tests {
|
|||||||
assert_eq!(accumulator.state(), &State::Begin);
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
for i in 0..7 {
|
for i in 0..7 {
|
||||||
accumulator.import_message(LocalizedMessage {
|
accumulator.import_message(LocalizedVote {
|
||||||
sender: AuthorityId(i),
|
sender: AuthorityId(i),
|
||||||
signature: Signature(999, i),
|
signature: Signature(999, i),
|
||||||
message: Message::Commit(1, Digest(999)),
|
vote: Vote::Commit(1, Digest(999)),
|
||||||
});
|
}.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
match accumulator.state() {
|
match accumulator.state() {
|
||||||
@@ -606,4 +701,76 @@ mod tests {
|
|||||||
s => panic!("wrong state: {:?}", s),
|
s => panic!("wrong state: {:?}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn double_prepare_is_misbehavior() {
|
||||||
|
let mut accumulator = Accumulator::<Candidate, _, _, _>::new(1, 7, AuthorityId(8));
|
||||||
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
|
for i in 0..7 {
|
||||||
|
accumulator.import_message(LocalizedVote {
|
||||||
|
sender: AuthorityId(i),
|
||||||
|
signature: Signature(999, i),
|
||||||
|
vote: Vote::Prepare(1, Digest(999)),
|
||||||
|
}.into()).unwrap();
|
||||||
|
|
||||||
|
let res = accumulator.import_message(LocalizedVote {
|
||||||
|
sender: AuthorityId(i),
|
||||||
|
signature: Signature(123, i),
|
||||||
|
vote: Vote::Prepare(1, Digest(123)),
|
||||||
|
}.into());
|
||||||
|
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn double_commit_is_misbehavior() {
|
||||||
|
let mut accumulator = Accumulator::<Candidate, _, _, _>::new(1, 7, AuthorityId(8));
|
||||||
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
|
for i in 0..7 {
|
||||||
|
accumulator.import_message(LocalizedVote {
|
||||||
|
sender: AuthorityId(i),
|
||||||
|
signature: Signature(999, i),
|
||||||
|
vote: Vote::Commit(1, Digest(999)),
|
||||||
|
}.into()).unwrap();
|
||||||
|
|
||||||
|
let res = accumulator.import_message(LocalizedVote {
|
||||||
|
sender: AuthorityId(i),
|
||||||
|
signature: Signature(123, i),
|
||||||
|
vote: Vote::Commit(1, Digest(123)),
|
||||||
|
}.into());
|
||||||
|
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn double_propose_is_misbehavior() {
|
||||||
|
let mut accumulator = Accumulator::<Candidate, _, _, _>::new(1, 7, AuthorityId(8));
|
||||||
|
assert_eq!(accumulator.state(), &State::Begin);
|
||||||
|
|
||||||
|
accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal {
|
||||||
|
sender: AuthorityId(8),
|
||||||
|
full_signature: Signature(999, 8),
|
||||||
|
digest_signature: Signature(999, 8),
|
||||||
|
round_number: 1,
|
||||||
|
proposal: Candidate(999),
|
||||||
|
digest: Digest(999),
|
||||||
|
})).unwrap();
|
||||||
|
|
||||||
|
let res = accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal {
|
||||||
|
sender: AuthorityId(8),
|
||||||
|
full_signature: Signature(500, 8),
|
||||||
|
digest_signature: Signature(500, 8),
|
||||||
|
round_number: 1,
|
||||||
|
proposal: Candidate(500),
|
||||||
|
digest: Digest(500),
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
//! Very general implementation.
|
//! Very general implementation.
|
||||||
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::collections::hash_map;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
@@ -25,19 +26,16 @@ use futures::{future, Future, Stream, Sink, Poll, Async, AsyncSink};
|
|||||||
|
|
||||||
use self::accumulator::State;
|
use self::accumulator::State;
|
||||||
|
|
||||||
pub use self::accumulator::{Accumulator, Justification, PrepareJustification, UncheckedJustification};
|
pub use self::accumulator::{Accumulator, Justification, PrepareJustification, UncheckedJustification, Misbehavior};
|
||||||
|
|
||||||
mod accumulator;
|
mod accumulator;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// Messages over the proposal.
|
/// Votes during a round.
|
||||||
/// Each message carries an associated round number.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Message<C, D> {
|
pub enum Vote<D> {
|
||||||
/// Send a full proposal.
|
|
||||||
Propose(usize, C),
|
|
||||||
/// Prepare to vote for proposal with digest D.
|
/// Prepare to vote for proposal with digest D.
|
||||||
Prepare(usize, D),
|
Prepare(usize, D),
|
||||||
/// Commit to proposal with digest D..
|
/// Commit to proposal with digest D..
|
||||||
@@ -46,29 +44,94 @@ pub enum Message<C, D> {
|
|||||||
AdvanceRound(usize),
|
AdvanceRound(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, D> Message<C, D> {
|
impl<D> Vote<D> {
|
||||||
/// Extract the round number.
|
/// Extract the round number.
|
||||||
pub fn round_number(&self) -> usize {
|
pub fn round_number(&self) -> usize {
|
||||||
match *self {
|
match *self {
|
||||||
Message::Propose(round, _) => round,
|
Vote::Prepare(round, _) => round,
|
||||||
Message::Prepare(round, _) => round,
|
Vote::Commit(round, _) => round,
|
||||||
Message::Commit(round, _) => round,
|
Vote::AdvanceRound(round) => round,
|
||||||
Message::AdvanceRound(round) => round,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A localized message, including the sender.
|
/// Messages over the proposal.
|
||||||
|
/// Each message carries an associated round number.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Message<C, D> {
|
||||||
|
/// A proposal itself.
|
||||||
|
Propose(usize, C),
|
||||||
|
/// A vote of some kind, localized to a round number.
|
||||||
|
Vote(Vote<D>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, D> From<Vote<D>> for Message<C, D> {
|
||||||
|
fn from(vote: Vote<D>) -> Self {
|
||||||
|
Message::Vote(vote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A localized proposal message. Contains two signed pieces of data.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LocalizedMessage<C, D, V, S> {
|
pub struct LocalizedProposal<C, D, V, S> {
|
||||||
/// The message received.
|
/// The round number.
|
||||||
pub message: Message<C, D>,
|
pub round_number: usize,
|
||||||
|
/// The proposal sent.
|
||||||
|
pub proposal: C,
|
||||||
|
/// The digest of the proposal.
|
||||||
|
pub digest: D,
|
||||||
|
/// The sender of the proposal
|
||||||
|
pub sender: V,
|
||||||
|
/// The signature on the message (propose, round number, digest)
|
||||||
|
pub digest_signature: S,
|
||||||
|
/// The signature on the message (propose, round number, proposal)
|
||||||
|
pub full_signature: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A localized vote message, including the sender.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LocalizedVote<D, V, S> {
|
||||||
|
/// The message sent.
|
||||||
|
pub vote: Vote<D>,
|
||||||
/// The sender of the message
|
/// The sender of the message
|
||||||
pub sender: V,
|
pub sender: V,
|
||||||
/// The signature of the message.
|
/// The signature of the message.
|
||||||
pub signature: S,
|
pub signature: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A localized message.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum LocalizedMessage<C, D, V, S> {
|
||||||
|
/// A proposal.
|
||||||
|
Propose(LocalizedProposal<C, D, V, S>),
|
||||||
|
/// A vote.
|
||||||
|
Vote(LocalizedVote<D, V, S>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, D, V, S> LocalizedMessage<C, D, V, S> {
|
||||||
|
/// Extract the sender.
|
||||||
|
pub fn sender(&self) -> &V {
|
||||||
|
match *self {
|
||||||
|
LocalizedMessage::Propose(ref proposal) => &proposal.sender,
|
||||||
|
LocalizedMessage::Vote(ref vote) => &vote.sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the round number.
|
||||||
|
pub fn round_number(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
LocalizedMessage::Propose(ref proposal) => proposal.round_number,
|
||||||
|
LocalizedMessage::Vote(ref vote) => vote.vote.round_number(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, D, V, S> From<LocalizedVote<D, V, S>> for LocalizedMessage<C, D, V, S> {
|
||||||
|
fn from(vote: LocalizedVote<D, V, S>) -> Self {
|
||||||
|
LocalizedMessage::Vote(vote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Context necessary for agreement.
|
/// Context necessary for agreement.
|
||||||
///
|
///
|
||||||
/// Provides necessary types for protocol messages, and functions necessary for a
|
/// Provides necessary types for protocol messages, and functions necessary for a
|
||||||
@@ -101,6 +164,8 @@ pub trait Context {
|
|||||||
fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest;
|
fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest;
|
||||||
|
|
||||||
/// Sign a message using the local authority ID.
|
/// Sign a message using the local authority ID.
|
||||||
|
/// In the case of a proposal message, it should sign on the hash and
|
||||||
|
/// the bytes of the proposal.
|
||||||
fn sign_local(&self, message: Message<Self::Candidate, Self::Digest>)
|
fn sign_local(&self, message: Message<Self::Candidate, Self::Digest>)
|
||||||
-> LocalizedMessage<Self::Candidate, Self::Digest, Self::AuthorityId, Self::Signature>;
|
-> LocalizedMessage<Self::Candidate, Self::Digest, Self::AuthorityId, Self::Signature>;
|
||||||
|
|
||||||
@@ -258,6 +323,7 @@ struct Strategy<C: Context> {
|
|||||||
current_accumulator: Accumulator<C::Candidate, C::Digest, C::AuthorityId, C::Signature>,
|
current_accumulator: Accumulator<C::Candidate, C::Digest, C::AuthorityId, C::Signature>,
|
||||||
future_accumulator: Accumulator<C::Candidate, C::Digest, C::AuthorityId, C::Signature>,
|
future_accumulator: Accumulator<C::Candidate, C::Digest, C::AuthorityId, C::Signature>,
|
||||||
local_id: C::AuthorityId,
|
local_id: C::AuthorityId,
|
||||||
|
misbehavior: HashMap<C::AuthorityId, Misbehavior<C::Digest, C::Signature>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Context> Strategy<C> {
|
impl<C: Context> Strategy<C> {
|
||||||
@@ -289,6 +355,7 @@ impl<C: Context> Strategy<C> {
|
|||||||
notable_candidates: HashMap::new(),
|
notable_candidates: HashMap::new(),
|
||||||
round_timeout: timeout.fuse(),
|
round_timeout: timeout.fuse(),
|
||||||
local_id: context.local_id(),
|
local_id: context.local_id(),
|
||||||
|
misbehavior: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,12 +363,19 @@ impl<C: Context> Strategy<C> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
msg: LocalizedMessage<C::Candidate, C::Digest, C::AuthorityId, C::Signature>
|
msg: LocalizedMessage<C::Candidate, C::Digest, C::AuthorityId, C::Signature>
|
||||||
) {
|
) {
|
||||||
let round_number = msg.message.round_number();
|
let round_number = msg.round_number();
|
||||||
|
|
||||||
if round_number == self.current_accumulator.round_number() {
|
let sender = msg.sender().clone();
|
||||||
self.current_accumulator.import_message(msg);
|
let misbehavior = if round_number == self.current_accumulator.round_number() {
|
||||||
|
self.current_accumulator.import_message(msg)
|
||||||
} else if round_number == self.future_accumulator.round_number() {
|
} else if round_number == self.future_accumulator.round_number() {
|
||||||
self.future_accumulator.import_message(msg);
|
self.future_accumulator.import_message(msg)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(misbehavior) = misbehavior {
|
||||||
|
self.misbehavior.insert(sender, misbehavior);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,10 +600,10 @@ impl<C: Context> Strategy<C> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(digest) = prepare_for {
|
if let Some(digest) = prepare_for {
|
||||||
let message = Message::Prepare(
|
let message = Vote::Prepare(
|
||||||
self.current_accumulator.round_number(),
|
self.current_accumulator.round_number(),
|
||||||
digest
|
digest
|
||||||
);
|
).into();
|
||||||
|
|
||||||
self.import_and_send_message(message, context, sending);
|
self.import_and_send_message(message, context, sending);
|
||||||
self.local_state = LocalState::Prepared;
|
self.local_state = LocalState::Prepared;
|
||||||
@@ -559,10 +633,10 @@ impl<C: Context> Strategy<C> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(digest) = commit_for {
|
if let Some(digest) = commit_for {
|
||||||
let message = Message::Commit(
|
let message = Vote::Commit(
|
||||||
self.current_accumulator.round_number(),
|
self.current_accumulator.round_number(),
|
||||||
digest
|
digest
|
||||||
);
|
).into();
|
||||||
|
|
||||||
self.import_and_send_message(message, context, sending);
|
self.import_and_send_message(message, context, sending);
|
||||||
self.local_state = LocalState::Committed;
|
self.local_state = LocalState::Committed;
|
||||||
@@ -588,9 +662,9 @@ impl<C: Context> Strategy<C> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if attempt_advance {
|
if attempt_advance {
|
||||||
let message = Message::AdvanceRound(
|
let message = Vote::AdvanceRound(
|
||||||
self.current_accumulator.round_number(),
|
self.current_accumulator.round_number(),
|
||||||
);
|
).into();
|
||||||
|
|
||||||
self.import_and_send_message(message, context, sending);
|
self.import_and_send_message(message, context, sending);
|
||||||
self.local_state = LocalState::VoteAdvance;
|
self.local_state = LocalState::VoteAdvance;
|
||||||
@@ -715,6 +789,18 @@ impl<C, I, O> Future for Agreement<C, I, O>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<C: Context, I, O> Agreement<C, I, O> {
|
||||||
|
/// Get a reference to the underlying context.
|
||||||
|
pub fn context(&self) -> &C {
|
||||||
|
&self.context
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drain the misbehavior vector.
|
||||||
|
pub fn drain_misbehavior(&mut self) -> hash_map::Drain<C::AuthorityId, Misbehavior<C::Digest, C::Signature>> {
|
||||||
|
self.strategy.misbehavior.drain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to reach BFT agreement on a candidate.
|
/// Attempt to reach BFT agreement on a candidate.
|
||||||
///
|
///
|
||||||
/// `nodes` is the number of nodes in the system.
|
/// `nodes` is the number of nodes in the system.
|
||||||
|
|||||||
@@ -191,10 +191,21 @@ impl Context for TestContext {
|
|||||||
-> LocalizedMessage<Candidate, Digest, AuthorityId, Signature>
|
-> LocalizedMessage<Candidate, Digest, AuthorityId, Signature>
|
||||||
{
|
{
|
||||||
let signature = Signature(message.clone(), self.local_id.clone());
|
let signature = Signature(message.clone(), self.local_id.clone());
|
||||||
LocalizedMessage {
|
|
||||||
message,
|
match message {
|
||||||
signature,
|
Message::Propose(r, proposal) => LocalizedMessage::Propose(LocalizedProposal {
|
||||||
sender: self.local_id.clone()
|
round_number: r,
|
||||||
|
digest: Digest(proposal.0),
|
||||||
|
proposal,
|
||||||
|
digest_signature: signature.clone(),
|
||||||
|
full_signature: signature,
|
||||||
|
sender: self.local_id.clone(),
|
||||||
|
}),
|
||||||
|
Message::Vote(vote) => LocalizedMessage::Vote(LocalizedVote {
|
||||||
|
vote,
|
||||||
|
signature,
|
||||||
|
sender: self.local_id.clone(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +344,7 @@ fn threshold_plus_one_locked_on_proposal_only_one_with_candidate() {
|
|||||||
round_number: locked_round,
|
round_number: locked_round,
|
||||||
digest: locked_digest.clone(),
|
digest: locked_digest.clone(),
|
||||||
signatures: (0..7)
|
signatures: (0..7)
|
||||||
.map(|i| Signature(Message::Prepare(locked_round, locked_digest.clone()), AuthorityId(i)))
|
.map(|i| Signature(Message::Vote(Vote::Prepare(locked_round, locked_digest.clone())), AuthorityId(i)))
|
||||||
.collect()
|
.collect()
|
||||||
}.check(7, |_, _, s| Some(s.1.clone())).unwrap();
|
}.check(7, |_, _, s| Some(s.1.clone())).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ pub type Committed = generic::Committed<Block, HeaderHash, LocalizedSignature>;
|
|||||||
/// Communication between BFT participants.
|
/// Communication between BFT participants.
|
||||||
pub type Communication = generic::Communication<Block, HeaderHash, AuthorityId, LocalizedSignature>;
|
pub type Communication = generic::Communication<Block, HeaderHash, AuthorityId, LocalizedSignature>;
|
||||||
|
|
||||||
|
/// Misbehavior observed from BFT participants.
|
||||||
|
pub type Misbehavior = generic::Misbehavior<HeaderHash, LocalizedSignature>;
|
||||||
|
|
||||||
/// Proposer factory. Can be used to create a proposer instance.
|
/// Proposer factory. Can be used to create a proposer instance.
|
||||||
pub trait ProposerFactory {
|
pub trait ProposerFactory {
|
||||||
/// The proposer type this creates.
|
/// The proposer type this creates.
|
||||||
@@ -129,6 +132,8 @@ pub trait Proposer {
|
|||||||
/// Evaluate proposal. True means valid.
|
/// Evaluate proposal. True means valid.
|
||||||
// TODO: change this to a future.
|
// TODO: change this to a future.
|
||||||
fn evaluate(&self, proposal: &Block) -> Self::Evaluate;
|
fn evaluate(&self, proposal: &Block) -> Self::Evaluate;
|
||||||
|
/// Import witnessed misbehavior.
|
||||||
|
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, Misbehavior)>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block import trait.
|
/// Block import trait.
|
||||||
@@ -269,6 +274,14 @@ impl<P: Proposer, I: BlockImport> Future for BftFuture<P, I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P: Proposer, I> Drop for BftFuture<P, I> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// TODO: have a trait member to pass misbehavior reports into.
|
||||||
|
let misbehavior = self.inner.drain_misbehavior().collect::<Vec<_>>();
|
||||||
|
self.inner.context().proposer.import_misbehavior(misbehavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct AgreementHandle {
|
struct AgreementHandle {
|
||||||
cancel: Arc<AtomicBool>,
|
cancel: Arc<AtomicBool>,
|
||||||
task: Option<oneshot::Receiver<task::Task>>,
|
task: Option<oneshot::Receiver<task::Task>>,
|
||||||
@@ -317,7 +330,6 @@ impl<P, E, I> BftService<P, E, I>
|
|||||||
|
|
||||||
let authorities = self.client.authorities(&BlockId::Hash(hash))?;
|
let authorities = self.client.authorities(&BlockId::Hash(hash))?;
|
||||||
|
|
||||||
// TODO: check key is one of the authorities.
|
|
||||||
let n = authorities.len();
|
let n = authorities.len();
|
||||||
let max_faulty = max_faulty_of(n);
|
let max_faulty = max_faulty_of(n);
|
||||||
|
|
||||||
@@ -426,28 +438,49 @@ pub fn check_prepare_justification(authorities: &[AuthorityId], parent: HeaderHa
|
|||||||
|
|
||||||
/// Sign a BFT message with the given key.
|
/// Sign a BFT message with the given key.
|
||||||
pub fn sign_message(message: Message, key: &ed25519::Pair, parent_hash: HeaderHash) -> LocalizedMessage {
|
pub fn sign_message(message: Message, key: &ed25519::Pair, parent_hash: HeaderHash) -> LocalizedMessage {
|
||||||
let action = match message.clone() {
|
let signer = key.public();
|
||||||
::generic::Message::Propose(r, p) => PrimitiveAction::Propose(r as u32, p),
|
|
||||||
::generic::Message::Prepare(r, h) => PrimitiveAction::Prepare(r as u32, h),
|
let sign_action = |action| {
|
||||||
::generic::Message::Commit(r, h) => PrimitiveAction::Commit(r as u32, h),
|
let primitive = PrimitiveMessage {
|
||||||
::generic::Message::AdvanceRound(r) => PrimitiveAction::AdvanceRound(r as u32),
|
parent: parent_hash,
|
||||||
|
action,
|
||||||
|
};
|
||||||
|
|
||||||
|
let to_sign = Slicable::encode(&primitive);
|
||||||
|
LocalizedSignature {
|
||||||
|
signer: signer.clone(),
|
||||||
|
signature: key.sign(&to_sign),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let primitive = PrimitiveMessage {
|
match message {
|
||||||
parent: parent_hash,
|
::generic::Message::Propose(r, proposal) => {
|
||||||
action,
|
let header_hash = proposal.header.hash();
|
||||||
};
|
let action_header = PrimitiveAction::ProposeHeader(r as u32, header_hash.clone());
|
||||||
|
let action_propose = PrimitiveAction::Propose(r as u32, proposal.clone());
|
||||||
|
|
||||||
let to_sign = Slicable::encode(&primitive);
|
::generic::LocalizedMessage::Propose(::generic::LocalizedProposal {
|
||||||
let signature = LocalizedSignature {
|
round_number: r,
|
||||||
signer: key.public(),
|
proposal,
|
||||||
signature: key.sign(&to_sign),
|
digest: header_hash,
|
||||||
};
|
sender: signer.0,
|
||||||
|
digest_signature: sign_action(action_header),
|
||||||
|
full_signature: sign_action(action_propose),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
::generic::Message::Vote(vote) => {
|
||||||
|
let action = match vote {
|
||||||
|
::generic::Vote::Prepare(r, h) => PrimitiveAction::Prepare(r as u32, h),
|
||||||
|
::generic::Vote::Commit(r, h) => PrimitiveAction::Commit(r as u32, h),
|
||||||
|
::generic::Vote::AdvanceRound(r) => PrimitiveAction::AdvanceRound(r as u32),
|
||||||
|
};
|
||||||
|
|
||||||
LocalizedMessage {
|
::generic::LocalizedMessage::Vote(::generic::LocalizedVote {
|
||||||
message,
|
vote: vote,
|
||||||
signature,
|
sender: signer.0,
|
||||||
sender: key.public().0
|
signature: sign_action(action),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,6 +539,8 @@ mod tests {
|
|||||||
fn evaluate(&self, proposal: &Block) -> Result<bool, Error> {
|
fn evaluate(&self, proposal: &Block) -> Result<bool, Error> {
|
||||||
Ok(proposal.header.number == self.0)
|
Ok(proposal.header.number == self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn import_misbehavior(&self, _misbehavior: Vec<(AuthorityId, Misbehavior)>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_service(client: FakeClient, handle: Handle)
|
fn make_service(client: FakeClient, handle: Handle)
|
||||||
@@ -522,6 +557,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sign_vote(vote: ::generic::Vote<HeaderHash>, key: &ed25519::Pair, parent_hash: HeaderHash) -> LocalizedSignature {
|
||||||
|
match sign_message(vote.into(), key, parent_hash) {
|
||||||
|
::generic::LocalizedMessage::Vote(vote) => vote.signature,
|
||||||
|
_ => panic!("signing vote leads to signed vote"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn future_gets_preempted() {
|
fn future_gets_preempted() {
|
||||||
let client = FakeClient {
|
let client = FakeClient {
|
||||||
@@ -591,7 +633,7 @@ mod tests {
|
|||||||
digest: hash,
|
digest: hash,
|
||||||
round_number: 1,
|
round_number: 1,
|
||||||
signatures: authorities_keys.iter().take(3).map(|key| {
|
signatures: authorities_keys.iter().take(3).map(|key| {
|
||||||
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
|
sign_vote(generic::Vote::Commit(1, hash).into(), key, parent_hash)
|
||||||
}).collect(),
|
}).collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -601,7 +643,7 @@ mod tests {
|
|||||||
digest: hash,
|
digest: hash,
|
||||||
round_number: 0, // wrong round number (vs. the signatures)
|
round_number: 0, // wrong round number (vs. the signatures)
|
||||||
signatures: authorities_keys.iter().take(3).map(|key| {
|
signatures: authorities_keys.iter().take(3).map(|key| {
|
||||||
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
|
sign_vote(generic::Vote::Commit(1, hash).into(), key, parent_hash)
|
||||||
}).collect(),
|
}).collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -612,7 +654,7 @@ mod tests {
|
|||||||
digest: hash,
|
digest: hash,
|
||||||
round_number: 1,
|
round_number: 1,
|
||||||
signatures: authorities_keys.iter().take(2).map(|key| {
|
signatures: authorities_keys.iter().take(2).map(|key| {
|
||||||
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
|
sign_vote(generic::Vote::Commit(1, hash).into(), key, parent_hash)
|
||||||
}).collect(),
|
}).collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -623,7 +665,7 @@ mod tests {
|
|||||||
digest: [0xfe; 32].into(),
|
digest: [0xfe; 32].into(),
|
||||||
round_number: 1,
|
round_number: 1,
|
||||||
signatures: authorities_keys.iter().take(3).map(|key| {
|
signatures: authorities_keys.iter().take(3).map(|key| {
|
||||||
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
|
sign_vote(generic::Vote::Commit(1, hash).into(), key, parent_hash)
|
||||||
}).collect(),
|
}).collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -393,11 +393,16 @@ mod tests {
|
|||||||
bft::UncheckedJustification {
|
bft::UncheckedJustification {
|
||||||
digest: hash,
|
digest: hash,
|
||||||
signatures: authorities.iter().map(|key| {
|
signatures: authorities.iter().map(|key| {
|
||||||
bft::sign_message(
|
let msg = bft::sign_message(
|
||||||
bft::generic::Message::Commit(1, hash),
|
bft::generic::Vote::Commit(1, hash).into(),
|
||||||
key,
|
key,
|
||||||
header.parent_hash
|
header.parent_hash
|
||||||
).signature
|
);
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
bft::generic::LocalizedMessage::Vote(vote) => vote.signature,
|
||||||
|
_ => panic!("signing vote leads to signed vote"),
|
||||||
|
}
|
||||||
}).collect(),
|
}).collect(),
|
||||||
round_number: 1,
|
round_number: 1,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
//! Support code for the runtime.
|
//! Support code for the runtime.
|
||||||
|
|
||||||
#[macro_use] extern crate hex_literal;
|
#[macro_use] extern crate hex_literal;
|
||||||
extern crate ed25519;
|
pub extern crate ed25519;
|
||||||
|
|
||||||
use ed25519::{Pair, Public, Signature};
|
use ed25519::{Pair, Public, Signature};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "substrate-misbehavior-check"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
substrate-codec = { path = "../codec", default-features = false }
|
||||||
|
substrate-primitives = { path = "../primitives", default-features = false }
|
||||||
|
substrate-runtime-io = { path = "../runtime-io", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
substrate-bft = { path = "../bft" }
|
||||||
|
substrate-keyring = { path = "../keyring" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = ["substrate-codec/std", "substrate-primitives/std", "substrate-runtime-io/std"]
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot 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.
|
||||||
|
|
||||||
|
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Utility for substrate-based runtimes that want to check misbehavior reports.
|
||||||
|
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
extern crate substrate_codec as codec;
|
||||||
|
extern crate substrate_primitives as primitives;
|
||||||
|
extern crate substrate_runtime_io as runtime_io;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate substrate_bft;
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate substrate_keyring as keyring;
|
||||||
|
|
||||||
|
use codec::Slicable;
|
||||||
|
use primitives::{AuthorityId, Signature};
|
||||||
|
use primitives::block::HeaderHash;
|
||||||
|
use primitives::bft::{Action, Message, MisbehaviorKind};
|
||||||
|
|
||||||
|
// check a message signature. returns true if signed by that authority.
|
||||||
|
fn check_message_sig(message: Message, signature: &Signature, from: &AuthorityId) -> bool {
|
||||||
|
let msg = message.encode();
|
||||||
|
runtime_io::ed25519_verify(&signature.0, &msg, from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(parent: HeaderHash, round_number: u32, hash: HeaderHash) -> Message {
|
||||||
|
Message {
|
||||||
|
parent,
|
||||||
|
action: Action::Prepare(round_number, hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(parent: HeaderHash, round_number: u32, hash: HeaderHash) -> Message {
|
||||||
|
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(
|
||||||
|
misbehaved: &AuthorityId,
|
||||||
|
parent_hash: HeaderHash,
|
||||||
|
kind: &MisbehaviorKind,
|
||||||
|
) -> bool {
|
||||||
|
match *kind {
|
||||||
|
MisbehaviorKind::BftDoublePrepare(round, (h_1, ref s_1), (h_2, ref s_2)) => {
|
||||||
|
s_1 != s_2 &&
|
||||||
|
check_message_sig(prepare(parent_hash, round, h_1), s_1, misbehaved) &&
|
||||||
|
check_message_sig(prepare(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(commit(parent_hash, round, h_1), s_1, misbehaved) &&
|
||||||
|
check_message_sig(commit(parent_hash, round, h_2), s_2, misbehaved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use substrate_bft::generic;
|
||||||
|
use keyring::ed25519;
|
||||||
|
use keyring::Keyring;
|
||||||
|
|
||||||
|
fn sign_prepare(key: &ed25519::Pair, round: u32, hash: HeaderHash, parent_hash: HeaderHash) -> (HeaderHash, Signature) {
|
||||||
|
let msg = substrate_bft::sign_message(
|
||||||
|
generic::Message::Vote(generic::Vote::Prepare(round as _, hash)),
|
||||||
|
key,
|
||||||
|
parent_hash
|
||||||
|
);
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
generic::LocalizedMessage::Vote(vote) => (hash, vote.signature.signature),
|
||||||
|
_ => panic!("signing vote leads to signed vote"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_commit(key: &ed25519::Pair, round: u32, hash: HeaderHash, parent_hash: HeaderHash) -> (HeaderHash, Signature) {
|
||||||
|
let msg = substrate_bft::sign_message(
|
||||||
|
generic::Message::Vote(generic::Vote::Commit(round as _, hash)),
|
||||||
|
key,
|
||||||
|
parent_hash
|
||||||
|
);
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
generic::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(
|
||||||
|
&key.public().0,
|
||||||
|
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(
|
||||||
|
&key.public().0,
|
||||||
|
parent_hash,
|
||||||
|
&MisbehaviorKind::BftDoublePrepare(
|
||||||
|
1,
|
||||||
|
signed,
|
||||||
|
signed,
|
||||||
|
)
|
||||||
|
) == false);
|
||||||
|
|
||||||
|
// misbehavior has wrong target.
|
||||||
|
assert!(evaluate_misbehavior(
|
||||||
|
&Keyring::Two.to_raw_public(),
|
||||||
|
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(
|
||||||
|
&key.public().0,
|
||||||
|
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(
|
||||||
|
&key.public().0,
|
||||||
|
parent_hash,
|
||||||
|
&MisbehaviorKind::BftDoubleCommit(
|
||||||
|
1,
|
||||||
|
signed,
|
||||||
|
signed,
|
||||||
|
)
|
||||||
|
) == false);
|
||||||
|
|
||||||
|
// misbehavior has wrong target.
|
||||||
|
assert!(evaluate_misbehavior(
|
||||||
|
&Keyring::Two.to_raw_public(),
|
||||||
|
parent_hash,
|
||||||
|
&MisbehaviorKind::BftDoubleCommit(
|
||||||
|
1,
|
||||||
|
sign_commit(&key, 1, hash_1, parent_hash),
|
||||||
|
sign_commit(&key, 1, hash_2, parent_hash),
|
||||||
|
)
|
||||||
|
) == false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -167,11 +167,16 @@ impl Peer {
|
|||||||
bft::UncheckedJustification {
|
bft::UncheckedJustification {
|
||||||
digest: hash,
|
digest: hash,
|
||||||
signatures: authorities.iter().map(|key| {
|
signatures: authorities.iter().map(|key| {
|
||||||
bft::sign_message(
|
let msg = bft::sign_message(
|
||||||
bft::generic::Message::Commit(1, hash),
|
bft::generic::Vote::Commit(1, hash).into(),
|
||||||
key,
|
key,
|
||||||
header.parent_hash
|
header.parent_hash
|
||||||
).signature
|
);
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
bft::generic::LocalizedMessage::Vote(vote) => vote.signature,
|
||||||
|
_ => panic!("signing vote leads to signed vote"),
|
||||||
|
}
|
||||||
}).collect(),
|
}).collect(),
|
||||||
round_number: 1,
|
round_number: 1,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,17 @@
|
|||||||
use block::{Block, HeaderHash};
|
use block::{Block, HeaderHash};
|
||||||
use codec::{Slicable, Input};
|
use codec::{Slicable, Input};
|
||||||
use rstd::vec::Vec;
|
use rstd::vec::Vec;
|
||||||
|
use ::{AuthorityId, Signature};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "std", derive(Debug))]
|
#[cfg_attr(feature = "std", derive(Debug))]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
enum ActionKind {
|
enum ActionKind {
|
||||||
Propose = 1,
|
Propose = 1,
|
||||||
Prepare = 2,
|
ProposeHeader = 2,
|
||||||
Commit = 3,
|
Prepare = 3,
|
||||||
AdvanceRound = 4,
|
Commit = 4,
|
||||||
|
AdvanceRound = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actions which can be taken during the BFT process.
|
/// Actions which can be taken during the BFT process.
|
||||||
@@ -36,6 +38,9 @@ enum ActionKind {
|
|||||||
pub enum Action {
|
pub enum Action {
|
||||||
/// Proposal of a block candidate.
|
/// Proposal of a block candidate.
|
||||||
Propose(u32, Block),
|
Propose(u32, Block),
|
||||||
|
/// Proposal header of a block candidate. Accompanies any proposal,
|
||||||
|
/// but is used for misbehavior reporting since blocks themselves are big.
|
||||||
|
ProposeHeader(u32, HeaderHash),
|
||||||
/// Preparation to commit for a candidate.
|
/// Preparation to commit for a candidate.
|
||||||
Prepare(u32, HeaderHash),
|
Prepare(u32, HeaderHash),
|
||||||
/// Vote to commit to a candidate.
|
/// Vote to commit to a candidate.
|
||||||
@@ -53,6 +58,11 @@ impl Slicable for Action {
|
|||||||
round.using_encoded(|s| v.extend(s));
|
round.using_encoded(|s| v.extend(s));
|
||||||
block.using_encoded(|s| v.extend(s));
|
block.using_encoded(|s| v.extend(s));
|
||||||
}
|
}
|
||||||
|
Action::ProposeHeader(ref round, ref hash) => {
|
||||||
|
v.push(ActionKind::ProposeHeader as u8);
|
||||||
|
round.using_encoded(|s| v.extend(s));
|
||||||
|
hash.using_encoded(|s| v.extend(s));
|
||||||
|
}
|
||||||
Action::Prepare(ref round, ref hash) => {
|
Action::Prepare(ref round, ref hash) => {
|
||||||
v.push(ActionKind::Prepare as u8);
|
v.push(ActionKind::Prepare as u8);
|
||||||
round.using_encoded(|s| v.extend(s));
|
round.using_encoded(|s| v.extend(s));
|
||||||
@@ -78,6 +88,11 @@ impl Slicable for Action {
|
|||||||
let (round, block) = try_opt!(Slicable::decode(value));
|
let (round, block) = try_opt!(Slicable::decode(value));
|
||||||
Some(Action::Propose(round, block))
|
Some(Action::Propose(round, block))
|
||||||
}
|
}
|
||||||
|
Some(x) if x == ActionKind::ProposeHeader as u8 => {
|
||||||
|
let (round, hash) = try_opt!(Slicable::decode(value));
|
||||||
|
|
||||||
|
Some(Action::ProposeHeader(round, hash))
|
||||||
|
}
|
||||||
Some(x) if x == ActionKind::Prepare as u8 => {
|
Some(x) if x == ActionKind::Prepare as u8 => {
|
||||||
let (round, hash) = try_opt!(Slicable::decode(value));
|
let (round, hash) = try_opt!(Slicable::decode(value));
|
||||||
|
|
||||||
@@ -152,3 +167,142 @@ impl Slicable for Justification {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// single-byte code to represent misbehavior kind.
|
||||||
|
#[repr(u8)]
|
||||||
|
enum MisbehaviorCode {
|
||||||
|
/// BFT: double prepare.
|
||||||
|
BftDoublePrepare = 0x11,
|
||||||
|
/// BFT: double commit.
|
||||||
|
BftDoubleCommit = 0x12,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MisbehaviorCode {
|
||||||
|
fn from_u8(x: u8) -> Option<Self> {
|
||||||
|
match x {
|
||||||
|
0x11 => Some(MisbehaviorCode::BftDoublePrepare),
|
||||||
|
0x12 => Some(MisbehaviorCode::BftDoubleCommit),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Misbehavior kinds.
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
|
||||||
|
pub enum MisbehaviorKind {
|
||||||
|
/// BFT: double prepare.
|
||||||
|
BftDoublePrepare(u32, (HeaderHash, Signature), (HeaderHash, Signature)),
|
||||||
|
/// BFT: double commit.
|
||||||
|
BftDoubleCommit(u32, (HeaderHash, Signature), (HeaderHash, Signature)),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A report of misbehavior by an authority.
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
|
||||||
|
pub struct MisbehaviorReport {
|
||||||
|
/// The parent hash of the block where the misbehavior occurred.
|
||||||
|
pub parent_hash: HeaderHash,
|
||||||
|
/// The parent number of the block where the misbehavior occurred.
|
||||||
|
pub parent_number: ::block::Number,
|
||||||
|
/// The authority who misbehavior.
|
||||||
|
pub target: AuthorityId,
|
||||||
|
/// The misbehavior kind.
|
||||||
|
pub misbehavior: MisbehaviorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Slicable for MisbehaviorReport {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
self.parent_hash.using_encoded(|s| v.extend(s));
|
||||||
|
self.parent_number.using_encoded(|s| v.extend(s));
|
||||||
|
self.target.using_encoded(|s| v.extend(s));
|
||||||
|
|
||||||
|
match self.misbehavior {
|
||||||
|
MisbehaviorKind::BftDoublePrepare(ref round, (ref h_a, ref s_a), (ref h_b, ref s_b)) => {
|
||||||
|
(MisbehaviorCode::BftDoublePrepare as u8).using_encoded(|s| v.extend(s));
|
||||||
|
round.using_encoded(|s| v.extend(s));
|
||||||
|
h_a.using_encoded(|s| v.extend(s));
|
||||||
|
s_a.using_encoded(|s| v.extend(s));
|
||||||
|
h_b.using_encoded(|s| v.extend(s));
|
||||||
|
s_b.using_encoded(|s| v.extend(s));
|
||||||
|
}
|
||||||
|
MisbehaviorKind::BftDoubleCommit(ref round, (ref h_a, ref s_a), (ref h_b, ref s_b)) => {
|
||||||
|
(MisbehaviorCode::BftDoubleCommit as u8).using_encoded(|s| v.extend(s));
|
||||||
|
round.using_encoded(|s| v.extend(s));
|
||||||
|
h_a.using_encoded(|s| v.extend(s));
|
||||||
|
s_a.using_encoded(|s| v.extend(s));
|
||||||
|
h_b.using_encoded(|s| v.extend(s));
|
||||||
|
s_b.using_encoded(|s| v.extend(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||||
|
let parent_hash = HeaderHash::decode(input)?;
|
||||||
|
let parent_number = ::block::Number::decode(input)?;
|
||||||
|
let target = AuthorityId::decode(input)?;
|
||||||
|
|
||||||
|
let misbehavior = match u8::decode(input).and_then(MisbehaviorCode::from_u8)? {
|
||||||
|
MisbehaviorCode::BftDoublePrepare => {
|
||||||
|
MisbehaviorKind::BftDoublePrepare(
|
||||||
|
u32::decode(input)?,
|
||||||
|
(HeaderHash::decode(input)?, Signature::decode(input)?),
|
||||||
|
(HeaderHash::decode(input)?, Signature::decode(input)?),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MisbehaviorCode::BftDoubleCommit => {
|
||||||
|
MisbehaviorKind::BftDoubleCommit(
|
||||||
|
u32::decode(input)?,
|
||||||
|
(HeaderHash::decode(input)?, Signature::decode(input)?),
|
||||||
|
(HeaderHash::decode(input)?, Signature::decode(input)?),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(MisbehaviorReport {
|
||||||
|
parent_hash,
|
||||||
|
parent_number,
|
||||||
|
target,
|
||||||
|
misbehavior,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn misbehavior_report_roundtrip() {
|
||||||
|
let report = MisbehaviorReport {
|
||||||
|
parent_hash: [0; 32].into(),
|
||||||
|
parent_number: 999,
|
||||||
|
target: [1; 32].into(),
|
||||||
|
misbehavior: MisbehaviorKind::BftDoubleCommit(
|
||||||
|
511,
|
||||||
|
([2; 32].into(), [3; 64].into()),
|
||||||
|
([4; 32].into(), [5; 64].into()),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded = report.encode();
|
||||||
|
assert_eq!(MisbehaviorReport::decode(&mut &encoded[..]).unwrap(), report);
|
||||||
|
|
||||||
|
let report = MisbehaviorReport {
|
||||||
|
parent_hash: [0; 32].into(),
|
||||||
|
parent_number: 999,
|
||||||
|
target: [1; 32].into(),
|
||||||
|
misbehavior: MisbehaviorKind::BftDoublePrepare(
|
||||||
|
511,
|
||||||
|
([2; 32].into(), [3; 64].into()),
|
||||||
|
([4; 32].into(), [5; 64].into()),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded = report.encode();
|
||||||
|
assert_eq!(MisbehaviorReport::decode(&mut &encoded[..]).unwrap(), report);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
pub use std::borrow;
|
||||||
pub use std::boxed;
|
pub use std::boxed;
|
||||||
pub use std::cell;
|
pub use std::cell;
|
||||||
pub use std::cmp;
|
pub use std::cmp;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ extern crate pwasm_alloc;
|
|||||||
pub use alloc::boxed;
|
pub use alloc::boxed;
|
||||||
pub use alloc::rc;
|
pub use alloc::rc;
|
||||||
pub use alloc::vec;
|
pub use alloc::vec;
|
||||||
|
pub use core::borrow;
|
||||||
pub use core::cell;
|
pub use core::cell;
|
||||||
pub use core::cmp;
|
pub use core::cmp;
|
||||||
pub use core::intrinsics;
|
pub use core::intrinsics;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
//! Stuff to do with the runtime's storage.
|
//! Stuff to do with the runtime's storage.
|
||||||
|
|
||||||
use rstd::prelude::*;
|
use rstd::prelude::*;
|
||||||
|
use rstd::borrow::Borrow;
|
||||||
use runtime_io::{self, twox_128};
|
use runtime_io::{self, twox_128};
|
||||||
use codec::{Slicable, KeyedVec, Input};
|
use codec::{Slicable, KeyedVec, Input};
|
||||||
|
|
||||||
@@ -131,9 +132,26 @@ pub trait StorageVec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current set of items.
|
/// Set the current set of items.
|
||||||
fn set_items(items: &[Self::Item]) {
|
fn set_items<I, T>(items: I)
|
||||||
Self::set_count(items.len() as u32);
|
where
|
||||||
items.iter().enumerate().for_each(|(v, ref i)| Self::set_item(v as u32, i));
|
I: IntoIterator<Item=T>,
|
||||||
|
T: Borrow<Self::Item>,
|
||||||
|
{
|
||||||
|
let mut count: u32 = 0;
|
||||||
|
|
||||||
|
for i in items.into_iter() {
|
||||||
|
put(&count.to_keyed_vec(Self::PREFIX), i.borrow());
|
||||||
|
count = count.checked_add(1).expect("exceeded runtime storage capacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::set_count(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push an item.
|
||||||
|
fn push(item: &Self::Item) {
|
||||||
|
let len = Self::count();
|
||||||
|
put(&len.to_keyed_vec(Self::PREFIX), item);
|
||||||
|
Self::set_count(len + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_item(index: u32, item: &Self::Item) {
|
fn set_item(index: u32, item: &Self::Item) {
|
||||||
@@ -163,6 +181,7 @@ pub trait StorageVec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod unhashed {
|
pub mod unhashed {
|
||||||
|
use rstd::borrow::Borrow;
|
||||||
use super::{runtime_io, Slicable, KeyedVec, Vec, IncrementalInput};
|
use super::{runtime_io, Slicable, KeyedVec, Vec, IncrementalInput};
|
||||||
|
|
||||||
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
||||||
@@ -258,9 +277,19 @@ pub mod unhashed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current set of items.
|
/// Set the current set of items.
|
||||||
fn set_items(items: &[Self::Item]) {
|
fn set_items<I, T>(items: I)
|
||||||
Self::set_count(items.len() as u32);
|
where
|
||||||
items.iter().enumerate().for_each(|(v, ref i)| Self::set_item(v as u32, i));
|
I: IntoIterator<Item=T>,
|
||||||
|
T: Borrow<Self::Item>,
|
||||||
|
{
|
||||||
|
let mut count: u32 = 0;
|
||||||
|
|
||||||
|
for i in items.into_iter() {
|
||||||
|
put(&count.to_keyed_vec(Self::PREFIX), i.borrow());
|
||||||
|
count = count.checked_add(1).expect("exceeded runtime storage capacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::set_count(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_item(index: u32, item: &Self::Item) {
|
fn set_item(index: u32, item: &Self::Item) {
|
||||||
@@ -293,8 +322,8 @@ pub mod unhashed {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use primitives::hexdisplay::HexDisplay;
|
use primitives::hexdisplay;
|
||||||
use runtime_io::{storage, twox_128, TestExternalities, with_externalities};
|
use runtime_io::{twox_128, TestExternalities, with_externalities};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integers_can_be_stored() {
|
fn integers_can_be_stored() {
|
||||||
@@ -337,7 +366,6 @@ mod tests {
|
|||||||
with_externalities(&mut t, || {
|
with_externalities(&mut t, || {
|
||||||
runtime_io::set_storage(&twox_128(b":test"), b"\x0b\0\0\0Hello world");
|
runtime_io::set_storage(&twox_128(b":test"), b"\x0b\0\0\0Hello world");
|
||||||
let x = b"Hello world".to_vec();
|
let x = b"Hello world".to_vec();
|
||||||
println!("Hex: {}", HexDisplay::from(&storage(&twox_128(b":test")).unwrap()));
|
|
||||||
let y = get::<Vec<u8>>(b":test").unwrap();
|
let y = get::<Vec<u8>>(b":test").unwrap();
|
||||||
assert_eq!(x, y);
|
assert_eq!(x, y);
|
||||||
|
|
||||||
@@ -353,9 +381,7 @@ mod tests {
|
|||||||
put(b":test", &x);
|
put(b":test", &x);
|
||||||
});
|
});
|
||||||
|
|
||||||
println!("Ext is {:?}", t);
|
|
||||||
with_externalities(&mut t, || {
|
with_externalities(&mut t, || {
|
||||||
println!("Hex: {}", HexDisplay::from(&storage(&twox_128(b":test")).unwrap()));
|
|
||||||
let y: Vec<u8> = get(b":test").unwrap();
|
let y: Vec<u8> = get(b":test").unwrap();
|
||||||
assert_eq!(x, y);
|
assert_eq!(x, y);
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user