Skip to content
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

Merged
merged 23 commits into from
Aug 8, 2018
Merged

Conversation

cwgoes
Copy link
Contributor

@cwgoes cwgoes commented Jul 27, 2018

  • Implement updateCliffValidator which is called via UpdateValidator when the power of a validator increases.
  • Updated and added to the original tests @cwgoes wrote to test the initial bugs.
  • Update some comments

closes: #1857


  • Linked to github-issue with discussion and accepted design
  • Updated all relevant documentation (docs/)
  • Updated all relevant code comments
  • Wrote tests
  • Added entries in PENDING.md that include links to the relevant issue or PR that most accurately describes the change.
  • Updated cmd/gaia and examples/

For Admin Use:

  • Added appropriate labels to PR (ex. wip, ready-for-review, docs)
  • Reviewers Assigned
  • Squashed all commits, uses message "Merge pull request #XYZ: [title]" (coding standards)

@rigelrozanski
Copy link
Contributor

@cwgoes are you working on this more or can @alexanderbez / myself work form here?

@cwgoes
Copy link
Contributor Author

cwgoes commented Jul 30, 2018

@cwgoes are you working on this more or can @alexanderbez / myself work form here?

Not presently - by all means take over this PR.

@codecov
Copy link

codecov bot commented Aug 3, 2018

Codecov Report

Merging #1858 into develop will increase coverage by 0.04%.
The diff coverage is 77.77%.

@@             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

@alexanderbez
Copy link
Contributor

@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

@alexanderbez alexanderbez changed the title WIP: Cliff validator power increase bug R4R: Fix Cliff Validator Update Bugs Aug 3, 2018
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)
Copy link
Contributor

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?

Copy link
Contributor

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.

iterator := sdk.KVStorePrefixIterator(store, ValidatorsByPowerIndexKey)
offset := 0
for {
if !iterator.Valid() || offset == 1 {
Copy link
Contributor

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)

Copy link
Contributor

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.


// Create a validator iterator ranging from smallest to largest in power
// where only the smallest by power is needed.
iterator := sdk.KVStorePrefixIterator(store, ValidatorsByPowerIndexKey)
Copy link
Contributor Author

@cwgoes cwgoes Aug 5, 2018

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).

Copy link
Contributor

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.

Copy link
Contributor Author

@cwgoes cwgoes left a 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.

@alexanderbez alexanderbez changed the title R4R: Fix Cliff Validator Update Bugs WIP: Fix Cliff Validator Update Bugs Aug 6, 2018
}

k.SetValidator(ctx, validator)
return validator
}

// updateCliffValidator determines if the current cliff validator needs to be
Copy link
Contributor

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.

Copy link
Contributor

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 👍

Copy link
Contributor

@rigelrozanski rigelrozanski Aug 7, 2018

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

Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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)

Copy link
Contributor

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.

Copy link
Contributor Author

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?

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)
Copy link
Contributor

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

Copy link
Contributor

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.

@ebuchman ebuchman mentioned this pull request Aug 8, 2018
6 tasks
affectedValidator types.Validator) (updatedVal types.Validator, updated bool) {
func (k Keeper) UpdateBondedValidators(
ctx sdk.Context, affectedValidator types.Validator,
) (updatedVal types.Validator, updated bool) {
Copy link
Contributor

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) {

Copy link
Contributor

@alexanderbez alexanderbez Aug 8, 2018

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 😄

offset := 0
var newCliffVal types.Validator

for ; iterator.Valid() && offset < 1; iterator.Next() {
Copy link
Contributor Author

@cwgoes cwgoes Aug 8, 2018

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?

Copy link
Contributor

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).

Copy link
Contributor

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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good defensive coding!

// 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)
Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

@cwgoes cwgoes left a 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.

@alexanderbez
Copy link
Contributor

Addressed comments @cwgoes .

Copy link
Contributor Author

@cwgoes cwgoes left a 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.


var newCliffVal types.Validator

for ; iterator.Valid(); iterator.Next() {
Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Contributor

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

@alexanderbez
Copy link
Contributor

Updated @cwgoes @rigelrozanski

newCliffVal = currVal
iterator.Close()
} else {
panic("failed to create valid validator power iterator")
Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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 👍

Copy link
Contributor

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
Copy link
Contributor Author

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?

@alexanderbez
Copy link
Contributor

@cwgoes re ensureCliffValidator, I'm not totally sure because I removed the original conditional check out of the method. So I don't think it applies anymore?

@cwgoes cwgoes merged commit ac26d33 into develop Aug 8, 2018
@ValarDragon ValarDragon deleted the cwgoes/cliff-validator-power-bug branch September 23, 2018 03:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cliff validator power not correctly updated when cliff validator increases in power
5 participants