diff --git a/.circleci/config.yml b/.circleci/config.yml index 69ba3c4cc3..879df07f02 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -56,6 +56,7 @@ jobs: name: Get metalinter command: | export PATH="$GOBIN:$PATH" + make get_tools make get_dev_tools - run: name: Lint source diff --git a/Makefile b/Makefile index 5225076fa5..ec02f1402e 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/ve GCC := $(shell command -v gcc 2> /dev/null) LEDGER_ENABLED ?= true UNAME_S := $(shell uname -s) +GOTOOLS = \ + github.com/golang/dep/cmd/dep \ + github.com/alecthomas/gometalinter \ + github.com/rakyll/statik all: get_tools get_vendor_deps install install_examples install_cosmos-sdk-cli test_lint test ######################################## @@ -91,22 +95,27 @@ dist: ### Tools & dependencies check_tools: - cd tools && $(MAKE) check_tools - -check_dev_tools: - cd tools && $(MAKE) check_dev_tools + @# https://stackoverflow.com/a/25668869 + @echo "Found tools: $(foreach tool,$(notdir $(GOTOOLS)),\ + $(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))" update_tools: - cd tools && $(MAKE) update_tools + @echo "--> Updating tools to correct version" + ./scripts/get_tools.sh update_dev_tools: - cd tools && $(MAKE) update_dev_tools + @echo "--> Downloading linters (this may take awhile)" + $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) + go get -u github.com/tendermint/lint/golint get_tools: - cd tools && $(MAKE) get_tools + @echo "--> Installing tools" + ./scripts/get_tools.sh get_dev_tools: - cd tools && $(MAKE) get_dev_tools + @echo "--> Downloading linters (this may take awhile)" + $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) + go get github.com/tendermint/lint/golint get_vendor_deps: @echo "--> Generating vendor directory via dep ensure" @@ -181,8 +190,8 @@ test_cover: @export VERSION=$(VERSION); bash tests/test_cover.sh test_lint: - gometalinter.v2 --config=tools/gometalinter.json ./... - !(gometalinter.v2 --exclude /usr/lib/go/src/ --exclude client/lcd/statik/statik.go --disable-all --enable='errcheck' --vendor ./... | grep -v "client/") + gometalinter --config=tools/gometalinter.json ./... + !(gometalinter --exclude /usr/lib/go/src/ --exclude client/lcd/statik/statik.go --exclude 'vendor/*' --disable-all --enable='errcheck' --vendor ./... | grep -v "client/") find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s dep status >> /dev/null !(grep -n branch Gopkg.toml) diff --git a/PENDING.md b/PENDING.md index 1ced472c70..44ee631d61 100644 --- a/PENDING.md +++ b/PENDING.md @@ -44,7 +44,7 @@ BREAKING CHANGES * [simulation] \#2162 Added back correct supply invariants * [x/slashing] \#2430 Simulate more slashes, check if validator is jailed before jailing * [x/stake] \#2393 Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker - + * SDK * [core] \#2219 Update to Tendermint 0.24.0 * Validator set updates delayed by one block @@ -70,13 +70,14 @@ BREAKING CHANGES * [x/staking] \#2236 more distribution hooks for distribution * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock * [x/slashing] \#2480 Fix signing info handling bugs & faulty slashing + * [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 * Mempool now won't build too large blocks, or too computationally expensive blocks * Maximum tx sizes and gas are now removed, and are implicitly the blocks maximums * ABCI validators no longer send the pubkey. The pubkey is only sent in validator updates - * Validator set changes are now delayed by one block + * Validator set changes are now delayed by one block * Block header now includes the next validator sets hash * BFT time is implemented * Secp256k1 signature format has changed @@ -92,6 +93,7 @@ FEATURES * [gaia-lite] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`. * [gaia-lite] [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) Add /broadcast endpoint to broadcast transactions signed by the /sign endpoint. * [gaia-lite] [\#2113](https://github.com/cosmos/cosmos-sdk/issues/2113) Rename `/accounts/{address}/send` to `/bank/accounts/{address}/transfers`, rename `/accounts/{address}` to `/auth/accounts/{address}` + * [gaia-lite] [\#2478](https://github.com/cosmos/cosmos-sdk/issues/2478) Add query gov proposal's deposits endpoint * Gaia CLI (`gaiacli`) * [cli] Cmds to query staking pool and params @@ -171,6 +173,7 @@ IMPROVEMENTS calls which includes dynamic consumption of value length. * [types/decimal] \#2378 - Added truncate functionality to decimal * [client] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Remove unused `client/tx/sign.go`. + * [tools] \#2464 Lock binary dependencies to a specific version * Tendermint diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 1a7aa7ffeb..f675afe14d 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -10,19 +10,19 @@ import ( "testing" "time" + "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" + p2p "github.com/tendermint/tendermint/p2p" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" - p2p "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" - rpc "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/codec" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" @@ -613,7 +613,7 @@ func TestSubmitProposal(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -635,7 +635,7 @@ func TestDeposit(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -650,7 +650,7 @@ func TestDeposit(t *testing.T) { require.Equal(t, "Test", proposal.GetTitle()) // create SubmitProposal TX - resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID, 5) tests.WaitForHeight(resultTx.Height+1, port) // query proposal @@ -669,7 +669,7 @@ func TestVote(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -684,7 +684,7 @@ func TestVote(t *testing.T) { require.Equal(t, "Test", proposal.GetTitle()) // create SubmitProposal TX - resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID, 5) tests.WaitForHeight(resultTx.Height+1, port) // query proposal @@ -725,27 +725,52 @@ func TestProposalsQuery(t *testing.T) { defer cleanup() // Addr1 proposes (and deposits) proposals #1 and #2 - resultTx := doSubmitProposal(t, port, seed, name, password1, addr) + resultTx := doSubmitProposal(t, port, seed, name, password1, addr, 5) var proposalID1 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doSubmitProposal(t, port, seed, name, password1, addr) + resultTx = doSubmitProposal(t, port, seed, name, password1, addr, 5) var proposalID2 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 proposes (and deposits) proposals #3 - resultTx = doSubmitProposal(t, port, seed2, name2, password2, addr2) + resultTx = doSubmitProposal(t, port, seed2, name2, password2, addr2, 5) var proposalID3 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 deposits on proposals #2 & #3 - resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID2) + resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID2, 5) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3) + resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3, 5) tests.WaitForHeight(resultTx.Height+1, port) + // check deposits match proposal and individual deposits + deposits := getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + deposit := getDeposit(t, port, proposalID1, addr) + require.Equal(t, deposit, deposits[0]) + + deposits = getDeposits(t, port, proposalID2) + require.Len(t, deposits, 2) + deposit = getDeposit(t, port, proposalID2, addr) + require.Equal(t, deposit, deposits[0]) + deposit = getDeposit(t, port, proposalID2, addr2) + require.Equal(t, deposit, deposits[1]) + + deposits = getDeposits(t, port, proposalID3) + require.Len(t, deposits, 1) + deposit = getDeposit(t, port, proposalID3, addr2) + require.Equal(t, deposit, deposits[0]) + + // increasing the amount of the deposit should update the existing one + resultTx = doDeposit(t, port, seed, name, password1, addr, proposalID1, 1) + tests.WaitForHeight(resultTx.Height+1, port) + + deposits = getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + // Only proposals #1 should be in Deposit Period proposals := getProposalsFilterStatus(t, port, gov.StatusDepositPeriod) require.Len(t, proposals, 1) @@ -1168,6 +1193,15 @@ func getProposal(t *testing.T, port string, proposalID int64) gov.Proposal { return proposal } +func getDeposits(t *testing.T, port string, proposalID int64) []gov.Deposit { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var deposits []gov.Deposit + err := cdc.UnmarshalJSON([]byte(body), &deposits) + require.Nil(t, err) + return deposits +} + func getDeposit(t *testing.T, port string, proposalID int64, depositerAddr sdk.AccAddress) gov.Deposit { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits/%s", proposalID, depositerAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1245,7 +1279,7 @@ func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStat return proposals } -func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { +func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() @@ -1259,7 +1293,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA "description": "test", "proposal_type": "Text", "proposer": "%s", - "initial_deposit": [{ "denom": "steak", "amount": "5" }], + "initial_deposit": [{ "denom": "steak", "amount": "%d" }], "base_req": { "name": "%s", "password": "%s", @@ -1267,7 +1301,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA "account_number":"%d", "sequence":"%d" } - }`, proposerAddr, name, password, chainID, accnum, sequence)) + }`, proposerAddr, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", "/gov/proposals", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1278,7 +1312,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA return results } -func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() @@ -1289,7 +1323,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk // deposit on proposal jsonStr := []byte(fmt.Sprintf(`{ "depositer": "%s", - "amount": [{ "denom": "steak", "amount": "5" }], + "amount": [{ "denom": "steak", "amount": "%d" }], "base_req": { "name": "%s", "password": "%s", @@ -1297,7 +1331,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk "account_number":"%d", "sequence": "%d" } - }`, proposerAddr, name, password, chainID, accnum, sequence)) + }`, proposerAddr, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) 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/scripts/get_tools.sh b/scripts/get_tools.sh new file mode 100755 index 0000000000..79ab32deca --- /dev/null +++ b/scripts/get_tools.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -e + +# This file downloads all of the binary dependencies we have, and checks out a +# specific git hash. +# +# repos it installs: +# github.com/golang/dep/cmd/dep +# gopkg.in/alecthomas/gometalinter.v2 +# github.com/rakyll/statiik + +## check if GOPATH is set +if [ -z ${GOPATH+x} ]; then + echo "please set GOPATH (https://github.com/golang/go/wiki/SettingGOPATH)" + exit 1 +fi + +mkdir -p "$GOPATH/src/github.com" +cd "$GOPATH/src/github.com" || exit 1 + +installFromGithub() { + repo=$1 + commit=$2 + # optional + subdir=$3 + echo "--> Installing $repo ($commit)..." + if [ ! -d "$repo" ]; then + mkdir -p "$repo" + git clone "https://github.com/$repo.git" "$repo" + fi + if [ ! -z ${subdir+x} ] && [ ! -d "$repo/$subdir" ]; then + echo "ERROR: no such directory $repo/$subdir" + exit 1 + fi + pushd "$repo" && \ + git fetch origin && \ + git checkout -q "$commit" && \ + if [ ! -z ${subdir+x} ]; then cd "$subdir" || exit 1; fi && \ + go install && \ + if [ ! -z ${subdir+x} ]; then cd - || exit 1; fi && \ + popd || exit 1 + echo "--> Done" + echo "" +} + +installFromGithub golang/dep 22125cfaa6ddc71e145b1535d4b7ee9744fefff2 cmd/dep +## gometalinter v2.0.11 +installFromGithub alecthomas/gometalinter 17a7ffa42374937bfecabfb8d2efbd4db0c26741 +installFromGithub rakyll/statik v0.1.5 \ No newline at end of file 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/tools/Makefile b/tools/Makefile deleted file mode 100644 index 33fde3f4a9..0000000000 --- a/tools/Makefile +++ /dev/null @@ -1,170 +0,0 @@ -all: get_tools - - -######################################## -### DEP - -DEP = github.com/golang/dep/cmd/dep -GOLINT = github.com/tendermint/lint/golint -GOMETALINTER = gopkg.in/alecthomas/gometalinter.v2 -UNCONVERT = github.com/mdempsky/unconvert -INEFFASSIGN = github.com/gordonklaus/ineffassign -MISSPELL = github.com/client9/misspell/cmd/misspell -ERRCHECK = github.com/kisielk/errcheck -UNPARAM = mvdan.cc/unparam -STATIK = github.com/rakyll/statik - -DEP_CHECK := $(shell command -v dep 2> /dev/null) -GOLINT_CHECK := $(shell command -v golint 2> /dev/null) -GOMETALINTER_CHECK := $(shell command -v gometalinter.v2 2> /dev/null) -UNCONVERT_CHECK := $(shell command -v unconvert 2> /dev/null) -INEFFASSIGN_CHECK := $(shell command -v ineffassign 2> /dev/null) -MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null) -ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null) -UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null) -STATIK_CHECK := $(shell command -v statik 2> /dev/null) - -check_tools: -ifndef DEP_CHECK - @echo "No dep in path. Install with 'make get_tools'." -else - @echo "Found dep in path." -endif - -check_dev_tools: - $(MAKE) check_tools -ifndef GOLINT_CHECK - @echo "No golint in path. Install with 'make get_tools'." -else - @echo "Found golint in path." -endif -ifndef GOMETALINTER_CHECK - @echo "No gometalinter in path. Install with 'make get_tools'." -else - @echo "Found gometalinter in path." -endif -ifndef UNCONVERT_CHECK - @echo "No unconvert in path. Install with 'make get_tools'." -else - @echo "Found unconvert in path." -endif -ifndef INEFFASSIGN_CHECK - @echo "No ineffassign in path. Install with 'make get_tools'." -else - @echo "Found ineffassign in path." -endif -ifndef MISSPELL_CHECK - @echo "No misspell in path. Install with 'make get_tools'." -else - @echo "Found misspell in path." -endif -ifndef ERRCHECK_CHECK - @echo "No errcheck in path. Install with 'make get_tools'." -else - @echo "Found errcheck in path." -endif -ifndef UNPARAM_CHECK - @echo "No unparam in path. Install with 'make get_tools'." -else - @echo "Found unparam in path." -endif -ifndef STATIK_CHECK - @echo "No statik in path. Install with 'make get_tools'." -else - @echo "Found statik in path." -endif - - -get_tools: -ifdef DEP_CHECK - @echo "Dep is already installed. Run 'make update_tools' to update." -else - @echo "Installing dep" - go get -v $(DEP) -endif -ifdef STATIK_CHECK - @echo "Statik is already installed. Run 'make update_tools' to update." -else - @echo "Installing statik" - go version - go get -v $(STATIK) -endif - -get_dev_tools: - $(MAKE) get_tools -ifdef GOLINT_CHECK - @echo "Golint is already installed. Run 'make update_tools' to update." -else - @echo "Installing golint" - go get -v $(GOLINT) -endif -ifdef GOMETALINTER_CHECK - @echo "Gometalinter.v2 is already installed. Run 'make update_tools' to update." -else - @echo "Installing gometalinter.v2" - go get -v $(GOMETALINTER) -endif -ifdef UNCONVERT_CHECK - @echo "Unconvert is already installed. Run 'make update_tools' to update." -else - @echo "Installing unconvert" - go get -v $(UNCONVERT) -endif -ifdef INEFFASSIGN_CHECK - @echo "Ineffassign is already installed. Run 'make update_tools' to update." -else - @echo "Installing ineffassign" - go get -v $(INEFFASSIGN) -endif -ifdef MISSPELL_CHECK - @echo "misspell is already installed. Run 'make update_tools' to update." -else - @echo "Installing misspell" - go get -v $(MISSPELL) -endif -ifdef ERRCHECK_CHECK - @echo "errcheck is already installed. Run 'make update_tools' to update." -else - @echo "Installing errcheck" - go get -v $(ERRCHECK) -endif -ifdef UNPARAM_CHECK - @echo "unparam is already installed. Run 'make update_tools' to update." -else - @echo "Installing unparam" - go get -v $(UNPARAM) -endif -ifdef STATIK_CHECK - @echo "statik is already installed. Run 'make update_tools' to update." -else - @echo "Installing statik" - go get -v $(STATIK) -endif - -update_tools: - @echo "Updating dep" - go get -u -v $(DEP) - -update_dev_tools: - $(MAKE) update_tools - @echo "Updating tendermint/golint" - go get -u -v $(GOLINT) - @echo "Updating gometalinter.v2" - go get -u -v $(GOMETALINTER) - @echo "Updating unconvert" - go get -u -v $(UNCONVERT) - @echo "Updating ineffassign" - go get -u -v $(INEFFASSIGN) - @echo "Updating misspell" - go get -u -v $(MISSPELL) - @echo "Updating errcheck" - go get -u -v $(ERRCHECK) - @echo "Updating unparam" - go get -u -v $(UNPARAM) - @echo "Updating statik" - go get -u -v $(STATIK) - -# To avoid unintended conflicts with file names, always add to .PHONY -# unless there is a reason not to. -# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check_tools get_tools update_tools check_dev_tools get_dev_tools update_dev_tools diff --git a/tools/gometalinter.json b/tools/gometalinter.json index 1745cd9e0c..fe1084821b 100644 --- a/tools/gometalinter.json +++ b/tools/gometalinter.json @@ -6,5 +6,5 @@ "Deadline": "500s", "Vendor": true, "Cyclo": 11, - "Exclude": ["/usr/lib/go/src/", "client/lcd/statik/statik.go"] + "Exclude": ["/usr/lib/go/src/", "client/lcd/statik/statik.go", "vendor/*"] } 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/client/rest/rest.go b/x/gov/client/rest/rest.go index f66331ad5a..e7d9770e2c 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -31,13 +31,12 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc)).Methods("GET") - - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc)).Methods("GET") - - r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc)).Methods("GET") + r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc, cliCtx)).Methods("GET") } type postProposalReq struct { @@ -164,7 +163,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc } } -func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -180,8 +179,6 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryProposalParams{ ProposalID: proposalID, } @@ -198,11 +195,41 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } -func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + params := gov.QueryDepositsParams{ + ProposalID: proposalID, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, err := cliCtx.QueryWithData("custom/gov/deposits", bz) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -232,8 +259,6 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryDepositParams{ ProposalID: proposalID, Depositer: depositerAddr, @@ -265,11 +290,11 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } -func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -299,8 +324,6 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryVoteParams{ Voter: voterAddr, ProposalID: proposalID, @@ -335,12 +358,12 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -356,8 +379,6 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryVotesParams{ ProposalID: proposalID, } @@ -373,12 +394,12 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { +func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { bechVoterAddr := r.URL.Query().Get(RestVoter) bechDepositerAddr := r.URL.Query().Get(RestDepositer) @@ -430,20 +451,18 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := cliCtx.QueryWithData("custom/gov/proposals", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -461,8 +480,6 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryTallyParams{ ProposalID: proposalID, } @@ -480,6 +497,6 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } 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/queryable.go b/x/gov/queryable.go index 606f73a9ca..b0cb423749 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -118,7 +118,7 @@ type QueryDepositsParams struct { // nolint: unparam func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryDepositParams + var params QueryDepositsParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) 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 79092aebba..d993f8c200 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 23e30fe1ec..61cfbb15ca 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 2efc8dd847..7b1ee24bc8 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) amtInt := int64(100) @@ -67,7 +69,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()) amtInt := int64(100) operatorAddr, amt := addrs[0], sdk.NewInt(amtInt) valConsPubKey, valConsAddr := pks[0], pks[0].Address() @@ -132,7 +134,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()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) @@ -284,7 +286,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) @@ -323,7 +325,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) @@ -371,7 +373,7 @@ func TestHandleAlreadyJailed(t *testing.T) { func TestValidatorDippingInAndOut(t *testing.T) { // initial setup - ctx, _, sk, _, keeper := createTestInput(t) + ctx, _, sk, _, keeper := createTestInput(t, keeperTestParams()) params := sk.GetParams(ctx) params.MaxValidators = 1 sk.SetParams(ctx, params) 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 1b22d7461a..f5c8d6ea32 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()) missed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) require.False(t, missed) // treat empty key as not missed 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 25140c8240..81fc0e63da 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,9 +86,15 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para }) } require.Nil(t, err) - keeper := NewKeeper(cdc, keySlashing, sk, params.Getter(), DefaultCodespace) + paramstore := paramsKeeper.Subspace(DefaultParamspace) + keeper := NewKeeper(cdc, keySlashing, sk, paramstore, DefaultCodespace) sk = sk.WithHooks(keeper.Hooks()) - return ctx, ck, sk, params.Setter(), keeper + + 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 c85d4f4cb7..a13e8c6188 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