mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 16:31:07 +00:00
Vote out offline authorities (#524)
* notify when an authority appears to have missed their block * Runtime API * offline tracker * Move to consensus * generating reports of offline indices * stubbed-out evaluation logic * Slashing data pathwat * usize -> u32 * Slash bad validators. * update to rhododendron 0.3 * fix compilation of polkadot-consensus * Support offline noting in checked_block * include offline reports in block authorship voting * do not vote validators offline after some time * add test for offline-tracker * fix test build * bump spec version * update wasm * Only allow validators that are possible to slash * Fix grumble * More idiomatic * New Wasm. * update rhododendron * improve logging and reduce round time exponent * format offline validators in ss58
This commit is contained in:
committed by
Gav Wood
parent
c2b20fe5b0
commit
e8f21cf0c9
Generated
+7
-7
@@ -1916,7 +1916,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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-bft 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -1947,7 +1947,7 @@ dependencies = [
|
||||
"polkadot-availability-store 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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-bft 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-codec-derive 0.1.0",
|
||||
@@ -2290,7 +2290,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rhododendron"
|
||||
version = "0.2.0"
|
||||
version = "0.3.1"
|
||||
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)",
|
||||
@@ -2643,7 +2643,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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-executor 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
@@ -2819,7 +2819,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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-bft 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
@@ -3279,7 +3279,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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-bft 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -4164,7 +4164,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.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b4c928dfb981e491432f0809e93c99857112b0a348a93eee6b13e0bf0f0f3"
|
||||
"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)" = "<none>"
|
||||
"checksum rlp 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "89db7f8dfdd5eb7ab3ac3ece7a07fd273a680b4b224cb231181280e8996f9f0b"
|
||||
|
||||
@@ -121,6 +121,7 @@ impl Convert<AccountId, SessionKey> for SessionKeyConversion {
|
||||
}
|
||||
|
||||
impl session::Trait for Concrete {
|
||||
const NOTE_OFFLINE_POSITION: u32 = 1;
|
||||
type ConvertAccountIdToSessionKey = SessionKeyConversion;
|
||||
type OnSessionChange = Staking;
|
||||
}
|
||||
|
||||
@@ -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<B: LocalBackend<Block>> PolkadotApi for Client<B, LocalCallExecutor<B, Nati
|
||||
with_runtime!(self, at, || ::runtime::Parachains::parachain_head(parachain))
|
||||
}
|
||||
|
||||
fn build_block(&self, at: &BlockId, timestamp: Timestamp, new_heads: Vec<CandidateReceipt>) -> Result<Self::BlockBuilder> {
|
||||
fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result<Self::BlockBuilder> {
|
||||
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<CandidateReceipt>) -> Result<Vec<UncheckedExtrinsic>> {
|
||||
fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result<Vec<UncheckedExtrinsic>> {
|
||||
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 {
|
||||
|
||||
@@ -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<bool>;
|
||||
|
||||
/// Build a block on top of the given, with inherent extrinsics pre-pushed.
|
||||
fn build_block(&self, at: &BlockId, timestamp: Timestamp, new_heads: Vec<CandidateReceipt>) -> Result<Self::BlockBuilder>;
|
||||
fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result<Self::BlockBuilder>;
|
||||
|
||||
/// 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<CandidateReceipt>) -> Result<Vec<UncheckedExtrinsic>>;
|
||||
fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result<Vec<UncheckedExtrinsic>>;
|
||||
}
|
||||
|
||||
/// Mark for all Polkadot API implementations, that are making use of state data, stored locally.
|
||||
|
||||
@@ -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<B: Backend<Block>, E: CallExecutor<Block>> PolkadotApi for RemotePolkadotAp
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn build_block(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec<CandidateReceipt>) -> Result<Self::BlockBuilder> {
|
||||
fn build_block(&self, _at: &BlockId, _inherent: InherentData) -> Result<Self::BlockBuilder> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn inherent_extrinsics(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec<CandidateReceipt>) -> Result<Vec<Vec<u8>>> {
|
||||
fn inherent_extrinsics(&self, _at: &BlockId, _inherent: InherentData) -> Result<Vec<Vec<u8>>> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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-availability-store = { path = "../availability-store" }
|
||||
polkadot-parachain = { path = "../parachain" }
|
||||
|
||||
@@ -74,6 +74,9 @@ impl DynamicInclusion {
|
||||
Some(now + until)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the start instant.
|
||||
pub fn started_at(&self) -> Instant { self.start }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -69,7 +69,7 @@ use std::time::{Duration, Instant};
|
||||
use codec::{Decode, Encode};
|
||||
use extrinsic_store::Store as ExtrinsicStore;
|
||||
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;
|
||||
@@ -80,20 +80,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<RwLock<OfflineTracker>>;
|
||||
|
||||
// block size limit.
|
||||
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
@@ -240,6 +246,8 @@ pub struct ProposerFactory<C, N, P> {
|
||||
pub parachain_empty_duration: Duration,
|
||||
/// Store for extrinsic data.
|
||||
pub extrinsic_store: ExtrinsicStore,
|
||||
/// Offline-tracker.
|
||||
pub offline: SharedOfflineTracker,
|
||||
}
|
||||
|
||||
impl<C, N, P> bft::Environment<Block> for ProposerFactory<C, N, P>
|
||||
@@ -255,10 +263,11 @@ impl<C, N, P> bft::Environment<Block> for ProposerFactory<C, N, P>
|
||||
type Output = N::Output;
|
||||
type Error = Error;
|
||||
|
||||
fn init(&self,
|
||||
fn init(
|
||||
&self,
|
||||
parent_header: &Header,
|
||||
authorities: &[AuthorityId],
|
||||
sign_with: Arc<ed25519::Pair>
|
||||
sign_with: Arc<ed25519::Pair>,
|
||||
) -> Result<(Self::Proposer, Self::Input, Self::Output), Error> {
|
||||
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
|
||||
|
||||
@@ -269,6 +278,9 @@ impl<C, N, P> bft::Environment<Block> for ProposerFactory<C, N, P>
|
||||
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,
|
||||
@@ -326,6 +338,8 @@ impl<C, N, P> bft::Environment<Block> for ProposerFactory<C, N, P>
|
||||
random_seed,
|
||||
table,
|
||||
transaction_pool: self.transaction_pool.clone(),
|
||||
offline: self.offline.clone(),
|
||||
validators,
|
||||
_drop_signal: drop_signal,
|
||||
};
|
||||
|
||||
@@ -403,9 +417,22 @@ pub struct Proposer<C: PolkadotApi> {
|
||||
random_seed: Hash,
|
||||
table: Arc<SharedTable>,
|
||||
transaction_pool: Arc<TransactionPool<C>>,
|
||||
offline: SharedOfflineTracker,
|
||||
validators: Vec<AccountId>,
|
||||
_drop_signal: exit_future::Signal,
|
||||
}
|
||||
|
||||
impl<C: PolkadotApi + Send + Sync> Proposer<C> {
|
||||
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<C> bft::Proposer<Block> for Proposer<C>
|
||||
where
|
||||
C: PolkadotApi + Send + Sync,
|
||||
@@ -441,6 +468,8 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
||||
client: self.client.clone(),
|
||||
transaction_pool: self.transaction_pool.clone(),
|
||||
table: self.table.clone(),
|
||||
offline: self.offline.clone(),
|
||||
validators: self.validators.clone(),
|
||||
timing,
|
||||
})
|
||||
}
|
||||
@@ -515,6 +544,13 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
||||
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
|
||||
@@ -536,13 +572,8 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
||||
}
|
||||
|
||||
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
|
||||
@@ -611,6 +642,36 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
||||
.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());
|
||||
let public = ::ed25519::Public::from_raw(primary_validator.0);
|
||||
match forced_delay {
|
||||
None => info!(
|
||||
"Potential Offline Validator: {} failed to propose during assigned slot: {}",
|
||||
public,
|
||||
round_number,
|
||||
),
|
||||
Some(_) => info!(
|
||||
"Potential Offline Validator {} potentially forced to skip assigned slot: {}",
|
||||
public,
|
||||
round_number,
|
||||
),
|
||||
}
|
||||
|
||||
forced_delay.is_some()
|
||||
};
|
||||
|
||||
self.offline.write().note_round_end(primary_validator, consider_online);
|
||||
}
|
||||
}
|
||||
|
||||
fn current_timestamp() -> Timestamp {
|
||||
@@ -667,16 +728,42 @@ pub struct CreateProposal<C: PolkadotApi> {
|
||||
transaction_pool: Arc<TransactionPool<C>>,
|
||||
table: Arc<SharedTable>,
|
||||
timing: ProposalTiming,
|
||||
validators: Vec<AccountId>,
|
||||
offline: SharedOfflineTracker,
|
||||
}
|
||||
|
||||
impl<C> CreateProposal<C> where C: PolkadotApi {
|
||||
fn propose_with(&self, candidates: Vec<CandidateReceipt>) -> Result<Block, Error> {
|
||||
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::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<AccountId, Observed>,
|
||||
}
|
||||
|
||||
impl OfflineTracker {
|
||||
/// Create a new tracker.
|
||||
pub fn new() -> Self {
|
||||
OfflineTracker { observed: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Note new consensus is starting with the given set of validators.
|
||||
pub fn note_new_block(&mut self, validators: &[AccountId]) {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let set: HashSet<_> = validators.iter().cloned().collect();
|
||||
self.observed.retain(|k, _| set.contains(k));
|
||||
}
|
||||
|
||||
/// Note that a round has ended.
|
||||
pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) {
|
||||
self.observed.entry(validator)
|
||||
.or_insert_with(Observed::new)
|
||||
.note_round_end(was_online);
|
||||
}
|
||||
|
||||
/// Generate a vector of indices for offline account IDs.
|
||||
pub fn reports(&self, validators: &[AccountId]) -> Vec<u32> {
|
||||
validators.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| if self.is_online(v) {
|
||||
None
|
||||
} else {
|
||||
Some(i as u32)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether reports on a validator set are consistent with our view of things.
|
||||
pub fn check_consistency(&self, validators: &[AccountId], reports: &[u32]) -> bool {
|
||||
reports.iter().cloned().all(|r| {
|
||||
let v = match validators.get(r as usize) {
|
||||
Some(v) => v,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// we must think all validators reported externally are offline.
|
||||
let thinks_online = self.is_online(v);
|
||||
!thinks_online
|
||||
})
|
||||
}
|
||||
|
||||
fn is_online(&self, v: &AccountId) -> bool {
|
||||
self.observed.get(v).map(Observed::is_active).unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn validator_offline() {
|
||||
let mut tracker = OfflineTracker::new();
|
||||
let v = [0; 32].into();
|
||||
let v2 = [1; 32].into();
|
||||
let v3 = [2; 32].into();
|
||||
tracker.note_round_end(v, true);
|
||||
tracker.note_round_end(v2, true);
|
||||
tracker.note_round_end(v3, true);
|
||||
|
||||
let slash_time = REPORT_TIME + Duration::from_secs(5);
|
||||
tracker.observed.get_mut(&v).unwrap().offline_since -= slash_time;
|
||||
tracker.observed.get_mut(&v2).unwrap().offline_since -= slash_time;
|
||||
|
||||
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]);
|
||||
|
||||
tracker.note_new_block(&[v, v3]);
|
||||
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,9 @@ impl Service {
|
||||
N::TableRouter: Send + 'static,
|
||||
<N::Collation as IntoFuture>::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");
|
||||
@@ -185,6 +188,7 @@ impl Service {
|
||||
parachain_empty_duration,
|
||||
handle: thread_pool.clone(),
|
||||
extrinsic_store: extrinsic_store.clone(),
|
||||
offline: Arc::new(RwLock::new(OfflineTracker::new())),
|
||||
};
|
||||
let bft_service = Arc::new(BftService::new(client.clone(), key, factory));
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@ ed25519 = { path = "../../substrate/ed25519" }
|
||||
futures = "0.1"
|
||||
tokio = "0.1.7"
|
||||
log = "0.4"
|
||||
rhododendron = "0.2"
|
||||
rhododendron = "0.3"
|
||||
|
||||
@@ -110,3 +110,14 @@ pub type BlockId = generic::BlockId<Block>;
|
||||
#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct Log(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Inherent data to include in a block.
|
||||
#[derive(Encode, Decode)]
|
||||
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<u32>,
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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<Concrete>;
|
||||
@@ -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: 100,
|
||||
spec_version: 101,
|
||||
impl_version: 0,
|
||||
};
|
||||
|
||||
@@ -160,6 +162,7 @@ impl Convert<AccountId, SessionKey> 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()
|
||||
);
|
||||
|
||||
@@ -268,6 +268,7 @@ mod tests {
|
||||
type Header = Header;
|
||||
}
|
||||
impl session::Trait for Test {
|
||||
const NOTE_OFFLINE_POSITION: u32 = 1;
|
||||
type ConvertAccountIdToSessionKey = Identity;
|
||||
type OnSessionChange = ();
|
||||
}
|
||||
|
||||
@@ -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<CandidateReceipt>) -> Vec<UncheckedExtrinsic> {
|
||||
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<UncheckedExtrinsic> {
|
||||
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.
|
||||
|
||||
+15
-10
@@ -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)",
|
||||
]
|
||||
|
||||
@@ -466,7 +466,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"safe-mix 0.1.0",
|
||||
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-runtime-consensus 0.1.0",
|
||||
@@ -652,7 +652,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "safe-mix"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -692,8 +693,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"
|
||||
@@ -766,7 +770,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)",
|
||||
"safe-mix 0.1.0",
|
||||
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -788,7 +792,7 @@ name = "substrate-runtime-democracy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safe-mix 0.1.0",
|
||||
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -867,7 +871,7 @@ name = "substrate-runtime-session"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safe-mix 0.1.0",
|
||||
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -887,7 +891,7 @@ name = "substrate-runtime-staking"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safe-mix 0.1.0",
|
||||
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -932,7 +936,7 @@ name = "substrate-runtime-system"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safe-mix 0.1.0",
|
||||
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -1222,12 +1226,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ceb8ce7a5e520de349e1fa172baeba4a9e8d5ef06c47471863530bc4972ee1e"
|
||||
"checksum rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b03280c2813907a030785570c577fb27d3deec8da4c18566751ade94de0ace"
|
||||
"checksum rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a54aa04a10c68c1c4eacb4337fd883b435997ede17a9385784b990777686b09a"
|
||||
"checksum safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f7bf422d23a88c16d5090d455f182bc99c60af4df6a345c63428acf5129e347"
|
||||
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
|
||||
"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.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"
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -286,7 +286,7 @@ impl<'a, A> txpool::Verifier<UncheckedExtrinsic> 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<Vec<ParaId>> { unimplemented!() }
|
||||
fn parachain_code(&self, _at: &BlockId, _parachain: ParaId) -> Result<Option<Vec<u8>>> { unimplemented!() }
|
||||
fn parachain_head(&self, _at: &BlockId, _parachain: ParaId) -> Result<Option<Vec<u8>>> { unimplemented!() }
|
||||
fn build_block(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec<CandidateReceipt>) -> Result<Self::BlockBuilder> { unimplemented!() }
|
||||
fn inherent_extrinsics(&self, _at: &BlockId, _timestamp: Timestamp, _new_heads: Vec<CandidateReceipt>) -> Result<Vec<Vec<u8>>> { unimplemented!() }
|
||||
fn build_block(&self, _at: &BlockId, _inherent: ::primitives::InherentData) -> Result<Self::BlockBuilder> { unimplemented!() }
|
||||
fn inherent_extrinsics(&self, _at: &BlockId, _inherent: ::primitives::InherentData) -> Result<Vec<Vec<u8>>> { unimplemented!() }
|
||||
|
||||
fn index(&self, _at: &BlockId, _account: AccountId) -> Result<Index> {
|
||||
Ok((_account[0] as u32) + number_of(_at))
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -56,6 +56,7 @@ extern crate error_chain;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
use codec::Encode;
|
||||
use ed25519::LocalizedSignature;
|
||||
@@ -69,7 +70,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 +185,9 @@ pub trait Proposer<B: Block> {
|
||||
/// 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.
|
||||
@@ -207,6 +211,26 @@ struct BftInstance<B: Block, P> {
|
||||
proposer: P,
|
||||
}
|
||||
|
||||
impl<B: Block, P: Proposer<B>> BftInstance<B, P>
|
||||
where
|
||||
B: Clone + Eq,
|
||||
B::Hash: ::std::hash::Hash
|
||||
|
||||
{
|
||||
fn round_timeout_duration(&self, round: usize) -> Duration {
|
||||
const ROUND_INCREMENT_STEP: usize = 10000;
|
||||
|
||||
let round = round / ROUND_INCREMENT_STEP;
|
||||
let round = ::std::cmp::min(63, round) as u32;
|
||||
|
||||
let timeout = 1u64.checked_shl(round)
|
||||
.unwrap_or_else(u64::max_value)
|
||||
.saturating_mul(self.round_timeout_multiplier);
|
||||
|
||||
Duration::from_secs(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Block, P: Proposer<B>> rhododendron::Context for BftInstance<B, P>
|
||||
where
|
||||
B: Clone + Eq,
|
||||
@@ -246,20 +270,39 @@ impl<B: Block, P: Proposer<B>> rhododendron::Context for BftInstance<B, P>
|
||||
}
|
||||
|
||||
fn begin_round_timeout(&self, round: usize) -> Self::RoundTimeout {
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
let round = round / 3;
|
||||
let round = ::std::cmp::min(63, round) as u32;
|
||||
let timeout = 1u64.checked_shl(round)
|
||||
.unwrap_or_else(u64::max_value)
|
||||
.saturating_mul(self.round_timeout_multiplier);
|
||||
|
||||
let fut = Delay::new(Instant::now() + Duration::from_secs(timeout))
|
||||
let timeout = self.round_timeout_duration(round);
|
||||
let fut = Delay::new(Instant::now() + timeout)
|
||||
.map_err(|e| Error::from(ErrorKind::FaultyTimer(e)))
|
||||
.map_err(Into::into);
|
||||
|
||||
Box::new(fut)
|
||||
}
|
||||
|
||||
fn on_advance_round(
|
||||
&self,
|
||||
accumulator: &::rhododendron::Accumulator<B, B::Hash, Self::AuthorityId, Self::Signature>,
|
||||
round: usize,
|
||||
next_round: usize,
|
||||
reason: AdvanceRoundReason,
|
||||
) {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let collect_pubkeys = |participants: HashSet<&Self::AuthorityId>| participants.into_iter()
|
||||
.map(|p| ::ed25519::Public::from_raw(p.0))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let round_timeout = self.round_timeout_duration(next_round);
|
||||
debug!(target: "bft", "Advancing to round {} from {}", next_round, round);
|
||||
debug!(target: "bft", "Participating authorities: {:?}",
|
||||
collect_pubkeys(accumulator.participants()));
|
||||
debug!(target: "bft", "Voting authorities: {:?}",
|
||||
collect_pubkeys(accumulator.voters()));
|
||||
debug!(target: "bft", "Round {} should end in at most {} seconds from now", next_round, round_timeout.as_secs());
|
||||
|
||||
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 +346,9 @@ impl<B, P, I, InStream, OutSink> Future for BftFuture<B, P, I, InStream, OutSink
|
||||
// TODO: handle and log this error in a way which isn't noisy on exit.
|
||||
let committed = try_ready!(self.inner.poll().map_err(|_| ()));
|
||||
|
||||
// if something was committed, the round leader must have proposed.
|
||||
self.inner.context().proposer.on_round_end(committed.round_number, true);
|
||||
|
||||
// If we didn't see the proposal (very unlikely),
|
||||
// we will get the block from the network later.
|
||||
if let Some(justified_block) = committed.candidate {
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Test>;
|
||||
}
|
||||
|
||||
@@ -392,6 +392,7 @@ mod tests {
|
||||
type Header = Header;
|
||||
}
|
||||
impl session::Trait for Test {
|
||||
const NOTE_OFFLINE_POSITION: u32 = 1;
|
||||
type ConvertAccountIdToSessionKey = Identity;
|
||||
type OnSessionChange = staking::Module<Test>;
|
||||
}
|
||||
@@ -495,7 +496,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);
|
||||
});
|
||||
@@ -573,19 +574,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);
|
||||
});
|
||||
}
|
||||
@@ -606,7 +607,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);
|
||||
});
|
||||
@@ -621,7 +622,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);
|
||||
});
|
||||
@@ -639,7 +640,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);
|
||||
});
|
||||
@@ -660,7 +661,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);
|
||||
});
|
||||
@@ -677,7 +678,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);
|
||||
});
|
||||
@@ -698,7 +699,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);
|
||||
});
|
||||
|
||||
@@ -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<Test>;
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
pub trait OnSessionChange<T, A> {
|
||||
/// Session has changed.
|
||||
fn on_session_change(normal_rotation: bool, time_elapsed: T);
|
||||
fn on_session_change(time_elapsed: T, bad_validators: Vec<A>);
|
||||
}
|
||||
|
||||
impl<T> OnSessionChange<T> for () {
|
||||
fn on_session_change(_: bool, _: T) {}
|
||||
impl<T, A> OnSessionChange<T, A> for () {
|
||||
fn on_session_change(_: T, _: Vec<A>) {}
|
||||
}
|
||||
|
||||
pub trait Trait: timestamp::Trait {
|
||||
// the position of the required timestamp-set extrinsic.
|
||||
const NOTE_OFFLINE_POSITION: u32;
|
||||
|
||||
type ConvertAccountIdToSessionKey: Convert<Self::AccountId, Self::SessionKey>;
|
||||
type OnSessionChange: OnSessionChange<Self::Moment>;
|
||||
type OnSessionChange: OnSessionChange<Self::Moment, Self::AccountId>;
|
||||
}
|
||||
|
||||
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<u32>) -> 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<T::AccountId>;
|
||||
|
||||
// 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<T: Trait> Module<T> {
|
||||
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<u32>) -> Result {
|
||||
assert!(aux.is_empty());
|
||||
assert!(
|
||||
<system::Module<T>>::extrinsic_index() == T::NOTE_OFFLINE_POSITION,
|
||||
"note_offline extrinsic must be at position {} in the block",
|
||||
T::NOTE_OFFLINE_POSITION
|
||||
);
|
||||
|
||||
let vs = Self::validators();
|
||||
<BadValidators<T>>::put(offline_val_indices.into_iter().map(|i| vs[i as usize].clone()).collect::<Vec<T::AccountId>>());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// INTERNAL API (available to other runtime modules)
|
||||
|
||||
/// Set the current set of validators.
|
||||
@@ -156,17 +178,15 @@ impl<T: Trait> Module<T> {
|
||||
// check block number and call next_session if necessary.
|
||||
let block_number = <system::Module<T>>::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);
|
||||
<ForcingNewSession<T>>::kill();
|
||||
} else if is_final_block || broken_validation {
|
||||
Self::rotate_session(!broken_validation, is_final_block);
|
||||
let bad_validators = <BadValidators<T>>::take().unwrap_or_default();
|
||||
let should_end_session = <ForcingNewSession<T>>::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<T::AccountId>) {
|
||||
let now = <timestamp::Module<T>>::get();
|
||||
let time_elapsed = now.clone() - Self::current_start();
|
||||
|
||||
@@ -186,7 +206,7 @@ impl<T: Trait> Module<T> {
|
||||
<LastLengthChange<T>>::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);
|
||||
|
||||
@@ -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<T: Trait> Module<T> {
|
||||
|
||||
/// 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<T::AccountId>) {
|
||||
let session_index = <session::Module<T>>::current_index();
|
||||
let early_exit_era = !bad_validators.is_empty();
|
||||
|
||||
if early_exit_era {
|
||||
// slash
|
||||
let slash = Self::current_slash() + Self::early_era_slash();
|
||||
<CurrentSlash<T>>::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.
|
||||
<CurrentSlash<T>>::kill();
|
||||
|
||||
if normal_rotation {
|
||||
// reward
|
||||
let ideal_elapsed = <session::Module<T>>::ideal_session_duration();
|
||||
let per65536: u64 = (T::Moment::sa(65536u64) * ideal_elapsed.clone() / actual_elapsed.max(ideal_elapsed)).as_();
|
||||
@@ -609,25 +632,10 @@ impl<T: Trait> Module<T> {
|
||||
let _ = Self::reward(v, safe_mul_rational(Self::voting_balance(v)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// slash
|
||||
let early_era_slash = Self::early_era_slash();
|
||||
for v in <session::Module<T>>::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 <ForcingNewEra<T>>::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<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
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<T: Trait> Module<T> {
|
||||
let mut intentions = <Intentions<T>>::get()
|
||||
.into_iter()
|
||||
.map(|v| (Self::voting_balance(&v) + Self::nomination_balance(&v), v))
|
||||
.filter(|&(b, _)| b >= minimum_allowed)
|
||||
.collect::<Vec<_>>();
|
||||
intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1));
|
||||
|
||||
<StakeThreshold<T>>::put(
|
||||
if intentions.len() > 0 {
|
||||
if !intentions.is_empty() {
|
||||
let i = (<ValidatorCount<T>>::get() as usize).min(intentions.len() - 1);
|
||||
intentions[i].0.clone()
|
||||
} else { Zero::zero() }
|
||||
@@ -797,9 +808,9 @@ impl<T: Trait> Executable for Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnSessionChange<T::Moment> for Module<T> {
|
||||
fn on_session_change(normal_rotation: bool, elapsed: T::Moment) {
|
||||
Self::new_session(normal_rotation, elapsed);
|
||||
impl<T: Trait> OnSessionChange<T::Moment, T::AccountId> for Module<T> {
|
||||
fn on_session_change(elapsed: T::Moment, bad_validators: Vec<T::AccountId>) {
|
||||
Self::new_session(elapsed, bad_validators);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
rhododendron = "0.2"
|
||||
rhododendron = "0.3"
|
||||
substrate-bft = { path = "../bft" }
|
||||
substrate-client = { path = "../client" }
|
||||
substrate-codec = { path = "../codec" }
|
||||
|
||||
Reference in New Issue
Block a user