diff --git a/x/genutil/gentx_test.go b/x/genutil/gentx_test.go index 510c04d8d1e4..f3f81af59974 100644 --- a/x/genutil/gentx_test.go +++ b/x/genutil/gentx_test.go @@ -17,7 +17,6 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/genutil/types" - "github.com/cosmos/cosmos-sdk/x/staking" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -181,7 +180,7 @@ func (suite *GenTxTestSuite) TestValidateAccountInGenesis() { cdc := suite.encodingConfig.Codec suite.app.StakingKeeper.SetParams(suite.ctx, stakingtypes.DefaultParams()) - stakingGenesisState := staking.ExportGenesis(suite.ctx, suite.app.StakingKeeper) + stakingGenesisState := suite.app.StakingKeeper.ExportGenesis(suite.ctx) suite.Require().Equal(stakingGenesisState.Params, stakingtypes.DefaultParams()) stakingGenesis, err := cdc.MarshalJSON(stakingGenesisState) // TODO switch this to use Marshaler suite.Require().NoError(err) diff --git a/x/gov/genesis_test.go b/x/gov/genesis_test.go index 72057751177e..073e1b73fb89 100644 --- a/x/gov/genesis_test.go +++ b/x/gov/genesis_test.go @@ -19,7 +19,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/gov/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - "github.com/cosmos/cosmos-sdk/x/staking" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -56,7 +55,7 @@ func TestImportExportQueues(t *testing.T) { authGenState := auth.ExportGenesis(ctx, app.AccountKeeper) bankGenState := app.BankKeeper.ExportGenesis(ctx) - stakingGenState := staking.ExportGenesis(ctx, app.StakingKeeper) + stakingGenState := app.StakingKeeper.ExportGenesis(ctx) distributionGenState := app.DistrKeeper.ExportGenesis(ctx) // export the state and import it into a new app diff --git a/x/staking/genesis.go b/x/staking/genesis.go index c019359c3526..5b94b8adf48b 100644 --- a/x/staking/genesis.go +++ b/x/staking/genesis.go @@ -3,7 +3,6 @@ package staking import ( "fmt" - abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -12,201 +11,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// InitGenesis sets the pool and parameters for the provided keeper. For each -// validator in data, it sets that validator in the keeper along with manually -// setting the indexes. In addition, it also sets any delegations found in -// data. Finally, it updates the bonded validators. -// Returns final validator set after applying all declaration and delegations -func InitGenesis( - ctx sdk.Context, keeper keeper.Keeper, accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, data *types.GenesisState, -) (res []abci.ValidatorUpdate) { - bondedTokens := sdk.ZeroInt() - notBondedTokens := sdk.ZeroInt() - - // We need to pretend to be "n blocks before genesis", where "n" is the - // validator update delay, so that e.g. slashing periods are correctly - // initialized for the validator set e.g. with a one-block offset - the - // first TM block is at height 1, so state updates applied from - // genesis.json are in block 0. - ctx = ctx.WithBlockHeight(1 - sdk.ValidatorUpdateDelay) - - keeper.SetParams(ctx, data.Params) - keeper.SetLastTotalPower(ctx, data.LastTotalPower) - - for _, validator := range data.Validators { - keeper.SetValidator(ctx, validator) - - // Manually set indices for the first time - keeper.SetValidatorByConsAddr(ctx, validator) - keeper.SetValidatorByPowerIndex(ctx, validator) - - // Call the creation hook if not exported - if !data.Exported { - if err := keeper.AfterValidatorCreated(ctx, validator.GetOperator()); err != nil { - panic(err) - } - } - - // update timeslice if necessary - if validator.IsUnbonding() { - keeper.InsertUnbondingValidatorQueue(ctx, validator) - } - - switch validator.GetStatus() { - case types.Bonded: - bondedTokens = bondedTokens.Add(validator.GetTokens()) - case types.Unbonding, types.Unbonded: - notBondedTokens = notBondedTokens.Add(validator.GetTokens()) - default: - panic("invalid validator status") - } - } - - for _, delegation := range data.Delegations { - delegatorAddress, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) - if err != nil { - panic(err) - } - - // Call the before-creation hook if not exported - if !data.Exported { - if err := keeper.BeforeDelegationCreated(ctx, delegatorAddress, delegation.GetValidatorAddr()); err != nil { - panic(err) - } - } - - keeper.SetDelegation(ctx, delegation) - - // Call the after-modification hook if not exported - if !data.Exported { - if err := keeper.AfterDelegationModified(ctx, delegatorAddress, delegation.GetValidatorAddr()); err != nil { - panic(err) - } - } - } - - for _, ubd := range data.UnbondingDelegations { - keeper.SetUnbondingDelegation(ctx, ubd) - - for _, entry := range ubd.Entries { - keeper.InsertUBDQueue(ctx, ubd, entry.CompletionTime) - notBondedTokens = notBondedTokens.Add(entry.Balance) - } - } - - for _, red := range data.Redelegations { - keeper.SetRedelegation(ctx, red) - - for _, entry := range red.Entries { - keeper.InsertRedelegationQueue(ctx, red, entry.CompletionTime) - } - } - - bondedCoins := sdk.NewCoins(sdk.NewCoin(data.Params.BondDenom, bondedTokens)) - notBondedCoins := sdk.NewCoins(sdk.NewCoin(data.Params.BondDenom, notBondedTokens)) - - // check if the unbonded and bonded pools accounts exists - bondedPool := keeper.GetBondedPool(ctx) - if bondedPool == nil { - panic(fmt.Sprintf("%s module account has not been set", types.BondedPoolName)) - } - - // TODO: remove with genesis 2-phases refactor https://github.com/cosmos/cosmos-sdk/issues/2862 - bondedBalance := bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - if bondedBalance.IsZero() { - accountKeeper.SetModuleAccount(ctx, bondedPool) - } - - // if balance is different from bonded coins panic because genesis is most likely malformed - if !bondedBalance.IsEqual(bondedCoins) { - panic(fmt.Sprintf("bonded pool balance is different from bonded coins: %s <-> %s", bondedBalance, bondedCoins)) - } - - notBondedPool := keeper.GetNotBondedPool(ctx) - if notBondedPool == nil { - panic(fmt.Sprintf("%s module account has not been set", types.NotBondedPoolName)) - } - - notBondedBalance := bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) - if notBondedBalance.IsZero() { - accountKeeper.SetModuleAccount(ctx, notBondedPool) - } - - // If balance is different from non bonded coins panic because genesis is most - // likely malformed. - if !notBondedBalance.IsEqual(notBondedCoins) { - panic(fmt.Sprintf("not bonded pool balance is different from not bonded coins: %s <-> %s", notBondedBalance, notBondedCoins)) - } - - // don't need to run Tendermint updates if we exported - if data.Exported { - for _, lv := range data.LastValidatorPowers { - valAddr, err := sdk.ValAddressFromBech32(lv.Address) - if err != nil { - panic(err) - } - - keeper.SetLastValidatorPower(ctx, valAddr, lv.Power) - validator, found := keeper.GetValidator(ctx, valAddr) - - if !found { - panic(fmt.Sprintf("validator %s not found", lv.Address)) - } - - update := validator.ABCIValidatorUpdate(keeper.PowerReduction(ctx)) - update.Power = lv.Power // keep the next-val-set offset, use the last power for the first block - res = append(res, update) - } - } else { - var err error - - res, err = keeper.ApplyAndReturnValidatorSetUpdates(ctx) - if err != nil { - panic(err) - } - } - - return res -} - -// ExportGenesis returns a GenesisState for a given context and keeper. The -// GenesisState will contain the pool, params, validators, and bonds found in -// the keeper. -func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState { - var unbondingDelegations []types.UnbondingDelegation - - keeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) (stop bool) { - unbondingDelegations = append(unbondingDelegations, ubd) - return false - }) - - var redelegations []types.Redelegation - - keeper.IterateRedelegations(ctx, func(_ int64, red types.Redelegation) (stop bool) { - redelegations = append(redelegations, red) - return false - }) - - var lastValidatorPowers []types.LastValidatorPower - - keeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { - lastValidatorPowers = append(lastValidatorPowers, types.LastValidatorPower{Address: addr.String(), Power: power}) - return false - }) - - return &types.GenesisState{ - Params: keeper.GetParams(ctx), - LastTotalPower: keeper.GetLastTotalPower(ctx), - LastValidatorPowers: lastValidatorPowers, - Validators: keeper.GetAllValidators(ctx), - Delegations: keeper.GetAllDelegations(ctx), - UnbondingDelegations: unbondingDelegations, - Redelegations: redelegations, - Exported: true, - } -} - // WriteValidators returns a slice of bonded genesis validators. func WriteValidators(ctx sdk.Context, keeper keeper.Keeper) (vals []tmtypes.GenesisValidator, err error) { keeper.IterateLastValidators(ctx, func(_ int64, validator types.ValidatorI) (stop bool) { diff --git a/x/staking/genesis_test.go b/x/staking/genesis_test.go index 24b794123a99..6f4a8ad8bda0 100644 --- a/x/staking/genesis_test.go +++ b/x/staking/genesis_test.go @@ -1,218 +1,17 @@ package staking_test import ( - "fmt" - "log" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank/testutil" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking/teststaking" "github.com/cosmos/cosmos-sdk/x/staking/types" ) -func bootstrapGenesisTest(t *testing.T, numAddrs int) (*simapp.SimApp, sdk.Context, []sdk.AccAddress) { - _, app, ctx := getBaseSimappWithCustomKeeper(t) - - addrDels, _ := generateAddresses(app, ctx, numAddrs, sdk.NewInt(10000)) - return app, ctx, addrDels -} - -func TestInitGenesis(t *testing.T) { - app, ctx, addrs := bootstrapGenesisTest(t, 10) - - valTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 1) - - params := app.StakingKeeper.GetParams(ctx) - validators := app.StakingKeeper.GetAllValidators(ctx) - require.Len(t, validators, 1) - var delegations []types.Delegation - - pk0, err := codectypes.NewAnyWithValue(PKs[0]) - require.NoError(t, err) - - pk1, err := codectypes.NewAnyWithValue(PKs[1]) - require.NoError(t, err) - - // initialize the validators - bondedVal1 := types.Validator{ - OperatorAddress: sdk.ValAddress(addrs[0]).String(), - ConsensusPubkey: pk0, - Status: types.Bonded, - Tokens: valTokens, - DelegatorShares: sdk.NewDecFromInt(valTokens), - Description: types.NewDescription("hoop", "", "", "", ""), - } - bondedVal2 := types.Validator{ - OperatorAddress: sdk.ValAddress(addrs[1]).String(), - ConsensusPubkey: pk1, - Status: types.Bonded, - Tokens: valTokens, - DelegatorShares: sdk.NewDecFromInt(valTokens), - Description: types.NewDescription("bloop", "", "", "", ""), - } - - // append new bonded validators to the list - validators = append(validators, bondedVal1, bondedVal2) - log.Printf("%#v", len(validators)) - // mint coins in the bonded pool representing the validators coins - i2 := len(validators) - 1 // -1 to exclude genesis validator - require.NoError(t, - testutil.FundModuleAccount( - app.BankKeeper, - ctx, - types.BondedPoolName, - sdk.NewCoins( - sdk.NewCoin(params.BondDenom, valTokens.MulRaw((int64)(i2))), - ), - ), - ) - genesisDelegations := app.StakingKeeper.GetAllDelegations(ctx) - delegations = append(delegations, genesisDelegations...) - - genesisState := types.NewGenesisState(params, validators, delegations) - vals := staking.InitGenesis(ctx, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, genesisState) - - actualGenesis := staking.ExportGenesis(ctx, app.StakingKeeper) - require.Equal(t, genesisState.Params, actualGenesis.Params) - require.Equal(t, genesisState.Delegations, actualGenesis.Delegations) - require.EqualValues(t, app.StakingKeeper.GetAllValidators(ctx), actualGenesis.Validators) - - // Ensure validators have addresses. - vals2, err := staking.WriteValidators(ctx, app.StakingKeeper) - require.NoError(t, err) - for _, val := range vals2 { - require.NotEmpty(t, val.Address) - } - - // now make sure the validators are bonded and intra-tx counters are correct - resVal, found := app.StakingKeeper.GetValidator(ctx, sdk.ValAddress(addrs[0])) - require.True(t, found) - require.Equal(t, types.Bonded, resVal.Status) - - resVal, found = app.StakingKeeper.GetValidator(ctx, sdk.ValAddress(addrs[1])) - require.True(t, found) - require.Equal(t, types.Bonded, resVal.Status) - - abcivals := make([]abci.ValidatorUpdate, len(vals)) - - validators = validators[1:] // remove genesis validator - for i, val := range validators { - abcivals[i] = val.ABCIValidatorUpdate(app.StakingKeeper.PowerReduction(ctx)) - } - - require.Equal(t, abcivals, vals) -} - -func TestInitGenesis_PoolsBalanceMismatch(t *testing.T) { - app := simapp.Setup(t, false) - ctx := app.NewContext(false, tmproto.Header{}) - - consPub, err := codectypes.NewAnyWithValue(PKs[0]) - require.NoError(t, err) - - // create mock validator - validator := types.Validator{ - OperatorAddress: sdk.ValAddress("12345678901234567890").String(), - ConsensusPubkey: consPub, - Jailed: false, - Tokens: sdk.NewInt(10), - DelegatorShares: sdk.NewDecFromInt(sdk.NewInt(10)), - Description: types.NewDescription("bloop", "", "", "", ""), - } - // valid params - params := types.Params{ - UnbondingTime: 10000, - MaxValidators: 1, - MaxEntries: 10, - BondDenom: "stake", - } - - // test - - require.Panics(t, func() { - // setting validator status to bonded so the balance counts towards bonded pool - validator.Status = types.Bonded - staking.InitGenesis(ctx, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, &types.GenesisState{ - Params: params, - Validators: []types.Validator{validator}, - }) - }, "should panic because bonded pool balance is different from bonded pool coins") - - require.Panics(t, func() { - // setting validator status to unbonded so the balance counts towards not bonded pool - validator.Status = types.Unbonded - staking.InitGenesis(ctx, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, &types.GenesisState{ - Params: params, - Validators: []types.Validator{validator}, - }) - }, "should panic because not bonded pool balance is different from not bonded pool coins") -} - -func TestInitGenesisLargeValidatorSet(t *testing.T) { - size := 200 - require.True(t, size > 100) - - app, ctx, addrs := bootstrapGenesisTest(t, 200) - genesisValidators := app.StakingKeeper.GetAllValidators(ctx) - - params := app.StakingKeeper.GetParams(ctx) - delegations := []types.Delegation{} - validators := make([]types.Validator, size) - var err error - - bondedPoolAmt := sdk.ZeroInt() - for i := range validators { - validators[i], err = types.NewValidator(sdk.ValAddress(addrs[i]), - PKs[i], types.NewDescription(fmt.Sprintf("#%d", i), "", "", "", "")) - require.NoError(t, err) - validators[i].Status = types.Bonded - - tokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 1) - if i < 100 { - tokens = app.StakingKeeper.TokensFromConsensusPower(ctx, 2) - } - validators[i].Tokens = tokens - validators[i].DelegatorShares = sdk.NewDecFromInt(tokens) - // add bonded coins - bondedPoolAmt = bondedPoolAmt.Add(tokens) - } - - validators = append(validators, genesisValidators...) - - genesisState := types.NewGenesisState(params, validators, delegations) - - // mint coins in the bonded pool representing the validators coins - require.NoError(t, - testutil.FundModuleAccount( - app.BankKeeper, - ctx, - types.BondedPoolName, - sdk.NewCoins(sdk.NewCoin(params.BondDenom, bondedPoolAmt)), - ), - ) - - vals := staking.InitGenesis(ctx, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, genesisState) - - abcivals := make([]abci.ValidatorUpdate, 100) - for i, val := range validators[:100] { - abcivals[i] = val.ABCIValidatorUpdate(app.StakingKeeper.PowerReduction(ctx)) - } - - // remove genesis validator - vals = vals[:100] - require.Equal(t, abcivals, vals) -} - func TestValidateGenesis(t *testing.T) { genValidators1 := make([]types.Validator, 1, 5) pk := ed25519.GenPrivKey().PubKey() @@ -244,9 +43,11 @@ func TestValidateGenesis(t *testing.T) { for _, tt := range tests { tt := tt + t.Run(tt.name, func(t *testing.T) { genesisState := types.DefaultGenesisState() tt.mutate(genesisState) + if tt.wantErr { assert.Error(t, staking.ValidateGenesis(genesisState)) } else { diff --git a/x/staking/keeper/genesis.go b/x/staking/keeper/genesis.go new file mode 100644 index 000000000000..86eae3ada04b --- /dev/null +++ b/x/staking/keeper/genesis.go @@ -0,0 +1,204 @@ +package keeper + +import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// InitGenesis sets the pool and parameters for the provided keeper. For each +// validator in data, it sets that validator in the keeper along with manually +// setting the indexes. In addition, it also sets any delegations found in +// data. Finally, it updates the bonded validators. +// Returns final validator set after applying all declaration and delegations +func (k Keeper) InitGenesis(ctx sdk.Context, data *types.GenesisState) (res []abci.ValidatorUpdate) { + bondedTokens := sdk.ZeroInt() + notBondedTokens := sdk.ZeroInt() + + // We need to pretend to be "n blocks before genesis", where "n" is the + // validator update delay, so that e.g. slashing periods are correctly + // initialized for the validator set e.g. with a one-block offset - the + // first TM block is at height 1, so state updates applied from + // genesis.json are in block 0. + ctx = ctx.WithBlockHeight(1 - sdk.ValidatorUpdateDelay) + + k.SetParams(ctx, data.Params) + k.SetLastTotalPower(ctx, data.LastTotalPower) + + for _, validator := range data.Validators { + k.SetValidator(ctx, validator) + + // Manually set indices for the first time + k.SetValidatorByConsAddr(ctx, validator) + k.SetValidatorByPowerIndex(ctx, validator) + + // Call the creation hook if not exported + if !data.Exported { + if err := k.AfterValidatorCreated(ctx, validator.GetOperator()); err != nil { + panic(err) + } + } + + // update timeslice if necessary + if validator.IsUnbonding() { + k.InsertUnbondingValidatorQueue(ctx, validator) + } + + switch validator.GetStatus() { + case types.Bonded: + bondedTokens = bondedTokens.Add(validator.GetTokens()) + + case types.Unbonding, types.Unbonded: + notBondedTokens = notBondedTokens.Add(validator.GetTokens()) + + default: + panic("invalid validator status") + } + } + + for _, delegation := range data.Delegations { + delegatorAddress, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) + if err != nil { + panic(err) + } + + // Call the before-creation hook if not exported + if !data.Exported { + if err := k.BeforeDelegationCreated(ctx, delegatorAddress, delegation.GetValidatorAddr()); err != nil { + panic(err) + } + } + + k.SetDelegation(ctx, delegation) + + // Call the after-modification hook if not exported + if !data.Exported { + if err := k.AfterDelegationModified(ctx, delegatorAddress, delegation.GetValidatorAddr()); err != nil { + panic(err) + } + } + } + + for _, ubd := range data.UnbondingDelegations { + k.SetUnbondingDelegation(ctx, ubd) + + for _, entry := range ubd.Entries { + k.InsertUBDQueue(ctx, ubd, entry.CompletionTime) + notBondedTokens = notBondedTokens.Add(entry.Balance) + } + } + + for _, red := range data.Redelegations { + k.SetRedelegation(ctx, red) + + for _, entry := range red.Entries { + k.InsertRedelegationQueue(ctx, red, entry.CompletionTime) + } + } + + bondedCoins := sdk.NewCoins(sdk.NewCoin(data.Params.BondDenom, bondedTokens)) + notBondedCoins := sdk.NewCoins(sdk.NewCoin(data.Params.BondDenom, notBondedTokens)) + + // check if the unbonded and bonded pools accounts exists + bondedPool := k.GetBondedPool(ctx) + if bondedPool == nil { + panic(fmt.Sprintf("%s module account has not been set", types.BondedPoolName)) + } + + // TODO: remove with genesis 2-phases refactor https://github.com/cosmos/cosmos-sdk/issues/2862 + bondedBalance := k.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + if bondedBalance.IsZero() { + k.authKeeper.SetModuleAccount(ctx, bondedPool) + } + + // if balance is different from bonded coins panic because genesis is most likely malformed + if !bondedBalance.IsEqual(bondedCoins) { + panic(fmt.Sprintf("bonded pool balance is different from bonded coins: %s <-> %s", bondedBalance, bondedCoins)) + } + + notBondedPool := k.GetNotBondedPool(ctx) + if notBondedPool == nil { + panic(fmt.Sprintf("%s module account has not been set", types.NotBondedPoolName)) + } + + notBondedBalance := k.bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) + if notBondedBalance.IsZero() { + k.authKeeper.SetModuleAccount(ctx, notBondedPool) + } + + // If balance is different from non bonded coins panic because genesis is most + // likely malformed. + if !notBondedBalance.IsEqual(notBondedCoins) { + panic(fmt.Sprintf("not bonded pool balance is different from not bonded coins: %s <-> %s", notBondedBalance, notBondedCoins)) + } + + // don't need to run Tendermint updates if we exported + if data.Exported { + for _, lv := range data.LastValidatorPowers { + valAddr, err := sdk.ValAddressFromBech32(lv.Address) + if err != nil { + panic(err) + } + + k.SetLastValidatorPower(ctx, valAddr, lv.Power) + validator, found := k.GetValidator(ctx, valAddr) + + if !found { + panic(fmt.Sprintf("validator %s not found", lv.Address)) + } + + update := validator.ABCIValidatorUpdate(k.PowerReduction(ctx)) + update.Power = lv.Power // keep the next-val-set offset, use the last power for the first block + res = append(res, update) + } + } else { + var err error + + res, err = k.ApplyAndReturnValidatorSetUpdates(ctx) + if err != nil { + panic(err) + } + } + + return res +} + +// ExportGenesis returns a GenesisState for a given context and keeper. The +// GenesisState will contain the pool, params, validators, and bonds found in +// the keeper. +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + var unbondingDelegations []types.UnbondingDelegation + + k.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) (stop bool) { + unbondingDelegations = append(unbondingDelegations, ubd) + return false + }) + + var redelegations []types.Redelegation + + k.IterateRedelegations(ctx, func(_ int64, red types.Redelegation) (stop bool) { + redelegations = append(redelegations, red) + return false + }) + + var lastValidatorPowers []types.LastValidatorPower + + k.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { + lastValidatorPowers = append(lastValidatorPowers, types.LastValidatorPower{Address: addr.String(), Power: power}) + return false + }) + + return &types.GenesisState{ + Params: k.GetParams(ctx), + LastTotalPower: k.GetLastTotalPower(ctx), + LastValidatorPowers: lastValidatorPowers, + Validators: k.GetAllValidators(ctx), + Delegations: k.GetAllDelegations(ctx), + UnbondingDelegations: unbondingDelegations, + Redelegations: redelegations, + Exported: true, + } +} diff --git a/x/staking/keeper/genesis_test.go b/x/staking/keeper/genesis_test.go new file mode 100644 index 000000000000..908e301decfa --- /dev/null +++ b/x/staking/keeper/genesis_test.go @@ -0,0 +1,219 @@ +package keeper_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +func bootstrapGenesisTest(t *testing.T, numAddrs int) (*simapp.SimApp, sdk.Context, []sdk.AccAddress) { + app := simapp.Setup(t, false) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + addrDels, _ := generateAddresses(app, ctx, numAddrs) + return app, ctx, addrDels +} + +func TestInitGenesis(t *testing.T) { + app, ctx, addrs := bootstrapGenesisTest(t, 10) + + valTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 1) + + params := app.StakingKeeper.GetParams(ctx) + validators := app.StakingKeeper.GetAllValidators(ctx) + require.Len(t, validators, 1) + var delegations []types.Delegation + + pk0, err := codectypes.NewAnyWithValue(PKs[0]) + require.NoError(t, err) + + pk1, err := codectypes.NewAnyWithValue(PKs[1]) + require.NoError(t, err) + + // initialize the validators + bondedVal1 := types.Validator{ + OperatorAddress: sdk.ValAddress(addrs[0]).String(), + ConsensusPubkey: pk0, + Status: types.Bonded, + Tokens: valTokens, + DelegatorShares: sdk.NewDecFromInt(valTokens), + Description: types.NewDescription("hoop", "", "", "", ""), + } + bondedVal2 := types.Validator{ + OperatorAddress: sdk.ValAddress(addrs[1]).String(), + ConsensusPubkey: pk1, + Status: types.Bonded, + Tokens: valTokens, + DelegatorShares: sdk.NewDecFromInt(valTokens), + Description: types.NewDescription("bloop", "", "", "", ""), + } + + // append new bonded validators to the list + validators = append(validators, bondedVal1, bondedVal2) + + // mint coins in the bonded pool representing the validators coins + i2 := len(validators) - 1 // -1 to exclude genesis validator + require.NoError(t, + testutil.FundModuleAccount( + app.BankKeeper, + ctx, + types.BondedPoolName, + sdk.NewCoins( + sdk.NewCoin(params.BondDenom, valTokens.MulRaw((int64)(i2))), + ), + ), + ) + + genesisDelegations := app.StakingKeeper.GetAllDelegations(ctx) + delegations = append(delegations, genesisDelegations...) + + genesisState := types.NewGenesisState(params, validators, delegations) + vals := app.StakingKeeper.InitGenesis(ctx, genesisState) + + actualGenesis := app.StakingKeeper.ExportGenesis(ctx) + require.Equal(t, genesisState.Params, actualGenesis.Params) + require.Equal(t, genesisState.Delegations, actualGenesis.Delegations) + require.EqualValues(t, app.StakingKeeper.GetAllValidators(ctx), actualGenesis.Validators) + + // Ensure validators have addresses. + vals2, err := staking.WriteValidators(ctx, app.StakingKeeper) + require.NoError(t, err) + + for _, val := range vals2 { + require.NotEmpty(t, val.Address) + } + + // now make sure the validators are bonded and intra-tx counters are correct + resVal, found := app.StakingKeeper.GetValidator(ctx, sdk.ValAddress(addrs[0])) + require.True(t, found) + require.Equal(t, types.Bonded, resVal.Status) + + resVal, found = app.StakingKeeper.GetValidator(ctx, sdk.ValAddress(addrs[1])) + require.True(t, found) + require.Equal(t, types.Bonded, resVal.Status) + + abcivals := make([]abci.ValidatorUpdate, len(vals)) + + validators = validators[1:] // remove genesis validator + for i, val := range validators { + abcivals[i] = val.ABCIValidatorUpdate(app.StakingKeeper.PowerReduction(ctx)) + } + + require.Equal(t, abcivals, vals) +} + +func TestInitGenesis_PoolsBalanceMismatch(t *testing.T) { + app := simapp.Setup(t, false) + ctx := app.NewContext(false, tmproto.Header{}) + + consPub, err := codectypes.NewAnyWithValue(PKs[0]) + require.NoError(t, err) + + validator := types.Validator{ + OperatorAddress: sdk.ValAddress("12345678901234567890").String(), + ConsensusPubkey: consPub, + Jailed: false, + Tokens: sdk.NewInt(10), + DelegatorShares: sdk.NewDecFromInt(sdk.NewInt(10)), + Description: types.NewDescription("bloop", "", "", "", ""), + } + + params := types.Params{ + UnbondingTime: 10000, + MaxValidators: 1, + MaxEntries: 10, + BondDenom: "stake", + } + + require.Panics(t, func() { + // setting validator status to bonded so the balance counts towards bonded pool + validator.Status = types.Bonded + app.StakingKeeper.InitGenesis(ctx, &types.GenesisState{ + Params: params, + Validators: []types.Validator{validator}, + }) + }, + "should panic because bonded pool balance is different from bonded pool coins", + ) + + require.Panics(t, func() { + // setting validator status to unbonded so the balance counts towards not bonded pool + validator.Status = types.Unbonded + app.StakingKeeper.InitGenesis(ctx, &types.GenesisState{ + Params: params, + Validators: []types.Validator{validator}, + }) + }, + "should panic because not bonded pool balance is different from not bonded pool coins", + ) +} + +func TestInitGenesisLargeValidatorSet(t *testing.T) { + size := 200 + require.True(t, size > 100) + + app, ctx, addrs := bootstrapGenesisTest(t, 200) + genesisValidators := app.StakingKeeper.GetAllValidators(ctx) + + params := app.StakingKeeper.GetParams(ctx) + delegations := []types.Delegation{} + validators := make([]types.Validator, size) + + var err error + + bondedPoolAmt := sdk.ZeroInt() + for i := range validators { + validators[i], err = types.NewValidator( + sdk.ValAddress(addrs[i]), + PKs[i], + types.NewDescription(fmt.Sprintf("#%d", i), "", "", "", ""), + ) + require.NoError(t, err) + validators[i].Status = types.Bonded + + tokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 1) + if i < 100 { + tokens = app.StakingKeeper.TokensFromConsensusPower(ctx, 2) + } + + validators[i].Tokens = tokens + validators[i].DelegatorShares = sdk.NewDecFromInt(tokens) + + // add bonded coins + bondedPoolAmt = bondedPoolAmt.Add(tokens) + } + + validators = append(validators, genesisValidators...) + genesisState := types.NewGenesisState(params, validators, delegations) + + // mint coins in the bonded pool representing the validators coins + require.NoError(t, + testutil.FundModuleAccount( + app.BankKeeper, + ctx, + types.BondedPoolName, + sdk.NewCoins(sdk.NewCoin(params.BondDenom, bondedPoolAmt)), + ), + ) + + vals := app.StakingKeeper.InitGenesis(ctx, genesisState) + + abcivals := make([]abci.ValidatorUpdate, 100) + for i, val := range validators[:100] { + abcivals[i] = val.ABCIValidatorUpdate(app.StakingKeeper.PowerReduction(ctx)) + } + + // remove genesis validator + vals = vals[:100] + require.Equal(t, abcivals, vals) +} diff --git a/x/staking/module.go b/x/staking/module.go index 92aab0885d44..c1051be3c99d 100644 --- a/x/staking/module.go +++ b/x/staking/module.go @@ -149,14 +149,13 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json. cdc.MustUnmarshalJSON(data, &genesisState) - return InitGenesis(ctx, am.keeper, am.accountKeeper, am.bankKeeper, &genesisState) + return am.keeper.InitGenesis(ctx, &genesisState) } // ExportGenesis returns the exported genesis state as raw bytes for the staking // module. func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - gs := ExportGenesis(ctx, am.keeper) - return cdc.MustMarshalJSON(gs) + return cdc.MustMarshalJSON(am.keeper.ExportGenesis(ctx)) } // ConsensusVersion implements AppModule/ConsensusVersion.