2018-08-06 11:11:30 -07:00
|
|
|
package utils
|
|
|
|
|
|
|
|
import (
|
2018-08-22 04:38:55 -07:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
|
2018-08-06 11:11:30 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/client/context"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context"
|
2018-08-22 04:38:55 -07:00
|
|
|
amino "github.com/tendermint/go-amino"
|
2018-08-24 01:48:02 -07:00
|
|
|
"github.com/tendermint/tendermint/libs/common"
|
2018-08-06 11:11:30 -07:00
|
|
|
)
|
|
|
|
|
2018-08-22 04:38:55 -07:00
|
|
|
// 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
|
|
|
|
|
2018-08-06 11:11:30 -07:00
|
|
|
// SendTx implements a auxiliary handler that facilitates sending a series of
|
|
|
|
// messages in a signed transaction given a TxContext and a QueryContext. It
|
|
|
|
// ensures that the account exists, has a proper number and sequence set. In
|
|
|
|
// 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()
|
|
|
|
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)
|
|
|
|
if err != nil {
|
|
|
|
return 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 err
|
|
|
|
}
|
|
|
|
|
|
|
|
txCtx = txCtx.WithSequence(accSeq)
|
|
|
|
}
|
|
|
|
|
|
|
|
passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-08-24 06:57:45 -07:00
|
|
|
if cliCtx.Gas == 0 {
|
|
|
|
txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, passphrase, msgs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-08-22 04:38:55 -07:00
|
|
|
}
|
|
|
|
|
2018-08-06 11:11:30 -07:00
|
|
|
// build and sign the transaction
|
|
|
|
txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// broadcast to a Tendermint node
|
|
|
|
return cliCtx.EnsureBroadcastTx(txBytes)
|
|
|
|
}
|
2018-08-22 04:38:55 -07:00
|
|
|
|
2018-08-24 06:57:45 -07:00
|
|
|
// 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)
|
|
|
|
if err != nil {
|
|
|
|
return txCtx, err
|
|
|
|
}
|
|
|
|
estimate, adjusted, err := CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment)
|
|
|
|
if err != nil {
|
|
|
|
return txCtx, err
|
2018-08-22 04:38:55 -07:00
|
|
|
}
|
2018-08-24 06:57:45 -07:00
|
|
|
fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted)
|
|
|
|
return txCtx.WithGas(adjusted), nil
|
2018-08-22 04:38:55 -07:00
|
|
|
}
|
|
|
|
|
2018-08-24 01:48:02 -07:00
|
|
|
// 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) {
|
2018-08-22 04:38:55 -07:00
|
|
|
// run a simulation (via /app/simulate query) to
|
|
|
|
// estimate gas and update TxContext accordingly
|
2018-08-24 01:48:02 -07:00
|
|
|
rawRes, err := queryFunc("/app/simulate", txBytes)
|
2018-08-22 04:38:55 -07:00
|
|
|
if err != nil {
|
2018-08-24 01:48:02 -07:00
|
|
|
return
|
2018-08-22 04:38:55 -07:00
|
|
|
}
|
2018-08-24 01:48:02 -07:00
|
|
|
estimate, err = parseQueryResponse(cdc, rawRes)
|
2018-08-22 04:38:55 -07:00
|
|
|
if err != nil {
|
2018-08-24 01:48:02 -07:00
|
|
|
return
|
2018-08-22 04:38:55 -07:00
|
|
|
}
|
2018-08-24 01:48:02 -07:00
|
|
|
adjusted = adjustGasEstimate(estimate, adjustment)
|
2018-08-22 04:38:55 -07:00
|
|
|
fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted)
|
2018-08-24 01:48:02 -07:00
|
|
|
return
|
2018-08-22 04:38:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func adjustGasEstimate(estimate int64, adjustment float64) int64 {
|
|
|
|
if adjustment == 0 {
|
|
|
|
return int64(DefaultGasAdjustment * float64(estimate))
|
|
|
|
}
|
|
|
|
return int64(adjustment * float64(estimate))
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) {
|
|
|
|
var simulationResult sdk.Result
|
|
|
|
if err := cdc.UnmarshalBinary(rawRes, &simulationResult); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return simulationResult.GasUsed, nil
|
|
|
|
}
|