Slash Authorities for irrefutable misbehavior (#84)

* double-commit and prepare misbehavior

* get misbehavior on completion

* collect misbehavior on drop, not only on success

* kill unused transaction_index field

* add primitive misbehavior report type

* add misbehavior report transaction

* store prior session

* fix set_items

* basic checks for misbehavior reports

* crate for substrate bft misbehavior checking

* integrate misbehavior check crate

* fix comment

* new wasm binaries

* fix hash in test

* import misbehavior transactions into queue

* fix test build

* sign on digest and full proposal when proposing

* detect proposal misbehavior

* fix fallout

* restore balance/bondage types
This commit is contained in:
Robert Habermeier
2018-03-13 16:39:27 +01:00
committed by GitHub
parent de6e7e9136
commit 27c9e6de9a
34 changed files with 1188 additions and 222 deletions
@@ -31,8 +31,6 @@ pub struct Environment {
pub parent_hash: Hash,
/// The current block digest.
pub digest: Digest,
/// The current transaction index
pub transaction_index: u64,
}
/// Do something with the environment and return its value. Keep the function short.
+1 -2
View File
@@ -21,8 +21,7 @@ use std::collections::HashMap;
use runtime_io::twox_128;
use runtime_support::Hashable;
use primitives::Block;
use polkadot_primitives::{BlockNumber, AccountId};
use runtime::staking::Balance;
use polkadot_primitives::{Balance, BlockNumber, AccountId};
/// Configuration of a general Polkadot genesis block.
pub struct GenesisConfig {
+18 -8
View File
@@ -19,23 +19,33 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate substrate_runtime_std as rstd;
#[macro_use] extern crate substrate_runtime_io as runtime_io;
extern crate substrate_runtime_support as runtime_support;
#[cfg(all(feature = "std", test))] extern crate substrate_keyring as keyring;
#[cfg(feature = "std")] extern crate rustc_hex;
extern crate substrate_codec as codec;
#[cfg(feature = "std")] #[macro_use] extern crate substrate_primitives as primitives;
extern crate substrate_misbehavior_check as misbehavior_check;
extern crate polkadot_primitives;
#[cfg(test)] #[macro_use] extern crate hex_literal;
#[cfg(all(feature = "std", test))]
extern crate substrate_keyring as keyring;
#[cfg(feature = "std")]
extern crate rustc_hex;
#[cfg_attr(any(test, feature = "std"), macro_use)]
extern crate substrate_primitives as primitives;
#[macro_use]
extern crate substrate_runtime_io as runtime_io;
#[cfg(test)]
#[macro_use]
extern crate hex_literal;
pub mod api;
pub mod environment;
pub mod runtime;
#[cfg(feature = "std")] pub mod genesismap;
#[cfg(feature = "std")]
pub mod genesismap;
/// Type definitions and helpers for transactions.
pub mod transaction {
@@ -23,7 +23,7 @@ use polkadot_primitives::SessionKey;
struct AuthorityStorageVec {}
impl StorageVec for AuthorityStorageVec {
type Item = SessionKey;
const PREFIX: &'static[u8] = b":auth:";
const PREFIX: &'static [u8] = b":auth:";
}
/// Get the current set of authorities. These are the session keys.
@@ -37,7 +37,7 @@ pub mod internal {
/// Set the current set of authorities' session keys.
///
/// Called by `next_session` only.
pub fn set_authorities(authorities: &[SessionKey]) {
pub fn set_authorities<'a, I: IntoIterator<Item=&'a SessionKey>>(authorities: I) {
AuthorityStorageVec::set_items(authorities);
}
@@ -25,14 +25,23 @@ use runtime::{system, staking, consensus};
const SESSION_LENGTH: &[u8] = b"ses:len";
const CURRENT_INDEX: &[u8] = b"ses:ind";
const CURRENT_SESSION_START: &[u8] = b"ses:sta";
const LAST_SESSION_START: &[u8] = b"ses:lst";
const LAST_LENGTH_CHANGE: &[u8] = b"ses:llc";
const NEXT_KEY_FOR: &[u8] = b"ses:nxt:";
const NEXT_SESSION_LENGTH: &[u8] = b"ses:nln";
struct ValidatorStorageVec {}
struct ValidatorStorageVec;
impl StorageVec for ValidatorStorageVec {
type Item = AccountId;
const PREFIX: &'static[u8] = b"ses:val:";
const PREFIX: &'static [u8] = b"ses:val:";
}
// the session keys before the previous.
struct LastValidators;
impl StorageVec for LastValidators {
type Item = (AccountId, SessionKey);
const PREFIX: &'static [u8] = b"ses:old:";
}
/// Get the current set of validators.
@@ -50,11 +59,31 @@ pub fn validator_count() -> u32 {
ValidatorStorageVec::count() as u32
}
/// The current era index.
/// The current session index.
pub fn current_index() -> BlockNumber {
storage::get_or(CURRENT_INDEX, 0)
}
/// Get the starting block of the current session.
pub fn current_start_block() -> BlockNumber {
// this seems like it's computable just by examining the current block number, session length,
// and last length change, but it's not simple to tell whether we are before or after
// a session rotation on a block which will have one.
storage::get_or(CURRENT_SESSION_START, 0)
}
/// Get the last session's validators, paired with their authority keys.
pub fn last_session_keys() -> Vec<(AccountId, SessionKey)> {
LastValidators::items()
}
/// Get the start block of the last session.
/// In general this is computable from the session length,
/// but when the current session is the first with a new length it is uncomputable.
pub fn last_session_start() -> Option<BlockNumber> {
storage::get(LAST_SESSION_START)
}
/// The block number at which the era length last changed.
pub fn last_length_change() -> BlockNumber {
storage::get_or(LAST_LENGTH_CHANGE, 0)
@@ -90,11 +119,14 @@ pub mod privileged {
pub mod internal {
use super::*;
/// Set the current set of validators.
/// Transition to a new era, with a new set of valiators.
///
/// Called by staking::next_era() only. `next_session` should be called after this in order to
/// update the session keys to the next validator set.
pub fn set_validators(new: &[AccountId]) {
LastValidators::set_items(
new.iter().cloned().zip(consensus::authorities())
);
ValidatorStorageVec::set_items(new);
consensus::internal::set_authorities(new);
}
@@ -114,7 +146,6 @@ pub mod internal {
fn rotate_session() {
// Increment current session index.
storage::put(CURRENT_INDEX, &(current_index() + 1));
// Enact era length change.
if let Some(next_len) = storage::get::<u64>(NEXT_SESSION_LENGTH) {
storage::put(SESSION_LENGTH, &next_len);
@@ -122,10 +153,23 @@ fn rotate_session() {
storage::kill(NEXT_SESSION_LENGTH);
}
let validators = validators();
storage::put(LAST_SESSION_START, &current_start_block());
storage::put(CURRENT_SESSION_START, &system::block_number());
LastValidators::set_items(
validators.iter()
.cloned()
.zip(consensus::authorities())
);
// Update any changes in session keys.
validators().iter().enumerate().for_each(|(i, v)| {
validators.iter().enumerate().for_each(|(i, v)| {
let k = v.to_keyed_vec(NEXT_KEY_FOR);
if let Some(n) = storage::take(&k) {
// this is fine because the authorities vector currently
// matches the validators length perfectly.
consensus::internal::set_authority(i as u32, &n);
}
});
@@ -22,18 +22,16 @@ use runtime_io::print;
use codec::KeyedVec;
use runtime_support::{storage, StorageVec};
use polkadot_primitives::{BlockNumber, AccountId};
use runtime::{system, session, governance};
use primitives::bft::{MisbehaviorReport, MisbehaviorKind};
use runtime::{system, session, governance, consensus};
/// The balance of an account.
pub type Balance = u64;
/// The amount of bonding period left in an account. Measured in eras.
pub type Bondage = u64;
type Balance = u64;
type Bondage = u64;
struct IntentionStorageVec {}
impl StorageVec for IntentionStorageVec {
type Item = AccountId;
const PREFIX: &'static[u8] = b"sta:wil:";
const PREFIX: &'static [u8] = b"sta:wil:";
}
const BONDING_DURATION: &[u8] = b"sta:loc";
@@ -80,11 +78,16 @@ pub fn balance(who: &AccountId) -> Balance {
storage::get_or_default(&who.to_keyed_vec(BALANCE_OF))
}
/// The liquidity-state of a given account.
/// Gives the index of the era where the account's balance will no longer
/// be bonded.
pub fn bondage(who: &AccountId) -> Bondage {
storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF))
}
fn set_balance(who: &AccountId, amount: Balance) {
storage::put(&who.to_keyed_vec(BALANCE_OF), &amount)
}
// Each identity's stake may be in one of three bondage states, given by an integer:
// - n | n <= current_era(): inactive: free to be transferred.
// - ~0: active: currently representing a validator.
@@ -113,7 +116,7 @@ pub mod public {
pub fn stake(transactor: &AccountId) {
let mut intentions = IntentionStorageVec::items();
// can't be in the list twice.
assert!(intentions.iter().find(|t| *t == transactor).is_none(), "Cannot stake if already staked.");
assert!(intentions.iter().find(|t| t == &transactor).is_none(), "Cannot stake if already staked.");
intentions.push(transactor.clone());
IntentionStorageVec::set_items(&intentions);
storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &u64::max_value());
@@ -132,6 +135,46 @@ pub mod public {
IntentionStorageVec::set_items(&intentions);
storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &(current_era() + bonding_duration()));
}
/// Report misbehavior. Only validators may do this, signing under
/// the authority key of the session the report corresponds to.
///
/// Reports older than one session in the past will be ignored.
pub fn report_misbehavior(transactor: &AccountId, report: &MisbehaviorReport) {
let (validators, authorities) = if report.parent_number < session::last_session_start().unwrap_or(0) {
panic!("report is too old");
} else if report.parent_number < session::current_start_block() {
session::last_session_keys().into_iter().unzip()
} else {
(session::validators(), consensus::authorities())
};
if report.parent_hash != system::block_hash(report.parent_number) {
// report out of chain.
panic!("report not from this blockchain");
}
let reporting_validator = match authorities.iter().position(|x| x == transactor) {
None => panic!("only validators may report"),
Some(pos) => validators.get(pos).expect("validators and authorities have same cardinality; qed"),
};
// any invalidity beyond this point is actually its own misbehavior.
let target = match authorities.iter().position(|x| x == &report.target) {
None => {
slash(reporting_validator, None);
return;
}
Some(pos) => validators.get(pos).expect("validators and authorities have same cardinality; qed"),
};
let misbehaved = ::misbehavior_check::evaluate_misbehavior(&report.target, report.parent_hash, &report.misbehavior);
if misbehaved {
slash(target, Some(reporting_validator))
} else {
slash(reporting_validator, None);
}
}
}
pub mod privileged {
@@ -171,6 +214,23 @@ pub mod internal {
}
}
/// Slash a validator, with an optional benefactor.
fn slash(who: &AccountId, benefactor: Option<&AccountId>) {
// the reciprocal of the proportion of the amount slashed to give
// to the benefactor.
const SLASH_REWARD_DENOMINATOR: Balance = 10;
let slashed = balance(who);
set_balance(who, 0);
if let Some(benefactor) = benefactor {
let reward = slashed / SLASH_REWARD_DENOMINATOR;
let prior = balance(benefactor);
set_balance(benefactor, prior + reward);
}
}
/// The era has changed - enact new staking set.
///
/// NOTE: This always happens immediately before a session change to ensure that new validators
@@ -404,4 +464,31 @@ mod tests {
transfer(&one, &two, 69);
});
}
#[test]
#[should_panic]
fn misbehavior_report_by_non_validator_panics() {
let one = Keyring::One.to_raw_public();
let two = Keyring::Two.to_raw_public();
let mut t: TestExternalities = map![
twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].and(&111u64)
];
with_externalities(&mut t, || {
// the misbehavior report here is invalid, but that
// actually doesn't panic; instead it would slash the bad
// reporter.
report_misbehavior(&one, &MisbehaviorReport {
parent_hash: [0; 32].into(),
parent_number: 0,
target: two,
misbehavior: MisbehaviorKind::BftDoubleCommit(
2,
([1; 32].into(), [2; 64].into()),
([3; 32].into(), [4; 64].into()),
),
})
});
}
}
@@ -167,6 +167,9 @@ fn dispatch_function(function: &Function, transactor: &AccountId) {
Function::StakingTransfer(dest, value) => {
::runtime::staking::public::transfer(transactor, &dest, value);
}
Function::ReportMisbehavior(ref report) => {
::runtime::staking::public::report_misbehavior(transactor, report)
}
Function::SessionSetKey(session) => {
::runtime::session::public::set_key(transactor, &session);
}