refactor: move x/staking Init/Export genesis to keeper (#11866)
This commit is contained in:
parent
35d3312c3b
commit
35c81467cb
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
204
x/staking/keeper/genesis.go
Normal file
204
x/staking/keeper/genesis.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
||||
219
x/staking/keeper/genesis_test.go
Normal file
219
x/staking/keeper/genesis_test.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user