diff --git a/Gopkg.lock b/Gopkg.lock index b647d2021..d5ccc6589 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -679,7 +679,6 @@ "github.com/tendermint/tmlibs/cli", "github.com/zondax/ledger-goclient", "golang.org/x/crypto/blowfish", - "golang.org/x/crypto/ripemd160", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Makefile b/Makefile index 88ead03e6..a015e5a6b 100644 --- a/Makefile +++ b/Makefile @@ -157,11 +157,11 @@ test_sim_gaia_nondeterminism: test_sim_gaia_fast: @echo "Running quick Gaia simulation. This may take several minutes..." - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=200 -v -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=400 -SimulationBlockSize=200 -SimulationCommit=true -v -timeout 24h test_sim_gaia_slow: @echo "Running full Gaia simulation. This may take awhile!" - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -v -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -SimulationCommit=true -v -timeout 24h SIM_NUM_BLOCKS ?= 210 SIM_BLOCK_SIZE ?= 200 diff --git a/PENDING.md b/PENDING.md index 331d1fc85..33b60198c 100644 --- a/PENDING.md +++ b/PENDING.md @@ -33,6 +33,7 @@ BREAKING CHANGES renamed for accounts and validator operators: * `cosmosaccaddr` / `cosmosaccpub` => `cosmos` / `cosmospub` * `cosmosvaladdr` / `cosmosvalpub` => `cosmosvaloper` / `cosmosvaloperpub` + * [x/stake] [#1013] TendermintUpdates now uses transient store * SDK * [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal @@ -43,10 +44,10 @@ BREAKING CHANGES * [simulation] Remove log and testing.TB from Operation and Invariants, in favor of using errors \#2282 * [tools] Removed gocyclo [#2211](https://github.com/cosmos/cosmos-sdk/issues/2211) * [baseapp] Remove `SetTxDecoder` in favor of requiring the decoder be set in baseapp initialization. [#1441](https://github.com/cosmos/cosmos-sdk/issues/1441) + * [store] Change storeInfo within the root multistore to use tmhash instead of ripemd160 \#2308 * Tendermint - FEATURES * Gaia REST API (`gaiacli advanced rest-server`) @@ -61,8 +62,9 @@ FEATURES * [gov][cli] #2062 added `--proposal` flag to `submit-proposal` that allows a JSON file containing a proposal to be passed in * [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Add `--bech` to `gaiacli keys show` and respective REST endpoint to provide desired Bech32 prefix encoding - * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) Setting the --gas flag value to 0 triggers a simulation of the tx before the actual execution. The gas estimate obtained via the simulation will be used as gas limit in the actual execution. - * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0. + * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) [\#2306](https://github.com/cosmos/cosmos-sdk/pull/2306) Passing --gas=simulate triggers a simulation of the tx before the actual execution. + The gas estimate obtained via the simulation will be used as gas limit in the actual execution. + * [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=simulate. * [cli] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated. * [cli] [\#2204](https://github.com/cosmos/cosmos-sdk/issues/2204) Support generating and broadcasting messages with multiple signatures via command line: * [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT. @@ -97,6 +99,7 @@ IMPROVEMENTS * [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883). * [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200) * [genesis] \#2229 Ensure that there are no duplicate accounts or validators in the genesis state. + * Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) \#1571 * SDK * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. @@ -106,6 +109,9 @@ IMPROVEMENTS * [store] \#1952, \#2281 Update IAVL dependency to v0.11.0 * [simulation] Make timestamps randomized [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) * [simulation] Make logs not just pure strings, speeding it up by a large factor at greater block heights \#2282 + * [simulation] Add a concept of weighting the operations \#2303 + * [simulation] Logs get written to file if large, and also get printed on panics \#2285 + * [gaiad] \#1992 Add optional flag to `gaiad testnet` to make config directory of daemon (default `gaiad`) and cli (default `gaiacli`) configurable * Tendermint @@ -118,6 +124,8 @@ BUG FIXES * [cli] [\#2265](https://github.com/cosmos/cosmos-sdk/issues/2265) Fix JSON formatting of the `gaiacli send` command. * Gaia + * [x/stake] Return correct Tendermint validator update set on `EndBlocker` by not + including non previously bonded validators that have zero power. [#2189](https://github.com/cosmos/cosmos-sdk/issues/2189) * SDK * [\#1988](https://github.com/cosmos/cosmos-sdk/issues/1988) Make us compile on OpenBSD (disable ledger) [#1988] (https://github.com/cosmos/cosmos-sdk/issues/1988) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 5552ff784..10d9f55f7 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -120,13 +120,20 @@ func (app *BaseApp) RegisterCodespace(codespace sdk.CodespaceType) sdk.Codespace return app.codespacer.RegisterNext(codespace) } -// Mount a store to the provided key in the BaseApp multistore +// Mount IAVL stores to the provided keys in the BaseApp multistore func (app *BaseApp) MountStoresIAVL(keys ...*sdk.KVStoreKey) { for _, key := range keys { app.MountStore(key, sdk.StoreTypeIAVL) } } +// Mount stores to the provided keys in the BaseApp multistore +func (app *BaseApp) MountStoresTransient(keys ...*sdk.TransientStoreKey) { + for _, key := range keys { + app.MountStore(key, sdk.StoreTypeTransient) + } +} + // Mount a store to the provided key in the BaseApp multistore, using a specified DB func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) { app.cms.MountStoreWithDB(key, typ, db) diff --git a/client/context/context.go b/client/context/context.go index a4d4afd3b..3e785a28e 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -27,8 +27,6 @@ type CLIContext struct { Client rpcclient.Client Logger io.Writer Height int64 - Gas int64 - GasAdjustment float64 NodeURI string FromAddressName string AccountStore string @@ -58,8 +56,6 @@ func NewCLIContext() CLIContext { AccountStore: ctxAccStoreName, FromAddressName: viper.GetString(client.FlagFrom), Height: viper.GetInt64(client.FlagHeight), - Gas: viper.GetInt64(client.FlagGas), - GasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), TrustNode: viper.GetBool(client.FlagTrustNode), UseLedger: viper.GetBool(client.FlagUseLedger), Async: viper.GetBool(client.FlagAsync), @@ -164,9 +160,3 @@ func (ctx CLIContext) WithCertifier(certifier tmlite.Certifier) CLIContext { ctx.Certifier = certifier return ctx } - -// WithGasAdjustment returns a copy of the context with an updated GasAdjustment flag. -func (ctx CLIContext) WithGasAdjustment(adjustment float64) CLIContext { - ctx.GasAdjustment = adjustment - return ctx -} diff --git a/client/flags.go b/client/flags.go index 97fce42a5..c98898029 100644 --- a/client/flags.go +++ b/client/flags.go @@ -1,6 +1,11 @@ package client -import "github.com/spf13/cobra" +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" +) // nolint const ( @@ -9,6 +14,7 @@ const ( // occur between the tx simulation and the actual run. DefaultGasAdjustment = 1.0 DefaultGasLimit = 200000 + GasFlagSimulate = "simulate" FlagUseLedger = "ledger" FlagChainID = "chain-id" @@ -32,7 +38,10 @@ const ( // LineBreak can be included in a command list to provide a blank line // to help with readability -var LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} +var ( + LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} + GasFlagVar = GasSetting{Gas: DefaultGasLimit} +) // GetCommands adds common flags to query commands func GetCommands(cmds ...*cobra.Command) []*cobra.Command { @@ -58,7 +67,6 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") - c.Flags().Int64(FlagGas, DefaultGasLimit, "gas limit to set per-transaction; set to 0 to calculate required gas automatically") c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ") c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") c.Flags().Bool(FlagJson, false, "return output in json format") @@ -66,6 +74,50 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for query responses") c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it") c.Flags().Bool(FlagGenerateOnly, false, "build an unsigned transaction and write it to STDOUT") + // --gas can accept integers and "simulate" + c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf( + "gas limit to set per-transaction; set to %q to calculate required gas automatically (default %d)", GasFlagSimulate, DefaultGasLimit)) } return cmds } + +// Gas flag parsing functions + +// GasSetting encapsulates the possible values passed through the --gas flag. +type GasSetting struct { + Simulate bool + Gas int64 +} + +// Type returns the flag's value type. +func (v *GasSetting) Type() string { return "string" } + +// Set parses and sets the value of the --gas flag. +func (v *GasSetting) Set(s string) (err error) { + v.Simulate, v.Gas, err = ReadGasFlag(s) + return +} + +func (v *GasSetting) String() string { + if v.Simulate { + return GasFlagSimulate + } + return strconv.FormatInt(v.Gas, 10) +} + +// ParseGasFlag parses the value of the --gas flag. +func ReadGasFlag(s string) (simulate bool, gas int64, err error) { + switch s { + case "": + gas = DefaultGasLimit + case GasFlagSimulate: + simulate = true + default: + gas, err = strconv.ParseInt(s, 10, 64) + if err != nil { + err = fmt.Errorf("gas must be either integer or %q", GasFlagSimulate) + return + } + } + return +} diff --git a/client/keys/show.go b/client/keys/show.go index 9710cac11..c7cae1f7c 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -5,9 +5,7 @@ import ( "fmt" "net/http" - "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -19,7 +17,7 @@ const ( FlagAddress = "address" // FlagPublicKey represents the user's public key on the command line. FlagPublicKey = "pubkey" - // FlagBechPrefix defines a desired Bech32 prefix encoding for a key + // FlagBechPrefix defines a desired Bech32 prefix encoding for a key. FlagBechPrefix = "bech" ) @@ -29,39 +27,7 @@ func showKeysCmd() *cobra.Command { Short: "Show key info for the given name", Long: `Return public details of one local key.`, Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - name := args[0] - info, err := getKey(name) - if err != nil { - return err - } - - showAddress := viper.GetBool(FlagAddress) - showPublicKey := viper.GetBool(FlagPublicKey) - outputSet := cmd.Flag(cli.OutputFlag).Changed - - if showAddress && showPublicKey { - return errors.New("cannot use both --address and --pubkey at once") - } - if outputSet && (showAddress || showPublicKey) { - return errors.New("cannot use --output with --address or --pubkey") - } - - bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix)) - if err != nil { - return err - } - - switch { - case showAddress: - printKeyAddress(info, bechKeyOut) - case showPublicKey: - printPubKey(info, bechKeyOut) - default: - printKeyInfo(info, bechKeyOut) - } - return nil - }, + RunE: runShowCmd, } cmd.Flags().String(FlagBechPrefix, "acc", "The Bech32 prefix encoding for a key (acc|val|cons)") @@ -71,6 +37,43 @@ func showKeysCmd() *cobra.Command { return cmd } +func runShowCmd(cmd *cobra.Command, args []string) error { + name := args[0] + + info, err := GetKeyInfo(name) + if err != nil { + return err + } + + isShowAddr := viper.GetBool(FlagAddress) + isShowPubKey := viper.GetBool(FlagPublicKey) + isOutputSet := cmd.Flag(cli.OutputFlag).Changed + + if isShowAddr && isShowPubKey { + return errors.New("cannot use both --address and --pubkey at once") + } + + if isOutputSet && (isShowAddr || isShowPubKey) { + return errors.New("cannot use --output with --address or --pubkey") + } + + bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix)) + if err != nil { + return err + } + + switch { + case isShowAddr: + printKeyAddress(info, bechKeyOut) + case isShowPubKey: + printPubKey(info, bechKeyOut) + default: + printKeyInfo(info, bechKeyOut) + } + + return nil +} + func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { switch bechPrefix { case "acc": @@ -84,15 +87,6 @@ func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix) } -func getKey(name string) (keys.Info, error) { - kb, err := GetKeyBase() - if err != nil { - return nil, err - } - - return kb.Get(name) -} - /////////////////////////// // REST @@ -113,7 +107,7 @@ func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - info, err := getKey(name) + info, err := GetKeyInfo(name) // TODO: check for the error if key actually does not exist, instead of // assuming this as the reason if err != nil { diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 977c03cf7..b3c27bfba 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -267,21 +267,29 @@ func TestCoinSend(t *testing.T) { require.Equal(t, int64(1), mycoins.Amount.Int64()) // test failure with too little gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100, 0, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "100", 0, "") + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + + // test failure with negative gas + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "-200", 0, "") + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + + // test failure with 0 gas + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "0", 0, "") require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // test failure with wrong adjustment - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0.1, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "simulate", 0.1, "") require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // run simulation and test success with estimated gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?simulate=true") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, "", 0, "?simulate=true") require.Equal(t, http.StatusOK, res.StatusCode, body) var responseBody struct { GasEstimate int64 `json:"gas_estimate"` } require.Nil(t, json.Unmarshal([]byte(body), &responseBody)) - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, responseBody.GasEstimate, 0, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, fmt.Sprintf("%v", responseBody.GasEstimate), 0, "") require.Equal(t, http.StatusOK, res.StatusCode, body) } @@ -322,7 +330,7 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { acc := getAccount(t, port, addr) // generate TX - res, body, _ := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?generate_only=true") + res, body, _ := doSendWithGas(t, port, seed, name, password, addr, "simulate", 0, "?generate_only=true") require.Equal(t, http.StatusOK, res.StatusCode, body) var msg auth.StdTx require.Nil(t, cdc.UnmarshalJSON([]byte(body), &msg)) @@ -792,7 +800,7 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { return acc } -func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64, gasAdjustment float64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { +func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas string, gasAdjustment float64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := client.MockKeyBase() @@ -811,14 +819,14 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc } gasStr := "" - if gas > 0 { + if len(gas) != 0 { gasStr = fmt.Sprintf(` - "gas":"%v", + "gas":%q, `, gas) } gasAdjustmentStr := "" if gasAdjustment > 0 { - gasStr = fmt.Sprintf(` + gasAdjustmentStr = fmt.Sprintf(` "gas_adjustment":"%v", `, gasAdjustment) } @@ -837,7 +845,7 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc } func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { - res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "") + res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, "", 0, "") require.Equal(t, http.StatusOK, res.StatusCode, body) err := cdc.UnmarshalJSON([]byte(body), &resultTx) diff --git a/client/utils/utils.go b/client/utils/utils.go index 54d9dd584..52472ba1e 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -24,8 +24,8 @@ func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) if err != nil { return err } - autogas := cliCtx.DryRun || (cliCtx.Gas == 0) - if autogas { + + if txBldr.SimulateGas || cliCtx.DryRun { txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, cliCtx.FromAddressName, msgs) if err != nil { return err @@ -50,20 +50,10 @@ func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) return cliCtx.EnsureBroadcastTx(txBytes) } -// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. -func SimulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg, gas int64) (estimated, adjusted int64, err error) { - txBytes, err := txBldr.WithGas(gas).BuildWithPubKey(name, msgs) - if err != nil { - return - } - estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment) - return -} - // EnrichCtxWithGas calculates the gas estimate that would be consumed by the // transaction and set the transaction's respective value accordingly. func EnrichCtxWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authtxb.TxBuilder, error) { - _, adjusted, err := SimulateMsgs(txBldr, cliCtx, name, msgs, 0) + _, adjusted, err := simulateMsgs(txBldr, cliCtx, name, msgs) if err != nil { return txBldr, err } @@ -143,6 +133,16 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, return txBldr.SignStdTx(name, passphrase, stdTx, appendSig) } +// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. +func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (estimated, adjusted int64, err error) { + txBytes, err := txBldr.BuildWithPubKey(name, msgs) + if err != nil { + return + } + estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment) + return +} + func adjustGasEstimate(estimate int64, adjustment float64) int64 { return int64(adjustment * float64(estimate)) } @@ -194,7 +194,7 @@ func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg if err != nil { return } - if txBldr.Gas == 0 { + if txBldr.SimulateGas { txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, cliCtx.FromAddressName, msgs) if err != nil { return diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index c80da0bd0..7cf235b16 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -43,6 +43,7 @@ type GaiaApp struct { keyAccount *sdk.KVStoreKey keyIBC *sdk.KVStoreKey keyStake *sdk.KVStoreKey + tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey keyGov *sdk.KVStoreKey keyFeeCollection *sdk.KVStoreKey @@ -74,6 +75,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio keyAccount: sdk.NewKVStoreKey("acc"), keyIBC: sdk.NewKVStoreKey("ibc"), keyStake: sdk.NewKVStoreKey("stake"), + tkeyStake: sdk.NewTransientStoreKey("transient_stake"), keySlashing: sdk.NewKVStoreKey("slashing"), keyGov: sdk.NewKVStoreKey("gov"), keyFeeCollection: sdk.NewKVStoreKey("fee"), @@ -92,7 +94,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + 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.WithValidatorHooks(app.slashingKeeper.ValidatorHooks()) app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.bankKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace)) @@ -114,8 +116,9 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) - app.MountStore(app.tkeyParams, sdk.StoreTypeTransient) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, + app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) + app.MountStoresTransient(app.tkeyParams, app.tkeyStake) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index e809495fc..078eece11 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -89,19 +89,19 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json return appState } -func testAndRunTxs(app *GaiaApp) []simulation.Operation { - return []simulation.Operation{ - banksim.SimulateSingleInputMsgSend(app.accountMapper), - govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakeKeeper), - govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper), - stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgEditValidator(app.stakeKeeper), - stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper), - stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper), - slashingsim.SimulateMsgUnjail(app.slashingKeeper), +func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { + return []simulation.WeightedOperation{ + {100, banksim.SimulateSingleInputMsgSend(app.accountMapper)}, + {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakeKeeper)}, + {100, govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper)}, + {5, stakesim.SimulateMsgEditValidator(app.stakeKeeper)}, + {100, stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper)}, + {100, stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper)}, + {100, stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper)}, + {100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)}, } } @@ -131,7 +131,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { // Run randomized simulation // TODO parameterize numbers, save for a later PR - simulation.SimulateFromSeed( + err := simulation.SimulateFromSeed( b, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -140,6 +140,10 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { blockSize, commit, ) + if err != nil { + fmt.Println(err) + b.Fail() + } if commit { fmt.Println("GoLevelDB Stats") fmt.Println(db.Stats()["leveldb.stats"]) @@ -164,7 +168,7 @@ func TestFullGaiaSimulation(t *testing.T) { require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - simulation.SimulateFromSeed( + err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -176,6 +180,7 @@ func TestFullGaiaSimulation(t *testing.T) { if commit { fmt.Println("Database Size", db.Stats()["database.size"]) } + require.Nil(t, err) } // TODO: Make another test for the fuzzer itself, which just has noOp txs @@ -204,9 +209,9 @@ func TestAppStateDeterminism(t *testing.T) { []simulation.Invariant{}, 50, 100, - false, + true, ) - app.Commit() + //app.Commit() appHash := app.LastCommitID().Hash appHashList[j] = appHash } diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 032482e7b..43f35925e 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -112,8 +112,16 @@ func TestGaiaCLIGasAuto(t *testing.T) { fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) + // Test failure with negative gas + success = executeWrite(t, fmt.Sprintf("gaiacli send %v --gas=-100 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + require.False(t, success) + + // Test failure with 0 gas + success = executeWrite(t, fmt.Sprintf("gaiacli send %v --gas=0 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + require.False(t, success) + // Enable auto gas - success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli send %v --json --gas=0 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli send %v --json --gas=simulate --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.True(t, success) // check that gas wanted == gas used cdc := app.MakeCodec() @@ -381,7 +389,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx, estimate gas success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=0 --generate-only", + "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=simulate --generate-only", flags, barAddr), []string{}...) require.True(t, success) require.NotEmpty(t, stderr) diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index b2f1183b3..d0a0ad9d7 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -134,6 +134,7 @@ type GaiaApp struct { keyAccount *sdk.KVStoreKey keyIBC *sdk.KVStoreKey keyStake *sdk.KVStoreKey + tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey keyParams *sdk.KVStoreKey @@ -161,6 +162,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp keyAccount: sdk.NewKVStoreKey("acc"), keyIBC: sdk.NewKVStoreKey("ibc"), keyStake: sdk.NewKVStoreKey("stake"), + tkeyStake: sdk.NewTransientStoreKey("transient_stake"), keySlashing: sdk.NewKVStoreKey("slashing"), keyParams: sdk.NewKVStoreKey("params"), } @@ -176,7 +178,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) - app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + 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)) // register message routes diff --git a/docs/config.js b/docs/config.js index 93426bcc5..5de166179 100644 --- a/docs/config.js +++ b/docs/config.js @@ -57,7 +57,9 @@ module.exports = { { title: "Lotion JS", collapsable: false, - children: [["/lotion/overview", "Overview"], "/lotion/building-an-app"] + children: [ + ["/lotion/overview", "Overview"] + ] }, { title: "Validators", diff --git a/docs/light/api.md b/docs/light/api.md index 6c7f9de17..7fbf9fbe1 100644 --- a/docs/light/api.md +++ b/docs/light/api.md @@ -763,7 +763,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "chain_id": "string", "account_number": 0, "sequence": 0, - "gas": 0 + "gas": "simulate" }, "depositer": "string", "amount": 0, @@ -866,7 +866,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "chain_id": "string", "account_number": 0, "sequence": 0, - "gas": 0 + "gas": "simulate" }, // A cosmos address "voter": "string", diff --git a/docs/lotion/building-an-app.md b/docs/lotion/building-an-app.md deleted file mode 100644 index 93b3363c5..000000000 --- a/docs/lotion/building-an-app.md +++ /dev/null @@ -1,46 +0,0 @@ -# Building an App - -::: tip -Lotion requires __node v7.6.0__ or higher, and a mac or linux machine. -::: - -## Installation -``` -$ npm install lotion -``` - -## Simple App -`app.js`: -```js -let lotion = require('lotion') - -let app = lotion({ - initialState: { - count: 0 - } -}) - -app.use(function (state, tx) { - if(state.count === tx.nonce) { - state.count++ - } -}) - -app.listen(3000) -``` - -run `node app.js`, then: -```bash -$ curl http://localhost:3000/state -# { "count": 0 } - -$ curl http://localhost:3000/txs -d '{ "nonce": 0 }' -# { "ok": true } - -$ curl http://localhost:3000/state -# { "count": 1 } -``` - -## Learn More - -You can learn more about Lotion JS by visiting Lotion on [Github](https://github.com/keppel/lotion). diff --git a/docs/lotion/overview.md b/docs/lotion/overview.md index 03c79c571..b28d2c75b 100644 --- a/docs/lotion/overview.md +++ b/docs/lotion/overview.md @@ -1,5 +1,54 @@ -# Lotion JS Overview +# Overview -Lotion is a new way to create blockchain apps in JavaScript, which aims to make writing new blockchains fast and fun. It builds on top of Tendermint using the ABCI protocol. Lotion lets you write secure, scalable applications that can easily interoperate with other blockchains on the Cosmos Network using IBC. +Lotion is an alternative to the Cosmos SDK and allows you to create blockchain apps in JavaScript. It aims to make writing new blockchain apps fast and easy by using the ABCI protocol to build on top of Tendermint. Lotion lets you write secure, scalable applications that can easily interoperate with other blockchains on the Cosmos Network using IBC. Lotion itself is a tiny framework; its true power comes from the network of small, focused modules built upon it. Adding a fully-featured cryptocurrency to your blockchain, for example, takes only a few lines of code. + +For more information see the [website](https://lotionjs.com) and [GitHub repo](https://github.com/keppel/lotion), for complete documentation which expands on the following example. + +## Building an App + +### Installation + +::: tip +Lotion requires __node v7.6.0__ or higher, and a mac or linux machine. +::: + +``` +$ npm install lotion +``` + +### Simple App + +`app.js`: + +```js +let lotion = require('lotion') + +let app = lotion({ + initialState: { + count: 0 + } +}) + +app.use(function (state, tx) { + if(state.count === tx.nonce) { + state.count++ + } +}) + +app.listen(3000) +``` + +run `node app.js`, then: + +```bash +$ curl http://localhost:3000/state +# { "count": 0 } + +$ curl http://localhost:3000/txs -d '{ "nonce": 0 }' +# { "ok": true } + +$ curl http://localhost:3000/state +# { "count": 1 } +``` diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 4d02d3c90..5b7d3ca86 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -111,7 +111,7 @@ The `--amount` flag accepts the format `--amount=`. ::: tip Note You may want to cap the maximum gas that can be consumed by the transaction via the `--gas` flag. -If set to 0, the gas limit will be automatically estimated. +If you pass `--gas=simulate`, the gas limit will be automatically estimated. Gas estimate might be inaccurate as state changes could occur in between the end of the simulation and the actual execution of a transaction, thus an adjustment is applied on top of the original estimate in order to ensure the transaction is broadcasted successfully. The adjustment can be controlled via the `--gas-adjustment` flag, whose default value is 1.0. ::: diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index c2fe143ba..a29469261 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -12,7 +12,7 @@ the changes cleared ```golang EndBlock() ValidatorSetChanges - vsc = GetTendermintUpdates() + vsc = GetValidTendermintUpdates() ClearTendermintUpdates() return vsc ``` diff --git a/examples/README.md b/examples/README.md index 11aef9d28..5d872a7c9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -253,7 +253,7 @@ first time. Accounts are serialized and stored in a Merkle tree under the key ``base/a/
``, where ``
`` is the address of the account. -Typically, the address of the account is the 20-byte ``RIPEMD160`` hash +Typically, the address of the account is the first 20-bytes of the ``sha256`` hash of the public key, but other formats are acceptable as well, as defined in the `Tendermint crypto library `__. The Merkle tree diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go index 8d778045e..3349928eb 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -55,7 +55,7 @@ func getMockApp(t *testing.T) *mock.App { mapp.SetInitChainer(getInitChainer(mapp, keeper, "ice-cold")) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyCool})) + require.NoError(t, mapp.CompleteSetup(keyCool)) return mapp } diff --git a/examples/democoin/x/pow/app_test.go b/examples/democoin/x/pow/app_test.go index de8642e4c..0a4f95cf1 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/examples/democoin/x/pow/app_test.go @@ -32,7 +32,7 @@ func getMockApp(t *testing.T) *mock.App { mapp.SetInitChainer(getInitChainer(mapp, keeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyPOW})) + require.NoError(t, mapp.CompleteSetup(keyPOW)) mapp.Seal() diff --git a/server/testnet.go b/server/testnet.go index 93f563005..951e378d1 100644 --- a/server/testnet.go +++ b/server/testnet.go @@ -18,9 +18,11 @@ import ( ) var ( - nodeDirPrefix = "node-dir-prefix" - nValidators = "v" - outputDir = "output-dir" + nodeDirPrefix = "node-dir-prefix" + nValidators = "v" + outputDir = "output-dir" + nodeDaemonHome = "node-daemon-home" + nodeCliHome = "node-cli-home" startingIPAddress = "starting-ip-address" ) @@ -39,7 +41,7 @@ Note, strict routability for addresses is turned off in the config file. Example: - gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 + gaiad testnet --v 4 --o ./output --starting-ip-address 192.168.10.2 `, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config @@ -53,6 +55,10 @@ Example: "Directory to store initialization data for the testnet") cmd.Flags().String(nodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)") + cmd.Flags().String(nodeDaemonHome, "gaiad", + "Home directory of the node's daemon configuration") + cmd.Flags().String(nodeCliHome, "gaiacli", + "Home directory of the node's cli configuration") cmd.Flags().String(startingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") @@ -66,8 +72,10 @@ func testnetWithConfig(config *cfg.Config, cdc *wire.Codec, appInit AppInit) err // Generate private key, node ID, initial transaction for i := 0; i < numValidators; i++ { nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDir := filepath.Join(outDir, nodeDirName, "gaiad") - clientDir := filepath.Join(outDir, nodeDirName, "gaiacli") + nodeDaemonHomeName := viper.GetString(nodeDaemonHome) + nodeCliHomeName := viper.GetString(nodeCliHome) + nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) + clientDir := filepath.Join(outDir, nodeDirName, nodeCliHomeName) gentxsDir := filepath.Join(outDir, "gentxs") config.SetRoot(nodeDir) @@ -122,7 +130,8 @@ func testnetWithConfig(config *cfg.Config, cdc *wire.Codec, appInit AppInit) err for i := 0; i < numValidators; i++ { nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDir := filepath.Join(outDir, nodeDirName, "gaiad") + nodeDaemonHomeName := viper.GetString(nodeDaemonHome) + nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) gentxsDir := filepath.Join(outDir, "gentxs") initConfig := InitConfig{ chainID, diff --git a/server/util.go b/server/util.go index 04c539eb4..6aff52965 100644 --- a/server/util.go +++ b/server/util.go @@ -51,6 +51,10 @@ func PersistentPreRunEFn(context *Context) func(*cobra.Command, []string) error if err != nil { return err } + err = validateConfig(config) + if err != nil { + return err + } logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) if err != nil { @@ -96,6 +100,14 @@ func interceptLoadConfig() (conf *cfg.Config, err error) { return } +// validate the config with the sdk's requirements. +func validateConfig(conf *cfg.Config) error { + if conf.Consensus.CreateEmptyBlocks == false { + return errors.New("config option CreateEmptyBlocks = false is currently unsupported") + } + return nil +} + // add server commands func AddCommands( ctx *Context, cdc *wire.Codec, diff --git a/store/multistoreproof.go b/store/multistoreproof.go index e25f1cc1f..d62bc4aca 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -2,6 +2,7 @@ package store import ( "bytes" + "github.com/pkg/errors" "github.com/tendermint/iavl" cmn "github.com/tendermint/tendermint/libs/common" @@ -47,7 +48,6 @@ func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHas Version: height, StoreInfos: storeInfos, } - if !bytes.Equal(appHash, ci.Hash()) { return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash") } diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 790588fb2..45a102cd3 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -5,13 +5,14 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tendermint/iavl" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/db" ) func TestVerifyMultiStoreCommitInfo(t *testing.T) { - appHash, _ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84") + appHash, _ := hex.DecodeString("69959B1B4E68E0F7BD3551A50C8F849B81801AF2") substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") storeName := "acc" @@ -83,13 +84,13 @@ func TestVerifyMultiStoreCommitInfo(t *testing.T) { }) commitHash, err := VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) - assert.Nil(t, err) - assert.Equal(t, commitHash, substoreRootHash) + require.Nil(t, err) + require.Equal(t, commitHash, substoreRootHash) appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") _, err = VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) - assert.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") + require.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") } func TestVerifyRangeProof(t *testing.T) { diff --git a/store/rootmultistore.go b/store/rootmultistore.go index d9cf8a29a..8aa2da0ba 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -5,10 +5,9 @@ import ( "io" "strings" - "golang.org/x/crypto/ripemd160" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/crypto/tmhash" dbm "github.com/tendermint/tendermint/libs/db" sdk "github.com/cosmos/cosmos-sdk/types" @@ -424,7 +423,7 @@ func (si storeInfo) Hash() []byte { // Doesn't write Name, since merkle.SimpleHashFromMap() will // include them via the keys. bz, _ := cdc.MarshalBinary(si.Core) // Does not error - hasher := ripemd160.New() + hasher := tmhash.New() _, err := hasher.Write(bz) if err != nil { // TODO: Handle with #870 diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index 026eabece..6daa75e12 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -16,7 +16,9 @@ type TxBuilder struct { Codec *wire.Codec AccountNumber int64 Sequence int64 - Gas int64 + Gas int64 // TODO: should this turn into uint64? requires further discussion - see #2173 + GasAdjustment float64 + SimulateGas bool ChainID string Memo string Fee string @@ -36,9 +38,11 @@ func NewTxBuilderFromCLI() TxBuilder { return TxBuilder{ ChainID: chainID, - Gas: viper.GetInt64(client.FlagGas), AccountNumber: viper.GetInt64(client.FlagAccountNumber), + Gas: client.GasFlagVar.Gas, + GasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), Sequence: viper.GetInt64(client.FlagSequence), + SimulateGas: client.GasFlagVar.Simulate, Fee: viper.GetString(client.FlagFee), Memo: viper.GetString(client.FlagMemo), } diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index 998d4e4bc..2a3cd5e66 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -19,7 +19,7 @@ func getBenchmarkMockApp() (*mock.App, error) { bankKeeper := NewBaseKeeper(mapp.AccountMapper) mapp.Router().AddRoute("bank", NewHandler(bankKeeper)) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + err := mapp.CompleteSetup() return mapp, err } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 82df24642..02a66f2b9 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -32,7 +32,7 @@ type sendBody struct { ChainID string `json:"chain_id"` AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` + Gas string `json:"gas"` GasAdjustment string `json:"gas_adjustment"` } @@ -81,31 +81,37 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo return } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: m.Gas, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, + simulateGas, gas, err := cliclient.ReadGasFlag(m.Gas) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return } adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, cliclient.DefaultGasAdjustment) if !ok { return } - cliCtx = cliCtx.WithGasAdjustment(adjustment) + txBldr := authtxb.TxBuilder{ + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: m.ChainID, + AccountNumber: m.AccountNumber, + Sequence: m.Sequence, + } - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || txBldr.SimulateGas { + newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) + utils.WriteSimulationResponse(w, newBldr.Gas) return } - txBldr = newCtx + txBldr = newBldr } if utils.HasGenerateOnlyArg(r) { diff --git a/x/bank/simulation/sim_test.go b/x/bank/simulation/sim_test.go index f0279abef..cd2353c6e 100644 --- a/x/bank/simulation/sim_test.go +++ b/x/bank/simulation/sim_test.go @@ -21,7 +21,7 @@ func TestBankWithRandomMessages(t *testing.T) { bankKeeper := bank.NewBaseKeeper(mapper) mapp.Router().AddRoute("bank", bank.NewHandler(bankKeeper)) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + err := mapp.CompleteSetup() if err != nil { panic(err) } @@ -33,8 +33,8 @@ func TestBankWithRandomMessages(t *testing.T) { simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateSingleInputMsgSend(mapper), + []simulation.WeightedOperation{ + {1, SimulateSingleInputMsgSend(mapper)}, }, []simulation.RandSetup{}, []simulation.Invariant{ diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go index f9987c030..b152cb20b 100644 --- a/x/gov/client/rest/util.go +++ b/x/gov/client/rest/util.go @@ -20,7 +20,7 @@ type baseReq struct { ChainID string `json:"chain_id"` AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` + Gas string `json:"gas"` GasAdjustment string `json:"gas_adjustment"` } @@ -69,32 +69,37 @@ func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { // TODO: Build this function out into a more generic base-request // (probably should live in client/lcd). func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { - var err error - txBldr := authtxb.TxBuilder{ - Codec: cdc, - AccountNumber: baseReq.AccountNumber, - Sequence: baseReq.Sequence, - ChainID: baseReq.ChainID, - Gas: baseReq.Gas, + simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return } adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } - cliCtx = cliCtx.WithGasAdjustment(adjustment) + txBldr := authtxb.TxBuilder{ + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: baseReq.ChainID, + AccountNumber: baseReq.AccountNumber, + Sequence: baseReq.Sequence, + } - if utils.HasDryRunArg(r) || baseReq.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || txBldr.SimulateGas { + newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) + utils.WriteSimulationResponse(w, newBldr.Gas) return } - txBldr = newCtx + txBldr = newBldr } if utils.HasGenerateOnlyArg(r) { diff --git a/x/gov/handler.go b/x/gov/handler.go index 6424bb0a1..c99674926 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -114,8 +114,14 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { resTags.AppendTag(tags.Action, tags.ActionProposalDropped) resTags.AppendTag(tags.ProposalID, proposalIDBytes) - logger.Info(fmt.Sprintf("Proposal %d - \"%s\" - didn't mean minimum deposit (had only %s), deleted", - inactiveProposal.GetProposalID(), inactiveProposal.GetTitle(), inactiveProposal.GetTotalDeposit())) + logger.Info( + fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %v steak (had only %v steak); deleted", + inactiveProposal.GetProposalID(), + inactiveProposal.GetTitle(), + keeper.GetDepositProcedure(ctx).MinDeposit.AmountOf("steak"), + inactiveProposal.GetTotalDeposit().AmountOf("steak"), + ), + ) } // Check if earliest Active Proposal ended voting period yet @@ -143,7 +149,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { activeProposal.SetTallyResult(tallyResults) keeper.SetProposal(ctx, activeProposal) - logger.Info(fmt.Sprintf("Proposal %d - \"%s\" - tallied, passed: %v", + logger.Info(fmt.Sprintf("proposal %d (%s) tallied; passed: %v", activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)) for _, valAddr := range nonVotingVals { @@ -154,7 +160,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { val.GetPower().RoundInt64(), keeper.GetTallyingProcedure(ctx).GovernancePenalty) - logger.Info(fmt.Sprintf("Validator %s failed to vote on proposal %d, slashing", + logger.Info(fmt.Sprintf("validator %s failed to vote on proposal %d; slashing", val.GetOperator(), activeProposal.GetProposalID())) } diff --git a/x/gov/queryable.go b/x/gov/queryable.go index 090b9a914..6aa0c9884 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -8,22 +8,33 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) +// query endpoints supported by the governance Querier +const ( + QueryProposals = "proposals" + QueryProposal = "proposal" + QueryDeposits = "deposits" + QueryDeposit = "deposit" + QueryVotes = "votes" + QueryVote = "vote" + QueryTally = "tally" +) + func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { switch path[0] { - case "proposal": - return queryProposal(ctx, path[1:], req, keeper) - case "deposit": - return queryDeposit(ctx, path[1:], req, keeper) - case "vote": - return queryVote(ctx, path[1:], req, keeper) - case "deposits": - return queryDeposits(ctx, path[1:], req, keeper) - case "votes": - return queryVotes(ctx, path[1:], req, keeper) - case "proposals": + case QueryProposals: return queryProposals(ctx, path[1:], req, keeper) - case "tally": + case QueryProposal: + return queryProposal(ctx, path[1:], req, keeper) + case QueryDeposits: + return queryDeposits(ctx, path[1:], req, keeper) + case QueryDeposit: + return queryDeposit(ctx, path[1:], req, keeper) + case QueryVotes: + return queryVotes(ctx, path[1:], req, keeper) + case QueryVote: + return queryVote(ctx, path[1:], req, keeper) + case QueryTally: return queryTally(ctx, path[1:], req, keeper) default: return nil, sdk.ErrUnknownRequest("unknown gov query endpoint") diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 399f73512..3eb21b79a 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -156,13 +156,16 @@ func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { return operationSimulateMsgVote(k, sk, nil, -1) } + // nolint: unparam func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, proposalID int64) simulation.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { if key == nil { key = simulation.RandomKey(r, keys) } + var ok bool + if proposalID < 0 { proposalID, ok = randomProposalID(r, k, ctx) if !ok { @@ -171,15 +174,18 @@ func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, } addr := sdk.AccAddress(key.PubKey().Address()) option := randomVotingOption(r) + msg := gov.NewMsgVote(addr, proposalID, option) if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } + ctx, write := ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) if result.IsOK() { write() } + event(fmt.Sprintf("gov/MsgVote/%v", result.IsOK())) action = fmt.Sprintf("TestMsgVote: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index b7f0be580..841b4c0f7 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -26,7 +26,8 @@ func TestGovWithRandomMessages(t *testing.T) { mapper := mapp.AccountMapper bankKeeper := bank.NewBaseKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, bankKeeper, stake.DefaultCodespace) + 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) govKey := sdk.NewKVStoreKey("gov") @@ -37,7 +38,7 @@ func TestGovWithRandomMessages(t *testing.T) { return abci.ResponseEndBlock{} }) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey, paramKey, govKey}) + err := mapp.CompleteSetup(stakeKey, stakeTKey, paramKey, govKey) if err != nil { panic(err) } @@ -56,10 +57,10 @@ func TestGovWithRandomMessages(t *testing.T) { // Test with unscheduled votes simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateMsgSubmitProposal(govKeeper, stakeKeeper), - SimulateMsgDeposit(govKeeper, stakeKeeper), - SimulateMsgVote(govKeeper, stakeKeeper), + []simulation.WeightedOperation{ + {2, SimulateMsgSubmitProposal(govKeeper, stakeKeeper)}, + {3, SimulateMsgDeposit(govKeeper, stakeKeeper)}, + {20, SimulateMsgVote(govKeeper, stakeKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ @@ -71,9 +72,9 @@ func TestGovWithRandomMessages(t *testing.T) { // Test with scheduled votes simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper), - SimulateMsgDeposit(govKeeper, stakeKeeper), + []simulation.WeightedOperation{ + {10, SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper)}, + {5, SimulateMsgDeposit(govKeeper, stakeKeeper)}, }, []simulation.RandSetup{ setup, }, []simulation.Invariant{ diff --git a/x/gov/test_common.go b/x/gov/test_common.go index a0d556521..f2f3625e6 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -28,18 +28,19 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, keyGlobalParams := sdk.NewKVStoreKey("params") keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") keyGov := sdk.NewKVStoreKey("gov") pk := params.NewKeeper(mapp.Cdc, keyGlobalParams) ck := bank.NewBaseKeeper(mapp.AccountMapper) - sk := stake.NewKeeper(mapp.Cdc, keyStake, ck, mapp.RegisterCodespace(stake.DefaultCodespace)) + sk := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, ck, mapp.RegisterCodespace(stake.DefaultCodespace)) keeper := NewKeeper(mapp.Cdc, keyGov, pk.Setter(), ck, sk, DefaultCodespace) mapp.Router().AddRoute("gov", NewHandler(keeper)) mapp.SetEndBlocker(getEndBlocker(keeper)) mapp.SetInitChainer(getInitChainer(mapp, keeper, sk)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake, keyGov, keyGlobalParams})) + require.NoError(t, mapp.CompleteSetup(keyStake, keyGov, keyGlobalParams, tkeyStake)) genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewInt64Coin("steak", 42)}) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 07c3616ce..e92e71a8c 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -24,7 +24,7 @@ func getMockApp(t *testing.T) *mock.App { bankKeeper := bank.NewBaseKeeper(mapp.AccountMapper) mapp.Router().AddRoute("ibc", NewHandler(ibcMapper, bankKeeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyIBC})) + require.NoError(t, mapp.CompleteSetup(keyIBC)) return mapp } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 110efe601..5a7b632e8 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -29,7 +29,7 @@ type transferBody struct { SrcChainID string `json:"src_chain_id"` AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` + Gas string `json:"gas"` GasAdjustment string `json:"gas_adjustment"` } @@ -71,21 +71,26 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C packet := ibc.NewIBCPacket(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount, m.SrcChainID, destChainID) msg := ibc.IBCTransferMsg{packet} - txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: m.SrcChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - Gas: m.Gas, + simulateGas, gas, err := client.ReadGasFlag(m.Gas) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } - cliCtx = cliCtx.WithGasAdjustment(adjustment) + txBldr := authtxb.TxBuilder{ + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: m.SrcChainID, + AccountNumber: m.AccountNumber, + Sequence: m.Sequence, + } - if utils.HasDryRunArg(r) || m.Gas == 0 { + if utils.HasDryRunArg(r) || txBldr.SimulateGas { newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) diff --git a/x/mock/app.go b/x/mock/app.go index 97068a3a5..ebfb97ae5 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -1,6 +1,7 @@ package mock import ( + "fmt" "math/rand" "os" @@ -75,11 +76,21 @@ func NewApp() *App { // CompleteSetup completes the application setup after the routes have been // registered. -func (app *App) CompleteSetup(newKeys []*sdk.KVStoreKey) error { +func (app *App) CompleteSetup(newKeys ...sdk.StoreKey) error { newKeys = append(newKeys, app.KeyMain) newKeys = append(newKeys, app.KeyAccount) - app.MountStoresIAVL(newKeys...) + for _, key := range newKeys { + switch key.(type) { + case *sdk.KVStoreKey: + app.MountStore(key, sdk.StoreTypeIAVL) + case *sdk.TransientStoreKey: + app.MountStore(key, sdk.StoreTypeTransient) + default: + return fmt.Errorf("unsupported StoreKey: %+v", key) + } + } + err := app.LoadLatestVersion(app.KeyMain) return err diff --git a/x/mock/app_test.go b/x/mock/app_test.go index 460757a04..1319482ca 100644 --- a/x/mock/app_test.go +++ b/x/mock/app_test.go @@ -41,7 +41,7 @@ func getMockApp(t *testing.T) *App { mApp := NewApp() mApp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) - require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{})) + require.NoError(t, mApp.CompleteSetup()) return mApp } diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 08b70d101..de78ae094 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -7,6 +7,7 @@ import ( "math/rand" "os" "os/signal" + "runtime/debug" "sort" "strings" "syscall" @@ -23,12 +24,13 @@ import ( ) // Simulate tests application by sending random messages. -func Simulate( - t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, ops []Operation, setups []RandSetup, - invariants []Invariant, numBlocks int, blockSize int, commit bool, -) { +func Simulate(t *testing.T, app *baseapp.BaseApp, + appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, + ops []WeightedOperation, setups []RandSetup, + invariants []Invariant, numBlocks int, blockSize int, commit bool) error { + time := time.Now().UnixNano() - SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) + return SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) } func initChain(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress, setups []RandSetup, app *baseapp.BaseApp, @@ -53,10 +55,13 @@ func randTimestamp(r *rand.Rand) time.Time { // SimulateFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. -func SimulateFromSeed( - tb testing.TB, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup, - invariants []Invariant, numBlocks int, blockSize int, commit bool, -) { +func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, + appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, + seed int64, ops []WeightedOperation, setups []RandSetup, invariants []Invariant, + numBlocks int, blockSize int, commit bool) (simError error) { + + // in case we have to end early, don't os.Exit so that we can run cleanup code. + stopEarly := false testingMode, t, b := getTestingMode(tb) fmt.Printf("Starting SimulateFromSeed with randomness created with seed %d\n", int(seed)) r := rand.New(rand.NewSource(seed)) @@ -79,12 +84,12 @@ func SimulateFromSeed( // Setup code to catch SIGTERM's c := make(chan os.Signal) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) go func() { - <-c - fmt.Printf("Exiting early due to SIGTERM, on block %d, operation %d\n", header.Height, opCount) - DisplayEvents(events) - os.Exit(128 + int(syscall.SIGTERM)) + receivedSignal := <-c + fmt.Printf("Exiting early due to %s, on block %d, operation %d\n", receivedSignal, header.Height, opCount) + simError = fmt.Errorf("Exited due to %s", receivedSignal) + stopEarly = true }() var pastTimes []time.Time @@ -95,15 +100,27 @@ func SimulateFromSeed( operationQueue := make(map[int][]Operation) var blockLogBuilders []*strings.Builder - if !testingMode { - b.ResetTimer() - } else { + if testingMode { blockLogBuilders = make([]*strings.Builder, numBlocks) } displayLogs := logPrinter(testingMode, blockLogBuilders) blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, numBlocks, displayLogs) + if !testingMode { + b.ResetTimer() + } else { + // Recover logs in case of panic + defer func() { + if r := recover(); r != nil { + fmt.Println("Panic with err\n", r) + stackTrace := string(debug.Stack()) + fmt.Println(stackTrace) + displayLogs() + simError = fmt.Errorf("Simulation halted due to panic on block %d", header.Height) + } + }() + } - for i := 0; i < numBlocks; i++ { + for i := 0; i < numBlocks && !stopEarly; i++ { // Log the header time for future lookup pastTimes = append(pastTimes, header.Time) pastSigningValidators = append(pastSigningValidators, request.LastCommitInfo.Validators) @@ -122,10 +139,9 @@ func SimulateFromSeed( // Run queued operations. Ignores blocksize if blocksize is too small numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, logWriter, displayLogs, event) - opCount += numQueuedOpsRan thisBlockSize -= numQueuedOpsRan operations := blockSimulator(thisBlockSize, r, app, ctx, keys, header, logWriter) - opCount += operations + opCount += operations + numQueuedOpsRan res := app.EndBlock(abci.RequestEndBlock{}) header.Height++ @@ -146,19 +162,38 @@ func SimulateFromSeed( // Update the validator set validators = updateValidators(tb, r, validators, res.ValidatorUpdates, event) } - + if stopEarly { + DisplayEvents(events) + return + } fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount) DisplayEvents(events) + return nil } // Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize // memory overhead -func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []Operation, operationQueue map[int][]Operation, totalNumBlocks int, displayLogs func()) func( +func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []WeightedOperation, operationQueue map[int][]Operation, totalNumBlocks int, displayLogs func()) func( blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, header abci.Header, logWriter func(string)) (opCount int) { + totalOpWeight := 0 + for i := 0; i < len(ops); i++ { + totalOpWeight += ops[i].Weight + } + selectOp := func(r *rand.Rand) Operation { + x := r.Intn(totalOpWeight) + for i := 0; i < len(ops); i++ { + if x <= ops[i].Weight { + return ops[i].Op + } + x -= ops[i].Weight + } + // shouldn't happen + return ops[0].Op + } return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, header abci.Header, logWriter func(string)) (opCount int) { for j := 0; j < blocksize; j++ { - logUpdate, futureOps, err := ops[r.Intn(len(ops))](r, app, ctx, keys, event) + logUpdate, futureOps, err := selectOp(r)(r, app, ctx, keys, event) if err != nil { displayLogs() tb.Fatalf("error on operation %d within block %d, %v", header.Height, opCount, err) @@ -324,18 +359,14 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, // updateValidators mimicks Tendermint's update logic // nolint: unparam func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValidator, updates []abci.Validator, event func(string)) map[string]mockValidator { + for _, update := range updates { switch { case update.Power == 0: - // // TEMPORARY DEBUG CODE TO PROVE THAT THE OLD METHOD WAS BROKEN - // // (i.e. didn't catch in the event of problem) - // if val, ok := tb.(*testing.T); ok { - // require.NotNil(val, current[string(update.PubKey.Data)]) - // } - // // CORRECT CHECK - // if _, ok := current[string(update.PubKey.Data)]; !ok { - // tb.Fatalf("tried to delete a nonexistent validator") - // } + if _, ok := current[string(update.PubKey.Data)]; !ok { + tb.Fatalf("tried to delete a nonexistent validator") + } + event("endblock/validatorupdates/kicked") delete(current, string(update.PubKey.Data)) default: @@ -350,5 +381,6 @@ func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValida } } } + return current } diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 25fb1a6e8..401899efe 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -47,6 +47,13 @@ type ( BlockHeight int Op Operation } + + // WeightedOperation is an operation with associated weight. + // This is used to bias the selection operation within the simulator. + WeightedOperation struct { + Weight int + Op Operation + } ) // PeriodicInvariant returns an Invariant function closure that asserts diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index d4e1ca1a7..a46d6dd54 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -3,9 +3,11 @@ package simulation import ( "fmt" "math/rand" + "os" "sort" "strings" "testing" + "time" "github.com/tendermint/tendermint/crypto" @@ -94,13 +96,34 @@ func assertAllInvariants(t *testing.T, app *baseapp.BaseApp, invariants []Invari func logPrinter(testingmode bool, logs []*strings.Builder) func() { if testingmode { return func() { + numLoggers := 0 for i := 0; i < len(logs); i++ { // We're passed the last created block if logs[i] == nil { - return + numLoggers = i - 1 + break + } + } + var f *os.File + if numLoggers > 10 { + fileName := fmt.Sprintf("simulation_log_%s.txt", time.Now().Format("2006-01-02 15:04:05")) + fmt.Printf("Too many logs to display, instead writing to %s\n", fileName) + f, _ = os.Create(fileName) + } + for i := 0; i < numLoggers; i++ { + if f != nil { + _, err := f.WriteString(fmt.Sprintf("Begin block %d\n", i)) + if err != nil { + panic("Failed to write logs to file") + } + _, err = f.WriteString((*logs[i]).String()) + if err != nil { + panic("Failed to write logs to file") + } + } else { + fmt.Printf("Begin block %d\n", i) + fmt.Println((*logs[i]).String()) } - fmt.Printf("Begin block %d\n", i) - fmt.Println((*logs[i]).String()) } } } diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 4ceeac94d..90e8137e2 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -26,11 +26,12 @@ func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { RegisterWire(mapp.Cdc) 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, bankKeeper, mapp.RegisterCodespace(stake.DefaultCodespace)) + stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, tkeyStake, bankKeeper, mapp.RegisterCodespace(stake.DefaultCodespace)) keeper := NewKeeper(mapp.Cdc, keySlashing, stakeKeeper, paramsKeeper.Getter(), mapp.RegisterCodespace(DefaultCodespace)) mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) @@ -38,7 +39,7 @@ func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { mapp.SetEndBlocker(getEndBlocker(stakeKeeper)) mapp.SetInitChainer(getInitChainer(mapp, stakeKeeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake, keySlashing, keyParams})) + require.NoError(t, mapp.CompleteSetup(keyStake, keySlashing, keyParams, tkeyStake)) return mapp, stakeKeeper, keeper } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 1d4fdefa1..969cdcce0 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -70,22 +70,20 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI return } + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } txBldr := authtxb.TxBuilder{ Codec: cdc, ChainID: m.ChainID, AccountNumber: m.AccountNumber, Sequence: m.Sequence, Gas: m.Gas, + GasAdjustment: adjustment, } msg := slashing.NewMsgUnjail(valAddr) - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - cliCtx = cliCtx.WithGasAdjustment(adjustment) - if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index b10c640f5..a21930ad9 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -52,11 +52,13 @@ func createTestCodec() *wire.Codec { func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, params.Setter, Keeper) { keyAcc := sdk.NewKVStoreKey("acc") keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") keySlashing := sdk.NewKVStoreKey("slashing") keyParams := sdk.NewKVStoreKey("params") db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) @@ -67,7 +69,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount) ck := bank.NewBaseKeeper(accountMapper) params := params.NewKeeper(cdc, keyParams) - sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) + sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, stake.DefaultCodespace) genesis := stake.DefaultGenesisState() genesis.Pool.LooseTokens = sdk.NewDec(initCoins.MulRaw(int64(len(addrs))).Int64()) diff --git a/x/stake/app_test.go b/x/stake/app_test.go index b5cf90433..a787e5e4d 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -34,14 +34,15 @@ func getMockApp(t *testing.T) (*mock.App, Keeper) { RegisterWire(mApp.Cdc) keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") bankKeeper := bank.NewBaseKeeper(mApp.AccountMapper) - keeper := NewKeeper(mApp.Cdc, keyStake, bankKeeper, mApp.RegisterCodespace(DefaultCodespace)) + keeper := NewKeeper(mApp.Cdc, keyStake, tkeyStake, bankKeeper, mApp.RegisterCodespace(DefaultCodespace)) mApp.Router().AddRoute("stake", NewHandler(keeper)) mApp.SetEndBlocker(getEndBlocker(keeper)) mApp.SetInitChainer(getInitChainer(mApp, keeper)) - require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{keyStake})) + require.NoError(t, mApp.CompleteSetup(keyStake, tkeyStake)) return mApp, keeper } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index d84a8daea..fda92f85a 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -60,7 +60,7 @@ type EditDelegationsBody struct { ChainID string `json:"chain_id"` AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` + Gas string `json:"gas"` GasAdjustment string `json:"gas_adjustment"` Delegations []msgDelegationsInput `json:"delegations"` BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` @@ -263,10 +263,21 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex i++ } + simulateGas, gas, err := client.ReadGasFlag(m.Gas) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: m.ChainID, - Gas: m.Gas, + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: m.ChainID, } // sign messages @@ -275,26 +286,19 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex // increment sequence for each message txBldr = txBldr.WithAccountNumber(m.AccountNumber) txBldr = txBldr.WithSequence(m.Sequence) - m.Sequence++ - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - cliCtx = cliCtx.WithGasAdjustment(adjustment) - - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || txBldr.SimulateGas { + newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) + utils.WriteSimulationResponse(w, newBldr.Gas) return } - txBldr = newCtx + txBldr = newBldr } if utils.HasGenerateOnlyArg(r) { diff --git a/x/stake/handler.go b/x/stake/handler.go index e6ceb5e7b..e7641393d 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -52,8 +52,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid k.SetIntraTxCounter(ctx, 0) // calculate validator set changes - ValidatorUpdates = k.GetTendermintUpdates(ctx) - k.ClearTendermintUpdates(ctx) + ValidatorUpdates = k.GetValidTendermintUpdates(ctx) return } diff --git a/x/stake/keeper/_store.md b/x/stake/keeper/_store.md index 818b17ac0..5c070b9e0 100644 --- a/x/stake/keeper/_store.md +++ b/x/stake/keeper/_store.md @@ -3,6 +3,7 @@ This document provided a bit more insight as to the purpose of several related prefixed areas of the staking store which are accessed in `x/stake/keeper.go`. +# IAVL Store ## Validators - Prefix Key Space: ValidatorsKey @@ -36,10 +37,13 @@ prefixed areas of the staking store which are accessed in `x/stake/keeper.go`. through this set to determine who we've kicked out. retrieving validator by tendermint index +# Transient Store + +The transient store persists between transations but not between blocks + ## Tendermint Updates - - Prefix Key Space: TendermintUpdatesKey + - Prefix Key Space: TendermintUpdatesTKey - Key/Sort: Validator Operator Address - Value: Tendermint ABCI Validator - Contains: Validators are queued to affect the consensus validation set in Tendermint - - Used For: Informing Tendermint of the validator set updates, is used only intra-block, as the - updates are applied then cleared on endblock + - Used For: Informing Tendermint of the validator set updates diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index c928c9744..cbf6e39e4 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -11,6 +11,7 @@ import ( // keeper of the stake store type Keeper struct { storeKey sdk.StoreKey + storeTKey sdk.StoreKey cdc *wire.Codec bankKeeper bank.Keeper validatorHooks sdk.ValidatorHooks @@ -19,9 +20,10 @@ type Keeper struct { codespace sdk.CodespaceType } -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *wire.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ storeKey: key, + storeTKey: tkey, cdc: cdc, bankKeeper: ck, validatorHooks: nil, diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 9dbf50c03..c445e2552 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -22,14 +22,16 @@ var ( ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator ValidatorPowerCliffKey = []byte{0x07} // key for the power of the validator on the cliff - TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated - IntraTxCounterKey = []byte{0x09} // key for intra-block tx index - DelegationKey = []byte{0x0A} // key for a delegation - UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation - UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator operator - RedelegationKey = []byte{0x0D} // key for a redelegation - RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by source validator operator - RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by destination validator operator + IntraTxCounterKey = []byte{0x08} // key for intra-block tx index + DelegationKey = []byte{0x09} // key for a delegation + UnbondingDelegationKey = []byte{0x0A} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x0B} // prefix for each key for an unbonding-delegation, by validator operator + RedelegationKey = []byte{0x0C} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x0D} // prefix for each key for an redelegation, by source validator operator + RedelegationByValDstIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by destination validator operator + + // Keys for store prefixes (transient) + TendermintUpdatesTKey = []byte{0x00} // prefix for each key to a validator which is being updated ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -98,8 +100,8 @@ func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { // get the key for the accumulated update validators // VALUE: abci.Validator // note records using these keys should never persist between blocks -func GetTendermintUpdatesKey(operatorAddr sdk.ValAddress) []byte { - return append(TendermintUpdatesKey, operatorAddr.Bytes()...) +func GetTendermintUpdatesTKey(operatorAddr sdk.ValAddress) []byte { + return append(TendermintUpdatesTKey, operatorAddr.Bytes()...) } //______________________________________________________________________________ diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index 5a004e807..52dbd21c7 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -28,7 +28,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in logger := ctx.Logger().With("module", "x/stake") if slashFactor.LT(sdk.ZeroDec()) { - panic(fmt.Errorf("attempted to slash with a negative slashFactor: %v", slashFactor)) + panic(fmt.Errorf("attempted to slash with a negative slash factor: %v", slashFactor)) } // Amount of slashing = slash slashFactor * power at time of infraction @@ -50,7 +50,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // should not be slashing unbonded if validator.IsUnbonded(ctx) { - panic(fmt.Sprintf("should not be slashing unbonded validator: %v", validator)) + panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) } operatorAddress := validator.GetOperator() @@ -72,7 +72,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Special-case slash at current height for efficiency - we don't need to look through unbonding delegations or redelegations logger.Info(fmt.Sprintf( - "Slashing at current height %d, not scanning unbonding delegations & redelegations", + "slashing at current height %d, not scanning unbonding delegations & redelegations", infractionHeight)) case infractionHeight < ctx.BlockHeight(): @@ -117,8 +117,8 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Log that a slash occurred! logger.Info(fmt.Sprintf( - "Validator %s slashed by slashFactor %s, burned %v tokens", - pubkey.Address(), slashFactor.String(), tokensToBurn)) + "validator %s slashed by slash factor of %s; burned %v tokens", + validator.GetOperator(), slashFactor.String(), tokensToBurn)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return @@ -127,8 +127,12 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // jail a validator func (k Keeper) Jail(ctx sdk.Context, pubkey crypto.PubKey) { k.setJailed(ctx, pubkey, true) + validatorAddr, err := sdk.ValAddressFromHex(pubkey.Address().String()) + if err != nil { + panic(err.Error()) + } logger := ctx.Logger().With("module", "x/stake") - logger.Info(fmt.Sprintf("Validator %s jailed", pubkey.Address())) + logger.Info(fmt.Sprintf("validator %s jailed", validatorAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } @@ -136,8 +140,12 @@ func (k Keeper) Jail(ctx sdk.Context, pubkey crypto.PubKey) { // unjail a validator func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { k.setJailed(ctx, pubkey, false) + validatorAddr, err := sdk.ValAddressFromHex(pubkey.Address().String()) + if err != nil { + panic(err.Error()) + } logger := ctx.Logger().With("module", "x/stake") - logger.Info(fmt.Sprintf("Validator %s unjailed", pubkey.Address())) + logger.Info(fmt.Sprintf("validator %s unjailed", validatorAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } @@ -146,7 +154,7 @@ func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, isJailed bool) { validator, found := k.GetValidatorByPubKey(ctx, pubkey) if !found { - panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, isJailed)) + panic(fmt.Errorf("validator with pubkey %s not found, cannot set jailed to %v", pubkey, isJailed)) } validator.Jailed = isJailed k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index 247ff262b..8ebded7bf 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -90,10 +90,12 @@ func ParamsNoInflation() types.Params { func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") keyAcc := sdk.NewKVStoreKey("acc") 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) err := ms.LoadLatestVersion() @@ -107,7 +109,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context auth.ProtoBaseAccount, // prototype ) ck := bank.NewBaseKeeper(accountMapper) - keeper := NewKeeper(cdc, keyStake, ck, types.DefaultCodespace) + keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) keeper.SetNewParams(ctx, types.DefaultParams()) keeper.InitIntraTxCounter(ctx) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index d2411de72..1f65aaa95 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -158,9 +158,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat } address := GetAddressFromValBondedIndexKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } + ensureValidatorFound(found, address) validators[i] = validator i++ @@ -184,9 +182,8 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { } address := iterator.Value() validator, found := k.GetValidator(ctx, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } + ensureValidatorFound(found, address) + if validator.Status == sdk.Bonded { validators[i] = validator i++ @@ -201,32 +198,41 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { // Accumulated updates to the active/bonded validator set for tendermint // get the most recently updated validators -func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { - store := ctx.KVStore(k.storeKey) +// +// CONTRACT: Only validators with non-zero power or zero-power that were bonded +// at the previous block height or were removed from the validator set entirely +// are returned to Tendermint. +func (k Keeper) GetValidTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { + tstore := ctx.TransientStore(k.storeTKey) - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest + iterator := sdk.KVStorePrefixIterator(tstore, TendermintUpdatesTKey) for ; iterator.Valid(); iterator.Next() { - valBytes := iterator.Value() - var val abci.Validator - k.cdc.MustUnmarshalBinary(valBytes, &val) - updates = append(updates, val) + var abciVal abci.Validator + + abciValBytes := iterator.Value() + k.cdc.MustUnmarshalBinary(abciValBytes, &abciVal) + + val, found := k.GetValidator(ctx, abciVal.GetAddress()) + if found { + // The validator is new or already exists in the store and must adhere to + // Tendermint invariants. + prevBonded := val.BondHeight < ctx.BlockHeight() && val.BondHeight > val.UnbondingHeight + zeroPower := val.GetPower().Equal(sdk.ZeroDec()) + + if !zeroPower || zeroPower && prevBonded { + updates = append(updates, abciVal) + } + } else { + // Add the ABCI validator in such a case where the validator was removed + // from the store as it must have existed before. + updates = append(updates, abciVal) + } } + iterator.Close() return } -// remove all validator update entries after applied to Tendermint -func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - - // delete subspace - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) - for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) - } - iterator.Close() -} - //___________________________________________________________________________ // Perform all the necessary steps for when a validator changes its power. This @@ -237,15 +243,20 @@ func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { // nolint: gocyclo // TODO: Remove above nolint, function needs to be simplified! func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { - store := ctx.KVStore(k.storeKey) + tstore := ctx.TransientStore(k.storeTKey) pool := k.GetPool(ctx) oldValidator, oldFound := k.GetValidator(ctx, validator.OperatorAddr) validator = k.updateForJailing(ctx, oldFound, oldValidator, validator) powerIncreasing := k.getPowerIncreasing(ctx, oldFound, oldValidator, validator) - validator.BondHeight, validator.BondIntraTxCounter = k.bondIncrement(ctx, oldFound, oldValidator, validator) + validator.BondHeight, validator.BondIntraTxCounter = k.bondIncrement(ctx, oldFound, oldValidator) valPower := k.updateValidatorPower(ctx, oldFound, oldValidator, validator, pool) cliffPower := k.GetCliffValidatorPower(ctx) + cliffValExists := (cliffPower != nil) + var valPowerLTcliffPower bool + if cliffValExists { + valPowerLTcliffPower = (bytes.Compare(valPower, cliffPower) == -1) + } switch { @@ -257,9 +268,9 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type (oldFound && oldValidator.Status == sdk.Bonded): bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bz) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bz) - if cliffPower != nil { + if cliffValExists { cliffAddr := sdk.ValAddress(k.GetCliffValidator(ctx)) if bytes.Equal(cliffAddr, validator.OperatorAddr) { k.updateCliffValidator(ctx, validator) @@ -267,14 +278,13 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type } // if is a new validator and the new power is less than the cliff validator - case cliffPower != nil && !oldFound && - bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower + case cliffValExists && !oldFound && valPowerLTcliffPower: // skip to completion // if was unbonded and the new power is less than the cliff validator - case cliffPower != nil && + case cliffValExists && (oldFound && oldValidator.Status == sdk.Unbonded) && - bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower + valPowerLTcliffPower: //(valPower < cliffPower // skip to completion default: @@ -293,7 +303,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type // if decreased in power but still bonded, update Tendermint validator if oldFound && oldValidator.BondedTokens().GT(validator.BondedTokens()) { bz := k.cdc.MustMarshalBinary(validator.ABCIValidator()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bz) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bz) } } @@ -315,7 +325,7 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato oldCliffVal, found := k.GetValidator(ctx, cliffAddr) if !found { - panic(fmt.Sprintf("cliff validator record not found for address: %v\n", cliffAddr)) + panic(fmt.Sprintf("cliff validator record not found for address: %X\n", cliffAddr)) } // Create a validator iterator ranging from smallest to largest by power @@ -327,12 +337,10 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato if iterator.Valid() { ownerAddr := iterator.Value() currVal, found := k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) if currVal.Status != sdk.Bonded || currVal.Jailed { - panic(fmt.Sprintf("unexpected jailed or unbonded validator for address: %s\n", ownerAddr)) + panic(fmt.Sprintf("unexpected jailed or unbonded validator for address: %X\n", ownerAddr)) } newCliffVal = currVal @@ -345,13 +353,10 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato newCliffValRank := GetValidatorsByPowerIndexKey(newCliffVal, pool) if bytes.Equal(affectedVal.OperatorAddr, newCliffVal.OperatorAddr) { - // The affected validator remains the cliff validator, however, since // the store does not contain the new power, update the new power rank. store.Set(ValidatorPowerCliffKey, affectedValRank) - } else if bytes.Compare(affectedValRank, newCliffValRank) > 0 { - // The affected validator no longer remains the cliff validator as it's // power is greater than the new cliff validator. k.setCliffValidator(ctx, newCliffVal, pool) @@ -382,18 +387,20 @@ func (k Keeper) getPowerIncreasing(ctx sdk.Context, oldFound bool, oldValidator, // get the bond height and incremented intra-tx counter // nolint: unparam -func (k Keeper) bondIncrement(ctx sdk.Context, oldFound bool, oldValidator, - newValidator types.Validator) (height int64, intraTxCounter int16) { +func (k Keeper) bondIncrement( + ctx sdk.Context, found bool, oldValidator types.Validator) (height int64, intraTxCounter int16) { - // if already a validator, copy the old block height and counter, else set them - if oldFound && oldValidator.Status == sdk.Bonded { + // if already a validator, copy the old block height and counter + if found && oldValidator.Status == sdk.Bonded { height = oldValidator.BondHeight intraTxCounter = oldValidator.BondIntraTxCounter return } + height = ctx.BlockHeight() counter := k.GetIntraTxCounter(ctx) intraTxCounter = counter + k.SetIntraTxCounter(ctx, counter+1) return } @@ -454,29 +461,25 @@ func (k Keeper) UpdateBondedValidators( } else { var found bool validator, found = k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) } // if we've reached jailed validators no further bonded validators exist if validator.Jailed { - break - } - - // increment bondedValidatorsCount / get the validator to bond - if validator.Status != sdk.Bonded { - validatorToBond = validator - if newValidatorBonded { - panic("already decided to bond a validator, can't bond another!") + if validator.Status == sdk.Bonded { + panic(fmt.Sprintf("jailed validator cannot be bonded, address: %X\n", ownerAddr)) } - newValidatorBonded = true + + break } // increment the total number of bonded validators and potentially mark // the validator to bond if validator.Status != sdk.Bonded { validatorToBond = validator + if newValidatorBonded { + panic("already decided to bond a validator, can't bond another!") + } newValidatorBonded = true } @@ -502,9 +505,7 @@ func (k Keeper) UpdateBondedValidators( if newValidatorBonded { if oldCliffValidatorAddr != nil { oldCliffVal, found := k.GetValidator(ctx, oldCliffValidatorAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) - } + ensureValidatorFound(found, oldCliffValidatorAddr) if bytes.Equal(validatorToBond.OperatorAddr, affectedValidator.OperatorAddr) { @@ -512,7 +513,6 @@ func (k Keeper) UpdateBondedValidators( // validator was newly bonded and has greater power k.beginUnbondingValidator(ctx, oldCliffVal) } else { - // otherwise begin unbonding the affected validator, which must // have been kicked out affectedValidator = k.beginUnbondingValidator(ctx, affectedValidator) @@ -560,9 +560,7 @@ func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { ownerAddr := iterator.Value() validator, found = k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) _, found = toKickOut[string(ownerAddr)] if found { @@ -605,9 +603,7 @@ func kickOutValidators(k Keeper, ctx sdk.Context, toKickOut map[string]byte) { for key := range toKickOut { ownerAddr := []byte(key) validator, found := k.GetValidator(ctx, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } + ensureValidatorFound(found, ownerAddr) k.beginUnbondingValidator(ctx, validator) } } @@ -637,7 +633,8 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat // add to accumulated changes for tendermint bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bzABCI) + tstore := ctx.TransientStore(k.storeTKey) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) // also remove from the Bonded types.Validators Store store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) @@ -662,6 +659,8 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator)) } + validator.BondHeight = ctx.BlockHeight() + // set the status validator, pool = validator.UpdateStatus(pool, sdk.Bonded) k.SetPool(ctx, pool) @@ -672,7 +671,8 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. // add to accumulated changes for tendermint bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator()) - store.Set(GetTendermintUpdatesKey(validator.OperatorAddr), bzABCI) + tstore := ctx.TransientStore(k.storeTKey) + tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) // call the bond hook if present if k.validatorHooks != nil { @@ -707,7 +707,8 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero()) - store.Set(GetTendermintUpdatesKey(address), bz) + tstore := ctx.TransientStore(k.storeTKey) + tstore.Set(GetTendermintUpdatesTKey(address), bz) } //__________________________________________________________________________ @@ -738,3 +739,9 @@ func (k Keeper) clearCliffValidator(ctx sdk.Context) { store.Delete(ValidatorPowerCliffKey) store.Delete(ValidatorCliffIndexKey) } + +func ensureValidatorFound(found bool, ownerAddr []byte) { + if !found { + panic(fmt.Sprintf("validator record not found for address: %X\n", ownerAddr)) + } +} diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 87fbcbc65..89dd40677 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -6,18 +6,34 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" - tmtypes "github.com/tendermint/tendermint/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// for testing, remove all validator update entries after applied to Tendermint +func clearTendermintUpdates(ctx sdk.Context, k Keeper) { + store := ctx.TransientStore(k.storeTKey) + + // delete subspace + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesTKey) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) + } + iterator.Close() +} + +//_______________________________________________________ + func TestSetValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 10) pool := keeper.GetPool(ctx) + valPubKey := PKs[0] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + // test how the validator is set from a purely unbonbed pool - validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator := types.NewValidator(valAddr, valPubKey, types.Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, sdk.Unbonded, validator.Status) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) @@ -26,14 +42,14 @@ func TestSetValidator(t *testing.T) { keeper.UpdateValidator(ctx, validator) // after the save the validator should be bonded - validator, found := keeper.GetValidator(ctx, addrVals[0]) + validator, found := keeper.GetValidator(ctx, valAddr) require.True(t, found) require.Equal(t, sdk.Bonded, validator.Status) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) // Check each store for being saved - resVal, found := keeper.GetValidator(ctx, addrVals[0]) + resVal, found := keeper.GetValidator(ctx, valAddr) assert.True(ValEq(t, validator, resVal)) require.True(t, found) @@ -45,7 +61,7 @@ func TestSetValidator(t *testing.T) { require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validator, resVals[0])) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validator.ABCIValidator(), updates[0]) } @@ -633,67 +649,35 @@ func TestFullValidatorSetPowerChange(t *testing.T) { assert.True(ValEq(t, validators[2], resValidators[1])) } -// clear the tracked changes to the gotValidator set -func TestClearTendermintUpdates(t *testing.T) { - ctx, _, keeper := CreateTestInput(t, false, 1000) - - amts := []int64{100, 400, 200} - validators := make([]types.Validator, len(amts)) - for i, amt := range amts { - pool := keeper.GetPool(ctx) - validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) - validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) - keeper.SetPool(ctx, pool) - keeper.UpdateValidator(ctx, validators[i]) - } - - updates := keeper.GetTendermintUpdates(ctx) - require.Equal(t, len(amts), len(updates)) - keeper.ClearTendermintUpdates(ctx) - updates = keeper.GetTendermintUpdates(ctx) - require.Equal(t, 0, len(updates)) -} - -func TestGetTendermintUpdatesAllNone(t *testing.T) { +func TestGetValidTendermintUpdatesAllNone(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} var validators [2]types.Validator for i, amt := range amts { pool := keeper.GetPool(ctx) - validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{}) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } // test from nothing to something // tendermintUpdate set: {} -> {c1, c3} - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) assert.Equal(t, 2, len(updates)) assert.Equal(t, validators[0].ABCIValidator(), updates[0]) assert.Equal(t, validators[1].ABCIValidator(), updates[1]) - - // test from something to nothing - // tendermintUpdate set: {} -> {c1, c2, c3, c4} - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) - - keeper.RemoveValidator(ctx, validators[0].OperatorAddr) - keeper.RemoveValidator(ctx, validators[1].OperatorAddr) - - updates = keeper.GetTendermintUpdates(ctx) - assert.Equal(t, 2, len(updates)) - assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].ConsPubKey), updates[0].PubKey) - assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].ConsPubKey), updates[1].PubKey) - assert.Equal(t, int64(0), updates[0].Power) - assert.Equal(t, int64(0), updates[1].Power) } -func TestGetTendermintUpdatesIdentical(t *testing.T) { +func TestGetValidTendermintUpdatesIdentical(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -706,17 +690,17 @@ func TestGetTendermintUpdatesIdentical(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test identical, // tendermintUpdate set: {} -> {} validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) } -func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { +func TestGetValidTendermintUpdatesSingleValueChange(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -729,8 +713,8 @@ func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test single value change // tendermintUpdate set: {} -> {c1'} @@ -738,13 +722,13 @@ func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { validators[0].Tokens = sdk.NewDec(600) validators[0] = keeper.UpdateValidator(ctx, validators[0]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[0].ABCIValidator(), updates[0]) } -func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { +func TestGetValidTendermintUpdatesMultipleValueChange(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -757,8 +741,8 @@ func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test multiple value change // tendermintUpdate set: {c1, c3} -> {c1', c3'} @@ -769,13 +753,13 @@ func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 2, len(updates)) require.Equal(t, validators[0].ABCIValidator(), updates[0]) require.Equal(t, validators[1].ABCIValidator(), updates[1]) } -func TestGetTendermintUpdatesInserted(t *testing.T) { +func TestGetValidTendermintUpdatesInserted(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20, 5, 15, 25} @@ -788,34 +772,34 @@ func TestGetTendermintUpdatesInserted(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test validtor added at the beginning // tendermintUpdate set: {} -> {c0} validators[2] = keeper.UpdateValidator(ctx, validators[2]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[2].ABCIValidator(), updates[0]) // test validtor added at the beginning // tendermintUpdate set: {} -> {c0} - keeper.ClearTendermintUpdates(ctx) + clearTendermintUpdates(ctx, keeper) validators[3] = keeper.UpdateValidator(ctx, validators[3]) - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[3].ABCIValidator(), updates[0]) // test validtor added at the end // tendermintUpdate set: {} -> {c0} - keeper.ClearTendermintUpdates(ctx) + clearTendermintUpdates(ctx, keeper) validators[4] = keeper.UpdateValidator(ctx, validators[4]) - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[4].ABCIValidator(), updates[0]) } -func TestGetTendermintUpdatesWithCliffValidator(t *testing.T) { +func TestGetValidTendermintUpdatesWithCliffValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) params := types.DefaultParams() params.MaxValidators = 2 @@ -831,32 +815,32 @@ func TestGetTendermintUpdatesWithCliffValidator(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // test validator added at the end but not inserted in the valset // tendermintUpdate set: {} -> {} keeper.UpdateValidator(ctx, validators[2]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 0, len(updates)) // test validator change its power and become a gotValidator (pushing out an existing) // tendermintUpdate set: {} -> {c0, c4} - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) pool := keeper.GetPool(ctx) validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(10)) keeper.SetPool(ctx, pool) validators[2] = keeper.UpdateValidator(ctx, validators[2]) - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 2, len(updates), "%v", updates) require.Equal(t, validators[0].ABCIValidatorZero(), updates[0]) require.Equal(t, validators[2].ABCIValidator(), updates[1]) } -func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { +func TestGetValidTendermintUpdatesPowerDecrease(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{100, 100} @@ -869,8 +853,8 @@ func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { } validators[0] = keeper.UpdateValidator(ctx, validators[0]) validators[1] = keeper.UpdateValidator(ctx, validators[1]) - keeper.ClearTendermintUpdates(ctx) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) // check initial power require.Equal(t, sdk.NewDec(100).RoundInt64(), validators[0].GetPower().RoundInt64()) @@ -890,8 +874,158 @@ func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { require.Equal(t, sdk.NewDec(70).RoundInt64(), validators[1].GetPower().RoundInt64()) // Tendermint updates should reflect power change - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.GetValidTendermintUpdates(ctx) require.Equal(t, 2, len(updates)) require.Equal(t, validators[0].ABCIValidator(), updates[0]) require.Equal(t, validators[1].ABCIValidator(), updates[1]) } + +func TestGetValidTendermintUpdatesNewValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := keeper.GetParams(ctx) + params.MaxValidators = uint16(3) + + keeper.SetParams(ctx, params) + + amts := []int64{100, 100} + var validators [2]types.Validator + + // initialize some validators into the state + for i, amt := range amts { + pool := keeper.GetPool(ctx) + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + + // verify initial Tendermint updates are correct + updates := keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, len(validators), len(updates)) + require.Equal(t, validators[0].ABCIValidator(), updates[0]) + require.Equal(t, validators[1].ABCIValidator(), updates[1]) + + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) + + // update initial validator set + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + + // add a new validator that goes from zero power, to non-zero power, back to + // zero power + pool := keeper.GetPool(ctx) + valPubKey := PKs[len(validators)+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + amt := sdk.NewInt(100) + + validator := types.NewValidator(valAddr, valPubKey, types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, amt) + + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + validator, pool, _ = validator.RemoveDelShares(pool, sdk.NewDecFromInt(amt)) + validator = keeper.UpdateValidator(ctx, validator) + + // add a new validator that increases in power + valPubKey = PKs[len(validators)+2] + valAddr = sdk.ValAddress(valPubKey.Address().Bytes()) + + validator = types.NewValidator(valAddr, valPubKey, types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(500)) + + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + // verify initial Tendermint updates are correct + updates = keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, len(validators)+1, len(updates)) + require.Equal(t, validator.ABCIValidator(), updates[0]) + require.Equal(t, validators[0].ABCIValidator(), updates[1]) + require.Equal(t, validators[1].ABCIValidator(), updates[2]) +} + +func TestGetValidTendermintUpdatesBondTransition(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := keeper.GetParams(ctx) + params.MaxValidators = uint16(2) + + keeper.SetParams(ctx, params) + + amts := []int64{100, 200, 300} + var validators [3]types.Validator + + // initialize some validators into the state + for i, amt := range amts { + pool := keeper.GetPool(ctx) + moniker := fmt.Sprintf("%d", i) + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{Moniker: moniker}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + + // verify initial Tendermint updates are correct + updates := keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[2].ABCIValidator(), updates[0]) + require.Equal(t, validators[1].ABCIValidator(), updates[1]) + + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) + + // delegate to validator with lowest power but not enough to bond + ctx = ctx.WithBlockHeight(1) + pool := keeper.GetPool(ctx) + + validator, found := keeper.GetValidator(ctx, validators[0].OperatorAddr) + require.True(t, found) + + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(1)) + + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validator) + + // verify initial Tendermint updates are correct + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) + + // create a series of events that will bond and unbond the validator with + // lowest power in a single block context (height) + ctx = ctx.WithBlockHeight(2) + pool = keeper.GetPool(ctx) + + validator, found = keeper.GetValidator(ctx, validators[1].OperatorAddr) + require.True(t, found) + + validator, pool, _ = validator.RemoveDelShares(pool, validator.DelegatorShares) + + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(250)) + + keeper.SetPool(ctx, pool) + validators[1] = keeper.UpdateValidator(ctx, validator) + + // verify initial Tendermint updates are correct + updates = keeper.GetValidTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[1].ABCIValidator(), updates[0]) + + clearTendermintUpdates(ctx, keeper) + require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) +} diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 6ad8b4182..3bfd665f4 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -23,7 +23,8 @@ func TestStakeWithRandomMessages(t *testing.T) { mapper := mapp.AccountMapper bankKeeper := bank.NewBaseKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, bankKeeper, stake.DefaultCodespace) + stakeTKey := sdk.NewTransientStoreKey("transient_stake") + stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, 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 +33,7 @@ func TestStakeWithRandomMessages(t *testing.T) { } }) - err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey}) + err := mapp.CompleteSetup(stakeKey, stakeTKey) if err != nil { panic(err) } @@ -44,14 +45,14 @@ func TestStakeWithRandomMessages(t *testing.T) { simulation.Simulate( t, mapp.BaseApp, appStateFn, - []simulation.Operation{ - SimulateMsgCreateValidator(mapper, stakeKeeper), - SimulateMsgEditValidator(stakeKeeper), - SimulateMsgDelegate(mapper, stakeKeeper), - SimulateMsgBeginUnbonding(mapper, stakeKeeper), - SimulateMsgCompleteUnbonding(stakeKeeper), - SimulateMsgBeginRedelegate(mapper, stakeKeeper), - SimulateMsgCompleteRedelegate(stakeKeeper), + []simulation.WeightedOperation{ + {10, SimulateMsgCreateValidator(mapper, stakeKeeper)}, + {5, SimulateMsgEditValidator(stakeKeeper)}, + {15, SimulateMsgDelegate(mapper, stakeKeeper)}, + {10, SimulateMsgBeginUnbonding(mapper, stakeKeeper)}, + {3, SimulateMsgCompleteUnbonding(stakeKeeper)}, + {10, SimulateMsgBeginRedelegate(mapper, stakeKeeper)}, + {3, SimulateMsgCompleteRedelegate(stakeKeeper)}, }, []simulation.RandSetup{ Setup(mapp, stakeKeeper), }, []simulation.Invariant{ diff --git a/x/stake/stake.go b/x/stake/stake.go index d60e40299..767169f9c 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -33,7 +33,7 @@ var ( GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey - GetTendermintUpdatesKey = keeper.GetTendermintUpdatesKey + GetTendermintUpdatesTKey = keeper.GetTendermintUpdatesTKey GetDelegationKey = keeper.GetDelegationKey GetDelegationsKey = keeper.GetDelegationsKey ParamKey = keeper.ParamKey @@ -44,7 +44,7 @@ var ( ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey ValidatorPowerCliffKey = keeper.ValidatorPowerCliffKey - TendermintUpdatesKey = keeper.TendermintUpdatesKey + TendermintUpdatesTKey = keeper.TendermintUpdatesTKey DelegationKey = keeper.DelegationKey IntraTxCounterKey = keeper.IntraTxCounterKey GetUBDKey = keeper.GetUBDKey