Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
New slashing mechanism (#554)
* Slashing improvements

- unstake when balance too low
- unstake after N slashes according to val prefs
- don't early-terminate session/era unless unstaked
- offline grace period before punishment

* Fix warning

* Cleanups and ensure slash_count decays

* Bump authoring version and introduce needed authoring stub

* Rename

* Fix offline tracker

* Fix offline tracker

* Renames

* Add test

* Tests

* Tests.
  • Loading branch information
gavofyork committed Aug 15, 2018
commit b06b022cb3e56f843e3bea5099336d1cbb83abd0
2 changes: 1 addition & 1 deletion demo/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ impl Convert<AccountId, SessionKey> for SessionKeyConversion {
}

impl session::Trait for Concrete {
const NOTE_OFFLINE_POSITION: u32 = 1;
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type ConvertAccountIdToSessionKey = SessionKeyConversion;
type OnSessionChange = Staking;
}
Expand Down
167 changes: 167 additions & 0 deletions polkadot/consensus/src/offline_tracker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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};

struct Observed {
last_round_end: Instant,
offline_since: Instant,
}

#[derive(Eq, PartialEq)]
enum Activity {
Offline,
StillOffline(Duration),
Online,
}

impl Observed {
fn new() -> Observed {
let now = Instant::now();
Observed {
last_round_end: now,
offline_since: now,
}
}

fn note_round_end(&mut self, now: Instant, was_online: Option<bool>) {
self.last_round_end = now;
if let Some(false) = was_online {
self.offline_since = now;
}
}

/// Returns what we have observed about the online/offline state of the validator.
fn activity(&self) -> Activity {
// can happen if clocks are not monotonic
if self.offline_since > self.last_round_end { return Activity::Online }
if self.offline_since == self.last_round_end { return Activity::Offline }
Activity::StillOffline(self.last_round_end.duration_since(self.offline_since))
}
}

/// Tracks offline validators and can issue a report for those offline.
pub struct OfflineTracker {
observed: HashMap<AccountId, Observed>,
block_instant: Instant,
}

impl OfflineTracker {
/// Create a new tracker.
pub fn new() -> Self {
OfflineTracker { observed: HashMap::new(), block_instant: Instant::now() }
}

/// 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));

self.block_instant = Instant::now();
}

/// 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);
for (val, obs) in self.observed.iter_mut() {
obs.note_round_end(
self.block_instant,
if val == &validator {
Some(was_online)
} else {
None
}
)
}
}

/// 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_known_offline_now(v) {
Some(i as u32)
} else {
None
})
.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.
self.is_known_offline_now(v)
})
}

/// Rwturns true only if we have seen the validator miss the last round. For further
/// rounds where we can't say for sure that they're still offline, we give them the
/// benefit of the doubt.
fn is_known_offline_now(&self, v: &AccountId) -> bool {
self.observed.get(v).map(|o| o.activity() == Activity::Offline).unwrap_or(false)
}
}

#[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_new_block(&[v, v2, v3]);
tracker.note_round_end(v, true);
tracker.note_round_end(v2, true);
tracker.note_round_end(v3, true);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0u32; 0]);

tracker.note_new_block(&[v, v2, v3]);
tracker.note_round_end(v, true);
tracker.note_round_end(v2, false);
tracker.note_round_end(v3, true);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![1]);

tracker.note_new_block(&[v, v2, v3]);
tracker.note_round_end(v, false);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);

tracker.note_new_block(&[v, v2, v3]);
tracker.note_round_end(v, false);
tracker.note_round_end(v2, true);
tracker.note_round_end(v3, false);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 2]);

tracker.note_new_block(&[v, v2]);
tracker.note_round_end(v, false);
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);
}
}
118 changes: 118 additions & 0 deletions polkadot/runtime/src/checked_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

//! Typesafe block interaction.

use super::{Call, Block, TIMESTAMP_SET_POSITION, PARACHAINS_SET_POSITION, NOTE_MISSED_PROPOSAL_POSITION};
use timestamp::Call as TimestampCall;
use parachains::Call as ParachainsCall;
use staking::Call as StakingCall;
use primitives::parachain::CandidateReceipt;

/// Provides a type-safe wrapper around a structurally valid block.
pub struct CheckedBlock {
inner: Block,
file_line: Option<(&'static str, u32)>,
}

impl CheckedBlock {
/// Create a new checked block. Fails if the block is not structurally valid.
pub fn new(block: Block) -> Result<Self, Block> {
let has_timestamp = block.extrinsics.get(TIMESTAMP_SET_POSITION as usize).map_or(false, |xt| {
!xt.is_signed() && match xt.extrinsic.function {
Call::Timestamp(TimestampCall::set(_)) => true,
_ => false,
}
});

if !has_timestamp { return Err(block) }

let has_heads = block.extrinsics.get(PARACHAINS_SET_POSITION as usize).map_or(false, |xt| {
!xt.is_signed() && match xt.extrinsic.function {
Call::Parachains(ParachainsCall::set_heads(_)) => true,
_ => false,
}
});

if !has_heads { return Err(block) }

Ok(CheckedBlock {
inner: block,
file_line: None,
})
}

// Creates a new checked block, asserting that it is valid.
#[doc(hidden)]
pub fn new_unchecked(block: Block, file: &'static str, line: u32) -> Self {
CheckedBlock {
inner: block,
file_line: Some((file, line)),
}
}

/// Extract the timestamp from the block.
pub fn timestamp(&self) -> ::primitives::Timestamp {
let x = self.inner.extrinsics.get(TIMESTAMP_SET_POSITION as usize).and_then(|xt| match xt.extrinsic.function {
Call::Timestamp(TimestampCall::set(x)) => Some(x),
_ => None
});

match x {
Some(x) => x,
None => panic!("Invalid polkadot block asserted at {:?}", self.file_line),
}
}

/// Extract the parachain heads from the block.
pub fn parachain_heads(&self) -> &[CandidateReceipt] {
let x = self.inner.extrinsics.get(PARACHAINS_SET_POSITION as usize).and_then(|xt| match xt.extrinsic.function {
Call::Parachains(ParachainsCall::set_heads(ref x)) => Some(&x[..]),
_ => None
});

match x {
Some(x) => x,
None => panic!("Invalid polkadot block asserted at {:?}", self.file_line),
}
}

/// Extract the noted offline validator indices (if any) from the block.
pub fn noted_offline(&self) -> &[u32] {
self.inner.extrinsics.get(NOTE_MISSED_PROPOSAL_POSITION as usize).and_then(|xt| match xt.extrinsic.function {
Call::Staking(StakingCall::note_missed_proposal(ref x)) => Some(&x[..]),
_ => None,
}).unwrap_or(&[])
}

/// Convert into inner block.
pub fn into_inner(self) -> Block { self.inner }
}

impl ::std::ops::Deref for CheckedBlock {
type Target = Block;

fn deref(&self) -> &Block { &self.inner }
}

/// Assert that a block is structurally valid. May lead to panic in the future
/// in case it isn't.
#[macro_export]
macro_rules! assert_polkadot_block {
($block: expr) => {
$crate::CheckedBlock::new_unchecked($block, file!(), line!())
}
}
Loading