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 all commits
Commits
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
16 changes: 1 addition & 15 deletions node/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,13 +445,7 @@ mod tests {
]
);

// let mut digest = generic::Digest::<Log>::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::<Log>::default(); // TODO test this
let digest = generic::Digest::<Log>::default();
assert_eq!(Header::decode(&mut &block2.0[..]).unwrap().digest, digest);

(block1, block2)
Expand Down Expand Up @@ -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))
Expand Down
213 changes: 204 additions & 9 deletions srml/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,200 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.

//! Staking manager: Periodically determines the best set of validators.
//! # Staking Module
//!
//! <!-- Original author of paragraph: @gavofyork -->
//! 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`].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also link to the Call enum. See timestamp as an example.

//! The supported dispatchable functions are documented as part of the [`Call`] enum.
//!
//! ## Overview
//!
//! ### Terminology
//! <!-- Original author of paragraph: @gavofyork -->
//!
//! - 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
//! <!-- Original author of paragraph: @gavofyork -->
//!
//! 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't you remove this file in the last pr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, the intention was to maintain that as well. Though Gavin said he is also ok with the old one not being kept if everything is covered in a new doc.

I will still keep this section and see where the missing file is + if it is recoverable.



#![cfg_attr(not(feature = "std"), no_std)]

Expand Down Expand Up @@ -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: <T::Lookup as StaticLookup>::Source, #[compact] value: BalanceOf<T>, payee: RewardDestination) {
let stash = ensure_signed(origin)?;

Expand All @@ -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<T>) {
let controller = ensure_signed(origin)?;
let mut ledger = Self::ledger(&controller).ok_or("not a controller")?;
Expand All @@ -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<T>) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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<BalanceOf<T>>) {
let controller = ensure_signed(origin)?;
let _ledger = Self::ledger(&controller).ok_or("not a controller")?;
Expand All @@ -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<<T::Lookup as StaticLookup>::Source>) {
let controller = ensure_signed(origin)?;
let _ledger = Self::ledger(&controller).ok_or("not a controller")?;
Expand All @@ -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")?;
Expand All @@ -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")?;
Expand Down Expand Up @@ -660,7 +855,7 @@ impl<T: Trait> Module<T> {

/// 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<T> {
// Map of (would-be) validator account to amount of stake backing it.

Expand Down
34 changes: 15 additions & 19 deletions srml/staking/src/phragmen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,25 @@ pub struct Vote<AccountId, Balance: HasCompact> {
///
/// Reference implementation: https://github.com/w3f/consensus
///
/// @returns a vector of elected candidates
/// Returns a vector of elected candidates
pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
get_rounds: FR,
get_validators: FV,
get_nominators: FN,
stash_of: FS,
minimum_validator_count: usize,
) -> Vec<Candidate<T::AccountId, BalanceOf<T>>> where
FR: Fn() -> usize,
FV: Fn() -> Box<dyn Iterator<
Item =(T::AccountId, ValidatorPrefs<BalanceOf<T>>)
>>,
FN: Fn() -> Box<dyn Iterator<
Item =(T::AccountId, Vec<T::AccountId>)
>>,
FS: Fn(T::AccountId) -> BalanceOf<T>,
) -> Vec<Candidate<T::AccountId, BalanceOf<T>>> where
FR: Fn() -> usize,
FV: Fn() -> Box<dyn Iterator<
Item =(T::AccountId, ValidatorPrefs<BalanceOf<T>>)
>>,
FN: Fn() -> Box<dyn Iterator<
Item =(T::AccountId, Vec<T::AccountId>)
>>,
FS: Fn(T::AccountId) -> BalanceOf<T>,
{
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, _)| {
Expand Down Expand Up @@ -130,6 +130,7 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(

// 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
Expand Down Expand Up @@ -177,23 +178,18 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
}

elected_candidates.push(winner);

} // end of all rounds

// 4.1- Update backing stake of candidates and nominators
for n in &mut nominations {
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 = <BalanceOf<T> as As<u64>>::sa(
n.stake.as_()
* *v.load
/ *n.load
);
v.backing_stake = <BalanceOf<T> as As<u64>>::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 }
);
}
}
Expand All @@ -208,7 +204,7 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
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 }
);
}
}
Expand Down