Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
07d82af
Recompile runtime.
gavofyork Jun 22, 2018
d467109
Introduce and enforce block time
gavofyork Jun 22, 2018
f714307
Introduce early session ending.
gavofyork Jun 22, 2018
19fe55f
Report most of staking module
gavofyork Jun 23, 2018
b8f2689
rewards, proper early exit and slashing
gavofyork Jun 23, 2018
5ac27b5
Merge remote-tracking branch 'origin/master' into gav-staking-rewards
gavofyork Jun 24, 2018
2ac1ecd
Fix build & session logic, introduce tests
gavofyork Jun 24, 2018
bd41bbc
Fixed staking tests.
gavofyork Jun 24, 2018
9016bc6
Initial test for reward
gavofyork Jun 25, 2018
7a11706
Merge remote-tracking branch 'origin/master' into gav-staking-rewards
gavofyork Jun 25, 2018
f679c22
Fix test
gavofyork Jun 25, 2018
4fb942f
Tests for slashing
gavofyork Jun 25, 2018
4b19d28
Merge remote-tracking branch 'origin/master' into gav-staking-rewards
gavofyork Jun 25, 2018
1ac443b
Update/fix preset configs
gavofyork Jun 25, 2018
31284c3
Fix some tests.
gavofyork Jun 25, 2018
bc9ad04
Fix some staking tests
gavofyork Jun 25, 2018
bd1b4a4
Minor fix
gavofyork Jun 25, 2018
cdfa86b
minor cleanups
gavofyork Jun 25, 2018
3ce3ca7
Nominating.
gavofyork Jun 25, 2018
c2f95ca
Slash/reward nominators
gavofyork Jun 25, 2018
447538c
Tests for nominating + slash/reward
gavofyork Jun 25, 2018
8cd7914
Merge branch 'master' into gav-staking-rewards
gavofyork Jun 26, 2018
353f51c
Merge branch 'gav-staking-rewards' into gav-delegation
gavofyork Jun 26, 2018
fe75f9a
Fix build
gavofyork Jun 26, 2018
75e160f
Merge branch 'gav-staking-rewards' into gav-delegation
gavofyork Jun 26, 2018
5b92dc1
Rename timestamp::Value -> Moment
gavofyork Jun 27, 2018
924ca4b
Merge remote-tracking branch 'origin/master' into gav-staking-rewards
gavofyork Jun 27, 2018
a3e8fe2
Merge branch 'gav-staking-rewards' into gav-delegation
gavofyork Jun 27, 2018
217ecc1
Avoid double-nominating/staking
gavofyork Jun 28, 2018
71f2a9e
Fix comment
gavofyork Jun 28, 2018
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
Prev Previous commit
Next Next commit
rewards, proper early exit and slashing
  • Loading branch information
gavofyork committed Jun 23, 2018
commit b8f2689a357ade9724fb11ff8e709b98c616c3b4
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
41 changes: 33 additions & 8 deletions substrate/runtime/session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,19 @@ use primitives::traits::{Zero, One, RefInto, Executable, Convert, As};
use runtime_support::{StorageValue, StorageMap};
use runtime_support::dispatch::Result;

/// A session has changed.
pub trait OnSessionChange<T> {
/// Session has changed.
fn on_session_change(normal_rotation: bool, time_elapsed: T);
}

impl<T> OnSessionChange<T> for () {
fn on_session_change(_: bool, _: T) {}
}

pub trait Trait: timestamp::Trait {
type ConvertAccountIdToSessionKey: Convert<Self::AccountId, Self::SessionKey>;
type OnSessionChange: OnSessionChange<Self::Value>;
}

