joystream icon indicating copy to clipboard operation
joystream copied to clipboard

Staking/Nomination lock does not behave as rivalrous

Open Lezek123 opened this issue 2 years ago • 5 comments

While doing final checks as part of https://github.com/Joystream/joystream/issues/4162 I noticed that it's possible to use the same account (and therefore the same funds) to stake for both some rivalrous purpose (for example: working group application/role) and validation/nomination at the same time.

I suppose this should not be possible, because:

  • Validation/nomination lock is not part of NON_RIVALROUS_LOCKS
  • It's mentioned as Rivalrous in the handbook: https://joystream.gitbook.io/testnet-workspace/system/accounts-and-staking#locks-1
  • It makes sense for it to be rivalrous, as nomination/validation stake is subject to slashing (and so are other rivalrous stakes)

Despite that I have been able to setup an Alice account with those 3 stakes simultaneously:

  • staking account candidate stake (boundsta, Non-Rivalrous)
  • forum working group role (lead) stake (wg-forum, Rivalrous!)
  • nomination stake (staking, Rivalrous!)

image

Notice that this is only possible if I create wg-forum stake before staking stake, because otherwise the application would fail on ensure!(T::StakingHandler::is_account_free_of_conflicting_stakes, /* ... */). There is no such check when staking as validator/nominator however, as this logic lives in pallet_staking which is outside of our control.

┆Issue is synchronized with this Asana task by Unito

Lezek123 avatar Aug 17 '22 16:08 Lezek123

to stake for both some rivalrous purpose (for example: working group application/role) and validation/nomination at the same time.

I guess this first off begs the question: why did we not find out about this sooner? It certainly means we don't have QA for this? Can we add this @dmtrjsg ?

We also need to add integration tests to for this clearly, so adding this issue https://github.com/Joystream/joystream/issues/4190.

There is no such check when staking as validator/nominator however, as this logic lives in pallet_staking which is outside of our control.

Damn, this is so bad. I can't even compute how we did not think of that, nor how two audits missed this basic point 🤯 . Seems there are two options

  1. Fork pallet_staking, and only tweak the place it checks to use our lock type aware conflict chcker.
  2. Make validation and nomination stacke non-rivalrous: main risk I can immediately think of is that it may corrupt the internal assumptions of the staking system if the balance on such an account is just slashed for exogenous reasons, possibly breaks some invariant, and at the very least means someone will earn returns on tokens that don't exist.

bedeho avatar Aug 17 '22 17:08 bedeho

Damn, this is so bad (...)

I agree, I cannot think of any "clean" solution at the moment. Initially I thought we could alternatively create our own type (for example StakingCurrency) that implements Currency and LockableCurrency and just override the set_lock method, but unfortunately this method is infallible, so we can't really add any checks there.

The other solution would be to use locks for non-rivalrous staking and reserve for rivalrous staking, but this would be a relatively big and risky rework and would require adjustments in (almost) all of our apps.

Lezek123 avatar Aug 18 '22 09:08 Lezek123

but this would be a relatively big and risky rework and would require adjustments in (almost) all of our apps.

Agree, and I believe we did evaluate it carefully and passed, although at this point my confidence in that work has dropped a bit!

bedeho avatar Aug 18 '22 10:08 bedeho

Initially I thought we could alternatively create our own type (for example StakingCurrency) that implements Currency and LockableCurrency and just override the set_lock method, but unfortunately this method is infallible, so we can't really add any checks there.

Following this thought, I think we can actually override Currency::free_balance() however in this custom StakingCurrency type. Provided we would only use it for pallet_staking::Config it should be safe to just make it return 0 in case there are any rivalrous locks :thinking:

Lezek123 avatar Aug 18 '22 10:08 Lezek123

That is actually quite interesting, it is almost too clever, like its a trap for people in the future, but I do like it.

bedeho avatar Aug 18 '22 20:08 bedeho

I tested alternative approach using additional SignedExtension to validate transactions that make a call to the bond() dispatchable in https://github.com/Joystream/joystream/pull/4213

Tested manually and works as expected. Since a node will apply the checks when a tx is submitted to the mempool, the transaction will never make it into a block, and on submission the error will be TransactionInvalid with a custom code, as you would get if submitting an extrinsic with a bad nonce for example. The check also comes before paying transaction fees so user will not loose funds. Our Integrations tests ran successfully without any noticeable slow down. (time taken to complete the full test scenario)

mnaamani avatar Aug 25 '22 22:08 mnaamani

Tested manually and works as expected. Since a node will apply the checks when a tx is submitted to the mempool, the transaction will never make it into a block

Does this mean this check is not part of consensus? if so then I don't think this really works, a motivated attacker could still produce a valid block which violates the rule.

bedeho avatar Aug 26 '22 07:08 bedeho

Does this mean this check is not part of consensus? if so then I don't think this really works, a motivated attacker could still produce a valid block which violates the rule.

Of course it is still part of consensus, used to verify blocks and transactions. My point was that it was a bonus that the check is also applied before the tx enters mempool.

mnaamani avatar Aug 26 '22 12:08 mnaamani

So after trying several approaches we finally bit the bullet and decided to make modification in the staking pallet. We are now using a fork of substrate, where we added a BondingRestriction trait on the pallet. Staking Pallet changes: https://github.com/Joystream/substrate/pull/8 Runtime configuration in joystream: https://github.com/Joystream/joystream/pull/4244

mnaamani avatar Sep 01 '22 15:09 mnaamani

worth testing Joystream/pioneer/pull/3894

traumschule avatar Nov 28 '22 08:11 traumschule