-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Refactor staking ledger #14582
base: master
Are you sure you want to change the base?
Refactor staking ledger #14582
Conversation
…Removes Staking::ledger getter; All mutating and reading calls for the staking ledger go through StakingLedger
… or stash account
frame/staking/src/ledger.rs
Outdated
|
||
/// Remove entries from `unlocking` that are sufficiently old and reduce the | ||
/// total by the sum of their balances. | ||
pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am assuming you have not changed this and the next two methods, so won't review detailed for now.
Possibly, moving them to a different impl block might be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the sake of reducing the diff, you could even leave that impl
block in lib.rs
and move it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No changes to this code. I will move the logic back to to decrease the diff for now.
@@ -318,8 +318,10 @@ pub mod pallet { | |||
pub type MinCommission<T: Config> = StorageValue<_, Perbill, ValueQuery>; | |||
|
|||
/// Map from all (unlocked) "controller" accounts to the info regarding the staking. | |||
/// | |||
/// Note: All the reads and mutations to this storage *MUST* be done through the methods exposed | |||
/// by [`StakingLedger`] to ensure data and lock consistency. | |||
#[pallet::storage] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could actually put this into a private mod now and use frame::use_parts()
or something like that? not that it is super critical here, but I'd like to see pallet splitting in action, if it is even useful @gupnik might help here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll look into this with @gupnik. Depending on the changes required, I'd suggest to open an issue and work on this asap once this PR is merged -- will check what's the best way fwd.
frame/staking/src/pallet/mod.rs
Outdated
|
||
// You're auto-bonded forever, here. We might improve this by only bonding when | ||
// you actually validate/nominate and remove once you unbond __everything__. | ||
ledger.bond()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the beginning of this function, we made sure that this account is not staked already. Therefore, this shoudl be defensive.
If this is the only reason why fn bond
exists thourgh, I would suggest just using fn update
.
ledger.bond()?; | |
ledger.update(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new version where Payee
mutations are encapsulated in the staking ledger, I think that ledger.bond(reward_destination)
makes more sense. So that we don't need to pass an option with the reward destination every time we want to update the ledger or make the API more convoluted. wdyt? (will update the PR accordingly done)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks on the right track, but foremost:
This PR refactors the staking ledger logic to encapsulate all reads and mutations of Ledger, Bonded and stake locks within the StakingLedger struct implementation.
is actually not fully respected, as Bonded
is still being deleted outside of StakingLedger
.
This is critical work and I don't want to push any PR to get larger, but I do genuinely think that making Payee
be bundled into the same should also simplify the code, and in the process, you should be able to verify that indeed it is updated at the same time as Ledger
and Bonded
.
The CI pipeline was cancelled due to failure one of the required jobs. |
/// *MUST* be performed through the methods exposed by this struct, to ensure the consistency of | ||
/// ledger's data and corresponding staking lock | ||
/// | ||
/// TODO: move struct definition and full implementation into `/src/ledger.rs`. Currently |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
StakingAccount::Controller(controller) => Ok(controller), | ||
}?; | ||
|
||
<Ledger<T>>::get(&controller) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not suggesting you do this as it is too much in the fringes, but hear me out:
// NewType for stash
pub struct Stash<T>(T::AccountId)
// NewType for controller
pub struct Controller<T>(T::AccountId)
enum StakingAccount {
Stash(Stash),
Controller(Controller)
}
Now you can actually make the key of Ledger
be Controller
, not an opaque AccountId
.
If deemed worthy, could be a nice follow-up, but I am worried it would have too much clutter, and once Controller is gone, it is all in vein.
But I hope the idea is clear. We are pury complicating things so that we can use Rust's type system for more safety.
Self::paired_account(StakingAccount::Controller(controller)), | ||
}; | ||
|
||
if let Some(stash) = stash { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mostly equal to <Payee<T>>::get(stash).defensive_unwrap_or_default();
, but I guess you might want the custom error log.
/// not exist in storage, it returns `None`. | ||
pub(crate) fn controller(&self) -> Option<T::AccountId> { | ||
self.controller | ||
.clone() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defensive_or_else
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can certainly see corners that can be improved, but all in all it is correct and a step in the right direction.
// update this staker in the sorted list, if they exist in it. | ||
if T::VoterList::contains(&stash) { | ||
let _ = | ||
T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)).defensive(); | ||
let _ = T::VoterList::on_update(&stash, Self::weight_of(&stash)).defensive(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a simple follow-up, we can move weight_of
into Ledger
as well, as it can be compute using just the ledger.
This PR refactors the staking ledger logic to encapsulate all reads and mutations of
Ledger
,Bonded
,Payee
and stake locks within theStakingLedger
struct implementation.With these changes, all the reads and mutations to the
Ledger
,Payee
andBonded
storage map should be done through the methods exposed byStakingLedger
to ensure the data and lock consistency of the operations. The new introduced methods that mutate and readLedger
are:ledger.update()
: inserts/updates a staking ledger in storage; updates staking locks accordingly (andledger.bond()
, which is synthatic sugar forledger.update()
)ledger.kill()
: removes allBonded
andStakingLedger
related data for a given ledger; updates staking locks accordingly;StakingLedger::get(account)
: queries both theBonded
andLedger
storages and returns aOption<StakingLedger>
. The pallet impl exposesfn ledger(account)
as synthatic sugar forStakingLedger::get(account)
.Retrieving a ledger with
StakingLedger::get()
can be done by providing either a stash or controller account. The input must be wrapped in aStakingAccount
variant (Stash
orController
) which is treated accordingly. This simplifies the caller API but will eventually be deprecated once we completely get rid of the controller account in staking. However, this refactor will help with the work necessary when completely removing the controller.Other goals:
OnStakingUpdate
updates to the target list (related to [Staking] Approval Stake tracking polkadot-sdk#443)Note: it would be great to prevent or at least raise a warning if
Ledger<T>
,Payee<T>
andBonded<T>
storage types are accessed outside theStakingLedger
implementation. This PR should not get blocked by that feature, but there's a tracking issue here paritytech/polkadot-sdk#149polkadot companion: paritytech/polkadot#7565
To Finish:
Bonded
,Ledger
andPayee
underStakingLedger
ledger.rs
Related and step towards paritytech/polkadot-sdk#443