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: Power Store Invariant #2671

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ IMPROVEMENTS
- #2617 [x/mock/simulation] Randomize all genesis parameters
- \#1924 [simulation] Use a transition matrix for block size
- #2610 [x/stake] Block redelegation to and from the same validator
- #2669 [x/stake] Added invarant check to make sure validator's power aligns with its spot in the power store.


* Tendermint
Expand All @@ -58,6 +59,7 @@ BUG FIXES
* Gaia CLI (`gaiacli`)

* Gaia
- #2670 [x/stake] fixed incorrent `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators`

* SDK
- #2625 [x/gov] fix AppendTag function usage error
Expand Down
4 changes: 4 additions & 0 deletions types/stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ type ValidatorSet interface {
IterateValidatorsBonded(Context,
func(index int64, validator Validator) (stop bool))

// iterate through the consensus validator set of the last block by operator address, execute func for each validator
IterateLastBlockConsValidators(Context,
func(index int64, validator Validator) (stop bool))

Validator(Context, ValAddress) Validator // get a particular validator by operator address
ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address
TotalPower(Context) Dec // total power of the validator set
Expand Down
24 changes: 23 additions & 1 deletion x/stake/keeper/sdk_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,29 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey)
maxValidators := k.MaxValidators(ctx)

iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey)
defer iterator.Close()

i := int64(0)
for ; iterator.Valid() && i < int64(maxValidators); iterator.Next() {
address := iterator.Value()
validator := k.mustGetValidator(ctx, address)

if validator.Status == sdk.Bonded {
stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to?
Copy link
Contributor

Choose a reason for hiding this comment

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

if stop {
break
}
i++
}
}
}

// iterate through the active validator set and perform the provided function
func (k Keeper) IterateLastBlockConsValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
iterator := k.LastValidatorsIterator(ctx)
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
address := AddressFromLastValidatorPowerKey(iterator.Key())
Expand Down
14 changes: 14 additions & 0 deletions x/stake/keeper/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ func (k Keeper) GetLastValidators(ctx sdk.Context) (validators []types.Validator
return validators[:i] // trim
}

// returns an iterator for the consensus validators in the last block
func (k Keeper) LastValidatorsIterator(ctx sdk.Context) (iterator sdk.Iterator) {
store := ctx.KVStore(k.storeKey)
iterator = sdk.KVStorePrefixIterator(store, LastValidatorPowerKey)
return iterator
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved
}

// get the current group of bonded validators sorted by power-rank
func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator {
store := ctx.KVStore(k.storeKey)
Expand All @@ -283,6 +290,13 @@ func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator {
return validators[:i] // trim
}

// returns an iterator for the current validator power store
func (k Keeper) ValidatorsPowerStoreIterator(ctx sdk.Context) (iterator sdk.Iterator) {
store := ctx.KVStore(k.storeKey)
iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey)
return iterator
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved
}

// gets a specific validator queue timeslice. A timeslice is a slice of ValAddresses corresponding to unbonding validators
// that expire at a certain time.
func (k Keeper) GetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (valAddrs []sdk.ValAddress) {
Expand Down
34 changes: 23 additions & 11 deletions x/stake/simulation/invariants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package simulation

import (
"bytes"
"fmt"

"github.com/cosmos/cosmos-sdk/baseapp"
Expand All @@ -10,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
"github.com/cosmos/cosmos-sdk/x/stake"
"github.com/cosmos/cosmos-sdk/x/stake/keeper"
abci "github.com/tendermint/tendermint/abci/types"
)

Expand All @@ -24,7 +26,7 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper,
if err != nil {
return err
}
err = PositivePowerInvariant(k)(app, header)
err = PowerStoreInvariant(k)(app, header)
if err != nil {
return err
}
Expand Down Expand Up @@ -101,22 +103,32 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper,
}

// PositivePowerInvariant checks that all stored validators have > 0 power
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved
func PositivePowerInvariant(k stake.Keeper) simulation.Invariant {
func PowerStoreInvariant(k stake.Keeper) simulation.Invariant {
return func(app *baseapp.BaseApp, _ abci.Header) error {
ctx := app.NewContext(false, abci.Header{})
var err error
k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool {
if !validator.GetPower().GT(sdk.ZeroDec()) {
err = fmt.Errorf("validator with non-positive power stored. (pubkey %v)", validator.GetConsPubKey())
return true

iterator := k.ValidatorsPowerStoreIterator(ctx)
pool := k.GetPool(ctx)

for ; iterator.Valid(); iterator.Next() {
validator, found := k.GetValidator(ctx, iterator.Value())
if !found {
panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value()))
}
return false
})
return err

powerKey := keeper.GetValidatorsByPowerIndexKey(validator, pool)

if !bytes.Equal(iterator.Key(), powerKey) {
return fmt.Errorf("power store invariance:\n\tvalidator.Power: %v"+
"\n\tkey should be: %v\n\tkey in store: %v", validator.GetPower(), powerKey, iterator.Key())
}
}
iterator.Close()
return nil
}
}

// ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set
// ValidatorSetInvariZant checks equivalence of Tendermint validator set and SDK validator set
sunnya97 marked this conversation as resolved.
Show resolved Hide resolved
func ValidatorSetInvariant(k stake.Keeper) simulation.Invariant {
return func(app *baseapp.BaseApp, _ abci.Header) error {
// TODO
Expand Down