Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

staking multiple nominations #189

Merged
merged 26 commits into from
Feb 3, 2021
Merged

staking multiple nominations #189

merged 26 commits into from
Feb 3, 2021

Conversation

4meta5
Copy link
Contributor

@4meta5 4meta5 commented Jan 19, 2021

What does it do?

  • The previous design only allowed one validator per nominator. This PR replaces that constraint with a new one that allows T::MaxValidatorsPerNominator validators per nominator, such that each nomination > T::MinNomination and total nominated > T::MinNominatorStk.
  • update chainspecs with separate input param stakers: Vec<(AccountId, Option<AccountId>, Balance)>. For validators, it is (AccountId, None, Balance) and for nominators it is (AccountId, Some(ValidatorAccountId), Balance). This logic can be found in the stake pallet genesis build() impl.
  • validator candidates may bond more/less
  • nominators may bond more/less
  • switch_nomination runtime method

What important points reviewers should know?

Here are some high-level docs, which should make the code easier to read/review.

Is there something left for follow-up PRs?

enforcing inflation schedule, which was an original goal of this PR

What alternative implementations were considered?

Are there relevant PRs or issues in other repositories (Substrate, Polkadot, Frontier, Cumulus)?

#179

What value does it bring to the blockchain users?

Checklist

  • Does it require a purge of the network? No
  • You bumped the runtime version if there are breaking changes in the runtime ?
  • Does it require changes in documentation/tutorials ? No

@4meta5 4meta5 mentioned this pull request Jan 21, 2021
4 tasks
@4meta5 4meta5 mentioned this pull request Jan 25, 2021
3 tasks
@@ -111,17 +111,17 @@ impl<A, B: HasCompact> Into<IndividualExposure<A, B>> for Bond<A, B> {

#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
/// The activity status of the validator
pub enum ValidatorStatus<BlockNumber> {
pub enum ValidatorStatus {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ValidatorStatus used to be generic over the inner variant of Leaving, but it was only ever instantiated with the RoundIndex type. So, the generic was removed and the inner variant of ValidatorStatus::Leaving was defined to always be RoundIndex. Earlier in this PR, something similar was done to remove RoundIndex as a generic parameter from CandidateState.

@4meta5 4meta5 marked this pull request as ready for review January 27, 2021 20:11
@4meta5 4meta5 added the A0-pleasereview Pull request needs code review. label Jan 27, 2021
T::Origin::from(Some(actor.clone()).into()),
nominated_val.clone(),
balance,
)
} else {
<Module<T>>::join_candidates(
<Module<T>>::join_candidates_no_fee(
Copy link
Contributor Author

@4meta5 4meta5 Jan 27, 2021

Choose a reason for hiding this comment

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

This changes the default fee for validators registered at genesis to 0%. It was previously 2%.

We could also add a DefaultValidatorFee config constant and use that here with the old join_candidates method. Then, there's no need to create the join_candidates_no_fee runtime method, but it does add a config constant and there are a few already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Never mind, changed this so didn't add a new runtime method. The default fee was still changed to 0% for validators registered at genesis. is this a suitable default?

type IssuancePerRound = IssuancePerRound;
type MaxFee = MaxFee;
type MinValidatorStk = MinValidatorStk;
type MinNomination = MinNominatorStk;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This sets the minimum nomination as equal to MinNominatorStk = 5 * GLMR, but it could be set at some value less than MinNominatorStk. These limits exist to prevent trivial nominations from abusing runtime storage.

@4meta5
Copy link
Contributor Author

4meta5 commented Jan 28, 2021

  • update polkadot-js types based on changes to Rust types

@4meta5 4meta5 added the B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes label Jan 29, 2021
Copy link
Contributor

@JoshOrndorff JoshOrndorff left a comment

Choose a reason for hiding this comment

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

The notes are awesome. Can we get those as doc comments or something?

What does it mean to go offline?

Why do we need seperate bond_more (same question about bond_less) methods for validators and nominators? Maybe you could have a single fn bond_more(origin, candidate, more: BalanceOf<T>) and if the candidate is the same as the caller then treat it as a candidate bonding more. Or use an option like you did for the genesis config.

Design question: Can one account be a validator and also a nominator? If so can the account self nominate?

Concern: This pallet just ticked past 1k lines code, and some parts are deeply nested. I'm reassured by the thorough test coverage. But I wonder how this footprint compares to eg pallet staking.

pallets/stake/src/mock.rs Outdated Show resolved Hide resolved
@4meta5
Copy link
Contributor Author

4meta5 commented Jan 29, 2021

The notes are awesome. Can we get those as doc comments or something?

Thanks, the reason I made those notes is because I had stuff that needed to be communicated but didn't know where to put the relevant doc comment. For example, the discussion of OrderedSet usage because it's used in a few places. I'll look over it again and see if I can find better places to put some of the information. LMK if you have any suggestions.

What does it mean to go offline?

Leave the pool of candidates so cannot be selected as a validator, but still bonded and can rejoin the candidate pool by calling go_online. It also changes their status to offline so any changes to the nominations or the validator's bond do not require updating the CandidatePool. This is why there are a few places with code like

if candidate.is_active() {
 Self::update_active_candidate()
}

Design question: Can one account be a validator and also a nominator? If so can the account self nominate?

The validator and nominator roles are separate and no account is allowed to occupy both roles. So, when an account tries to join_candidates, there are two checks that (1) it isn't a nominator and (2) it isn't a validator candidate. For join_nominators, it has the same two checks.

So no validator can self-nominate. The validator has to be bonded > T::MinValidatorStk, but that value is stored separately from its nominations. It is the same as a self-nomination in practice, but it does not use the nominator runtime methods.

Why do we need seperate bond_more (same question about bond_less) methods for validators and nominators? Maybe you could have a single fn bond_more(origin, candidate, more: BalanceOf) and if the candidate is the same as the caller then treat it as a candidate bonding more. Or use an option like you did for the genesis config.

I could make this change, but I like having separate runtime methods for validator candidates and nominators because the storage items are separate.

Also I generally try to avoid using Options as input params on runtime methods when it splits the path inside the runtime method and there is not a lot of shared logic between the branches. This changes

fn validator_bond_more() {
   _validator_bond_more()
}

fn nominator_bond_more() {
   _nominator_bond_more()
}

to this

fn bond_more(Option) {
 if let Some(_) = Option {
    _nominator_bond_more()
  } else {
    _validator_bond_more()
  }
}

Because they have separate storage items, the branches don't share any logic so this change makes it more nested in order to combine them into a single method.

Concern: This pallet just ticked past 1k lines code, and some parts are deeply nested. I'm reassured by the thorough test coverage. But I wonder how this footprint compares to eg pallet staking.

Still ~2500 loc less than pallet-staking/src/lib.rs and that doesn't count the other files in that pallet.

To implement support for multiple nominations, I added another Vec that has to be iterated over whenever we add nominations, remove nominations, or change nominations. I think it's worth it, but agree that surface area has increased.

@JoshOrndorff
Copy link
Contributor

Thanks for the answers. That all makes good sense to me.

I think it makes sense to do another learning session where the team gets to stake and validate and nominate so we can develop a stronger intuition for the various code paths. This pallet is really nice.

Copy link
Contributor

@JoshOrndorff JoshOrndorff left a comment

Choose a reason for hiding this comment

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

LGTM

@4meta5 4meta5 merged commit 4fc505a into master Feb 3, 2021
@4meta5 4meta5 deleted the staking-inflation branch February 3, 2021 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A0-pleasereview Pull request needs code review. B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants