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

Add ValidateGenesis to staking, add more tests #2450

Merged
merged 2 commits into from
Oct 8, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ IMPROVEMENTS
* [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200)
* [x/stake] \#2435 Improve memory efficiency of getting the various store keys
* [genesis] \#2229 Ensure that there are no duplicate accounts or validators in the genesis state.
* [genesis] \#2450 Validate staking genesis parameters.
* Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) \#1571
* \#1941(https://github.com/cosmos/cosmos-sdk/issues/1941) Version is now inferred via `git describe --tags`.
* [x/distribution] \#1671 add distribution types and tests
Expand Down
19 changes: 1 addition & 18 deletions cmd/gaia/app/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/stake"
stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types"

"github.com/spf13/pflag"

Expand Down Expand Up @@ -242,29 +241,13 @@ func GaiaValidateGenesisState(genesisState GenesisState) (err error) {
if err != nil {
return
}
err = validateGenesisStateValidators(genesisState.StakeData.Validators)
err = stake.ValidateGenesis(genesisState.StakeData)
if err != nil {
return
}
return
}

func validateGenesisStateValidators(validators []stakeTypes.Validator) (err error) {
addrMap := make(map[string]bool, len(validators))
for i := 0; i < len(validators); i++ {
val := validators[i]
strKey := string(val.ConsPubKey.Bytes())
if _, ok := addrMap[strKey]; ok {
return fmt.Errorf("Duplicate validator in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress())
}
if val.Jailed && val.Status == sdk.Bonded {
return fmt.Errorf("Validator is bonded and jailed in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress())
}
addrMap[strKey] = true
}
return
}

// Ensures that there are no duplicate accounts in the genesis state,
func validateGenesisStateAccounts(accs []GenesisAccount) (err error) {
addrMap := make(map[string]bool, len(accs))
Expand Down
57 changes: 57 additions & 0 deletions x/stake/genesis.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package stake

import (
"fmt"

abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"

Expand Down Expand Up @@ -75,3 +77,58 @@ func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisVali

return
}

// ValidateGenesis validates the provided staking genesis state to ensure the
// expected invariants holds. (i.e. params in correct bounds, no duplicate validators)
func ValidateGenesis(data types.GenesisState) error {
err := validateGenesisStateValidators(data.Validators)
if err != nil {
return err
}
err = validateParams(data.Params)
if err != nil {
return err
}

return nil
}

func validateParams(params types.Params) error {
if params.GoalBonded.LTE(sdk.ZeroDec()) {
bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String()
return fmt.Errorf("staking parameter GoalBonded should be positive, instead got %s percent", bondedPercent)
}
if params.GoalBonded.GT(sdk.OneDec()) {
bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String()
return fmt.Errorf("staking parameter GoalBonded should be less than 100 percent, instead got %s percent", bondedPercent)
}
if params.BondDenom == "" {
return fmt.Errorf("staking parameter BondDenom can't be an empty string")
}
if params.InflationMax.LT(params.InflationMin) {
return fmt.Errorf("staking parameter Max inflation must be greater than or equal to min inflation")
}
return nil
}

func validateGenesisStateValidators(validators []types.Validator) (err error) {
addrMap := make(map[string]bool, len(validators))
for i := 0; i < len(validators); i++ {
val := validators[i]
strKey := string(val.ConsPubKey.Bytes())
if _, ok := addrMap[strKey]; ok {
return fmt.Errorf("duplicate validator in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress())
}
if val.Jailed && val.Status == sdk.Bonded {
return fmt.Errorf("validator is bonded and jailed in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress())
}
if val.Tokens.IsZero() {
return fmt.Errorf("genesis validator cannot have zero pool shares, validator: %v", val)
}
if val.DelegatorShares.IsZero() {
return fmt.Errorf("genesis validator cannot have zero delegator shares, validator: %v", val)
}
addrMap[strKey] = true
}
return
}
62 changes: 60 additions & 2 deletions x/stake/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"

abci "github.com/tendermint/tendermint/abci/types"
"github.com/stretchr/testify/assert"

sdk "github.com/cosmos/cosmos-sdk/types"
keep "github.com/cosmos/cosmos-sdk/x/stake/keeper"
"github.com/cosmos/cosmos-sdk/x/stake/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
)

func TestInitGenesis(t *testing.T) {
Expand Down Expand Up @@ -105,3 +107,59 @@ func TestInitGenesisLargeValidatorSet(t *testing.T) {

require.Equal(t, abcivals, vals)
}

func TestValidateGenesis(t *testing.T) {
genValidators1 := make([]types.Validator, 1, 5)
pk := ed25519.GenPrivKey().PubKey()
genValidators1[0] = types.NewValidator(sdk.ValAddress(pk.Address()), pk, types.NewDescription("", "", "", ""))
genValidators1[0].Tokens = sdk.OneDec()
genValidators1[0].DelegatorShares = sdk.OneDec()

tests := []struct {
name string
mutate func(*types.GenesisState)
wantErr bool
Copy link
Contributor

Choose a reason for hiding this comment

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

cool testcase design pattern!

}{
{"default", func(*types.GenesisState) {}, false},
// validate params
{"200% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Add(sdk.OneDec()) }, true},
{"-67% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Neg() }, true},
{"no bond denom", func(data *types.GenesisState) { (*data).Params.BondDenom = "" }, true},
{"min inflation > max inflation", func(data *types.GenesisState) {
(*data).Params.InflationMin = (*data).Params.InflationMax.Add(sdk.OneDec())
}, true},
{"min inflation = max inflation", func(data *types.GenesisState) {
(*data).Params.InflationMax = (*data).Params.InflationMin
}, false},
// validate genesis validators
{"duplicate validator", func(data *types.GenesisState) {
(*data).Validators = genValidators1
(*data).Validators = append((*data).Validators, genValidators1[0])
}, true},
{"no pool shares", func(data *types.GenesisState) {
(*data).Validators = genValidators1
(*data).Validators[0].Tokens = sdk.ZeroDec()
}, true},
{"no delegator shares", func(data *types.GenesisState) {
(*data).Validators = genValidators1
(*data).Validators[0].DelegatorShares = sdk.ZeroDec()
}, true},
{"jailed and bonded validator", func(data *types.GenesisState) {
(*data).Validators = genValidators1
(*data).Validators[0].Jailed = true
(*data).Validators[0].Status = sdk.Bonded
}, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
genesisState := types.DefaultGenesisState()
tt.mutate(&genesisState)
if tt.wantErr {
assert.Error(t, ValidateGenesis(genesisState))
} else {
assert.NoError(t, ValidateGenesis(genesisState))
}
})
}
}