-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
R4R: Unbonding and Redelegations Queue #2405
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #2405 +/- ##
==========================================
+ Coverage 59.69% 59.7% +0.01%
==========================================
Files 136 136
Lines 8405 8428 +23
==========================================
+ Hits 5017 5032 +15
- Misses 3055 3060 +5
- Partials 333 336 +3 |
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.
Thanks @sunnya97 - mostly looks good, left comments on spec correctness, BeginBlock/EndBlock, and iterator ordering.
I think these changes make it pretty easy to allow delegators to have multiple simultaneous unbonding delegations from the same validator / redelegations from one validator to another validator. Just need some simple ID for the struct store keys - maybe the counter we're now using for validator ordering. (that can be a separate PR though)
docs/spec/staking/end_block.md
Outdated
|
||
Note that unlike CompleteUnbonding slashing of redelegating shares does not | ||
take place during completion. Slashing on redelegated shares takes place | ||
actively as a slashing occurs. |
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.
Then explain why we need this, e.g. "The redelegation completion queue serves simply to clean up state, as redelegations older than an unbonding period need not be kept."
docs/spec/staking/end_block.md
Outdated
|
||
## CompleteUnbonding | ||
|
||
Complete the unbonding and transfer the coins to the delegate. Perform any |
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.
How about "realize" any slashing, "perform" is a bit confusing since the slashing already happened. Let's explain how the slashing already occurred - namely, that the unbonding delegation struct will be updated at that point to reflect the reduced later withdrawal amount.
docs/spec/staking/end_block.md
Outdated
unbondingQueue(currTime time.Time): | ||
for all unbondings whose CompleteTime < currTime: | ||
validator = GetValidator(unbonding.ValidatorAddr) | ||
returnTokens = ExpectedTokens * unbonding.startSlashRatio/validator.SlashRatio |
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 think this is wrong, we now track Balance
explicitly.
x/stake/handler.go
Outdated
@@ -37,6 +33,46 @@ func NewHandler(k keeper.Keeper) sdk.Handler { | |||
|
|||
// Called every block, process inflation, update validator set | |||
func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Validator) { | |||
cdc := types.MsgCdc |
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.
Is there a reason the queue logic is run in the EndBlocker
instead of the BeginBlocker
? I guess it doesn't matter much but the latter would seem a bit more accurate since it means state during execution of transactions within the block will always be consistent (any old redelegations / unbonding delegations will have been deleted).
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.
Idk, I did the governance one during EndBlock, so just did this one in EndBlock as well. Don't mind moving it though, cause I don't think it really matters either way.
What's the reason for doing it in BeginBlock instead of EndBlock? Also, why would the state of transactions in a block not be consistent if it happens in EndBlock?
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.
any old redelegations / unbonding delegations will have been deleted
This is also true if you move if move it to the endblock of the previous block - so I'd say it's still just as arbitrary.
The general trend which I've seen in our development is to include more state machine logic in EndBlock - and more special block prep logic in BeginBlock. Although the more I think about it, the more I want to just Merge the two
I'm in favour of leaving in endblock - however I also think it's totally arbitrary
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 don't think it's arbitrary, if we put the logic in BeginBlock
instead we now have the invariant that all transactions will see a consistent view of the unbonding delegations / redelegations (where expired ones have been deleted), so we don't need to e.g. check timestamps on slashing.
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 don't see why it changes the consistency? All the txs in this block will still see it in the queue, and all the txs in the next block, won't. You can already get rid of the check in slashing. Basically, it gives one extra block for evidence to be submitted through txs (which no evidence types are submitted in txs rn anyways, so kinda irrelevant).
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.
x/stake/keeper/delegation.go
Outdated
@@ -155,6 +155,41 @@ func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDe | |||
store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr)) | |||
} | |||
|
|||
// Gets a specific unbonding queue timeslice. A timeslice is a slice of keys to an unbonding delegation |
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.
Missing period. Also, the latter is a bit confusing no? Are we getting a list of delegator/validator address pairs for a certain time?
I would also prefix this and the methods below with Get
, Set
, and Insert
respectively to make it more clear and easier to read.
x/stake/keeper/delegation.go
Outdated
|
||
// Insert an unbonding delegation to the appropriate timeslice in the unbonding queue | ||
func (k Keeper) UnbondingQueueInsert(ctx sdk.Context, ubd types.UnbondingDelegation) { | ||
timeSlice := k.UnbondingQueueGetTimeSlice(ctx, ubd.MinTime) |
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 think we can put types.DVPair{ubd.DelegatorAddr, ubd.ValidatorAddr}
into a variable atop and use that in the cases. It'll shorten the code to read and DRY it up.
x/stake/keeper/delegation.go
Outdated
@@ -241,6 +276,41 @@ func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { | |||
store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) | |||
} | |||
|
|||
// Gets a specific redelegation queue timeslice. A timeslice is a slice of keys to an redelegation delegation | |||
func (k Keeper) RedelegationQueueGetTimeSlice(ctx sdk.Context, timestamp time.Time) (dvvTriplets []types.DVVTriplet) { |
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.
Same feedback as above.
x/stake/keeper/key.go
Outdated
@@ -154,6 +157,12 @@ func GetUBDsByValIndexKey(valAddr sdk.ValAddress) []byte { | |||
return append(UnbondingDelegationByValIndexKey, valAddr.Bytes()...) | |||
} | |||
|
|||
// gets the prefix for all unbonding delegations from a delegator | |||
func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte { | |||
bz, _ := timestamp.MarshalBinary() |
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.
Do you think we should panic here and in GetRedelegationTimeKey
to be defensive/safe?
x/stake/types/delegation.go
Outdated
@@ -9,6 +9,19 @@ import ( | |||
sdk "github.com/cosmos/cosmos-sdk/types" | |||
) | |||
|
|||
// struct that just has a delegator validator pair with no other data. To be used to marshal keys |
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.
Let's be consistent with the punctuation/use of full sentences. i.e. pick one or the other 👍
x/stake/types/delegation.go
Outdated
ValidatorAddr sdk.ValAddress | ||
} | ||
|
||
// struct that just has a delegator validator validator triplet with no other data. To be used to marshal keys |
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.
ditto
aed49da
to
766dc94
Compare
x/stake/handler.go
Outdated
endBlockerTags := sdk.EmptyTags() | ||
|
||
unbondingTimesliceIterator := k.UnbondingQueueIterator(ctx, ctx.BlockHeader().Time) | ||
for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() { |
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.
could we add a short comment here describing how this iterator actually works? - it's a big confusing just looking at the code. - for instance, it's not immediately apparent to me how the iterator breaks out once it's gone past the appropriate time for an unbond
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.
Generally looks well done - couple comments above - the only important request I'd like to make is that we test the "inserted" queue. By inserted queue test I mean, write a test for EndBlock functionality testing an redelegation/unbond transaction from a validator which is already in the unbonding state. The existing tests didn't cover this case because it was relevant previous to the queue (triggered by transaction).
This test should probably be located in x/stake/handler_test.go
and look kinda like something between TestRedelegationPeriod
from x/stake/handler_test.go
and TestRedelegateFromUnbondingValidator
from x/stake/keeper/delegation_test.go
3eac436
to
2baada9
Compare
x/stake/keeper/delegation.go
Outdated
@@ -449,6 +457,8 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sd | |||
minTime time.Time, height int64, completeNow bool) { | |||
|
|||
validator, found := k.GetValidator(ctx, valSrcAddr) | |||
fmt.Println(validator.Status) |
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.
please delete all the debugging code in this file (see 6ae62f6)
Also test_cover failing |
9b5c60c
to
b192b1d
Compare
x/stake/keeper/delegation.go
Outdated
} | ||
|
||
// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue | ||
func (k Keeper) GetAllMatureUnbondingQueue(ctx sdk.Context, currTime time.Time) (matureUnbonds []types.DVPair) { |
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.
Doesn't seem like GetAllMatureUnbondingQueue
and GetRedelegationQueueTimeSlice
are idempotent. Based on the Get*
nomenclature, I'd expect it to be so.
How do we feel about that? If we're going to keep it as non-idempotent. May I suggest changing the name?
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.
If I understand this correctly, during this Get
we are actually also deleting the records from the queue after they are retrieved (aka modifying the state, aka non-idempotent) - Yeah, for that reason I'd agree we should consider a different function-name-prefix. Seems like the most technically correct term to use here would be Dequeue
- ideally we should also prefix the function which adds elements to these queues with Enqueue
to stay consistent
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.
++
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.
utACK pending CI fixes -- thanks @sunnya97 👍
@sunnya97 Do you know why the simulator is failing? |
Isn't the simulator failing on develop as well? |
No. |
Let's rebase onto staking refactor |
Staking refactor now merged, this just needs conflict resolution. |
RE: as a part of this fix we should use the method |
74ef6c5
to
668cfef
Compare
closes #2393
Targeted PR against correct branch (see CONTRIBUTING.md)
Linked to github-issue with discussion and accepted design OR link to spec that describes this work.
Wrote tests
Updated relevant documentation (
docs/
)Added entries in
PENDING.md
with issue #rereviewed
Files changed
in the github PR explorerFor Admin Use: