diff --git a/PENDING.md b/PENDING.md index 75805fd111..6ef5c3717a 100644 --- a/PENDING.md +++ b/PENDING.md @@ -69,6 +69,7 @@ BREAKING CHANGES * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index * [x/staking] \#2236 more distribution hooks for distribution * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock + * [x/stake] \#2412 Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding * Tendermint * Update tendermint version from v0.23.0 to v0.25.0, notable changes diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 31b912a9de..40b55493ee 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -88,12 +88,41 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio // add handlers app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) - app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) - app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace)) - app.stakeKeeper = app.stakeKeeper.WithHooks(app.slashingKeeper.Hooks()) - app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.bankKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace)) - app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) + + app.paramsKeeper = params.NewKeeper( + app.cdc, + app.keyParams, app.tkeyParams, + ) + + app.stakeKeeper = stake.NewKeeper( + app.cdc, + app.keyStake, app.tkeyStake, + app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), + app.RegisterCodespace(stake.DefaultCodespace), + ) + + app.slashingKeeper = slashing.NewKeeper( + app.cdc, + app.keySlashing, + app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), + app.RegisterCodespace(slashing.DefaultCodespace), + ) + + app.stakeKeeper = app.stakeKeeper.WithHooks( + app.slashingKeeper.Hooks(), + ) + + app.govKeeper = gov.NewKeeper( + app.cdc, + app.keyGov, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, app.stakeKeeper, + app.RegisterCodespace(gov.DefaultCodespace), + ) + + app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( + app.cdc, + app.keyFeeCollection, + ) // register message routes app.Router(). @@ -184,7 +213,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci } // load the address to pubkey map - slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.StakeData) + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) err = GaiaValidateGenesisState(genesisState) diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index bcd16a1761..d16cba40ea 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/db" @@ -21,8 +22,9 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { } genesisState := GenesisState{ - Accounts: genaccs, - StakeData: stake.DefaultGenesisState(), + Accounts: genaccs, + StakeData: stake.DefaultGenesisState(), + SlashingData: slashing.DefaultGenesisState(), } stateBytes, err := codec.MarshalJSONIndent(gapp.cdc, genesisState) diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 3abf5d94b8..f4a5e2b41a 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -12,6 +12,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/spf13/pflag" @@ -31,9 +32,10 @@ var ( // State to Unmarshal type GenesisState struct { - Accounts []GenesisAccount `json:"accounts"` - StakeData stake.GenesisState `json:"stake"` - GovData gov.GenesisState `json:"gov"` + Accounts []GenesisAccount `json:"accounts"` + StakeData stake.GenesisState `json:"stake"` + GovData gov.GenesisState `json:"gov"` + SlashingData slashing.GenesisState `json:"slashing"` } // GenesisAccount doesn't need pubkey or sequence @@ -168,6 +170,8 @@ func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisStat // start with the default staking genesis state stakeData := stake.DefaultGenesisState() + slashingData := slashing.DefaultGenesisState() + // get genesis flag account information genaccs := make([]GenesisAccount, len(appGenTxs)) for i, appGenTx := range appGenTxs { @@ -190,9 +194,10 @@ func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisStat // create the final app state genesisState = GenesisState{ - Accounts: genaccs, - StakeData: stakeData, - GovData: gov.DefaultGenesisState(), + Accounts: genaccs, + StakeData: stakeData, + GovData: gov.DefaultGenesisState(), + SlashingData: slashingData, } return diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 78c35eb609..2521e07883 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation" + "github.com/cosmos/cosmos-sdk/x/slashing" slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" stake "github.com/cosmos/cosmos-sdk/x/stake" stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" @@ -52,9 +53,11 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { Coins: coins, }) } - govGenesis := gov.DefaultGenesisState() + // Default genesis state + govGenesis := gov.DefaultGenesisState() stakeGenesis := stake.DefaultGenesisState() + slashingGenesis := slashing.DefaultGenesisState() var validators []stake.Validator var delegations []stake.Delegation // XXX Try different numbers of initially bonded validators @@ -74,9 +77,10 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { stakeGenesis.Params.InflationMax = sdk.NewDec(0) stakeGenesis.Params.InflationMin = sdk.NewDec(0) genesis := GenesisState{ - Accounts: genesisAccounts, - StakeData: stakeGenesis, - GovData: govGenesis, + Accounts: genesisAccounts, + StakeData: stakeGenesis, + SlashingData: slashingGenesis, + GovData: govGenesis, } // Marshal genesis diff --git a/cmd/gaia/app/test_utils.go b/cmd/gaia/app/test_utils.go index d793e5bfc9..32e4c70a59 100644 --- a/cmd/gaia/app/test_utils.go +++ b/cmd/gaia/app/test_utils.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" tmtypes "github.com/tendermint/tendermint/types" ) @@ -69,8 +70,9 @@ func NewTestGaiaAppGenState( } return GenesisState{ - Accounts: genAccs, - StakeData: stakeData, - GovData: gov.DefaultGenesisState(), + Accounts: genAccs, + StakeData: stakeData, + SlashingData: slashing.DefaultGenesisState(), + GovData: gov.DefaultGenesisState(), }, nil } diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index 182e789bea..db22da0b52 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -135,6 +135,7 @@ type GaiaApp struct { tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey keyParams *sdk.KVStoreKey + tkeyParams *sdk.TransientStoreKey // Manage getting and setting accounts accountMapper auth.AccountMapper @@ -161,6 +162,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp tkeyStake: sdk.NewTransientStoreKey("transient_stake"), keySlashing: sdk.NewKVStoreKey("slashing"), keyParams: sdk.NewKVStoreKey("params"), + tkeyParams: sdk.NewTransientStoreKey("transient_params"), } // define the accountMapper @@ -172,9 +174,9 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp // add handlers app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) - app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) - app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace)) + app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) + app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), app.RegisterCodespace(stake.DefaultCodespace)) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), app.RegisterCodespace(slashing.DefaultCodespace)) // register message routes app.Router(). @@ -186,7 +188,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keySlashing) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keySlashing, app.keyParams) + app.MountStore(app.tkeyParams, sdk.StoreTypeTransient) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) @@ -252,6 +255,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "") } + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) + return abci.ResponseInitChain{ Validators: validators, } diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index 2502baf0dc..6439ca0c71 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -1,5 +1,20 @@ # End-Block +## Unbonding Validator Queue + +For all unbonding validators that have finished their unbonding period, this switches their validator.Status +from sdk.Unbonding to sdk.Unbonded + +```golang +validatorQueue(currTime time.Time): + // unbonding validators are in ordered queue from oldest to newest + for all unbondingValidators whose CompleteTime < currTime: + validator = GetValidator(unbondingValidator.ValidatorAddr) + validator.Status = sdk.Bonded + SetValidator(unbondingValidator) + return +``` + ## Validator Set Changes The Tendermint validator set may be updated by state transitions that run at diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go index eb3ae7dd4f..ba77bae1f8 100644 --- a/store/gaskvstore_test.go +++ b/store/gaskvstore_test.go @@ -13,13 +13,13 @@ import ( func newGasKVStore() KVStore { meter := sdk.NewGasMeter(1000) mem := dbStoreAdapter{dbm.NewMemDB()} - return NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) + return NewGasKVStore(meter, sdk.KVGasConfig(), mem) } func TestGasKVStoreBasic(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(1000) - st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") st.Set(keyFmt(1), valFmt(1)) require.Equal(t, valFmt(1), st.Get(keyFmt(1))) @@ -31,7 +31,7 @@ func TestGasKVStoreBasic(t *testing.T) { func TestGasKVStoreIterator(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(1000) - st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty") st.Set(keyFmt(1), valFmt(1)) @@ -55,14 +55,14 @@ func TestGasKVStoreIterator(t *testing.T) { func TestGasKVStoreOutOfGasSet(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(0) - st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas") } func TestGasKVStoreOutOfGasIterator(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(200) - st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) st.Set(keyFmt(1), valFmt(1)) iterator := st.Iterator(nil, nil) iterator.Next() diff --git a/store/prefixstore.go b/store/prefixstore.go index 55af1084e3..3abea46539 100644 --- a/store/prefixstore.go +++ b/store/prefixstore.go @@ -26,6 +26,9 @@ func cloneAppend(bz []byte, tail []byte) (res []byte) { } func (s prefixStore) key(key []byte) (res []byte) { + if key == nil { + panic("nil key on prefixStore") + } res = cloneAppend(s.prefix, key) return } diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go index 499f30d500..8445991b6e 100644 --- a/store/prefixstore_test.go +++ b/store/prefixstore_test.go @@ -17,7 +17,7 @@ type kvpair struct { value []byte } -func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { +func genRandomKVPairs(t *testing.T) []kvpair { kvps := make([]kvpair, 20) for i := 0; i < 20; i++ { @@ -25,17 +25,26 @@ func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { rand.Read(kvps[i].key) kvps[i].value = make([]byte, 32) rand.Read(kvps[i].value) - - store.Set(kvps[i].key, kvps[i].value) } return kvps } +func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { + kvps := genRandomKVPairs(t) + for _, kvp := range kvps { + store.Set(kvp.key, kvp.value) + } + return kvps +} + func testPrefixStore(t *testing.T, baseStore KVStore, prefix []byte) { prefixStore := baseStore.Prefix(prefix) prefixPrefixStore := prefixStore.Prefix([]byte("prefix")) + require.Panics(t, func() { prefixStore.Get(nil) }) + require.Panics(t, func() { prefixStore.Set(nil, []byte{}) }) + kvps := setRandomKVPairs(t, prefixPrefixStore) for i := 0; i < 20; i++ { @@ -81,7 +90,7 @@ func TestCacheKVStorePrefix(t *testing.T) { func TestGasKVStorePrefix(t *testing.T) { meter := sdk.NewGasMeter(100000000) mem := dbStoreAdapter{dbm.NewMemDB()} - gasStore := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) + gasStore := NewGasKVStore(meter, sdk.KVGasConfig(), mem) testPrefixStore(t, gasStore, []byte("test")) } @@ -109,6 +118,33 @@ func TestPrefixStoreIterate(t *testing.T) { pIter.Close() } +func incFirstByte(bz []byte) { + if bz[0] == byte(255) { + bz[0] = byte(0) + return + } + bz[0]++ +} + +func TestCloneAppend(t *testing.T) { + kvps := genRandomKVPairs(t) + for _, kvp := range kvps { + bz := cloneAppend(kvp.key, kvp.value) + require.Equal(t, bz, append(kvp.key, kvp.value...)) + + incFirstByte(bz) + require.NotEqual(t, bz, append(kvp.key, kvp.value...)) + + bz = cloneAppend(kvp.key, kvp.value) + incFirstByte(kvp.key) + require.NotEqual(t, bz, append(kvp.key, kvp.value...)) + + bz = cloneAppend(kvp.key, kvp.value) + incFirstByte(kvp.value) + require.NotEqual(t, bz, append(kvp.key, kvp.value...)) + } +} + func TestPrefixStoreIteratorEdgeCase(t *testing.T) { db := dbm.NewMemDB() baseStore := dbStoreAdapter{db} diff --git a/types/context.go b/types/context.go index cb5958c5c9..a3150087b1 100644 --- a/types/context.go +++ b/types/context.go @@ -32,7 +32,6 @@ type Context struct { } // create a new context -// nolint: unparam func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Logger) Context { c := Context{ Context: context.Background(), @@ -74,7 +73,7 @@ func (c Context) Value(key interface{}) interface{} { // KVStore fetches a KVStore from the MultiStore. func (c Context) KVStore(key StoreKey) KVStore { - return c.multiStore().GetKVStore(key).Gas(c.GasMeter(), cachedDefaultGasConfig) + return c.multiStore().GetKVStore(key).Gas(c.GasMeter(), cachedKVGasConfig) } // TransientStore fetches a TransientStore from the MultiStore. @@ -189,7 +188,9 @@ func (c Context) WithBlockTime(newTime time.Time) Context { } func (c Context) WithBlockHeight(height int64) Context { - return c.withValue(contextKeyBlockHeight, height) + newHeader := c.BlockHeader() + newHeader.Height = height + return c.withValue(contextKeyBlockHeight, height).withValue(contextKeyBlockHeader, newHeader) } func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { diff --git a/types/context_test.go b/types/context_test.go index 0026912298..0ab6c8dfc7 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -164,15 +164,16 @@ func TestContextWithCustom(t *testing.T) { meter := types.NewGasMeter(10000) minFees := types.Coins{types.NewInt64Coin("feeCoin", 1)} - ctx = types.NewContext(nil, header, ischeck, logger). + ctx = types.NewContext(nil, header, ischeck, logger) + require.Equal(t, header, ctx.BlockHeader()) + + ctx = ctx. WithBlockHeight(height). WithChainID(chainid). WithTxBytes(txbytes). WithVoteInfos(voteinfos). WithGasMeter(meter). WithMinimumFees(minFees) - - require.Equal(t, header, ctx.BlockHeader()) require.Equal(t, height, ctx.BlockHeight()) require.Equal(t, chainid, ctx.ChainID()) require.Equal(t, ischeck, ctx.IsCheckTx()) diff --git a/types/gas.go b/types/gas.go index cce4975c30..7b03474670 100644 --- a/types/gas.go +++ b/types/gas.go @@ -13,7 +13,7 @@ const ( ) var ( - cachedDefaultGasConfig = DefaultGasConfig() + cachedKVGasConfig = KVGasConfig() cachedTransientGasConfig = TransientGasConfig() ) @@ -86,8 +86,8 @@ type GasConfig struct { IterNextCostFlat Gas } -// DefaultGasConfig returns a default gas config for KVStores. -func DefaultGasConfig() GasConfig { +// KVGasConfig returns a default gas config for KVStores. +func KVGasConfig() GasConfig { return GasConfig{ HasCost: 10, DeleteCost: 10, @@ -103,5 +103,5 @@ func DefaultGasConfig() GasConfig { // TransientGasConfig returns a default gas config for TransientStores. func TransientGasConfig() GasConfig { // TODO: define gasconfig for transient stores - return DefaultGasConfig() + return KVGasConfig() } diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 80e5a205cd..4b7ec26b5c 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -7,17 +7,34 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" ) -// nolint +// Parameter store default namestore const ( - ParamStoreKeyDepositProcedure = "gov/depositprocedure" - ParamStoreKeyVotingProcedure = "gov/votingprocedure" - ParamStoreKeyTallyingProcedure = "gov/tallyingprocedure" + DefaultParamspace = "gov" ) +// Parameter store key +var ( + ParamStoreKeyDepositProcedure = []byte("depositprocedure") + ParamStoreKeyVotingProcedure = []byte("votingprocedure") + ParamStoreKeyTallyingProcedure = []byte("tallyingprocedure") +) + +// Type declaration for parameters +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyDepositProcedure, DepositProcedure{}, + ParamStoreKeyVotingProcedure, VotingProcedure{}, + ParamStoreKeyTallyingProcedure, TallyingProcedure{}, + ) +} + // Governance Keeper type Keeper struct { - // The reference to the ParamSetter to get and set Global Params - ps params.Setter + // The reference to the Param Keeper to get and set Global Params + paramsKeeper params.Keeper + + // The reference to the Paramstore to get and set gov specific params + paramSpace params.Subspace // The reference to the CoinKeeper to modify balances ck bank.Keeper @@ -43,15 +60,16 @@ type Keeper struct { // - depositing funds into proposals, and activating upon sufficient funds being deposited // - users voting on proposals, with weight proportional to stake in the system // - and tallying the result of the vote. -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ps params.Setter, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, paramSpace params.Subspace, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { return Keeper{ - storeKey: key, - ps: ps, - ck: ck, - ds: ds, - vs: ds.GetValidatorSet(), - cdc: cdc, - codespace: codespace, + storeKey: key, + paramsKeeper: paramsKeeper, + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + ck: ck, + ds: ds, + vs: ds.GetValidatorSet(), + cdc: cdc, + codespace: codespace, } } @@ -210,7 +228,7 @@ func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { // nolint: errcheck func (keeper Keeper) GetDepositProcedure(ctx sdk.Context) DepositProcedure { var depositProcedure DepositProcedure - keeper.ps.Get(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) + keeper.paramSpace.Get(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) return depositProcedure } @@ -218,7 +236,7 @@ func (keeper Keeper) GetDepositProcedure(ctx sdk.Context) DepositProcedure { // nolint: errcheck func (keeper Keeper) GetVotingProcedure(ctx sdk.Context) VotingProcedure { var votingProcedure VotingProcedure - keeper.ps.Get(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) + keeper.paramSpace.Get(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) return votingProcedure } @@ -226,23 +244,23 @@ func (keeper Keeper) GetVotingProcedure(ctx sdk.Context) VotingProcedure { // nolint: errcheck func (keeper Keeper) GetTallyingProcedure(ctx sdk.Context) TallyingProcedure { var tallyingProcedure TallyingProcedure - keeper.ps.Get(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) + keeper.paramSpace.Get(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) return tallyingProcedure } // nolint: errcheck func (keeper Keeper) setDepositProcedure(ctx sdk.Context, depositProcedure DepositProcedure) { - keeper.ps.Set(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) + keeper.paramSpace.Set(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) } // nolint: errcheck func (keeper Keeper) setVotingProcedure(ctx sdk.Context, votingProcedure VotingProcedure) { - keeper.ps.Set(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) + keeper.paramSpace.Set(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) } // nolint: errcheck func (keeper Keeper) setTallyingProcedure(ctx sdk.Context, tallyingProcedure TallyingProcedure) { - keeper.ps.Set(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) + keeper.paramSpace.Set(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) } // ===================================================== diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index b0317d1161..a7d4e40d05 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -23,21 +23,23 @@ func TestGovWithRandomMessages(t *testing.T) { bank.RegisterCodec(mapp.Cdc) gov.RegisterCodec(mapp.Cdc) mapper := mapp.AccountMapper + bankKeeper := bank.NewBaseKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") stakeTKey := sdk.NewTransientStoreKey("transient_stake") - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, stake.DefaultCodespace) paramKey := sdk.NewKVStoreKey("params") - paramKeeper := params.NewKeeper(mapp.Cdc, paramKey) + paramTKey := sdk.NewTransientStoreKey("transient_params") + paramKeeper := params.NewKeeper(mapp.Cdc, paramKey, paramTKey) + stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, paramKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) govKey := sdk.NewKVStoreKey("gov") - govKeeper := gov.NewKeeper(mapp.Cdc, govKey, paramKeeper.Setter(), bankKeeper, stakeKeeper, gov.DefaultCodespace) + govKeeper := gov.NewKeeper(mapp.Cdc, govKey, paramKeeper, paramKeeper.Subspace(gov.DefaultParamspace), bankKeeper, stakeKeeper, gov.DefaultCodespace) mapp.Router().AddRoute("gov", gov.NewHandler(govKeeper)) mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { gov.EndBlocker(ctx, govKeeper) return abci.ResponseEndBlock{} }) - err := mapp.CompleteSetup(stakeKey, stakeTKey, paramKey, govKey) + err := mapp.CompleteSetup(stakeKey, stakeTKey, paramKey, paramTKey, govKey) if err != nil { panic(err) } diff --git a/x/gov/test_common.go b/x/gov/test_common.go index fdede91eb4..b33f580841 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -27,20 +27,22 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, RegisterCodec(mapp.Cdc) keyGlobalParams := sdk.NewKVStoreKey("params") + tkeyGlobalParams := sdk.NewTransientStoreKey("transient_params") keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") keyGov := sdk.NewKVStoreKey("gov") - pk := params.NewKeeper(mapp.Cdc, keyGlobalParams) + pk := params.NewKeeper(mapp.Cdc, keyGlobalParams, tkeyGlobalParams) ck := bank.NewBaseKeeper(mapp.AccountMapper) - sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, mapp.RegisterCodespace(stake.DefaultCodespace)) - keeper := NewKeeper(mapp.Cdc, keyGov, pk.Setter(), ck, sk, DefaultCodespace) + sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, pk.Subspace(stake.DefaultParamspace), mapp.RegisterCodespace(stake.DefaultCodespace)) + keeper := NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace) + mapp.Router().AddRoute("gov", NewHandler(keeper)) mapp.SetEndBlocker(getEndBlocker(keeper)) mapp.SetInitChainer(getInitChainer(mapp, keeper, sk)) - require.NoError(t, mapp.CompleteSetup(keyStake, keyGov, keyGlobalParams, tkeyStake)) + require.NoError(t, mapp.CompleteSetup(keyStake, tkeyStake, keyGov, keyGlobalParams, tkeyGlobalParams)) genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewInt64Coin("steak", 42)}) diff --git a/x/params/doc.go b/x/params/doc.go new file mode 100644 index 0000000000..06d6620b20 --- /dev/null +++ b/x/params/doc.go @@ -0,0 +1,85 @@ +package params + +/* +Package params provides a globally available parameter store. + +There are two main types, Keeper and Space. Space is an isolated namespace for a +paramstore, where keys are prefixed by preconfigured spacename. Keeper has a +permission to access all existing spaces and create new space. + +Space can be used by the individual keepers, who needs a private parameter store +that the other keeper are not able to modify. Keeper can be used by the Governance +keeper, who need to modify any parameter in case of the proposal passes. + +Basic Usage: + +First, declare parameter space and parameter keys for the module. Then include +params.Store in the keeper. Since we prefix the keys with the spacename, it is +recommended to use the same name with the module's. + + const ( + DefaultParamspace = "mymodule" + ) + + const ( + KeyParameter1 = "myparameter1" + KeyParameter2 = "myparameter2" + ) + + type Keeper struct { + cdc *wire.Codec + key sdk.StoreKey + + ps params.Subspace + } + +Pass a params.Store to NewKeeper with DefaultParamSpace (or another) + + app.myKeeper = mymodule.NewKeeper(app.paramStore.SubStore(mymodule.DefaultParamspace)) + +Now we can access to the paramstore using Paramstore Keys + + k.ps.Get(KeyParameter1, ¶m) + k.ps.Set(KeyParameter2, param) + +Genesis Usage: + +Declare a struct for parameters and make it implement ParamStruct. It will then +be able to be passed to SetFromParamStruct. + + type MyParams struct { + Parameter1 uint64 + Parameter2 string + } + + func (p *MyParams) KeyFieldPairs() params.KeyFieldPairs { + return params.KeyFieldPairs { + {KeyParameter1, &p.Parameter1}, + {KeyParameter2, &p.Parameter2}, + } + } + + func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + k.ps.SetFromParamStruct(ctx, &data.params) + } + +The method is pointer receiver because there could be a case that we read from +the store and set the result to the struct. + +Master Permission Usage: + +Keepers that require master permission to the paramstore, such as gov, can take +params.Keeper itself to access all substores(using GetSubstore) + + type MasterKeeper struct { + ps params.Store + } + + func (k MasterKeeper) SetParam(ctx sdk.Context, space string, key string, param interface{}) { + store, ok := k.ps.GetSubstore(space) + if !ok { + return + } + store.Set(ctx, key, param) + } +*/ diff --git a/x/params/keeper.go b/x/params/keeper.go index ca158ad429..cf78b60ff4 100644 --- a/x/params/keeper.go +++ b/x/params/keeper.go @@ -1,406 +1,57 @@ package params import ( - "fmt" - "reflect" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/x/params/subspace" ) -// Keeper manages global parameter store +// Keeper of the global paramstore type Keeper struct { - cdc *codec.Codec - key sdk.StoreKey + cdc *codec.Codec + key sdk.StoreKey + tkey sdk.StoreKey + + spaces map[string]*Subspace } -// NewKeeper constructs a new Keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey) Keeper { - return Keeper{ - cdc: cdc, - key: key, - } -} +// NewKeeper constructs a params keeper +func NewKeeper(cdc *codec.Codec, key *sdk.KVStoreKey, tkey *sdk.TransientStoreKey) (k Keeper) { + k = Keeper{ + cdc: cdc, + key: key, + tkey: tkey, -// InitKeeper constructs a new Keeper with initial parameters -// nolint: errcheck -func InitKeeper(ctx sdk.Context, cdc *codec.Codec, key sdk.StoreKey, params ...interface{}) Keeper { - if len(params)%2 != 0 { - panic("Odd params list length for InitKeeper") - } - - k := NewKeeper(cdc, key) - - for i := 0; i < len(params); i += 2 { - k.set(ctx, params[i].(string), params[i+1]) + spaces: make(map[string]*Subspace), } return k } -// get automatically unmarshalls parameter to pointer -func (k Keeper) get(ctx sdk.Context, key string, ptr interface{}) error { - store := ctx.KVStore(k.key) - bz := store.Get([]byte(key)) - return k.cdc.UnmarshalBinary(bz, ptr) -} - -// getRaw returns raw byte slice -func (k Keeper) getRaw(ctx sdk.Context, key string) []byte { - store := ctx.KVStore(k.key) - return store.Get([]byte(key)) -} - -// set automatically marshalls and type check parameter -func (k Keeper) set(ctx sdk.Context, key string, param interface{}) error { - store := ctx.KVStore(k.key) - bz := store.Get([]byte(key)) - if bz != nil { - ptrty := reflect.PtrTo(reflect.TypeOf(param)) - ptr := reflect.New(ptrty).Interface() - - if k.cdc.UnmarshalBinary(bz, ptr) != nil { - return fmt.Errorf("Type mismatch with stored param and provided param") - } +// Allocate subspace used for keepers +func (k Keeper) Subspace(spacename string) Subspace { + _, ok := k.spaces[spacename] + if ok { + panic("subspace already occupied") } - bz, err := k.cdc.MarshalBinary(param) - if err != nil { - return err + if spacename == "" { + panic("cannot use empty string for subspace") } - store.Set([]byte(key), bz) - return nil + space := subspace.NewSubspace(k.cdc, k.key, k.tkey, spacename) + + k.spaces[spacename] = &space + + return space } -// setRaw sets raw byte slice -func (k Keeper) setRaw(ctx sdk.Context, key string, param []byte) { - store := ctx.KVStore(k.key) - store.Set([]byte(key), param) -} - -// Getter returns readonly struct -func (k Keeper) Getter() Getter { - return Getter{k} -} - -// Setter returns read/write struct -func (k Keeper) Setter() Setter { - return Setter{Getter{k}} -} - -// Getter exposes methods related with only getting params -type Getter struct { - k Keeper -} - -// Get exposes get -func (k Getter) Get(ctx sdk.Context, key string, ptr interface{}) error { - return k.k.get(ctx, key, ptr) -} - -// GetRaw exposes getRaw -func (k Getter) GetRaw(ctx sdk.Context, key string) []byte { - return k.k.getRaw(ctx, key) -} - -// GetString is helper function for string params -func (k Getter) GetString(ctx sdk.Context, key string) (res string, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetBool is helper function for bool params -func (k Getter) GetBool(ctx sdk.Context, key string) (res bool, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetInt16 is helper function for int16 params -func (k Getter) GetInt16(ctx sdk.Context, key string) (res int16, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetInt32 is helper function for int32 params -func (k Getter) GetInt32(ctx sdk.Context, key string) (res int32, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetInt64 is helper function for int64 params -func (k Getter) GetInt64(ctx sdk.Context, key string) (res int64, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetUint16 is helper function for uint16 params -func (k Getter) GetUint16(ctx sdk.Context, key string) (res uint16, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetUint32 is helper function for uint32 params -func (k Getter) GetUint32(ctx sdk.Context, key string) (res uint32, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetUint64 is helper function for uint64 params -func (k Getter) GetUint64(ctx sdk.Context, key string) (res uint64, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetInt is helper function for sdk.Int params -func (k Getter) GetInt(ctx sdk.Context, key string) (res sdk.Int, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetUint is helper function for sdk.Uint params -func (k Getter) GetUint(ctx sdk.Context, key string) (res sdk.Uint, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetDec is helper function for decimal params -func (k Getter) GetDec(ctx sdk.Context, key string) (res sdk.Dec, err error) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - err = k.k.cdc.UnmarshalBinary(bz, &res) - return -} - -// GetStringWithDefault is helper function for string params with default value -func (k Getter) GetStringWithDefault(ctx sdk.Context, key string, def string) (res string) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetBoolWithDefault is helper function for bool params with default value -func (k Getter) GetBoolWithDefault(ctx sdk.Context, key string, def bool) (res bool) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetInt16WithDefault is helper function for int16 params with default value -func (k Getter) GetInt16WithDefault(ctx sdk.Context, key string, def int16) (res int16) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetInt32WithDefault is helper function for int32 params with default value -func (k Getter) GetInt32WithDefault(ctx sdk.Context, key string, def int32) (res int32) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetInt64WithDefault is helper function for int64 params with default value -func (k Getter) GetInt64WithDefault(ctx sdk.Context, key string, def int64) (res int64) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetUint16WithDefault is helper function for uint16 params with default value -func (k Getter) GetUint16WithDefault(ctx sdk.Context, key string, def uint16) (res uint16) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetUint32WithDefault is helper function for uint32 params with default value -func (k Getter) GetUint32WithDefault(ctx sdk.Context, key string, def uint32) (res uint32) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetUint64WithDefault is helper function for uint64 params with default value -func (k Getter) GetUint64WithDefault(ctx sdk.Context, key string, def uint64) (res uint64) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetIntWithDefault is helper function for sdk.Int params with default value -func (k Getter) GetIntWithDefault(ctx sdk.Context, key string, def sdk.Int) (res sdk.Int) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetUintWithDefault is helper function for sdk.Uint params with default value -func (k Getter) GetUintWithDefault(ctx sdk.Context, key string, def sdk.Uint) (res sdk.Uint) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// GetDecWithDefault is helper function for sdk.Dec params with default value -func (k Getter) GetDecWithDefault(ctx sdk.Context, key string, def sdk.Dec) (res sdk.Dec) { - store := ctx.KVStore(k.k.key) - bz := store.Get([]byte(key)) - if bz == nil { - return def - } - k.k.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// Setter exposes all methods including Set -type Setter struct { - Getter -} - -// Set exposes set -func (k Setter) Set(ctx sdk.Context, key string, param interface{}) error { - return k.k.set(ctx, key, param) -} - -// SetRaw exposes setRaw -func (k Setter) SetRaw(ctx sdk.Context, key string, param []byte) { - k.k.setRaw(ctx, key, param) -} - -// SetString is helper function for string params -func (k Setter) SetString(ctx sdk.Context, key string, param string) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetBool is helper function for bool params -func (k Setter) SetBool(ctx sdk.Context, key string, param bool) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetInt16 is helper function for int16 params -func (k Setter) SetInt16(ctx sdk.Context, key string, param int16) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetInt32 is helper function for int32 params -func (k Setter) SetInt32(ctx sdk.Context, key string, param int32) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetInt64 is helper function for int64 params -func (k Setter) SetInt64(ctx sdk.Context, key string, param int64) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetUint16 is helper function for uint16 params -func (k Setter) SetUint16(ctx sdk.Context, key string, param uint16) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetUint32 is helper function for uint32 params -func (k Setter) SetUint32(ctx sdk.Context, key string, param uint32) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetUint64 is helper function for uint64 params -func (k Setter) SetUint64(ctx sdk.Context, key string, param uint64) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetInt is helper function for sdk.Int params -func (k Setter) SetInt(ctx sdk.Context, key string, param sdk.Int) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetUint is helper function for sdk.Uint params -func (k Setter) SetUint(ctx sdk.Context, key string, param sdk.Uint) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) - } -} - -// SetDec is helper function for decimal params -func (k Setter) SetDec(ctx sdk.Context, key string, param sdk.Dec) { - if err := k.k.set(ctx, key, param); err != nil { - panic(err) +// Get existing substore from keeper +func (k Keeper) GetSubspace(storename string) (Subspace, bool) { + space, ok := k.spaces[storename] + if !ok { + return Subspace{}, false } + return *space, ok } diff --git a/x/params/keeper_test.go b/x/params/keeper_test.go index 7eac7319f0..640661a245 100644 --- a/x/params/keeper_test.go +++ b/x/params/keeper_test.go @@ -1,9 +1,10 @@ package params import ( + "reflect" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" @@ -14,15 +15,30 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func defaultContext(key sdk.StoreKey) sdk.Context { +func defaultContext(key sdk.StoreKey, tkey sdk.StoreKey) sdk.Context { db := dbm.NewMemDB() cms := store.NewCommitMultiStore(db) cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.MountStoreWithDB(tkey, sdk.StoreTypeTransient, db) cms.LoadLatestVersion() ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) return ctx } +type invalid struct{} + +type s struct { + I int +} + +func createTestCodec() *codec.Codec { + cdc := codec.New() + sdk.RegisterCodec(cdc) + cdc.RegisterConcrete(s{}, "test/s", nil) + cdc.RegisterConcrete(invalid{}, "test/invalid", nil) + return cdc +} + func TestKeeper(t *testing.T) { kvs := []struct { key string @@ -33,248 +49,120 @@ func TestKeeper(t *testing.T) { {"key3", 182}, {"key4", 17582}, {"key5", 2768554}, + {"key6", 1157279}, + {"key7", 9058701}, } + table := NewTypeTable( + []byte("key1"), int64(0), + []byte("key2"), int64(0), + []byte("key3"), int64(0), + []byte("key4"), int64(0), + []byte("key5"), int64(0), + []byte("key6"), int64(0), + []byte("key7"), int64(0), + []byte("extra1"), bool(false), + []byte("extra2"), string(""), + ) + skey := sdk.NewKVStoreKey("test") - ctx := defaultContext(skey) - setter := NewKeeper(codec.New(), skey).Setter() + tkey := sdk.NewTransientStoreKey("transient_test") + ctx := defaultContext(skey, tkey) + store := NewKeeper(codec.New(), skey, tkey).Subspace("test").WithTypeTable(table) - for _, kv := range kvs { - err := setter.Set(ctx, kv.key, kv.param) - assert.Nil(t, err) + for i, kv := range kvs { + require.NotPanics(t, func() { store.Set(ctx, []byte(kv.key), kv.param) }, "store.Set panics, tc #%d", i) } - for _, kv := range kvs { + for i, kv := range kvs { var param int64 - err := setter.Get(ctx, kv.key, ¶m) - assert.Nil(t, err) - assert.Equal(t, kv.param, param) + require.NotPanics(t, func() { store.Get(ctx, []byte(kv.key), ¶m) }, "store.Get panics, tc #%d", i) + require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) } cdc := codec.New() - for _, kv := range kvs { + for i, kv := range kvs { var param int64 - bz := setter.GetRaw(ctx, kv.key) - err := cdc.UnmarshalBinary(bz, ¶m) - assert.Nil(t, err) - assert.Equal(t, kv.param, param) + bz := store.GetRaw(ctx, []byte(kv.key)) + err := cdc.UnmarshalJSON(bz, ¶m) + require.Nil(t, err, "err is not nil, tc #%d", i) + require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) } - for _, kv := range kvs { + for i, kv := range kvs { var param bool - err := setter.Get(ctx, kv.key, ¶m) - assert.NotNil(t, err) + require.Panics(t, func() { store.Get(ctx, []byte(kv.key), ¶m) }, "invalid store.Get not panics, tc #%d", i) } - for _, kv := range kvs { - err := setter.Set(ctx, kv.key, true) - assert.NotNil(t, err) + for i, kv := range kvs { + require.Panics(t, func() { store.Set(ctx, []byte(kv.key), true) }, "invalid store.Set not panics, tc #%d", i) } } -func TestGetter(t *testing.T) { +func TestGet(t *testing.T) { key := sdk.NewKVStoreKey("test") - ctx := defaultContext(key) - keeper := NewKeeper(codec.New(), key) - - g := keeper.Getter() - s := keeper.Setter() + tkey := sdk.NewTransientStoreKey("transient_test") + ctx := defaultContext(key, tkey) + keeper := NewKeeper(createTestCodec(), key, tkey) kvs := []struct { key string param interface{} + zero interface{} + ptr interface{} }{ - {"string", "test"}, - {"bool", true}, - {"int16", int16(1)}, - {"int32", int32(1)}, - {"int64", int64(1)}, - {"uint16", uint16(1)}, - {"uint32", uint32(1)}, - {"uint64", uint64(1)}, - {"int", sdk.NewInt(1)}, - {"uint", sdk.NewUint(1)}, - {"rat", sdk.NewDec(1)}, + {"string", "test", "", new(string)}, + {"bool", true, false, new(bool)}, + {"int16", int16(1), int16(0), new(int16)}, + {"int32", int32(1), int32(0), new(int32)}, + {"int64", int64(1), int64(0), new(int64)}, + {"uint16", uint16(1), uint16(0), new(uint16)}, + {"uint32", uint32(1), uint32(0), new(uint32)}, + {"uint64", uint64(1), uint64(0), new(uint64)}, + {"int", sdk.NewInt(1), *new(sdk.Int), new(sdk.Int)}, + {"uint", sdk.NewUint(1), *new(sdk.Uint), new(sdk.Uint)}, + {"dec", sdk.NewDec(1), *new(sdk.Dec), new(sdk.Dec)}, + {"struct", s{1}, s{0}, new(s)}, } - assert.NotPanics(t, func() { s.SetString(ctx, kvs[0].key, "test") }) - assert.NotPanics(t, func() { s.SetBool(ctx, kvs[1].key, true) }) - assert.NotPanics(t, func() { s.SetInt16(ctx, kvs[2].key, int16(1)) }) - assert.NotPanics(t, func() { s.SetInt32(ctx, kvs[3].key, int32(1)) }) - assert.NotPanics(t, func() { s.SetInt64(ctx, kvs[4].key, int64(1)) }) - assert.NotPanics(t, func() { s.SetUint16(ctx, kvs[5].key, uint16(1)) }) - assert.NotPanics(t, func() { s.SetUint32(ctx, kvs[6].key, uint32(1)) }) - assert.NotPanics(t, func() { s.SetUint64(ctx, kvs[7].key, uint64(1)) }) - assert.NotPanics(t, func() { s.SetInt(ctx, kvs[8].key, sdk.NewInt(1)) }) - assert.NotPanics(t, func() { s.SetUint(ctx, kvs[9].key, sdk.NewUint(1)) }) - assert.NotPanics(t, func() { s.SetDec(ctx, kvs[10].key, sdk.NewDec(1)) }) + table := NewTypeTable( + []byte("string"), string(""), + []byte("bool"), bool(false), + []byte("int16"), int16(0), + []byte("int32"), int32(0), + []byte("int64"), int64(0), + []byte("uint16"), uint16(0), + []byte("uint32"), uint32(0), + []byte("uint64"), uint64(0), + []byte("int"), sdk.Int{}, + []byte("uint"), sdk.Uint{}, + []byte("dec"), sdk.Dec{}, + []byte("struct"), s{}, + ) - var res interface{} - var err error + store := keeper.Subspace("test").WithTypeTable(table) - // String - def0 := "default" - res, err = g.GetString(ctx, kvs[0].key) - assert.Nil(t, err) - assert.Equal(t, kvs[0].param, res) + for i, kv := range kvs { + require.False(t, store.Modified(ctx, []byte(kv.key)), "store.Modified returns true before setting, tc #%d", i) + require.NotPanics(t, func() { store.Set(ctx, []byte(kv.key), kv.param) }, "store.Set panics, tc #%d", i) + require.True(t, store.Modified(ctx, []byte(kv.key)), "store.Modified returns false after setting, tc #%d", i) + } - _, err = g.GetString(ctx, "invalid") - assert.NotNil(t, err) + for i, kv := range kvs { + require.NotPanics(t, func() { store.GetIfExists(ctx, []byte("invalid"), kv.ptr) }, "store.GetIfExists panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, reflect.ValueOf(kv.ptr).Elem().Interface(), "store.GetIfExists unmarshalls when no value exists, tc #%d", i) + require.Panics(t, func() { store.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid store.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, reflect.ValueOf(kv.ptr).Elem().Interface(), "invalid store.Get unmarshalls when no value exists, tc #%d", i) - res = g.GetStringWithDefault(ctx, kvs[0].key, def0) - assert.Equal(t, kvs[0].param, res) + require.NotPanics(t, func() { store.GetIfExists(ctx, []byte(kv.key), kv.ptr) }, "store.GetIfExists panics, tc #%d", i) + require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "stored param not equal, tc #%d", i) + require.NotPanics(t, func() { store.Get(ctx, []byte(kv.key), kv.ptr) }, "store.Get panics, tc #%d", i) + require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "stored param not equal, tc #%d", i) - res = g.GetStringWithDefault(ctx, "invalid", def0) - assert.Equal(t, def0, res) - - // Bool - def1 := false - res, err = g.GetBool(ctx, kvs[1].key) - assert.Nil(t, err) - assert.Equal(t, kvs[1].param, res) - - _, err = g.GetBool(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetBoolWithDefault(ctx, kvs[1].key, def1) - assert.Equal(t, kvs[1].param, res) - - res = g.GetBoolWithDefault(ctx, "invalid", def1) - assert.Equal(t, def1, res) - - // Int16 - def2 := int16(0) - res, err = g.GetInt16(ctx, kvs[2].key) - assert.Nil(t, err) - assert.Equal(t, kvs[2].param, res) - - _, err = g.GetInt16(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetInt16WithDefault(ctx, kvs[2].key, def2) - assert.Equal(t, kvs[2].param, res) - - res = g.GetInt16WithDefault(ctx, "invalid", def2) - assert.Equal(t, def2, res) - - // Int32 - def3 := int32(0) - res, err = g.GetInt32(ctx, kvs[3].key) - assert.Nil(t, err) - assert.Equal(t, kvs[3].param, res) - - _, err = g.GetInt32(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetInt32WithDefault(ctx, kvs[3].key, def3) - assert.Equal(t, kvs[3].param, res) - - res = g.GetInt32WithDefault(ctx, "invalid", def3) - assert.Equal(t, def3, res) - - // Int64 - def4 := int64(0) - res, err = g.GetInt64(ctx, kvs[4].key) - assert.Nil(t, err) - assert.Equal(t, kvs[4].param, res) - - _, err = g.GetInt64(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetInt64WithDefault(ctx, kvs[4].key, def4) - assert.Equal(t, kvs[4].param, res) - - res = g.GetInt64WithDefault(ctx, "invalid", def4) - assert.Equal(t, def4, res) - - // Uint16 - def5 := uint16(0) - res, err = g.GetUint16(ctx, kvs[5].key) - assert.Nil(t, err) - assert.Equal(t, kvs[5].param, res) - - _, err = g.GetUint16(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetUint16WithDefault(ctx, kvs[5].key, def5) - assert.Equal(t, kvs[5].param, res) - - res = g.GetUint16WithDefault(ctx, "invalid", def5) - assert.Equal(t, def5, res) - - // Uint32 - def6 := uint32(0) - res, err = g.GetUint32(ctx, kvs[6].key) - assert.Nil(t, err) - assert.Equal(t, kvs[6].param, res) - - _, err = g.GetUint32(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetUint32WithDefault(ctx, kvs[6].key, def6) - assert.Equal(t, kvs[6].param, res) - - res = g.GetUint32WithDefault(ctx, "invalid", def6) - assert.Equal(t, def6, res) - - // Uint64 - def7 := uint64(0) - res, err = g.GetUint64(ctx, kvs[7].key) - assert.Nil(t, err) - assert.Equal(t, kvs[7].param, res) - - _, err = g.GetUint64(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetUint64WithDefault(ctx, kvs[7].key, def7) - assert.Equal(t, kvs[7].param, res) - - res = g.GetUint64WithDefault(ctx, "invalid", def7) - assert.Equal(t, def7, res) - - // Int - def8 := sdk.NewInt(0) - res, err = g.GetInt(ctx, kvs[8].key) - assert.Nil(t, err) - assert.Equal(t, kvs[8].param, res) - - _, err = g.GetInt(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetIntWithDefault(ctx, kvs[8].key, def8) - assert.Equal(t, kvs[8].param, res) - - res = g.GetIntWithDefault(ctx, "invalid", def8) - assert.Equal(t, def8, res) - - // Uint - def9 := sdk.NewUint(0) - res, err = g.GetUint(ctx, kvs[9].key) - assert.Nil(t, err) - assert.Equal(t, kvs[9].param, res) - - _, err = g.GetUint(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetUintWithDefault(ctx, kvs[9].key, def9) - assert.Equal(t, kvs[9].param, res) - - res = g.GetUintWithDefault(ctx, "invalid", def9) - assert.Equal(t, def9, res) - - // Rat - def10 := sdk.NewDec(0) - res, err = g.GetDec(ctx, kvs[10].key) - assert.Nil(t, err) - assert.Equal(t, kvs[10].param, res) - - _, err = g.GetDec(ctx, "invalid") - assert.NotNil(t, err) - - res = g.GetDecWithDefault(ctx, kvs[10].key, def10) - assert.Equal(t, kvs[10].param, res) - - res = g.GetDecWithDefault(ctx, "invalid", def10) - assert.Equal(t, def10, res) + require.Panics(t, func() { store.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid store.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "invalid store.Get unmarshalls when no value existt, tc #%d", i) + require.Panics(t, func() { store.Get(ctx, []byte(kv.key), nil) }, "invalid store.Get not panics when the pointer is nil, tc #%d", i) + require.Panics(t, func() { store.Get(ctx, []byte(kv.key), new(invalid)) }, "invalid store.Get not panics when the pointer is different type, tc #%d", i) + } } diff --git a/x/params/msg_status.go b/x/params/msg_status.go deleted file mode 100644 index a8516a85b7..0000000000 --- a/x/params/msg_status.go +++ /dev/null @@ -1,37 +0,0 @@ -package params - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// GenesisState defines initial activated msg types -type GenesisState struct { - ActivatedTypes []string `json:"activated-types"` -} - -// ActivatedParamKey - paramstore key for msg type activation -func ActivatedParamKey(ty string) string { - return "Activated/" + ty -} - -// InitGenesis stores activated type to param store -// nolint: errcheck -func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - for _, ty := range data.ActivatedTypes { - k.set(ctx, ActivatedParamKey(ty), true) - } -} - -// NewAnteHandler returns an AnteHandler that checks -// whether msg type is activate or not -func NewAnteHandler(k Keeper) sdk.AnteHandler { - return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, sdk.Result, bool) { - for _, msg := range tx.GetMsgs() { - ok := k.Getter().GetBoolWithDefault(ctx, ActivatedParamKey(msg.Type()), false) - if !ok { - return ctx, sdk.ErrUnauthorized("deactivated msg type").Result(), true - } - } - return ctx, sdk.Result{}, false - } -} diff --git a/x/params/subspace.go b/x/params/subspace.go new file mode 100644 index 0000000000..203d32e120 --- /dev/null +++ b/x/params/subspace.go @@ -0,0 +1,19 @@ +package params + +import ( + "github.com/cosmos/cosmos-sdk/x/params/subspace" +) + +// re-export types from subspace +type ( + Subspace = subspace.Subspace + ReadOnlySubspace = subspace.ReadOnlySubspace + ParamSet = subspace.ParamSet + KeyValuePairs = subspace.KeyValuePairs + TypeTable = subspace.TypeTable +) + +// re-export functions from subspace +func NewTypeTable(keytypes ...interface{}) TypeTable { + return subspace.NewTypeTable(keytypes...) +} diff --git a/x/params/subspace/doc.go b/x/params/subspace/doc.go new file mode 100644 index 0000000000..1ab87ac019 --- /dev/null +++ b/x/params/subspace/doc.go @@ -0,0 +1,14 @@ +package subspace + +/* +To prevent namespace collision between consumer modules, we define type +"space". A Space can only be generated by the keeper, and the keeper checks +the existence of the space having the same name before generating the +space. + +Consumer modules must take a space (via Keeper.Subspace), not the keeper +itself. This isolates each modules from the others and make them modify the +parameters safely. Keeper can be treated as master permission for all +subspaces (via Keeper.GetSubspace), so should be passed to proper modules +(ex. gov) +*/ diff --git a/x/params/subspace/pair.go b/x/params/subspace/pair.go new file mode 100644 index 0000000000..06fde6cef9 --- /dev/null +++ b/x/params/subspace/pair.go @@ -0,0 +1,15 @@ +package subspace + +// Used for associating paramsubspace key and field of param structs +type KeyValuePair struct { + Key []byte + Value interface{} +} + +// Slice of KeyFieldPair +type KeyValuePairs []KeyValuePair + +// Interface for structs containing parameters for a module +type ParamSet interface { + KeyValuePairs() KeyValuePairs +} diff --git a/x/params/subspace/subspace.go b/x/params/subspace/subspace.go new file mode 100644 index 0000000000..7c9ca5fdaa --- /dev/null +++ b/x/params/subspace/subspace.go @@ -0,0 +1,198 @@ +package subspace + +import ( + "reflect" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Additional capicity to be allocated for Subspace.name +// So we don't have to allocate extra space each time appending to the key +const extraKeyCap = 20 + +// Individual parameter store for each keeper +// Transient store persists for a block, so we use it for +// recording whether the parameter has been changed or not +type Subspace struct { + cdc *codec.Codec + key sdk.StoreKey // []byte -> []byte, stores parameter + tkey sdk.StoreKey // []byte -> bool, stores parameter change + + name []byte + + table TypeTable +} + +// NewSubspace constructs a store with namestore +func NewSubspace(cdc *codec.Codec, key sdk.StoreKey, tkey sdk.StoreKey, name string) (res Subspace) { + res = Subspace{ + cdc: cdc, + key: key, + tkey: tkey, + } + + namebz := []byte(name) + res.name = make([]byte, len(namebz), len(namebz)+extraKeyCap) + copy(res.name, namebz) + return +} + +// WithTypeTable initializes TypeTable and returns modified Subspace +func (s Subspace) WithTypeTable(table TypeTable) (res Subspace) { + if table == nil { + panic("SetTypeTable() called with nil TypeTable") + } + if s.table != nil { + panic("SetTypeTable() called on initialized Subspace") + } + + res = Subspace{ + cdc: s.cdc, + key: s.key, + tkey: s.tkey, + name: s.name, + table: table, + } + + return +} + +// Returns a KVStore identical with ctx.KVStore(s.key).Prefix() +func (s Subspace) kvStore(ctx sdk.Context) sdk.KVStore { + // append here is safe, appends within a function won't cause + // weird side effects when its singlethreaded + return ctx.KVStore(s.key).Prefix(append(s.name, '/')) +} + +// Returns a KVStore identical with ctx.TransientStore(s.tkey).Prefix() +func (s Subspace) transientStore(ctx sdk.Context) sdk.KVStore { + // append here is safe, appends within a function won't cause + // weird side effects when its singlethreaded + return ctx.TransientStore(s.tkey).Prefix(append(s.name, '/')) +} + +// Get parameter from store +func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { + store := s.kvStore(ctx) + bz := store.Get(key) + err := s.cdc.UnmarshalJSON(bz, ptr) + if err != nil { + panic(err) + } +} + +// GetIfExists do not modify ptr if the stored parameter is nil +func (s Subspace) GetIfExists(ctx sdk.Context, key []byte, ptr interface{}) { + store := s.kvStore(ctx) + bz := store.Get(key) + if bz == nil { + return + } + err := s.cdc.UnmarshalJSON(bz, ptr) + if err != nil { + panic(err) + } +} + +// Get raw bytes of parameter from store +func (s Subspace) GetRaw(ctx sdk.Context, key []byte) []byte { + store := s.kvStore(ctx) + return store.Get(key) +} + +// Check if the parameter is set in the store +func (s Subspace) Has(ctx sdk.Context, key []byte) bool { + store := s.kvStore(ctx) + return store.Has(key) +} + +// Returns true if the parameter is set in the block +func (s Subspace) Modified(ctx sdk.Context, key []byte) bool { + tstore := s.transientStore(ctx) + return tstore.Has(key) +} + +// Set parameter, return error if stored parameter has different type from input +// Also set to the transient store to record change +func (s Subspace) Set(ctx sdk.Context, key []byte, param interface{}) { + store := s.kvStore(ctx) + + ty, ok := s.table[string(key)] + if !ok { + panic("Parameter not registered") + } + + pty := reflect.TypeOf(param) + if pty.Kind() == reflect.Ptr { + pty = pty.Elem() + } + + if pty != ty { + panic("Type mismatch with registered table") + } + + bz, err := s.cdc.MarshalJSON(param) + if err != nil { + panic(err) + } + store.Set(key, bz) + + tstore := s.transientStore(ctx) + tstore.Set(key, []byte{}) + +} + +// Get to ParamSet +func (s Subspace) GetParamSet(ctx sdk.Context, ps ParamSet) { + for _, pair := range ps.KeyValuePairs() { + s.Get(ctx, pair.Key, pair.Value) + } +} + +// Set from ParamSet +func (s Subspace) SetParamSet(ctx sdk.Context, ps ParamSet) { + for _, pair := range ps.KeyValuePairs() { + // pair.Field is a pointer to the field, so indirecting the ptr. + // go-amino automatically handles it but just for sure, + // since SetStruct is meant to be used in InitGenesis + // so this method will not be called frequently + v := reflect.Indirect(reflect.ValueOf(pair.Value)).Interface() + s.Set(ctx, pair.Key, v) + } +} + +// Returns name of Subspace +func (s Subspace) Name() string { + return string(s.name) +} + +// Wrapper of Subspace, provides immutable functions only +type ReadOnlySubspace struct { + s Subspace +} + +// Exposes Get +func (ros ReadOnlySubspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { + ros.s.Get(ctx, key, ptr) +} + +// Exposes GetRaw +func (ros ReadOnlySubspace) GetRaw(ctx sdk.Context, key []byte) []byte { + return ros.s.GetRaw(ctx, key) +} + +// Exposes Has +func (ros ReadOnlySubspace) Has(ctx sdk.Context, key []byte) bool { + return ros.s.Has(ctx, key) +} + +// Exposes Modified +func (ros ReadOnlySubspace) Modified(ctx sdk.Context, key []byte) bool { + return ros.s.Modified(ctx, key) +} + +// Exposes Space +func (ros ReadOnlySubspace) Name() string { + return ros.s.Name() +} diff --git a/x/params/subspace/table.go b/x/params/subspace/table.go new file mode 100644 index 0000000000..363d0243d2 --- /dev/null +++ b/x/params/subspace/table.go @@ -0,0 +1,50 @@ +package subspace + +import ( + "reflect" +) + +// TypeTable subspaces appropriate type for each parameter key +type TypeTable map[string]reflect.Type + +// Constructs new table +func NewTypeTable(keytypes ...interface{}) (res TypeTable) { + if len(keytypes)%2 != 0 { + panic("odd number arguments in NewTypeTypeTable") + } + + res = make(map[string]reflect.Type) + + for i := 0; i < len(keytypes); i += 2 { + res = res.RegisterType(keytypes[i].([]byte), keytypes[i+1]) + } + + return +} + +// Register single key-type pair +func (t TypeTable) RegisterType(key []byte, ty interface{}) TypeTable { + keystr := string(key) + if _, ok := t[keystr]; ok { + panic("duplicate parameter key") + } + + rty := reflect.TypeOf(ty) + + // Indirect rty if it is ptr + if rty.Kind() == reflect.Ptr { + rty = rty.Elem() + } + + t[keystr] = rty + + return t +} + +// Register multiple pairs from ParamSet +func (t TypeTable) RegisterParamSet(ps ParamSet) TypeTable { + for _, kvp := range ps.KeyValuePairs() { + t = t.RegisterType(kvp.Key, kvp.Value) + } + return t +} diff --git a/x/params/subspace/test_common.go b/x/params/subspace/test_common.go new file mode 100644 index 0000000000..e3d980a72a --- /dev/null +++ b/x/params/subspace/test_common.go @@ -0,0 +1,40 @@ +package subspace + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Keys for parameter access +const ( + TestParamStore = "ParamsTest" +) + +// Returns components for testing +func DefaultTestComponents(t *testing.T, table TypeTable) (sdk.Context, Subspace, func() sdk.CommitID) { + cdc := codec.New() + key := sdk.NewKVStoreKey("params") + tkey := sdk.NewTransientStoreKey("tparams") + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ms.WithTracer(os.Stdout) + ms.WithTracingContext(sdk.TraceContext{}) + ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkey, sdk.StoreTypeTransient, db) + err := ms.LoadLatestVersion() + require.Nil(t, err) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewTMLogger(os.Stdout)) + subspace := NewSubspace(cdc, key, tkey, TestParamStore).WithTypeTable(table) + + return ctx, subspace, ms.Commit +} diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index a3370b1d87..c0ed10747c 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -28,18 +28,21 @@ func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") keySlashing := sdk.NewKVStoreKey("slashing") - keyParams := sdk.NewKVStoreKey("params") - bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) - paramsKeeper := params.NewKeeper(mapp.Cdc, keyParams) - stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, bankKeeper, mapp.RegisterCodespace(stake.DefaultCodespace)) - keeper := NewKeeper(mapp.Cdc, keySlashing, stakeKeeper, paramsKeeper.Getter(), mapp.RegisterCodespace(DefaultCodespace)) + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) + + paramsKeeper := params.NewKeeper(mapp.Cdc, keyParams, tkeyParams) + stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, bankKeeper, paramsKeeper.Subspace(stake.DefaultParamspace), mapp.RegisterCodespace(stake.DefaultCodespace)) + keeper := NewKeeper(mapp.Cdc, keySlashing, stakeKeeper, paramsKeeper.Subspace(DefaultParamspace), mapp.RegisterCodespace(DefaultCodespace)) mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) mapp.Router().AddRoute("slashing", NewHandler(keeper)) mapp.SetEndBlocker(getEndBlocker(stakeKeeper)) mapp.SetInitChainer(getInitChainer(mapp, stakeKeeper)) - require.NoError(t, mapp.CompleteSetup(keyStake, keySlashing, keyParams, tkeyStake)) + + require.NoError(t, mapp.CompleteSetup(keyStake, tkeyStake, keySlashing, keyParams, tkeyParams)) return mapp, stakeKeeper, keeper } diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 43ae6b0d0a..10af155d68 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -5,10 +5,24 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// InitGenesis initializes the keeper's address to pubkey map. -func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { - for _, validator := range data.Validators { +// GenesisState - all slashing state that must be provided at genesis +type GenesisState struct { + Params Params +} + +// HubDefaultGenesisState - default GenesisState used by Cosmos Hub +func DefaultGenesisState() GenesisState { + return GenesisState{ + Params: DefaultParams(), + } +} + +// InitGenesis initialize default parameters +// and the keeper's address to pubkey map +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState, sdata types.GenesisState) { + for _, validator := range sdata.Validators { keeper.addPubkey(ctx, validator.GetConsPubKey()) } - return + + keeper.paramspace.SetParamSet(ctx, &data.Params) } diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 059b190597..20395b2e9f 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -12,7 +12,7 @@ import ( func TestCannotUnjailUnlessJailed(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t) + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) slh := NewHandler(keeper) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) @@ -30,7 +30,7 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { } func TestJailedValidatorDelegations(t *testing.T) { - ctx, _, stakeKeeper, _, slashingKeeper := createTestInput(t) + ctx, _, stakeKeeper, _, slashingKeeper := createTestInput(t, DefaultParams()) stakeParams := stakeKeeper.GetParams(ctx) stakeParams.UnbondingTime = 0 diff --git a/x/slashing/hooks_test.go b/x/slashing/hooks_test.go index 0731fd8f26..951e3637f3 100644 --- a/x/slashing/hooks_test.go +++ b/x/slashing/hooks_test.go @@ -9,7 +9,7 @@ import ( ) func TestHookOnValidatorBonded(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t) + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) addr := sdk.ConsAddress(addrs[0]) keeper.onValidatorBonded(ctx, addr) period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) @@ -17,7 +17,7 @@ func TestHookOnValidatorBonded(t *testing.T) { } func TestHookOnValidatorBeginUnbonding(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t) + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) addr := sdk.ConsAddress(addrs[0]) keeper.onValidatorBonded(ctx, addr) keeper.onValidatorBeginUnbonding(ctx, addr) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 94dd9f0d0f..f5fe3bc362 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -19,18 +19,19 @@ type Keeper struct { storeKey sdk.StoreKey cdc *codec.Codec validatorSet sdk.ValidatorSet - params params.Getter + paramspace params.Subspace + // codespace codespace sdk.CodespaceType } // NewKeeper creates a slashing keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, params params.Getter, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspace params.Subspace, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ storeKey: key, cdc: cdc, validatorSet: vs, - params: params, + paramspace: paramspace.WithTypeTable(ParamTypeTable()), codespace: codespace, } return keeper diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index cf67af1920..627eff6f26 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -12,10 +12,12 @@ import ( // Have to change these parameters for tests // lest the tests take forever -func init() { - defaultSignedBlocksWindow = 1000 - defaultDowntimeUnbondDuration = 60 * 60 - defaultDoubleSignUnbondDuration = 60 * 60 +func keeperTestParams() Params { + params := DefaultParams() + params.SignedBlocksWindow = 1000 + params.DowntimeUnbondDuration = 60 * 60 + params.DoubleSignUnbondDuration = 60 * 60 + return params } // ______________________________________________________________ @@ -25,7 +27,7 @@ func init() { func TestHandleDoubleSign(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t) + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) // validator added pre-genesis ctx = ctx.WithBlockHeight(-1) sk = sk.WithHooks(keeper.Hooks()) @@ -68,7 +70,7 @@ func TestHandleDoubleSign(t *testing.T) { func TestSlashingPeriodCap(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t) + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) operatorAddr, amt := addrs[0], sdk.NewInt(amtInt) @@ -134,7 +136,7 @@ func TestSlashingPeriodCap(t *testing.T) { func TestHandleAbsentValidator(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t) + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) @@ -289,7 +291,7 @@ func TestHandleAbsentValidator(t *testing.T) { // and that they are not immediately jailed func TestHandleNewValidator(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t) + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) addr, val, amt := addrs[0], pks[0], int64(100) sh := stake.NewHandler(sk) got := sh(ctx, newTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) @@ -326,7 +328,7 @@ func TestHandleNewValidator(t *testing.T) { func TestHandleAlreadyJailed(t *testing.T) { // initial setup - ctx, _, sk, _, keeper := createTestInput(t) + ctx, _, sk, _, keeper := createTestInput(t, DefaultParams()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) diff --git a/x/slashing/params.go b/x/slashing/params.go index 6e18e5f481..54d8f7f128 100644 --- a/x/slashing/params.go +++ b/x/slashing/params.go @@ -4,77 +4,119 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" ) -// nolint +// Default parameter namespace const ( - MaxEvidenceAgeKey = "slashing/MaxEvidenceAge" - SignedBlocksWindowKey = "slashing/SignedBlocksWindow" - MinSignedPerWindowKey = "slashing/MinSignedPerWindow" - DoubleSignUnbondDurationKey = "slashing/DoubleSignUnbondDuration" - DowntimeUnbondDurationKey = "slashing/DowntimeUnbondDuration" - SlashFractionDoubleSignKey = "slashing/SlashFractionDoubleSign" - SlashFractionDowntimeKey = "slashing/SlashFractionDowntime" + DefaultParamspace = "slashing" ) +// Parameter store key +var ( + KeyMaxEvidenceAge = []byte("MaxEvidenceAge") + KeySignedBlocksWindow = []byte("SignedBlocksWindow") + KeyMinSignedPerWindow = []byte("MinSignedPerWindow") + KeyDoubleSignUnbondDuration = []byte("DoubleSignUnbondDuration") + KeyDowntimeUnbondDuration = []byte("DowntimeUnbondDuration") + KeySlashFractionDoubleSign = []byte("SlashFractionDoubleSign") + KeySlashFractionDowntime = []byte("SlashFractionDowntime") +) + +// ParamTypeTable for slashing module +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable().RegisterParamSet(&Params{}) +} + +// Params - used for initializing default parameter for slashing at genesis +type Params struct { + MaxEvidenceAge time.Duration `json:"max-evidence-age"` + SignedBlocksWindow int64 `json:"signed-blocks-window"` + MinSignedPerWindow sdk.Dec `json:"min-signed-per-window"` + DoubleSignUnbondDuration time.Duration `json:"double-sign-unbond-duration"` + DowntimeUnbondDuration time.Duration `json:"downtime-unbond-duration"` + SlashFractionDoubleSign sdk.Dec `json:"slash-fraction-double-sign"` + SlashFractionDowntime sdk.Dec `json:"slash-fraction-downtime"` +} + +// Implements params.ParamStruct +func (p *Params) KeyValuePairs() params.KeyValuePairs { + return params.KeyValuePairs{ + {KeyMaxEvidenceAge, &p.MaxEvidenceAge}, + {KeySignedBlocksWindow, &p.SignedBlocksWindow}, + {KeyMinSignedPerWindow, &p.MinSignedPerWindow}, + {KeyDoubleSignUnbondDuration, &p.DoubleSignUnbondDuration}, + {KeyDowntimeUnbondDuration, &p.DowntimeUnbondDuration}, + {KeySlashFractionDoubleSign, &p.SlashFractionDoubleSign}, + {KeySlashFractionDowntime, &p.SlashFractionDowntime}, + } +} + +// Default parameters used by Cosmos Hub +func DefaultParams() Params { + return Params{ + // defaultMaxEvidenceAge = 60 * 60 * 24 * 7 * 3 + // TODO Temporarily set to 2 minutes for testnets. + MaxEvidenceAge: 60 * 2 * time.Second, + + // TODO Temporarily set to five minutes for testnets + DoubleSignUnbondDuration: 60 * 5 * time.Second, + + // TODO Temporarily set to 100 blocks for testnets + SignedBlocksWindow: 100, + + // TODO Temporarily set to 10 minutes for testnets + DowntimeUnbondDuration: 60 * 10 * time.Second, + + MinSignedPerWindow: sdk.NewDecWithPrec(5, 1), + + SlashFractionDoubleSign: sdk.NewDec(1).Quo(sdk.NewDec(20)), + + SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(100)), + } +} + // MaxEvidenceAge - Max age for evidence - 21 days (3 weeks) // MaxEvidenceAge = 60 * 60 * 24 * 7 * 3 -func (k Keeper) MaxEvidenceAge(ctx sdk.Context) time.Duration { - return time.Duration(k.params.GetInt64WithDefault(ctx, MaxEvidenceAgeKey, defaultMaxEvidenceAge)) * time.Second +func (k Keeper) MaxEvidenceAge(ctx sdk.Context) (res time.Duration) { + k.paramspace.Get(ctx, KeyMaxEvidenceAge, &res) + return } // SignedBlocksWindow - sliding window for downtime slashing -func (k Keeper) SignedBlocksWindow(ctx sdk.Context) int64 { - return k.params.GetInt64WithDefault(ctx, SignedBlocksWindowKey, defaultSignedBlocksWindow) +func (k Keeper) SignedBlocksWindow(ctx sdk.Context) (res int64) { + k.paramspace.Get(ctx, KeySignedBlocksWindow, &res) + return } -// Downtime slashing thershold - default 50% +// Downtime slashing thershold - default 50% of the SignedBlocksWindow func (k Keeper) MinSignedPerWindow(ctx sdk.Context) int64 { - minSignedPerWindow := k.params.GetDecWithDefault(ctx, MinSignedPerWindowKey, defaultMinSignedPerWindow) + var minSignedPerWindow sdk.Dec + k.paramspace.Get(ctx, KeyMinSignedPerWindow, &minSignedPerWindow) signedBlocksWindow := k.SignedBlocksWindow(ctx) return sdk.NewDec(signedBlocksWindow).Mul(minSignedPerWindow).RoundInt64() } // Double-sign unbond duration -func (k Keeper) DoubleSignUnbondDuration(ctx sdk.Context) time.Duration { - return time.Duration(k.params.GetInt64WithDefault(ctx, DoubleSignUnbondDurationKey, defaultDoubleSignUnbondDuration)) * time.Second +func (k Keeper) DoubleSignUnbondDuration(ctx sdk.Context) (res time.Duration) { + k.paramspace.Get(ctx, KeyDoubleSignUnbondDuration, &res) + return } // Downtime unbond duration -func (k Keeper) DowntimeUnbondDuration(ctx sdk.Context) time.Duration { - return time.Duration(k.params.GetInt64WithDefault(ctx, DowntimeUnbondDurationKey, defaultDowntimeUnbondDuration)) * time.Second +func (k Keeper) DowntimeUnbondDuration(ctx sdk.Context) (res time.Duration) { + k.paramspace.Get(ctx, KeyDowntimeUnbondDuration, &res) + return } // SlashFractionDoubleSign - currently default 5% -func (k Keeper) SlashFractionDoubleSign(ctx sdk.Context) sdk.Dec { - return k.params.GetDecWithDefault(ctx, SlashFractionDoubleSignKey, defaultSlashFractionDoubleSign) +func (k Keeper) SlashFractionDoubleSign(ctx sdk.Context) (res sdk.Dec) { + k.paramspace.Get(ctx, KeySlashFractionDoubleSign, &res) + return } // SlashFractionDowntime - currently default 1% -func (k Keeper) SlashFractionDowntime(ctx sdk.Context) sdk.Dec { - return k.params.GetDecWithDefault(ctx, SlashFractionDowntimeKey, defaultSlashFractionDowntime) +func (k Keeper) SlashFractionDowntime(ctx sdk.Context) (res sdk.Dec) { + k.paramspace.Get(ctx, KeySlashFractionDowntime, &res) + return } - -// declared as var because of keeper_test.go -// TODO: make it const or parameter of NewKeeper - -var ( - // defaultMaxEvidenceAge = 60 * 60 * 24 * 7 * 3 - // TODO Temporarily set to 2 minutes for testnets. - defaultMaxEvidenceAge int64 = 60 * 2 - - // TODO Temporarily set to five minutes for testnets - defaultDoubleSignUnbondDuration int64 = 60 * 5 - - // TODO Temporarily set to 10000 blocks for testnets - defaultSignedBlocksWindow int64 = 10000 - - // TODO Temporarily set to 10 minutes for testnets - defaultDowntimeUnbondDuration int64 = 60 * 10 - - defaultMinSignedPerWindow = sdk.NewDecWithPrec(5, 1) - - defaultSlashFractionDoubleSign = sdk.NewDec(1).Quo(sdk.NewDec(20)) - - defaultSlashFractionDowntime = sdk.NewDec(1).Quo(sdk.NewDec(100)) -) diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index 7aff0da95f..123457d5d5 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -10,7 +10,7 @@ import ( ) func TestGetSetValidatorSigningInfo(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t) + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) require.False(t, found) newInfo := ValidatorSigningInfo{ @@ -29,7 +29,7 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { } func TestGetSetValidatorSigningBitArray(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t) + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) signed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) require.False(t, signed) // treat empty key as unsigned keeper.setValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) diff --git a/x/slashing/slashing_period_test.go b/x/slashing/slashing_period_test.go index 54157bb9cc..bd12ef1273 100644 --- a/x/slashing/slashing_period_test.go +++ b/x/slashing/slashing_period_test.go @@ -9,7 +9,7 @@ import ( ) func TestGetSetValidatorSlashingPeriod(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t) + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) addr := sdk.ConsAddress(addrs[0]) height := int64(5) require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) }) @@ -60,7 +60,7 @@ func TestGetSetValidatorSlashingPeriod(t *testing.T) { } func TestValidatorSlashingPeriodCap(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t) + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) addr := sdk.ConsAddress(addrs[0]) height := int64(5) newPeriod := ValidatorSlashingPeriod{ diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 7c97a85372..af2a3f7d82 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -49,12 +49,13 @@ func createTestCodec() *codec.Codec { return cdc } -func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, params.Setter, Keeper) { +func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, stake.Keeper, params.Subspace, Keeper) { keyAcc := sdk.NewKVStoreKey("acc") keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") keySlashing := sdk.NewKVStoreKey("slashing") keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) @@ -62,14 +63,16 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) err := ms.LoadLatestVersion() require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewTMLogger(os.Stdout)) cdc := createTestCodec() accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount) + ck := bank.NewBaseKeeper(accountMapper) - params := params.NewKeeper(cdc, keyParams) - sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, stake.DefaultCodespace) + paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams) + sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, paramsKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) genesis := stake.DefaultGenesisState() genesis.Pool.LooseTokens = sdk.NewDec(initCoins.MulRaw(int64(len(addrs))).Int64()) @@ -83,8 +86,14 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para }) } require.Nil(t, err) - keeper := NewKeeper(cdc, keySlashing, sk, params.Getter(), DefaultCodespace) - return ctx, ck, sk, params.Setter(), keeper + paramstore := paramsKeeper.Subspace(DefaultParamspace) + keeper := NewKeeper(cdc, keySlashing, sk, paramstore, DefaultCodespace) + + require.NotPanics(t, func() { + InitGenesis(ctx, keeper, GenesisState{defaults}, genesis) + }) + + return ctx, ck, sk, paramstore, keeper } func newPubKey(pk string) (res crypto.PubKey) { diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index a98d97c54c..085ce9eba8 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -13,7 +13,7 @@ import ( ) func TestBeginBlocker(t *testing.T) { - ctx, ck, sk, _, keeper := createTestInput(t) + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) addr, pk, amt := addrs[2], pks[2], sdk.NewInt(100) // bond the validator diff --git a/x/stake/app_test.go b/x/stake/app_test.go index f96408c11f..ac0bd8fcc5 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/cosmos/cosmos-sdk/x/params" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" @@ -36,15 +37,21 @@ func getMockApp(t *testing.T) (*mock.App, Keeper) { RegisterCodec(mApp.Cdc) keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + bankKeeper := bank.NewBaseKeeper(mApp.AccountMapper) - keeper := NewKeeper(mApp.Cdc, keyStake, tkeyStake, bankKeeper, mApp.RegisterCodespace(DefaultCodespace)) + pk := params.NewKeeper(mApp.Cdc, keyParams, tkeyParams) + + keeper := NewKeeper(mApp.Cdc, keyStake, tkeyStake, bankKeeper, pk.Subspace(DefaultParamspace), mApp.RegisterCodespace(DefaultCodespace)) mApp.Router().AddRoute("stake", NewHandler(keeper)) mApp.SetEndBlocker(getEndBlocker(keeper)) mApp.SetInitChainer(getInitChainer(mApp, keeper)) - require.NoError(t, mApp.CompleteSetup(keyStake, tkeyStake)) + require.NoError(t, mApp.CompleteSetup(keyStake, tkeyStake, keyParams, tkeyParams)) return mApp, keeper } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 26b57c2a69..ad8030c931 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -464,15 +464,17 @@ func GetCmdQueryParams(storeName string, cdc *codec.Codec) *cobra.Command { Short: "Query the current staking parameters information", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - key := stake.ParamKey cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, err := cliCtx.QueryStore(key, storeName) + bz, err := cliCtx.QueryWithData("custom/stake/"+stake.QueryParameters, nil) if err != nil { return err } - params := types.MustUnmarshalParams(cdc, res) + var params stake.Params + err = cdc.UnmarshalJSON(bz, ¶ms) + if err != nil { + return err + } switch viper.Get(cli.OutputFlag) { case "text": diff --git a/x/stake/handler.go b/x/stake/handler.go index 2367182891..57430d58fa 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -35,6 +35,8 @@ func NewHandler(k keeper.Keeper) sdk.Handler { func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.ValidatorUpdate) { endBlockerTags := sdk.EmptyTags() + k.UnbondAllMatureValidatorQueue(ctx) + matureUnbonds := k.DequeueAllMatureUnbondingQueue(ctx, ctx.BlockHeader().Time) for _, dvPair := range matureUnbonds { err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 1802f30c13..de7aee90f4 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -629,6 +629,56 @@ func TestJailValidator(t *testing.T) { require.True(t, got.IsOK(), "expected ok, got %v", got) } +func TestValidatorQueue(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 * time.Second + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // bond a delegator + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) + + EndBlocker(ctx, keeper) + + // unbond the all self-delegation to put validator in unbonding state + msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + origHeader := ctx.BlockHeader() + + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should still be unbonding at time 6 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should be in unbonded state at time 7 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonded, "%v", validator) +} + func TestUnbondingPeriod(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) validatorAddr := sdk.ValAddress(keep.Addrs[0]) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 4f1f8e122f..6f5a44ac05 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -437,7 +437,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA //______________________________________________________________________________________________________ // get info for begin functions: MinTime and CreationHeight -func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sdk.ValAddress) ( +func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( minTime time.Time, height int64, completeNow bool) { validator, found := k.GetValidator(ctx, valSrcAddr) @@ -446,11 +446,11 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sd case !found || validator.Status == sdk.Bonded: // the longest wait - just unbonding period from now - minTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) - height = ctx.BlockHeader().Height + minTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) + height = ctx.BlockHeight() return minTime, height, false - case validator.IsUnbonded(ctx): + case validator.Status == sdk.Unbonded: return minTime, height, true case validator.Status == sdk.Unbonding: @@ -474,15 +474,14 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, } // create the unbonding delegation - params := k.GetParams(ctx) - minTime, height, completeNow := k.getBeginInfo(ctx, params, valAddr) + minTime, height, completeNow := k.getBeginInfo(ctx, valAddr) returnAmount, err := k.unbond(ctx, delAddr, valAddr, sharesAmount) if err != nil { return types.UnbondingDelegation{}, err } - balance := sdk.NewCoin(params.BondDenom, returnAmount.RoundInt()) + balance := sdk.NewCoin(k.BondDenom(ctx), returnAmount.RoundInt()) // no need to create the ubd object just complete now if completeNow { @@ -537,8 +536,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, return types.Redelegation{}, err } - params := k.GetParams(ctx) - returnCoin := sdk.NewCoin(params.BondDenom, returnAmount.RoundInt()) + returnCoin := sdk.Coin{k.BondDenom(ctx), returnAmount.RoundInt()} dstValidator, found := k.GetValidator(ctx, valDstAddr) if !found { return types.Redelegation{}, types.ErrBadRedelegationDst(k.Codespace()) @@ -549,7 +547,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, } // create the unbonding delegation - minTime, height, completeNow := k.getBeginInfo(ctx, params, valSrcAddr) + minTime, height, completeNow := k.getBeginInfo(ctx, valSrcAddr) if completeNow { // no need to create the redelegation object return types.Redelegation{MinTime: minTime}, nil diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 0964f10def..6c42feb413 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -374,12 +374,8 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { } keeper.SetDelegation(ctx, delegation) - header := ctx.BlockHeader() - blockHeight := int64(10) - header.Height = blockHeight - blockTime := time.Unix(333, 0) - header.Time = blockTime - ctx = ctx.WithBlockHeader(header) + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) @@ -391,17 +387,12 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, blockHeight, validator.UnbondingHeight) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) - // change the context to one which makes the validator considered unbonded - header = ctx.BlockHeader() - blockHeight2 := int64(20) - header.Height = blockHeight2 - blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) - header.Time = blockTime2 - ctx = ctx.WithBlockHeader(header) + // unbond the validator + keeper.unbondingToUnbonded(ctx, validator) // unbond some of the other delegation's shares _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) @@ -696,12 +687,8 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator2 = testingUpdateValidator(keeper, ctx, validator2) require.Equal(t, sdk.Bonded, validator2.Status) - header := ctx.BlockHeader() - blockHeight := int64(10) - header.Height = blockHeight - blockTime := time.Unix(333, 0) - header.Time = blockTime - ctx = ctx.WithBlockHeader(header) + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) @@ -713,23 +700,18 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, blockHeight, validator.UnbondingHeight) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) - // change the context to one which makes the validator considered unbonded - header = ctx.BlockHeader() - blockHeight2 := int64(20) - header.Height = blockHeight2 - blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) - header.Time = blockTime2 - ctx = ctx.WithBlockHeader(header) + // unbond the validator + keeper.unbondingToUnbonded(ctx, validator) - // unbond some of the other delegation's shares + // redelegate some of the delegation's shares _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) require.NoError(t, err) - // no ubd should have been found, coins should have been returned direcly to account - ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.False(t, found, "%v", ubd) + // no red should have been found + red, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.False(t, found, "%v", red) } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 007c2a5de7..2092786986 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -15,17 +16,19 @@ type Keeper struct { cdc *codec.Codec bankKeeper bank.Keeper hooks sdk.StakingHooks + paramstore params.Subspace // codespace codespace sdk.CodespaceType } -func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, paramstore params.Subspace, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ storeKey: key, storeTKey: tkey, cdc: cdc, bankKeeper: ck, + paramstore: paramstore.WithTypeTable(ParamTypeTable()), hooks: nil, codespace: codespace, } @@ -48,29 +51,6 @@ func (k Keeper) Codespace() sdk.CodespaceType { return k.codespace } -//_________________________________________________________________________ -// some generic reads/writes that don't need their own files - -// load/save the global staking params -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - store := ctx.KVStore(k.storeKey) - - b := store.Get(ParamKey) - if b == nil { - panic("Stored params should not have been nil") - } - - k.cdc.MustUnmarshalBinary(b, ¶ms) - return -} - -// set the params -func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - //_______________________________________________________________________ // load/save the pool diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index d965dce057..9f6592ef33 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -13,7 +13,8 @@ import ( //nolint var ( // Keys for store prefixes - ParamKey = []byte{0x00} // key for parameters relating to staking + // TODO DEPRECATED: delete in next release and reorder keys + // ParamKey = []byte{0x00} // key for parameters relating to staking PoolKey = []byte{0x01} // key for the staking pools ValidatorsKey = []byte{0x02} // prefix for each key to a validator ValidatorsByConsAddrKey = []byte{0x03} // prefix for each key to a validator index, by pubkey @@ -28,6 +29,7 @@ var ( RedelegationByValDstIndexKey = []byte{0x0C} // prefix for each key for an redelegation, by destination validator operator UnbondingQueueKey = []byte{0x0D} // prefix for the timestamps in unbonding queue RedelegationQueueKey = []byte{0x0E} // prefix for the timestamps in redelegations queue + ValidatorQueueKey = []byte{0x0F} // prefix for the timestamps in validator queue ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -85,6 +87,12 @@ func getValidatorPowerRank(validator types.Validator) []byte { return key } +// gets the prefix for all unbonding delegations from a delegator +func GetValidatorQueueTimeKey(timestamp time.Time) []byte { + bz := types.MsgCdc.MustMarshalBinary(timestamp) + return append(ValidatorQueueKey, bz...) +} + //______________________________________________________________________________ // gets the key for delegator bond with validator diff --git a/x/stake/keeper/params.go b/x/stake/keeper/params.go new file mode 100644 index 0000000000..294c6dcd5a --- /dev/null +++ b/x/stake/keeper/params.go @@ -0,0 +1,78 @@ +package keeper + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// Default parameter namespace +const ( + DefaultParamspace = "stake" +) + +// ParamTable for stake module +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable().RegisterParamSet(&types.Params{}) +} + +// InflationRateChange - Maximum annual change in inflation rate +func (k Keeper) InflationRateChange(ctx sdk.Context) (res sdk.Dec) { + k.paramstore.Get(ctx, types.KeyInflationRateChange, &res) + return +} + +// InflationMax - Maximum inflation rate +func (k Keeper) InflationMax(ctx sdk.Context) (res sdk.Dec) { + k.paramstore.Get(ctx, types.KeyInflationMax, &res) + return +} + +// InflationMin - Minimum inflation rate +func (k Keeper) InflationMin(ctx sdk.Context) (res sdk.Dec) { + k.paramstore.Get(ctx, types.KeyInflationMin, &res) + return +} + +// GoalBonded - Goal of percent bonded atoms +func (k Keeper) GoalBonded(ctx sdk.Context) (res sdk.Dec) { + k.paramstore.Get(ctx, types.KeyGoalBonded, &res) + return +} + +// UnbondingTime +func (k Keeper) UnbondingTime(ctx sdk.Context) (res time.Duration) { + k.paramstore.Get(ctx, types.KeyUnbondingTime, &res) + return +} + +// MaxValidators - Maximum number of validators +func (k Keeper) MaxValidators(ctx sdk.Context) (res uint16) { + k.paramstore.Get(ctx, types.KeyMaxValidators, &res) + return +} + +// BondDenom - Bondable coin denomination +func (k Keeper) BondDenom(ctx sdk.Context) (res string) { + k.paramstore.Get(ctx, types.KeyBondDenom, &res) + return +} + +// Get all parameteras as types.Params +func (k Keeper) GetParams(ctx sdk.Context) (res types.Params) { + res.InflationRateChange = k.InflationRateChange(ctx) + res.InflationMax = k.InflationMax(ctx) + res.InflationMin = k.InflationMin(ctx) + res.GoalBonded = k.GoalBonded(ctx) + res.UnbondingTime = k.UnbondingTime(ctx) + res.MaxValidators = k.MaxValidators(ctx) + res.BondDenom = k.BondDenom(ctx) + return +} + +// set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramstore.SetParamSet(ctx, ¶ms) +} diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index dc57dc5dd4..a1562da33c 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -46,7 +46,7 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh } // should not be slashing unbonded - if validator.IsUnbonded(ctx) { + if validator.Status == sdk.Unbonded { panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) } diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index e41b8203b1..10bf2755d1 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -19,6 +19,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -90,12 +91,16 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") keyAcc := sdk.NewKVStoreKey("acc") + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) err := ms.LoadLatestVersion() require.Nil(t, err) @@ -106,8 +111,11 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context keyAcc, // target store auth.ProtoBaseAccount, // prototype ) + ck := bank.NewBaseKeeper(accountMapper) - keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, types.DefaultCodespace) + + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(DefaultParamspace), types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) keeper.SetParams(ctx, types.DefaultParams()) keeper.InitIntraTxCounter(ctx) @@ -116,7 +124,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context for _, addr := range Addrs { pool := keeper.GetPool(ctx) _, _, err := ck.AddCoins(ctx, addr, sdk.Coins{ - {keeper.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, + {keeper.BondDenom(ctx), sdk.NewInt(initCoins)}, }) require.Nil(t, err) pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(initCoins)) diff --git a/x/stake/keeper/val_state_change.go b/x/stake/keeper/val_state_change.go index f9cf6e463f..89d3f681fa 100644 --- a/x/stake/keeper/val_state_change.go +++ b/x/stake/keeper/val_state_change.go @@ -131,8 +131,9 @@ func (k Keeper) unbondedToBonded(ctx sdk.Context, validator types.Validator) typ return k.bondValidator(ctx, validator) } +// switches a validator from unbonding state to unbonded state func (k Keeper) unbondingToUnbonded(ctx sdk.Context, validator types.Validator) types.Validator { - if validator.Status != sdk.Unbonded { + if validator.Status != sdk.Unbonding { panic(fmt.Sprintf("bad state transition unbondingToBonded, validator: %v\n", validator)) } return k.completeUnbondingValidator(ctx, validator) @@ -213,6 +214,9 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat k.SetValidatorByPowerIndex(ctx, validator, pool) + // Adds to unbonding validator queue + k.InsertValidatorQueue(ctx, validator) + // call the unbond hook if present if k.hooks != nil { k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index b405312a1b..41cfe6faa6 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -3,6 +3,7 @@ package keeper import ( "container/list" "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -238,7 +239,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat store := ctx.KVStore(k.storeKey) // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators + maxValidators := k.MaxValidators(ctx) validators = make([]types.Validator, maxValidators) iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) @@ -263,7 +264,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat // get the group of bonded validators sorted by power-rank func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { store := ctx.KVStore(k.storeKey) - maxValidators := k.GetParams(ctx).MaxValidators + maxValidators := k.MaxValidators(ctx) validators := make([]types.Validator, maxValidators) iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) @@ -281,3 +282,69 @@ func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { } return validators[:i] // trim } + +// 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) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetValidatorQueueTimeKey(timestamp)) + if bz == nil { + return []sdk.ValAddress{} + } + k.cdc.MustUnmarshalBinary(bz, &valAddrs) + return valAddrs +} + +// Sets a specific validator queue timeslice. +func (k Keeper) SetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(keys) + store.Set(GetValidatorQueueTimeKey(timestamp), bz) +} + +// Insert an validator address to the appropriate timeslice in the validator queue +func (k Keeper) InsertValidatorQueue(ctx sdk.Context, val types.Validator) { + timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + if len(timeSlice) == 0 { + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, []sdk.ValAddress{val.OperatorAddr}) + } else { + timeSlice = append(timeSlice, val.OperatorAddr) + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, timeSlice) + } +} + +// Returns all the validator queue timeslices from time 0 until endTime +func (k Keeper) ValidatorQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + return store.Iterator(ValidatorQueueKey, sdk.InclusiveEndBytes(GetValidatorQueueTimeKey(endTime))) +} + +// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue +func (k Keeper) GetAllMatureValidatorQueue(ctx sdk.Context, currTime time.Time) (matureValsAddrs []sdk.ValAddress) { + // gets an iterator for all timeslices from time 0 until the current Blockheader time + validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) + for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { + timeslice := []sdk.ValAddress{} + k.cdc.MustUnmarshalBinary(validatorTimesliceIterator.Value(), ×lice) + matureValsAddrs = append(matureValsAddrs, timeslice...) + } + return matureValsAddrs +} + +// Unbonds all the unbonding validators that have finished their unbonding period +func (k Keeper) UnbondAllMatureValidatorQueue(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) + for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { + timeslice := []sdk.ValAddress{} + k.cdc.MustUnmarshalBinary(validatorTimesliceIterator.Value(), ×lice) + for _, valAddr := range timeslice { + val, found := k.GetValidator(ctx, valAddr) + if !found || val.GetStatus() != sdk.Unbonding { + continue + } + k.unbondingToUnbonded(ctx, val) + } + store.Delete(validatorTimesliceIterator.Key()) + } +} diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 0f637b51d6..6aa7801139 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock/simulation" + "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -23,7 +24,11 @@ func TestStakeWithRandomMessages(t *testing.T) { bankKeeper := bank.NewBaseKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") stakeTKey := sdk.NewTransientStoreKey("transient_stake") - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, stake.DefaultCodespace) + paramsKey := sdk.NewKVStoreKey("params") + paramsTKey := sdk.NewTransientStoreKey("transient_params") + + paramstore := params.NewKeeper(mapp.Cdc, paramsKey, paramsTKey).Subspace(stake.DefaultParamspace) + stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, paramstore, stake.DefaultCodespace) mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := stake.EndBlocker(ctx, stakeKeeper) @@ -32,7 +37,7 @@ func TestStakeWithRandomMessages(t *testing.T) { } }) - err := mapp.CompleteSetup(stakeKey, stakeTKey) + err := mapp.CompleteSetup(stakeKey, stakeTKey, paramsKey, paramsTKey) if err != nil { panic(err) } diff --git a/x/stake/stake.go b/x/stake/stake.go index 7bec2d9628..0baa468ba6 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -38,7 +38,6 @@ var ( GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey GetDelegationKey = keeper.GetDelegationKey GetDelegationsKey = keeper.GetDelegationsKey - ParamKey = keeper.ParamKey PoolKey = keeper.PoolKey ValidatorsKey = keeper.ValidatorsKey ValidatorsByConsAddrKey = keeper.ValidatorsByConsAddrKey @@ -58,6 +57,14 @@ var ( GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey + DefaultParamspace = keeper.DefaultParamspace + KeyInflationRateChange = types.KeyInflationRateChange + KeyInflationMax = types.KeyInflationMax + KeyGoalBonded = types.KeyGoalBonded + KeyUnbondingTime = types.KeyUnbondingTime + KeyMaxValidators = types.KeyMaxValidators + KeyBondDenom = types.KeyBondDenom + DefaultParams = types.DefaultParams InitialPool = types.InitialPool NewValidator = types.NewValidator @@ -79,6 +86,18 @@ var ( NewQuerier = querier.NewQuerier ) +const ( + QueryValidators = querier.QueryValidators + QueryValidator = querier.QueryValidator + QueryDelegator = querier.QueryDelegator + QueryDelegation = querier.QueryDelegation + QueryUnbondingDelegation = querier.QueryUnbondingDelegation + QueryDelegatorValidators = querier.QueryDelegatorValidators + QueryDelegatorValidator = querier.QueryDelegatorValidator + QueryPool = querier.QueryPool + QueryParameters = querier.QueryParameters +) + const ( DefaultCodespace = types.DefaultCodespace CodeInvalidValidator = types.CodeInvalidValidator diff --git a/x/stake/types/params.go b/x/stake/types/params.go index c3685f7c30..abc1db4d56 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" ) const ( @@ -21,6 +22,19 @@ const ( ValidatorUpdateDelay int64 = 1 ) +// nolint - Keys for parameter access +var ( + KeyInflationRateChange = []byte("InflationRateChange") + KeyInflationMax = []byte("InflationMax") + KeyInflationMin = []byte("InflationMin") + KeyGoalBonded = []byte("GoalBonded") + KeyUnbondingTime = []byte("UnbondingTime") + KeyMaxValidators = []byte("MaxValidators") + KeyBondDenom = []byte("BondDenom") +) + +var _ params.ParamSet = (*Params)(nil) + // Params defines the high level settings for staking type Params struct { InflationRateChange sdk.Dec `json:"inflation_rate_change"` // maximum annual change in inflation rate @@ -34,6 +48,19 @@ type Params struct { BondDenom string `json:"bond_denom"` // bondable coin denomination } +// Implements params.ParamSet +func (p *Params) KeyValuePairs() params.KeyValuePairs { + return params.KeyValuePairs{ + {KeyInflationRateChange, &p.InflationRateChange}, + {KeyInflationMax, &p.InflationMax}, + {KeyInflationMin, &p.InflationMin}, + {KeyGoalBonded, &p.GoalBonded}, + {KeyUnbondingTime, &p.UnbondingTime}, + {KeyMaxValidators, &p.MaxValidators}, + {KeyBondDenom, &p.BondDenom}, + } +} + // Equal returns a boolean determining if two Param types are identical. func (p Params) Equal(p2 Params) bool { bz1 := MsgCdc.MustMarshalBinary(&p) diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index dbd4e2a540..d774f761bd 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -435,21 +435,6 @@ func (v Validator) BondedTokens() sdk.Dec { return sdk.ZeroDec() } -// TODO remove this once the validator queue logic is implemented -// Returns if the validator should be considered unbonded -func (v Validator) IsUnbonded(ctx sdk.Context) bool { - switch v.Status { - case sdk.Unbonded: - return true - case sdk.Unbonding: - ctxTime := ctx.BlockHeader().Time - if ctxTime.After(v.UnbondingMinTime) { - return true - } - } - return false -} - //______________________________________________________________________ // ensure fulfills the sdk validator types