-
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: Fix Cliff Validator Update Bugs #1858
Conversation
@cwgoes are you working on this more or can @alexanderbez / myself work form here? |
Not presently - by all means take over this PR. |
Codecov Report
@@ Coverage Diff @@
## develop #1858 +/- ##
===========================================
+ Coverage 64.82% 64.87% +0.04%
===========================================
Files 114 114
Lines 6804 6836 +32
===========================================
+ Hits 4411 4435 +24
- Misses 2114 2119 +5
- Partials 279 282 +3 |
@cwgoes since this was originally your PR, I cannot add you as a reviewer, but please still feel free to review. Also, still getting acquainted with the staking code/logic, so lmk if there is a more immediate and efficient solution to what I have implemented 👍 /cc @rigelrozanski |
x/stake/keeper/validator.go
Outdated
case powerIncreasing && !validator.Revoked && | ||
(oldFound && oldValidator.Status == sdk.Bonded): | ||
|
||
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) | ||
store.Set(GetTendermintUpdatesKey(validator.Owner), bz) | ||
|
||
if cliffPower != nil { | ||
k.updateCliffValidator(ctx, validator) |
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.
from the godoc of k.updateCliffVlaidator, it seems that we should have the cliffPower != nil check in there?
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.
Not sure I totally agree, I think have the explicit check here makes the context more clear opposed to always calling updateCliffValidator
.
x/stake/keeper/validator.go
Outdated
iterator := sdk.KVStorePrefixIterator(store, ValidatorsByPowerIndexKey) | ||
offset := 0 | ||
for { | ||
if !iterator.Valid() || offset == 1 { |
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 understand this for loop. Why are we breaking when offset == 1
. It seems to me that the only code path here is offset incrementing after the first iteration, and this for loop breaking. (Which means it shouldn't be a loop)
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 originally had it with an offset of two. In which case I needed the loop, but then discovered that would not work. I glossed over the fact that I no longer need the loop.
x/stake/keeper/validator.go
Outdated
|
||
// Create a validator iterator ranging from smallest to largest in power | ||
// where only the smallest by power is needed. | ||
iterator := sdk.KVStorePrefixIterator(store, ValidatorsByPowerIndexKey) |
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.
Hmm I don't think this is the correct logic. The power store includes (a) many validator candidates which are not validators (not in the top hundred by stake) and (b) revoked validators, neither of which should be set as the cliff validator. The cliff validator is simply the "lowest-ranked bonded validator by stake" - but this logic reads the "lowest-ranked validator candidate", which is not necessarily the same.
For the case of "same cliff validator, increased power" this rough logic is fine. For the case of "cliff validator increased power, so now no longer cliff validator" I think we need to seek from the old cliff validator power store key and find the next-highest stake validator (which might become the cliff validator).
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 see. It did not occur to me that the power store held those validators as well. I'll need to refactor this and add test cases where we also have revoked validators and candidates which are not validators.
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't "request changes" on my own PR (apparently), but see comment - also, we should extend the testcases to cover the situation described in the comment.
} | ||
|
||
k.SetValidator(ctx, validator) | ||
return validator | ||
} | ||
|
||
// updateCliffValidator determines if the current cliff validator needs to be |
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 like to call this, "maybeUpdateCliffValidator", which implies that it only happens conditionally.
The comment could be better, as it does more than determine the conditional.
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.
ehhhh I'm not totally sure about maybeUpdateCliffValidator
, but I will certainly update the godoc 👍
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.
Not of the idea of putting maybe
in the func name. Something we've used for a similar idea for the sequence number is Ensure
, -> I'd be okay with something more like EnsureCliffValidator
or UpdateEnsureCliffValidator
or UpdateOrEnsureCliffValidator
Use of this type of language for func naming would be cool to document in the Tenderming/coding
- opened an issue here tendermint/coding#71
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.
Agreed more appropriate nomenclature could be defined and standardized, but I'm not convinced Ensure*
is it?
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.
ensure
implies a post-condition for the function, which is accurate
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 we should comment explaining what the postcondition is)
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 first it seemed to me, ensure
implies something must be done (in this, now old, case would imply that cliff must be updated, even though it's possible for it not to).
Now that I think about it more, it kinda makes sense.
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.
@alexanderbez Did we reach consensus on updating the name to ensureCliffValidator
?
x/stake/keeper/validator.go
Outdated
case powerIncreasing && !validator.Revoked && | ||
(oldFound && oldValidator.Status == sdk.Bonded): | ||
|
||
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) | ||
store.Set(GetTendermintUpdatesKey(validator.Owner), bz) | ||
|
||
if cliffPower != nil { | ||
k.attemptUpdateCliffValidator(ctx, validator) |
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.
Why don't we just call this UpdateCliffValidator
and move the code currently in this attempt
function to the switch statement here:
if cliffPower != nil {
cliffAddr := sdk.AccAddress(k.GetCliffValidator(ctx))
if bytes.Equal(cliffAddr, affectedVal.Owner) {
k.UpdateCliffValidator(ctx, validator)
}
}
I bet there is an even nicer way to combine those if statements too
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 like this idea as well. I'll update.
x/stake/keeper/validator.go
Outdated
affectedValidator types.Validator) (updatedVal types.Validator, updated bool) { | ||
func (k Keeper) UpdateBondedValidators( | ||
ctx sdk.Context, affectedValidator types.Validator, | ||
) (updatedVal types.Validator, updated bool) { |
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.
better indentation:
func (k Keeper) UpdateBondedValidators(
ctx sdk.Context, affectedValidator types.Validator) (
updatedVal types.Validator, updated bool) {
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.
Tbh, I don't necessarily agree here because it's hard for me to immediately see the return values (and their potential names) due to the location of the opening parenthesis. (e.g. immediate glance seems to show it has no returns).
But this is your baby and I'd like to keep in-line with what you've written and the team's standards, so I can update it 😄
x/stake/keeper/validator.go
Outdated
offset := 0 | ||
var newCliffVal types.Validator | ||
|
||
for ; iterator.Valid() && offset < 1; iterator.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.
Why a loop and an offset
- we should only need to scan one record?
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 believe Rige and I discussed to use an offset to be a bit cautious (offset should only be 1).
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 it'll be cleaner w/o one. I'll remove it.
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) | ||
} | ||
|
||
if currVal.Status != sdk.Bonded || currVal.Revoked { |
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.
Good defensive coding!
x/stake/keeper/validator.go
Outdated
// The affected validator remains the cliff validator, however, since | ||
// the store does not contain the new power, set the new cliff | ||
// validator to the affected validator. | ||
k.setCliffValidator(ctx, affectedVal, pool) |
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 just need to update the power, right? I think setCliffValidator
will unnecessarily re-set the cliff validator address.
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.
Correct! I suppose we can save a write, huh?
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.
A few minor suggestions but otherwise LGTM.
Addressed comments @cwgoes . |
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.
Can't approve my own PR, but LGTM. Left one minor comment, just a nit.
x/stake/keeper/validator.go
Outdated
|
||
var newCliffVal types.Validator | ||
|
||
for ; iterator.Valid(); iterator.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.
I still don't understand the point of a for loop with only one iteration, why do we need a loop structure at all?
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.
Yeah ++ this comment we shouldn't have a loop it should just panic at the higher level if it would have reached the end of the loop
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.
Guys my brain was fried yesterday...sorry about this.
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.
haha no worries (me too!) thanks for the updated
…cosmos-sdk into cwgoes/cliff-validator-power-bug
Updated @cwgoes @rigelrozanski |
newCliffVal = currVal | ||
iterator.Close() | ||
} else { | ||
panic("failed to create valid validator power iterator") |
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 wonder if this might crash if the user is running only one validator. Not sure if that is a usecase we care about or not - but we could check it with params.MaxVals
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 probably want to support that for convenience in testing.
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.
This should not crash, but let me check.
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.
Yeah it works with one validator 👍
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.
Sweet! Lets merge this then!
} | ||
|
||
k.SetValidator(ctx, validator) | ||
return validator | ||
} | ||
|
||
// updateCliffValidator determines if the current cliff validator needs to be |
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.
@alexanderbez Did we reach consensus on updating the name to ensureCliffValidator
?
@cwgoes re |
updateCliffValidator
which is called viaUpdateValidator
when the power of a validator increases.closes: #1857
docs/
)PENDING.md
that include links to the relevant issue or PR that most accurately describes the change.cmd/gaia
andexamples/
For Admin Use: