Introduce Runtime Events (#607)

* Squashed 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.
Remove accidental merge files.
Merge remote-tracking branch 'origin/master' into gav-new-pos
Version bump, fixes (#572)  …
* Bump version, don't propose invalid blocks
* Fix build.
* Fixes.
* More fixes.
* Fix tests.
* Fix more tests
* More tests fixed
Fix merge
Fix accidental merge bug
Fixes.
Staking failsafes  …
- Don't slash/unstake/change session when too few staking participants
- Introduce set_balance PrivCall
Make minimum validator count dynamic.
test fixes
Fix tests.
Fix tests
Fix tests, update readme.
Merge remote-tracking branch 'origin/master' into gav-new-pos
Test with release.
Use safe math when dealing with total stake
Fix test again.
Introduce events into runtime.
Fix tests
Add events for account new/reap
Integration-style tests for events.

* Remove old code
This commit is contained in:
Gav Wood
2018-08-28 12:11:40 +01:00
committed by GitHub
parent f5c684dca6
commit 278a054b81
26 changed files with 518 additions and 86 deletions
@@ -83,3 +83,26 @@ macro_rules! assert_ok {
assert_eq!($x, Ok($y));
}
}
#[macro_export]
macro_rules! impl_outer_event {
($(#[$attr:meta])* pub enum $name:ident for $trait:ident { $( $module:ident ),* }) => {
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
$(#[$attr])*
#[allow(non_camel_case_types)]
pub enum $name {
$(
$module($module::Event<$trait>),
)*
}
$(
impl From<$module::Event<$trait>> for $name {
fn from(x: $module::Event<$trait>) -> Self {
$name::$module(x)
}
}
)*
}
}
@@ -69,7 +69,7 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
if let staking::UpdateBalanceOutcome::AccountKilled =
staking::Module::<T>::set_free_balance_creating(&address, balance)
{
// Account killed. This will ultimately lead to calling `OnAccountKill` callback
// Account killed. This will ultimately lead to calling `OnFreeBalanceZero` callback
// which will make removal of CodeOf and StorageOf for this account.
// In order to avoid writing over the deleted properties we `continue` here.
continue;
@@ -247,8 +247,8 @@ impl<T: Trait> Module<T> {
}
}
impl<T: Trait> staking::OnAccountKill<T::AccountId> for Module<T> {
fn on_account_kill(who: &T::AccountId) {
impl<T: Trait> staking::OnFreeBalanceZero<T::AccountId> for Module<T> {
fn on_free_balance_zero(who: &T::AccountId) {
<CodeOf<T>>::remove(who);
<StorageOf<T>>::remove_prefix(who.clone());
}
@@ -44,20 +44,23 @@ impl system::Trait for Test {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = ();
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl staking::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type Balance = u64;
type AccountIndex = u64;
type OnAccountKill = Contract;
type OnFreeBalanceZero = Contract;
type Event = ();
}
impl session::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = Staking;
type Event = ();
}
impl Trait for Test {
type Gas = u64;
@@ -89,7 +92,6 @@ fn new_test_ext(existential_deposit: u64, gas_price: u64) -> runtime_io::TestExt
session::GenesisConfig::<Test> {
session_length: 1,
validators: vec![10, 20],
broken_percent_late: 100,
}.build_storage()
.unwrap(),
);
@@ -651,16 +651,19 @@ mod tests {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = ();
}
impl session::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = staking::Module<Test>;
type Event = ();
}
impl staking::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type Balance = u64;
type AccountIndex = u64;
type OnAccountKill = ();
type OnFreeBalanceZero = ();
type Event = ();
}
impl democracy::Trait for Test {
type Proposal = Proposal;
@@ -680,7 +683,6 @@ mod tests {
t.extend(session::GenesisConfig::<Test>{
session_length: 1, //??? or 2?
validators: vec![10, 20],
broken_percent_late: 100,
}.build_storage().unwrap());
t.extend(staking::GenesisConfig::<Test>{
sessions_per_era: 1,
@@ -393,16 +393,19 @@ mod tests {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = ();
}
impl session::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = staking::Module<Test>;
type Event = ();
}
impl staking::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type Balance = u64;
type AccountIndex = u64;
type OnAccountKill = ();
type OnFreeBalanceZero = ();
type Event = ();
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
@@ -421,7 +424,6 @@ mod tests {
t.extend(session::GenesisConfig::<Test>{
session_length: 1, //??? or 2?
validators: vec![10, 20],
broken_percent_late: 100,
}.build_storage().unwrap());
t.extend(staking::GenesisConfig::<Test>{
sessions_per_era: 1,
@@ -8,6 +8,7 @@ hex-literal = "0.1.0"
serde = { version = "1.0", default_features = false }
serde_derive = { version = "1.0", optional = true }
substrate-codec = { path = "../../codec", default_features = false }
substrate-codec-derive = { path = "../../codec/derive", default_features = false }
substrate-runtime-std = { path = "../../runtime-std", default_features = false }
substrate-runtime-io = { path = "../../runtime-io", default_features = false }
substrate-runtime-support = { path = "../../runtime-support", default_features = false }
@@ -29,6 +30,7 @@ std = [
"serde/std",
"serde_derive",
"substrate-codec/std",
"substrate-codec-derive/std",
"substrate-runtime-primitives/std",
"substrate-runtime-io/std",
"substrate-runtime-system/std",
@@ -24,12 +24,19 @@ extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate substrate_runtime_std as rstd;
#[cfg(test)]
#[macro_use]
extern crate substrate_codec_derive;
#[cfg_attr(test, macro_use)]
extern crate substrate_runtime_support as runtime_support;
extern crate substrate_runtime_std as rstd;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_codec as codec;
extern crate substrate_runtime_primitives as primitives;
extern crate substrate_runtime_system as system;
#[cfg(test)]
extern crate substrate_runtime_timestamp as timestamp;
@@ -52,7 +59,6 @@ extern crate substrate_runtime_staking as staking;
use rstd::prelude::*;
use rstd::marker::PhantomData;
use rstd::result;
use runtime_support::StorageValue;
use primitives::traits::{self, Header, Zero, One, Checkable, Applyable, CheckEqual, Executable,
MakePayment, Hash, AuxLookup};
use codec::{Codec, Encode};
@@ -125,6 +131,7 @@ impl<
extrinsics.into_iter().for_each(Self::apply_extrinsic_no_note);
// post-transactional book-keeping.
<system::Module<System>>::note_finished_extrinsics();
Finalisation::execute();
// any final checks
@@ -134,6 +141,7 @@ impl<
/// Finalise the block - it is up the caller to ensure that all header fields are valid
/// except state-root.
pub fn finalise_block() -> System::Header {
<system::Module<System>>::note_finished_extrinsics();
Finalisation::execute();
// setup extrinsics
@@ -194,7 +202,7 @@ impl<
// decode parameters and dispatch
let r = xt.apply();
<system::ExtrinsicIndex<System>>::put(<system::ExtrinsicIndex<System>>::get() + 1u32);
<system::Module<System>>::note_applied_extrinsic();
r.map(|_| internal::ApplyOutcome::Success).or_else(|e| Ok(internal::ApplyOutcome::Fail(e)))
}
@@ -232,6 +240,12 @@ mod tests {
}
}
impl_outer_event!{
pub enum MetaEvent for Test {
session, staking
}
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct Test;
@@ -250,16 +264,19 @@ mod tests {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = MetaEvent;
}
impl session::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = staking::Module<Test>;
type Event = MetaEvent;
}
impl staking::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type Balance = u64;
type AccountIndex = u64;
type OnAccountKill = ();
type OnFreeBalanceZero = ();
type Event = MetaEvent;
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
@@ -319,7 +336,7 @@ mod tests {
// Blake
// state_root: hex!("02532989c613369596025dfcfc821339fc9861987003924913a5a1382f87034a").into(),
// Keccak
state_root: hex!("ed456461b82664990b6ebd1caf1360056f6e8a062e73fada331e1c92cd81cad4").into(),
state_root: hex!("246ea6d86eefe3fc32f746fdcb1749a5f245570c70a04b43d08b5defac44505a").into(),
extrinsics_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(),
digest: Digest { logs: vec![], },
},
@@ -353,7 +370,7 @@ mod tests {
header: Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: hex!("ed456461b82664990b6ebd1caf1360056f6e8a062e73fada331e1c92cd81cad4").into(),
state_root: hex!("246ea6d86eefe3fc32f746fdcb1749a5f245570c70a04b43d08b5defac44505a").into(),
extrinsics_root: [0u8; 32].into(),
digest: Digest { logs: vec![], },
},
@@ -369,7 +386,7 @@ mod tests {
with_externalities(&mut t, || {
Executive::initialise_block(&Header::new(1, H256::default(), H256::default(), [69u8; 32].into(), Digest::default()));
assert!(Executive::apply_extrinsic(xt).is_err());
assert_eq!(<system::Module<Test>>::extrinsic_index(), 0);
assert_eq!(<system::Module<Test>>::extrinsic_index(), Some(0));
});
}
}
@@ -104,6 +104,10 @@ pub struct Identity;
impl<T> Convert<T, T> for Identity {
fn convert(a: T) -> T { a }
}
pub struct Empty;
impl<T> Convert<T, ()> for Empty {
fn convert(_: T) -> () { () }
}
pub trait MaybeEmpty {
fn is_empty(&self) -> bool;
@@ -10,6 +10,7 @@ serde_derive = { version = "1.0", optional = true }
safe-mix = { version = "1.0", default_features = false}
substrate-keyring = { path = "../../keyring", optional = true }
substrate-codec = { path = "../../codec", default_features = false }
substrate-codec-derive = { path = "../../codec/derive", default_features = false }
substrate-primitives = { path = "../../primitives", default_features = false }
substrate-runtime-std = { path = "../../runtime-std", default_features = false }
substrate-runtime-io = { path = "../../runtime-io", default_features = false }
@@ -27,6 +28,7 @@ std = [
"safe-mix/std",
"substrate-keyring",
"substrate-codec/std",
"substrate-codec-derive/std",
"substrate-primitives/std",
"substrate-runtime-std/std",
"substrate-runtime-io/std",
+32 -28
View File
@@ -38,6 +38,9 @@ extern crate substrate_runtime_std as rstd;
#[macro_use]
extern crate substrate_runtime_support as runtime_support;
#[macro_use]
extern crate substrate_codec_derive;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_codec as codec;
extern crate substrate_runtime_primitives as primitives;
@@ -46,7 +49,7 @@ extern crate substrate_runtime_system as system;
extern crate substrate_runtime_timestamp as timestamp;
use rstd::prelude::*;
use primitives::traits::{Zero, One, RefInto, MaybeEmpty, Executable, Convert, As};
use primitives::traits::{Zero, One, RefInto, Executable, Convert, As};
use runtime_support::{StorageValue, StorageMap};
use runtime_support::dispatch::Result;
@@ -64,20 +67,19 @@ impl<T> OnSessionChange<T> for () {
}
pub trait Trait: timestamp::Trait {
// the position of the required note_missed_proposal extrinsic.
const NOTE_MISSED_PROPOSAL_POSITION: u32;
type ConvertAccountIdToSessionKey: Convert<Self::AccountId, Self::SessionKey>;
type OnSessionChange: OnSessionChange<Self::Moment>;
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
pub type Event<T> = RawEvent<<T as system::Trait>::BlockNumber>;
decl_module! {
pub struct Module<T: Trait>;
#[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))]
@@ -87,6 +89,19 @@ decl_module! {
}
}
/// An event in this module.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
pub enum RawEvent<BlockNumber> {
/// New session has happened. Note that the argument is the session index, not the block number
/// as the type might suggest.
NewSession(BlockNumber),
}
impl<N> From<RawEvent<N>> for () {
fn from(_: RawEvent<N>) -> () { () }
}
decl_storage! {
trait Store for Module<T: Trait>;
@@ -98,8 +113,6 @@ decl_storage! {
pub CurrentIndex get(current_index): b"ses:ind" => required T::BlockNumber;
// Timestamp when current session started.
pub CurrentStart get(current_start): b"ses:current_start" => required T::Moment;
// 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.
@@ -147,20 +160,6 @@ 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_MISSED_PROPOSAL_POSITION,
"note_missed_proposal extrinsic must be at position {} in the block",
T::NOTE_MISSED_PROPOSAL_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.
@@ -188,13 +187,21 @@ impl<T: Trait> Module<T> {
}
}
/// Deposit one of this module's events.
fn deposit_event(event: Event<T>) {
<system::Module<T>>::deposit_event(<T as Trait>::Event::from(event).into());
}
/// Move onto next session: register the new authority set.
pub fn rotate_session(is_final_block: bool, apply_rewards: bool) {
let now = <timestamp::Module<T>>::get();
let time_elapsed = now.clone() - Self::current_start();
let session_index = <CurrentIndex<T>>::get() + One::one();
Self::deposit_event(RawEvent::NewSession(session_index));
// Increment current session index.
<CurrentIndex<T>>::put(<CurrentIndex<T>>::get() + One::one());
<CurrentIndex<T>>::put(session_index);
<CurrentStart<T>>::put(now);
// Enact era length change.
@@ -250,7 +257,6 @@ impl<T: Trait> Executable for Module<T> {
pub struct GenesisConfig<T: Trait> {
pub session_length: T::BlockNumber,
pub validators: Vec<T::AccountId>,
pub broken_percent_late: T::Moment,
}
#[cfg(any(feature = "std", test))]
@@ -260,7 +266,6 @@ impl<T: Trait> Default for GenesisConfig<T> {
GenesisConfig {
session_length: T::BlockNumber::sa(1000),
validators: vec![],
broken_percent_late: T::Moment::sa(30),
}
}
}
@@ -276,8 +281,7 @@ impl<T: Trait> primitives::BuildStorage for GenesisConfig<T>
Self::hash(<SessionLength<T>>::key()).to_vec() => self.session_length.encode(),
Self::hash(<CurrentIndex<T>>::key()).to_vec() => T::BlockNumber::sa(0).encode(),
Self::hash(<CurrentStart<T>>::key()).to_vec() => T::Moment::zero().encode(),
Self::hash(<Validators<T>>::key()).to_vec() => self.validators.encode(),
Self::hash(<BrokenPercentLate<T>>::key()).to_vec() => self.broken_percent_late.encode()
Self::hash(<Validators<T>>::key()).to_vec() => self.validators.encode()
])
}
}
@@ -308,15 +312,16 @@ mod tests {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = ();
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = ();
type Event = ();
}
type System = system::Module<Test>;
@@ -336,7 +341,6 @@ mod tests {
t.extend(GenesisConfig::<Test>{
session_length: 2,
validators: vec![1, 2, 3],
broken_percent_late: 30,
}.build_storage().unwrap());
t.into()
}
+72 -16
View File
@@ -77,6 +77,12 @@ const RECLAIM_INDEX_MAGIC: usize = 0x69;
pub type Address<T> = RawAddress<<T as system::Trait>::AccountId, <T as Trait>::AccountIndex>;
pub type Event<T> = RawEvent<
<T as Trait>::Balance,
<T as system::Trait>::AccountId,
<T as Trait>::AccountIndex
>;
#[cfg(test)]
#[derive(Debug, PartialEq, Clone)]
pub enum LockStatus<BlockNumber: Debug + PartialEq + Clone> {
@@ -94,13 +100,13 @@ pub enum LockStatus<BlockNumber: PartialEq + Clone> {
}
/// The account was the given id was killed.
pub trait OnAccountKill<AccountId> {
pub trait OnFreeBalanceZero<AccountId> {
/// The account was the given id was killed.
fn on_account_kill(who: &AccountId);
fn on_free_balance_zero(who: &AccountId);
}
impl<AccountId> OnAccountKill<AccountId> for () {
fn on_account_kill(_who: &AccountId) {}
impl<AccountId> OnFreeBalanceZero<AccountId> for () {
fn on_free_balance_zero(_who: &AccountId) {}
}
/// Preference of what happens on a slash event.
@@ -121,17 +127,21 @@ impl Default for SlashPreference {
pub trait Trait: system::Trait + session::Trait {
/// The allowed extrinsic position for `missed_proposal` inherent.
// const NOTE_MISSED_PROPOSAL_POSITION: u32; // TODO: uncomment when removed from session::Trait
const NOTE_MISSED_PROPOSAL_POSITION: u32;
/// The balance of an account.
type Balance: Parameter + SimpleArithmetic + Codec + Default + Copy + As<Self::AccountIndex> + As<usize> + As<u64>;
/// Type used for storing an account's index; implies the maximum number of accounts the system
/// can hold.
type AccountIndex: Parameter + Member + Codec + SimpleArithmetic + As<u8> + As<u16> + As<u32> + As<u64> + As<usize> + Copy;
/// A function which is invoked when the given account is dead.
/// A function which is invoked when the free-balance has fallen below the existential deposit and
/// has been reduced to zero.
///
/// Gives a chance to clean up resources associated with the given account.
type OnAccountKill: OnAccountKill<Self::AccountId>;
type OnFreeBalanceZero: OnFreeBalanceZero<Self::AccountId>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
decl_module! {
@@ -159,6 +169,26 @@ decl_module! {
}
}
/// An event in this module.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
pub enum RawEvent<Balance, AccountId, AccountIndex> {
/// All validators have been rewarded by the given balance.
Reward(Balance),
/// One validator (and their nominators) has been given a offline-warning (they're still within
/// their grace). The accrued number of slashes is recorded, too.
OfflineWarning(AccountId, u32),
/// One validator (and their nominators) has been slashed by the given amount.
OfflineSlash(AccountId, Balance),
/// A new account was created.
NewAccount(AccountId, AccountIndex, NewAccountOutcome),
/// An account was reaped.
ReapedAccount(AccountId),
}
impl<B, A, I> From<RawEvent<B, A, I>> for () {
fn from(_: RawEvent<B, A, I>) -> () { () }
}
decl_storage! {
trait Store for Module<T: Trait>;
@@ -227,7 +257,7 @@ decl_storage! {
// This is the only balance that matters in terms of most operations on tokens. It is
// alone used to determine the balance when in the contract execution environment. When this
// balance falls below the value of `ExistentialDeposit`, then the "current account" is
// deleted: specifically, `Bondage` and `FreeBalance`. Furthermore, `OnAccountKill` callback
// deleted: specifically, `Bondage` and `FreeBalance`. Furthermore, `OnFreeBalanceZero` callback
// is invoked, giving a chance to external modules to cleanup data associated with
// the deleted account.
//
@@ -253,7 +283,10 @@ decl_storage! {
pub Bondage get(bondage): b"sta:bon:" => default map [ T::AccountId => T::BlockNumber ];
}
enum NewAccountOutcome {
/// Whatever happened about the hint given when creating the new account.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Encode, Decode, PartialEq, Eq, Clone, Copy)]
pub enum NewAccountOutcome {
NoHint,
GoodHint,
BadHint,
@@ -485,7 +518,7 @@ impl<T: Trait> Module<T> {
fn note_missed_proposal(aux: &T::PublicAux, offline_val_indices: Vec<u32>) -> Result {
assert!(aux.is_empty());
assert!(
<system::Module<T>>::extrinsic_index() == T::NOTE_MISSED_PROPOSAL_POSITION,
<system::Module<T>>::extrinsic_index() == Some(T::NOTE_MISSED_PROPOSAL_POSITION),
"note_missed_proposal extrinsic must be at position {} in the block",
T::NOTE_MISSED_PROPOSAL_POSITION
);
@@ -496,7 +529,7 @@ impl<T: Trait> Module<T> {
<SlashCount<T>>::insert(v.clone(), slash_count + 1);
let grace = Self::offline_slash_grace();
if slash_count >= grace {
let event = if slash_count >= grace {
let instances = slash_count - grace;
let slash = Self::early_era_slash() << instances;
let next_slash = slash << 1u32;
@@ -512,7 +545,11 @@ impl<T: Trait> Module<T> {
}
let _ = Self::force_new_era(false);
}
}
RawEvent::OfflineSlash(v, slash)
} else {
RawEvent::OfflineWarning(v, slash_count)
};
Self::deposit_event(event);
}
Ok(())
@@ -520,6 +557,11 @@ impl<T: Trait> Module<T> {
// PRIV DISPATCH
/// Deposit one of this module's events.
fn deposit_event(event: Event<T>) {
<system::Module<T>>::deposit_event(<T as Trait>::Event::from(event).into());
}
/// Set the number of sessions in an era.
fn set_sessions_per_era(new: T::BlockNumber) -> Result {
<NextSessionsPerEra<T>>::put(&new);
@@ -806,6 +848,7 @@ impl<T: Trait> Module<T> {
for v in <session::Module<T>>::validators().iter() {
Self::reward_validator(v, reward);
}
Self::deposit_event(RawEvent::Reward(reward));
}
let session_index = <session::Module<T>>::current_index();
@@ -911,7 +954,9 @@ impl<T: Trait> Module<T> {
try_set[item_index] = who.clone();
<EnumSet<T>>::insert(set_index, try_set);
return NewAccountOutcome::GoodHint;
Self::deposit_event(RawEvent::NewAccount(who.clone(), try_index, NewAccountOutcome::GoodHint));
return NewAccountOutcome::GoodHint
}
}
NewAccountOutcome::BadHint
@@ -931,6 +976,8 @@ impl<T: Trait> Module<T> {
set_index += One::one();
};
let index = T::AccountIndex::sa(set_index.as_() * ENUM_SET_SIZE + set.len());
// update set.
set.push(who.clone());
@@ -942,18 +989,26 @@ impl<T: Trait> Module<T> {
// write set.
<EnumSet<T>>::insert(set_index, set);
Self::deposit_event(RawEvent::NewAccount(who.clone(), index, ret));
ret
}
fn reap_account(who: &T::AccountId) {
<system::AccountNonce<T>>::remove(who);
Self::deposit_event(RawEvent::ReapedAccount(who.clone()));
}
/// Kill an account's free portion.
fn on_free_too_low(who: &T::AccountId) {
Self::decrease_total_stake_by(Self::free_balance(who));
<FreeBalance<T>>::remove(who);
<Bondage<T>>::remove(who);
T::OnAccountKill::on_account_kill(who);
T::OnFreeBalanceZero::on_free_balance_zero(who);
if Self::reserved_balance(who).is_zero() {
<system::AccountNonce<T>>::remove(who);
Self::reap_account(who);
}
}
@@ -961,8 +1016,9 @@ impl<T: Trait> Module<T> {
fn on_reserved_too_low(who: &T::AccountId) {
Self::decrease_total_stake_by(Self::reserved_balance(who));
<ReservedBalance<T>>::remove(who);
if Self::free_balance(who).is_zero() {
<system::AccountNonce<T>>::remove(who);
Self::reap_account(who);
}
}
@@ -43,20 +43,23 @@ impl system::Trait for Test {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = ();
}
impl session::Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 0;
type ConvertAccountIdToSessionKey = Identity;
type OnSessionChange = Staking;
type Event = ();
}
impl timestamp::Trait for Test {
const TIMESTAMP_SET_POSITION: u32 = 0;
type Moment = u64;
}
impl Trait for Test {
const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1;
type Balance = u64;
type AccountIndex = u64;
type OnAccountKill = ();
type OnFreeBalanceZero = ();
type Event = ();
}
pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool, reward: u64) -> runtime_io::TestExternalities<KeccakHasher> {
@@ -73,7 +76,6 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64
t.extend(session::GenesisConfig::<Test>{
session_length,
validators: vec![10, 20],
broken_percent_late: 30,
}.build_storage().unwrap());
t.extend(GenesisConfig::<Test>{
sessions_per_era,
@@ -28,6 +28,7 @@ fn note_null_missed_proposal_should_work() {
assert_eq!(Staking::offline_slash_grace(), 0);
assert_eq!(Staking::slash_count(&10), 0);
assert_eq!(Staking::free_balance(&10), 1);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![]));
assert_eq!(Staking::slash_count(&10), 0);
assert_eq!(Staking::free_balance(&10), 1);
@@ -42,6 +43,7 @@ fn note_missed_proposal_should_work() {
assert_eq!(Staking::offline_slash_grace(), 0);
assert_eq!(Staking::slash_count(&10), 0);
assert_eq!(Staking::free_balance(&10), 70);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0]));
assert_eq!(Staking::slash_count(&10), 1);
assert_eq!(Staking::free_balance(&10), 50);
@@ -56,9 +58,11 @@ fn note_missed_proposal_exponent_should_work() {
assert_eq!(Staking::offline_slash_grace(), 0);
assert_eq!(Staking::slash_count(&10), 0);
assert_eq!(Staking::free_balance(&10), 150);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0]));
assert_eq!(Staking::slash_count(&10), 1);
assert_eq!(Staking::free_balance(&10), 130);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0]));
assert_eq!(Staking::slash_count(&10), 2);
assert_eq!(Staking::free_balance(&10), 90);
@@ -77,12 +81,14 @@ fn note_missed_proposal_grace_should_work() {
assert_eq!(Staking::slash_count(&10), 0);
assert_eq!(Staking::free_balance(&10), 70);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0]));
assert_eq!(Staking::slash_count(&10), 1);
assert_eq!(Staking::free_balance(&10), 70);
assert_eq!(Staking::slash_count(&20), 0);
assert_eq!(Staking::free_balance(&20), 70);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1]));
assert_eq!(Staking::slash_count(&10), 2);
assert_eq!(Staking::free_balance(&10), 50);
@@ -104,11 +110,13 @@ fn note_missed_proposal_force_unstake_session_change_should_work() {
assert_eq!(Staking::intentions(), vec![10, 20, 1]);
assert_eq!(Session::validators(), vec![10, 20]);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0]));
assert_eq!(Staking::free_balance(&10), 50);
assert_eq!(Staking::slash_count(&10), 1);
assert_eq!(Staking::intentions(), vec![10, 20, 1]);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0]));
assert_eq!(Staking::intentions(), vec![1, 20]);
assert_eq!(Staking::free_balance(&10), 10);
@@ -125,23 +133,27 @@ fn note_missed_proposal_auto_unstake_session_change_should_work() {
assert_eq!(Staking::intentions(), vec![10, 20]);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1]));
assert_eq!(Staking::free_balance(&10), 6980);
assert_eq!(Staking::free_balance(&20), 6980);
assert_eq!(Staking::intentions(), vec![10, 20]);
assert!(Staking::forcing_new_era().is_none());
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1]));
assert_eq!(Staking::free_balance(&10), 6940);
assert_eq!(Staking::free_balance(&20), 6940);
assert_eq!(Staking::intentions(), vec![20]);
assert!(Staking::forcing_new_era().is_some());
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![1]));
assert_eq!(Staking::free_balance(&10), 6940);
assert_eq!(Staking::free_balance(&20), 6860);
assert_eq!(Staking::intentions(), vec![20]);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![1]));
assert_eq!(Staking::free_balance(&10), 6940);
assert_eq!(Staking::free_balance(&20), 6700);
@@ -213,6 +225,7 @@ fn slashing_should_work() {
assert_eq!(Staking::voting_balance(&10), 21);
System::set_block_number(7);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1]));
assert_eq!(Staking::voting_balance(&10), 1);
});
@@ -433,6 +446,7 @@ fn nominating_slashes_should_work() {
assert_eq!(Staking::voting_balance(&4), 40);
System::set_block_number(5);
::system::ExtrinsicIndex::<Test>::put(1);
assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1]));
assert_eq!(Staking::voting_balance(&1), 0);
assert_eq!(Staking::voting_balance(&2), 20);
@@ -9,6 +9,7 @@ serde = { version = "1.0", default_features = false }
serde_derive = { version = "1.0", optional = true }
safe-mix = { version = "1.0", default_features = false}
substrate-codec = { path = "../../codec", default_features = false }
substrate-codec-derive = { path = "../../codec/derive", default_features = false }
substrate-primitives = { path = "../../primitives", default_features = false }
substrate-runtime-std = { path = "../../runtime-std", default_features = false }
substrate-runtime-io = { path = "../../runtime-io", default_features = false }
@@ -22,6 +23,7 @@ std = [
"serde_derive",
"safe-mix/std",
"substrate-codec/std",
"substrate-codec-derive/std",
"substrate-primitives/std",
"substrate-runtime-std/std",
"substrate-runtime-io/std",
+115 -7
View File
@@ -19,6 +19,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(any(feature = "std", test))]
extern crate substrate_primitives;
#[cfg_attr(any(feature = "std", test), macro_use)]
extern crate substrate_runtime_std as rstd;
@@ -32,8 +35,11 @@ extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate substrate_runtime_io as runtime_io;
#[macro_use]
extern crate substrate_codec_derive;
extern crate substrate_codec as codec;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_runtime_primitives as primitives;
extern crate safe_mix;
@@ -74,26 +80,50 @@ pub trait Trait: Eq + Clone {
Hash = Self::Hash,
Digest = Self::Digest
>;
type Event: Parameter + Member;
}
decl_module! {
pub struct Module<T: Trait>;
}
/// A phase of a block's execution.
#[derive(Encode, Decode)]
#[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone, Debug))]
pub enum Phase {
/// Applying an extrinsic.
ApplyExtrinsic(u32),
/// The end.
Finalization,
}
/// Record of an event happening.
#[derive(Encode, Decode)]
#[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone, Debug))]
pub struct EventRecord<E: Parameter + Member> {
/// The phase of the block it happened in.
pub phase: Phase,
/// The event itself.
pub event: E,
}
decl_storage! {
trait Store for Module<T: Trait>;
pub AccountNonce get(account_nonce): b"sys:non" => default map [ T::AccountId => T::Index ];
pub BlockHash get(block_hash): b"sys:old" => required map [ T::BlockNumber => T::Hash ];
pub ExtrinsicIndex get(extrinsic_index): b"sys:xti" => required u32;
pub ExtrinsicData get(extrinsic_data): b"sys:xtd" => required map [ u32 => Vec<u8> ];
ExtrinsicCount: b"sys:extrinsic_count" => u32;
pub ExtrinsicIndex get(extrinsic_index): b"sys:xti" => u32;
ExtrinsicData get(extrinsic_data): b"sys:xtd" => required map [ u32 => Vec<u8> ];
RandomSeed get(random_seed): b"sys:rnd" => required T::Hash;
// The current block number being processed. Set by `execute_block`.
Number get(block_number): b"sys:num" => required T::BlockNumber;
ParentHash get(parent_hash): b"sys:pha" => required T::Hash;
ExtrinsicsRoot get(extrinsics_root): b"sys:txr" => required T::Hash;
Digest get(digest): b"sys:dig" => default T::Digest;
Events get(events): b"sys:events" => default Vec<EventRecord<T::Event>>;
}
impl<T: Trait> Module<T> {
@@ -105,19 +135,23 @@ impl<T: Trait> Module<T> {
<BlockHash<T>>::insert(*number - One::one(), parent_hash);
<ExtrinsicsRoot<T>>::put(txs_root);
<RandomSeed<T>>::put(Self::calculate_random());
<ExtrinsicIndex<T>>::put(0);
<ExtrinsicIndex<T>>::put(0u32);
<Events<T>>::kill();
}
/// Remove temporary "environment" entries in storage.
pub fn finalise() -> T::Header {
<RandomSeed<T>>::kill();
<ExtrinsicIndex<T>>::kill();
<ExtrinsicCount<T>>::kill();
let number = <Number<T>>::take();
let parent_hash = <ParentHash<T>>::take();
let digest = <Digest<T>>::take();
let extrinsics_root = <ExtrinsicsRoot<T>>::take();
let storage_root = T::Hashing::storage_root();
// <Events<T>> stays to be inspected by the client.
<T::Header as traits::Header>::new(number, extrinsics_root, storage_root, parent_hash, digest)
}
@@ -128,6 +162,14 @@ impl<T: Trait> Module<T> {
<Digest<T>>::put(l);
}
/// Deposits an event onto this block's event record.
pub fn deposit_event(event: T::Event) {
let phase = <ExtrinsicIndex<T>>::get().map_or(Phase::Finalization, |c| Phase::ApplyExtrinsic(c));
let mut events = Self::events();
events.push(EventRecord { phase, event });
<Events<T>>::put(events);
}
/// Calculate the current block's random seed.
fn calculate_random() -> T::Hash {
assert!(Self::block_number() > Zero::zero(), "Block number may never be zero");
@@ -180,12 +222,24 @@ impl<T: Trait> Module<T> {
/// Note what the extrinsic data of the current extrinsic index is. If this is called, then
/// ensure `derive_extrinsics` is also called before block-building is completed.
pub fn note_extrinsic(encoded_xt: Vec<u8>) {
<ExtrinsicData<T>>::insert(Self::extrinsic_index(), encoded_xt);
<ExtrinsicData<T>>::insert(<ExtrinsicIndex<T>>::get().unwrap_or_default(), encoded_xt);
}
/// To be called immediately after an extrinsic has been applied.
pub fn note_applied_extrinsic() {
<ExtrinsicIndex<T>>::put(<ExtrinsicIndex<T>>::get().unwrap_or_default() + 1u32);
}
/// To be called immediately after `note_applied_extrinsic` of the last extrinsic of the block
/// has been called.
pub fn note_finished_extrinsics() {
<ExtrinsicCount<T>>::put(<ExtrinsicIndex<T>>::get().unwrap_or_default());
<ExtrinsicIndex<T>>::kill();
}
/// Remove all extrinsics data and save the extrinsics trie root.
pub fn derive_extrinsics() {
let extrinsics = (0..Self::extrinsic_index()).map(<ExtrinsicData<T>>::take).collect();
let extrinsics = (0..<ExtrinsicCount<T>>::get().unwrap_or_default()).map(<ExtrinsicData<T>>::take).collect();
let xts_root = extrinsics_data_root::<T::Hashing>(extrinsics);
<ExtrinsicsRoot<T>>::put(xts_root);
}
@@ -219,3 +273,57 @@ impl<T: Trait> primitives::BuildStorage for GenesisConfig<T>
])
}
}
#[cfg(test)]
mod tests {
use super::*;
use runtime_io::with_externalities;
use substrate_primitives::H256;
use primitives::BuildStorage;
use primitives::traits::BlakeTwo256;
use primitives::testing::{Digest, Header};
#[derive(Clone, Eq, PartialEq)]
pub struct Test;
impl Trait for Test {
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = u16;
}
type System = Module<Test>;
fn new_test_ext() -> runtime_io::TestExternalities<KeccakHasher> {
GenesisConfig::<Test>::default().build_storage().unwrap().into()
}
#[test]
fn deposit_event_should_work() {
with_externalities(&mut new_test_ext(), || {
System::initialise(&1, &[0u8; 32].into(), &[0u8; 32].into());
System::note_finished_extrinsics();
System::deposit_event(1u16);
System::finalise();
assert_eq!(System::events(), vec![EventRecord { phase: Phase::Finalization, event: 1u16 }]);
System::initialise(&2, &[0u8; 32].into(), &[0u8; 32].into());
System::deposit_event(42u16);
System::note_applied_extrinsic();
System::deposit_event(69u16);
System::note_applied_extrinsic();
System::note_finished_extrinsics();
System::deposit_event(3u16);
System::finalise();
assert_eq!(System::events(), vec![
EventRecord { phase: Phase::ApplyExtrinsic(0), event: 42u16 },
EventRecord { phase: Phase::ApplyExtrinsic(1), event: 69u16 },
EventRecord { phase: Phase::Finalization, event: 3u16 }
]);
});
}
}
@@ -80,7 +80,7 @@ impl<T: Trait> Module<T> {
assert!(aux.is_empty());
assert!(!<Self as Store>::DidUpdate::exists(), "Timestamp must be updated only once in the block");
assert!(
<system::Module<T>>::extrinsic_index() == T::TIMESTAMP_SET_POSITION,
<system::Module<T>>::extrinsic_index() == Some(T::TIMESTAMP_SET_POSITION),
"Timestamp extrinsic must be at position {} in the block",
T::TIMESTAMP_SET_POSITION
);
@@ -158,6 +158,7 @@ mod tests {
type Digest = Digest;
type AccountId = u64;
type Header = Header;
type Event = ();
}
impl consensus::Trait for Test {
type PublicAux = u64;