Rewards & slashes proportional to the least-slashable validator (#781)

* Initial stuff

* Treasury now mints based on lowest staked.

* Tests build

* Fix tests

* Fix hashes

* Fix tests & grumble
This commit is contained in:
Gav Wood
2018-09-21 09:10:16 +02:00
committed by GitHub
parent ebb688f81f
commit 25393abef8
8 changed files with 137 additions and 56 deletions
+13 -6
View File
@@ -22,9 +22,10 @@ use rstd::prelude::*;
use codec::Encode;
use runtime_support::StorageValue;
use primitives::traits::As;
use primitives;
use primitives::{self, Perbill};
use super::{Trait, Intentions, CurrentEra, OfflineSlashGrace, MinimumValidatorCount,
BondingDuration, SessionsPerEra, ValidatorCount, SessionReward, OfflineSlash};
BondingDuration, SessionsPerEra, ValidatorCount, SessionReward, OfflineSlash,
CurrentSessionReward, CurrentOfflineSlash};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -36,8 +37,10 @@ pub struct GenesisConfig<T: Trait> {
pub validator_count: u32,
pub minimum_validator_count: u32,
pub bonding_duration: T::BlockNumber,
pub session_reward: T::Balance,
pub offline_slash: T::Balance,
pub session_reward: Perbill,
pub offline_slash: Perbill,
pub current_session_reward: T::Balance,
pub current_offline_slash: T::Balance,
pub offline_slash_grace: u32,
}
@@ -50,8 +53,10 @@ impl<T: Trait> Default for GenesisConfig<T> {
validator_count: 0,
minimum_validator_count: 0,
bonding_duration: T::BlockNumber::sa(1000),
session_reward: T::Balance::sa(0),
offline_slash: T::Balance::sa(0),
session_reward: Perbill::from_billionths(60),
offline_slash: Perbill::from_fraction(0.001),
current_session_reward: T::Balance::sa(0),
current_offline_slash: T::Balance::sa(0),
offline_slash_grace: 0,
}
}
@@ -68,6 +73,8 @@ impl<T: Trait> primitives::BuildStorage for GenesisConfig<T> {
Self::hash(<CurrentEra<T>>::key()).to_vec() => self.current_era.encode(),
Self::hash(<SessionReward<T>>::key()).to_vec() => self.session_reward.encode(),
Self::hash(<OfflineSlash<T>>::key()).to_vec() => self.offline_slash.encode(),
Self::hash(<CurrentSessionReward<T>>::key()).to_vec() => self.current_session_reward.encode(),
Self::hash(<CurrentOfflineSlash<T>>::key()).to_vec() => self.current_offline_slash.encode(),
Self::hash(<OfflineSlashGrace<T>>::key()).to_vec() => self.offline_slash_grace.encode()
])
}
+24 -14
View File
@@ -52,7 +52,7 @@ use rstd::cmp;
use runtime_support::{Parameter, StorageValue, StorageMap};
use runtime_support::dispatch::Result;
use session::OnSessionChange;
use primitives::traits::{Zero, One, Bounded, OnFinalise, As};
use primitives::{Perbill, traits::{Zero, One, Bounded, OnFinalise, As}};
use balances::{address::Address, OnDilution};
use system::ensure_signed;
@@ -143,9 +143,9 @@ decl_storage! {
/// The length of a staking era in sessions.
pub SessionsPerEra get(sessions_per_era): required T::BlockNumber;
/// Maximum reward, per validator, that is provided per acceptable session.
pub SessionReward get(session_reward): required T::Balance;
pub SessionReward get(session_reward): required Perbill;
/// Slash, per validator that is taken for the first time they are found to be offline.
pub OfflineSlash get(offline_slash): required T::Balance;
pub OfflineSlash get(offline_slash): required Perbill;
/// Number of instances of offline reports before slashing begins for validators.
pub OfflineSlashGrace get(offline_slash_grace): default u32;
/// The length of the bonding duration in blocks.
@@ -163,6 +163,12 @@ decl_storage! {
pub NominatorsFor get(nominators_for): default map [ T::AccountId => Vec<T::AccountId> ];
/// Nominators for a particular account that is in action right now.
pub CurrentNominatorsFor get(current_nominators_for): default map [ T::AccountId => Vec<T::AccountId> ];
/// Maximum reward, per validator, that is provided per acceptable session.
pub CurrentSessionReward get(current_session_reward): default T::Balance;
/// Slash, per validator that is taken for the first time they are found to be offline.
pub CurrentOfflineSlash get(current_offline_slash): default T::Balance;
/// The next value of sessions per era.
pub NextSessionsPerEra get(next_sessions_per_era): T::BlockNumber;
/// The session index at which the era length last changed.
@@ -423,7 +429,7 @@ impl<T: Trait> Module<T> {
fn this_session_reward(actual_elapsed: T::Moment) -> T::Balance {
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_();
Self::session_reward() * T::Balance::sa(per65536) / T::Balance::sa(65536u64)
Self::current_session_reward() * T::Balance::sa(per65536) / T::Balance::sa(65536u64)
}
/// Session has just changed. We need to determine whether we pay a reward, slash and/or
@@ -438,7 +444,7 @@ impl<T: Trait> Module<T> {
}
Self::deposit_event(RawEvent::Reward(reward));
let total_minted = reward * <T::Balance as As<usize>>::sa(validators.len());
let total_rewarded_stake = Self::stake_range().0 * <T::Balance as As<usize>>::sa(validators.len());
let total_rewarded_stake = Self::stake_range().1 * <T::Balance as As<usize>>::sa(validators.len());
T::OnRewardMinted::on_dilution(total_minted, total_rewarded_stake);
}
@@ -485,14 +491,14 @@ impl<T: Trait> Module<T> {
intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1));
let desired_validator_count = <ValidatorCount<T>>::get() as usize;
<StakeRange<T>>::put(
if !intentions.is_empty() {
let n = cmp::min(desired_validator_count, intentions.len());
(intentions[0].0, intentions[n - 1].0)
} else {
(Zero::zero(), Zero::zero())
}
);
let stake_range = if !intentions.is_empty() {
let n = cmp::min(desired_validator_count, intentions.len());
(intentions[0].0, intentions[n - 1].0)
} else {
(Zero::zero(), Zero::zero())
};
<StakeRange<T>>::put(&stake_range);
let vals = &intentions.into_iter()
.map(|(_, v)| v)
.take(desired_validator_count)
@@ -508,6 +514,10 @@ impl<T: Trait> Module<T> {
<CurrentNominatorsFor<T>>::insert(v, Self::nominators_for(v));
}
<session::Module<T>>::set_validators(vals);
// Update the balances for slashing/rewarding according to the stakes.
<CurrentOfflineSlash<T>>::put(Self::offline_slash().times(stake_range.1));
<CurrentSessionReward<T>>::put(Self::session_reward().times(stake_range.1));
}
}
@@ -547,7 +557,7 @@ impl<T: Trait> consensus::OnOfflineValidator for Module<T> {
let event = if slash_count >= grace {
let instances = slash_count - grace;
let slash = Self::offline_slash() << instances;
let slash = Self::current_offline_slash() << instances;
let next_slash = slash << 1u32;
let _ = Self::slash_validator(&v, slash);
if instances >= Self::validator_preferences(&v).unstake_threshold
+5 -3
View File
@@ -19,7 +19,7 @@
#![cfg(test)]
use primitives::BuildStorage;
use primitives::traits::{Identity};
use primitives::{Perbill, traits::Identity};
use primitives::testing::{Digest, DigestItem, Header};
use substrate_primitives::{H256, Blake2Hasher, RlpCodec};
use runtime_io;
@@ -117,8 +117,10 @@ pub fn new_test_ext(
validator_count: 2,
minimum_validator_count: 0,
bonding_duration: sessions_per_era * session_length * 3,
session_reward: reward,
offline_slash: if monied { 20 } else { 0 },
session_reward: Perbill::from_millionths((1000000 * reward / balance_factor) as u32),
offline_slash: if monied { Perbill::from_percent(40) } else { Perbill::zero() },
current_session_reward: reward,
current_offline_slash: 20,
offline_slash_grace: 0,
}.build_storage().unwrap());
t.extend(timestamp::GenesisConfig::<Test>{
+19 -18
View File
@@ -316,10 +316,10 @@ fn nominating_and_rewards_should_work() {
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 2);
assert_eq!(Session::validators(), vec![3, 2]);
assert_eq!(Balances::total_balance(&1), 12);
assert_eq!(Balances::total_balance(&1), 16);
assert_eq!(Balances::total_balance(&2), 20);
assert_eq!(Balances::total_balance(&3), 40);
assert_eq!(Balances::total_balance(&4), 48);
assert_eq!(Balances::total_balance(&3), 60);
assert_eq!(Balances::total_balance(&4), 64);
System::set_block_number(3);
assert_ok!(Staking::stake(Origin::signed(4)));
@@ -327,17 +327,17 @@ fn nominating_and_rewards_should_work() {
assert_ok!(Staking::nominate(Origin::signed(3), 1.into()));
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![1, 4]);
assert_eq!(Balances::total_balance(&1), 12);
assert_eq!(Balances::total_balance(&2), 30);
assert_eq!(Balances::total_balance(&3), 50);
assert_eq!(Balances::total_balance(&4), 48);
assert_eq!(Balances::total_balance(&1), 16);
assert_eq!(Balances::total_balance(&2), 40);
assert_eq!(Balances::total_balance(&3), 80);
assert_eq!(Balances::total_balance(&4), 64);
System::set_block_number(4);
Session::check_rotate_session(System::block_number());
assert_eq!(Balances::total_balance(&1), 13);
assert_eq!(Balances::total_balance(&2), 30);
assert_eq!(Balances::total_balance(&3), 58);
assert_eq!(Balances::total_balance(&4), 58);
assert_eq!(Balances::total_balance(&1), 26);
assert_eq!(Balances::total_balance(&2), 40);
assert_eq!(Balances::total_balance(&3), 133);
assert_eq!(Balances::total_balance(&4), 128);
});
}
@@ -357,9 +357,9 @@ fn rewards_with_off_the_table_should_work() {
System::set_block_number(2);
assert_ok!(Staking::register_preferences(Origin::signed(1), Staking::intentions().into_iter().position(|i| i == 1).unwrap() as u32, ValidatorPrefs { unstake_threshold: 3, validator_payment: 4 }));
Session::check_rotate_session(System::block_number());
assert_eq!(Balances::total_balance(&1), 16);
assert_eq!(Balances::total_balance(&2), 24);
assert_eq!(Balances::total_balance(&3), 40);
assert_eq!(Balances::total_balance(&1), 22);
assert_eq!(Balances::total_balance(&2), 37);
assert_eq!(Balances::total_balance(&3), 60);
});
}
@@ -393,10 +393,11 @@ fn nominating_slashes_should_work() {
System::set_extrinsic_index(1);
Staking::on_offline_validator(0);
Staking::on_offline_validator(1);
assert_eq!(Balances::total_balance(&1), 0);
assert_eq!(Balances::total_balance(&2), 20);
assert_eq!(Balances::total_balance(&3), 10);
assert_eq!(Balances::total_balance(&4), 30);
assert_eq!(Balances::total_balance(&1), 0); //slashed
assert_eq!(Balances::total_balance(&2), 20); //not slashed
assert_eq!(Balances::total_balance(&3), 10); //slashed
assert_eq!(Balances::total_balance(&4), 30); //slashed
// TODO: change slash % to something sensible.
});
}