decl_module! {
Expand All @@ -65,7 +76,7 @@ decl_module! {
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum PrivCall {
fn set_length(new: T::BlockNumber) -> Result = 0;
fn force_new_session() -> Result = 1;
fn force_new_session(normal_rotation: bool) -> Result = 1;
}
}
decl_storage! {
Expand Down Expand Up @@ -114,8 +125,8 @@ impl<T: Trait> Module<T> {
}

/// Forces a new session.
fn force_new_session() -> Result {
Self::rotate_session();
fn force_new_session(normal_rotation: bool) -> Result {
Self::rotate_session(normal_rotation);
Ok(())
}

Expand All @@ -138,16 +149,20 @@ impl<T: Trait> Module<T> {
// new set.
// check block number and call next_session if necessary.
let block_number = <system::Module<T>>::block_number();
if ((block_number - Self::last_length_change()) % Self::length()).is_zero() || Self::broken_validation() {
Self::rotate_session();
let is_final_block = ((block_number - Self::last_length_change()) % Self::length()).is_zero();
if is_final_block || Self::broken_validation() {
Self::rotate_session(is_final_block);
}
}

/// Move onto next session: register the new authority set.
pub fn rotate_session() {
pub fn rotate_session(normal_rotation: bool) {
let now = <timestamp::Module<T>>::get();
let time_elapsed = now.clone() - Self::current_start();

// Increment current session index.
<CurrentIndex<T>>::put(<CurrentIndex<T>>::get() + One::one());
<CurrentStart<T>>::put(<timestamp::Module<T>>::get());
<CurrentStart<T>>::put(now);

// Enact era length change.
if let Some(next_len) = <NextSessionLength<T>>::take() {
Expand All @@ -156,6 +171,8 @@ impl<T: Trait> Module<T> {
<LastLengthChange<T>>::put(block_number);
}

T::OnSessionChange::on_session_change(normal_rotation, time_elapsed);

// Update any changes in session keys.
Self::validators().iter().enumerate().for_each(|(i, v)| {
if let Some(n) = <NextKeyFor<T>>::take(v) {
Expand All @@ -164,6 +181,13 @@ impl<T: Trait> Module<T> {
});
}

/// Get the time that should have elapsed over a session if everything was working perfectly.
pub fn ideal_session_duration() -> T::Value {
let block_period = <timestamp::Module<T>>::block_period();
let session_length = <T::Value as As<T::BlockNumber>>::sa(Self::length());
session_length * block_period
}

/// Returns `true` if the current validator set is taking took long to validate blocks.
pub fn broken_validation() -> bool {
let now = <timestamp::Module<T>>::get();
Expand All @@ -174,7 +198,7 @@ impl<T: Trait> Module<T> {
false
} else {
let blocks_remaining = <T::Value as As<T::BlockNumber>>::sa(blocks_remaining);
Self::current_start() + blocks_remaining * block_period > now
now + blocks_remaining * block_period > Self::current_start() + Self::ideal_session_duration() * T::Value::sa(13) / T::Value::sa(10) // if we're 30% behind schedule then end abnormally
}
}
}
Expand Down Expand Up @@ -251,6 +275,7 @@ mod tests {
}
impl Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = ();
}

type System = system::Module<Test>;
Expand Down
55 changes: 46 additions & 9 deletions substrate/runtime/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,13 @@ use rstd::collections::btree_map::BTreeMap;
use codec::{Input, Slicable};
use runtime_support::{StorageValue, StorageMap, Parameter};
use runtime_support::dispatch::Result;
use session::OnSessionChange;
use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment,
As, AuxLookup, Hashing as HashingT, Member};
use address::Address as RawAddress;

pub mod address;
#[cfg(feature = "std")]
mod mock;
#[cfg(test)]
mod tests;
mod genesis_config;
mod account_db;
Expand Down Expand Up @@ -167,6 +166,11 @@ decl_storage! {
// The fee required to create a contract. At least as big as ReclaimRebate.
pub ContractFee get(contract_fee): b"sta:contract_fee" => required T::Balance;

// Maximum reward, per validator, that is provided per acceptable session.
pub SessionReward get(session_reward): b"sta:session_reward" => required T::Balance;
// Slash, per validator that is taken per abnormal era end.
pub EarlyEraSlash get(early_era_slash): b"sta:early_era_slash" => required T::Balance;

// The current era index.
pub CurrentEra get(current_era): b"sta:era" => required T::BlockNumber;
// All the accounts with a desire to stake.
Expand Down Expand Up @@ -337,8 +341,7 @@ impl<T: Trait> Module<T> {

/// Force there to be a new era. This also forces a new session immediately after.
fn force_new_era() -> Result {
Self::new_era();
<session::Module<T>>::rotate_session();
<session::Module<T>>::rotate_session(false);
Ok(())
}

Expand Down Expand Up @@ -389,6 +392,17 @@ impl<T: Trait> Module<T> {
}
}

/// Adds up to `value` to the free balance of `who`.
///
/// If `who` doesn't exist, nothing is done and an Err returned.
pub fn reward(who: &T::AccountId, value: T::Balance) -> Result {
if Self::voting_balance(who).is_zero() {
return Err("beneficiary account must pre-exist");
}
Self::set_free_balance(who, Self::free_balance(who) + value);
Ok(())
}

/// Moves `value` from balance to reserved balance.
///
/// If the free balance is lower than `value`, then no funds will be moved and an `Err` will
Expand Down Expand Up @@ -463,10 +477,28 @@ impl<T: Trait> Module<T> {
}
}

/// Hook to be called after to transaction processing.
pub fn check_new_era() {
// check block number and call new_era if necessary.
if (<system::Module<T>>::block_number() - Self::last_era_length_change()) % Self::era_length() == Zero::zero() {
/// 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::Value) {
let session_index = <session::Module<T>>::current_index();

if normal_rotation {
// reward
let ideal_elapsed = <session::Module<T>>::ideal_session_duration();
let percent: usize = (T::Value::sa(100usize) * ideal_elapsed / actual_elapsed).as_();
let reward = Self::session_reward() * T::Balance::sa(percent) / T::Balance::sa(100usize);
// apply good session reward
for v in <session::Module<T>>::validators().iter() {
let _ = Self::reward(v, reward); // will never fail as validator accounts must be created, but even if it did, it's just a missed reward.
}
} else {
// slash
let early_era_slash = Self::early_era_slash();
for v in <session::Module<T>>::validators().iter() {
Self::slash(v, early_era_slash);
}
}
if (session_index % Self::era_length()).is_zero() || !normal_rotation {
Self::new_era();
}
}
Expand Down Expand Up @@ -710,7 +742,12 @@ impl<T: Trait> Module<T> {

impl<T: Trait> Executable for Module<T> {
fn execute() {
Self::check_new_era();
}
}

impl<T: Trait> OnSessionChange<T::Value> for Module<T> {
fn on_session_change(normal_rotation: bool, elapsed: T::Value) {
Self::new_session(normal_rotation, elapsed);
}
}

Expand Down
1 change: 1 addition & 0 deletions substrate/runtime/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl system::Trait for Test {
}
impl session::Trait for Test {
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = Staking;
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
Expand Down
32 changes: 17 additions & 15 deletions substrate/runtime/staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

//! Tests for the module.

#![cfg(test)]

use super::*;
use runtime_io::with_externalities;
use mock::*;
Expand Down Expand Up @@ -111,44 +113,44 @@ fn staking_should_work() {
assert_ok!(Staking::stake(&1));
assert_ok!(Staking::stake(&2));
assert_ok!(Staking::stake(&4));
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![10, 20]);

// Block 2: New validator set now.
System::set_block_number(2);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![4, 2]);

// Block 3: Unstake highest, introduce another staker. No change yet.
System::set_block_number(3);
assert_ok!(Staking::stake(&3));
assert_ok!(Staking::unstake(&4));
Staking::check_new_era();
Session::check_rotate_session();

// Block 4: New era - validators change.
System::set_block_number(4);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![3, 2]);

// Block 5: Transfer stake from highest to lowest. No change yet.
System::set_block_number(5);
assert_ok!(Staking::transfer(&4, 1.into(), 40));
Staking::check_new_era();
Session::check_rotate_session();

// Block 6: Lowest now validator.
System::set_block_number(6);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 3]);

// Block 7: Unstake three. No change yet.
System::set_block_number(7);
assert_ok!(Staking::unstake(&3));
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 3]);

// Block 8: Back to one and two.
System::set_block_number(8);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 2]);
});
}
Expand All @@ -163,50 +165,50 @@ fn staking_eras_work() {

// Block 1: No change.
System::set_block_number(1);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::sessions_per_era(), 2);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 0);

// Block 2: Simple era change.
System::set_block_number(2);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::sessions_per_era(), 2);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 1);

// Block 3: Schedule an era length change; no visible changes.
System::set_block_number(3);
assert_ok!(Staking::set_sessions_per_era(3));
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::sessions_per_era(), 2);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 1);

// Block 4: Era change kicks in.
System::set_block_number(4);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 2);

// Block 5: No change.
System::set_block_number(5);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 2);

// Block 6: No change.
System::set_block_number(6);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 2);

// Block 7: Era increment.
System::set_block_number(7);
Staking::check_new_era();
Session::check_rotate_session();
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 4);
assert_eq!(Staking::current_era(), 3);
Expand Down
Binary file not shown.
Binary file not shown.