diff --git a/Cargo.lock b/Cargo.lock index 393dd9a73b77a..47b5706400551 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1876,7 +1876,7 @@ dependencies = [ "polkadot-runtime 0.1.0", "polkadot-statement-table 0.1.0", "polkadot-transaction-pool 0.1.0", - "rhododendron 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rhododendron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-bft 0.1.0", "substrate-client 0.1.0", "substrate-codec 0.1.0", @@ -1906,7 +1906,7 @@ dependencies = [ "polkadot-api 0.1.0", "polkadot-consensus 0.1.0", "polkadot-primitives 0.1.0", - "rhododendron 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rhododendron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-bft 0.1.0", "substrate-codec 0.1.0", "substrate-network 0.1.0", @@ -2244,7 +2244,7 @@ dependencies = [ [[package]] name = "rhododendron" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2601,7 +2601,7 @@ dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rhododendron 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rhododendron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-executor 0.1.0", "substrate-keyring 0.1.0", @@ -2767,7 +2767,7 @@ dependencies = [ name = "substrate-misbehavior-check" version = "0.1.0" dependencies = [ - "rhododendron 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rhododendron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-bft 0.1.0", "substrate-codec 0.1.0", "substrate-keyring 0.1.0", @@ -3221,7 +3221,7 @@ dependencies = [ name = "substrate-test-client" version = "0.1.0" dependencies = [ - "rhododendron 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rhododendron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-bft 0.1.0", "substrate-client 0.1.0", "substrate-codec 0.1.0", @@ -4094,7 +4094,7 @@ dependencies = [ "checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" "checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" -"checksum rhododendron 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9e38401cc1b63e71ec9119115c7e1354fcf54c8006ad59a22409dd8bd93737b2" +"checksum rhododendron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "252bd5559d8b9209fee949a42f0977c982062d9c92c953cf2e286eb49c2642d4" "checksum ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f7d28b30a72c01b458428e0ae988d4149c20d902346902be881e3edc4bb325c" "checksum rlp 0.2.1 (git+https://github.com/paritytech/parity.git)" = "" "checksum rlp 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "89db7f8dfdd5eb7ab3ac3ece7a07fd273a680b4b224cb231181280e8996f9f0b" diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 3f5165ef2998b..ebc7c777da0c6 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -121,6 +121,7 @@ impl Convert for SessionKeyConversion { } impl session::Trait for Concrete { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = SessionKeyConversion; type OnSessionChange = Staking; } diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index 2a8d69f517a6c..758ac481c9151 100644 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index abfa92a5b9e9f..e04914bba8703 100755 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/polkadot/api/src/full.rs b/polkadot/api/src/full.rs index caf9a8907281b..79d77612c72bb 100644 --- a/polkadot/api/src/full.rs +++ b/polkadot/api/src/full.rs @@ -25,8 +25,11 @@ use state_machine; use runtime::Address; use runtime_primitives::traits::AuxLookup; -use primitives::{AccountId, Block, Header, BlockId, Hash, Index, SessionKey, Timestamp, UncheckedExtrinsic}; -use primitives::parachain::{CandidateReceipt, DutyRoster, Id as ParaId}; +use primitives::{ + AccountId, Block, Header, BlockId, Hash, Index, InherentData, + SessionKey, Timestamp, UncheckedExtrinsic, +}; +use primitives::parachain::{DutyRoster, Id as ParaId}; use {BlockBuilder, PolkadotApi, LocalPolkadotApi, ErrorKind, Error, Result}; @@ -132,20 +135,20 @@ impl> PolkadotApi for Client) -> Result { + fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result { let mut block_builder = self.new_block_at(at)?; - for inherent in self.inherent_extrinsics(at, timestamp, new_heads)? { + for inherent in self.inherent_extrinsics(at, inherent_data)? { block_builder.push(inherent)?; } Ok(block_builder) } - fn inherent_extrinsics(&self, at: &BlockId, timestamp: Timestamp, new_heads: Vec) -> Result> { + fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result> { use codec::{Encode, Decode}; with_runtime!(self, at, || { - let extrinsics = ::runtime::inherent_extrinsics(timestamp, new_heads); + let extrinsics = ::runtime::inherent_extrinsics(inherent_data); extrinsics.into_iter() .map(|x| x.encode()) // get encoded representation .map(|x| Decode::decode(&mut &x[..])) // get byte-vec equivalent to extrinsic @@ -216,7 +219,11 @@ mod tests { let client = client(); let id = BlockId::number(0); - let block_builder = client.build_block(&id, 1_000_000, Vec::new()).unwrap(); + let block_builder = client.build_block(&id, InherentData { + timestamp: 1_000_000, + parachain_heads: Vec::new(), + offline_indices: Vec::new(), + }).unwrap(); let block = block_builder.bake().unwrap(); assert_eq!(block.header.number, 1); @@ -228,7 +235,11 @@ mod tests { let client = client(); let id = BlockId::number(0); - let inherent = client.inherent_extrinsics(&id, 1_000_000, Vec::new()).unwrap(); + let inherent = client.inherent_extrinsics(&id, InherentData { + timestamp: 1_000_000, + parachain_heads: Vec::new(), + offline_indices: Vec::new(), + }).unwrap(); let mut block_builder = client.new_block_at(&id).unwrap(); for extrinsic in inherent { diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index 27bea18a0d0a3..8c1e4b1de832e 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -38,10 +38,12 @@ extern crate substrate_keyring as keyring; pub mod full; pub mod light; -use primitives::{AccountId, Block, BlockId, Hash, Index, SessionKey, Timestamp, - UncheckedExtrinsic}; +use primitives::{ + AccountId, Block, BlockId, Hash, Index, SessionKey, Timestamp, + UncheckedExtrinsic, InherentData, +}; use runtime::Address; -use primitives::parachain::{CandidateReceipt, DutyRoster, Id as ParaId}; +use primitives::parachain::{DutyRoster, Id as ParaId}; error_chain! { errors { @@ -128,11 +130,11 @@ pub trait PolkadotApi { fn evaluate_block(&self, at: &BlockId, block: Block) -> Result; /// Build a block on top of the given, with inherent extrinsics pre-pushed. - fn build_block(&self, at: &BlockId, timestamp: Timestamp, new_heads: Vec) -> Result; + fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result; /// Attempt to produce the (encoded) inherent extrinsics for a block being built upon the given. /// This may vary by runtime and will fail if a runtime doesn't follow the same API. - fn inherent_extrinsics(&self, at: &BlockId, timestamp: Timestamp, new_heads: Vec) -> Result>; + fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result>; } /// Mark for all Polkadot API implementations, that are making use of state data, stored locally. diff --git a/polkadot/api/src/light.rs b/polkadot/api/src/light.rs index 7e2015c0b701e..a281cde6e3a58 100644 --- a/polkadot/api/src/light.rs +++ b/polkadot/api/src/light.rs @@ -20,9 +20,12 @@ use std::sync::Arc; use client::backend::{Backend, RemoteBackend}; use client::{Client, CallExecutor}; use codec::Decode; -use primitives::{AccountId, Block, BlockId, Hash, Index, SessionKey, Timestamp, UncheckedExtrinsic}; +use primitives::{ + AccountId, Block, BlockId, Hash, Index, InherentData, + SessionKey, Timestamp, UncheckedExtrinsic, +}; use runtime::Address; -use primitives::parachain::{CandidateReceipt, DutyRoster, Id as ParaId}; +use primitives::parachain::{DutyRoster, Id as ParaId}; use {PolkadotApi, BlockBuilder, RemotePolkadotApi, Result, ErrorKind}; /// Light block builder. TODO: make this work (efficiently) @@ -92,11 +95,11 @@ impl, E: CallExecutor> PolkadotApi for RemotePolkadotAp Err(ErrorKind::UnknownRuntime.into()) } - fn build_block(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec) -> Result { + fn build_block(&self, _at: &BlockId, _inherent: InherentData) -> Result { Err(ErrorKind::UnknownRuntime.into()) } - fn inherent_extrinsics(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec) -> Result>> { + fn inherent_extrinsics(&self, _at: &BlockId, _inherent: InherentData) -> Result>> { Err(ErrorKind::UnknownRuntime.into()) } } diff --git a/polkadot/consensus/Cargo.toml b/polkadot/consensus/Cargo.toml index 68ad8265518e4..e50551c518cb3 100644 --- a/polkadot/consensus/Cargo.toml +++ b/polkadot/consensus/Cargo.toml @@ -11,7 +11,7 @@ ed25519 = { path = "../../substrate/ed25519" } error-chain = "0.12" log = "0.3" exit-future = "0.1" -rhododendron = "0.2" +rhododendron = "0.3" polkadot-api = { path = "../api" } polkadot-parachain = { path = "../parachain" } polkadot-primitives = { path = "../primitives" } diff --git a/polkadot/consensus/src/dynamic_inclusion.rs b/polkadot/consensus/src/dynamic_inclusion.rs index bec2bd0fa80e3..232acea238872 100644 --- a/polkadot/consensus/src/dynamic_inclusion.rs +++ b/polkadot/consensus/src/dynamic_inclusion.rs @@ -74,6 +74,9 @@ impl DynamicInclusion { Some(now + until) } } + + /// Get the start instant. + pub fn started_at(&self) -> Instant { self.start } } #[cfg(test)] diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs index 4bef0fceafd93..2d9639a8cdb64 100644 --- a/polkadot/consensus/src/lib.rs +++ b/polkadot/consensus/src/lib.rs @@ -67,7 +67,7 @@ use std::time::{Duration, Instant}; use codec::{Decode, Encode}; use polkadot_api::PolkadotApi; -use polkadot_primitives::{Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey}; +use polkadot_primitives::{AccountId, Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey}; use polkadot_primitives::parachain::{Id as ParaId, Chain, DutyRoster, BlockData, Extrinsic as ParachainExtrinsic, CandidateReceipt, CandidateSignature}; use primitives::AuthorityId; use transaction_pool::TransactionPool; @@ -78,20 +78,26 @@ use futures::prelude::*; use futures::future; use collation::CollationFetch; use dynamic_inclusion::DynamicInclusion; +use parking_lot::RwLock; pub use self::collation::{validate_collation, Collators}; pub use self::error::{ErrorKind, Error}; +pub use self::offline_tracker::OfflineTracker; pub use self::shared_table::{SharedTable, StatementProducer, ProducedStatements, Statement, SignedStatement, GenericStatement}; pub use service::Service; mod dynamic_inclusion; mod evaluation; mod error; +mod offline_tracker; mod service; mod shared_table; pub mod collation; +/// Shared offline validator tracker. +pub type SharedOfflineTracker = Arc>; + // block size limit. const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024; @@ -236,6 +242,8 @@ pub struct ProposerFactory { pub handle: TaskExecutor, /// The duration after which parachain-empty blocks will be allowed. pub parachain_empty_duration: Duration, + /// Offline-tracker. + pub offline: SharedOfflineTracker, } impl bft::Environment for ProposerFactory @@ -251,10 +259,11 @@ impl bft::Environment for ProposerFactory type Output = N::Output; type Error = Error; - fn init(&self, + fn init( + &self, parent_header: &Header, authorities: &[AuthorityId], - sign_with: Arc + sign_with: Arc, ) -> Result<(Self::Proposer, Self::Input, Self::Output), Error> { use runtime_primitives::traits::{Hash as HashT, BlakeTwo256}; @@ -265,6 +274,9 @@ impl bft::Environment for ProposerFactory let random_seed = self.client.random_seed(&id)?; let random_seed = BlakeTwo256::hash(&*random_seed); + let validators = self.client.validators(&id)?; + self.offline.write().note_new_block(&validators[..]); + let (group_info, local_duty) = make_group_info( duty_roster, authorities, @@ -316,6 +328,8 @@ impl bft::Environment for ProposerFactory random_seed, table, transaction_pool: self.transaction_pool.clone(), + offline: self.offline.clone(), + validators, _drop_signal: drop_signal, }; @@ -370,9 +384,22 @@ pub struct Proposer { random_seed: Hash, table: Arc, transaction_pool: Arc>, + offline: SharedOfflineTracker, + validators: Vec, _drop_signal: exit_future::Signal, } +impl Proposer { + fn primary_index(&self, round_number: usize, len: usize) -> usize { + use primitives::uint::U256; + + let big_len = U256::from(len); + let offset = U256::from_big_endian(&self.random_seed.0) % big_len; + let offset = offset.low_u64() as usize + round_number; + offset % len + } +} + impl bft::Proposer for Proposer where C: PolkadotApi + Send + Sync, @@ -408,6 +435,8 @@ impl bft::Proposer for Proposer client: self.client.clone(), transaction_pool: self.transaction_pool.clone(), table: self.table.clone(), + offline: self.offline.clone(), + validators: self.validators.clone(), timing, }) } @@ -482,6 +511,13 @@ impl bft::Proposer for Proposer includability_tracker.join(temporary_delay) }; + // refuse to vote if this block says a validator is offline that we + // think isn't. + let offline = proposal.noted_offline(); + if !self.offline.read().check_consistency(&self.validators[..], offline) { + return Box::new(futures::empty()); + } + // evaluate whether the block is actually valid. // TODO: is it better to delay this until the delays are finished? let evaluated = self.client @@ -503,13 +539,8 @@ impl bft::Proposer for Proposer } fn round_proposer(&self, round_number: usize, authorities: &[AuthorityId]) -> AuthorityId { - use primitives::uint::U256; - - let len: U256 = authorities.len().into(); - let offset = U256::from_big_endian(&self.random_seed.0) % len; - let offset = offset.low_u64() as usize + round_number; - - let proposer = authorities[offset % authorities.len()].clone(); + let offset = self.primary_index(round_number, authorities.len()); + let proposer = authorities[offset].clone(); trace!(target: "bft", "proposer for round {} is {}", round_number, proposer); proposer @@ -578,6 +609,34 @@ impl bft::Proposer for Proposer .expect("locally signed extrinsic is valid; qed"); } } + + fn on_round_end(&self, round_number: usize, was_proposed: bool) { + let primary_validator = self.validators[ + self.primary_index(round_number, self.validators.len()) + ]; + + // alter the message based on whether we think the empty proposer was forced to skip the round. + // this is determined by checking if our local validator would have been forced to skip the round. + let consider_online = was_proposed || { + let forced_delay = self.dynamic_inclusion.acceptable_in(Instant::now(), self.table.includable_count()); + match forced_delay { + None => info!( + "Potential Offline Validator: {:?} failed to propose during assigned slot: {}", + primary_validator, + round_number, + ), + Some(_) => info!( + "Potential Offline Validator {:?} potentially forced to skip assigned slot: {}", + primary_validator, + round_number, + ), + } + + forced_delay.is_some() + }; + + self.offline.write().note_round_end(primary_validator, consider_online); + } } fn current_timestamp() -> Timestamp { @@ -634,16 +693,42 @@ pub struct CreateProposal { transaction_pool: Arc>, table: Arc, timing: ProposalTiming, + validators: Vec, + offline: SharedOfflineTracker, } impl CreateProposal where C: PolkadotApi { fn propose_with(&self, candidates: Vec) -> Result { use polkadot_api::BlockBuilder; use runtime_primitives::traits::{Hash as HashT, BlakeTwo256}; + use polkadot_primitives::InherentData; + + const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60); // TODO: handle case when current timestamp behind that in state. let timestamp = current_timestamp(); - let mut block_builder = self.client.build_block(&self.parent_id, timestamp, candidates)?; + + let elapsed_since_start = self.timing.dynamic_inclusion.started_at().elapsed(); + let offline_indices = if elapsed_since_start > MAX_VOTE_OFFLINE_SECONDS { + Vec::new() + } else { + self.offline.read().reports(&self.validators[..]) + }; + + if !offline_indices.is_empty() { + info!( + "Submitting offline validators {:?} for slash-vote", + offline_indices.iter().map(|&i| self.validators[i as usize]).collect::>(), + ) + } + + let inherent_data = InherentData { + timestamp, + parachain_heads: candidates, + offline_indices, + }; + + let mut block_builder = self.client.build_block(&self.parent_id, inherent_data)?; { let mut unqueue_invalid = Vec::new(); diff --git a/polkadot/consensus/src/offline_tracker.rs b/polkadot/consensus/src/offline_tracker.rs new file mode 100644 index 0000000000000..efb317ea5c913 --- /dev/null +++ b/polkadot/consensus/src/offline_tracker.rs @@ -0,0 +1,137 @@ +// Copyright 2018 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 . + +//! Tracks offline validators. + +use polkadot_primitives::AccountId; + +use std::collections::HashMap; +use std::time::{Instant, Duration}; + +// time before we report a validator. +const REPORT_TIME: Duration = Duration::from_secs(60 * 5); + +struct Observed { + last_round_end: Instant, + offline_since: Instant, +} + +impl Observed { + fn new() -> Observed { + let now = Instant::now(); + Observed { + last_round_end: now, + offline_since: now, + } + } + + fn note_round_end(&mut self, was_online: bool) { + let now = Instant::now(); + + self.last_round_end = now; + if was_online { + self.offline_since = now; + } + } + + fn is_active(&self) -> bool { + // can happen if clocks are not monotonic + if self.offline_since > self.last_round_end { return true } + self.last_round_end.duration_since(self.offline_since) < REPORT_TIME + } +} + +/// Tracks offline validators and can issue a report for those offline. +pub struct OfflineTracker { + observed: HashMap, +} + +impl OfflineTracker { + /// Create a new tracker. + pub fn new() -> Self { + OfflineTracker { observed: HashMap::new() } + } + + /// Note new consensus is starting with the given set of validators. + pub fn note_new_block(&mut self, validators: &[AccountId]) { + use std::collections::HashSet; + + let set: HashSet<_> = validators.iter().cloned().collect(); + self.observed.retain(|k, _| set.contains(k)); + } + + /// Note that a round has ended. + pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) { + self.observed.entry(validator) + .or_insert_with(Observed::new) + .note_round_end(was_online); + } + + /// Generate a vector of indices for offline account IDs. + pub fn reports(&self, validators: &[AccountId]) -> Vec { + validators.iter() + .enumerate() + .filter_map(|(i, v)| if self.is_online(v) { + None + } else { + Some(i as u32) + }) + .collect() + } + + /// Whether reports on a validator set are consistent with our view of things. + pub fn check_consistency(&self, validators: &[AccountId], reports: &[u32]) -> bool { + reports.iter().cloned().all(|r| { + let v = match validators.get(r as usize) { + Some(v) => v, + None => return false, + }; + + // we must think all validators reported externally are offline. + let thinks_online = self.is_online(v); + !thinks_online + }) + } + + fn is_online(&self, v: &AccountId) -> bool { + self.observed.get(v).map(Observed::is_active).unwrap_or(true) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn validator_offline() { + let mut tracker = OfflineTracker::new(); + let v = [0; 32].into(); + let v2 = [1; 32].into(); + let v3 = [2; 32].into(); + tracker.note_round_end(v, true); + tracker.note_round_end(v2, true); + tracker.note_round_end(v3, true); + + let slash_time = REPORT_TIME + Duration::from_secs(5); + tracker.observed.get_mut(&v).unwrap().offline_since -= slash_time; + tracker.observed.get_mut(&v2).unwrap().offline_since -= slash_time; + + assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]); + + tracker.note_new_block(&[v, v3]); + assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]); + } +} diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs index ca7561bd4d818..d0d89786c3603 100644 --- a/polkadot/consensus/src/service.rs +++ b/polkadot/consensus/src/service.rs @@ -113,6 +113,9 @@ impl Service { N::TableRouter: Send + 'static, ::Future: Send + 'static, { + use parking_lot::RwLock; + use super::OfflineTracker; + let (signal, exit) = ::exit_future::signal(); let thread = thread::spawn(move || { let mut runtime = LocalRuntime::new().expect("Could not create local runtime"); @@ -125,6 +128,7 @@ impl Service { network, parachain_empty_duration, handle: thread_pool, + offline: Arc::new(RwLock::new(OfflineTracker::new())), }; let bft_service = Arc::new(BftService::new(client.clone(), key, factory)); diff --git a/polkadot/network/Cargo.toml b/polkadot/network/Cargo.toml index 37d36ea205e50..e674fad158f50 100644 --- a/polkadot/network/Cargo.toml +++ b/polkadot/network/Cargo.toml @@ -17,4 +17,4 @@ ed25519 = { path = "../../substrate/ed25519" } futures = "0.1" tokio = "0.1.7" log = "0.4" -rhododendron = "0.2" +rhododendron = "0.3" diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 83118799bb355..5185227720bcf 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -120,3 +120,32 @@ impl Encode for Log { self.0.encode_to(dest) } } + +/// Inherent data to include in a block. +pub struct InherentData { + /// Current timestamp. + pub timestamp: Timestamp, + /// Parachain heads update. + pub parachain_heads: Vec<::parachain::CandidateReceipt>, + /// Indices of offline validators. + pub offline_indices: Vec, +} + +impl Decode for InherentData { + fn decode(input: &mut I) -> Option { + let (timestamp, parachain_heads, offline_indices) = Decode::decode(input)?; + Some(InherentData { + timestamp, + parachain_heads, + offline_indices, + }) + } +} + +impl Encode for InherentData { + fn encode_to(&self, dest: &mut T) { + self.timestamp.encode_to(dest); + self.parachain_heads.encode_to(dest); + self.offline_indices.encode_to(dest); + } +} diff --git a/polkadot/runtime/src/checked_block.rs b/polkadot/runtime/src/checked_block.rs index 4ea7bd0e88163..d193d26963fd0 100644 --- a/polkadot/runtime/src/checked_block.rs +++ b/polkadot/runtime/src/checked_block.rs @@ -16,9 +16,10 @@ //! Typesafe block interaction. -use super::{Call, Block, TIMESTAMP_SET_POSITION, PARACHAINS_SET_POSITION}; +use super::{Call, Block, TIMESTAMP_SET_POSITION, PARACHAINS_SET_POSITION, NOTE_OFFLINE_POSITION}; use timestamp::Call as TimestampCall; use parachains::Call as ParachainsCall; +use session::Call as SessionCall; use primitives::parachain::CandidateReceipt; /// Provides a type-safe wrapper around a structurally valid block. @@ -47,6 +48,7 @@ impl CheckedBlock { }); if !has_heads { return Err(block) } + Ok(CheckedBlock { inner: block, file_line: None, @@ -88,6 +90,14 @@ impl CheckedBlock { } } + /// Extract the noted offline validator indices (if any) from the block. + pub fn noted_offline(&self) -> &[u32] { + self.inner.extrinsics.get(NOTE_OFFLINE_POSITION as usize).and_then(|xt| match xt.extrinsic.function { + Call::Session(SessionCall::note_offline(ref x)) => Some(&x[..]), + _ => None, + }).unwrap_or(&[]) + } + /// Convert into inner block. pub fn into_inner(self) -> Block { self.inner } } diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index 7c366e30e7a8a..dfe8e61048986 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -85,6 +85,8 @@ pub use primitives::Header; pub const TIMESTAMP_SET_POSITION: u32 = 0; /// The position of the parachains set extrinsic. pub const PARACHAINS_SET_POSITION: u32 = 1; +/// The position of the offline nodes noting extrinsic. +pub const NOTE_OFFLINE_POSITION: u32 = 2; /// The address format for describing accounts. pub type Address = staking::Address; @@ -110,7 +112,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: ver_str!("polkadot"), impl_name: ver_str!("parity-polkadot"), authoring_version: 1, - spec_version: 2, + spec_version: 3, impl_version: 0, }; @@ -160,6 +162,7 @@ impl Convert for SessionKeyConversion { } impl session::Trait for Concrete { + const NOTE_OFFLINE_POSITION: u32 = NOTE_OFFLINE_POSITION; type ConvertAccountIdToSessionKey = SessionKeyConversion; type OnSessionChange = Staking; } @@ -247,7 +250,7 @@ pub mod api { apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic), execute_block => |block| super::Executive::execute_block(block), finalise_block => |()| super::Executive::finalise_block(), - inherent_extrinsics => |(timestamp, heads)| super::inherent_extrinsics(timestamp, heads), + inherent_extrinsics => |inherent| super::inherent_extrinsics(inherent), validator_count => |()| super::Session::validator_count(), validators => |()| super::Session::validators() ); diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index 4ac5f3f2a1089..16cdcdca33249 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -265,6 +265,7 @@ mod tests { type Header = Header; } impl session::Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = (); } diff --git a/polkadot/runtime/src/utils.rs b/polkadot/runtime/src/utils.rs index 1531590ff5fca..acef06092539f 100644 --- a/polkadot/runtime/src/utils.rs +++ b/polkadot/runtime/src/utils.rs @@ -19,30 +19,33 @@ use rstd::prelude::*; use super::{Call, UncheckedExtrinsic, Extrinsic, Staking}; use runtime_primitives::traits::{Checkable, AuxLookup}; -use primitives::parachain::CandidateReceipt; use timestamp::Call as TimestampCall; use parachains::Call as ParachainsCall; +use session::Call as SessionCall; /// Produces the list of inherent extrinsics. -pub fn inherent_extrinsics(timestamp: ::primitives::Timestamp, parachain_heads: Vec) -> Vec { - vec![ - UncheckedExtrinsic::new( - Extrinsic { - signed: Default::default(), - function: Call::Timestamp(TimestampCall::set(timestamp)), - index: 0, - }, - Default::default() - ), - UncheckedExtrinsic::new( - Extrinsic { - signed: Default::default(), - function: Call::Parachains(ParachainsCall::set_heads(parachain_heads)), - index: 0, - }, - Default::default() - ) - ] +pub fn inherent_extrinsics(data: ::primitives::InherentData) -> Vec { + let make_inherent = |function| UncheckedExtrinsic::new( + Extrinsic { + signed: Default::default(), + function, + index: 0, + }, + Default::default(), + ); + + let mut inherent = vec![ + make_inherent(Call::Timestamp(TimestampCall::set(data.timestamp))), + make_inherent(Call::Parachains(ParachainsCall::set_heads(data.parachain_heads))), + ]; + + if !data.offline_indices.is_empty() { + inherent.push(make_inherent( + Call::Session(SessionCall::note_offline(data.offline_indices)) + )); + } + + inherent } /// Checks an unchecked extrinsic for validity. diff --git a/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock index b90d8e14e89d8..f69011af31232 100644 --- a/polkadot/runtime/wasm/Cargo.lock +++ b/polkadot/runtime/wasm/Cargo.lock @@ -416,7 +416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -691,8 +691,11 @@ dependencies = [ [[package]] name = "smallvec" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "stable_deref_trait" @@ -1213,7 +1216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)" = "fba5be06346c5200249c8c8ca4ccba4a09e8747c71c16e420bd359a0db4d8f91" "checksum serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)" = "79e4620ba6fbe051fc7506fab6f84205823564d55da18d55b695160fb3479cd8" -"checksum smallvec 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03dab98ab5ded3a8b43b2c80751194608d0b2aa0f1d46cf95d1c35e192844aa7" +"checksum smallvec 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "211a489e65e94b103926d2054ae515a1cdb5d515ea0ef414fee23b7e043ce748" "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" "checksum syn 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6dfd71b2be5a58ee30a6f8ea355ba8290d397131c00dfa55c3d34e6e13db5101" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index 7e6dbc7c1bdbe..dba5dc7d2ded4 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index 4029d032ca768..717beffa27451 100755 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/polkadot/transaction-pool/src/lib.rs b/polkadot/transaction-pool/src/lib.rs index 265f72ba9903a..447d97d004595 100644 --- a/polkadot/transaction-pool/src/lib.rs +++ b/polkadot/transaction-pool/src/lib.rs @@ -286,7 +286,7 @@ impl<'a, A> txpool::Verifier for Verifier<'a, A> where let encoded = uxt.encode(); let (encoded_size, hash) = (encoded.len(), BlakeTwo256::hash(&encoded)); - + debug!(target: "transaction-pool", "Transaction submitted: {}", ::substrate_primitives::hexdisplay::HexDisplay::from(&encoded)); let inner = match uxt.clone().check_with(|a| self.lookup(a)) { @@ -446,10 +446,10 @@ mod tests { use substrate_keyring::Keyring::{self, *}; use codec::{Decode, Encode}; use polkadot_api::{PolkadotApi, BlockBuilder, Result}; - use primitives::{AccountId, AccountIndex, Block, BlockId, Hash, Index, SessionKey, Timestamp, + use primitives::{AccountId, AccountIndex, Block, BlockId, Hash, Index, SessionKey, UncheckedExtrinsic as FutureProofUncheckedExtrinsic}; use runtime::{RawAddress, Call, TimestampCall, BareExtrinsic, Extrinsic, UncheckedExtrinsic}; - use primitives::parachain::{CandidateReceipt, DutyRoster, Id as ParaId}; + use primitives::parachain::{DutyRoster, Id as ParaId}; use substrate_runtime_primitives::{MaybeUnsigned, generic}; struct TestBlockBuilder; @@ -494,8 +494,8 @@ mod tests { fn active_parachains(&self, _at: &BlockId) -> Result> { unimplemented!() } fn parachain_code(&self, _at: &BlockId, _parachain: ParaId) -> Result>> { unimplemented!() } fn parachain_head(&self, _at: &BlockId, _parachain: ParaId) -> Result>> { unimplemented!() } - fn build_block(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec) -> Result { unimplemented!() } - fn inherent_extrinsics(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec) -> Result>> { unimplemented!() } + fn build_block(&self, _at: &BlockId, _inherent: ::primitives::InherentData) -> Result { unimplemented!() } + fn inherent_extrinsics(&self, _at: &BlockId, _inherent: ::primitives::InherentData) -> Result>> { unimplemented!() } fn index(&self, _at: &BlockId, _account: AccountId) -> Result { Ok((_account[0] as u32) + number_of(_at)) diff --git a/substrate/bft/Cargo.toml b/substrate/bft/Cargo.toml index 52367a06711ad..f15f24a0d5bd8 100644 --- a/substrate/bft/Cargo.toml +++ b/substrate/bft/Cargo.toml @@ -15,7 +15,7 @@ tokio = "0.1.7" parking_lot = "0.4" error-chain = "0.12" log = "0.3" -rhododendron = "0.2" +rhododendron = "0.3" [dev-dependencies] substrate-keyring = { path = "../keyring" } diff --git a/substrate/bft/src/lib.rs b/substrate/bft/src/lib.rs index c85002dbccfd1..99ede0f1345f6 100644 --- a/substrate/bft/src/lib.rs +++ b/substrate/bft/src/lib.rs @@ -69,7 +69,7 @@ use futures::sync::oneshot; use tokio::timer::Delay; use parking_lot::Mutex; -pub use rhododendron::InputStreamConcluded; +pub use rhododendron::{InputStreamConcluded, AdvanceRoundReason}; pub use error::{Error, ErrorKind}; /// Messages over the proposal. @@ -184,6 +184,9 @@ pub trait Proposer { /// Determine the proposer for a given round. This should be a deterministic function /// with consistent results across all authorities. fn round_proposer(&self, round_number: usize, authorities: &[AuthorityId]) -> AuthorityId; + + /// Hook called when a BFT round advances without a proposal. + fn on_round_end(&self, _round_number: usize, _proposed: bool) { } } /// Block import trait. @@ -260,6 +263,18 @@ impl> rhododendron::Context for BftInstance Box::new(fut) } + + fn on_advance_round( + &self, + accumulator: &::rhododendron::Accumulator, + round: usize, + _next_round: usize, + reason: AdvanceRoundReason, + ) { + if let AdvanceRoundReason::Timeout = reason { + self.proposer.on_round_end(round, accumulator.proposal().is_some()); + } + } } /// A future that resolves either when canceled (witnessing a block from the network at same height) @@ -303,6 +318,9 @@ impl Future for BftFuture" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum rustc-hex 2.0.0 (git+https://github.com/rphmeier/rustc-hex.git)" = "" -"checksum rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9743a7670d88d5d52950408ecdb7c71d8986251ab604d4689dd2ca25c9bca69" -"checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" +"checksum serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)" = "6dfad05c8854584e5f72fb859385ecdfa03af69c3fd0572f0da2d4c95f060bdb" "checksum uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)" = "" diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm index bd930a1e4cc7a..66ecde9dbfefa 100644 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm index 15312bde3b2a0..8b38553ef6cd7 100755 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm differ diff --git a/substrate/misbehavior-check/Cargo.toml b/substrate/misbehavior-check/Cargo.toml index c51e705369564..aa726c18b18db 100644 --- a/substrate/misbehavior-check/Cargo.toml +++ b/substrate/misbehavior-check/Cargo.toml @@ -11,7 +11,7 @@ substrate-runtime-io = { path = "../runtime-io", default-features = false } [dev-dependencies] substrate-bft = { path = "../bft" } -rhododendron = "0.2" +rhododendron = "0.3" substrate-keyring = { path = "../keyring" } [features] diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index 503fb780c3010..43dcb7bacbae4 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -54,6 +54,7 @@ impl staking::Trait for Test { type OnAccountKill = Contract; } impl session::Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = Staking; } diff --git a/substrate/runtime/council/src/lib.rs b/substrate/runtime/council/src/lib.rs index 56d87ea59bff5..1fd210021a71d 100644 --- a/substrate/runtime/council/src/lib.rs +++ b/substrate/runtime/council/src/lib.rs @@ -649,6 +649,7 @@ mod tests { type Header = Header; } impl session::Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = staking::Module; } diff --git a/substrate/runtime/democracy/src/lib.rs b/substrate/runtime/democracy/src/lib.rs index 003ce97bb1ea9..85b2a505227b1 100644 --- a/substrate/runtime/democracy/src/lib.rs +++ b/substrate/runtime/democracy/src/lib.rs @@ -391,6 +391,7 @@ mod tests { type Header = Header; } impl session::Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = staking::Module; } @@ -494,7 +495,7 @@ mod tests { assert_eq!(Democracy::tally(r), (10, 0)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::era_length(), 2); }); @@ -572,19 +573,19 @@ mod tests { System::set_block_number(1); assert_ok!(Democracy::vote(&1, 0, true)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::bonding_duration(), 4); System::set_block_number(2); assert_ok!(Democracy::vote(&1, 1, true)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::bonding_duration(), 3); System::set_block_number(3); assert_ok!(Democracy::vote(&1, 2, true)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::bonding_duration(), 2); }); } @@ -605,7 +606,7 @@ mod tests { assert_eq!(Democracy::tally(r), (10, 0)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::era_length(), 2); }); @@ -620,7 +621,7 @@ mod tests { assert_ok!(Democracy::cancel_referendum(r)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::era_length(), 1); }); @@ -638,7 +639,7 @@ mod tests { assert_eq!(Democracy::tally(r), (0, 10)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::era_length(), 1); }); @@ -659,7 +660,7 @@ mod tests { assert_eq!(Democracy::tally(r), (110, 100)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::era_length(), 2); }); @@ -676,7 +677,7 @@ mod tests { assert_eq!(Democracy::tally(r), (60, 50)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::era_length(), 1); }); @@ -697,7 +698,7 @@ mod tests { assert_eq!(Democracy::tally(r), (100, 50)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::on_session_change(true, 0); + Staking::on_session_change(0, Vec::new()); assert_eq!(Staking::era_length(), 2); }); diff --git a/substrate/runtime/executive/src/lib.rs b/substrate/runtime/executive/src/lib.rs index 0f01b7a7cb960..a918f062e86a5 100644 --- a/substrate/runtime/executive/src/lib.rs +++ b/substrate/runtime/executive/src/lib.rs @@ -252,6 +252,7 @@ mod tests { type Header = Header; } impl session::Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = staking::Module; } diff --git a/substrate/runtime/session/src/lib.rs b/substrate/runtime/session/src/lib.rs index 8e53428de63a0..06368d651e2b1 100644 --- a/substrate/runtime/session/src/lib.rs +++ b/substrate/runtime/session/src/lib.rs @@ -46,23 +46,26 @@ extern crate substrate_runtime_system as system; extern crate substrate_runtime_timestamp as timestamp; use rstd::prelude::*; -use primitives::traits::{Zero, One, RefInto, Executable, Convert, As}; +use primitives::traits::{Zero, One, RefInto, MaybeEmpty, Executable, Convert, As}; use runtime_support::{StorageValue, StorageMap}; use runtime_support::dispatch::Result; /// A session has changed. -pub trait OnSessionChange { +pub trait OnSessionChange { /// Session has changed. - fn on_session_change(normal_rotation: bool, time_elapsed: T); + fn on_session_change(time_elapsed: T, bad_validators: Vec); } -impl OnSessionChange for () { - fn on_session_change(_: bool, _: T) {} +impl OnSessionChange for () { + fn on_session_change(_: T, _: Vec) {} } pub trait Trait: timestamp::Trait { + // the position of the required timestamp-set extrinsic. + const NOTE_OFFLINE_POSITION: u32; + type ConvertAccountIdToSessionKey: Convert; - type OnSessionChange: OnSessionChange; + type OnSessionChange: OnSessionChange; } decl_module! { @@ -71,6 +74,7 @@ decl_module! { #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum Call where aux: T::PublicAux { fn set_key(aux, key: T::SessionKey) -> Result = 0; + fn note_offline(aux, offline_val_indices: Vec) -> Result = 1; } #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -94,6 +98,10 @@ decl_storage! { // Percent by which the session must necessarily finish late before we early-exit the session. pub BrokenPercentLate get(broken_percent_late): b"ses:broken_percent_late" => required T::Moment; + // Opinions of the current validator set about the activeness of their peers. + // Gets cleared when the validator set changes. + pub BadValidators get(bad_validators): b"ses:bad_validators" => Vec; + // New session is being forced is this entry exists; in which case, the boolean value is whether // the new session should be considered a normal rotation (rewardable) or exceptional (slashable). pub ForcingNewSession get(forcing_new_session): b"ses:forcing_new_session" => bool; @@ -136,6 +144,20 @@ impl Module { Ok(()) } + /// Notes which of the validators appear to be online from the point of the view of the block author. + pub fn note_offline(aux: &T::PublicAux, offline_val_indices: Vec) -> Result { + assert!(aux.is_empty()); + assert!( + >::extrinsic_index() == T::NOTE_OFFLINE_POSITION, + "note_offline extrinsic must be at position {} in the block", + T::NOTE_OFFLINE_POSITION + ); + + let vs = Self::validators(); + >::put(offline_val_indices.into_iter().map(|i| vs[i as usize].clone()).collect::>()); + Ok(()) + } + // INTERNAL API (available to other runtime modules) /// Set the current set of validators. @@ -156,17 +178,15 @@ impl Module { // check block number and call next_session if necessary. let block_number = >::block_number(); let is_final_block = ((block_number - Self::last_length_change()) % Self::length()).is_zero(); - let broken_validation = Self::broken_validation(); - if let Some(normal_rotation) = Self::forcing_new_session() { - Self::rotate_session(normal_rotation, is_final_block); - >::kill(); - } else if is_final_block || broken_validation { - Self::rotate_session(!broken_validation, is_final_block); + let bad_validators = >::take().unwrap_or_default(); + let should_end_session = >::take().is_some() || !bad_validators.is_empty() || is_final_block; + if should_end_session { + Self::rotate_session(is_final_block, bad_validators); } } /// Move onto next session: register the new authority set. - pub fn rotate_session(normal_rotation: bool, is_final_block: bool) { + pub fn rotate_session(is_final_block: bool, bad_validators: Vec) { let now = >::get(); let time_elapsed = now.clone() - Self::current_start(); @@ -186,7 +206,7 @@ impl Module { >::put(block_number); } - T::OnSessionChange::on_session_change(normal_rotation, time_elapsed); + T::OnSessionChange::on_session_change(time_elapsed, bad_validators); // Update any changes in session keys. Self::validators().iter().enumerate().for_each(|(i, v)| { @@ -301,6 +321,7 @@ mod tests { type Moment = u64; } impl Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = (); } @@ -350,7 +371,7 @@ mod tests { assert_eq!(Session::ideal_session_duration(), 15); // ideal end = 0 + 15 * 3 = 15 // broken_limit = 15 * 130 / 100 = 19 - + System::set_block_number(3); assert_eq!(Session::blocks_remaining(), 2); Timestamp::set_timestamp(9); // earliest end = 9 + 2 * 5 = 19; OK. @@ -378,7 +399,7 @@ mod tests { assert_eq!(Session::blocks_remaining(), 0); Session::check_rotate_session(); assert_eq!(Session::length(), 10); - + System::set_block_number(7); assert_eq!(Session::current_index(), 1); assert_eq!(Session::blocks_remaining(), 5); diff --git a/substrate/runtime/staking/src/lib.rs b/substrate/runtime/staking/src/lib.rs index f7c03972d574f..825a09f171413 100644 --- a/substrate/runtime/staking/src/lib.rs +++ b/substrate/runtime/staking/src/lib.rs @@ -177,6 +177,9 @@ decl_storage! { // The current era stake threshold pub StakeThreshold get(stake_threshold): b"sta:stake_threshold" => required T::Balance; + // The current bad validator slash. + pub CurrentSlash get(current_slash): b"sta:current_slash" => default T::Balance; + // The next free enumeration set. pub NextEnumSet get(next_enum_set): b"sta:next_enum" => required T::AccountIndex; // The enumeration sets. @@ -589,10 +592,30 @@ impl Module { /// Session has just changed. We need to determine whether we pay a reward, slash and/or /// move to a new era. - fn new_session(normal_rotation: bool, actual_elapsed: T::Moment) { + fn new_session(actual_elapsed: T::Moment, bad_validators: Vec) { let session_index = >::current_index(); + let early_exit_era = !bad_validators.is_empty(); + + if early_exit_era { + // slash + let slash = Self::current_slash() + Self::early_era_slash(); + >::put(&slash); + for v in bad_validators.into_iter() { + if let Some(rem) = Self::slash(&v, slash) { + let noms = Self::current_nominators_for(&v); + let total = noms.iter().map(Self::voting_balance).fold(T::Balance::zero(), |acc, x| acc + x); + if !total.is_zero() { + let safe_mul_rational = |b| b * rem / total;// TODO: avoid overflow + for n in noms.iter() { + let _ = Self::slash(n, safe_mul_rational(Self::voting_balance(n))); // best effort - not much that can be done on fail. + } + } + } + } + } else { + // Zero any cumulative slash since we're healthy now. + >::kill(); - if normal_rotation { // reward let ideal_elapsed = >::ideal_session_duration(); let per65536: u64 = (T::Moment::sa(65536u64) * ideal_elapsed.clone() / actual_elapsed.max(ideal_elapsed)).as_(); @@ -609,25 +632,10 @@ impl Module { let _ = Self::reward(v, safe_mul_rational(Self::voting_balance(v))); } } - } else { - // slash - let early_era_slash = Self::early_era_slash(); - for v in >::validators().iter() { - if let Some(rem) = Self::slash(v, early_era_slash) { - let noms = Self::current_nominators_for(v); - let total = noms.iter().map(Self::voting_balance).fold(T::Balance::zero(), |acc, x| acc + x); - if !total.is_zero() { - let safe_mul_rational = |b| b * rem / total;// TODO: avoid overflow - for n in noms.iter() { - let _ = Self::slash(n, safe_mul_rational(Self::voting_balance(n))); // best effort - not much that can be done on fail. - } - } - } - } } if >::take().is_some() || ((session_index - Self::last_era_length_change()) % Self::sessions_per_era()).is_zero() - || !normal_rotation + || early_exit_era { Self::new_era(); } @@ -654,6 +662,8 @@ impl Module { } } + let minimum_allowed = Self::early_era_slash(); + // evaluate desired staking amounts and nominations and optimise to find the best // combination of validators, then use session::internal::set_validators(). // for now, this just orders would-be stakers by their balances and chooses the top-most @@ -662,11 +672,12 @@ impl Module { let mut intentions = >::get() .into_iter() .map(|v| (Self::voting_balance(&v) + Self::nomination_balance(&v), v)) + .filter(|&(b, _)| b >= minimum_allowed) .collect::>(); intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1)); >::put( - if intentions.len() > 0 { + if !intentions.is_empty() { let i = (>::get() as usize).min(intentions.len() - 1); intentions[i].0.clone() } else { Zero::zero() } @@ -797,9 +808,9 @@ impl Executable for Module { } } -impl OnSessionChange for Module { - fn on_session_change(normal_rotation: bool, elapsed: T::Moment) { - Self::new_session(normal_rotation, elapsed); +impl OnSessionChange for Module { + fn on_session_change(elapsed: T::Moment, bad_validators: Vec) { + Self::new_session(elapsed, bad_validators); } } diff --git a/substrate/runtime/staking/src/mock.rs b/substrate/runtime/staking/src/mock.rs index 66119cef6bd92..dcc96b03735e1 100644 --- a/substrate/runtime/staking/src/mock.rs +++ b/substrate/runtime/staking/src/mock.rs @@ -45,6 +45,7 @@ impl system::Trait for Test { type Header = Header; } impl session::Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = Staking; } diff --git a/substrate/test-client/Cargo.toml b/substrate/test-client/Cargo.toml index 4897dae974704..69920cb48500c 100644 --- a/substrate/test-client/Cargo.toml +++ b/substrate/test-client/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Parity Technologies "] [dependencies] -rhododendron = "0.2" +rhododendron = "0.3" substrate-bft = { path = "../bft" } substrate-client = { path = "../client" } substrate-codec = { path = "../codec" } diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index d449d647e20ce..246a16df63e1d 100644 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm index 2fe9433d00c92..db96610f828c9 100755 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm differ