diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index e29aa3a664c20154197184ac96897af3fcf9bc67..d270ab4d0c558560e4b5a61678ccbdae4551d37b 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -1120,6 +1120,7 @@ dependencies = [ "ed25519 0.1.0", "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)", + "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)", "polkadot-api 0.1.0", "polkadot-collator 0.1.0", @@ -1173,6 +1174,7 @@ dependencies = [ "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-keyring 0.1.0", + "substrate-misbehavior-check 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-std 0.1.0", @@ -1601,6 +1603,17 @@ dependencies = [ "lazy_static 1.0.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]] name = "substrate-network" version = "0.1.0" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index 635370e2dfcb8be558134b90d6243fa85ea81c4b..27ff076184e40a75d0834b8466e0996459b077ed 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -30,6 +30,7 @@ members = [ "substrate/executor", "substrate/keyring", "substrate/network", + "substrate/misbehavior-check", "substrate/primitives", "substrate/rpc-servers", "substrate/rpc", diff --git a/substrate/demo/primitives/src/transaction.rs b/substrate/demo/primitives/src/transaction.rs index cc7e410a72c4412652d65b7296cae0251a4c1bf9..8f0027459a6b4f2d5ca189a548dae2cc189a4360 100644 --- a/substrate/demo/primitives/src/transaction.rs +++ b/substrate/demo/primitives/src/transaction.rs @@ -16,7 +16,7 @@ //! Transaction type. -use rstd::vec::Vec; +use rstd::prelude::*; use codec::{Input, Slicable, NonTrivialSlicable}; use {AccountId, SessionKey}; @@ -75,6 +75,7 @@ impl InternalFunctionId { } } +/// A means of determining whether a referendum has gone through or not. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] pub enum VoteThreshold { diff --git a/substrate/demo/runtime/src/dispatch.rs b/substrate/demo/runtime/src/dispatch.rs index 9a9aded039fb11a856a32aa5448e5a0e3ae454e4..ee5c03d9520fe151a05f148a0e4030c7ccba52d2 100644 --- a/substrate/demo/runtime/src/dispatch.rs +++ b/substrate/demo/runtime/src/dispatch.rs @@ -40,8 +40,6 @@ pub fn proposal(proposal: Proposal) { democracy::privileged::cancel_referendum(a), Proposal::DemocracyStartReferendum(a, b) => democracy::privileged::start_referendum(*a, b), - Proposal::DemocracyCancelReferendum(a) => - democracy::privileged::cancel_referendum(a), Proposal::CouncilSetDesiredSeats(a) => council::privileged::set_desired_seats(a), Proposal::CouncilRemoveMember(a) => diff --git a/substrate/demo/runtime/src/runtime/council_vote.rs b/substrate/demo/runtime/src/runtime/council_vote.rs index b40c79d79b486dab91204834bd60c57da3af4927..9887d14cae293cf48244a26602871b9ab31ce23e 100644 --- a/substrate/demo/runtime/src/runtime/council_vote.rs +++ b/substrate/demo/runtime/src/runtime/council_vote.rs @@ -190,7 +190,6 @@ pub mod internal { pub fn end_block(now: BlockNumber) { while let Some((proposal, proposal_hash)) = take_proposal_if_expiring_at(now) { let tally = take_tally(&proposal_hash); - println!("Executing proposal {:?} {:?}", proposal, tally); if let &Proposal::DemocracyCancelReferendum(ref_index) = &proposal { if let (_, 0, 0) = tally { democracy::privileged::cancel_referendum(ref_index); diff --git a/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index f542540c93e1ab432c2fed89067e577220d80c48..21685747dc5032d642e339bb5062e9b2f58d1f2a 100644 Binary files a/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index 8ecf2b7224ed72d40b1bee2ca4d924177a099644..639cf3e8e4f2128ceac881d694983b02b7d3f0e8 100644 Binary files a/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/substrate/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/substrate/polkadot/consensus/Cargo.toml b/substrate/polkadot/consensus/Cargo.toml index 60c8009b7c9d5652afe89b0dfbe49341b0fe9746..b7ccbc6534b048da19a4a12eea0c2ff297af23d6 100644 --- a/substrate/polkadot/consensus/Cargo.toml +++ b/substrate/polkadot/consensus/Cargo.toml @@ -9,6 +9,7 @@ parking_lot = "0.4" tokio-timer = "0.1.2" ed25519 = { path = "../../substrate/ed25519" } error-chain = "0.11" +log = "0.4" polkadot-api = { path = "../api" } polkadot-collator = { path = "../collator" } polkadot-primitives = { path = "../primitives" } diff --git a/substrate/polkadot/consensus/src/lib.rs b/substrate/polkadot/consensus/src/lib.rs index 7321948dd773432f2fe09d24c1f9577d5d685290..a5c34e30ee406dfaf29f86990b8a78198edc5929 100644 --- a/substrate/polkadot/consensus/src/lib.rs +++ b/substrate/polkadot/consensus/src/lib.rs @@ -45,6 +45,9 @@ extern crate substrate_primitives as primitives; #[macro_use] extern crate error_chain; +#[macro_use] +extern crate log; + use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -55,9 +58,9 @@ use polkadot_api::{PolkadotApi, BlockBuilder}; use polkadot_primitives::{Hash, Timestamp}; use polkadot_primitives::block::Block as PolkadotBlock; 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 transaction_pool::TransactionPool; +use transaction_pool::{Ready, TransactionPool}; use futures::prelude::*; 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 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()); // TODO [PoC-2]: kick off collation process. Ok(Proposer { parent_hash, + parent_number: parent_header.number, parent_id: checked_id, - _table: table, - _router: router, + local_key: sign_with, client: self.client.clone(), transaction_pool: self.transaction_pool.clone(), + _table: table, + _router: router, }) } } @@ -503,8 +508,10 @@ fn current_timestamp() -> Timestamp { /// The Polkadot proposer logic. pub struct Proposer<C: PolkadotApi, R> { parent_hash: HeaderHash, + parent_number: BlockNumber, parent_id: C::CheckedBlockId, client: Arc<C>, + local_key: Arc<ed25519::Pair>, transaction_pool: Arc<Mutex<TransactionPool>>, _table: Arc<SharedTable>, _router: R, @@ -516,8 +523,6 @@ impl<C: PolkadotApi, R: TableRouter> bft::Proposer for Proposer<C, R> { type Evaluate = Result<bool, Error>; fn propose(&self) -> Result<SubstrateBlock, Error> { - use transaction_pool::Ready; - // TODO: handle case when current timestamp behind that in state. let mut block_builder = self.client.build_block( &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> { 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>( diff --git a/substrate/polkadot/executor/src/lib.rs b/substrate/polkadot/executor/src/lib.rs index a1f472a859ec54bcc1a33b9afa9b5b5a0fcee613..186e5469ccfda52b66fde4587b4ad5f775b63a49 100644 --- a/substrate/polkadot/executor/src/lib.rs +++ b/substrate/polkadot/executor/src/lib.rs @@ -162,7 +162,7 @@ mod tests { construct_block( 2, block1().1, - hex!("c8776c92e8012bf6b3f206448eda3f00bca26d77f220f4714c81cbc92a30e1e2").into(), + hex!("5604fe023cd6effd93aec9b4a008398abdd32afb3fec988a19aa853ab0424a7c").into(), 200_000, vec![ Transaction { diff --git a/substrate/polkadot/primitives/src/lib.rs b/substrate/polkadot/primitives/src/lib.rs index d65291476ab715fdfedce1a5cdccf387a1616086..fb02e3d121117e3c6fc56de29d86661f8ba75b5e 100644 --- a/substrate/polkadot/primitives/src/lib.rs +++ b/substrate/polkadot/primitives/src/lib.rs @@ -80,3 +80,9 @@ pub type Signature = primitives::hash::H512; /// A timestamp: seconds since the unix epoch. 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; diff --git a/substrate/polkadot/primitives/src/transaction.rs b/substrate/polkadot/primitives/src/transaction.rs index 3d4095b86c8d7661f84985958504a957a5134a31..f13cf223a93e2a21642ea9826135ff4f8f0abda2 100644 --- a/substrate/polkadot/primitives/src/transaction.rs +++ b/substrate/polkadot/primitives/src/transaction.rs @@ -18,6 +18,7 @@ use rstd::vec::Vec; use codec::{Input, Slicable}; +use primitives::bft::MisbehaviorReport; use ::Signature; #[cfg(feature = "std")] @@ -168,6 +169,8 @@ enum FunctionId { StakingUnstake = 0x21, /// Staking subsystem: transfer stake. StakingTransfer = 0x22, + /// Report misbehavior. + StakingReportMisbehavior = 0x23, /// Make a proposal for the governance system. GovernancePropose = 0x30, /// 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. fn from_u8(value: u8) -> Option<FunctionId> { use self::*; - let functions = [FunctionId::StakingStake, FunctionId::StakingUnstake, - FunctionId::StakingTransfer, FunctionId::SessionSetKey, FunctionId::TimestampSet, - FunctionId::GovernancePropose, FunctionId::GovernanceApprove]; + let functions = [ + FunctionId::StakingStake, + 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) } } @@ -222,6 +232,8 @@ pub enum Function { StakingUnstake, /// Staking subsystem: transfer stake. StakingTransfer(::AccountId, u64), + /// Staking subsystem: report misbehavior of a validator. + ReportMisbehavior(MisbehaviorReport), /// Make a proposal for the governance system. GovernancePropose(Proposal), /// Approve a proposal for the governance system. @@ -269,6 +281,7 @@ impl Slicable for Function { Function::StakingTransfer(to, amount) } + FunctionId::StakingReportMisbehavior => Function::ReportMisbehavior(MisbehaviorReport::decode(input)?), FunctionId::GovernancePropose => Function::GovernancePropose(try_opt!(Slicable::decode(input))), FunctionId::GovernanceApprove => @@ -293,6 +306,10 @@ impl Slicable for Function { Function::StakingUnstake => { (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) => { (FunctionId::StakingTransfer as u8).using_encoded(|s| v.extend(s)); to.using_encoded(|s| v.extend(s)); diff --git a/substrate/polkadot/runtime/Cargo.toml b/substrate/polkadot/runtime/Cargo.toml index 4efe5c15e77ef0d41ab6af152f886c5a83824a45..ce4565025a21e37cd67e72f8c916fbfbc6cd7629 100644 --- a/substrate/polkadot/runtime/Cargo.toml +++ b/substrate/polkadot/runtime/Cargo.toml @@ -12,6 +12,7 @@ substrate-runtime-std = { path = "../../substrate/runtime-std" } substrate-runtime-io = { path = "../../substrate/runtime-io" } substrate-runtime-support = { path = "../../substrate/runtime-support" } substrate-primitives = { path = "../../substrate/primitives" } +substrate-misbehavior-check = { path = "../../substrate/misbehavior-check" } polkadot-primitives = { path = "../primitives" } [dev-dependencies] @@ -25,6 +26,7 @@ std = [ "substrate-runtime-io/std", "substrate-runtime-support/std", "substrate-primitives/std", + "substrate-misbehavior-check/std", "polkadot-primitives/std", "log" ] diff --git a/substrate/polkadot/runtime/src/environment.rs b/substrate/polkadot/runtime/src/environment.rs index 5b311ce1f98b9f74bcfd2457619a3876a520d8aa..b7fef7cdd87fdcad37bc628fa578123bd80e126d 100644 --- a/substrate/polkadot/runtime/src/environment.rs +++ b/substrate/polkadot/runtime/src/environment.rs @@ -31,8 +31,6 @@ pub struct Environment { pub parent_hash: Hash, /// The current block 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. diff --git a/substrate/polkadot/runtime/src/genesismap.rs b/substrate/polkadot/runtime/src/genesismap.rs index 4100f411b0d31d739ab81843bf3505a0c3bd3dbe..d2b370fe4627b6cd3b7b2ad27d06a61227ac6393 100644 --- a/substrate/polkadot/runtime/src/genesismap.rs +++ b/substrate/polkadot/runtime/src/genesismap.rs @@ -21,8 +21,7 @@ use std::collections::HashMap; use runtime_io::twox_128; use runtime_support::Hashable; use primitives::Block; -use polkadot_primitives::{BlockNumber, AccountId}; -use runtime::staking::Balance; +use polkadot_primitives::{Balance, BlockNumber, AccountId}; /// Configuration of a general Polkadot genesis block. pub struct GenesisConfig { diff --git a/substrate/polkadot/runtime/src/lib.rs b/substrate/polkadot/runtime/src/lib.rs index 16379cac8e41188e7d0cb1ab49d05fc1ea9e8552..caf8cb47c1e3bbb0c1f244c3970a2d10110939ef 100644 --- a/substrate/polkadot/runtime/src/lib.rs +++ b/substrate/polkadot/runtime/src/lib.rs @@ -19,23 +19,33 @@ #![cfg_attr(not(feature = "std"), no_std)] 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; -#[cfg(all(feature = "std", test))] extern crate substrate_keyring as keyring; - -#[cfg(feature = "std")] extern crate rustc_hex; - 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; -#[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 environment; pub mod runtime; -#[cfg(feature = "std")] pub mod genesismap; +#[cfg(feature = "std")] +pub mod genesismap; /// Type definitions and helpers for transactions. pub mod transaction { diff --git a/substrate/polkadot/runtime/src/runtime/consensus.rs b/substrate/polkadot/runtime/src/runtime/consensus.rs index 7d50f6abd418f3cdfdf35adb96ed7f2c30c0ccf1..7841f1d47d5bcc0e9bb9f9c08326fa91c6fb635c 100644 --- a/substrate/polkadot/runtime/src/runtime/consensus.rs +++ b/substrate/polkadot/runtime/src/runtime/consensus.rs @@ -23,7 +23,7 @@ use polkadot_primitives::SessionKey; struct AuthorityStorageVec {} impl StorageVec for AuthorityStorageVec { 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. @@ -37,7 +37,7 @@ pub mod internal { /// Set the current set of authorities' session keys. /// /// 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); } diff --git a/substrate/polkadot/runtime/src/runtime/session.rs b/substrate/polkadot/runtime/src/runtime/session.rs index e9bf5f376564005ab01728be436a97e3a14f7227..01a5f78bbc5a090c2e6cd40b80ed97c93b589aec 100644 --- a/substrate/polkadot/runtime/src/runtime/session.rs +++ b/substrate/polkadot/runtime/src/runtime/session.rs @@ -25,14 +25,23 @@ use runtime::{system, staking, consensus}; const SESSION_LENGTH: &[u8] = b"ses:len"; 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 NEXT_KEY_FOR: &[u8] = b"ses:nxt:"; const NEXT_SESSION_LENGTH: &[u8] = b"ses:nln"; -struct ValidatorStorageVec {} +struct ValidatorStorageVec; impl StorageVec for ValidatorStorageVec { 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. @@ -50,11 +59,31 @@ pub fn validator_count() -> u32 { ValidatorStorageVec::count() as u32 } -/// The current era index. +/// The current session index. pub fn current_index() -> BlockNumber { 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. pub fn last_length_change() -> BlockNumber { storage::get_or(LAST_LENGTH_CHANGE, 0) @@ -90,11 +119,14 @@ pub mod privileged { pub mod internal { 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 /// update the session keys to the next validator set. pub fn set_validators(new: &[AccountId]) { + LastValidators::set_items( + new.iter().cloned().zip(consensus::authorities()) + ); ValidatorStorageVec::set_items(new); consensus::internal::set_authorities(new); } @@ -114,7 +146,6 @@ pub mod internal { fn rotate_session() { // Increment current session index. storage::put(CURRENT_INDEX, &(current_index() + 1)); - // Enact era length change. if let Some(next_len) = storage::get::<u64>(NEXT_SESSION_LENGTH) { storage::put(SESSION_LENGTH, &next_len); @@ -122,10 +153,23 @@ fn rotate_session() { 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. - validators().iter().enumerate().for_each(|(i, v)| { + validators.iter().enumerate().for_each(|(i, v)| { let k = v.to_keyed_vec(NEXT_KEY_FOR); 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); } }); diff --git a/substrate/polkadot/runtime/src/runtime/staking.rs b/substrate/polkadot/runtime/src/runtime/staking.rs index 1b69698265ef3ae68ac6b82fb1356941ea099b62..cca0a92718420272b11db33de96d13889996bae9 100644 --- a/substrate/polkadot/runtime/src/runtime/staking.rs +++ b/substrate/polkadot/runtime/src/runtime/staking.rs @@ -22,18 +22,16 @@ use runtime_io::print; use codec::KeyedVec; use runtime_support::{storage, StorageVec}; 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. -pub type Balance = u64; - -/// The amount of bonding period left in an account. Measured in eras. -pub type Bondage = u64; +type Balance = u64; +type Bondage = u64; struct IntentionStorageVec {} impl StorageVec for IntentionStorageVec { type Item = AccountId; - const PREFIX: &'static[u8] = b"sta:wil:"; + const PREFIX: &'static [u8] = b"sta:wil:"; } const BONDING_DURATION: &[u8] = b"sta:loc"; @@ -81,11 +79,16 @@ pub fn balance(who: &AccountId) -> Balance { 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 { 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: // - n | n <= current_era(): inactive: free to be transferred. // - ~0: active: currently representing a validator. @@ -114,7 +117,7 @@ pub mod public { pub fn stake(transactor: &AccountId) { let mut intentions = IntentionStorageVec::items(); // 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()); IntentionStorageVec::set_items(&intentions); storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &u64::max_value()); @@ -133,6 +136,46 @@ pub mod public { IntentionStorageVec::set_items(&intentions); 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 { @@ -172,6 +215,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. /// /// NOTE: This always happens immediately before a session change to ensure that new validators @@ -406,4 +466,31 @@ mod tests { 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()), + ), + }) + }); + } } diff --git a/substrate/polkadot/runtime/src/runtime/system.rs b/substrate/polkadot/runtime/src/runtime/system.rs index aa6cfc45ce7120ac6389137eef15df5edf3932fd..c7972c949a131f9f98408c5a496e16d2a910593b 100644 --- a/substrate/polkadot/runtime/src/runtime/system.rs +++ b/substrate/polkadot/runtime/src/runtime/system.rs @@ -167,6 +167,9 @@ fn dispatch_function(function: &Function, transactor: &AccountId) { Function::StakingTransfer(dest, value) => { ::runtime::staking::public::transfer(transactor, &dest, value); } + Function::ReportMisbehavior(ref report) => { + ::runtime::staking::public::report_misbehavior(transactor, report) + } Function::SessionSetKey(session) => { ::runtime::session::public::set_key(transactor, &session); } diff --git a/substrate/polkadot/runtime/wasm/Cargo.lock b/substrate/polkadot/runtime/wasm/Cargo.lock index 11fe2d48f84d0b5e9a18b35c4795185260062dc7..d2bf5b3e7875e0e9f2a4c1af07458a6271f529d0 100644 --- a/substrate/polkadot/runtime/wasm/Cargo.lock +++ b/substrate/polkadot/runtime/wasm/Cargo.lock @@ -392,6 +392,7 @@ version = "0.1.0" dependencies = [ "polkadot-primitives 0.1.0", "substrate-codec 0.1.0", + "substrate-misbehavior-check 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-std 0.1.0", @@ -599,6 +600,15 @@ dependencies = [ "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]] name = "substrate-primitives" version = "0.1.0" diff --git a/substrate/polkadot/runtime/wasm/Cargo.toml b/substrate/polkadot/runtime/wasm/Cargo.toml index 2452ce9bd28e104126e598661ca6b1bb04e62dc1..0e30ce9ee68570069d299cbb1d3559eb7d92e074 100644 --- a/substrate/polkadot/runtime/wasm/Cargo.toml +++ b/substrate/polkadot/runtime/wasm/Cargo.toml @@ -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-support = { path = "../../../substrate/runtime-support", 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 } [features] @@ -22,6 +23,7 @@ std = [ "substrate-runtime-std/std", "substrate-runtime-support/std", "substrate-primitives/std", + "substrate-misbehavior-check/std", "polkadot-primitives/std", ] diff --git a/substrate/publish-wasm.sh b/substrate/publish-wasm.sh index 42f60619200cef45cdc4a7830b46ab47abc0636c..8424817c66d7260d7f789a1a3b35cb4994e5d49f 100755 --- a/substrate/publish-wasm.sh +++ b/substrate/publish-wasm.sh @@ -7,7 +7,7 @@ REPO_AUTH="${GH_TOKEN}:@${REPO}" SRCS=( "polkadot/runtime/wasm" "substrate/executor/wasm" "substrate/test-runtime/wasm" ) DST=".wasm-binaries" TARGET="wasm32-unknown-unknown" -UTCDATE=`date -u "+%Y%m%d.%H%M%S"` +UTCDATE=`date -u "+%Y%m%d.%H%M%S.0"` pushd . diff --git a/substrate/substrate/bft/src/generic/accumulator.rs b/substrate/substrate/bft/src/generic/accumulator.rs index a7b2076bba5e64f80ca83bb3bd5b08864adeb078..64c4f22254898ad0746e5fb3a8c0307509d556d1 100644 --- a/substrate/substrate/bft/src/generic/accumulator.rs +++ b/substrate/substrate/bft/src/generic/accumulator.rs @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // 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::hash_map::Entry; use std::hash::Hash; -use generic::{Message, LocalizedMessage}; +use generic::{Vote, LocalizedMessage, LocalizedProposal}; /// Justification for some state at a given round. #[derive(Debug, Clone, PartialEq, Eq)] @@ -122,6 +122,26 @@ struct VoteCounts { 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. /// /// This isn't tied to the "view" of a single authority. It @@ -132,13 +152,13 @@ pub struct Accumulator<Candidate, Digest, AuthorityId, Signature> where Candidate: Eq + Clone, Digest: Hash + Eq + Clone, - AuthorityId: Hash + Eq, + AuthorityId: Hash + Eq + Clone, Signature: Eq + Clone, { round_number: usize, threshold: usize, round_proposer: AuthorityId, - proposal: Option<Candidate>, + proposal: Option<Proposal<Candidate, Digest, Signature>>, prepares: HashMap<AuthorityId, (Digest, Signature)>, commits: HashMap<AuthorityId, (Digest, Signature)>, vote_counts: HashMap<Digest, VoteCounts>, @@ -150,7 +170,7 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A where Candidate: Eq + Clone, Digest: Hash + Eq + Clone, - AuthorityId: Hash + Eq, + AuthorityId: Hash + Eq + Clone, Signature: Eq + Clone, { /// Create a new state accumulator. @@ -179,7 +199,7 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A } pub fn proposal(&self) -> Option<&Candidate> { - self.proposal.as_ref() + self.proposal.as_ref().map(|p| &p.proposal) } /// Inspect the current consensus state. @@ -192,32 +212,61 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A pub fn import_message( &mut self, message: LocalizedMessage<Candidate, Digest, AuthorityId, Signature>, - ) - { + ) -> Result<(), Misbehavior<Digest, Signature>> { // message from different round. - if message.message.round_number() != self.round_number { - return; + if message.round_number() != self.round_number { + return Ok(()); } - let (sender, signature) = (message.sender, message.signature); - - match message.message { - Message::Propose(_, p) => self.import_proposal(p, sender), - Message::Prepare(_, d) => self.import_prepare(d, sender, signature), - Message::Commit(_, d) => self.import_commit(d, sender, signature), - Message::AdvanceRound(_) => self.import_advance_round(sender), + match message { + LocalizedMessage::Propose(proposal) => self.import_proposal(proposal), + LocalizedMessage::Vote(vote) => { + let (sender, signature) = (vote.sender, vote.signature); + match vote.vote { + Vote::Prepare(_, d) => self.import_prepare(d, sender, signature), + Vote::Commit(_, d) => self.import_commit(d, sender, signature), + Vote::AdvanceRound(_) => self.import_advance_round(sender), + } + } } } fn import_proposal( &mut self, - proposal: Candidate, - sender: AuthorityId, - ) { - if sender != self.round_proposer || self.proposal.is_some() { return } + proposal: LocalizedProposal<Candidate, Digest, AuthorityId, Signature>, + ) -> Result<(), Misbehavior<Digest, Signature>> { + let sender = proposal.sender; + + if sender != self.round_proposer { + 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.proposal = Some(proposal.clone()); - self.state = State::Proposed(proposal); + self.state = State::Proposed(proposal.proposal); + Ok(()) } fn import_prepare( @@ -225,21 +274,32 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A digest: Digest, sender: AuthorityId, signature: Signature, - ) { + ) -> Result<(), Misbehavior<Digest, Signature>> { // ignore any subsequent prepares by the same sender. - // TODO: if digest is different, that's misbehavior. - let threshold_prepared = if let Entry::Vacant(vacant) = self.prepares.entry(sender) { - vacant.insert((digest.clone(), signature)); - let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); - count.prepared += 1; - - if count.prepared >= self.threshold { - Some(digest) - } else { + let threshold_prepared = match self.prepares.entry(sender.clone()) { + Entry::Vacant(vacant) => { + vacant.insert((digest.clone(), signature)); + let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); + 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) + )); + } + None } - } else { - None }; // 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, })); } + + Ok(()) } fn import_commit( @@ -268,21 +330,32 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A digest: Digest, sender: AuthorityId, signature: Signature, - ) { + ) -> Result<(), Misbehavior<Digest, Signature>> { // ignore any subsequent commits by the same sender. - // TODO: if digest is different, that's misbehavior. - let threshold_committed = if let Entry::Vacant(vacant) = self.commits.entry(sender) { - vacant.insert((digest.clone(), signature)); - let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); - count.committed += 1; - - if count.committed >= self.threshold { - Some(digest) - } else { + let threshold_committed = match self.commits.entry(sender.clone()) { + Entry::Vacant(vacant) => { + vacant.insert((digest.clone(), signature)); + let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); + 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) + )); + } + None } - } else { - None }; // transition to concluded state always valid. @@ -302,15 +375,17 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A signatures: signatures, })); } + + Ok(()) } fn import_advance_round( &mut self, sender: AuthorityId, - ) { + ) -> Result<(), Misbehavior<Digest, Signature>> { 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 // yet. @@ -319,13 +394,16 @@ impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, A State::Prepared(j) => State::Advanced(Some(j)), State::Advanced(j) => State::Advanced(j), State::Begin | State::Proposed(_) => State::Advanced(None), - } + }; + + Ok(()) } } #[cfg(test)] mod tests { use super::*; + use generic::{LocalizedMessage, LocalizedProposal, LocalizedVote}; #[derive(Clone, PartialEq, Eq, Debug)] pub struct Candidate(usize); @@ -333,7 +411,7 @@ mod tests { #[derive(Hash, PartialEq, Eq, Clone, Debug)] pub struct Digest(usize); - #[derive(Hash, PartialEq, Eq, Debug)] + #[derive(Hash, PartialEq, Eq, Debug, Clone)] pub struct AuthorityId(usize); #[derive(PartialEq, Eq, Clone, Debug)] @@ -375,19 +453,27 @@ mod tests { let mut accumulator = Accumulator::<_, Digest, _, _>::new(1, 7, AuthorityId(8)); assert_eq!(accumulator.state(), &State::Begin); - accumulator.import_message(LocalizedMessage { + let res = accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal { sender: AuthorityId(5), - signature: Signature(999, 5), - message: Message::Propose(1, Candidate(999)), - }); + full_signature: Signature(999, 5), + digest_signature: Signature(999, 5), + proposal: Candidate(999), + digest: Digest(999), + round_number: 1, + })); + + assert!(res.is_err()); assert_eq!(accumulator.state(), &State::Begin); - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal { sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); + full_signature: Signature(999, 8), + digest_signature: Signature(999, 8), + proposal: Candidate(999), + digest: Digest(999), + round_number: 1, + })).unwrap(); assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); } @@ -397,29 +483,32 @@ mod tests { let mut accumulator = Accumulator::new(1, 7, AuthorityId(8)); assert_eq!(accumulator.state(), &State::Begin); - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal { sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); + full_signature: Signature(999, 8), + digest_signature: Signature(999, 8), + round_number: 1, + proposal: Candidate(999), + digest: Digest(999), + })).unwrap(); assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); for i in 0..6 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(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))); } - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(7), signature: Signature(999, 7), - message: Message::Prepare(1, Digest(999)), - }); + vote: Vote::Prepare(1, Digest(999)), + }.into()).unwrap(); match accumulator.state() { &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)); assert_eq!(accumulator.state(), &State::Begin); - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal { sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); + full_signature: Signature(999, 8), + digest_signature: Signature(999, 8), + round_number: 1, + proposal: Candidate(999), + digest: Digest(999), + })).unwrap(); assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); for i in 0..6 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(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))); } - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(7), signature: Signature(999, 7), - message: Message::Prepare(1, Digest(999)), - }); + vote: Vote::Prepare(1, Digest(999)), + }.into()).unwrap(); match accumulator.state() { &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), @@ -462,11 +554,11 @@ mod tests { } for i in 0..6 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(i), signature: Signature(999, i), - message: Message::Commit(1, Digest(999)), - }); + vote: Vote::Commit(1, Digest(999)), + }.into()).unwrap(); match accumulator.state() { &State::Prepared(_) => {}, @@ -474,11 +566,11 @@ mod tests { } } - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(7), signature: Signature(999, 7), - message: Message::Commit(1, Digest(999)), - }); + vote: Vote::Commit(1, Digest(999)), + }.into()).unwrap(); match accumulator.state() { &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)); assert_eq!(accumulator.state(), &State::Begin); - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedMessage::Propose(LocalizedProposal { sender: AuthorityId(8), - signature: Signature(999, 8), - message: Message::Propose(1, Candidate(999)), - }); + full_signature: Signature(999, 8), + digest_signature: Signature(999, 8), + round_number: 1, + proposal: Candidate(999), + digest: Digest(999), + })).unwrap(); assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); for i in 0..7 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(i), signature: Signature(999, i), - message: Message::Prepare(1, Digest(999)), - }); + vote: Vote::Prepare(1, Digest(999)), + }.into()).unwrap(); } match accumulator.state() { @@ -513,11 +608,11 @@ mod tests { } for i in 0..6 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(i), signature: Signature(999, i), - message: Message::AdvanceRound(1), - }); + vote: Vote::AdvanceRound(1), + }.into()).unwrap(); match accumulator.state() { &State::Prepared(_) => {}, @@ -525,11 +620,11 @@ mod tests { } } - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(7), signature: Signature(999, 7), - message: Message::AdvanceRound(1), - }); + vote: Vote::AdvanceRound(1), + }.into()).unwrap(); match accumulator.state() { &State::Advanced(Some(_)) => {}, @@ -543,11 +638,11 @@ mod tests { assert_eq!(accumulator.state(), &State::Begin); for i in 0..7 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(i), signature: Signature(999, i), - message: Message::Prepare(1, Digest(999)), - }); + vote: Vote::Prepare(1, Digest(999)), + }.into()).unwrap(); } match accumulator.state() { @@ -556,11 +651,11 @@ mod tests { } for i in 0..7 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(i), signature: Signature(999, i), - message: Message::Commit(1, Digest(999)), - }); + vote: Vote::Commit(1, Digest(999)), + }.into()).unwrap(); } match accumulator.state() { @@ -575,11 +670,11 @@ mod tests { assert_eq!(accumulator.state(), &State::Begin); for i in 0..7 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(i), signature: Signature(1, i), - message: Message::AdvanceRound(1), - }); + vote: Vote::AdvanceRound(1), + }.into()).unwrap(); } match accumulator.state() { @@ -594,11 +689,11 @@ mod tests { assert_eq!(accumulator.state(), &State::Begin); for i in 0..7 { - accumulator.import_message(LocalizedMessage { + accumulator.import_message(LocalizedVote { sender: AuthorityId(i), signature: Signature(999, i), - message: Message::Commit(1, Digest(999)), - }); + vote: Vote::Commit(1, Digest(999)), + }.into()).unwrap(); } match accumulator.state() { @@ -606,4 +701,76 @@ mod tests { 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()); + } } diff --git a/substrate/substrate/bft/src/generic/mod.rs b/substrate/substrate/bft/src/generic/mod.rs index 4ebe508e36b0b1528a8858f2a008b170c64c1a82..aa0fe2c549a3fec73750e002a28bd5fe2686e875 100644 --- a/substrate/substrate/bft/src/generic/mod.rs +++ b/substrate/substrate/bft/src/generic/mod.rs @@ -18,6 +18,7 @@ //! Very general implementation. use std::collections::{HashMap, VecDeque}; +use std::collections::hash_map; use std::fmt::Debug; use std::hash::Hash; @@ -25,19 +26,16 @@ use futures::{future, Future, Stream, Sink, Poll, Async, AsyncSink}; use self::accumulator::State; -pub use self::accumulator::{Accumulator, Justification, PrepareJustification, UncheckedJustification}; +pub use self::accumulator::{Accumulator, Justification, PrepareJustification, UncheckedJustification, Misbehavior}; mod accumulator; #[cfg(test)] mod tests; -/// Messages over the proposal. -/// Each message carries an associated round number. +/// Votes during a round. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Message<C, D> { - /// Send a full proposal. - Propose(usize, C), +pub enum Vote<D> { /// Prepare to vote for proposal with digest D. Prepare(usize, D), /// Commit to proposal with digest D.. @@ -46,29 +44,94 @@ pub enum Message<C, D> { AdvanceRound(usize), } -impl<C, D> Message<C, D> { +impl<D> Vote<D> { /// Extract the round number. pub fn round_number(&self) -> usize { match *self { - Message::Propose(round, _) => round, - Message::Prepare(round, _) => round, - Message::Commit(round, _) => round, - Message::AdvanceRound(round) => round, + Vote::Prepare(round, _) => round, + Vote::Commit(round, _) => round, + Vote::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)] +pub struct LocalizedProposal<C, D, V, S> { + /// The round number. + 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 LocalizedMessage<C, D, V, S> { - /// The message received. - pub message: Message<C, D>, +pub struct LocalizedVote<D, V, S> { + /// The message sent. + pub vote: Vote<D>, /// The sender of the message pub sender: V, /// The signature of the message. 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. /// /// 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; /// 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>) -> 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>, future_accumulator: Accumulator<C::Candidate, C::Digest, C::AuthorityId, C::Signature>, local_id: C::AuthorityId, + misbehavior: HashMap<C::AuthorityId, Misbehavior<C::Digest, C::Signature>>, } impl<C: Context> Strategy<C> { @@ -289,6 +355,7 @@ impl<C: Context> Strategy<C> { notable_candidates: HashMap::new(), round_timeout: timeout.fuse(), local_id: context.local_id(), + misbehavior: HashMap::new(), } } @@ -296,12 +363,19 @@ impl<C: Context> Strategy<C> { &mut self, 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() { - self.current_accumulator.import_message(msg); + let sender = msg.sender().clone(); + 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() { - 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 { - let message = Message::Prepare( + let message = Vote::Prepare( self.current_accumulator.round_number(), digest - ); + ).into(); self.import_and_send_message(message, context, sending); self.local_state = LocalState::Prepared; @@ -559,10 +633,10 @@ impl<C: Context> Strategy<C> { } if let Some(digest) = commit_for { - let message = Message::Commit( + let message = Vote::Commit( self.current_accumulator.round_number(), digest - ); + ).into(); self.import_and_send_message(message, context, sending); self.local_state = LocalState::Committed; @@ -588,9 +662,9 @@ impl<C: Context> Strategy<C> { } if attempt_advance { - let message = Message::AdvanceRound( + let message = Vote::AdvanceRound( self.current_accumulator.round_number(), - ); + ).into(); self.import_and_send_message(message, context, sending); 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. /// /// `nodes` is the number of nodes in the system. diff --git a/substrate/substrate/bft/src/generic/tests.rs b/substrate/substrate/bft/src/generic/tests.rs index c64907cbeef770208cfc6433022a28968964c31a..349bec693f95da0b59fde534e03261140a471ab5 100644 --- a/substrate/substrate/bft/src/generic/tests.rs +++ b/substrate/substrate/bft/src/generic/tests.rs @@ -191,10 +191,21 @@ impl Context for TestContext { -> LocalizedMessage<Candidate, Digest, AuthorityId, Signature> { let signature = Signature(message.clone(), self.local_id.clone()); - LocalizedMessage { - message, - signature, - sender: self.local_id.clone() + + match message { + Message::Propose(r, proposal) => LocalizedMessage::Propose(LocalizedProposal { + 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, digest: locked_digest.clone(), 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() }.check(7, |_, _, s| Some(s.1.clone())).unwrap(); diff --git a/substrate/substrate/bft/src/lib.rs b/substrate/substrate/bft/src/lib.rs index 977245e26f4eee63b8e6a1fe2fa75aed65876339..f9d870799d38cda2468d1f6e0e0d86351d2b322a 100644 --- a/substrate/substrate/bft/src/lib.rs +++ b/substrate/substrate/bft/src/lib.rs @@ -100,6 +100,9 @@ pub type Committed = generic::Committed<Block, HeaderHash, LocalizedSignature>; /// Communication between BFT participants. 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. pub trait ProposerFactory { /// The proposer type this creates. @@ -129,6 +132,8 @@ pub trait Proposer { /// Evaluate proposal. True means valid. // TODO: change this to a future. fn evaluate(&self, proposal: &Block) -> Self::Evaluate; + /// Import witnessed misbehavior. + fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, Misbehavior)>); } /// 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 { cancel: Arc<AtomicBool>, 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))?; - // TODO: check key is one of the authorities. let n = authorities.len(); 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. pub fn sign_message(message: Message, key: &ed25519::Pair, parent_hash: HeaderHash) -> LocalizedMessage { - let action = match message.clone() { - ::generic::Message::Propose(r, p) => PrimitiveAction::Propose(r as u32, p), - ::generic::Message::Prepare(r, h) => PrimitiveAction::Prepare(r as u32, h), - ::generic::Message::Commit(r, h) => PrimitiveAction::Commit(r as u32, h), - ::generic::Message::AdvanceRound(r) => PrimitiveAction::AdvanceRound(r as u32), - }; + let signer = key.public(); - let primitive = PrimitiveMessage { - parent: parent_hash, - action, - }; + let sign_action = |action| { + let primitive = PrimitiveMessage { + parent: parent_hash, + action, + }; - let to_sign = Slicable::encode(&primitive); - let signature = LocalizedSignature { - signer: key.public(), - signature: key.sign(&to_sign), + let to_sign = Slicable::encode(&primitive); + LocalizedSignature { + signer: signer.clone(), + signature: key.sign(&to_sign), + } }; - LocalizedMessage { - message, - signature, - sender: key.public().0 + match message { + ::generic::Message::Propose(r, proposal) => { + 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()); + + ::generic::LocalizedMessage::Propose(::generic::LocalizedProposal { + round_number: r, + proposal, + 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), + }; + + ::generic::LocalizedMessage::Vote(::generic::LocalizedVote { + vote: vote, + sender: signer.0, + signature: sign_action(action), + }) + } } } @@ -506,6 +539,8 @@ mod tests { fn evaluate(&self, proposal: &Block) -> Result<bool, Error> { Ok(proposal.header.number == self.0) } + + fn import_misbehavior(&self, _misbehavior: Vec<(AuthorityId, Misbehavior)>) {} } 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] fn future_gets_preempted() { let client = FakeClient { @@ -591,7 +633,7 @@ mod tests { digest: hash, round_number: 1, 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(), }; @@ -601,7 +643,7 @@ mod tests { digest: hash, round_number: 0, // wrong round number (vs. the signatures) 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(), }; @@ -612,7 +654,7 @@ mod tests { digest: hash, round_number: 1, 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(), }; @@ -623,7 +665,7 @@ mod tests { digest: [0xfe; 32].into(), round_number: 1, 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(), }; diff --git a/substrate/substrate/client/src/client.rs b/substrate/substrate/client/src/client.rs index 3ccc9debff5b438a91fe00f272ee22f634a63ad3..402a67ef2a7767ed78b0f8ec61583076bb116bed 100644 --- a/substrate/substrate/client/src/client.rs +++ b/substrate/substrate/client/src/client.rs @@ -393,11 +393,16 @@ mod tests { bft::UncheckedJustification { digest: hash, signatures: authorities.iter().map(|key| { - bft::sign_message( - bft::generic::Message::Commit(1, hash), + let msg = bft::sign_message( + bft::generic::Vote::Commit(1, hash).into(), key, header.parent_hash - ).signature + ); + + match msg { + bft::generic::LocalizedMessage::Vote(vote) => vote.signature, + _ => panic!("signing vote leads to signed vote"), + } }).collect(), round_number: 1, } diff --git a/substrate/substrate/keyring/src/lib.rs b/substrate/substrate/keyring/src/lib.rs index 572e6775e6fe4dee70c134387eec66b42f425c3f..9749ef13449cf62cd2f5cd23a61a9cb6d0a605c0 100644 --- a/substrate/substrate/keyring/src/lib.rs +++ b/substrate/substrate/keyring/src/lib.rs @@ -18,7 +18,7 @@ #[macro_use] extern crate hex_literal; #[macro_use] extern crate lazy_static; -extern crate ed25519; +pub extern crate ed25519; use std::collections::HashMap; use std::ops::Deref; diff --git a/substrate/substrate/misbehavior-check/Cargo.toml b/substrate/substrate/misbehavior-check/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..58d27f45c7e206862e0a1d78a4c391db9a69da12 --- /dev/null +++ b/substrate/substrate/misbehavior-check/Cargo.toml @@ -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"] diff --git a/substrate/substrate/misbehavior-check/src/lib.rs b/substrate/substrate/misbehavior-check/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..71236ef457ee6f3f6306bc2dddc01c763999e359 --- /dev/null +++ b/substrate/substrate/misbehavior-check/src/lib.rs @@ -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); + } +} diff --git a/substrate/substrate/network/src/test/mod.rs b/substrate/substrate/network/src/test/mod.rs index 4cddc1bf8af8e609b2ca79c364bc7694949a3437..8aac22f16ee7de52fbad395d084d7be84c9f696c 100644 --- a/substrate/substrate/network/src/test/mod.rs +++ b/substrate/substrate/network/src/test/mod.rs @@ -167,11 +167,16 @@ impl Peer { bft::UncheckedJustification { digest: hash, signatures: authorities.iter().map(|key| { - bft::sign_message( - bft::generic::Message::Commit(1, hash), + let msg = bft::sign_message( + bft::generic::Vote::Commit(1, hash).into(), key, header.parent_hash - ).signature + ); + + match msg { + bft::generic::LocalizedMessage::Vote(vote) => vote.signature, + _ => panic!("signing vote leads to signed vote"), + } }).collect(), round_number: 1, } diff --git a/substrate/substrate/primitives/src/bft.rs b/substrate/substrate/primitives/src/bft.rs index 49e14075285bf4c8b6069553a6aeed1cb3bb51f6..717ff8a4242ad35995a3189e37a196e20160348d 100644 --- a/substrate/substrate/primitives/src/bft.rs +++ b/substrate/substrate/primitives/src/bft.rs @@ -19,15 +19,17 @@ use block::{Block, HeaderHash}; use codec::{Slicable, Input}; use rstd::vec::Vec; +use ::{AuthorityId, Signature}; #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Debug))] #[repr(u8)] enum ActionKind { Propose = 1, - Prepare = 2, - Commit = 3, - AdvanceRound = 4, + ProposeHeader = 2, + Prepare = 3, + Commit = 4, + AdvanceRound = 5, } /// Actions which can be taken during the BFT process. @@ -36,6 +38,9 @@ enum ActionKind { pub enum Action { /// Proposal of a block candidate. 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. Prepare(u32, HeaderHash), /// Vote to commit to a candidate. @@ -53,6 +58,11 @@ impl Slicable for Action { round.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) => { v.push(ActionKind::Prepare as u8); round.using_encoded(|s| v.extend(s)); @@ -78,6 +88,11 @@ impl Slicable for Action { let (round, block) = try_opt!(Slicable::decode(value)); 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 => { 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); + } +} diff --git a/substrate/substrate/runtime-std/with_std.rs b/substrate/substrate/runtime-std/with_std.rs index 581cc24d91cb9ac75ec1f5543cf87be1a8f3807a..2cfd266f48271aaa469fa38d14d6ca9fa1a184d2 100644 --- a/substrate/substrate/runtime-std/with_std.rs +++ b/substrate/substrate/runtime-std/with_std.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see <http://www.gnu.org/licenses/>. +pub use std::borrow; pub use std::boxed; pub use std::cell; pub use std::cmp; diff --git a/substrate/substrate/runtime-std/without_std.rs b/substrate/substrate/runtime-std/without_std.rs index 75b63407d1078181750a26b2331a1f686aa7a250..5fc2d7526b3ff01bde8b9c96ebc1293d81c56f9e 100644 --- a/substrate/substrate/runtime-std/without_std.rs +++ b/substrate/substrate/runtime-std/without_std.rs @@ -28,6 +28,7 @@ pub use alloc::vec; pub mod collections { pub use alloc::btree_map; } +pub use core::borrow; pub use core::cell; pub use core::cmp; pub use core::intrinsics; diff --git a/substrate/substrate/runtime-support/src/storage.rs b/substrate/substrate/runtime-support/src/storage.rs index 0960ecb0ba2843b40ade82717aa752664744e49f..54237200fb911f2a9ec8728715290cab1d507815 100644 --- a/substrate/substrate/runtime-support/src/storage.rs +++ b/substrate/substrate/runtime-support/src/storage.rs @@ -17,6 +17,7 @@ //! Stuff to do with the runtime's storage. use rstd::prelude::*; +use rstd::borrow::Borrow; use runtime_io::{self, twox_128}; use codec::{Slicable, KeyedVec, Input}; @@ -131,9 +132,26 @@ pub trait StorageVec { } /// Set the current set of items. - fn set_items(items: &[Self::Item]) { - Self::set_count(items.len() as u32); - items.iter().enumerate().for_each(|(v, ref i)| Self::set_item(v as u32, i)); + fn set_items<I, T>(items: I) + where + 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) { @@ -163,6 +181,7 @@ pub trait StorageVec { } pub mod unhashed { + use rstd::borrow::Borrow; 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. @@ -258,9 +277,19 @@ pub mod unhashed { } /// Set the current set of items. - fn set_items(items: &[Self::Item]) { - Self::set_count(items.len() as u32); - items.iter().enumerate().for_each(|(v, ref i)| Self::set_item(v as u32, i)); + fn set_items<I, T>(items: I) + where + 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) { @@ -293,8 +322,8 @@ pub mod unhashed { #[cfg(test)] mod tests { use super::*; - use primitives::hexdisplay::HexDisplay; - use runtime_io::{storage, twox_128, TestExternalities, with_externalities}; + use primitives::hexdisplay; + use runtime_io::{twox_128, TestExternalities, with_externalities}; #[test] fn integers_can_be_stored() { @@ -337,7 +366,6 @@ mod tests { with_externalities(&mut t, || { runtime_io::set_storage(&twox_128(b":test"), b"\x0b\0\0\0Hello world"); 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(); assert_eq!(x, y); @@ -353,9 +381,7 @@ mod tests { put(b":test", &x); }); - println!("Ext is {:?}", t); with_externalities(&mut t, || { - println!("Hex: {}", HexDisplay::from(&storage(&twox_128(b":test")).unwrap())); let y: Vec<u8> = get(b":test").unwrap(); assert_eq!(x, y); });