Skip to content

Commit

Permalink
Merge PR #2047: Simulate transactions by default to set gas automatic…
Browse files Browse the repository at this point in the history
…ally
  • Loading branch information
cwgoes authored Aug 28, 2018
2 parents d3021d4 + 76a16ab commit 73f90e8
Show file tree
Hide file tree
Showing 27 changed files with 471 additions and 308 deletions.
2 changes: 2 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ FEATURES
* 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.

* Gaia

Expand Down
36 changes: 24 additions & 12 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,15 +493,14 @@ func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error {
return nil
}

// retrieve the context for the ante handler and store the tx bytes; store
// the signing validators if the tx runs within the deliverTx() state.
func (app *BaseApp) getContextForAnte(mode runTxMode, txBytes []byte) (ctx sdk.Context) {
// Get the context
if mode == runTxModeCheck || mode == runTxModeSimulate {
ctx = app.checkState.ctx.WithTxBytes(txBytes)
} else {
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
ctx = getState(app, mode).ctx.WithTxBytes(txBytes)
if mode == runTxModeDeliver {
ctx = ctx.WithSigningValidators(app.signedValidators)
}

return
}

Expand Down Expand Up @@ -567,6 +566,13 @@ func getState(app *BaseApp, mode runTxMode) *state {
return app.deliverState
}

func (app *BaseApp) initializeContext(ctx sdk.Context, mode runTxMode) sdk.Context {
if mode == runTxModeSimulate {
ctx = ctx.WithMultiStore(getState(app, runTxModeSimulate).CacheMultiStore())
}
return ctx
}

// runTx processes a transaction. The transactions is proccessed via an
// anteHandler. txBytes may be nil in some cases, eg. in tests. Also, in the
// future we may support "internal" transactions.
Expand All @@ -575,7 +581,9 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
// determined by the GasMeter. We need access to the context to get the gas
// meter so we initialize upfront.
var gasWanted int64
var msCache sdk.CacheMultiStore
ctx := app.getContextForAnte(mode, txBytes)
ctx = app.initializeContext(ctx, mode)

defer func() {
if r := recover(); r != nil {
Expand All @@ -594,15 +602,13 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
}()

var msgs = tx.GetMsgs()

err := validateBasicTxMsgs(msgs)
if err != nil {
if err := validateBasicTxMsgs(msgs); err != nil {
return err.Result()
}

// run the ante handler
if app.anteHandler != nil {
newCtx, result, abort := app.anteHandler(ctx, tx)
newCtx, result, abort := app.anteHandler(ctx, tx, (mode == runTxModeSimulate))
if abort {
return result
}
Expand All @@ -613,9 +619,15 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
gasWanted = result.GasWanted
}

if mode == runTxModeSimulate {
result = app.runMsgs(ctx, msgs, mode)
result.GasWanted = gasWanted
return
}

// Keep the state in a transient CacheWrap in case processing the messages
// fails.
msCache := getState(app, mode).CacheMultiStore()
msCache = getState(app, mode).CacheMultiStore()
if msCache.TracingEnabled() {
msCache = msCache.WithTracingContext(sdk.TraceContext(
map[string]interface{}{"txHash": cmn.HexBytes(tmhash.Sum(txBytes)).String()},
Expand All @@ -626,8 +638,8 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
result = app.runMsgs(ctx, msgs, mode)
result.GasWanted = gasWanted

// only update state if all messages pass and we're not in a simulation
if result.IsOK() && mode != runTxModeSimulate {
// only update state if all messages pass
if result.IsOK() {
msCache.Write()
}

Expand Down
12 changes: 7 additions & 5 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ func testTxDecoder(cdc *wire.Codec) sdk.TxDecoder {
}

func anteHandlerTxTest(t *testing.T, capKey *sdk.KVStoreKey, storeKey []byte) sdk.AnteHandler {
return func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
store := ctx.KVStore(capKey)
msgCounter := tx.(txTest).Counter
res = incrementingCounter(t, store, storeKey, msgCounter)
Expand Down Expand Up @@ -595,7 +595,7 @@ func TestSimulateTx(t *testing.T) {
gasConsumed := int64(5)

anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasConsumed))
return
})
Expand Down Expand Up @@ -659,7 +659,9 @@ func TestSimulateTx(t *testing.T) {

func TestRunInvalidTransaction(t *testing.T) {
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
return
})
}
routerOpt := func(bapp *BaseApp) {
bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return })
Expand Down Expand Up @@ -734,7 +736,7 @@ func TestRunInvalidTransaction(t *testing.T) {
func TestTxGasLimits(t *testing.T) {
gasGranted := int64(10)
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))

// NOTE/TODO/XXX:
Expand Down Expand Up @@ -825,7 +827,7 @@ func TestTxGasLimits(t *testing.T) {
func TestQuery(t *testing.T) {
key, value := []byte("hello"), []byte("goodbye")
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
store := ctx.KVStore(capKey1)
store.Set(key, value)
return
Expand Down
4 changes: 4 additions & 0 deletions client/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type CLIContext struct {
Client rpcclient.Client
Logger io.Writer
Height int64
Gas int64
GasAdjustment float64
NodeURI string
FromAddressName string
AccountStore string
Expand All @@ -48,6 +50,8 @@ 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),
Expand Down
7 changes: 3 additions & 4 deletions client/context/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/pkg/errors"

"github.com/tendermint/tendermint/libs/common"
cmn "github.com/tendermint/tendermint/libs/common"
rpcclient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
Expand All @@ -27,8 +26,8 @@ func (ctx CLIContext) GetNode() (rpcclient.Client, error) {
}

// Query performs a query for information about the connected node.
func (ctx CLIContext) Query(path string) (res []byte, err error) {
return ctx.query(path, nil)
func (ctx CLIContext) Query(path string, data cmn.HexBytes) (res []byte, err error) {
return ctx.query(path, data)
}

// Query information about the connected node with a data payload
Expand Down Expand Up @@ -284,7 +283,7 @@ func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error {

// query performs a query from a Tendermint node with the provided store name
// and path.
func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err error) {
func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err error) {
node, err := ctx.GetNode()
if err != nil {
return res, err
Expand Down
7 changes: 6 additions & 1 deletion client/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import "github.com/spf13/cobra"

// nolint
const (
DefaultGasLimit = 200000
DefaultGasAdjustment = 1.2

FlagUseLedger = "ledger"
FlagChainID = "chain-id"
FlagNode = "node"
FlagHeight = "height"
FlagGas = "gas"
FlagGasAdjustment = "gas-adjustment"
FlagTrustNode = "trust-node"
FlagFrom = "from"
FlagName = "name"
Expand Down Expand Up @@ -49,7 +53,8 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device")
c.Flags().Int64(FlagGas, 200000, "gas limit to set per-transaction")
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().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)")
Expand Down
43 changes: 28 additions & 15 deletions client/lcd/lcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,14 @@ func TestCoinSend(t *testing.T) {

require.Equal(t, "steak", mycoins.Denom)
require.Equal(t, int64(1), mycoins.Amount.Int64())

// test failure with too little gas
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)
require.Equal(t, http.StatusOK, res.StatusCode, body)
}

func TestIBCTransfer(t *testing.T) {
Expand Down Expand Up @@ -712,7 +720,7 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
return acc
}

func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) {
func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64) (res *http.Response, body string, receiveAddr sdk.AccAddress) {

// create receive address
kb := client.MockKeyBase()
Expand All @@ -730,19 +738,31 @@ func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress
panic(err)
}

gasStr := ""
if gas > 0 {
gasStr = fmt.Sprintf(`
"gas":"%v",
`, gas)
}
jsonStr := []byte(fmt.Sprintf(`{
%v
"name":"%s",
"password":"%s",
"account_number":"%d",
"sequence":"%d",
"gas": "10000",
"amount":[%s],
"chain_id":"%s"
}`, name, password, accnum, sequence, coinbz, chainID))
res, body := Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), jsonStr)
}`, gasStr, name, password, accnum, sequence, coinbz, chainID))

res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), 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)
require.Equal(t, http.StatusOK, res.StatusCode, body)

err = cdc.UnmarshalJSON([]byte(body), &resultTx)
err := cdc.UnmarshalJSON([]byte(body), &resultTx)
require.Nil(t, err)

return receiveAddr, resultTx
Expand All @@ -768,7 +788,6 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc
"password": "%s",
"account_number":"%d",
"sequence": "%d",
"gas": "100000",
"src_chain_id": "%s",
"amount":[
{
Expand Down Expand Up @@ -887,7 +906,6 @@ func doDelegate(t *testing.T, port, seed, name, password string, delegatorAddr,
"password": "%s",
"account_number": "%d",
"sequence": "%d",
"gas": "10000",
"chain_id": "%s",
"delegations": [
{
Expand Down Expand Up @@ -925,7 +943,6 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string,
"password": "%s",
"account_number": "%d",
"sequence": "%d",
"gas": "20000",
"chain_id": "%s",
"delegations": [],
"begin_unbondings": [
Expand Down Expand Up @@ -964,7 +981,6 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string,
"password": "%s",
"account_number": "%d",
"sequence": "%d",
"gas": "10000",
"chain_id": "%s",
"delegations": [],
"begin_unbondings": [],
Expand Down Expand Up @@ -1116,8 +1132,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
"password": "%s",
"chain_id": "%s",
"account_number":"%d",
"sequence":"%d",
"gas":"100000"
"sequence":"%d"
}
}`, proposerAddr, name, password, chainID, accnum, sequence))
res, body := Request(t, port, "POST", "/gov/proposals", jsonStr)
Expand Down Expand Up @@ -1147,8 +1162,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
"password": "%s",
"chain_id": "%s",
"account_number":"%d",
"sequence": "%d",
"gas":"100000"
"sequence": "%d"
}
}`, proposerAddr, name, password, chainID, accnum, sequence))
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), jsonStr)
Expand Down Expand Up @@ -1178,8 +1192,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
"password": "%s",
"chain_id": "%s",
"account_number": "%d",
"sequence": "%d",
"gas":"100000"
"sequence": "%d"
}
}`, proposerAddr, name, password, chainID, accnum, sequence))
res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), jsonStr)
Expand Down
2 changes: 1 addition & 1 deletion client/lcd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
// connected node version REST handler endpoint
func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
version, err := cliCtx.Query("/app/version")
version, err := cliCtx.Query("/app/version", nil)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error())))
Expand Down
12 changes: 12 additions & 0 deletions client/utils/rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package utils

import (
"net/http"
)

// 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))
}
Loading

0 comments on commit 73f90e8

Please sign in to comment.