From e460e3ed91153c53b730e36bfbf8766f704fb354 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 29 Aug 2018 08:13:23 +0100 Subject: [PATCH] Introduce simulate mode Add a simulate only flag '--dry-run' to both CLI tx commands and RESTful endpoints to trigger the simulation of unsigned transactions. * Turning --dry-run on causes the --gas flag to be ignored. The simulation will return the estimate of the gas required to actually run the transaction. * Adjustment is no longer required. It now defaults to 1.0. * In some test cases accounts retrieved from the state do not come with a PubKey. In such cases, a fake secp256k1 key is generated and gas consumption calculated accordingly. Closes: #2110 --- PENDING.md | 2 + client/context/context.go | 2 + client/flags.go | 6 +- client/lcd/lcd_test.go | 19 ++++-- client/utils/rest.go | 18 ++++++ client/utils/utils.go | 97 +++++++++++++++------------- cmd/gaia/cli_test/cli_test.go | 17 ++++- docs/sdk/clients.md | 15 ++++- examples/democoin/x/cool/app_test.go | 16 ++--- examples/democoin/x/pow/app_test.go | 6 +- x/auth/ante.go | 86 +++++++++++++++--------- x/auth/ante_test.go | 66 ++++++++++++++++++- x/auth/client/context/context.go | 29 +++++++++ x/bank/app_test.go | 77 ++++++++++++---------- x/bank/client/rest/sendtx.go | 9 ++- x/gov/client/rest/rest.go | 6 +- x/gov/client/rest/util.go | 10 ++- x/ibc/app_test.go | 8 +-- x/ibc/client/rest/transfer.go | 9 ++- x/mock/app_test.go | 6 +- x/mock/test_utils.go | 4 +- x/slashing/app_test.go | 4 +- x/slashing/client/rest/tx.go | 9 ++- x/stake/app_test.go | 10 +-- x/stake/client/rest/tx.go | 8 ++- 25 files changed, 375 insertions(+), 164 deletions(-) diff --git a/PENDING.md b/PENDING.md index f45541ccdc19..42f2f208a682 100644 --- a/PENDING.md +++ b/PENDING.md @@ -33,12 +33,14 @@ FEATURES * Gaia REST API (`gaiacli advanced rest-server`) * [lcd] Endpoints to query staking pool and params + * [lcd] \#2110 Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions * Gaia CLI (`gaiacli`) * [cli] Cmds to query staking pool and params * [gov][cli] #2062 added `--proposal` flag to `submit-proposal` that allows a JSON file containing a proposal to be passed in * [cli] \#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 The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0. + * [cli] \#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. * Gaia diff --git a/client/context/context.go b/client/context/context.go index 743c923552c8..34bc1229a8e4 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -32,6 +32,7 @@ type CLIContext struct { Async bool JSON bool PrintResponse bool + DryRun bool } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -57,6 +58,7 @@ func NewCLIContext() CLIContext { Async: viper.GetBool(client.FlagAsync), JSON: viper.GetBool(client.FlagJson), PrintResponse: viper.GetBool(client.FlagPrintResponse), + DryRun: viper.GetBool(client.FlagDryRun), } } diff --git a/client/flags.go b/client/flags.go index 81e06706784a..513447299948 100644 --- a/client/flags.go +++ b/client/flags.go @@ -5,7 +5,7 @@ import "github.com/spf13/cobra" // nolint const ( DefaultGasLimit = 200000 - DefaultGasAdjustment = 1.2 + DefaultGasAdjustment = 1.0 FlagUseLedger = "ledger" FlagChainID = "chain-id" @@ -23,6 +23,7 @@ const ( FlagAsync = "async" FlagJson = "json" FlagPrintResponse = "print-response" + FlagDryRun = "dry-run" ) // LineBreak can be included in a command list to provide a blank line @@ -54,10 +55,11 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { 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") + 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") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") + c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it") } return cmds } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 298cf0027e47..cf4c5b6588ca 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -2,6 +2,7 @@ package lcd import ( "encoding/hex" + "encoding/json" "fmt" "net/http" "regexp" @@ -265,11 +266,17 @@ 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) + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100, "") require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) - // test success with just enough gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 3000) + // run simulation and test success with estimated gas + 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, "") require.Equal(t, http.StatusOK, res.StatusCode, body) } @@ -720,7 +727,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) (res *http.Response, body string, receiveAddr sdk.AccAddress) { +func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := client.MockKeyBase() @@ -754,12 +761,12 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc "chain_id":"%s" }`, gasStr, name, password, accnum, sequence, coinbz, chainID)) - res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), jsonStr) + res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr) return } 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) + 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/rest.go b/client/utils/rest.go index 0e264031241b..4bbc10b328f3 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -1,12 +1,30 @@ package utils import ( + "fmt" "net/http" ) +const ( + queryArgDryRun = "simulate" +) + // WriteErrorResponse prepares and writes a HTTP error // given a status code and an error message. func WriteErrorResponse(w *http.ResponseWriter, status int, msg string) { (*w).WriteHeader(status) (*w).Write([]byte(msg)) } + +// WriteGasEstimateResponse prepares and writes an HTTP +// response for transactions simulations. +func WriteSimulationResponse(w *http.ResponseWriter, gas int64) { + (*w).WriteHeader(http.StatusOK) + (*w).Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas))) +} + +// HasDryRunArg returns true if the request's URL query contains +// the dry run argument and its value is set to "true". +func HasDryRunArg(r *http.Request) bool { + return r.URL.Query().Get(queryArgDryRun) == "true" +} diff --git a/client/utils/utils.go b/client/utils/utils.go index fb5d6198871d..acf90efaa0b5 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -15,7 +15,7 @@ import ( // DefaultGasAdjustment is applied to gas estimates to avoid tx // execution failures due to state changes that might // occur between the tx simulation and the actual run. -const DefaultGasAdjustment = 1.2 +const DefaultGasAdjustment = 1.0 // SendTx implements a auxiliary handler that facilitates sending a series of // messages in a signed transaction given a TxContext and a QueryContext. It @@ -23,35 +23,20 @@ const DefaultGasAdjustment = 1.2 // addition, it builds and signs a transaction with the supplied messages. // Finally, it broadcasts the signed transaction to a node. func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error { - if err := cliCtx.EnsureAccountExists(); err != nil { - return err - } - - from, err := cliCtx.GetFromAddress() + txCtx, err := prepareTxContext(txCtx, cliCtx) if err != nil { return err } - - // TODO: (ref #1903) Allow for user supplied account number without - // automatically doing a manual lookup. - if txCtx.AccountNumber == 0 { - accNum, err := cliCtx.GetAccountNumber(from) + autogas := cliCtx.DryRun || (cliCtx.Gas == 0) + if autogas { + txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, msgs) if err != nil { return err } - - txCtx = txCtx.WithAccountNumber(accNum) + fmt.Fprintf(os.Stdout, "estimated gas = %v\n", txCtx.Gas) } - - // TODO: (ref #1903) Allow for user supplied account sequence without - // automatically doing a manual lookup. - if txCtx.Sequence == 0 { - accSeq, err := cliCtx.GetAccountSequence(from) - if err != nil { - return err - } - - txCtx = txCtx.WithSequence(accSeq) + if cliCtx.DryRun { + return nil } passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) @@ -59,13 +44,6 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) return err } - if cliCtx.Gas == 0 { - txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, passphrase, msgs) - if err != nil { - return err - } - } - // build and sign the transaction txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs) if err != nil { @@ -75,26 +53,26 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) return cliCtx.EnsureBroadcastTx(txBytes) } -// EnrichCtxWithGas calculates the gas estimate that would be consumed by the -// transaction and set the transaction's respective value accordingly. -func EnrichCtxWithGas(txCtx authctx.TxContext, cliCtx context.CLIContext, name, passphrase string, msgs []sdk.Msg) (authctx.TxContext, error) { - txBytes, err := BuildAndSignTxWithZeroGas(txCtx, name, passphrase, msgs) +// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. +func SimulateMsgs(txCtx authctx.TxContext, cliCtx context.CLIContext, name string, msgs []sdk.Msg, gas int64) (estimated, adjusted int64, err error) { + txBytes, err := txCtx.WithGas(gas).BuildWithPubKey(name, msgs) if err != nil { - return txCtx, err + return } - estimate, adjusted, err := CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment) + 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(txCtx authctx.TxContext, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authctx.TxContext, error) { + _, adjusted, err := SimulateMsgs(txCtx, cliCtx, name, msgs, 0) if err != nil { return txCtx, err } - fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted) return txCtx.WithGas(adjusted), nil } -// BuildAndSignTxWithZeroGas builds transactions with GasWanted set to 0. -func BuildAndSignTxWithZeroGas(txCtx authctx.TxContext, name, passphrase string, msgs []sdk.Msg) ([]byte, error) { - return txCtx.WithGas(0).BuildAndSign(name, passphrase, msgs) -} - // CalculateGas simulates the execution of a transaction and returns // both the estimate obtained by the query and the adjusted amount. func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc *amino.Codec, txBytes []byte, adjustment float64) (estimate, adjusted int64, err error) { @@ -109,13 +87,12 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc * return } adjusted = adjustGasEstimate(estimate, adjustment) - fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted) return } func adjustGasEstimate(estimate int64, adjustment float64) int64 { if adjustment == 0 { - return int64(DefaultGasAdjustment * float64(estimate)) + adjustment = DefaultGasAdjustment } return int64(adjustment * float64(estimate)) } @@ -127,3 +104,35 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) { } return simulationResult.GasUsed, nil } + +func prepareTxContext(txCtx authctx.TxContext, cliCtx context.CLIContext) (authctx.TxContext, error) { + if err := cliCtx.EnsureAccountExists(); err != nil { + return txCtx, err + } + + from, err := cliCtx.GetFromAddress() + if err != nil { + return txCtx, err + } + + // TODO: (ref #1903) Allow for user supplied account number without + // automatically doing a manual lookup. + if txCtx.AccountNumber == 0 { + accNum, err := cliCtx.GetAccountNumber(from) + if err != nil { + return txCtx, err + } + txCtx = txCtx.WithAccountNumber(accNum) + } + + // TODO: (ref #1903) Allow for user supplied account sequence without + // automatically doing a manual lookup. + if txCtx.Sequence == 0 { + accSeq, err := cliCtx.GetAccountSequence(from) + if err != nil { + return txCtx, err + } + txCtx = txCtx.WithSequence(accSeq) + } + return txCtx, nil +} diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index f794e5140df7..9e797faa5e3c 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -58,6 +58,13 @@ func TestGaiaCLISend(t *testing.T) { fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) + // Test --dry-run + success := executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass) + require.True(t, success) + // Check state didn't change + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) + // test autosequencing executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) @@ -148,6 +155,10 @@ func TestGaiaCLICreateValidator(t *testing.T) { initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1)) + // Test --dry-run + success := executeWrite(t, cvStr+" --dry-run", app.DefaultKeyPass) + require.True(t, success) + executeWrite(t, cvStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) @@ -164,7 +175,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { unbondStr += fmt.Sprintf(" --validator=%s", barAddr) unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") - success := executeWrite(t, unbondStr, app.DefaultKeyPass) + success = executeWrite(t, unbondStr, app.DefaultKeyPass) require.True(t, success) tests.WaitForNextNBlocksTM(2, port) @@ -211,6 +222,10 @@ func TestGaiaCLISubmitProposal(t *testing.T) { spStr += fmt.Sprintf(" --title=%s", "Test") spStr += fmt.Sprintf(" --description=%s", "test") + // Test --dry-run + success := executeWrite(t, spStr+" --dry-run", app.DefaultKeyPass) + require.True(t, success) + executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 0a1c2a38d990..df97923bb504 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -85,6 +85,8 @@ When you query an account balance with zero tokens, you will get this error: `No ### Send Tokens +The following command could be used to send coins from one account to another: + ```bash gaiacli send \ --amount=10faucetToken \ @@ -100,7 +102,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. -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.2. +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. ::: Now, view the updated balances of the origin and destination accounts: @@ -116,6 +118,17 @@ You can also check your balance at a given block by using the `--block` flag: gaiacli account --block= ``` +You can simulate a transaction without actually broadcasting it by appending the `--dry-run` flag to the command line: + +```bash +gaiacli send \ + --amount=10faucetToken \ + --chain-id= \ + --name= \ + --to= \ + --dry-run +``` + ### Staking #### Set up a Validator diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go index 71b4202bc332..c0453a58ec0e 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -88,17 +88,17 @@ func TestMsgQuiz(t *testing.T) { require.Equal(t, acc1, res1) // Set the trend, submit a really cool quiz and check for reward - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, priv1) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, priv1) // result without reward + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, false, priv1) // result without reward mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, priv1) // reset the trend - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, priv1) // the same answer will nolonger do! + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, true, priv1) // reset the trend + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, false, priv1) // the same answer will nolonger do! mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, priv1) // earlier answer now relevant again + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, true, priv1) // earlier answer now relevant again mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, priv1) // expect to fail to set the trend to something which is not cool + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, false, priv1) // expect to fail to set the trend to something which is not cool } diff --git a/examples/democoin/x/pow/app_test.go b/examples/democoin/x/pow/app_test.go index dc53d1d99844..76f062e3b048 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/examples/democoin/x/pow/app_test.go @@ -74,13 +74,13 @@ func TestMsgMine(t *testing.T) { // Mine and check for reward mineMsg1 := GenerateMsgMine(addr1, 1, 2) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(1)}}) // Mine again and check for reward mineMsg2 := GenerateMsgMine(addr1, 2, 3) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) // Mine again - should be invalid - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, false, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) } diff --git a/x/auth/ante.go b/x/auth/ante.go index c0a129be3eb2..94899a872666 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -65,9 +65,12 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return newCtx, err.Result(), true } - sigs := stdTx.GetSignatures() + sigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice. signerAddrs := stdTx.GetSigners() msgs := tx.GetMsgs() + if simulate { + sigs = make([]StdSignature, len(signerAddrs)) + } // charge gas for the memo newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") @@ -88,10 +91,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { // check signature, return account with incremented nonce signBytes := StdSignBytes(newCtx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) - signerAcc, res := processSig( - newCtx, am, - signerAddr, sig, signBytes, - ) + signerAcc, res := processSig(newCtx, am, signerAddr, sig, signBytes, simulate) if !res.IsOK() { return newCtx, res, true } @@ -149,58 +149,82 @@ func validateBasic(tx StdTx) (err sdk.Error) { // if the account doesn't have a pubkey, set it. func processSig( ctx sdk.Context, am AccountMapper, - addr sdk.AccAddress, sig StdSignature, signBytes []byte) ( + addr sdk.AccAddress, sig StdSignature, signBytes []byte, simulate bool) ( acc Account, res sdk.Result) { - // Get the account. acc = am.GetAccount(ctx, addr) if acc == nil { return nil, sdk.ErrUnknownAddress(addr.String()).Result() } - // Check account number. accnum := acc.GetAccountNumber() - if accnum != sig.AccountNumber { - return nil, sdk.ErrInvalidSequence( - fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result() - } - - // Check and increment sequence number. seq := acc.GetSequence() - if seq != sig.Sequence { - return nil, sdk.ErrInvalidSequence( - fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() + + // Perform checks that wouldn't pass successfully in simulation, i.e. sig + // would be empty as simulated transactions come with no signatures whatsoever. + if !simulate { + // Check account number. + if accnum != sig.AccountNumber { + return nil, sdk.ErrInvalidSequence( + fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result() + } + + // Check sequence number. + if seq != sig.Sequence { + return nil, sdk.ErrInvalidSequence( + fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() + } } + // Increment sequence number err := acc.SetSequence(seq + 1) if err != nil { // Handle w/ #870 panic(err) } + // If pubkey is not known for account, + // set it from the StdSignature. + pubKey, res := processPubKey(acc, sig, simulate) + if !res.IsOK() { + return nil, res + } + err = acc.SetPubKey(pubKey) + if err != nil { + return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() + } + + consumeSignatureVerificationGas(ctx.GasMeter(), pubKey) + if !simulate && !pubKey.VerifyBytes(signBytes, sig.Signature) { + return nil, sdk.ErrUnauthorized("signature verification failed").Result() + } + + return +} + +func processPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, sdk.Result) { // If pubkey is not known for account, // set it from the StdSignature. pubKey := acc.GetPubKey() + if simulate { + // In simulate mode the transaction comes with no signatures, thus + // if the account's pubkey is nil, both signature verification + // and gasKVStore.Set() shall consume the largest amount, i.e. + // it takes more gas to verifiy secp256k1 keys than ed25519 ones. + if pubKey == nil { + return secp256k1.GenPrivKey().PubKey(), sdk.Result{} + } + return pubKey, sdk.Result{} + } if pubKey == nil { pubKey = sig.PubKey if pubKey == nil { return nil, sdk.ErrInvalidPubKey("PubKey not found").Result() } - if !bytes.Equal(pubKey.Address(), addr) { + if !bytes.Equal(pubKey.Address(), acc.GetAddress()) { return nil, sdk.ErrInvalidPubKey( - fmt.Sprintf("PubKey does not match Signer address %v", addr)).Result() + fmt.Sprintf("PubKey does not match Signer address %v", acc.GetAddress())).Result() } - err = acc.SetPubKey(pubKey) - if err != nil { - return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() - } - } - - // Check sig. - consumeSignatureVerificationGas(ctx.GasMeter(), pubKey) - if !pubKey.VerifyBytes(signBytes, sig.Signature) { - return nil, sdk.ErrUnauthorized("signature verification failed").Result() } - - return + return pubKey, sdk.Result{} } func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) { diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index a841bc7760aa..e13784512233 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -4,14 +4,14 @@ import ( "fmt" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/libs/log" - - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { @@ -567,3 +567,63 @@ func TestAnteHandlerSetPubKey(t *testing.T) { acc2 = mapper.GetAccount(ctx, addr2) require.Nil(t, acc2.GetPubKey()) } + +func TestProcessPubKey(t *testing.T) { + ms, capKey, _ := setupMultiStore() + cdc := wire.NewCodec() + RegisterBaseAccount(cdc) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + // keys + _, addr1 := privAndAddr() + priv2, _ := privAndAddr() + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + type args struct { + acc Account + sig StdSignature + simulate bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"no sigs, simulate off", args{acc1, StdSignature{}, false}, true}, + {"no sigs, simulate on", args{acc1, StdSignature{}, true}, false}, + {"pubkey doesn't match addr, simulate off", args{acc1, StdSignature{PubKey: priv2.PubKey()}, false}, true}, + {"pubkey doesn't match addr, simulate on", args{acc1, StdSignature{PubKey: priv2.PubKey()}, true}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := processPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) + require.Equal(t, tt.wantErr, !err.IsOK()) + }) + } +} + +func TestConsumeSignatureVerificationGas(t *testing.T) { + type args struct { + meter sdk.GasMeter + pubkey crypto.PubKey + } + tests := []struct { + name string + args args + gasConsumed int64 + wantPanic bool + }{ + {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), ed25519.GenPrivKey().PubKey()}, ed25519VerifyCost, false}, + {"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), secp256k1.GenPrivKey().PubKey()}, secp256k1VerifyCost, false}, + {"unknown key", args{sdk.NewInfiniteGasMeter(), nil}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantPanic { + require.Panics(t, func() { consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) }) + } else { + consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) + require.Equal(t, tt.args.meter.GasConsumed(), tt.gasConsumed) + } + }) + } +} diff --git a/x/auth/client/context/context.go b/x/auth/client/context/context.go index 8d0a94136d89..5e55696b83e5 100644 --- a/x/auth/client/context/context.go +++ b/x/auth/client/context/context.go @@ -148,3 +148,32 @@ func (ctx TxContext) BuildAndSign(name, passphrase string, msgs []sdk.Msg) ([]by return ctx.Sign(name, passphrase, msg) } + +// BuildWithPubKey builds a single message to be signed from a TxContext given a set of +// messages and attach the public key associated to the given name. +// It returns an error if a fee is supplied but cannot be parsed or the key cannot be +// retrieved. +func (ctx TxContext) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, error) { + msg, err := ctx.Build(msgs) + if err != nil { + return nil, err + } + + keybase, err := keys.GetKeyBase() + if err != nil { + return nil, err + } + + info, err := keybase.Get(name) + if err != nil { + return nil, err + } + + sigs := []auth.StdSignature{{ + AccountNumber: msg.AccountNumber, + Sequence: msg.Sequence, + PubKey: info.GetPubKey(), + }} + + return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) +} diff --git a/x/bank/app_test.go b/x/bank/app_test.go index c8d0a417dc0b..7ef344ffcdec 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -21,6 +21,7 @@ type ( } appTestCase struct { + expSimPass bool expPass bool msgs []sdk.Msg accNums []int64 @@ -107,27 +108,29 @@ func TestMsgSendWithAccounts(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg1}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg1}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 57)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, }, }, { - msgs: []sdk.Msg{sendMsg1, sendMsg2}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: false, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg1, sendMsg2}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: false, + privKeys: []crypto.PrivKey{priv1}, }, } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) @@ -144,7 +147,7 @@ func TestMsgSendWithAccounts(t *testing.T) { require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) // resigning the tx with the bumped sequence should work - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, true, priv1) } func TestMsgSendMultipleOut(t *testing.T) { @@ -163,11 +166,12 @@ func TestMsgSendMultipleOut(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg2}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg2}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 47)}}, @@ -177,7 +181,7 @@ func TestMsgSendMultipleOut(t *testing.T) { } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) @@ -205,11 +209,12 @@ func TestSengMsgMultipleInOut(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg3}, - accNums: []int64{0, 2}, - accSeqs: []int64{0, 0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1, priv4}, + msgs: []sdk.Msg{sendMsg3}, + accNums: []int64{0, 2}, + accSeqs: []int64{0, 0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1, priv4}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr4, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, @@ -220,7 +225,7 @@ func TestSengMsgMultipleInOut(t *testing.T) { } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) @@ -240,22 +245,24 @@ func TestMsgSendDependent(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg1}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg1}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, }, }, { - msgs: []sdk.Msg{sendMsg4}, - accNums: []int64{1}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv2}, + msgs: []sdk.Msg{sendMsg4}, + accNums: []int64{1}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv2}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}}, }, @@ -263,7 +270,7 @@ func TestMsgSendDependent(t *testing.T) { } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index c7baa96910d8..07b0b878621c 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -40,6 +40,7 @@ func init() { } // SendRequestHandlerFn - http request handler to send coins to a address +// nolint: gocyclo func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // collect data @@ -85,12 +86,16 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo Sequence: m.Sequence, } - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 4e6a5c1b37d2..5241a39141af 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -81,7 +81,7 @@ func postProposalHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.Hand return } - signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) + signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) } } @@ -118,7 +118,7 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu return } - signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) + signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) } } @@ -155,7 +155,7 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) + signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) } } diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go index f98f7bfa592d..d664be5ad0f8 100644 --- a/x/gov/client/rest/util.go +++ b/x/gov/client/rest/util.go @@ -66,7 +66,7 @@ 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, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { +func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { var err error txCtx := authctx.TxContext{ Codec: cdc, @@ -76,12 +76,16 @@ func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq base Gas: baseReq.Gas, } - if baseReq.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, baseReq.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || baseReq.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } txBytes, err := txCtx.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 5c3a0df78e76..f761f861d66d 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -70,10 +70,10 @@ func TestIBCMsgs(t *testing.T) { Sequence: 0, } - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, emptyCoins) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{1}, false, priv1) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{1}, false, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, coins) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, false, false, priv1) } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 765208b05655..ba0a845c2967 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -33,6 +33,7 @@ type transferBody struct { // TransferRequestHandler - http request handler to transfer coins to a address // on a different chain via IBC +// nolint: gocyclo func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -76,12 +77,16 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C Gas: m.Gas, } - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } diff --git a/x/mock/app_test.go b/x/mock/app_test.go index 7477e120449e..460757a0437d 100644 --- a/x/mock/app_test.go +++ b/x/mock/app_test.go @@ -61,14 +61,14 @@ func TestCheckAndDeliverGenTx(t *testing.T) { SignCheckDeliver( t, mApp.BaseApp, []sdk.Msg{msg}, []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, - true, privKeys[0], + true, true, privKeys[0], ) // Signing a tx with the wrong privKey should result in an auth error res := SignCheckDeliver( t, mApp.BaseApp, []sdk.Msg{msg}, []int64{accs[1].GetAccountNumber()}, []int64{accs[1].GetSequence() + 1}, - false, privKeys[1], + true, false, privKeys[1], ) require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) @@ -76,7 +76,7 @@ func TestCheckAndDeliverGenTx(t *testing.T) { SignCheckDeliver( t, mApp.BaseApp, []sdk.Msg{msg}, []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence() + 1}, - true, privKeys[0], + true, true, privKeys[0], ) } diff --git a/x/mock/test_utils.go b/x/mock/test_utils.go index c97f1c0c85b6..caaca6c9a5cc 100644 --- a/x/mock/test_utils.go +++ b/x/mock/test_utils.go @@ -71,13 +71,13 @@ func CheckGenTx( // returned. func SignCheckDeliver( t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, - seq []int64, expPass bool, priv ...crypto.PrivKey, + seq []int64, expSimPass, expPass bool, priv ...crypto.PrivKey, ) sdk.Result { tx := GenTx(msgs, accNums, seq, priv...) // Must simulate now as CheckTx doesn't run Msgs anymore res := app.Simulate(tx) - if expPass { + if expSimPass { require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) } else { require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 86d350a84b92..a3e145ec2e82 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -102,7 +102,7 @@ func TestSlashingMsgs(t *testing.T) { createValidatorMsg := stake.NewMsgCreateValidator( addr1, priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) mapp.BeginBlock(abci.RequestBeginBlock{}) @@ -116,6 +116,6 @@ func TestSlashingMsgs(t *testing.T) { checkValidatorSigningInfo(t, mapp, keeper, sdk.ValAddress(addr1), false) // unjail should fail with unknown validator - res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, priv1) + res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, false, priv1) require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), res.Code) } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 26412ebc0028..34f97648efce 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -36,6 +36,7 @@ type UnjailBody struct { ValidatorAddr string `json:"validator_addr"` } +// nolint: gocyclo func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m UnjailBody @@ -77,12 +78,16 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI msg := slashing.NewMsgUnjail(validatorAddr) - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } diff --git a/x/stake/app_test.go b/x/stake/app_test.go index 65c64fdef222..3a09e7703ee0 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -131,7 +131,7 @@ func TestStakeMsgs(t *testing.T) { addr1, priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) mApp.BeginBlock(abci.RequestBeginBlock{}) @@ -143,7 +143,7 @@ func TestStakeMsgs(t *testing.T) { // addr1 create validator on behalf of addr2 createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf(addr1, addr2, priv2.PubKey(), bondCoin, description) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, priv1, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, true, priv1, priv2) mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) mApp.BeginBlock(abci.RequestBeginBlock{}) @@ -159,7 +159,7 @@ func TestStakeMsgs(t *testing.T) { description = NewDescription("bar_moniker", "", "", "") editValidatorMsg := NewMsgEditValidator(addr1, description) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, priv1) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, true, priv1) validator = checkValidator(t, mApp, keeper, addr1, true) require.Equal(t, description, validator.Description) @@ -167,13 +167,13 @@ func TestStakeMsgs(t *testing.T) { mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) delegateMsg := NewMsgDelegate(addr2, addr1, bondCoin) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, true, priv2) mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) checkDelegation(t, mApp, keeper, addr2, addr1, true, sdk.NewDec(10)) // begin unbonding beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewDec(10)) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, true, priv2) // delegation should exist anymore checkDelegation(t, mApp, keeper, addr2, addr1, false, sdk.Dec{}) diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 3d7d419a3a64..a1904d420b79 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -275,12 +275,16 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex m.Sequence++ - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx }