package utils import ( "bytes" "fmt" "io/ioutil" "os" "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/common" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" ) // GasEstimateResponse defines a response definition for tx gas estimation. type GasEstimateResponse struct { GasEstimate uint64 `json:"gas_estimate"` } func (gr GasEstimateResponse) String() string { return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) } // GenerateOrBroadcastMsgs respects CLI flags and outputs a message func GenerateOrBroadcastMsgs(cliCtx context.CLIContext, txBldr authtxb.TxBuilder, msgs []sdk.Msg, offline bool) error { if cliCtx.GenerateOnly { return PrintUnsignedStdTx(txBldr, cliCtx, msgs, offline) } return CompleteAndBroadcastTxCLI(txBldr, cliCtx, msgs) } // CompleteAndBroadcastTxCLI implements a utility function that facilitates // sending a series of messages in a signed transaction given a TxBuilder 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 CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { txBldr, err := PrepareTxBuilder(txBldr, cliCtx) if err != nil { return err } fromName := cliCtx.GetFromName() if txBldr.SimulateAndExecute() || cliCtx.Simulate { txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) if err != nil { return err } gasEst := GasEstimateResponse{GasEstimate: txBldr.Gas()} fmt.Fprintf(os.Stderr, "%s\n", gasEst.String()) } if cliCtx.Simulate { return nil } if !cliCtx.SkipConfirm { stdSignMsg, err := txBldr.BuildSignMsg(msgs) if err != nil { return err } var json []byte if viper.GetBool(client.FlagIndentResponse) { json, err = cliCtx.Codec.MarshalJSONIndent(stdSignMsg, "", " ") if err != nil { panic(err) } } else { json = cliCtx.Codec.MustMarshalJSON(stdSignMsg) } fmt.Fprintf(os.Stderr, "%s\n\n", json) buf := client.BufferStdin() ok, err := client.GetConfirmation("confirm transaction before signing and broadcasting", buf) if err != nil || !ok { fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") return err } } passphrase, err := keys.GetPassphrase(fromName) if err != nil { return err } // build and sign the transaction txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs) if err != nil { return err } // broadcast to a Tendermint node res, err := cliCtx.BroadcastTx(txBytes) if err != nil { return err } return cliCtx.PrintOutput(res) } // EnrichWithGas calculates the gas estimate that would be consumed by the // transaction and set the transaction's respective value accordingly. func EnrichWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtxb.TxBuilder, error) { _, adjusted, err := simulateMsgs(txBldr, cliCtx, msgs) if err != nil { return txBldr, err } return txBldr.WithGas(adjusted), nil } // 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 uint64, err error) { // run a simulation (via /app/simulate query) to // estimate gas and update TxBuilder accordingly rawRes, err := queryFunc("/app/simulate", txBytes) if err != nil { return } estimate, err = parseQueryResponse(cdc, rawRes) if err != nil { return } adjusted = adjustGasEstimate(estimate, adjustment) return } // PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout. // Don't perform online validation or lookups if offline is true. func PrintUnsignedStdTx( txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool, ) (err error) { var stdTx auth.StdTx if offline { stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) } else { stdTx, err = buildUnsignedStdTx(txBldr, cliCtx, msgs) } if err != nil { return } json, err := cliCtx.Codec.MarshalJSON(stdTx) if err == nil { fmt.Fprintf(cliCtx.Output, "%s\n", json) } return } // SignStdTx appends a signature to a StdTx and returns a copy of it. If appendSig // is false, it replaces the signatures already attached with the new signature. // Don't perform online validation or lookups if offline is true. func SignStdTx( txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool, ) (auth.StdTx, error) { var signedStdTx auth.StdTx info, err := txBldr.Keybase().Get(name) if err != nil { return signedStdTx, err } addr := info.GetPubKey().Address() // check whether the address is a signer if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name) } if !offline { txBldr, err = populateAccountFromState(txBldr, cliCtx, sdk.AccAddress(addr)) if err != nil { return signedStdTx, err } } passphrase, err := keys.GetPassphrase(name) if err != nil { return signedStdTx, err } return txBldr.SignStdTx(name, passphrase, stdTx, appendSig) } // SignStdTxWithSignerAddress attaches a signature to a StdTx and returns a copy of a it. // Don't perform online validation or lookups if offline is true, else // populate account and sequence numbers from a foreign account. func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress, name string, stdTx auth.StdTx, offline bool) (signedStdTx auth.StdTx, err error) { // check whether the address is a signer if !isTxSigner(addr, stdTx.GetSigners()) { return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name) } if !offline { txBldr, err = populateAccountFromState(txBldr, cliCtx, addr) if err != nil { return signedStdTx, err } } passphrase, err := keys.GetPassphrase(name) if err != nil { return signedStdTx, err } return txBldr.SignStdTx(name, passphrase, stdTx, false) } // Read and decode a StdTx from the given filename. Can pass "-" to read from stdin. func ReadStdTxFromFile(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) { var bytes []byte if filename == "-" { bytes, err = ioutil.ReadAll(os.Stdin) } else { bytes, err = ioutil.ReadFile(filename) } if err != nil { return } if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil { return } return } func populateAccountFromState( txBldr authtxb.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress, ) (authtxb.TxBuilder, error) { accNum, err := cliCtx.GetAccountNumber(addr) if err != nil { return txBldr, err } accSeq, err := cliCtx.GetAccountSequence(addr) if err != nil { return txBldr, err } return txBldr.WithAccountNumber(accNum).WithSequence(accSeq), nil } // GetTxEncoder return tx encoder from global sdk configuration if ones is defined. // Otherwise returns encoder with default logic. func GetTxEncoder(cdc *codec.Codec) (encoder sdk.TxEncoder) { encoder = sdk.GetConfig().GetTxEncoder() if encoder == nil { encoder = auth.DefaultTxEncoder(cdc) } return } // nolint // SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (estimated, adjusted uint64, err error) { txBytes, err := txBldr.BuildTxForSim(msgs) if err != nil { return } estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, txBldr.GasAdjustment()) return } func adjustGasEstimate(estimate uint64, adjustment float64) uint64 { return uint64(adjustment * float64(estimate)) } func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (uint64, error) { var simulationResult sdk.Result if err := cdc.UnmarshalBinaryLengthPrefixed(rawRes, &simulationResult); err != nil { return 0, err } return simulationResult.GasUsed, nil } // PrepareTxBuilder populates a TxBuilder in preparation for the build of a Tx. func PrepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { if err := cliCtx.EnsureAccountExists(); err != nil { return txBldr, err } from := cliCtx.GetFromAddress() // TODO: (ref #1903) Allow for user supplied account number without // automatically doing a manual lookup. if txBldr.AccountNumber() == 0 { accNum, err := cliCtx.GetAccountNumber(from) if err != nil { return txBldr, err } txBldr = txBldr.WithAccountNumber(accNum) } // TODO: (ref #1903) Allow for user supplied account sequence without // automatically doing a manual lookup. if txBldr.Sequence() == 0 { accSeq, err := cliCtx.GetAccountSequence(from) if err != nil { return txBldr, err } txBldr = txBldr.WithSequence(accSeq) } return txBldr, nil } // buildUnsignedStdTx builds a StdTx as per the parameters passed in the // contexts. Gas is automatically estimated if gas wanted is set to 0. func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { txBldr, err = PrepareTxBuilder(txBldr, cliCtx) if err != nil { return } return buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) } func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { if txBldr.SimulateAndExecute() { txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) if err != nil { return } fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas()) } stdSignMsg, err := txBldr.BuildSignMsg(msgs) if err != nil { return } return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil } func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool { for _, s := range signers { if bytes.Equal(user.Bytes(), s.Bytes()) { return true } } return false }