diff --git a/node/executor/src/lib.rs b/node/executor/src/lib.rs index 84b2de336ed58..cc5992a514555 100644 --- a/node/executor/src/lib.rs +++ b/node/executor/src/lib.rs @@ -445,13 +445,7 @@ mod tests { ] ); - // let mut digest = generic::Digest::::default(); - // digest.push(Log::from(::grandpa::RawLog::AuthoritiesChangeSignal(0, vec![ - // (Keyring::Charlie.to_raw_public().into(), 1), - // (Keyring::Bob.to_raw_public().into(), 1), - // (Keyring::Alice.to_raw_public().into(), 1), - // ]))); - let digest = generic::Digest::::default(); // TODO test this + let digest = generic::Digest::::default(); assert_eq!(Header::decode(&mut &block2.0[..]).unwrap().digest, digest); (block1, block2) @@ -584,14 +578,6 @@ mod tests { phase: Phase::Finalization, event: Event::session(session::RawEvent::NewSession(1)) }, - // EventRecord { // TODO: this might be wrong. - // phase: Phase::Finalization, - // event: Event::grandpa(::grandpa::RawEvent::NewAuthorities(vec![ - // (Keyring::Charlie.to_raw_public().into(), 1), - // (Keyring::Bob.to_raw_public().into(), 1), - // (Keyring::Alice.to_raw_public().into(), 1), - // ])), - // }, EventRecord { phase: Phase::Finalization, event: Event::treasury(treasury::RawEvent::Spending(0)) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 4650bff981d77..3751a14e3c6c2 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -16,7 +16,200 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! Staking manager: Periodically determines the best set of validators. +//! # Staking Module +//! +//! +//! The staking module is the means by which a set of network maintainers (known as "authorities" in some contexts and "validators" in others) +//! are chosen based upon those who voluntarily place funds under deposit. Under deposit, those funds are rewarded under +//! normal operation but are held at pain of "slash" (expropriation) should the staked maintainer be found not to be +//! discharging their duties properly. +//! You can start using the Staking module by implementing the staking [`Trait`]. +//! The supported dispatchable functions are documented as part of the [`Call`] enum. +//! +//! ## Overview +//! +//! ### Terminology +//! +//! +//! - Staking: The process of locking up funds for some time, placing them at risk of slashing (loss) in order to become a rewarded maintainer of the network. +//! - Validating: The process of running a node to actively maintain the network, either by producing blocks or guaranteeing finality of the chain. +//! - Nominating: The process of placing staked funds behind one or more validators in order to share in any reward, and punishment, they take. +//! - Stash account: The account holding an owner's funds used for staking. +//! - Controller account: The account which controls an owner's funds for staking. +//! - Era: A (whole) number of sessions, which is the period that the validator set (and each validator's active nominator set) is recalculated and where rewards are paid out. +//! - Slash: The punishment of a staker by reducing their funds ([reference](#references)). +//! +//! ### Goals +//! +//! +//! The staking system in Substrate NPoS is designed to achieve three goals: +//! - It should be possible to stake funds that are controlled by a cold wallet. +//! - It should be possible to withdraw some, or deposit more, funds without interrupting the role of an entity. +//! - It should be possible to switch between roles (nominator, validator, idle) with minimal overhead. +//! +//! ### Scenarios +//! +//! #### Staking +//! +//! Almost any interaction with the staking module requires a process of *bonding* (also known as +//! being a **staker**). To become *bonded* a fund-holding account known as the _**stash account**_, which holds some of all of the +//! funds that become frozen in place as part of the staking process, is paired with an active **controller** account which issues +//! instructions on how they shall be used. This process in the public API is mostly referred to as _bonding_ via +//! the `bond()` function. +//! +//! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` and `Idle`. There are +//! three corresponding instructions to change between roles, namely: `validate`, `nominate` and `chill`. Note that the +//! _controller_ account is always responsible +//! for declaring changes and the _stash_ account stays untouched, without directly interacting in any operation. +//! +//! #### Validating +//! +//! A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of +//! the network. A validator should avoid both any sort of malicious misbehavior and going offline. +//! Bonded accounts that state interest in being a validator do NOT get immediately chosen as a validator. Instead, they +//! are declared as a _candidate_ and they _might_ get elected at the _next **era**_ as a validator. The result of the +//! election is determined by nominators and their votes. An account can become a validator candidate via the `validate()` +//! call. +//! +//! #### Nomination +//! +//! A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on a set of validators +//! to be elected. Once interest in nomination is stated by an account, it takes effect at the next election round. The +//! funds in the nominator's stash account indicate the _weight_ of its vote. In some sense, the nominator bets on the +//! honesty of a set of validators by voting for them, with the goal of having some share of the reward granted to them. +//! Both rewards and any punishment that a validator earns are shared between itself and its nominators. +//! This rule incentivizes the nominators to NOT vote for the misbehaving/offline validators as much as possible, simply +//! because the nominators will also lose funds if they vote poorly. An account can become a nominator via the +//! `nominate()` call. +//! +//! #### Rewards and Slash +//! +//! The **reward and slashing** procedure are the core of the staking module, attempting to _embrace valid behavior_ +//! while _punishing any misbehavior or lack of availability_. Slashing can occur at any point in time, once +//! misbehavior is reported. One such misbehavior is a validator being detected as offline more than a certain number of +//! times. Once slashing is determined, a value is deducted from the balance of the validator and all the nominators who +//! voted for this validator. Same rules apply to the rewards in the sense of being shared among a validator and its +//! associated nominators. +//! +//! Finally, any of the roles above can choose to step back temporarily and just chill for a while. This means that if +//! they are a nominator, they will not be considered as voters anymore and if they are validators, they will no longer +//! be a candidate for the next election (again, both effects apply at the beginning of the next era). An account can +//! step back via the `chill()` call. +//! +//! ## Interface +//! +//! ### Dispatchable +//! +//! The Dispatchable functions of the staking module enable the steps needed for entities to accept and change their +//! role, alongside some helper functions to get/set the metadata of the module. +//! +//! Please refer to the [`Call`] enum and its associated variants for a detailed list of dispatchable functions. +//! +//! ### Public +//! The staking module contains many public storage items and (im)mutable functions. Please refer to the [struct list](#structs) +//! below and the [`Module`](https://crates.parity.io/srml_staking/struct.Module.html) struct definition for more details. +//! +//! ## Usage +//! +//! +//! ### Snippet: Bonding and Accepting Roles +//! +//! An arbitrary account pair, given that the associated stash has the required funds, can become stakers via the following call: +//! +//! ```rust,ignore +//! // bond account 3 as stash +//! // account 4 as controller +//! // with stash value 1500 units +//! // while the rewards get transferred to the controller account. +//! Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller); +//! ``` +//! +//! To state desire to become a validator: +//! +//! ```rust,ignore +//! // controller account 4 states desire for validation with the given preferences. +//! Staking::validate(Origin::signed(4), ValidatorPrefs::default()); +//! ``` +//! +//! Note that, as mentioned, the stash account is transparent in such calls and only the controller initiates the function calls. +//! +//! Similarly, to state desire in nominating: +//! +//! ```rust,ignore +//! // controller account 4 nominates for account 10 and 20. +//! Staking::nominate(Origin::signed(4), vec![20, 10]); +//! ``` +//! +//! Finally, account 4 can withdraw from any of the above roles via +//! +//! ```rust,ignore +//! Staking::chill(Origin::signed(4)); +//! ``` +//! +//! ## Implementation Details +//! +//! ### Slot Stake +//! +//! The term `slot_stake` will be used throughout this section. It refers to a value calculated at the end of each era, +//! containing the _minimum value at stake among all validators._ +//! +//! ### Reward Calculation +//! +//! - Rewards are recorded **per-session** and paid **per-era**. The value of the reward for each session is calculated at +//! the end of the session based on the timeliness of the session, then accumulated to be paid later. The value of +//! the new _per-session-reward_ is calculated at the end of each era by multiplying `slot_stake` and a configuration +//! storage item named `SessionReward`. +//! - Once a new era is triggered, rewards are paid to the validators and the associated nominators. +//! - The validator can declare an amount, named `validator_payment`, that does not get shared with the nominators at +//! each reward payout through their `ValidatorPrefs`. This value gets deducted from the total reward that can be paid. +//! The remaining portion is split among the validator and all of the nominators that nominated the validator, +//! proportional to the value staked behind this validator. +//! - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage item (see `set_payee()`), to be one of the following: +//! - Controller account. +//! - Stash account, not increasing the staked value. +//! - Stash account, also increasing the staked value. +//! +//! ### Slashing details +//! +//! - A validator can be _reported_ to be offline at any point via `on_offline_validator` public function. +//! - Each validator declares how many times it can be _reported_ before it actually gets slashed via the +//! `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces an `OfflineSlashGrace`, +//! which applies to all validators and prevents them from getting immediately slashed. +//! - Similar to the reward value, the slash value is updated at the end of each era by multiplying `slot_stake` and a +//! configuration storage item, `OfflineSlash`. +//! - Once a validator has been reported a sufficient number of times, the total value that gets deducted from that +//! validator and their nominators is calculated by multiplying the result of the above point +//! by `2.pow(unstake_threshold)`. This punishment is shared in the same manner as the rewards. +//! - If the previous overflows, then `slot_stake` is used. (NOTE: This should never happen in a correctly implemented, non-corrupted, well-configured system) +//! - All individual accounts' punishments are capped at their total stake. (NOTE: This cap should never come into force in a correctly implemented, non-corrupted, well-configured system) +//! +//! ### Additional Fund Management Operations +//! +//! Any funds already placed into stash can be the target of the following operations: +//! +//! - The controller account can free a portion (or all) of the funds using the `unbond()` call. Note that the funds +//! are not immediately accessible, instead, a duration denoted by `BondingDuration` (in number of eras) must pass until the funds can actually be removed. +//! - To prepare the funds for transfer away from the stash account, then `withdraw_unbonded()` must be used once the bonding duration is over. +//! - Additional funds that are placed in the stash account may be bonded with the `bond_extra()` transaction call. +//! +//! ### Election algorithm details. +//! +//! The current election algorithm is implemented based on Phragmén. The reference implementation can be found [here](https://github.com/w3f/consensus/tree/master/NPoS). +//! +//! ## GenesisConfig +//! +//! See the [`GenesisConfig`] for a list of attributes that can be provided. +//! +//! ## Related Modules +//! +//! - [**Balances**](https://crates.parity.io/srml_balances/index.html): Used to manage values at stake. +//! - [**Sessions**](https://crates.parity.io/srml_session/index.html): Used to manage sessions. Also, a list of new validators is also stored in the sessions module's `Validators` at the end of each era. +//! - [**System**](https://crates.parity.io/srml_system/index.html): Used to obtain block number and time, among other details. +//! +//! # References +//! +//! 1. This document is written as a more verbose version of the original [Staking.md](../Staking.md) file. Some sections, are taken directly from the aforementioned document. + #![cfg_attr(not(feature = "std"), no_std)] @@ -310,6 +503,8 @@ decl_module! { /// Take the origin account as a stash and lock up `value` of its balance. `controller` will be the /// account that controls it. + /// + /// The dispatch origin for this call must be _Signed_. fn bond(origin, controller: ::Source, #[compact] value: BalanceOf, payee: RewardDestination) { let stash = ensure_signed(origin)?; @@ -335,7 +530,7 @@ decl_module! { /// /// Use this if there are additional funds in your stash account that you wish to bond. /// - /// NOTE: This call must be made by the controller, not the stash. + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. fn bond_extra(origin, max_additional: BalanceOf) { let controller = ensure_signed(origin)?; let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; @@ -356,7 +551,7 @@ decl_module! { /// Once the unlock period is done, you can call `withdraw_unbonded` to actually move /// the funds out of management ready for transfer. /// - /// NOTE: This call must be made by the controller, not the stash. + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// /// See also [`Call::withdraw_unbonded`]. fn unbond(origin, #[compact] value: BalanceOf) { @@ -386,7 +581,7 @@ decl_module! { /// This essentially frees up that balance to be used by the stash account to do /// whatever it wants. /// - /// NOTE: This call must be made by the controller, not the stash. + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// /// See also [`Call::unbond`]. fn withdraw_unbonded(origin) { @@ -400,7 +595,7 @@ decl_module! { /// /// Effects will be felt at the beginning of the next era. /// - /// NOTE: This call must be made by the controller, not the stash. + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. fn validate(origin, prefs: ValidatorPrefs>) { let controller = ensure_signed(origin)?; let _ledger = Self::ledger(&controller).ok_or("not a controller")?; @@ -413,7 +608,7 @@ decl_module! { /// /// Effects will be felt at the beginning of the next era. /// - /// NOTE: This call must be made by the controller, not the stash. + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. fn nominate(origin, targets: Vec<::Source>) { let controller = ensure_signed(origin)?; let _ledger = Self::ledger(&controller).ok_or("not a controller")?; @@ -431,7 +626,7 @@ decl_module! { /// /// Effects will be felt at the beginning of the next era. /// - /// NOTE: This call must be made by the controller, not the stash. + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. fn chill(origin) { let controller = ensure_signed(origin)?; let _ledger = Self::ledger(&controller).ok_or("not a controller")?; @@ -443,7 +638,7 @@ decl_module! { /// /// Effects will be felt at the beginning of the next era. /// - /// NOTE: This call must be made by the controller, not the stash. + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. fn set_payee(origin, payee: RewardDestination) { let controller = ensure_signed(origin)?; let _ledger = Self::ledger(&controller).ok_or("not a controller")?; @@ -660,7 +855,7 @@ impl Module { /// Select a new validator set from the assembled stakers and their role preferences. /// - /// @returns the new SlotStake value. + /// Returns the new SlotStake value. fn select_validators() -> BalanceOf { // Map of (would-be) validator account to amount of stake backing it. diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index bdaed1fee9760..6211557ad525a 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -69,25 +69,25 @@ pub struct Vote { /// /// Reference implementation: https://github.com/w3f/consensus /// -/// @returns a vector of elected candidates +/// Returns a vector of elected candidates pub fn elect( get_rounds: FR, get_validators: FV, get_nominators: FN, stash_of: FS, minimum_validator_count: usize, - ) -> Vec>> where - FR: Fn() -> usize, - FV: Fn() -> Box>) - >>, - FN: Fn() -> Box) - >>, - FS: Fn(T::AccountId) -> BalanceOf, +) -> Vec>> where + FR: Fn() -> usize, + FV: Fn() -> Box>) + >>, + FN: Fn() -> Box) + >>, + FS: Fn(T::AccountId) -> BalanceOf, { let rounds = get_rounds(); - let mut elected_candidates = vec![]; + let mut elected_candidates; // 1- Pre-process candidates and place them in a container let mut candidates = get_validators().map(|(who, _)| { @@ -130,6 +130,7 @@ pub fn elect( // 4- If we have more candidates then needed, run Phragmén. if candidates.len() > rounds { + elected_candidates = Vec::with_capacity(rounds); // Main election loop for _round in 0..rounds { // Loop 1: initialize score @@ -177,7 +178,6 @@ pub fn elect( } elected_candidates.push(winner); - } // end of all rounds // 4.1- Update backing stake of candidates and nominators @@ -185,15 +185,11 @@ pub fn elect( for v in &mut n.nominees { // if the target of this vote is among the winners, otherwise let go. if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == v.who) { - v.backing_stake = as As>::sa( - n.stake.as_() - * *v.load - / *n.load - ); + v.backing_stake = as As>::sa(n.stake.as_() * *v.load / *n.load); c.exposure.total += v.backing_stake; // Update IndividualExposure of those who nominated and their vote won c.exposure.others.push( - IndividualExposure {who: n.who.clone(), value: v.backing_stake } + IndividualExposure { who: n.who.clone(), value: v.backing_stake } ); } } @@ -208,7 +204,7 @@ pub fn elect( if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == v.who) { c.exposure.total += n.stake; c.exposure.others.push( - IndividualExposure {who: n.who.clone(), value: n.stake } + IndividualExposure { who: n.who.clone(), value: n.stake } ); } }