Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Enable an easy way to unbond, if you are not *exposed* #8436

Closed
kianenigma opened this issue Mar 23, 2021 · 11 comments · Fixed by #12129
Closed

Enable an easy way to unbond, if you are not *exposed* #8436

kianenigma opened this issue Mar 23, 2021 · 11 comments · Fixed by #12129
Labels
J0-enhancement An additional feature request. J2-unconfirmed Issue might be valid, but it’s not yet known. U3-nice_to_have Issue is worth doing eventually.

Comments

@kianenigma
Copy link
Contributor

kianenigma commented Mar 23, 2021

For numerous reasons, we might have nominators who don't end up in the exposure, because of resource limits of the offchain solution submitted to the chain. So the setting is:

  1. They still do get their opinion imposed on the election result.
  2. They cannot be slashed.
  3. Nor can they be rewarded, both because they are not in anyone's Exposure.

We can, in practice, let someone who's unbonding off the hook (and let them not wait the whole BondingDuration) if we know that they are not in the Exposure of any validator, both in the current era and all the previous BondingDuration eras.

With the current storage layout, doing this is quite expensive. I think that transaction would cost a lot -- potentially not even fitting into a single block, so you probably have a better time waiting for the BondingDuration.

That being said, I want to open this meta-issue to bring up that this is a matter that could be improved here. Usually, I would say that this is a matter of UX, and good UX is not the main priority of the protocol. Nonetheless, it would be good to discuss if any other options exist to achieve this, without sacrificing security.

Inspired by comment from @horaciob paritytech/polkadot#2418 (comment)

@kianenigma kianenigma added J0-enhancement An additional feature request. U3-nice_to_have Issue is worth doing eventually. J2-unconfirmed Issue might be valid, but it’s not yet known. labels Mar 23, 2021
@kianenigma kianenigma added this to To do in FRAME Vision and Direction via automation Mar 23, 2021
@burdges
Copy link

burdges commented Mar 23, 2021

We could touch all nominators after every election to record "when last exposed", not sure what happens currently there.

In principle, we could provide some tool off-chain that crawls the old exposure sets of their current nominations, which plays okay with BEEFY, except all those past block references gets heavy.

@kianenigma
Copy link
Contributor Author

We could touch all nominators after every election to record "when last exposed", not sure what happens currently there.

That's one way. But of course we don't do this right now, and doing so won't be trivial. We would need to touch tens of thousands of accounts, as opposed to the current validator-major exposures that are stored, which is only a few hundred keys.


Alternatively, we could also store a separate storage item that stored the exposed nominators per era. But this is even more expensive than the previous solution.

