cosmos-sdk/client/utils/utils.go

309 lines
8.9 KiB
Go
Raw Normal View History

2018-08-06 11:11:30 -07:00
package utils
import (
"bytes"
"fmt"
"io"
"os"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
2018-12-10 06:27:25 -08:00
"github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/libs/common"
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"
"github.com/cosmos/cosmos-sdk/x/auth"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
2018-08-06 11:11:30 -07:00
)
// 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)
}
// 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.
//
// NOTE: Also see CompleteAndBroadcastTxREST.
func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
2019-02-04 07:48:26 -08:00
txBldr, err := PrepareTxBuilder(txBldr, cliCtx)
2018-08-06 11:11:30 -07:00
if err != nil {
return err
}
fromName := cliCtx.GetFromName()
if txBldr.GetSimulateAndExecute() || cliCtx.Simulate {
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
2018-08-06 11:11:30 -07:00
if err != nil {
return err
}
gasEst := GasEstimateResponse{GasEstimate: txBldr.GetGas()}
fmt.Fprintf(os.Stderr, gasEst.String())
2018-08-06 11:11:30 -07:00
}
if cliCtx.Simulate {
return nil
2018-08-06 11:11:30 -07:00
}
passphrase, err := keys.GetPassphrase(fromName)
2018-08-06 11:11:30 -07:00
if err != nil {
return err
}
// build and sign the transaction
txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs)
2018-08-06 11:11:30 -07:00
if err != nil {
return err
}
2018-08-06 11:11:30 -07:00
// broadcast to a Tendermint node
_, err = cliCtx.BroadcastTx(txBytes)
return err
2018-08-06 11:11:30 -07:00
}
// 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)
2018-08-24 06:57:45 -07:00
if err != nil {
2018-09-07 10:15:49 -07:00
return txBldr, err
}
2018-09-07 10:15:49 -07:00
return txBldr.WithGas(adjusted), nil
}
2018-08-24 01:48:02 -07:00
// 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
2018-09-07 06:36:21 -07:00
// estimate gas and update TxBuilder accordingly
2018-08-24 01:48:02 -07:00
rawRes, err := queryFunc("/app/simulate", txBytes)
if err != nil {
2018-08-24 01:48:02 -07:00
return
}
2018-08-24 01:48:02 -07:00
estimate, err = parseQueryResponse(cdc, rawRes)
if err != nil {
2018-08-24 01:48:02 -07:00
return
}
2018-08-24 01:48:02 -07:00
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(w io.Writer, 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(w, "%s\n", json)
}
return
}
// SignStdTx appends a signature to a StdTx and returns a copy of a 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
keybase, err := keys.GetKeyBase()
if err != nil {
return signedStdTx, err
}
info, err := 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)
}
func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext,
addr sdk.AccAddress) (authtxb.TxBuilder, error) {
if txBldr.GetAccountNumber() == 0 {
accNum, err := cliCtx.GetAccountNumber(addr)
if err != nil {
return txBldr, err
}
txBldr = txBldr.WithAccountNumber(accNum)
}
if txBldr.GetSequence() == 0 {
accSeq, err := cliCtx.GetAccountSequence(addr)
if err != nil {
return txBldr, err
}
txBldr = txBldr.WithSequence(accSeq)
}
return txBldr, 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
}
Merge PR #2249: Staking Querier pt1 * Cherry picked commits from prev branch * Added new keepers for querier functionalities * Renaming * Fixed gov errors and messages * Added Querier to stake and app * Update delegation keepers * REST Queriers not working * Fix marshalling error * Querier tests working * Pool and params working * sdk.NewCoin for test handler * Refactor and renaming * Update LCD queries and added more tests for queriers * use sdk.NewCoin * Delegator summary query and tests * Added more tests for keeper * Update PENDING.md * Update stake rest query * Format and replaced panics for sdk.Error * Refactor and addressed comments from Sunny and Aleks * Fixed some of the errors produced by addr type change * Fixed remaining errors * Updated and fixed lite tests * JSON Header and consistency on errors * Increased cov for genesis * Added comment for maxRetrieve param in keepers * Comment on DelegationWithoutDec * Bech32Validator Keepers * Changed Bech validator * Updated remaining tests and bech32 validator * Addressed most of Rigel's comments * Updated tests and types * Make codec to be unexported from keeper * Moved logic to query_utils and updated tests * Fix linter err and PENDING * Fix err * Fix err * Fixed tests * Update PENDING description * Update UpdateBondedValidatorsFull * Update iterator * defer iterator.Close() * delete comment * Address some of Aleks comments, need to fix tests * export querier * Fixed tests * Address Rigel's comments * More tests * return error for GetDelegatorValidator * Fixed conflicts * Fix linter warnings * Address @rigelrozanski comments * Delete comments * wire ––> codec
2018-09-13 14:23:44 -07:00
// 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.GetGasAdjustment())
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
}
2019-02-04 07:48:26 -08:00
// 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 {
2018-09-07 10:15:49 -07:00
return txBldr, err
}
from := cliCtx.GetFromAddress()
// TODO: (ref #1903) Allow for user supplied account number without
// automatically doing a manual lookup.
if txBldr.GetAccountNumber() == 0 {
accNum, err := cliCtx.GetAccountNumber(from)
if err != nil {
2018-09-07 10:15:49 -07:00
return txBldr, err
}
2018-09-07 10:15:49 -07:00
txBldr = txBldr.WithAccountNumber(accNum)
}
// TODO: (ref #1903) Allow for user supplied account sequence without
// automatically doing a manual lookup.
if txBldr.GetSequence() == 0 {
accSeq, err := cliCtx.GetAccountSequence(from)
if err != nil {
2018-09-07 10:15:49 -07:00
return txBldr, err
}
2018-09-07 10:15:49 -07:00
txBldr = txBldr.WithSequence(accSeq)
}
2018-09-07 10:15:49 -07:00
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.
2018-09-07 10:15:49 -07:00
func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) {
2019-02-04 07:48:26 -08:00
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.GetSimulateAndExecute() {
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
if err != nil {
return
}
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas())
}
2018-09-07 10:15:49 -07:00
stdSignMsg, err := txBldr.Build(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
}