-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Changes from 16 commits
c3aa256
790f579
374c71f
cc3c05f
5ac49aa
f0277aa
bcb65ea
4f3a5e3
b09d0f6
3fffea0
28a9f01
593e1ac
72c227f
8c38a85
80c9e18
a138f00
26c9500
f4a639f
53c1f64
0e8f864
3aa715e
20d8db3
a293869
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -134,6 +134,8 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat | |
} | ||
|
||
// get the group of bonded validators sorted by power-rank | ||
// | ||
// TODO: Rename to GetBondedValidatorsByPower or GetValidatorsByPower(ctx, status) | ||
func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { | ||
store := ctx.KVStore(k.storeKey) | ||
maxValidators := k.GetParams(ctx).MaxValidators | ||
|
@@ -191,13 +193,13 @@ func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { | |
|
||
//___________________________________________________________________________ | ||
|
||
// Perfom all the nessisary steps for when a validator changes its power. This | ||
// Perform all the necessary steps for when a validator changes its power. This | ||
// function updates all validator stores as well as tendermint update store. | ||
// It may kick out validators if new validator is entering the bonded validator | ||
// group. | ||
// | ||
// nolint: gocyclo | ||
// TODO: Remove above nolint, function needs to be simplified | ||
// TODO: Remove above nolint, function needs to be simplified! | ||
func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { | ||
store := ctx.KVStore(k.storeKey) | ||
pool := k.GetPool(ctx) | ||
|
@@ -210,19 +212,29 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type | |
cliffPower := k.GetCliffValidatorPower(ctx) | ||
|
||
switch { | ||
// if already bonded and power increasing only need to update tendermint | ||
// if the validator is already bonded and the power is increasing, we need | ||
// perform the following: | ||
// a) update Tendermint | ||
// b) check if the cliff validator needs to be updated | ||
case powerIncreasing && !validator.Revoked && | ||
(oldFound && oldValidator.Status == sdk.Bonded): | ||
|
||
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) | ||
store.Set(GetTendermintUpdatesKey(validator.Owner), bz) | ||
|
||
if cliffPower != nil { | ||
cliffAddr := sdk.AccAddress(k.GetCliffValidator(ctx)) | ||
if bytes.Equal(cliffAddr, validator.Owner) { | ||
k.updateCliffValidator(ctx, validator) | ||
} | ||
} | ||
|
||
// if is a new validator and the new power is less than the cliff validator | ||
case cliffPower != nil && !oldFound && | ||
bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower | ||
// skip to completion | ||
|
||
// if was unbonded and the new power is less than the cliff validator | ||
// if was unbonded and the new power is less than the cliff validator | ||
case cliffPower != nil && | ||
(oldFound && oldValidator.Status == sdk.Unbonded) && | ||
bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower | ||
|
@@ -232,11 +244,10 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type | |
// a) not-bonded and now has power-rank greater than cliff validator | ||
// b) bonded and now has decreased in power | ||
default: | ||
|
||
// update the validator set for this validator | ||
// if updated, the validator has changed bonding status | ||
updatedVal, updated := k.UpdateBondedValidators(ctx, validator) | ||
if updated { // updates to validator occurred to be updated | ||
if updated { | ||
// the validator has changed bonding status | ||
validator = updatedVal | ||
break | ||
} | ||
|
@@ -246,13 +257,73 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type | |
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) | ||
store.Set(GetTendermintUpdatesKey(validator.Owner), bz) | ||
} | ||
|
||
} | ||
|
||
k.SetValidator(ctx, validator) | ||
return validator | ||
} | ||
|
||
// updateCliffValidator determines if the current cliff validator needs to be | ||
// updated or swapped. If the provided affected validator is the current cliff | ||
// validator before it's power was increased, either the cliff power key will | ||
// be updated or if it's power is greater than the next bonded validator by | ||
// power, it'll be swapped. | ||
func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validator) { | ||
store := ctx.KVStore(k.storeKey) | ||
pool := k.GetPool(ctx) | ||
cliffAddr := sdk.AccAddress(k.GetCliffValidator(ctx)) | ||
|
||
oldCliffVal, found := k.GetValidator(ctx, cliffAddr) | ||
if !found { | ||
panic(fmt.Sprintf("cliff validator record not found for address: %v\n", cliffAddr)) | ||
} | ||
|
||
// NOTE: We get the power via affectedVal since the store (by power key) | ||
// has yet to be updated. | ||
affectedValPower := affectedVal.GetPower() | ||
|
||
// Create a validator iterator ranging from smallest to largest by power | ||
// starting the current cliff validator's power. | ||
start := GetValidatorsByPowerIndexKey(oldCliffVal, pool) | ||
end := sdk.PrefixEndBytes(ValidatorsByPowerIndexKey) | ||
iterator := store.Iterator(start, end) | ||
|
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Why a loop and an There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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. |
||
ownerAddr := iterator.Value() | ||
|
||
currVal, found := k.GetValidator(ctx, ownerAddr) | ||
if !found { | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Good defensive coding! |
||
panic(fmt.Sprintf("unexpected revoked or unbonded validator for address: %s\n", ownerAddr)) | ||
} | ||
|
||
newCliffVal = currVal | ||
offset++ | ||
} | ||
|
||
iterator.Close() | ||
if offset == 0 { | ||
panic("unable to find cliff validator") | ||
} | ||
|
||
if bytes.Equal(affectedVal.Owner, newCliffVal.Owner) { | ||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. We just need to update the power, right? I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct! I suppose we can save a write, huh? |
||
} else if affectedValPower.GT(newCliffVal.GetPower()) { | ||
// The affected validator no longer remains the cliff validator as it's | ||
// power is greater than the new current cliff validator. | ||
k.setCliffValidator(ctx, newCliffVal, pool) | ||
} | ||
} | ||
|
||
func (k Keeper) updateForRevoking(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator { | ||
if newValidator.Revoked && oldFound && oldValidator.Status == sdk.Bonded { | ||
newValidator = k.unbondValidator(ctx, newValidator) | ||
|
@@ -317,8 +388,9 @@ func (k Keeper) updateValidatorPower(ctx sdk.Context, oldFound bool, oldValidato | |
// | ||
// nolint: gocyclo | ||
// TODO: Remove the above golint | ||
func (k Keeper) UpdateBondedValidators(ctx sdk.Context, | ||
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 commentThe 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 commentThe 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 😄 |
||
|
||
store := ctx.KVStore(k.storeKey) | ||
|
||
|
@@ -328,7 +400,8 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context, | |
var validator, validatorToBond types.Validator | ||
newValidatorBonded := false | ||
|
||
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest | ||
// create a validator iterator ranging from largest to smallest by power | ||
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) | ||
for { | ||
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { | ||
break | ||
|
@@ -354,6 +427,7 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context, | |
validatorToBond = validator | ||
newValidatorBonded = true | ||
} | ||
|
||
bondedValidatorsCount++ | ||
|
||
// sanity check | ||
|
@@ -363,6 +437,7 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context, | |
|
||
iterator.Next() | ||
} | ||
|
||
iterator.Close() | ||
|
||
// clear or set the cliff validator | ||
|
@@ -372,17 +447,17 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context, | |
k.clearCliffValidator(ctx) | ||
} | ||
|
||
// swap the cliff validator for a new validator if the affected validator was bonded | ||
// swap the cliff validator for a new validator if the affected validator | ||
// was bonded | ||
if newValidatorBonded { | ||
|
||
// unbond the cliff validator | ||
if oldCliffValidatorAddr != nil { | ||
cliffVal, found := k.GetValidator(ctx, oldCliffValidatorAddr) | ||
if !found { | ||
panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) | ||
} | ||
k.unbondValidator(ctx, cliffVal) | ||
|
||
k.unbondValidator(ctx, cliffVal) | ||
} | ||
|
||
// bond the new validator | ||
|
@@ -391,6 +466,7 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context, | |
return validator, true | ||
} | ||
} | ||
|
||
return types.Validator{}, false | ||
} | ||
|
||
|
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 isEnsure
, -> I'd be okay with something more likeEnsureCliffValidator
orUpdateEnsureCliffValidator
orUpdateOrEnsureCliffValidator
Use of this type of language for func naming would be cool to document in the
Tenderming/coding
- opened an issue here tendermint/coding#71There 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 accurateThere 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
?