All in all, both of these need us to do a lot more work upon finishing an election on the staking side. We can only allow such a thing to happen as a kind of background task (#8197) or if we finish the election multi-block.


@thiolliere one way or another, reducing history depth will help with this. Any reason to keep this at the current, seemingly overkill 84?

@burdges
Copy link

burdges commented Jun 22, 2021

I'm not convinced we really need this honestly.. it's definitely nicer ux.. but does it buy us any concrete benefit?

@kianenigma
Copy link
Contributor Author

For anyone who bonded by mistake, or bonded but is not capable of participating in nomination, this would be useful. It is not important, as you said. Just nice ux.

@burdges
Copy link

burdges commented Jun 22, 2021

I see.. yes, we could do bonded and never nominated rather cleanly. We could likely handle bonded but chilled cleanly too. Also bonded but all nominations dropped out or kicked me. It's maybe simpler to cover all special cases like these directly? It's bonded but my nominations never win that sucks, although maybe not wholly impossible even there. Could we just make issues for all special cases and mark them as "good first issue" or whatever?

@thiolliere
Copy link
Contributor

@thiolliere one way or another, reducing history depth will help with this. Any reason to keep this at the current, seemingly overkill 84?

But history depth is not related to bounding duration, as far as I can see. history depth is for giving time to call the reward.
And slashing is using information in pallet-session-historical so not related to history depth, no ?

@kianenigma
Copy link
Contributor Author

@thiolliere one way or another, reducing history depth will help with this. Any reason to keep this at the current, seemingly overkill 84?

But history depth is not related to bounding duration, as far as I can see. history depth is for giving time to call the reward.
And slashing is using information in pallet-session-historical so not related to history depth, no ?

I just looked at the fact that to prove that you are not exposed you have to prove that you are not in ErasStakers, and this double map is pruned after HistoryDepth.

Although, I think it is enough to prove that you are not in the BondingDuration last eras of ErasStakers, not the whole thing, so yeah it is unrelated.

NOTE: should we not have an integrity test to assert HistoryDepth > BondingDuration?

@thiolliere
Copy link
Contributor

thiolliere commented Jun 23, 2021

NOTE: should we not have an integrity test to assert HistoryDepth > BondingDuration?

I actually think it is correct to have BondingDuration higher than HistoryDepth. BondingDuration store only the hash of the exposure on chain while HistoryDepth store the full exposure, so it is usually sensible to have HistoryDepth < BondingDuration

@kianenigma
Copy link
Contributor Author

With @rossbulat we came up with a different approach to solving this:

By default, in the current polkadot config, the cost of checking if someone is exposed or not is 28 * 300 = 8400 storage reads. In kusama, it is 28000. The reason is that we have to check every validator, every era. Ross's suggestion was that if we knew historical nomination info, we can only check a smaller subset of validators.

We create a new map, called HistoricalNominations = Map<T::AccountId, BoundedVec<(T::AccountId, EraIndex)>>. The key is the nominator, the value is a vector, representing the union of all of the validators that this nominator has nominated, combined with the era at which this nomination happened.

  1. upon nominate call:
  2. We read this union/vector, and prune the old eras that are old enough. This is one storage read, and a small in-memory pruning.
  3. We compute the new union, and store it*.
  4. the entire existing unstake function remains the same. Instead, upon a new call unstake_fast, we check the exposure of all the validators that exist in the origin's union storage.

Needless to say, for this to work, this union vector will be bounded, e.g. 128. This implies that:

  1. Within any window of 28 days, the union of all the validators that a nominator nominates need to be bounded.
  2. The worse case tx-fee and weight of the unstake_fast will be this bound, multiplied by 28.

The only missing piece here is that I really want to make this opt-in. This functionality is only useful for those who are ACTUALLY not exposed. Within an ideal staking system, we won't have that many of these people around, so constraining everyone for the sake of this small minority is wrong.

@rossbulat
Copy link

rossbulat commented Aug 1, 2022

To throw another idea out there, we could eliminate the EraIndex in the map if we have a "try before you buy" model, where there is a set limit of 28 eras to quick-unbond from opting in, in the event the nominator is not exposed.

Users can opt-in when they call nominate. HistoricalNominations would then be:

HistoricalNominations = Map<T::AccountId, (when: T::BlockNumber, validators: BoundedVec<T::AccountId>)>

where when is the block they opted-in (batch this call with nominate.

This is more of a convenience function for new users who may not be aware of how nominations work and therefore choose totally inactive nominations. After 28 ears pass, perhaps storage could be pruned somehow within another call.

@rossbulat
Copy link

rossbulat commented Aug 12, 2022

I’ve thought quite deeply about how a quick unstake system can be introduced in staking. Here are my findings.

A nominator should be able to immediately unstake if their nominations have not been exposed in the last BondingDuration eras. But it is extremely costly to check this. Instead of iterating the entirety of ErasStakers, we could just keep track of a nominator's nominees, and check whether that subset have been exposed in the last BondingDuration eras.

Such a feature will not be useful for everyone, so this should ideally be an opt-in feature.

We want to do this in a way that is easy to clean up stale era data. The most efficient way to do this is by calling a single remove_prefix with the expired era in question.

To achieve this, we introduce 2 new storage items:


New Storage items

A StorageDoubleMap to hold a nominator’s validators per era:

HistoricalNominations:  (Era, Nominator) => [...Validators]  // BoundedVec<64>

We also introduce a StorageMap to keep track of when fast unstake starts, and which eras a nominator sets their nominees to provide a means to refer to the previous storage map:

HistoricalNominatorEras: Nominator => (start_era, [...Eras]) // BoundedVec<28>

We now have a means of tracking which eras nominees are set, and which nominees were set per era, in a way we can easily remove outdated records.


Managing stale storage

We can use clear_era_information to remove HistoricalNominations items that have surpassed BondingDuration eras:

// this ensures there will always be a maximum of 64 * 28 validator exposures to check.
HistoricalNominations::remove_prefix(old_era)

New calls

register_fast_unstake()

  • For new nominators, this should be called before bonding and nominating, as this guarantees there are no outstanding exposures. start_era will be the current era.
  • For existing nominators, they will need to wait at least BondingDuration eras before exposures can be determined. start_era will be the current era + BondingDuration.
  • Initiates HistoricalNominatorEras with an empty array for the era indexes.

unregister_fast_unstake()

  • Removes HistoricalNominatorEras and any HistoricalNominations.

fast_unstake()

  • Aggregates all unique validators of HistoricalNominations and checks whether they have been exposed. If none have been exposed, the fast unstake succeeds, otherwise it fails.

Updates to existing calls

nominate(): Updates HistoricalNominations and HistoricalNominatorEras. Prunes HistoricalNominatorEras of stale eras.

withdraw(): Removes HistoricalNominationsand HistoricalNominatorEras as user is no longer staking.


Shortfalls:

  • No existing historical nominee records: Upon opting in to fast unstake, an existing nominator will still need to wait at least BondingDuration eras before we know whether their nominees have been exposed.
  • Lack of incentive to unregister: A deposit or some sort of incentive to opt-in / keep fast stake active (> deposit = > duration) could be introduced and stored in HistoricalNominatorEras.

Benefits

  • Unstake if not exposed: Ability to call fast_unstake() if no exposures.

Other Possible Benefits:

  • Immediate unstake before nominator's first era: By just having HistoricalNominatorEras, we can derive when a nominator first started nominating and allow them to unstake before they take part in their first election cycle.
  • Inactive nomination pools can offer fast unstake: Pool owners could register to fast unstake and allow any pool member to withdraw their funds if the pool has no exposures.

@kianenigma kianenigma mentioned this issue Aug 28, 2022
2 tasks
FRAME Vision and Direction automation moved this from To do to Done Sep 23, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
J0-enhancement An additional feature request. J2-unconfirmed Issue might be valid, but it’s not yet known. U3-nice_to_have Issue is worth doing eventually.
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

4 participants