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: Ensure Tendermint Validator Update Invariants #2238

Merged
merged 14 commits into from
Sep 12, 2018
Merged
2 changes: 2 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ BUG FIXES
* [cli] [\#2265](https://github.com/cosmos/cosmos-sdk/issues/2265) Fix JSON formatting of the `gaiacli send` command.

* Gaia
* [x/stake] Return correct Tendermint validator update set on `EndBlocker` by not
including non previously bonded validators that have zero power. [#2189](https://github.com/cosmos/cosmos-sdk/issues/2189)

* SDK
* [\#1988](https://github.com/cosmos/cosmos-sdk/issues/1988) Make us compile on OpenBSD (disable ledger) [#1988] (https://github.com/cosmos/cosmos-sdk/issues/1988)
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/staking/end_block.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ the changes cleared

```golang
EndBlock() ValidatorSetChanges
vsc = GetTendermintUpdates()
vsc = GetValidTendermintUpdates()
ClearTendermintUpdates()
return vsc
```
6 changes: 6 additions & 0 deletions x/gov/simulation/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,16 @@ func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation {
return operationSimulateMsgVote(k, sk, nil, -1)
}


// nolint: unparam
func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, proposalID int64) simulation.Operation {
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
if key == nil {
key = simulation.RandomKey(r, keys)
}

var ok bool

if proposalID < 0 {
proposalID, ok = randomProposalID(r, k, ctx)
if !ok {
Expand All @@ -166,15 +169,18 @@ func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey,
}
addr := sdk.AccAddress(key.PubKey().Address())
option := randomVotingOption(r)

msg := gov.NewMsgVote(addr, proposalID, option)
if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}

ctx, write := ctx.CacheContext()
result := gov.NewHandler(k)(ctx, msg)
if result.IsOK() {
write()
}

event(fmt.Sprintf("gov/MsgVote/%v", result.IsOK()))
action = fmt.Sprintf("TestMsgVote: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
return action, nil, nil
Expand Down
22 changes: 10 additions & 12 deletions x/mock/simulation/random_simulate_blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func SimulateFromSeed(
request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log)

// Update the validator set
validators = updateValidators(tb, r, validators, res.ValidatorUpdates, event)
validators = updateValidators(tb, r, log, validators, res.ValidatorUpdates, event)
}

fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount)
Expand Down Expand Up @@ -334,20 +334,17 @@ func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant,
}

// updateValidators mimicks Tendermint's update logic
// nolint: unparam
func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValidator, updates []abci.Validator, event func(string)) map[string]mockValidator {
func updateValidators(
tb testing.TB, r *rand.Rand, log string, current map[string]mockValidator,
updates []abci.Validator, event func(string)) map[string]mockValidator {

for _, update := range updates {
switch {
case update.Power == 0:
// // TEMPORARY DEBUG CODE TO PROVE THAT THE OLD METHOD WAS BROKEN
// // (i.e. didn't catch in the event of problem)
// if val, ok := tb.(*testing.T); ok {
// require.NotNil(val, current[string(update.PubKey.Data)])
// }
// // CORRECT CHECK
// if _, ok := current[string(update.PubKey.Data)]; !ok {
// tb.Fatalf("tried to delete a nonexistent validator")
// }
if _, ok := current[string(update.PubKey.Data)]; !ok {
tb.Fatalf("tried to delete a nonexistent validator: %v", log)
}

event("endblock/validatorupdates/kicked")
delete(current, string(update.PubKey.Data))
default:
Expand All @@ -362,5 +359,6 @@ func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValida
}
}
}

return current
}
2 changes: 1 addition & 1 deletion x/stake/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid
k.SetIntraTxCounter(ctx, 0)

// calculate validator set changes
ValidatorUpdates = k.GetTendermintUpdates(ctx)
ValidatorUpdates = k.GetValidTendermintUpdates(ctx)
k.ClearTendermintUpdates(ctx)
return
}
Expand Down
33 changes: 27 additions & 6 deletions x/stake/keeper/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,16 +201,37 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator {
// Accumulated updates to the active/bonded validator set for tendermint

// get the most recently updated validators
func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) {
//
// CONTRACT: Only validators with non-zero power or zero-power that were bonded
// at the previous block height or were removed from the validator set entirely
// are returned to Tendermint.
func (k Keeper) GetValidTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) {
store := ctx.KVStore(k.storeKey)

iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest
iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey)
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val abci.Validator
k.cdc.MustUnmarshalBinary(valBytes, &val)
updates = append(updates, val)
var abciVal abci.Validator

abciValBytes := iterator.Value()
k.cdc.MustUnmarshalBinary(abciValBytes, &abciVal)

val, found := k.GetValidator(ctx, abciVal.GetAddress())
if found {
// The validator is new or already exists in the store and must adhere to
// Tendermint invariants.
prevBonded := val.BondHeight < ctx.BlockHeight()
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'm not sure if this check is sound, but on the surface is seems correct.

zeroPower := val.GetPower().Equal(sdk.ZeroDec())

alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
if !zeroPower || zeroPower && prevBonded {
updates = append(updates, abciVal)
}
} else {
// Add the ABCI validator in such a case where the validator was removed
// from the store as it must have existed before.
updates = append(updates, abciVal)
}
}

iterator.Close()
return
}
Expand Down
Loading