From aa83f220a497467dc316b96510d33fff69fbf91a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sat, 9 Mar 2019 14:59:38 +0100 Subject: [PATCH 01/27] initial doc for the staking module --- srml/staking/README.adoc | 120 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 srml/staking/README.adoc diff --git a/srml/staking/README.adoc b/srml/staking/README.adoc new file mode 100644 index 0000000000000..7662b82a19d08 --- /dev/null +++ b/srml/staking/README.adoc @@ -0,0 +1,120 @@ +# 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”__ should they be found not to bee discharging their duties properly. + + +## 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. (see [here]() for the details of how block production and finalization are separated) +- **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. + +### Scenarios + +Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the [`bond()`](#todo) function. + +Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest and the _stash_ account stays untouched, without directly interacting in any operation. + +A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of the network in other words. A validator should avoid both any sort of malicious misbehavior and going offline. Unlike nominating, 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 election is determined by nominators and their votes. An account can become a validator via the [`validate()`](#todo) call. + +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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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()`](#todo) call. + +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 a misbehavior is reported. One such misbehavior is a validator to be detected as offline more than a certain number of times. Once slashing is determined, a value is deducted from the balance of validator and all the nominators who voted for this validator. Same rules apply to the rewards in the sense of being shared among validator and its associated nominators. + +Finally, any of the roles above can choose to temporarily step back 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()`](#todo) call. + +## Public Interface + +The staking module contains many public storage items and (im)mutable functions. Please refer to the [rustdoc](#todo) to see the full list. + +## Usage Example + +### Bonding and Accepting Roles + +An arbitrary account pair, given that the associated stash has the required funds, can become stakers via the following call: + +``` +// 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 in becoming a validator: + +``` +// 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: + +``` +// 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 + +``` +Staking::chill(Origin::signed(4)); +``` + +### ??? + +TODO: What else could be a usage example here interesting for the user? Do we have any? + +## 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 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 named [`SessionReward`](#todo). + - Once a new era is triggered, rewards are paid to the validators and the associated nominators. + - The validator can declare an amount that does not get shared with the nominators at each reward payout through their [`ValidatorPrefs`](#todo). 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 who had a vote for this validator, proportional to their staked value. + - All entities who receive a reward have the option to choose their reward destination, through the [`Payee`](#todo) storage, 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`](#todo) public function. +- Each validator declares how many times they can be _reported_ before it actually gets slashed via the `unstake_threshold` in [`ValidatorPrefs`](#todo). On top of this, the module also introduces a `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`](#todo). +- Once a validator has been reported a sufficient amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. + - If the previous overflow, then `slot_stake` is used. + - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). + +### Election algorithm details. + +Current election algorithm is implemented based on Phragmen. The reference implementation can be found [here](https://github.com/w3f/consensus/tree/master/NPoS). + +## Extensibility + +// Details that the user can modify or customize to make their own + + +## Dependencies + +### GenesisConfig + +See [`chain_spec.rs`](#todo) for a list of attributed that can be provided. + +### Related Modules + +- [Balances](#todo): Used to manage values at stake. +- [Sessions](#todo): Used to manage sessions. Also, a list of new validators is also stored in the sessions module's [`Validators`](#todo) at the end of each era. +- [System](#todo): Used to obtain block number and time, among other details. From b9d09bb7d751bc01480d813b1e1ac20eb1296362 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sat, 9 Mar 2019 15:04:47 +0100 Subject: [PATCH 02/27] Remove md style links. --- srml/staking/README.adoc | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/srml/staking/README.adoc b/srml/staking/README.adoc index 7662b82a19d08..931ff8c4cb5c2 100644 --- a/srml/staking/README.adoc +++ b/srml/staking/README.adoc @@ -17,21 +17,21 @@ The staking module is the means by which a set of network maintainers (known as ### Scenarios -Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the [`bond()`](#todo) function. +Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest and the _stash_ account stays untouched, without directly interacting in any operation. -A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of the network in other words. A validator should avoid both any sort of malicious misbehavior and going offline. Unlike nominating, 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 election is determined by nominators and their votes. An account can become a validator via the [`validate()`](#todo) call. +A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of the network in other words. A validator should avoid both any sort of malicious misbehavior and going offline. Unlike nominating, 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 election is determined by nominators and their votes. An account can become a validator via the `validate()` call. -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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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()`](#todo) call. +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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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. 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 a misbehavior is reported. One such misbehavior is a validator to be detected as offline more than a certain number of times. Once slashing is determined, a value is deducted from the balance of validator and all the nominators who voted for this validator. Same rules apply to the rewards in the sense of being shared among validator and its associated nominators. -Finally, any of the roles above can choose to temporarily step back 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()`](#todo) call. +Finally, any of the roles above can choose to temporarily step back 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. ## Public Interface -The staking module contains many public storage items and (im)mutable functions. Please refer to the [rustdoc](#todo) to see the full list. +The staking module contains many public storage items and (im)mutable functions. Please refer to the rustdoc to see the full list. ## Usage Example @@ -69,8 +69,6 @@ Finally, account 4 can withdraw from any of the above roles via Staking::chill(Origin::signed(4)); ``` -### ??? - TODO: What else could be a usage example here interesting for the user? Do we have any? ## Implementation Details @@ -81,19 +79,19 @@ The term `slot_stake` will be used throughout this section. It refers to a value ### Reward Calculation - - Rewards are recorded **per-session** and paid **per-era**. The value of 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 named [`SessionReward`](#todo). + - Rewards are recorded **per-session** and paid **per-era**. The value of 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 named `SessionReward`. - Once a new era is triggered, rewards are paid to the validators and the associated nominators. - - The validator can declare an amount that does not get shared with the nominators at each reward payout through their [`ValidatorPrefs`](#todo). 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 who had a vote for this validator, proportional to their staked value. - - All entities who receive a reward have the option to choose their reward destination, through the [`Payee`](#todo) storage, to be one of the following: + - The validator can declare an amount 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 who had a vote for this validator, proportional to their staked value. + - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage, 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`](#todo) public function. -- Each validator declares how many times they can be _reported_ before it actually gets slashed via the `unstake_threshold` in [`ValidatorPrefs`](#todo). On top of this, the module also introduces a `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`](#todo). +- A validator can be _reported_ to be offline at any point via `on_offline_validator` public function. +- Each validator declares how many times they can be _reported_ before it actually gets slashed via the `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces a `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 amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. - If the previous overflow, then `slot_stake` is used. - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). @@ -111,10 +109,10 @@ Current election algorithm is implemented based on Phragmen. The reference imple ### GenesisConfig -See [`chain_spec.rs`](#todo) for a list of attributed that can be provided. +See `chain_spec.rs` for a list of attributed that can be provided. ### Related Modules -- [Balances](#todo): Used to manage values at stake. -- [Sessions](#todo): Used to manage sessions. Also, a list of new validators is also stored in the sessions module's [`Validators`](#todo) at the end of each era. -- [System](#todo): Used to obtain block number and time, among other details. +- Balances: Used to manage values at stake. +- Sessions: 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: Used to obtain block number and time, among other details. From 7df2b19ff20e97f22213f69e32b6922b42fd3dcc Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sat, 9 Mar 2019 15:07:12 +0100 Subject: [PATCH 03/27] Remove todos. --- srml/staking/README.adoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/srml/staking/README.adoc b/srml/staking/README.adoc index 931ff8c4cb5c2..0ef8d4b90876a 100644 --- a/srml/staking/README.adoc +++ b/srml/staking/README.adoc @@ -8,7 +8,7 @@ The staking module is the means by which a set of network maintainers (known as ### 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. (see [here]() for the details of how block production and finalization are separated) +- **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. @@ -69,7 +69,6 @@ Finally, account 4 can withdraw from any of the above roles via Staking::chill(Origin::signed(4)); ``` -TODO: What else could be a usage example here interesting for the user? Do we have any? ## Implementation Details @@ -104,7 +103,6 @@ Current election algorithm is implemented based on Phragmen. The reference imple // Details that the user can modify or customize to make their own - ## Dependencies ### GenesisConfig From 28b959b5675a2f7208b142cf65b23b4d298550ec Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sat, 9 Mar 2019 15:11:53 +0100 Subject: [PATCH 04/27] Add rust code types --- srml/staking/README.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/srml/staking/README.adoc b/srml/staking/README.adoc index 0ef8d4b90876a..fc25f5a704909 100644 --- a/srml/staking/README.adoc +++ b/srml/staking/README.adoc @@ -39,7 +39,7 @@ The staking module contains many public storage items and (im)mutable functions. An arbitrary account pair, given that the associated stash has the required funds, can become stakers via the following call: -``` +```rust // bond account 3 as stash // account 4 as controller // with stash value 1500 units @@ -49,7 +49,7 @@ Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller); To state desire in becoming a validator: -``` +```rust // controller account 4 states desire for validation with the given preferences. Staking::validate(Origin::signed(4), ValidatorPrefs::default()); ``` @@ -58,14 +58,14 @@ Note that, as mentioned, the stash account is transparent in such calls and only Similarly, to state desire in nominating: -``` +```rust // 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 Staking::chill(Origin::signed(4)); ``` From 91be68b9a7d029b33331c58a89a7884e57366533 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 11 Mar 2019 16:10:27 +0100 Subject: [PATCH 05/27] Rename and fix review notes. --- srml/staking/README.adoc | 116 --------------------------------------- 1 file changed, 116 deletions(-) delete mode 100644 srml/staking/README.adoc diff --git a/srml/staking/README.adoc b/srml/staking/README.adoc deleted file mode 100644 index fc25f5a704909..0000000000000 --- a/srml/staking/README.adoc +++ /dev/null @@ -1,116 +0,0 @@ -# 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”__ should they be found not to bee discharging their duties properly. - - -## 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. - -### Scenarios - -Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. - -Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest and the _stash_ account stays untouched, without directly interacting in any operation. - -A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of the network in other words. A validator should avoid both any sort of malicious misbehavior and going offline. Unlike nominating, 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 election is determined by nominators and their votes. An account can become a validator via the `validate()` call. - -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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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. - -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 a misbehavior is reported. One such misbehavior is a validator to be detected as offline more than a certain number of times. Once slashing is determined, a value is deducted from the balance of validator and all the nominators who voted for this validator. Same rules apply to the rewards in the sense of being shared among validator and its associated nominators. - -Finally, any of the roles above can choose to temporarily step back 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. - -## Public Interface - -The staking module contains many public storage items and (im)mutable functions. Please refer to the rustdoc to see the full list. - -## Usage Example - -### 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 -// 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 in becoming a validator: - -```rust -// 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 -// 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 -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 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 named `SessionReward`. - - Once a new era is triggered, rewards are paid to the validators and the associated nominators. - - The validator can declare an amount 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 who had a vote for this validator, proportional to their staked value. - - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage, 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 they can be _reported_ before it actually gets slashed via the `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces a `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 amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. - - If the previous overflow, then `slot_stake` is used. - - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). - -### Election algorithm details. - -Current election algorithm is implemented based on Phragmen. The reference implementation can be found [here](https://github.com/w3f/consensus/tree/master/NPoS). - -## Extensibility - -// Details that the user can modify or customize to make their own - -## Dependencies - -### GenesisConfig - -See `chain_spec.rs` for a list of attributed that can be provided. - -### Related Modules - -- Balances: Used to manage values at stake. -- Sessions: 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: Used to obtain block number and time, among other details. From 5912ff59c4f7ac3dde384bc78ee0872cde1f5626 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 11 Mar 2019 16:11:13 +0100 Subject: [PATCH 06/27] Add new md file --- srml/staking/README.md | 139 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 srml/staking/README.md diff --git a/srml/staking/README.md b/srml/staking/README.md new file mode 100644 index 0000000000000..45d706ae7009a --- /dev/null +++ b/srml/staking/README.md @@ -0,0 +1,139 @@ +# 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 they be found not to bee discharhing their duties properly[[1](#references)]. + +### 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 am 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. + +### 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 t. +- It should be possible to switch between roles (nominator, validator, idle) with minimal overhead. + +### Scenarios + +#### Staking + +Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. + +Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest 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 in other words. A validator should avoid both any sort of malicious misbehavior and going offline. Unlike nominating, 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 election is determined by nominators and their votes. An account can become a validator 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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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 a misbehavior is reported. One such misbehavior is a validator to be detected as offline more than a certain number of times. Once slashing is determined, a value is deducted from the balance of validator and all the nominators who voted for this validator. Same rules apply to the rewards in the sense of being shared among validator and its associated nominators. + +Finally, any of the roles above can choose to temporarily step back 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. + +## Public Interface + +The staking module contains many public storage items and (im)mutable, and dispatchable, functions. Please refer to the `Module` struct definition for more details. + +## Usage Example + +### 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 +// 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 in becoming a validator: + +```rust +// 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 +// 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 +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 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 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 who had a vote for this validator, proportional to their staked value. + - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage (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 they can be _reported_ before it actually gets slashed via the `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces a `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 amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. + - If the previous overflow, then `slot_stake` is used. + - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). + +### Additional Fund Management Operations + +Any funds already placed into stash can be the target of the following operations: + +- The controller account can free an portion (or all) of the funds using the `unbond()` call. Note that the funds are not immediately accessible, instead, a duration denoted by `BondingDuration` number of eras must pass until the funds can be actually removed. +- To actually remove the funds, once the bonding duration is over, the `withdraw_unbonded()` can be used. +- As opposed to the above, additional funds can be added to the stash account via the `bond_extra()` transaction call. + +### Election algorithm details. + +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 [`GensisConfig`](https://crates.parity.io/srml_staking/struct.GenesisConfig.html) for a list of attributed 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]() file. Some sections, (denoted by `[1]`) are taken directly from the aforementioned document. \ No newline at end of file From 64ca546bfed1e5ae62bae883365616677a74bf0b Mon Sep 17 00:00:00 2001 From: Kian Peymani Date: Mon, 11 Mar 2019 16:17:03 +0100 Subject: [PATCH 07/27] Final touches. --- srml/staking/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/srml/staking/README.md b/srml/staking/README.md index 45d706ae7009a..589d881e1ac86 100644 --- a/srml/staking/README.md +++ b/srml/staking/README.md @@ -1,7 +1,7 @@ # 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 they be found not to bee discharhing their duties properly[[1](#references)]. +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 they be found not to bee discharhing their duties properly [[1](#references)]. ### Overview @@ -34,7 +34,7 @@ Any account pair successfully placed at stake can accept three possible roles, n #### Validating -A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of the network in other words. A validator should avoid both any sort of malicious misbehavior and going offline. Unlike nominating, 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 election is determined by nominators and their votes. An account can become a validator via the `validate()` call. +A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of the network in other words. 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 election is determined by nominators and their votes. An account can become a validator via the `validate()` call. #### Nomination @@ -48,7 +48,7 @@ Finally, any of the roles above can choose to temporarily step back and just chi ## Public Interface -The staking module contains many public storage items and (im)mutable, and dispatchable, functions. Please refer to the `Module` struct definition for more details. +The staking module contains many public storage items and (im)mutable, and dispatchable, functions. Please refer to the [`Module`](https://crates.parity.io/srml_staking/struct.Module.html) struct definition for more details. ## Usage Example @@ -136,4 +136,4 @@ See the [`GensisConfig`](https://crates.parity.io/srml_staking/struct.GenesisCon # References -1. This document is written as a more verbose version of the original [Staking.md]() file. Some sections, (denoted by `[1]`) are taken directly from the aforementioned document. \ No newline at end of file +1. This document is written as a more verbose version of the original [Staking.md](./Staking.md) file. Some sections, (denoted by `[1]`) are taken directly from the aforementioned document. From a8ba78e502341ed598c5da7c9a250772c1c18821 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 11 Mar 2019 18:29:37 +0100 Subject: [PATCH 08/27] Migrate compleatly to rustdoc --- srml/staking/README.md | 139 --------------------------------------- srml/staking/src/lib.rs | 141 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 140 deletions(-) delete mode 100644 srml/staking/README.md diff --git a/srml/staking/README.md b/srml/staking/README.md deleted file mode 100644 index 589d881e1ac86..0000000000000 --- a/srml/staking/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# 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 they be found not to bee discharhing their duties properly [[1](#references)]. - -### 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 am 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. - -### 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 t. -- It should be possible to switch between roles (nominator, validator, idle) with minimal overhead. - -### Scenarios - -#### Staking - -Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. - -Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest 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 in other words. 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 election is determined by nominators and their votes. An account can become a validator 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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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 a misbehavior is reported. One such misbehavior is a validator to be detected as offline more than a certain number of times. Once slashing is determined, a value is deducted from the balance of validator and all the nominators who voted for this validator. Same rules apply to the rewards in the sense of being shared among validator and its associated nominators. - -Finally, any of the roles above can choose to temporarily step back 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. - -## Public Interface - -The staking module contains many public storage items and (im)mutable, and dispatchable, functions. Please refer to the [`Module`](https://crates.parity.io/srml_staking/struct.Module.html) struct definition for more details. - -## Usage Example - -### 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 -// 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 in becoming a validator: - -```rust -// 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 -// 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 -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 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 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 who had a vote for this validator, proportional to their staked value. - - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage (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 they can be _reported_ before it actually gets slashed via the `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces a `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 amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. - - If the previous overflow, then `slot_stake` is used. - - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). - -### Additional Fund Management Operations - -Any funds already placed into stash can be the target of the following operations: - -- The controller account can free an portion (or all) of the funds using the `unbond()` call. Note that the funds are not immediately accessible, instead, a duration denoted by `BondingDuration` number of eras must pass until the funds can be actually removed. -- To actually remove the funds, once the bonding duration is over, the `withdraw_unbonded()` can be used. -- As opposed to the above, additional funds can be added to the stash account via the `bond_extra()` transaction call. - -### Election algorithm details. - -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 [`GensisConfig`](https://crates.parity.io/srml_staking/struct.GenesisConfig.html) for a list of attributed 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, (denoted by `[1]`) are taken directly from the aforementioned document. diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index b96cb8bbf630a..b038fb550efc5 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -16,7 +16,146 @@ // 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 they be found not to bee discharhing their duties properly [[1](#references)]. +//! +//! ### 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 am 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. +//! +//! ### 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 t. +//! - It should be possible to switch between roles (nominator, validator, idle) with minimal overhead. +//! +//! ### Scenarios +//! +//! #### Staking +//! +//! Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. +//! +//! Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest 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 in other words. 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 election is determined by nominators and their votes. An account can become a validator 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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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 a misbehavior is reported. One such misbehavior is a validator to be detected as offline more than a certain number of times. Once slashing is determined, a value is deducted from the balance of validator and all the nominators who voted for this validator. Same rules apply to the rewards in the sense of being shared among validator and its associated nominators. +//! +//! Finally, any of the roles above can choose to temporarily step back 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. +//! +//! ## Public Interface +//! +//! The staking module contains many public storage items and (im)mutable, and dispatchable, functions. Please refer to the [`Module`](https://crates.parity.io/srml_staking/struct.Module.html) struct definition for more details. +//! +//! ## Usage Example +//! +//! ### 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 +//! // 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 in becoming a validator: +//! +//! ```rust +//! // 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 +//! // 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 +//! 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 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 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 who had a vote for this validator, proportional to their staked value. +//! - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage (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 they can be _reported_ before it actually gets slashed via the `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces a `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 amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. +//! - If the previous overflow, then `slot_stake` is used. +//! - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). +//! +//! ### Additional Fund Management Operations +//! +//! Any funds already placed into stash can be the target of the following operations: +//! +//! - The controller account can free an portion (or all) of the funds using the `unbond()` call. Note that the funds are not immediately accessible, instead, a duration denoted by `BondingDuration` number of eras must pass until the funds can be actually removed. +//! - To actually remove the funds, once the bonding duration is over, the `withdraw_unbonded()` can be used. +//! - As opposed to the above, additional funds can be added to the stash account via the `bond_extra()` transaction call. +//! +//! ### Election algorithm details. +//! +//! 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 [`GensisConfig`](https://crates.parity.io/srml_staking/struct.GenesisConfig.html) for a list of attributed 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, (denoted by `[1]`) are taken directly from the aforementioned document. + #![cfg_attr(not(feature = "std"), no_std)] From 982d7e80426189983f561fe7879a3cfbfd4435cf Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 11 Mar 2019 18:29:58 +0100 Subject: [PATCH 09/27] Update link --- srml/staking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index b038fb550efc5..764b63cb82de2 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -154,7 +154,7 @@ //! //! # References //! -//! 1. This document is written as a more verbose version of the original [Staking.md](./Staking.md) file. Some sections, (denoted by `[1]`) are taken directly from the aforementioned document. +//! 1. This document is written as a more verbose version of the original [Staking.md](../Staking.md) file. Some sections, (denoted by `[1]`) are taken directly from the aforementioned document. #![cfg_attr(not(feature = "std"), no_std)] From 4a572a28e8c6c4cd69d779969ebd424b2d431403 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 11 Mar 2019 18:43:12 +0100 Subject: [PATCH 10/27] Fix heading --- srml/staking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 764b63cb82de2..5c2056c179bbb 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -21,7 +21,7 @@ //! //! 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 they be found not to bee discharhing their duties properly [[1](#references)]. //! -//! ### Overview +//! ## Overview //! //! ### Terminology //! From 89682a64d4b99fa98c33c85200bab8d244c0873c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 12 Mar 2019 16:06:07 +0100 Subject: [PATCH 11/27] Final touches wrt the new template. --- srml/staking/src/lib.rs | 51 ++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index b311cba1fcf81..c417955c49b59 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -19,7 +19,7 @@ //! # 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 they be found not to bee discharhing their duties properly [[1](#references)]. +//! 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 they be found not to bee discharhing their duties properly. //! //! ## Overview //! @@ -32,7 +32,7 @@ //! - Stash account: The account holding an owner's funds used for staking. //! - Controller account: The account which controls am 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. +//! - Slash: The punishment of a staker by reducing their funds ([reference](#references)). //! //! ### Goals //! @@ -64,17 +64,30 @@ //! //! Finally, any of the roles above can choose to temporarily step back 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. //! -//! ## Public Interface +//! ## Interface //! -//! The staking module contains many public storage items and (im)mutable, and dispatchable, functions. Please refer to the [`Module`](https://crates.parity.io/srml_staking/struct.Module.html) struct definition for more details. +//! ### Types //! -//! ## Usage Example +//! - `Currency`: Used as the measurement means of staking and funds management. +//! +//! ### Dispatchable //! -//! ### Bonding and Accepting Roles +//! The Dispatchable functions of the staking module enable the steps needed for entities to accept and and change their role, alongside some helper funcitons to get/set the metadata of the module. +//! +//! Please refer to the [`Call`](https://crates.parity.io/srml_staking/enum.Call.html) enum and its associated functions 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 +//! +//! ### Prerequisites +//! +//! ### 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 +//! ```rust,ignore //! // bond account 3 as stash //! // account 4 as controller //! // with stash value 1500 units @@ -84,7 +97,7 @@ //! //! To state desire in becoming a validator: //! -//! ```rust +//! ```rust,ignore //! // controller account 4 states desire for validation with the given preferences. //! Staking::validate(Origin::signed(4), ValidatorPrefs::default()); //! ``` @@ -93,18 +106,17 @@ //! //! Similarly, to state desire in nominating: //! -//! ```rust +//! ```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 +//! ```rust,ignore //! Staking::chill(Origin::signed(4)); //! ``` //! -//! //! ## Implementation Details //! //! ### Slot Stake @@ -154,7 +166,7 @@ //! //! # References //! -//! 1. This document is written as a more verbose version of the original [Staking.md](../Staking.md) file. Some sections, (denoted by `[1]`) are taken directly from the aforementioned document. +//! 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)] @@ -424,6 +436,7 @@ 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)?; @@ -449,7 +462,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")?; @@ -470,7 +483,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) { @@ -500,7 +513,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) { @@ -514,7 +527,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")?; @@ -527,7 +540,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")?; @@ -545,7 +558,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")?; @@ -557,7 +570,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")?; From 956ec9634c822986b0dff12e2d248528faa9b761 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 12 Mar 2019 16:11:34 +0100 Subject: [PATCH 12/27] Remove empty prereq. --- srml/staking/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index c417955c49b59..f28bed03e463d 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -81,7 +81,6 @@ //! //! ## Usage //! -//! ### Prerequisites //! //! ### Snippet: Bonding and Accepting Roles //! From 2879e1d6713398801a1294f0ce999ebf3bb5995d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 12 Mar 2019 16:58:28 +0100 Subject: [PATCH 13/27] Fix more reviews --- srml/staking/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index f28bed03e463d..68b62b2ede549 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -74,7 +74,7 @@ //! //! The Dispatchable functions of the staking module enable the steps needed for entities to accept and and change their role, alongside some helper funcitons to get/set the metadata of the module. //! -//! Please refer to the [`Call`](https://crates.parity.io/srml_staking/enum.Call.html) enum and its associated functions for a detailed list of dispatchable functions. +//! 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. @@ -155,7 +155,7 @@ //! //! ## GenesisConfig //! -//! See the [`GensisConfig`](https://crates.parity.io/srml_staking/struct.GenesisConfig.html) for a list of attributed that can be provided. +//! See the [`GensisConfig`] for a list of attributed that can be provided. //! //! ## Related Modules //! From 105723b1be449f4e72d634bd9735227369508719 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 12 Mar 2019 18:47:48 +0100 Subject: [PATCH 14/27] Some final nits. --- srml/staking/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 68b62b2ede549..ada23a39d5968 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -19,7 +19,7 @@ //! # 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 they be found not to bee discharhing their duties properly. +//! 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 they be found not to bee discharging their duties properly. //! //! ## Overview //! @@ -39,14 +39,14 @@ //! //! 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 t. +//! - 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 at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the later, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. +//! Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the latter, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. //! //! Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest and the _stash_ account stays untouched, without directly interacting in any operation. //! @@ -56,7 +56,7 @@ //! //! #### 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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share at the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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. +//! 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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share of the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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 //! @@ -72,7 +72,7 @@ //! //! ### Dispatchable //! -//! The Dispatchable functions of the staking module enable the steps needed for entities to accept and and change their role, alongside some helper funcitons to get/set the metadata of the module. +//! 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. //! @@ -137,7 +137,7 @@ //! - A validator can be _reported_ to be offline at any point via `on_offline_validator` public function. //! - Each validator declares how many times they can be _reported_ before it actually gets slashed via the `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces a `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 amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. +//! - Once a validator has been reported a sufficient amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it is calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. //! - If the previous overflow, then `slot_stake` is used. //! - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). //! From 5567859b677636dcab85b758ef5c3555f4c00b8d Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 14 Mar 2019 14:05:47 +0100 Subject: [PATCH 15/27] Fix some side issues. --- node/executor/src/lib.rs | 16 +--------------- srml/staking/src/lib.rs | 2 +- srml/staking/src/phragmen.rs | 34 +++++++++++++++------------------- 3 files changed, 17 insertions(+), 35 deletions(-) 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 bd354e0bf10e2..0311fdf618c2e 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -811,7 +811,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..d67612755bc0d 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 } ); } } From e739361fccf59f4119b4c41e1336e8f482a845b0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Mar 2019 10:56:03 +0100 Subject: [PATCH 16/27] Fix another set of reviews --- srml/staking/src/lib.rs | 101 ++++++++++++++++++++++++++--------- srml/staking/src/phragmen.rs | 2 +- 2 files changed, 76 insertions(+), 27 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 0311fdf618c2e..f5f5f7a8f1d03 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -19,7 +19,11 @@ //! # 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 they be found not to bee discharging their duties properly. +//! 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`]. //! //! ## Overview //! @@ -30,8 +34,8 @@ //! - 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 am 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. +//! - 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 @@ -46,23 +50,52 @@ //! //! #### Staking //! -//! Almost any interaction with the staking module requires at least one account to become **bonded**, also known as being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. Henceforth, the former account that initiated the interest is called the **controller** and the latter, holding the funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account pair_, one of which to take the role of the controller and one to be the frozen stash account (any value locked in stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via the `bond()` function. +//! Almost any interaction with the staking module requires at least one account to become **bonded**, also known as +//! being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. +//! Henceforth, the former account that initiated the interest is called the **controller** and the latter, holding the +//! funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account +//! pair_, one to take the role of the controller and one to be the frozen stash account (any value locked in +//! stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via +//! the `bond()` function. //! -//! Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible for declaring interest and the _stash_ account stays untouched, without directly interacting in any operation. +//! Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or +//! simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible +//! for declaring interest 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 in other words. 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 election is determined by nominators and their votes. An account can become a validator via the `validate()` call. +//! 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 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 _immediately_, meaning that their votes will be taken into account at the next election round. As mentioned above, a nominator must also place some fund in a stash account, essentially indicating the _weight_ of their vote. In some sense, the nominator bets on the honesty of a set of validators by voting for them, with the goal of having a share of the reward granted to them. Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. 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. +//! 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 _immediately_, meaning that its +//! votes will be taken into account at the next election round. As mentioned above, a nominator must also place some +//! funds in a stash account, essentially indicating 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 a share of the reward granted to them. +//! Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The +//! same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. +//! 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 a misbehavior is reported. One such misbehavior is a validator to be detected as offline more than a certain number of times. Once slashing is determined, a value is deducted from the balance of validator and all the nominators who voted for this validator. Same rules apply to the rewards in the sense of being shared among validator and its associated nominators. +//! 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 temporarily step back 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. +//! 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 //! @@ -72,12 +105,14 @@ //! //! ### 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. +//! 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. +//! 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 //! @@ -94,7 +129,7 @@ //! Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller); //! ``` //! -//! To state desire in becoming a validator: +//! To state desire to become a validator: //! //! ```rust,ignore //! // controller account 4 states desire for validation with the given preferences. @@ -120,14 +155,21 @@ //! //! ### 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._ +//! 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 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 named `SessionReward`. +//! - 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 who had a vote for this validator, proportional to their staked value. -//! - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage (see `set_payee()`), to be one of the following: +//! - 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 who had a vote for this validator, +//! proportional to their staked value. +//! - 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. @@ -135,27 +177,33 @@ //! ### 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 they can be _reported_ before it actually gets slashed via the `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces a `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 amount of times, the actual value that gets deducted from that validator, and every single nominator that voted for it is calculated by multiplying the result of the above point by `2.pow(unstake_threshold)`. -//! - If the previous overflow, then `slot_stake` is used. -//! - If the previous is more than what the validator/nominator has in stake, all of their stake is slashed (`.max(total_stake)` in other words). +//! - 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 actual value that gets deducted from that +//! validator, and every single nominator that voted for it is calculated by multiplying the result of the above point +//! by `2.pow(unstake_threshold)`. +//! - If the previous overflows, then `slot_stake` is used. +//! - If the previous is more than what the validator/nominator has in stake, all of its stake is slashed (`.max(total_stake)`). //! //! ### Additional Fund Management Operations //! //! Any funds already placed into stash can be the target of the following operations: //! -//! - The controller account can free an portion (or all) of the funds using the `unbond()` call. Note that the funds are not immediately accessible, instead, a duration denoted by `BondingDuration` number of eras must pass until the funds can be actually removed. -//! - To actually remove the funds, once the bonding duration is over, the `withdraw_unbonded()` can be used. +//! - 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 actually remove the funds, once the bonding duration is over, `withdraw_unbonded()` can be used. //! - As opposed to the above, additional funds can be added to the stash account via the `bond_extra()` transaction call. //! //! ### Election algorithm details. //! -//! Current election algorithm is implemented based on Phragmén. The reference implementation can be found [here](https://github.com/w3f/consensus/tree/master/NPoS). +//! 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 [`GensisConfig`] for a list of attributed that can be provided. +//! See the [`GensisConfig`] for a list of attributes that can be provided. //! //! ## Related Modules //! @@ -460,6 +508,7 @@ 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)?; @@ -811,7 +860,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 d67612755bc0d..6211557ad525a 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -69,7 +69,7 @@ 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, From 4eb568ccb22782ae0517c5d8687c5c58b87bac9c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Mar 2019 12:19:49 +0100 Subject: [PATCH 17/27] Fix + stabilize leftover reivews. --- srml/staking/src/lib.rs | 13 +++++++++---- srml/staking/src/mock.rs | 22 +++++++++++++++------ srml/staking/src/phragmen.rs | 37 +++++++++++++----------------------- srml/staking/src/tests.rs | 8 +++----- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 4650bff981d77..4b75f1f192167 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -31,7 +31,7 @@ use srml_support::traits::{ LockIdentifier, LockableCurrency, WithdrawReasons }; use session::OnSessionChange; -use primitives::{Perbill}; +use primitives::Perbill; use primitives::traits::{Zero, One, As, StaticLookup, Saturating, Bounded}; #[cfg(feature = "std")] use primitives::{Serialize, Deserialize}; @@ -48,7 +48,11 @@ const MAX_UNSTAKE_THRESHOLD: u32 = 10; // Indicates the initial status of the staker #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] -pub enum StakerStatus { Idle, Validator, Nominator(Vec), } +pub enum StakerStatus { + Idle, + Validator, + Nominator(Vec), +} /// A destination account for payment. #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode)] @@ -166,7 +170,7 @@ pub struct Exposure { pub others: Vec>, } -type BalanceOf = <::Currency as ArithmeticType>::Type; +type BalanceOf = <::Currency as ArithmeticType>::Type; pub trait Trait: system::Trait + session::Trait { /// The staking balance. @@ -277,6 +281,7 @@ decl_storage! { build(|storage: &mut primitives::StorageOverlay, _: &mut primitives::ChildrenStorageOverlay, config: &GenesisConfig| { with_storage(storage, || { for &(ref stash, ref controller, balance, ref status) in &config.stakers { + assert!(T::Currency::free_balance(&stash) >= balance); let _ = >::bond( T::Origin::from(Some(stash.clone()).into()), T::Lookup::unlookup(controller.clone()), @@ -483,7 +488,7 @@ decl_module! { } } -/// An event in this module. +// An event in this module. decl_event!( pub enum Event where Balance = BalanceOf, ::AccountId { /// All validators have been rewarded by the given balance. diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 6a76f350efbea..e91b6015cff44 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -176,21 +176,31 @@ impl ExtBuilder { (11, balance_factor * 1000), (20, balance_factor), (21, balance_factor * 2000), + (30, balance_factor), + (31, balance_factor * 3000), + (40, balance_factor), + (41, balance_factor * 4000), (100, 2000 * balance_factor), (101, 2000 * balance_factor), ] } else { vec![ - (1, 10 * balance_factor), (2, 20 * balance_factor), - (3, 300 * balance_factor), (4, 400 * balance_factor) + (1, 10 * balance_factor), + (2, 20 * balance_factor), + (3, 300 * balance_factor), + (4, 400 * balance_factor), ] } } else { vec![ - (10, balance_factor), (11, balance_factor * 10), - (20, balance_factor), (21, balance_factor * 20), - (30, balance_factor), (31, balance_factor * 30), - (40, balance_factor), (41, balance_factor * 40) + (10, balance_factor), + (11, balance_factor * 10), + (20, balance_factor), + (21, balance_factor * 20), + (30, balance_factor), + (31, balance_factor * 30), + (40, balance_factor), + (41, balance_factor * 40), ] }, existential_deposit: self.existential_deposit, diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index bdaed1fee9760..188c78f944e5b 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// Copyright 2019 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -24,7 +24,7 @@ use crate::{Exposure, BalanceOf, Trait, ValidatorPrefs, IndividualExposure}; // Wrapper around validation candidates some metadata. #[derive(Clone, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug))] +#[cfg_attr(feature = "std", derive(Debug, Default))] pub struct Candidate { // The validator's account pub who: AccountId, @@ -53,9 +53,10 @@ pub struct Nominations { } // Wrapper around a nominator vote and the load of that vote. +// // Referred to as 'edge' in the Phragmén reference implementation. #[derive(Clone, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug))] +#[cfg_attr(feature = "std", derive(Debug, Default))] pub struct Vote { // Account being voted for who: AccountId, @@ -94,9 +95,8 @@ pub fn elect( let stash_balance = stash_of(who.clone()); Candidate { who, - approval_stake: BalanceOf::::zero(), - score: Perquintill::zero(), exposure: Exposure { total: stash_balance, own: stash_balance, others: vec![] }, + ..Default::default() } }).collect::>>>(); @@ -116,7 +116,7 @@ pub fn elect( Nominations { who, nominees: nominees.into_iter() - .map(|n| Vote {who: n, load: Perquintill::zero(), backing_stake: BalanceOf::::zero()}) + .map(|n| Vote { who: n, ..Default::default() }) .collect::>>>(), stake: nominator_stake, load : Perquintill::zero(), @@ -135,23 +135,16 @@ pub fn elect( // Loop 1: initialize score for nominaotion in &nominations { for vote in &nominaotion.nominees { - let candidate = &vote.who; - if let Some(c) = candidates.iter_mut().find(|i| i.who == *candidate) { - let approval_stake = c.approval_stake; - c.score = Perquintill::from_xth(approval_stake.as_()); + if let Some(c) = candidates.iter_mut().find(|i| i.who == vote.who) { + c.score = Perquintill::from_xth(c.approval_stake.as_()); } } } // Loop 2: increment score. - for nominaotion in &nominations { - for vote in &nominaotion.nominees { - let candidate = &vote.who; - if let Some(c) = candidates.iter_mut().find(|i| i.who == *candidate) { - let approval_stake = c.approval_stake; - let temp = - nominaotion.stake.as_() - * *nominaotion.load - / approval_stake.as_(); + for nomination in &nominations { + for vote in &nomination.nominees { + if let Some(c) = candidates.iter_mut().find(|i| i.who == vote.who) { + let temp = nomination.stake.as_() * *nomination.load / c.approval_stake.as_(); c.score = Perquintill::from_quintillionths(*c.score + temp); } } @@ -166,11 +159,7 @@ pub fn elect( for n in &mut nominations { for v in &mut n.nominees { if v.who == winner.who { - v.load = - Perquintill::from_quintillionths( - *winner.score - - *n.load - ); + v.load = Perquintill::from_quintillionths(*winner.score - *n.load); n.load = winner.score; } } diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 7921d7f313027..1bcbe7d7e610d 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -761,7 +761,6 @@ fn double_staking_should_fail() { fn session_and_eras_work() { with_externalities(&mut ExtBuilder::default() .sessions_per_era(2) - .reward(10) .build(), || { assert_eq!(Staking::era_length(), 2); @@ -1066,7 +1065,6 @@ fn bond_extra_and_withdraw_unbonded_works() { // * it can unbond a portion of its funds from the stash account. // * Once the unbonding period is done, it can actually take the funds out of the stash. with_externalities(&mut ExtBuilder::default() - .reward(10) // it is the default, just for verbosity .nominate(false) .build(), || { @@ -1407,8 +1405,8 @@ fn phragmen_poc_works() { // This is only because 30 has been bonded on the fly, exposures are stored at the very end of the era. // 35 is the point, not 'own' Exposure. - assert_eq!(Staking::stakers(30).own, 0); - assert_eq!(Staking::stakers(30).total, 0 + 35); + assert_eq!(Staking::stakers(30).own, 1000); + assert_eq!(Staking::stakers(30).total, 1000 + 35); // same as above. +25 is the point assert_eq!(Staking::stakers(20).own, 2010); assert_eq!(Staking::stakers(20).total, 2010 + 25); @@ -1482,7 +1480,7 @@ fn phragmen_election_works() { assert_eq!(winner_10.exposure.others[0].value, 21); assert_eq!(winner_10.exposure.others[1].value, 5); - assert_eq!(winner_30.exposure.total, 23); + assert_eq!(winner_30.exposure.total, 1000 + 23); assert_eq!(winner_30.score, Perquintill::from_quintillionths(42222222222222222)); assert_eq!(winner_30.exposure.others[0].value, 23); }) From 9b026cc50ac18c929d1fde0cc32100c12c83397b Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Mar 2019 12:25:19 +0100 Subject: [PATCH 18/27] Remove unused test parameters --- srml/staking/src/mock.rs | 59 +++++++++------------------------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index e91b6015cff44..e3dc95664c350 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -79,7 +79,6 @@ pub struct ExtBuilder { session_length: u64, sessions_per_era: u64, current_era: u64, - monied: bool, reward: u64, validator_pool: bool, nominate: bool, @@ -94,7 +93,6 @@ impl Default for ExtBuilder { session_length: 1, sessions_per_era: 1, current_era: 0, - monied: true, reward: 10, validator_pool: false, nominate: true, @@ -121,16 +119,7 @@ impl ExtBuilder { self.current_era = current_era; self } - pub fn _monied(mut self, monied: bool) -> Self { - self.monied = monied; - self - } - pub fn reward(mut self, reward: u64) -> Self { - self.reward = reward; - self - } pub fn validator_pool(mut self, validator_pool: bool) -> Self { - // NOTE: this should only be set to true with monied = false. self.validator_pool = validator_pool; self } @@ -165,44 +154,22 @@ impl ExtBuilder { keys: vec![], }.assimilate_storage(&mut t, &mut c); let _ = balances::GenesisConfig::{ - balances: if self.monied { - if self.reward > 0 { - vec![ - (1, 10 * balance_factor), - (2, 20 * balance_factor), - (3, 300 * balance_factor), - (4, 400 * balance_factor), - (10, balance_factor), - (11, balance_factor * 1000), - (20, balance_factor), - (21, balance_factor * 2000), - (30, balance_factor), - (31, balance_factor * 3000), - (40, balance_factor), - (41, balance_factor * 4000), - (100, 2000 * balance_factor), - (101, 2000 * balance_factor), - ] - } else { - vec![ - (1, 10 * balance_factor), - (2, 20 * balance_factor), - (3, 300 * balance_factor), - (4, 400 * balance_factor), - ] - } - } else { - vec![ + balances: vec![ + (1, 10 * balance_factor), + (2, 20 * balance_factor), + (3, 300 * balance_factor), + (4, 400 * balance_factor), (10, balance_factor), - (11, balance_factor * 10), + (11, balance_factor * 1000), (20, balance_factor), - (21, balance_factor * 20), + (21, balance_factor * 2000), (30, balance_factor), - (31, balance_factor * 30), + (31, balance_factor * 3000), (40, balance_factor), - (41, balance_factor * 40), - ] - }, + (41, balance_factor * 4000), + (100, 2000 * balance_factor), + (101, 2000 * balance_factor), + ], existential_deposit: self.existential_deposit, transfer_fee: 0, creation_fee: 0, @@ -232,7 +199,7 @@ impl ExtBuilder { minimum_validator_count: self.minimum_validator_count, bonding_duration: self.sessions_per_era * self.session_length * 3, session_reward: Perbill::from_millionths((1000000 * self.reward / balance_factor) as u32), - offline_slash: if self.monied { Perbill::from_percent(40) } else { Perbill::zero() }, + offline_slash: Perbill::from_percent(40), current_session_reward: self.reward, current_offline_slash: 20, offline_slash_grace: 0, From a8075c74233e6b6347f756e76a89657e7182397c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Mar 2019 12:26:17 +0100 Subject: [PATCH 19/27] Fix typo. --- srml/staking/src/phragmen.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 188c78f944e5b..9017ed9549d59 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -133,8 +133,8 @@ pub fn elect( // Main election loop for _round in 0..rounds { // Loop 1: initialize score - for nominaotion in &nominations { - for vote in &nominaotion.nominees { + for nomination in &nominations { + for vote in &nomination.nominees { if let Some(c) = candidates.iter_mut().find(|i| i.who == vote.who) { c.score = Perquintill::from_xth(c.approval_stake.as_()); } From a33205ba8440325cc2191177979f40a71c5575c3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 15 Mar 2019 14:33:44 +0100 Subject: [PATCH 20/27] Merge redundant loops --- srml/staking/src/lib.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 4b75f1f192167..38ed6cfb3da83 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -682,14 +682,6 @@ impl Module { min_validator_count ); - // Figure out the minimum stake behind a slot. - let slot_stake = elected_candidates - .iter() - .min_by_key(|c| c.exposure.total) - .map(|c| c.exposure.total) - .unwrap_or_default(); - >::put(&slot_stake); - // Clear Stakers and reduce their slash_count. for v in >::validators().iter() { >::remove(v); @@ -699,10 +691,13 @@ impl Module { } } - // Populate Stakers. + // Populate Stakers and figure out the minimum stake behind a slot. + let mut slot_stake = elected_candidates[0].exposure.total; for candidate in &elected_candidates { + if candidate.exposure.total < slot_stake { slot_stake = candidate.exposure.total; } >::insert(candidate.who.clone(), candidate.exposure.clone()); } + >::put(&slot_stake); // Set the new validator set. >::set_validators( From cfb39b92e75b2f47d231d2421cfb4e18f5cb6f2a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sat, 16 Mar 2019 10:59:54 +0100 Subject: [PATCH 21/27] Adds phantom self-vote --- srml/staking/src/lib.rs | 7 +++++-- srml/staking/src/phragmen.rs | 24 ++++++++++++++++++------ srml/staking/src/tests.rs | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index b9ead1f12785d..08135a4db8798 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -246,9 +246,12 @@ const MAX_UNSTAKE_THRESHOLD: u32 = 10; // Indicates the initial status of the staker #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] -pub enum StakerStatus { +pub enum StakerStatus { + // Chilling. Idle, + // Declared state in validating or already participating in it. Validator, + // Nominating for a group of other stakers. Nominator(Vec), } @@ -872,7 +875,7 @@ impl Module { let rounds = || >::get() as usize; let validators = || >::enumerate(); let nominators = || >::enumerate(); - let stash_of = |w| Self::stash_balance(&w); + let stash_of = |w: &T::AccountId| -> BalanceOf { Self::stash_balance(w) }; let min_validator_count = Self::minimum_validator_count() as usize; let elected_candidates = phragmen::elect::( rounds, diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 5769192bd99cb..02380fd2c35b0 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -85,14 +85,14 @@ pub fn elect( FN: Fn() -> Box) >>, - FS: Fn(T::AccountId) -> BalanceOf, + for <'r> FS: Fn(&'r T::AccountId) -> BalanceOf, { let rounds = get_rounds(); let mut elected_candidates; // 1- Pre-process candidates and place them in a container let mut candidates = get_validators().map(|(who, _)| { - let stash_balance = stash_of(who.clone()); + let stash_balance = stash_of(&who); Candidate { who, exposure: Exposure { total: stash_balance, own: stash_balance, others: vec![] }, @@ -106,7 +106,7 @@ pub fn elect( // 2- Collect the nominators with the associated votes. // Also collect approval stake along the way. let mut nominations = get_nominators().map(|(who, nominees)| { - let nominator_stake = stash_of(who.clone()); + let nominator_stake = stash_of(&who); for n in &nominees { candidates.iter_mut().filter(|i| i.who == *n).for_each(|c| { c.approval_stake += nominator_stake; @@ -119,9 +119,19 @@ pub fn elect( .map(|n| Vote { who: n, ..Default::default() }) .collect::>>>(), stake: nominator_stake, - load : Perquintill::zero(), + load: Perquintill::zero(), } }).collect::>>>(); + + // 2.1- Add self-vote + candidates.iter().for_each(|v| { + nominations.push(Nominations { + who: v.who.clone(), + nominees: vec![Vote { who: v.who.clone(), ..Default::default() }], + stake: v.exposure.total, + load: Perquintill::zero(), + }) + }); // 3- optimization: // Candidates who have 0 stake => have no votes or all null-votes. Kick them out not. @@ -171,9 +181,10 @@ pub fn elect( // 4.1- Update backing stake of candidates and nominators for n in &mut nominations { + let nominator = n.who.clone(); 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) { + if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == v.who && c.who != nominator) { 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 @@ -189,8 +200,9 @@ pub fn elect( elected_candidates = candidates; // `Exposure.others` still needs an update for n in &mut nominations { + let nominator = n.who.clone(); for v in &mut n.nominees { - if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == v.who) { + if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == v.who && c.who != nominator) { c.exposure.total += n.stake; c.exposure.others.push( IndividualExposure { who: n.who.clone(), value: n.stake } diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 1bcbe7d7e610d..bff1043839780 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1447,7 +1447,7 @@ fn phragmen_election_works() { let rounds = || 2 as usize; let validators = || >::enumerate(); let nominators = || >::enumerate(); - let stash_of = |w| Staking::stash_balance(&w); + let stash_of = |w: &u64| -> u64 { Staking::stash_balance(w) }; let min_validator_count = Staking::minimum_validator_count() as usize; let winners = phragmen::elect::( From a4c298c466253dc9ee28c0edd72ce4bcd1feeb84 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 17 Mar 2019 16:14:57 +0100 Subject: [PATCH 22/27] Fix broken tests. --- srml/staking/src/mock.rs | 18 +- srml/staking/src/phragmen.rs | 32 +-- srml/staking/src/tests.rs | 415 +++++++++++++++++++---------------- 3 files changed, 256 insertions(+), 209 deletions(-) diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index e3dc95664c350..a639ffd5299b2 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -84,6 +84,7 @@ pub struct ExtBuilder { nominate: bool, validator_count: u32, minimum_validator_count: u32, + fare: bool, } impl Default for ExtBuilder { @@ -98,6 +99,7 @@ impl Default for ExtBuilder { nominate: true, validator_count: 2, minimum_validator_count: 0, + fare: true } } } @@ -136,6 +138,10 @@ impl ExtBuilder { self.minimum_validator_count = count; self } + pub fn fare(mut self, is_fare: bool) -> Self { + self.fare = is_fare; + self + } pub fn build(self) -> runtime_io::TestExternalities { let (mut t, mut c) = system::GenesisConfig::::default().build_storage().unwrap(); let balance_factor = if self.existential_deposit > 0 { @@ -164,9 +170,9 @@ impl ExtBuilder { (20, balance_factor), (21, balance_factor * 2000), (30, balance_factor), - (31, balance_factor * 3000), + (31, balance_factor * 2000), (40, balance_factor), - (41, balance_factor * 4000), + (41, balance_factor * 2000), (100, 2000 * balance_factor), (101, 2000 * balance_factor), ], @@ -181,16 +187,16 @@ impl ExtBuilder { stakers: if self.validator_pool { vec![ (11, 10, balance_factor * 1000, StakerStatus::::Validator), - (21, 20, balance_factor * 2000, StakerStatus::::Validator), - (31, 30, balance_factor * 3000, if self.validator_pool { StakerStatus::::Validator } else { StakerStatus::::Idle }), - (41, 40, balance_factor * 4000, if self.validator_pool { StakerStatus::::Validator } else { StakerStatus::::Idle }), + (21, 20, balance_factor * if self.fare { 1000 } else { 2000 }, StakerStatus::::Validator), + (31, 30, balance_factor * 1000, if self.validator_pool { StakerStatus::::Validator } else { StakerStatus::::Idle }), + (41, 40, balance_factor * 1000, if self.validator_pool { StakerStatus::::Validator } else { StakerStatus::::Idle }), // nominator (101, 100, balance_factor * 500, if self.nominate { StakerStatus::::Nominator(vec![10, 20]) } else { StakerStatus::::Nominator(vec![]) }) ] } else { vec![ (11, 10, balance_factor * 1000, StakerStatus::::Validator), - (21, 20, balance_factor * 2000, StakerStatus::::Validator), + (21, 20, balance_factor * if self.fare { 1000 } else { 2000 }, StakerStatus::::Validator), // nominator (101, 100, balance_factor * 500, if self.nominate { StakerStatus::::Nominator(vec![10, 20]) } else { StakerStatus::::Nominator(vec![]) }) ] diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 02380fd2c35b0..53b383b3502f7 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -103,9 +103,21 @@ pub fn elect( // Just to be used when we are below minimum validator count let original_candidates = candidates.clone(); + // 1.1- Add phantom votes. + let mut nominations: Vec>> = Vec::with_capacity(candidates.len()); + candidates.iter_mut().for_each(|c| { + c.approval_stake += c.exposure.total; + nominations.push(Nominations { + who: c.who.clone(), + nominees: vec![ Vote { who: c.who.clone(), ..Default::default() }], + stake: c.exposure.total, + load: Perquintill::zero(), + }) + }); + // 2- Collect the nominators with the associated votes. // Also collect approval stake along the way. - let mut nominations = get_nominators().map(|(who, nominees)| { + nominations.extend(get_nominators().map(|(who, nominees)| { let nominator_stake = stash_of(&who); for n in &nominees { candidates.iter_mut().filter(|i| i.who == *n).for_each(|c| { @@ -121,18 +133,11 @@ pub fn elect( stake: nominator_stake, load: Perquintill::zero(), } - }).collect::>>>(); + })); + + println!("Candidates : {:?}", candidates); + println!("Nominations: {:?}", nominations); - // 2.1- Add self-vote - candidates.iter().for_each(|v| { - nominations.push(Nominations { - who: v.who.clone(), - nominees: vec![Vote { who: v.who.clone(), ..Default::default() }], - stake: v.exposure.total, - load: Perquintill::zero(), - }) - }); - // 3- optimization: // Candidates who have 0 stake => have no votes or all null-votes. Kick them out not. let mut candidates = candidates.into_iter().filter(|c| c.approval_stake > BalanceOf::::zero()) @@ -215,6 +220,7 @@ pub fn elect( elected_candidates = original_candidates; } } - + + println!("Elected : {:?}", elected_candidates); elected_candidates } \ No newline at end of file diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index bff1043839780..f3daa69d57779 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -39,7 +39,7 @@ fn basic_setup_works() { // Account 10 controls the stash from account 11, which is 100 * balance_factor units assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] })); // Account 20 controls the stash from account 21, which is 200 * balance_factor units - assert_eq!(Staking::ledger(&20), Some(StakingLedger { stash: 21, total: 2000, active: 2000, unlocking: vec![] })); + assert_eq!(Staking::ledger(&20), Some(StakingLedger { stash: 21, total: 1000, active: 1000, unlocking: vec![] })); // Account 1 does not control any stash assert_eq!(Staking::ledger(&1), None); @@ -53,9 +53,10 @@ fn basic_setup_works() { assert_eq!(Staking::ledger(100), Some(StakingLedger { stash: 101, total: 500, active: 500, unlocking: vec![] })); assert_eq!(Staking::nominators(100), vec![10, 20]); - // Account 10 is exposed by 100 * balance_factor from their own stash in account 11 + // Account 10 is exposed by 1000 * balance_factor from their own stash in account 11 + the default nominator vote assert_eq!(Staking::stakers(10), Exposure { total: 1500, own: 1000, others: vec![ IndividualExposure { who: 100, value: 500 }] }); - assert_eq!(Staking::stakers(20), Exposure { total: 2500, own: 2000, others: vec![ IndividualExposure { who: 100, value: 500 }] }); + // Account 20 is exposed by 1000 * balance_factor from their own stash in account 21 + the default nominator vote + assert_eq!(Staking::stakers(20), Exposure { total: 1500, own: 1000, others: vec![ IndividualExposure { who: 100, value: 500 }] }); // The number of validators required. assert_eq!(Staking::validator_count(), 2); @@ -68,7 +69,7 @@ fn basic_setup_works() { assert_eq!(Staking::current_session_reward(), 10); // initial slot_stake - assert_eq!(Staking::slot_stake(), 1500); + assert_eq!(Staking::slot_stake(), 1500); // initial slash_count of validators assert_eq!(Staking::slash_count(&10), 0); @@ -440,42 +441,35 @@ fn staking_should_work() { with_externalities(&mut ExtBuilder::default() .sessions_per_era(3) .nominate(false) + .fare(false) // to give 20 more staked value .build(), || { - assert_eq!(Staking::era_length(), 3); // remember + compare this along with the test. assert_eq!(Session::validators(), vec![20, 10]); + assert_ok!(Staking::set_bonding_duration(2)); assert_eq!(Staking::bonding_duration(), 2); // put some money in account that we'll use. - for i in 1..5 { Balances::set_free_balance(&i, 1000); } - - // bond one account pair and state interest in nomination. - // this is needed to keep 10 and 20 in the validator list with phragmen - assert_ok!(Staking::bond(Origin::signed(1), 2, 500, RewardDestination::default())); - assert_ok!(Staking::nominate(Origin::signed(2), vec![20, 4])); + for i in 1..5 { Balances::set_free_balance(&i, 2000); } // --- Block 1: System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + assert_eq!(Staking::current_era(), 0); // add a new candidate for being a validator. account 3 controlled by 4. - assert_ok!(Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller)); // balance of 3 = 3000, stashed = 1500 + assert_ok!(Staking::bond(Origin::signed(3), 4, 1500, RewardDestination::Controller)); + assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default())); - Session::check_rotate_session(System::block_number()); - assert_eq!(Staking::current_era(), 0); - // No effects will be seen so far.s + // No effects will be seen so far. assert_eq!(Session::validators(), vec![20, 10]); - // --- Block 2: System::set_block_number(2); - // Explicitly state the desire to validate - // note that the controller account will state interest as representative of the stash-controller pair. - assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default())); - Session::check_rotate_session(System::block_number()); assert_eq!(Staking::current_era(), 0); + // No effects will be seen so far. Era has not been yet triggered. assert_eq!(Session::validators(), vec![20, 10]); @@ -486,32 +480,26 @@ fn staking_should_work() { // 2 only voted for 4 and 20 assert_eq!(Session::validators().len(), 2); - assert_eq!(Session::validators(), vec![4, 20]); + assert_eq!(Session::validators(), vec![20, 4]); assert_eq!(Staking::current_era(), 1); // --- Block 4: Unstake 4 as a validator, freeing up the balance stashed in 3 System::set_block_number(4); + Session::check_rotate_session(System::block_number()); - // unlock the entire stashed value. - // Note that this will ne be enough to remove 4 as a validator candidate! - Staking::unbond(Origin::signed(4), Staking::ledger(&4).unwrap().active).unwrap(); - // explicit chill indicated that 4 no longer wants to be a validator. + // 4 will chill Staking::chill(Origin::signed(4)).unwrap(); - // nominator votes for 10 - assert_ok!(Staking::nominate(Origin::signed(2), vec![20, 10])); - - Session::check_rotate_session(System::block_number()); // nothing should be changed so far. - assert_eq!(Session::validators(), vec![4, 20]); + assert_eq!(Session::validators(), vec![20, 4]); assert_eq!(Staking::current_era(), 1); // --- Block 5: nothing. 4 is still there. System::set_block_number(5); Session::check_rotate_session(System::block_number()); - assert_eq!(Session::validators(), vec![4, 20]); + assert_eq!(Session::validators(), vec![20, 4]); assert_eq!(Staking::current_era(), 1); @@ -521,6 +509,12 @@ fn staking_should_work() { assert_eq!(Staking::current_era(), 2); assert_eq!(Session::validators().contains(&4), false); assert_eq!(Session::validators(), vec![20, 10]); + + // Note: the stashed value of 4 is still lock + assert_eq!(Staking::ledger(&4), Some(StakingLedger { stash: 3, total: 1500, active: 1500, unlocking: vec![] })); + // e.g. it cannot spend more than 500 that it has free from the total 2000 + assert_noop!(Balances::reserve(&3, 501), "account liquidity restrictions prevent withdrawal"); + assert_ok!(Balances::reserve(&3, 409)); }); } @@ -532,21 +526,14 @@ fn less_than_needed_candidates_works() { .minimum_validator_count(1) .validator_count(3) .nominate(false) - .validator_pool(true) .build(), || { assert_eq!(Staking::era_length(), 1); assert_eq!(Staking::validator_count(), 3); - assert_eq!(Staking::minimum_validator_count(), 1); - assert_eq!(Staking::validator_count(), 3); // initial validators - assert_eq!(Session::validators(), vec![40, 30, 20, 10]); - - // only one nominator will exist and it will - assert_ok!(Staking::bond(Origin::signed(1), 2, 500, RewardDestination::default())); - assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20])); + assert_eq!(Session::validators(), vec![20, 10]); // 10 and 20 are now valid candidates. // trigger era @@ -557,9 +544,9 @@ fn less_than_needed_candidates_works() { // both validators will be chosen again. NO election algorithm is even executed. assert_eq!(Session::validators(), vec![20, 10]); - // But the exposure is updated in a simple way. Each nominators vote is applied - assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![2]); - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![2]); + // But the exposure is updated in a simple way. No external votes exists. This is purely self-vote. + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![]); }); } @@ -568,17 +555,14 @@ fn no_candidate_emergency_condition() { // Test the situation where the number of validators are less than `ValidatorCount` and less than // The expected behavior is to choose all candidates from the previous era. with_externalities(&mut ExtBuilder::default() - .minimum_validator_count(1) - .validator_count(3) - .nominate(false) + .minimum_validator_count(10) + .validator_count(15) .validator_pool(true) + .nominate(false) .build(), || { assert_eq!(Staking::era_length(), 1); - assert_eq!(Staking::validator_count(), 3); - - assert_eq!(Staking::minimum_validator_count(), 1); - assert_eq!(Staking::validator_count(), 3); + assert_eq!(Staking::validator_count(), 15); // initial validators assert_eq!(Session::validators(), vec![40, 30, 20, 10]); @@ -600,27 +584,39 @@ fn nominating_and_rewards_should_work() { // // PHRAGMEN OUTPUT: running this test with the reference impl gives: // - // Votes [('2', 500, ['10', '20', '30']), ('4', 500, ['10', '20', '40'])] + // Votes [('10', 1000, ['10']), ('20', 1000, ['20']), ('30', 1000, ['30']), ('40', 1000, ['40']), ('2', 1000, ['10', '20', '30']), ('4', 1000, ['10', '20', '40'])] // Sequential Phragmén gives - // 10 is elected with stake 500.0 and score 0.001 - // 20 is elected with stake 500.0 and score 0.002 - // - // 2 has load 0.002 and supported - // 10 with stake 250.0 20 with stake 250.0 30 with stake 0.0 - // 4 has load 0.002 and supported - // 10 with stake 250.0 20 with stake 250.0 40 with stake 0.0 + // 10 is elected with stake 2200.0 and score 0.0003333333333333333 + // 20 is elected with stake 1800.0 and score 0.0005555555555555556 + + // 10 has load 0.0003333333333333333 and supported + // 10 with stake 1000.0 + // 20 has load 0.0005555555555555556 and supported + // 20 with stake 1000.0 + // 30 has load 0 and supported + // 30 with stake 0 + // 40 has load 0 and supported + // 40 with stake 0 + // 2 has load 0.0005555555555555556 and supported + // 10 with stake 600.0 20 with stake 400.0 30 with stake 0.0 + // 4 has load 0.0005555555555555556 and supported + // 10 with stake 600.0 20 with stake 400.0 40 with stake 0.0 + + with_externalities(&mut ExtBuilder::default() .nominate(false) .validator_pool(true) .build(), || { - // initial validators - assert_eq!(Session::validators(), vec![40, 30, 20, 10]); + // initial validators -- everyone is actually even. + assert_eq!(Session::validators(), vec![40, 30]); // Set payee to controller assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(Origin::signed(30), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(Origin::signed(40), RewardDestination::Controller)); // default reward for the first session. let session_reward = 10; @@ -637,53 +633,59 @@ fn nominating_and_rewards_should_work() { // bond two account pairs and state interest in nomination. // 2 will nominate for 10, 20, 30 - assert_ok!(Staking::bond(Origin::signed(1), 2, 500, RewardDestination::Controller)); + assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller)); assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20, 30])); // 4 will nominate for 10, 20, 40 - assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Stash)); + assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); assert_ok!(Staking::nominate(Origin::signed(4), vec![10, 20, 40])); System::set_block_number(1); Session::check_rotate_session(System::block_number()); assert_eq!(Staking::current_era(), 1); + // 10 and 20 have more votes, they will be chosen by phragmen. assert_eq!(Session::validators(), vec![20, 10]); - // validators must have already received some rewards. - assert_eq!(Balances::total_balance(&10), initial_balance + session_reward); - assert_eq!(Balances::total_balance(&20), initial_balance + session_reward); + + // OLD validators must have already received some rewards. + assert_eq!(Balances::total_balance(&40), 1 + session_reward); + assert_eq!(Balances::total_balance(&30), 1 + session_reward); // ------ check the staked value of all parties. - // total expo of 10, with 500 coming from nominators (externals), according to phragmen. + + // total expo of 10, with 1200 coming from nominators (externals), according to phragmen. assert_eq!(Staking::stakers(10).own, 1000); - assert_eq!(Staking::stakers(10).total, 1000 + 500); - // 2 and 4 supported 10, each with stake 250, according to phragmen. - assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![250, 250]); + assert_eq!(Staking::stakers(10).total, 1000 + 800); + // 2 and 4 supported 10, each with stake 600, according to phragmen. + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![400, 400]); assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); // total expo of 20, with 500 coming from nominators (externals), according to phragmen. - assert_eq!(Staking::stakers(20).own, 2000); - assert_eq!(Staking::stakers(20).total, 2000 + 500); + assert_eq!(Staking::stakers(20).own, 1000); + assert_eq!(Staking::stakers(20).total, 1000 + 1200); // 2 and 4 supported 20, each with stake 250, according to phragmen. - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![250, 250]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![600, 600]); assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); + + // They are not chosen anymore + assert_eq!(Staking::stakers(30).total, 0); + assert_eq!(Staking::stakers(40).total, 0); System::set_block_number(2); + Session::check_rotate_session(System::block_number()); // next session reward. let new_session_reward = Staking::session_reward() * Staking::slot_stake(); // nothing else will happen, era ends and rewards are paid again, // it is expected that nominators will also be paid. See below - Session::check_rotate_session(System::block_number()); - // Nominator 2: has [250/1500 ~ 1/6 from 10] + [250/2500 ~ 1/10 from 20]'s reward. ==> 1/6 + 1/10 - assert_eq!(Balances::total_balance(&2), initial_balance + (new_session_reward/6 + new_session_reward/10)); - // The Associated validator will get the other 4/6 --> 1500(total) minus 1/6(250) by each nominator -> 6/6 - 1/6 - 1/6 - assert_eq!(Balances::total_balance(&10), initial_balance + session_reward + 4*new_session_reward/6) ; + // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 + assert_eq!(Balances::total_balance(&2), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11)); + // Nominator 4: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 + assert_eq!(Balances::total_balance(&4), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11)); - // Nominator 4: has [250/1500 ~ 1/6 from 10] + [250/2500 ~ 1/10 from 20]'s reward. ==> 1/6 + 1/10 - // This nominator chose stash as the reward destination. This means that the reward will go to 3, which is bonded as the stash of 4. - assert_eq!(Balances::total_balance(&3), initial_balance + (new_session_reward/6 + new_session_reward/10)); - // The Associated validator will get the other 8/10 --> 2500(total) minus 1/10(250) by each nominator -> 10/10 - 1/10 - 1/10 - assert_eq!(Balances::total_balance(&20), initial_balance + session_reward + 8*new_session_reward/10); + // 10 got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 + assert_eq!(Balances::total_balance(&10), initial_balance + 5*new_session_reward/9) ; + // 10 got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = 5/11 + assert_eq!(Balances::total_balance(&20), initial_balance + 5*new_session_reward/11); }); } @@ -831,13 +833,13 @@ fn session_and_eras_work() { #[test] fn cannot_transfer_staked_balance() { // Tests that a stash account cannot transfer funds - with_externalities(&mut ExtBuilder::default().build(), || { + with_externalities(&mut ExtBuilder::default().nominate(false).build(), || { // Confirm account 11 is stashed assert_eq!(Staking::bonded(&11), Some(10)); // Confirm account 11 has some free balance assert_eq!(Balances::free_balance(&11), 1000); // Confirm account 11 (via controller 10) is totally staked - assert_eq!(Staking::stakers(&10).total, 1000 + 500); + assert_eq!(Staking::stakers(&10).total, 1000); // Confirm account 11 cannot transfer as a result assert_noop!(Balances::transfer(Origin::signed(11), 20, 1), "account liquidity restrictions prevent withdrawal"); @@ -848,6 +850,30 @@ fn cannot_transfer_staked_balance() { }); } +#[test] +fn cannot_transfer_staked_balance_2() { + // Tests that a stash account cannot transfer funds + // Same test as above but with 20 + // 21 has 2000 free balance but 1000 at stake + with_externalities(&mut ExtBuilder::default() + .nominate(false) + .fare(true) + .build(), + || { + // Confirm account 21 is stashed + assert_eq!(Staking::bonded(&21), Some(20)); + // Confirm account 21 has some free balance + assert_eq!(Balances::free_balance(&21), 2000); + // Confirm account 21 (via controller 20) is totally staked + assert_eq!(Staking::stakers(&20).total, 1000); + // Confirm account 21 cannot transfer more than 1000 + assert_noop!(Balances::transfer(Origin::signed(21), 20, 1500), "account liquidity restrictions prevent withdrawal"); + + // Confirm that account 21 can transfer less than 1000 + assert_ok!(Balances::transfer(Origin::signed(21), 20, 500)); + }); +} + #[test] fn cannot_reserve_staked_balance() { // Checks that a bonded account cannot reserve balance from free balance @@ -871,7 +897,7 @@ fn cannot_reserve_staked_balance() { #[test] fn reward_destination_works() { // Rewards go to the correct destination as determined in Payee - with_externalities(&mut ExtBuilder::default().build(), || { + with_externalities(&mut ExtBuilder::default().nominate(false).build(), || { // Check that account 10 is a validator assert!(>::exists(10)); // Check the balance of the validator account @@ -895,13 +921,11 @@ fn reward_destination_works() { // Check current session reward is 10 assert_eq!(current_session_reward, 10); // Check that reward went to the stash account of validator - // 1/3 of the reward is for the nominator. - let validator_reward = (10. * (2./3.)) as u64; // = 6 - assert_eq!(Balances::free_balance(&11), 1000 + validator_reward); + assert_eq!(Balances::free_balance(&11), 1000 + current_session_reward); // Check that amount at stake increased accordingly - assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 6, active: 1000 + 6, unlocking: vec![] })); + assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 10, active: 1000 + 10, unlocking: vec![] })); // Update current session reward - current_session_reward = Staking::current_session_reward(); + current_session_reward = Staking::current_session_reward(); // 1010 (1* slot_stake) //Change RewardDestination to Stash >::insert(&10, RewardDestination::Stash); @@ -914,18 +938,19 @@ fn reward_destination_works() { // Check that RewardDestination is Stash assert_eq!(Staking::payee(&10), RewardDestination::Stash); // Check that reward went to the stash account - let new_validator_reward = ((1000 + 6) as f64 / ( (1000 + 6) + (500 + 4) ) as f64) * current_session_reward as f64; - assert_eq!(Balances::free_balance(&11), 1000 + validator_reward + new_validator_reward as u64); - // Check that amount at stake is not increased - assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1006, active: 1006, unlocking: vec![] })); + assert_eq!(Balances::free_balance(&11), 1000 + 10 + current_session_reward); + // Record this value + let recorded_stash_balance = 1000 + 10 + current_session_reward; + + // Check that amount at stake is NOT increased + assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 10, active: 1000 + 10, unlocking: vec![] })); - //Change RewardDestination to Controller + // Change RewardDestination to Controller >::insert(&10, RewardDestination::Controller); // Check controller balance assert_eq!(Balances::free_balance(&10), 1); - // Move forward the system for payment System::set_block_number(3); Timestamp::set_timestamp(15); @@ -934,10 +959,11 @@ fn reward_destination_works() { // Check that RewardDestination is Controller assert_eq!(Staking::payee(&10), RewardDestination::Controller); // Check that reward went to the controller account - let reward_of = |w| Staking::stakers(w).own * Staking::current_session_reward() / Staking::stakers(w).total; - assert_eq!(Balances::free_balance(&10), 1 + reward_of(&10)); - // Check that amount at stake is not increased - assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1006, active: 1006, unlocking: vec![] })); + assert_eq!(Balances::free_balance(&10), 1 + 1010); + // Check that amount at stake is NOT increased + assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 10, active: 1000 + 10, unlocking: vec![] })); + // Check that amount in staked account is NOT increased. + assert_eq!(Balances::free_balance(&11), recorded_stash_balance); }); } @@ -1162,13 +1188,19 @@ fn slot_stake_is_least_staked_validator_and_limits_maximum_punishment() { // Test that slot_stake is the maximum punishment that can happen to a validator // Note that rewardDestination is the stash account by default // Note that unlike reward slash will affect free_balance, not the stash account. - with_externalities(&mut ExtBuilder::default().nominate(false).build(), || { + with_externalities(&mut ExtBuilder::default() + .nominate(false) + .fare(false) + .build(), + || { + // Give the man some money. // Confirm validator count is 2 assert_eq!(Staking::validator_count(), 2); // Confirm account 10 and 20 are validators assert!(>::exists(&10) && >::exists(&20)); // Confirm 10 has less stake than 20 assert!(Staking::stakers(&10).total < Staking::stakers(&20).total); + assert_eq!(Staking::stakers(&10).total, 1000); assert_eq!(Staking::stakers(&20).total, 2000); @@ -1337,92 +1369,78 @@ fn on_free_balance_zero_stash_removes_nominator() { fn phragmen_poc_works() { // Tests the POC test of the phragmen, mentioned in the paper and reference implementation. // Initial votes: - // vote_list = [ - // ("A", 10.0, ["X", "Y"]), - // ("B", 20.0, ["X", "Z"]), - // ("C", 30.0, ["Y", "Z"]) - // ] + // Votes [ + // ('2', 500, ['10', '20', '30']), + // ('4', 500, ['10', '20', '40']), + // ('10', 1000, ['10']), + // ('20', 1000, ['20']), + // ('30', 1000, ['30']), + // ('40', 1000, ['40'])] // // Sequential Phragmén gives - // Z is elected with stake 35.0 and score 0.02 - // Y is elected with stake 25.0 and score 0.04 - // - // A has load 0.04 and supported - // X with stake 0.0 Y with stake 10.0 - // B has load 0.02 and supported - // X with stake 0.0 Z with stake 20.0 - // C has load 0.04 and supported - // Y with stake 15.0 Z with stake 15.0 - // - // NOTE: doesn't X/Y/Z's stash value make a difference here in phragmen? + // 10 is elected with stake 1666.6666666666665 and score 0.0005 + // 20 is elected with stake 1333.3333333333333 and score 0.00075 + + // 2 has load 0.00075 and supported + // 10 with stake 333.3333333333333 20 with stake 166.66666666666666 30 with stake 0.0 + // 4 has load 0.00075 and supported + // 10 with stake 333.3333333333333 20 with stake 166.66666666666666 40 with stake 0.0 + // 10 has load 0.0005 and supported + // 10 with stake 1000.0 + // 20 has load 0.00075 and supported + // 20 with stake 1000.0 + // 30 has load 0 and supported + // 30 with stake 0 + // 40 has load 0 and supported + // 40 with stake 0 with_externalities(&mut ExtBuilder::default() .nominate(false) + .validator_pool(true) .build(), || { - // initial setup of 10 and 20, both validators. - assert_eq!(Session::validators(), vec![20, 10]); + // We don't really care about this. At this point everything is even. + // assert_eq!(Session::validators(), vec![40, 30]); assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] })); - assert_eq!(Staking::ledger(&20), Some(StakingLedger { stash: 21, total: 2000, active: 2000, unlocking: vec![] })); - - assert_eq!(Staking::validators(10), ValidatorPrefs::default()); - assert_eq!(Staking::validators(20), ValidatorPrefs::default()); - - assert_eq!(Balances::free_balance(10), 1); - assert_eq!(Balances::free_balance(20), 1); + assert_eq!(Staking::ledger(&20), Some(StakingLedger { stash: 21, total: 1000, active: 1000, unlocking: vec![] })); + assert_eq!(Staking::ledger(&30), Some(StakingLedger { stash: 31, total: 1000, active: 1000, unlocking: vec![] })); + assert_eq!(Staking::ledger(&40), Some(StakingLedger { stash: 41, total: 1000, active: 1000, unlocking: vec![] })); // no one is a nominator assert_eq!(>::enumerate().count(), 0 as usize); - // Bond [30, 31] as the third validator - assert_ok!(Staking::bond(Origin::signed(31), 30, 1000, RewardDestination::default())); - assert_ok!(Staking::validate(Origin::signed(30), ValidatorPrefs::default())); - - // bond [2,1](A), [4,3](B), [6,5](C) as the 3 nominators - // Give all of them some balance to be able to bond properly. - for i in &[1, 3, 5] { Balances::set_free_balance(i, 50); } - // Linking names to the above test: - // 10 => X - // 20 => Y - // 30 => Z - assert_ok!(Staking::bond(Origin::signed(1), 2, 10, RewardDestination::default())); - assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20])); - - assert_ok!(Staking::bond(Origin::signed(3), 4, 20, RewardDestination::default())); - assert_ok!(Staking::nominate(Origin::signed(4), vec![10, 30])); - - assert_ok!(Staking::bond(Origin::signed(5), 6, 30, RewardDestination::default())); - assert_ok!(Staking::nominate(Origin::signed(6), vec![20, 30])); + // bond [2,1] / [4,3] a nominator + Balances::set_free_balance(&1, 1000); + Balances::set_free_balance(&3, 1000); + + assert_ok!(Staking::bond(Origin::signed(1), 2, 500, RewardDestination::default())); + assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20, 30])); + + assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::default())); + assert_ok!(Staking::nominate(Origin::signed(4), vec![10, 20, 40])); // New era => election algorithm will trigger System::set_block_number(1); Session::check_rotate_session(System::block_number()); - // Z and Y are chosen - assert_eq!(Session::validators(), vec![30, 20]); - - // with stake 35 and 25 respectively + assert_eq!(Session::validators(), vec![20, 10]); - // This is only because 30 has been bonded on the fly, exposures are stored at the very end of the era. - // 35 is the point, not 'own' Exposure. - assert_eq!(Staking::stakers(30).own, 1000); - assert_eq!(Staking::stakers(30).total, 1000 + 35); - // same as above. +25 is the point - assert_eq!(Staking::stakers(20).own, 2010); - assert_eq!(Staking::stakers(20).total, 2010 + 25); + // with stake 1666 and 1333 respectively + assert_eq!(Staking::stakers(10).own, 1000); + assert_eq!(Staking::stakers(10).total, 1000 + 332); + assert_eq!(Staking::stakers(20).own, 1000); + assert_eq!(Staking::stakers(20).total, 1000 + 666); - // 30(Z) was supported by B-4 and C-6 with stake 20 and 15 respectively. - assert_eq!(Staking::stakers(30).others.iter().map(|e| e.value).collect::>>(), vec![15, 20]); - assert_eq!(Staking::stakers(30).others.iter().map(|e| e.who).collect::>>(), vec![6, 4]); - - // 20(Y) was supported by A-2 and C-6 with stake 10 and 15 respectively. - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![15, 10]); - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![6, 2]); + // Nominator's stake distribution. + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![166, 166]); + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![333, 333]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); }); } #[test] -fn phragmen_election_works() { +fn phragmen_election_works_example_2() { // tests the encapsulated phragmen::elect function. with_externalities(&mut ExtBuilder::default().nominate(false).build(), || { // initial setup of 10 and 20, both validators @@ -1437,11 +1455,11 @@ fn phragmen_election_works() { // bond [2,1](A), [4,3](B), as 2 nominators // Give all of them some balance to be able to bond properly. - for i in &[1, 3] { Balances::set_free_balance(i, 50); } - assert_ok!(Staking::bond(Origin::signed(1), 2, 5, RewardDestination::default())); + for i in &[1, 3] { Balances::set_free_balance(i, 2000); } + assert_ok!(Staking::bond(Origin::signed(1), 2, 50, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20])); - assert_ok!(Staking::bond(Origin::signed(3), 4, 45, RewardDestination::default())); + assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(4), vec![10, 30])); let rounds = || 2 as usize; @@ -1466,23 +1484,38 @@ fn phragmen_election_works() { // python implementation output: /* - 10 is elected with stake 26.31578947368421 and score 0.02 - 30 is elected with stake 23.684210526315788 and score 0.042222222222222223 + Votes [ + ('10', 1000, ['10']), + ('20', 1000, ['20']), + ('30', 1000, ['30']), + ('2', 50, ['10', '20']), + ('4', 1000, ['10', '30']) + ] + Sequential Phragmén gives + 10 is elected with stake 1705.7377049180327 and score 0.0004878048780487805 + 30 is elected with stake 1344.2622950819673 and score 0.0007439024390243903 + + 10 has load 0.0004878048780487805 and supported + 10 with stake 1000.0 + 20 has load 0 and supported + 20 with stake 0 + 30 has load 0.0007439024390243903 and supported + 30 with stake 1000.0 + 2 has load 0.0004878048780487805 and supported + 10 with stake 50.0 20 with stake 0.0 + 4 has load 0.0007439024390243903 and supported + 10 with stake 655.7377049180328 30 with stake 344.26229508196724 - 2 has load 0.02 and supported - 10 with stake 5.0 20 with stake 0.0 - 4 has load 0.042222222222222223 and supported - 10 with stake 21.31578947368421 30 with stake 23.684210526315788 */ - assert_eq!(winner_10.exposure.total, 1000 + 26); - assert_eq!(winner_10.score, Perquintill::from_fraction(0.02)); - assert_eq!(winner_10.exposure.others[0].value, 21); - assert_eq!(winner_10.exposure.others[1].value, 5); + assert_eq!(winner_10.exposure.total, 1000 + 705); + assert_eq!(winner_10.score, Perquintill::from_quintillionths(487804878048780)); + assert_eq!(winner_10.exposure.others[0].value, 655); + assert_eq!(winner_10.exposure.others[1].value, 50); - assert_eq!(winner_30.exposure.total, 1000 + 23); - assert_eq!(winner_30.score, Perquintill::from_quintillionths(42222222222222222)); - assert_eq!(winner_30.exposure.others[0].value, 23); + assert_eq!(winner_30.exposure.total, 1000 + 344); + assert_eq!(winner_30.score, Perquintill::from_quintillionths(743902439024390)); + assert_eq!(winner_30.exposure.others[0].value, 344); }) } @@ -1494,20 +1527,23 @@ fn switching_roles() { .sessions_per_era(3) .build(), || { + // Reset reward destination + for i in &[10, 20] { assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); } + assert_eq!(Session::validators(), vec![20, 10]); // put some money in account that we'll use. for i in 1..7 { Balances::set_free_balance(&i, 5000); } // add 2 nominators - assert_ok!(Staking::bond(Origin::signed(1), 2, 2000, RewardDestination::default())); + assert_ok!(Staking::bond(Origin::signed(1), 2, 2000, RewardDestination::Controller)); assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 6])); - assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::default())); + assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller)); assert_ok!(Staking::nominate(Origin::signed(4), vec![20, 2])); // add a new validator candidate - assert_ok!(Staking::bond(Origin::signed(5), 6, 1500, RewardDestination::Controller)); + assert_ok!(Staking::bond(Origin::signed(5), 6, 1000, RewardDestination::Controller)); assert_ok!(Staking::validate(Origin::signed(6), ValidatorPrefs::default())); // new block @@ -1528,13 +1564,15 @@ fn switching_roles() { System::set_block_number(3); Session::check_rotate_session(System::block_number()); - // with current nominators 10 and 4 have the most stake + // with current nominators 10 and 5 have the most stake assert_eq!(Session::validators(), vec![6, 10]); // 2 decides to be a validator. Consequences: - // 6 will not be chosen in the next round (no votes) - // 2 itself will be chosen + 20 who now has the higher votes - // 10 wil have no votes. + // new stakes: + // 10: 1000 self vote + // 6: 1000 self vote + // 20: 1000 self vote + 500 vote + // 2: 2000 self vote + 500 vote. assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); System::set_block_number(4); @@ -1555,14 +1593,11 @@ fn switching_roles() { #[test] fn wrong_vote_is_null() { with_externalities(&mut ExtBuilder::default() - .session_length(1) - .sessions_per_era(1) .nominate(false) .validator_pool(true) .build(), || { - // from the first era onward, only two will be chosen - assert_eq!(Session::validators(), vec![40, 30, 20, 10]); + assert_eq!(Session::validators(), vec![40, 30]); // put some money in account that we'll use. for i in 1..3 { Balances::set_free_balance(&i, 5000); } From 5eb6f2499f7dff5fc06908f9de6d1b00a9d1cec0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 17 Mar 2019 22:01:09 +0100 Subject: [PATCH 23/27] Refactor some names to match the reference. --- srml/staking/src/phragmen.rs | 57 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 53b383b3502f7..2e54623582a2a 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -44,26 +44,25 @@ pub struct Nominations { // The nominator's account. who: AccountId, // List of validators proposed by this nominator. - nominees: Vec>, + edges: Vec>, // the stake amount proposed by the nominator as a part of the vote. - // Same as `nom.budget` in Phragmén reference. - stake: Balance, + budget: Balance, // Incremented each time a nominee that this nominator voted for has been elected. load: Perquintill, } -// Wrapper around a nominator vote and the load of that vote. -// -// Referred to as 'edge' in the Phragmén reference implementation. +// Wrapper around a nominator vote and the load of that vote. #[derive(Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct Vote { +pub struct Edge { // Account being voted for who: AccountId, // Load of this vote. load: Perquintill, // Final backing stake of this vote. - backing_stake: Balance + backing_stake: Balance, + // Reference to the target candidate object + // candidate: &'a Candidate, } /// Perform election based on Phragmén algorithm. @@ -109,8 +108,8 @@ pub fn elect( c.approval_stake += c.exposure.total; nominations.push(Nominations { who: c.who.clone(), - nominees: vec![ Vote { who: c.who.clone(), ..Default::default() }], - stake: c.exposure.total, + edges: vec![ Edge { who: c.who.clone(), ..Default::default() }], + budget: c.exposure.total, load: Perquintill::zero(), }) }); @@ -119,18 +118,18 @@ pub fn elect( // Also collect approval stake along the way. nominations.extend(get_nominators().map(|(who, nominees)| { let nominator_stake = stash_of(&who); + let mut edges: Vec>> = Vec::with_capacity(nominees.len()); for n in &nominees { - candidates.iter_mut().filter(|i| i.who == *n).for_each(|c| { + if let Some(c) = candidates.iter_mut().find(|i| i.who == *n) { c.approval_stake += nominator_stake; - }); + edges.push(Edge { who: n.clone(), ..Default::default() }); + } } - + Nominations { who, - nominees: nominees.into_iter() - .map(|n| Vote { who: n, ..Default::default() }) - .collect::>>>(), - stake: nominator_stake, + edges: edges, + budget: nominator_stake, load: Perquintill::zero(), } })); @@ -150,17 +149,17 @@ pub fn elect( for _round in 0..rounds { // Loop 1: initialize score for nomination in &nominations { - for vote in &nomination.nominees { - if let Some(c) = candidates.iter_mut().find(|i| i.who == vote.who) { + for edge in &nomination.edges { + if let Some(c) = candidates.iter_mut().find(|i| i.who == edge.who) { c.score = Perquintill::from_xth(c.approval_stake.as_()); } } } // Loop 2: increment score. for nomination in &nominations { - for vote in &nomination.nominees { - if let Some(c) = candidates.iter_mut().find(|i| i.who == vote.who) { - let temp = nomination.stake.as_() * *nomination.load / c.approval_stake.as_(); + for edge in &nomination.edges { + if let Some(c) = candidates.iter_mut().find(|i| i.who == edge.who) { + let temp = nomination.budget.as_() * *nomination.load / c.approval_stake.as_(); c.score = Perquintill::from_quintillionths(*c.score + temp); } } @@ -173,7 +172,7 @@ pub fn elect( // loop 3: update nominator and vote load let winner = candidates.remove(winner_index); for n in &mut nominations { - for v in &mut n.nominees { + for v in &mut n.edges { if v.who == winner.who { v.load = Perquintill::from_quintillionths(*winner.score - *n.load); n.load = winner.score; @@ -187,10 +186,10 @@ pub fn elect( // 4.1- Update backing stake of candidates and nominators for n in &mut nominations { let nominator = n.who.clone(); - for v in &mut n.nominees { + for v in &mut n.edges { // 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 && c.who != nominator) { - v.backing_stake = as As>::sa(n.stake.as_() * *v.load / *n.load); + v.backing_stake = as As>::sa(n.budget.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( @@ -206,11 +205,11 @@ pub fn elect( // `Exposure.others` still needs an update for n in &mut nominations { let nominator = n.who.clone(); - for v in &mut n.nominees { - if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == v.who && c.who != nominator) { - c.exposure.total += n.stake; + for e in &mut n.edges { + if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who && c.who != nominator) { + c.exposure.total += n.budget; c.exposure.others.push( - IndividualExposure { who: n.who.clone(), value: n.stake } + IndividualExposure { who: n.who.clone(), value: n.budget } ); } } From c4bdb819d0552d6a102e2baec97cd6d9ffa41152 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 18 Mar 2019 10:57:16 +0100 Subject: [PATCH 24/27] Remove redundant inner loops from election round. --- srml/staking/src/phragmen.rs | 49 ++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 2e54623582a2a..71b0fe1294732 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -30,11 +30,13 @@ pub struct Candidate { pub who: AccountId, // Exposure struct, holding info about the value that the validator has in stake. pub exposure: Exposure, - // Accumulator of the stake of this candidate based on received votes. - approval_stake: Balance, // Intermediary value used to sort candidates. - // See Phragmén reference implementation. pub score: Perquintill, + // Accumulator of the stake of this candidate based on received votes. + approval_stake: Balance, + // Flag for being elected. + elected: bool, + } // Wrapper around the nomination info of a single nominator for a group of validators. @@ -61,8 +63,8 @@ pub struct Edge { load: Perquintill, // Final backing stake of this vote. backing_stake: Balance, - // Reference to the target candidate object - // candidate: &'a Candidate, + // Index of the candidate stored in the 'candidates' vecotr + candidate_idx: usize, } /// Perform election based on Phragmén algorithm. @@ -104,11 +106,11 @@ pub fn elect( // 1.1- Add phantom votes. let mut nominations: Vec>> = Vec::with_capacity(candidates.len()); - candidates.iter_mut().for_each(|c| { + candidates.iter_mut().enumerate().for_each(|(idx, c)| { c.approval_stake += c.exposure.total; nominations.push(Nominations { who: c.who.clone(), - edges: vec![ Edge { who: c.who.clone(), ..Default::default() }], + edges: vec![ Edge { who: c.who.clone(), candidate_idx: idx, ..Default::default() }], budget: c.exposure.total, load: Perquintill::zero(), }) @@ -120,9 +122,9 @@ pub fn elect( let nominator_stake = stash_of(&who); let mut edges: Vec>> = Vec::with_capacity(nominees.len()); for n in &nominees { - if let Some(c) = candidates.iter_mut().find(|i| i.who == *n) { - c.approval_stake += nominator_stake; - edges.push(Edge { who: n.clone(), ..Default::default() }); + if let Some(idx) = candidates.iter_mut().position(|i| i.who == *n) { + candidates[idx].approval_stake += nominator_stake; + edges.push(Edge { who: n.clone(), candidate_idx: idx, ..Default::default() }); } } @@ -150,7 +152,8 @@ pub fn elect( // Loop 1: initialize score for nomination in &nominations { for edge in &nomination.edges { - if let Some(c) = candidates.iter_mut().find(|i| i.who == edge.who) { + let c = &mut candidates[edge.candidate_idx]; + if !c.elected { c.score = Perquintill::from_xth(c.approval_stake.as_()); } } @@ -158,19 +161,23 @@ pub fn elect( // Loop 2: increment score. for nomination in &nominations { for edge in &nomination.edges { - if let Some(c) = candidates.iter_mut().find(|i| i.who == edge.who) { - let temp = nomination.budget.as_() * *nomination.load / c.approval_stake.as_(); + let c = &mut candidates[edge.candidate_idx]; + let temp = nomination.budget.as_() * *nomination.load / c.approval_stake.as_(); + if !c.elected { c.score = Perquintill::from_quintillionths(*c.score + temp); } } } // Find the best - let (winner_index, _) = candidates.iter().enumerate().min_by_key(|&(_i, c)| *c.score) + let winner = candidates + .iter_mut() + .filter(|c| !c.elected) + .min_by_key(|c| *c.score) .expect("candidates length is checked to be >0; qed"); // loop 3: update nominator and vote load - let winner = candidates.remove(winner_index); + winner.elected = true; for n in &mut nominations { for v in &mut n.edges { if v.who == winner.who { @@ -180,20 +187,20 @@ pub fn elect( } } - elected_candidates.push(winner); + elected_candidates.push(winner.clone()); } // end of all rounds // 4.1- Update backing stake of candidates and nominators for n in &mut nominations { let nominator = n.who.clone(); - for v in &mut n.edges { + for e in &mut n.edges { // 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 && c.who != nominator) { - v.backing_stake = as As>::sa(n.budget.as_() * *v.load / *n.load); - c.exposure.total += v.backing_stake; + if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who && c.who != nominator) { + e.backing_stake = as As>::sa(n.budget.as_() * *e.load / *n.load); + c.exposure.total += e.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: e.backing_stake } ); } } From 03d7583e0add4462ac2f765c4122287ecc11e3b0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 19 Mar 2019 14:24:30 +0100 Subject: [PATCH 25/27] Introduce phragmen post-processing. --- srml/staking/src/lib.rs | 118 +++++++++++----------- srml/staking/src/phragmen.rs | 187 ++++++++++++++++++++++++++++------- srml/staking/src/tests.rs | 125 +++++++++++++++++------ 3 files changed, 306 insertions(+), 124 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 08135a4db8798..07bd887302bff 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -17,105 +17,105 @@ // along with Substrate. If not, see . //! # 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. +//! 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`]. -//! -//! ## Overview -//! +//! +//! ## 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)). -//! +//! - 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 at least one account to become **bonded**, also known as +//! +//! #### Staking +//! +//! Almost any interaction with the staking module requires at least one account to become **bonded**, also known as //! being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds. //! Henceforth, the former account that initiated the interest is called the **controller** and the latter, holding the -//! funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account -//! pair_, one to take the role of the controller and one to be the frozen stash account (any value locked in -//! stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via -//! the `bond()` function. -//! -//! Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or +//! funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account +//! pair_, one to take the role of the controller and one to be the frozen stash account (any value locked in +//! stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via +//! the `bond()` function. +//! +//! Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or //! simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible -//! for declaring interest and the _stash_ account stays untouched, without directly interacting in any operation. -//! +//! for declaring interest 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 +//! +//! 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 via the `validate()` call. -//! -//! #### Nomination -//! +//! +//! #### 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 _immediately_, meaning that its //! votes will be taken into account at the next election round. As mentioned above, a nominator must also place some //! funds in a stash account, essentially indicating 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 a share of the reward granted to them. -//! Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The -//! same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. +//! honesty of a set of validators by voting for them, with the goal of having a share of the reward granted to them. +//! Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The +//! same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed. //! 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 +//! 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 +//! +//! 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 +//! 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 -//! +//! //! ### Types -//! +//! //! - `Currency`: Used as the measurement means of staking and funds management. -//! -//! ### Dispatchable //! -//! The Dispatchable functions of the staking module enable the steps needed for entities to accept and change their +//! ### 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 +//! +//! ### 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 //! @@ -210,9 +210,9 @@ //! - [**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. @@ -897,7 +897,9 @@ impl Module { // Populate Stakers and figure out the minimum stake behind a slot. let mut slot_stake = elected_candidates[0].exposure.total; for candidate in &elected_candidates { - if candidate.exposure.total < slot_stake { slot_stake = candidate.exposure.total; } + if candidate.exposure.total < slot_stake { + slot_stake = candidate.exposure.total; + } >::insert(candidate.who.clone(), candidate.exposure.clone()); } >::put(&slot_stake); @@ -906,7 +908,7 @@ impl Module { >::set_validators( &elected_candidates.into_iter().map(|i| i.who).collect::>() ); - + slot_stake } diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 71b0fe1294732..4857a3be6473d 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -18,7 +18,7 @@ use rstd::{prelude::*}; use primitives::Perquintill; -use primitives::traits::{Zero, As}; +use primitives::traits::{Zero, One, As, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; use parity_codec::{HasCompact, Encode, Decode}; use crate::{Exposure, BalanceOf, Trait, ValidatorPrefs, IndividualExposure}; @@ -36,13 +36,12 @@ pub struct Candidate { approval_stake: Balance, // Flag for being elected. elected: bool, - } // Wrapper around the nomination info of a single nominator for a group of validators. #[derive(Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug))] -pub struct Nominations { +pub struct Nominator { // The nominator's account. who: AccountId, // List of validators proposed by this nominator. @@ -53,7 +52,7 @@ pub struct Nominations { load: Perquintill, } -// Wrapper around a nominator vote and the load of that vote. +// Wrapper around a nominator vote and the load of that vote. #[derive(Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] pub struct Edge { @@ -63,8 +62,12 @@ pub struct Edge { load: Perquintill, // Final backing stake of this vote. backing_stake: Balance, - // Index of the candidate stored in the 'candidates' vecotr + // Index of the candidate stored in the 'candidates' vector candidate_idx: usize, + // Index of the candidate stored in the 'elected_candidates' vector. Used only with equalise. + elected_idx: usize, + // Indicates if this edge is a vote for an elected candidate. Used only with equalise. + elected: bool, } /// Perform election based on Phragmén algorithm. @@ -90,7 +93,7 @@ pub fn elect( { let rounds = get_rounds(); let mut elected_candidates; - + // 1- Pre-process candidates and place them in a container let mut candidates = get_validators().map(|(who, _)| { let stash_balance = stash_of(&who); @@ -103,12 +106,12 @@ pub fn elect( // Just to be used when we are below minimum validator count let original_candidates = candidates.clone(); - + // 1.1- Add phantom votes. - let mut nominations: Vec>> = Vec::with_capacity(candidates.len()); + let mut nominators: Vec>> = Vec::with_capacity(candidates.len()); candidates.iter_mut().enumerate().for_each(|(idx, c)| { c.approval_stake += c.exposure.total; - nominations.push(Nominations { + nominators.push(Nominator { who: c.who.clone(), edges: vec![ Edge { who: c.who.clone(), candidate_idx: idx, ..Default::default() }], budget: c.exposure.total, @@ -118,7 +121,7 @@ pub fn elect( // 2- Collect the nominators with the associated votes. // Also collect approval stake along the way. - nominations.extend(get_nominators().map(|(who, nominees)| { + nominators.extend(get_nominators().map(|(who, nominees)| { let nominator_stake = stash_of(&who); let mut edges: Vec>> = Vec::with_capacity(nominees.len()); for n in &nominees { @@ -127,18 +130,15 @@ pub fn elect( edges.push(Edge { who: n.clone(), candidate_idx: idx, ..Default::default() }); } } - - Nominations { + + Nominator { who, - edges: edges, + edges: edges, budget: nominator_stake, load: Perquintill::zero(), } })); - println!("Candidates : {:?}", candidates); - println!("Nominations: {:?}", nominations); - // 3- optimization: // Candidates who have 0 stake => have no votes or all null-votes. Kick them out not. let mut candidates = candidates.into_iter().filter(|c| c.approval_stake > BalanceOf::::zero()) @@ -150,19 +150,19 @@ pub fn elect( // Main election loop for _round in 0..rounds { // Loop 1: initialize score - for nomination in &nominations { - for edge in &nomination.edges { - let c = &mut candidates[edge.candidate_idx]; + for n in &nominators { + for e in &n.edges { + let c = &mut candidates[e.candidate_idx]; if !c.elected { c.score = Perquintill::from_xth(c.approval_stake.as_()); } } } // Loop 2: increment score. - for nomination in &nominations { - for edge in &nomination.edges { - let c = &mut candidates[edge.candidate_idx]; - let temp = nomination.budget.as_() * *nomination.load / c.approval_stake.as_(); + for n in &nominators { + for e in &n.edges { + let c = &mut candidates[e.candidate_idx]; + let temp = n.budget.as_() * *n.load / c.approval_stake.as_(); if !c.elected { c.score = Perquintill::from_quintillionths(*c.score + temp); } @@ -171,14 +171,14 @@ pub fn elect( // Find the best let winner = candidates - .iter_mut() + .iter_mut() .filter(|c| !c.elected) .min_by_key(|c| *c.score) .expect("candidates length is checked to be >0; qed"); // loop 3: update nominator and vote load winner.elected = true; - for n in &mut nominations { + for n in &mut nominators { for v in &mut n.edges { if v.who == winner.who { v.load = Perquintill::from_quintillionths(*winner.score - *n.load); @@ -191,26 +191,51 @@ pub fn elect( } // end of all rounds // 4.1- Update backing stake of candidates and nominators - for n in &mut nominations { - let nominator = n.who.clone(); + for n in &mut nominators { for e in &mut n.edges { // 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 == e.who && c.who != nominator) { + if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who) { + e.elected = true; e.backing_stake = as As>::sa(n.budget.as_() * *e.load / *n.load); - c.exposure.total += e.backing_stake; - // Update IndividualExposure of those who nominated and their vote won - c.exposure.others.push( - IndividualExposure { who: n.who.clone(), value: e.backing_stake } - ); + if c.who != n.who { + c.exposure.total += e.backing_stake; + // Update IndividualExposure of those who nominated and their vote won + c.exposure.others.push( + IndividualExposure { who: n.who.clone(), value: e.backing_stake } + ); + } } } } + + // equalise? + let do_equalise = true; + if do_equalise { + let tolerance = >::sa(10); + let equalise_iterations = 2; + for _ in 0..equalise_iterations { + let mut max_diff = >::zero(); + nominators.iter().for_each(|n| { + // TODO: equalise should accept a reference to a nominator maybe? + let diff = equalise::(n.clone(), &mut elected_candidates, tolerance); + if diff > max_diff { + max_diff = diff; + } + }); + if max_diff < tolerance { + break; + } + } + } + } else { if candidates.len() > minimum_validator_count { // if we don't have enough candidates, just choose all that have some vote. elected_candidates = candidates; + // TODO: I still don't really trust this. + // Exposure should either be updated in both cases or neither. // `Exposure.others` still needs an update - for n in &mut nominations { + for n in &mut nominators { let nominator = n.who.clone(); for e in &mut n.edges { if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who && c.who != nominator) { @@ -226,7 +251,97 @@ pub fn elect( elected_candidates = original_candidates; } } - - println!("Elected : {:?}", elected_candidates); elected_candidates +} + +pub fn equalise( + mut nominator: Nominator>, + elected_candidates: &mut Vec>>, + tolerance: BalanceOf +) -> BalanceOf { + // TODO: Might be more efficinet to do this once? maybe... + // update indexes. + nominator.edges.iter_mut().for_each(|e| { + if let Some(idx) = elected_candidates.iter().position(|c| c.who == e.who) { + e.elected_idx = idx; + } + }); + + // TODO: double check the direction of the sort. + nominator.edges.sort_unstable_by_key(|e| elected_candidates[e.elected_idx].exposure.total); + + // TODO: clone and into is not really optimal. + let mut elected_edges = nominator.edges.clone() + .into_iter() + .filter(|e| e.elected) + .collect::>>>(); + if elected_edges.len() == 0 { return >::zero(); } + let stake_used = elected_edges + .iter() + .fold(>::zero(), |s, e| s + e.backing_stake); + let backed_stakes = elected_edges + .iter() + .map(|e| elected_candidates[e.elected_idx].exposure.total) + .collect::>>(); + let backing_backed_stake = elected_edges + .iter() + .filter(|e| e.backing_stake > >::zero()) + .map(|e| elected_candidates[e.elected_idx].exposure.total) + .collect::>>(); + + let mut difference; + if backing_backed_stake.len() > 0 { + let max_stake = backing_backed_stake + .iter() + .max() + .expect("vector with positive length will have a max; qed") + .to_owned(); + let min_stake = backed_stakes + .iter() + .min() + .expect("vector with positive length will have a max; qed") + .to_owned(); + difference = max_stake - min_stake; + difference += nominator.budget - stake_used; + if difference < tolerance { + return difference; + } + } else { + difference = nominator.budget; + } + + // Undo updates to exposure + elected_edges.iter_mut().for_each(|e| { + assert_eq!(elected_candidates[e.elected_idx].who, e.who); + elected_candidates[e.elected_idx].exposure.total -= e.backing_stake; + e.backing_stake = >::zero(); + }); + + let mut cumulative_stake = >::zero(); + let mut last_index = elected_edges.len() - 1; + + elected_edges.iter_mut().enumerate().for_each(|(idx, e)| { + let stake = elected_candidates[e.elected_idx].exposure.total; + + let stake_mul = stake.checked_mul(&>::sa(idx as u64)).unwrap_or(>::one()); + let stake_sub = stake_mul.checked_sub(&cumulative_stake).unwrap_or_default(); + if stake_sub > nominator.budget { + last_index = idx.checked_sub(1).unwrap_or(0); + return + } + cumulative_stake += stake; + }); + + let last_stake = elected_candidates[elected_edges[last_index].elected_idx].exposure.total; + let split_ways = last_index + 1; + let excess = nominator.budget + cumulative_stake - last_stake * >::sa(split_ways as u64); + elected_edges.iter_mut().take(split_ways).for_each(|e| { + let c = &mut elected_candidates[e.elected_idx]; + e.backing_stake = excess / >::sa(split_ways as u64) + last_stake -c.exposure.total; + c.exposure.total += e.backing_stake; + if let Some(i_expo) = c.exposure.others.iter_mut().find(|i| i.who == nominator.who) { + i_expo.value = e.backing_stake; + } + }); + difference } \ No newline at end of file diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index f3daa69d57779..d99fdac8e8dff 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -602,7 +602,22 @@ fn nominating_and_rewards_should_work() { // 4 has load 0.0005555555555555556 and supported // 10 with stake 600.0 20 with stake 400.0 40 with stake 0.0 + // Sequential Phragmén with post processing gives + // 10 is elected with stake 2000.0 and score 0.0003333333333333333 + // 20 is elected with stake 2000.0 and score 0.0005555555555555556 + // 10 has load 0.0003333333333333333 and supported + // 10 with stake 1000.0 + // 20 has load 0.0005555555555555556 and supported + // 20 with stake 1000.0 + // 30 has load 0 and supported + // 30 with stake 0 + // 40 has load 0 and supported + // 40 with stake 0 + // 2 has load 0.0005555555555555556 and supported + // 10 with stake 400.0 20 with stake 600.0 30 with stake 0 + // 4 has load 0.0005555555555555556 and supported + // 10 with stake 600.0 20 with stake 400.0 40 with stake 0.0 with_externalities(&mut ExtBuilder::default() .nominate(false) @@ -638,11 +653,11 @@ fn nominating_and_rewards_should_work() { // 4 will nominate for 10, 20, 40 assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); assert_ok!(Staking::nominate(Origin::signed(4), vec![10, 20, 40])); - + System::set_block_number(1); Session::check_rotate_session(System::block_number()); assert_eq!(Staking::current_era(), 1); - + // 10 and 20 have more votes, they will be chosen by phragmen. assert_eq!(Session::validators(), vec![20, 10]); @@ -654,21 +669,21 @@ fn nominating_and_rewards_should_work() { // total expo of 10, with 1200 coming from nominators (externals), according to phragmen. assert_eq!(Staking::stakers(10).own, 1000); - assert_eq!(Staking::stakers(10).total, 1000 + 800); + assert_eq!(Staking::stakers(10).total, 1000 + 1000); // 2 and 4 supported 10, each with stake 600, according to phragmen. - assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![400, 400]); + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![600, 400]); assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); // total expo of 20, with 500 coming from nominators (externals), according to phragmen. assert_eq!(Staking::stakers(20).own, 1000); - assert_eq!(Staking::stakers(20).total, 1000 + 1200); + assert_eq!(Staking::stakers(20).total, 1000 + 1000); // 2 and 4 supported 20, each with stake 250, according to phragmen. - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![600, 600]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![400, 600]); assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); // They are not chosen anymore assert_eq!(Staking::stakers(30).total, 0); assert_eq!(Staking::stakers(40).total, 0); - + System::set_block_number(2); Session::check_rotate_session(System::block_number()); @@ -677,15 +692,15 @@ fn nominating_and_rewards_should_work() { // nothing else will happen, era ends and rewards are paid again, // it is expected that nominators will also be paid. See below - // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 - assert_eq!(Balances::total_balance(&2), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11)); - // Nominator 4: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 - assert_eq!(Balances::total_balance(&4), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11)); + // Nominator 2: has [400/2000 ~ 1/5 from 10] + [600/2000 ~ 3/10 from 20]'s reward. + assert_eq!(Balances::total_balance(&2), initial_balance + (new_session_reward/5 + 3*new_session_reward/10)); + // Nominator 4: has [600/2000 ~ 3/10 from 10] + [400/2000 ~ 1/5 from 20]'s reward. + assert_eq!(Balances::total_balance(&4), initial_balance + (new_session_reward/5 + 3*new_session_reward/10)); - // 10 got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 - assert_eq!(Balances::total_balance(&10), initial_balance + 5*new_session_reward/9) ; - // 10 got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = 5/11 - assert_eq!(Balances::total_balance(&20), initial_balance + 5*new_session_reward/11); + // 10 got 1000/2000 external stake => Validator's share = 1/2 + assert_eq!(Balances::total_balance(&10), initial_balance + new_session_reward/2); + // 20 got 1000/2000 external stake => Validator's share = 1/2 + assert_eq!(Balances::total_balance(&20), initial_balance + new_session_reward/2); }); } @@ -1021,7 +1036,7 @@ fn validator_payment_prefs_work() { // session triggered: the reward value stashed should be 10 -- defined in ExtBuilder genesis. assert_eq!(Staking::current_session_reward(), session_reward); assert_eq!(Staking::current_era_reward(), session_reward); - + block = 6; // Block 6 => Session 2 => Era 0 System::set_block_number(block); Timestamp::set_timestamp(block*5); // a little late. @@ -1054,7 +1069,7 @@ fn validator_payment_prefs_work() { #[test] fn bond_extra_works() { // Tests that extra `free_balance` in the stash can be added to stake - // NOTE: this tests only verifies `StakingLedger` for correct updates. + // NOTE: this tests only verifies `StakingLedger` for correct updates // See `bond_extra_and_withdraw_unbonded_works` for more details and updates on `Exposure`. with_externalities(&mut ExtBuilder::default().build(), || { @@ -1118,7 +1133,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // confirm that 10 is a normal validator and gets paid at the end of the era. System::set_block_number(1); Timestamp::set_timestamp(5); - Session::check_rotate_session(System::block_number()); + Session::check_rotate_session(System::block_number()); assert_eq!(Staking::current_era(), 1); assert_eq!(Session::current_index(), 1); @@ -1131,7 +1146,8 @@ fn bond_extra_and_withdraw_unbonded_works() { assert_eq!(Staking::stakers(&10), Exposure { total: 1000, own: 1000, others: vec![] }); - // deposit the extra 100 units + + // deposit the extra 100 units Staking::bond_extra(Origin::signed(10), 100).unwrap(); assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: vec![] })); @@ -1393,6 +1409,25 @@ fn phragmen_poc_works() { // 30 with stake 0 // 40 has load 0 and supported // 40 with stake 0 + + // Sequential Phragmén with post processing gives + // 10 is elected with stake 1500.0 and score 0.0005 + // 20 is elected with stake 1500.0 and score 0.00075 + // + // 10 has load 0.0005 and supported + // 10 with stake 1000.0 + // 20 has load 0.00075 and supported + // 20 with stake 1000.0 + // 30 has load 0 and supported + // 30 with stake 0 + // 40 has load 0 and supported + // 40 with stake 0 + // 2 has load 0.00075 and supported + // 10 with stake 166.66666666666674 20 with stake 333.33333333333326 30 with stake 0 + // 4 has load 0.00075 and supported + // 10 with stake 333.3333333333333 20 with stake 166.66666666666666 40 with stake 0.0 + + with_externalities(&mut ExtBuilder::default() .nominate(false) .validator_pool(true) @@ -1405,14 +1440,19 @@ fn phragmen_poc_works() { assert_eq!(Staking::ledger(&20), Some(StakingLedger { stash: 21, total: 1000, active: 1000, unlocking: vec![] })); assert_eq!(Staking::ledger(&30), Some(StakingLedger { stash: 31, total: 1000, active: 1000, unlocking: vec![] })); assert_eq!(Staking::ledger(&40), Some(StakingLedger { stash: 41, total: 1000, active: 1000, unlocking: vec![] })); - + + assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(Origin::signed(30), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(Origin::signed(40), RewardDestination::Controller)); + // no one is a nominator assert_eq!(>::enumerate().count(), 0 as usize); // bond [2,1] / [4,3] a nominator Balances::set_free_balance(&1, 1000); Balances::set_free_balance(&3, 1000); - + assert_ok!(Staking::bond(Origin::signed(1), 2, 500, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20, 30])); @@ -1426,15 +1466,18 @@ fn phragmen_poc_works() { assert_eq!(Session::validators(), vec![20, 10]); // with stake 1666 and 1333 respectively - assert_eq!(Staking::stakers(10).own, 1000); - assert_eq!(Staking::stakers(10).total, 1000 + 332); + assert_eq!(Staking::stakers(10).own, 1000); + assert_eq!(Staking::stakers(10).total, 1000 + 499); assert_eq!(Staking::stakers(20).own, 1000); - assert_eq!(Staking::stakers(20).total, 1000 + 666); + assert_eq!(Staking::stakers(20).total, 1000 + 499); // Nominator's stake distribution. - assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![166, 166]); + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![333, 166]); + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).sum::>(), 499); assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![333, 333]); + + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![166, 333]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).sum::>(), 499); assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); }); } @@ -1506,16 +1549,32 @@ fn phragmen_election_works_example_2() { 4 has load 0.0007439024390243903 and supported 10 with stake 655.7377049180328 30 with stake 344.26229508196724 + Sequential Phragmén with post processing gives + 10 is elected with stake 1525.0 and score 0.0004878048780487805 + 30 is elected with stake 1525.0 and score 0.0007439024390243903 + + 10 has load 0.0004878048780487805 and supported + 10 with stake 1000.0 + 20 has load 0 and supported + 20 with stake 0 + 30 has load 0.0007439024390243903 and supported + 30 with stake 1000.0 + 2 has load 0.0004878048780487805 and supported + 10 with stake 50.0 20 with stake 0.0 + 4 has load 0.0007439024390243903 and supported + 10 with stake 475.0 30 with stake 525.0 + + */ - assert_eq!(winner_10.exposure.total, 1000 + 705); + assert_eq!(winner_10.exposure.total, 1000 + 525); assert_eq!(winner_10.score, Perquintill::from_quintillionths(487804878048780)); - assert_eq!(winner_10.exposure.others[0].value, 655); + assert_eq!(winner_10.exposure.others[0].value, 475); assert_eq!(winner_10.exposure.others[1].value, 50); - assert_eq!(winner_30.exposure.total, 1000 + 344); + assert_eq!(winner_30.exposure.total, 1000 + 525); assert_eq!(winner_30.score, Perquintill::from_quintillionths(743902439024390)); - assert_eq!(winner_30.exposure.others[0].value, 344); + assert_eq!(winner_30.exposure.others[0].value, 525); }) } @@ -1616,3 +1675,9 @@ fn wrong_vote_is_null() { assert_eq!(Session::validators(), vec![20, 10]); }); } + +#[test] +fn bond_with_no_staked_value() { + // Behavior when someone bonds with no staked value. + // Particularly when she votes and the candidate is elected. +} From bf8212f7d029896b47860625c699cca309a21869 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 19 Mar 2019 18:12:58 +0100 Subject: [PATCH 26/27] Some fixes and todos. --- srml/staking/src/lib.rs | 13 +++++-- srml/staking/src/phragmen.rs | 69 ++++++++++++++++++++++-------------- srml/staking/src/tests.rs | 7 +++- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 07bd887302bff..f2e2682d150a9 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -239,6 +239,8 @@ mod mock; mod tests; mod phragmen; +use phragmen::{elect, ElectionConfig}; + const RECENT_OFFLINE_COUNT: usize = 32; const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; const MAX_NOMINATIONS: usize = 16; @@ -871,18 +873,23 @@ impl Module { /// Returns the new SlotStake value. fn select_validators() -> BalanceOf { // Map of (would-be) validator account to amount of stake backing it. - + let rounds = || >::get() as usize; let validators = || >::enumerate(); let nominators = || >::enumerate(); let stash_of = |w: &T::AccountId| -> BalanceOf { Self::stash_balance(w) }; let min_validator_count = Self::minimum_validator_count() as usize; - let elected_candidates = phragmen::elect::( + let elected_candidates = elect::( rounds, validators, nominators, stash_of, - min_validator_count + min_validator_count, + ElectionConfig::> { + equalise: true, + tolerance: >::sa(10 as u64), + iterations: 10, + } ); // Clear Stakers and reduce their slash_count. diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 4857a3be6473d..a50a01251b941 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -22,6 +22,18 @@ use primitives::traits::{Zero, One, As, CheckedAdd, CheckedDiv, CheckedMul, Chec use parity_codec::{HasCompact, Encode, Decode}; use crate::{Exposure, BalanceOf, Trait, ValidatorPrefs, IndividualExposure}; + +// Configure the behavior of the Phragmen election. +// Might be deprecated. +pub struct ElectionConfig { + // Perform equalise?. + pub equalise: bool, + // Number of equalise iterations. + pub iterations: usize, + // Tolerance of max change per equalise iteration. + pub tolerance: Balance, +} + // Wrapper around validation candidates some metadata. #[derive(Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] @@ -81,6 +93,7 @@ pub fn elect( get_nominators: FN, stash_of: FS, minimum_validator_count: usize, + config: ElectionConfig>, ) -> Vec>> where FR: Fn() -> usize, FV: Fn() -> Box( } } - // equalise? - let do_equalise = true; - if do_equalise { - let tolerance = >::sa(10); - let equalise_iterations = 2; + // Optionally perform equalise post-processing. + if config.equalise { + let tolerance = config.tolerance; + let equalise_iterations = config.iterations; + + // Fix indexes + nominators.iter_mut().for_each(|n| { + n.edges.iter_mut().for_each(|e| { + if let Some(idx) = elected_candidates.iter().position(|c| c.who == e.who) { + e.elected_idx = idx; + } + }); + }); + for _ in 0..equalise_iterations { let mut max_diff = >::zero(); - nominators.iter().for_each(|n| { - // TODO: equalise should accept a reference to a nominator maybe? - let diff = equalise::(n.clone(), &mut elected_candidates, tolerance); + nominators.iter_mut().for_each(|mut n| { + let diff = equalise::(&mut n, &mut elected_candidates, tolerance); if diff > max_diff { max_diff = diff; } @@ -255,26 +276,15 @@ pub fn elect( } pub fn equalise( - mut nominator: Nominator>, + nominator: &mut Nominator>, elected_candidates: &mut Vec>>, tolerance: BalanceOf ) -> BalanceOf { - // TODO: Might be more efficinet to do this once? maybe... - // update indexes. - nominator.edges.iter_mut().for_each(|e| { - if let Some(idx) = elected_candidates.iter().position(|c| c.who == e.who) { - e.elected_idx = idx; - } - }); - - // TODO: double check the direction of the sort. - nominator.edges.sort_unstable_by_key(|e| elected_candidates[e.elected_idx].exposure.total); - // TODO: clone and into is not really optimal. - let mut elected_edges = nominator.edges.clone() - .into_iter() + let mut elected_edges = nominator.edges + .iter_mut() .filter(|e| e.elected) - .collect::>>>(); + .collect::>>>(); if elected_edges.len() == 0 { return >::zero(); } let stake_used = elected_edges .iter() @@ -317,31 +327,36 @@ pub fn equalise( e.backing_stake = >::zero(); }); + elected_edges.sort_unstable_by_key(|e| elected_candidates[e.elected_idx].exposure.total); + let mut cumulative_stake = >::zero(); let mut last_index = elected_edges.len() - 1; - + let budget = nominator.budget; elected_edges.iter_mut().enumerate().for_each(|(idx, e)| { let stake = elected_candidates[e.elected_idx].exposure.total; let stake_mul = stake.checked_mul(&>::sa(idx as u64)).unwrap_or(>::one()); let stake_sub = stake_mul.checked_sub(&cumulative_stake).unwrap_or_default(); - if stake_sub > nominator.budget { - last_index = idx.checked_sub(1).unwrap_or(0); + if stake_sub > budget { + last_index = idx.clone().checked_sub(1).unwrap_or(0); return } cumulative_stake += stake; }); + // TODO: safe arithmatic here. let last_stake = elected_candidates[elected_edges[last_index].elected_idx].exposure.total; let split_ways = last_index + 1; let excess = nominator.budget + cumulative_stake - last_stake * >::sa(split_ways as u64); + let nominator_address = nominator.who.clone(); elected_edges.iter_mut().take(split_ways).for_each(|e| { let c = &mut elected_candidates[e.elected_idx]; e.backing_stake = excess / >::sa(split_ways as u64) + last_stake -c.exposure.total; c.exposure.total += e.backing_stake; - if let Some(i_expo) = c.exposure.others.iter_mut().find(|i| i.who == nominator.who) { + if let Some(i_expo) = c.exposure.others.iter_mut().find(|i| i.who == nominator_address) { i_expo.value = e.backing_stake; } }); + difference } \ No newline at end of file diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index d99fdac8e8dff..40532611773b6 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1516,7 +1516,12 @@ fn phragmen_election_works_example_2() { validators, nominators, stash_of, - min_validator_count + min_validator_count, + ElectionConfig::> { + equalise: true, + tolerance: >::sa(10 as u64), + iterations: 10, + } ); // 10 and 30 must be the winners From 85f12946bb37d208baa7d3f849b155505df35c0f Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 21 Mar 2019 10:18:58 +0100 Subject: [PATCH 27/27] Fix some tests with new phragmen params --- srml/staking/src/lib.rs | 8 +- srml/staking/src/phragmen.rs | 53 ++++----- srml/staking/src/tests.rs | 214 ++++++++++++++++++++++++++++++++--- 3 files changed, 228 insertions(+), 47 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index f2e2682d150a9..e243b58f7895f 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -903,11 +903,11 @@ impl Module { // Populate Stakers and figure out the minimum stake behind a slot. let mut slot_stake = elected_candidates[0].exposure.total; - for candidate in &elected_candidates { - if candidate.exposure.total < slot_stake { - slot_stake = candidate.exposure.total; + for c in &elected_candidates { + if c.exposure.total < slot_stake { + slot_stake = c.exposure.total; } - >::insert(candidate.who.clone(), candidate.exposure.clone()); + >::insert(c.who.clone(), c.exposure.clone()); } >::put(&slot_stake); diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index a50a01251b941..48d019e48c071 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -18,7 +18,7 @@ use rstd::{prelude::*}; use primitives::Perquintill; -use primitives::traits::{Zero, One, As, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; +use primitives::traits::{Zero, As, Bounded, CheckedMul, CheckedSub}; use parity_codec::{HasCompact, Encode, Decode}; use crate::{Exposure, BalanceOf, Trait, ValidatorPrefs, IndividualExposure}; @@ -48,6 +48,8 @@ pub struct Candidate { approval_stake: Balance, // Flag for being elected. elected: bool, + // This is most often equal to `Exposure.total` but not always. Needed for [`equalise`] + backing_stake: Balance } // Wrapper around the nomination info of a single nominator for a group of validators. @@ -158,25 +160,22 @@ pub fn elect( .collect::>>>(); // 4- If we have more candidates then needed, run Phragmén. - if candidates.len() > rounds { + if candidates.len() >= rounds { elected_candidates = Vec::with_capacity(rounds); // Main election loop for _round in 0..rounds { // Loop 1: initialize score - for n in &nominators { - for e in &n.edges { - let c = &mut candidates[e.candidate_idx]; - if !c.elected { - c.score = Perquintill::from_xth(c.approval_stake.as_()); - } + for c in &mut candidates { + if !c.elected { + c.score = Perquintill::from_xth(c.approval_stake.as_()); } } // Loop 2: increment score. for n in &nominators { for e in &n.edges { let c = &mut candidates[e.candidate_idx]; - let temp = n.budget.as_() * *n.load / c.approval_stake.as_(); if !c.elected { + let temp = n.budget.as_() * *n.load / c.approval_stake.as_(); c.score = Perquintill::from_quintillionths(*c.score + temp); } } @@ -189,12 +188,12 @@ pub fn elect( .min_by_key(|c| *c.score) .expect("candidates length is checked to be >0; qed"); - // loop 3: update nominator and vote load + // loop 3: update nominator and edge load winner.elected = true; for n in &mut nominators { - for v in &mut n.edges { - if v.who == winner.who { - v.load = Perquintill::from_quintillionths(*winner.score - *n.load); + for e in &mut n.edges { + if e.who == winner.who { + e.load = Perquintill::from_quintillionths(*winner.score - *n.load); n.load = winner.score; } } @@ -209,10 +208,11 @@ pub fn elect( // 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 == e.who) { e.elected = true; - e.backing_stake = as As>::sa(n.budget.as_() * *e.load / *n.load); + e.backing_stake = >::sa(n.budget.as_() * (*e.load / *n.load)); + c.backing_stake += e.backing_stake; if c.who != n.who { + // Only update the exposure if this vote is from some other account. c.exposure.total += e.backing_stake; - // Update IndividualExposure of those who nominated and their vote won c.exposure.others.push( IndividualExposure { who: n.who.clone(), value: e.backing_stake } ); @@ -235,7 +235,7 @@ pub fn elect( }); }); - for _ in 0..equalise_iterations { + for _i in 0..equalise_iterations { let mut max_diff = >::zero(); nominators.iter_mut().for_each(|mut n| { let diff = equalise::(&mut n, &mut elected_candidates, tolerance); @@ -253,9 +253,6 @@ pub fn elect( if candidates.len() > minimum_validator_count { // if we don't have enough candidates, just choose all that have some vote. elected_candidates = candidates; - // TODO: I still don't really trust this. - // Exposure should either be updated in both cases or neither. - // `Exposure.others` still needs an update for n in &mut nominators { let nominator = n.who.clone(); for e in &mut n.edges { @@ -291,12 +288,12 @@ pub fn equalise( .fold(>::zero(), |s, e| s + e.backing_stake); let backed_stakes = elected_edges .iter() - .map(|e| elected_candidates[e.elected_idx].exposure.total) + .map(|e| elected_candidates[e.elected_idx].backing_stake) .collect::>>(); let backing_backed_stake = elected_edges .iter() .filter(|e| e.backing_stake > >::zero()) - .map(|e| elected_candidates[e.elected_idx].exposure.total) + .map(|e| elected_candidates[e.elected_idx].backing_stake) .collect::>>(); let mut difference; @@ -323,19 +320,20 @@ pub fn equalise( // Undo updates to exposure elected_edges.iter_mut().for_each(|e| { assert_eq!(elected_candidates[e.elected_idx].who, e.who); + elected_candidates[e.elected_idx].backing_stake -= e.backing_stake; elected_candidates[e.elected_idx].exposure.total -= e.backing_stake; e.backing_stake = >::zero(); }); - elected_edges.sort_unstable_by_key(|e| elected_candidates[e.elected_idx].exposure.total); + elected_edges.sort_unstable_by_key(|e| elected_candidates[e.elected_idx].backing_stake); let mut cumulative_stake = >::zero(); let mut last_index = elected_edges.len() - 1; let budget = nominator.budget; elected_edges.iter_mut().enumerate().for_each(|(idx, e)| { - let stake = elected_candidates[e.elected_idx].exposure.total; + let stake = elected_candidates[e.elected_idx].backing_stake; - let stake_mul = stake.checked_mul(&>::sa(idx as u64)).unwrap_or(>::one()); + let stake_mul = stake.checked_mul(&>::sa(idx as u64)).unwrap_or(>::max_value()); let stake_sub = stake_mul.checked_sub(&cumulative_stake).unwrap_or_default(); if stake_sub > budget { last_index = idx.clone().checked_sub(1).unwrap_or(0); @@ -344,19 +342,18 @@ pub fn equalise( cumulative_stake += stake; }); - // TODO: safe arithmatic here. - let last_stake = elected_candidates[elected_edges[last_index].elected_idx].exposure.total; + let last_stake = elected_candidates[elected_edges[last_index].elected_idx].backing_stake; let split_ways = last_index + 1; let excess = nominator.budget + cumulative_stake - last_stake * >::sa(split_ways as u64); let nominator_address = nominator.who.clone(); elected_edges.iter_mut().take(split_ways).for_each(|e| { let c = &mut elected_candidates[e.elected_idx]; - e.backing_stake = excess / >::sa(split_ways as u64) + last_stake -c.exposure.total; + e.backing_stake = excess / >::sa(split_ways as u64) + last_stake - c.backing_stake; c.exposure.total += e.backing_stake; + c.backing_stake += e.backing_stake; if let Some(i_expo) = c.exposure.others.iter_mut().find(|i| i.who == nominator_address) { i_expo.value = e.backing_stake; } }); - difference } \ No newline at end of file diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 40532611773b6..5d318df61b4f4 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -54,9 +54,9 @@ fn basic_setup_works() { assert_eq!(Staking::nominators(100), vec![10, 20]); // Account 10 is exposed by 1000 * balance_factor from their own stash in account 11 + the default nominator vote - assert_eq!(Staking::stakers(10), Exposure { total: 1500, own: 1000, others: vec![ IndividualExposure { who: 100, value: 500 }] }); + assert_eq!(Staking::stakers(10), Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: 100, value: 250 }] }); // Account 20 is exposed by 1000 * balance_factor from their own stash in account 21 + the default nominator vote - assert_eq!(Staking::stakers(20), Exposure { total: 1500, own: 1000, others: vec![ IndividualExposure { who: 100, value: 500 }] }); + assert_eq!(Staking::stakers(20), Exposure { total: 1250, own: 1000, others: vec![ IndividualExposure { who: 100, value: 250 }] }); // The number of validators required. assert_eq!(Staking::validator_count(), 2); @@ -69,7 +69,7 @@ fn basic_setup_works() { assert_eq!(Staking::current_session_reward(), 10); // initial slot_stake - assert_eq!(Staking::slot_stake(), 1500); + assert_eq!(Staking::slot_stake(), 1250); // initial slash_count of validators assert_eq!(Staking::slash_count(&10), 0); @@ -671,13 +671,13 @@ fn nominating_and_rewards_should_work() { assert_eq!(Staking::stakers(10).own, 1000); assert_eq!(Staking::stakers(10).total, 1000 + 1000); // 2 and 4 supported 10, each with stake 600, according to phragmen. - assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![600, 400]); + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![500, 500]); assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); // total expo of 20, with 500 coming from nominators (externals), according to phragmen. assert_eq!(Staking::stakers(20).own, 1000); assert_eq!(Staking::stakers(20).total, 1000 + 1000); // 2 and 4 supported 20, each with stake 250, according to phragmen. - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![400, 600]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![500, 500]); assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); // They are not chosen anymore @@ -898,7 +898,7 @@ fn cannot_reserve_staked_balance() { // Confirm account 11 has some free balance assert_eq!(Balances::free_balance(&11), 1000); // Confirm account 11 (via controller 10) is totally staked - assert_eq!(Staking::stakers(&10).total, 1000 + 500); + assert_eq!(Staking::stakers(&10).total, 1000 + 250); // Confirm account 11 cannot transfer as a result assert_noop!(Balances::reserve(&11, 1), "account liquidity restrictions prevent withdrawal"); @@ -1455,7 +1455,7 @@ fn phragmen_poc_works() { assert_ok!(Staking::bond(Origin::signed(1), 2, 500, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20, 30])); - + assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(4), vec![10, 20, 40])); @@ -1467,17 +1467,17 @@ fn phragmen_poc_works() { // with stake 1666 and 1333 respectively assert_eq!(Staking::stakers(10).own, 1000); - assert_eq!(Staking::stakers(10).total, 1000 + 499); + assert_eq!(Staking::stakers(10).total, 1000 + 500); assert_eq!(Staking::stakers(20).own, 1000); - assert_eq!(Staking::stakers(20).total, 1000 + 499); + assert_eq!(Staking::stakers(20).total, 1000 + 500); // Nominator's stake distribution. - assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![333, 166]); - assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).sum::>(), 499); + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).collect::>>(), vec![250, 250]); + assert_eq!(Staking::stakers(10).others.iter().map(|e| e.value).sum::>(), 500); assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![166, 333]); - assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).sum::>(), 499); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).collect::>>(), vec![250, 250]); + assert_eq!(Staking::stakers(20).others.iter().map(|e| e.value).sum::>(), 500); assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::>>(), vec![4, 2]); }); } @@ -1669,11 +1669,11 @@ fn wrong_vote_is_null() { // add 1 nominators assert_ok!(Staking::bond(Origin::signed(1), 2, 2000, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(2), vec![ - 10, 20, // good votes + 10, 20, // good votes 1, 2, 15, 1000, 25 // crap votes. No effect. ])); - // new block + // new block System::set_block_number(1); Session::check_rotate_session(System::block_number()); @@ -1685,4 +1685,188 @@ fn wrong_vote_is_null() { fn bond_with_no_staked_value() { // Behavior when someone bonds with no staked value. // Particularly when she votes and the candidate is elected. + with_externalities(&mut ExtBuilder::default() + .validator_count(3) + .nominate(false) + .minimum_validator_count(1) + .build(), || { + // setup + assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller)); + Balances::set_free_balance(&3, 1000); + Balances::set_free_balance(&4, 1000); + Balances::set_free_balance(&2, 1000); + + // initial validators + assert_eq!(Session::validators(), vec![20, 10]); + + // Stingy validator. + assert_ok!(Staking::bond(Origin::signed(1), 2, 0, RewardDestination::Controller)); + assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); + + System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + + // Not elected even though we want 3. + assert_eq!(Session::validators(), vec![20, 10]); + + // min of 10 and 20. + assert_eq!(Staking::slot_stake(), 1000); + + // let's make the stingy one elected. + assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller)); + assert_ok!(Staking::nominate(Origin::signed(4), vec![2])); + + assert_eq!(Staking::ledger(4), Some(StakingLedger { stash: 3, active: 500, total: 500, unlocking: vec![]})); + + System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + + assert_eq!(Session::validators(), vec![20, 10, 2]); + assert_eq!(Staking::stakers(2), Exposure { own: 0, total: 500, others: vec![IndividualExposure { who: 4, value: 500}]}); + + assert_eq!(Staking::slot_stake(), 500); + + // no rewards paid to 2 and 4 yet + assert_eq!(Balances::free_balance(&2), 1000); + assert_eq!(Balances::free_balance(&4), 1000); + + System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + + let reward = Staking::current_session_reward(); + // 2 will not get any reward + // 4 will get all the reward share + assert_eq!(Balances::free_balance(&2), 1000); + assert_eq!(Balances::free_balance(&4), 1000 + reward); + }); +} +#[test] +fn bond_with_little_staked_value() { + // Behavior when someone bonds with little staked value. + // Particularly when she votes and the candidate is elected. + with_externalities(&mut ExtBuilder::default() + .validator_count(3) + .nominate(false) + .minimum_validator_count(1) + .build(), + || { + // setup + assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller)); + Balances::set_free_balance(&2, 1000); + + // initial validators + assert_eq!(Session::validators(), vec![20, 10]); + + // Stingy validator. + assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller)); + assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); + + System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + + // 2 is elected. + // and fucks up the slot stake. + assert_eq!(Session::validators(), vec![20, 10, 2]); + assert_eq!(Staking::slot_stake(), 1); + + // Old ones are rewarded. + assert_eq!(Balances::free_balance(&10), 1 + 10); + assert_eq!(Balances::free_balance(&20), 1 + 10); + // no rewards paid to 2. This was initial election. + assert_eq!(Balances::free_balance(&2), 1000); + + System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + + assert_eq!(Session::validators(), vec![20, 10, 2]); + assert_eq!(Staking::slot_stake(), 1); + + let reward = Staking::current_session_reward(); + // 2 will not get the full reward, practically 1 + assert_eq!(Balances::free_balance(&2), 1000 + reward.max(1)); + }); +} + + +#[test] +fn phragmen_linear_worse_case_equalise() { + with_externalities(&mut ExtBuilder::default() + .nominate(false) + .validator_pool(true) + .fare(true) + .build(), + || { + let bond_validator = |a, b| { + Balances::set_free_balance(&(a-1), b); + assert_ok!(Staking::bond(Origin::signed(a-1), a, b, RewardDestination::Controller)); + assert_ok!(Staking::validate(Origin::signed(a), ValidatorPrefs::default())); + }; + let bond_nominator = |a, b, v| { + Balances::set_free_balance(&(a-1), b); + assert_ok!(Staking::bond(Origin::signed(a-1), a, b, RewardDestination::Controller)); + assert_ok!(Staking::nominate(Origin::signed(a), v)); + }; + + for i in &[10, 20, 30, 40] { assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); } + + bond_validator(50, 1000); + bond_validator(60, 1000); + bond_validator(70, 1000); + + bond_nominator(2, 2000, vec![10]); + bond_nominator(4, 1000, vec![10, 20]); + bond_nominator(6, 1000, vec![20, 30]); + bond_nominator(8, 1000, vec![30, 40]); + bond_nominator(110, 1000, vec![40, 50]); + bond_nominator(112, 1000, vec![50, 60]); + bond_nominator(114, 1000, vec![60, 70]); + + assert_eq!(Session::validators(), vec![40, 30]); + assert_ok!(Staking::set_validator_count(7)); + + System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + + assert_eq!(Session::validators(), vec![10, 60, 40, 20, 50, 30, 70]); + + // Sequential Phragmén with post processing gives + // 10 is elected with stake 3000.0 and score 0.00025 + // 30 is elected with stake 2008.8712884829595 and score 0.0003333333333333333 + // 50 is elected with stake 2000.0001049958742 and score 0.0003333333333333333 + // 60 is elected with stake 1991.128921508789 and score 0.0004444444444444444 + // 20 is elected with stake 2017.7421569824219 and score 0.0005277777777777777 + // 40 is elected with stake 2000.0001049958742 and score 0.0005555555555555556 + // 70 is elected with stake 1982.2574230340813 and score 0.0007222222222222222 + + assert_eq!(Staking::stakers(10).total, 3000); + assert_eq!(Staking::stakers(30).total, 2035); + assert_eq!(Staking::stakers(50).total, 2000); + assert_eq!(Staking::stakers(60).total, 1968); + assert_eq!(Staking::stakers(20).total, 2035); + assert_eq!(Staking::stakers(40).total, 2024); + assert_eq!(Staking::stakers(70).total, 1936); + }) +} + +#[test] +fn phragmen_chooses_correct_validators() { + with_externalities(&mut ExtBuilder::default() + .nominate(true) + .validator_pool(true) + .fare(true) + .validator_count(1) + .build(), + || { + // 4 validator candidates + // self vote + default account 100 is nominator. + assert_eq!(Staking::validator_count(), 1); + assert_eq!(Session::validators().len(), 1); + + System::set_block_number(1); + Session::check_rotate_session(System::block_number()); + + assert_eq!(Session::validators().len(), 1); + }) }