github.com/cosmos/cosmos-sdk@v0.50.10/client/tx/factory.go (about)

     1  package tx
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/cosmos/go-bip39"
    10  	"github.com/spf13/pflag"
    11  
    12  	"cosmossdk.io/math"
    13  
    14  	"github.com/cosmos/cosmos-sdk/client"
    15  	"github.com/cosmos/cosmos-sdk/client/flags"
    16  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    17  	"github.com/cosmos/cosmos-sdk/crypto/keyring"
    18  	"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
    19  	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
    20  	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    21  	sdk "github.com/cosmos/cosmos-sdk/types"
    22  	"github.com/cosmos/cosmos-sdk/types/tx/signing"
    23  )
    24  
    25  // Factory defines a client transaction factory that facilitates generating and
    26  // signing an application-specific transaction.
    27  type Factory struct {
    28  	keybase            keyring.Keyring
    29  	txConfig           client.TxConfig
    30  	accountRetriever   client.AccountRetriever
    31  	accountNumber      uint64
    32  	sequence           uint64
    33  	gas                uint64
    34  	timeoutHeight      uint64
    35  	gasAdjustment      float64
    36  	chainID            string
    37  	fromName           string
    38  	offline            bool
    39  	generateOnly       bool
    40  	memo               string
    41  	fees               sdk.Coins
    42  	feeGranter         sdk.AccAddress
    43  	feePayer           sdk.AccAddress
    44  	gasPrices          sdk.DecCoins
    45  	extOptions         []*codectypes.Any
    46  	signMode           signing.SignMode
    47  	simulateAndExecute bool
    48  	preprocessTxHook   client.PreprocessTxFn
    49  }
    50  
    51  // NewFactoryCLI creates a new Factory.
    52  func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) (Factory, error) {
    53  	signMode := signing.SignMode_SIGN_MODE_UNSPECIFIED
    54  	switch clientCtx.SignModeStr {
    55  	case flags.SignModeDirect:
    56  		signMode = signing.SignMode_SIGN_MODE_DIRECT
    57  	case flags.SignModeLegacyAminoJSON:
    58  		signMode = signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
    59  	case flags.SignModeDirectAux:
    60  		signMode = signing.SignMode_SIGN_MODE_DIRECT_AUX
    61  	case flags.SignModeTextual:
    62  		signMode = signing.SignMode_SIGN_MODE_TEXTUAL
    63  	case flags.SignModeEIP191:
    64  		signMode = signing.SignMode_SIGN_MODE_EIP_191
    65  	}
    66  
    67  	var accNum, accSeq uint64
    68  	if clientCtx.Offline {
    69  		if flagSet.Changed(flags.FlagAccountNumber) && flagSet.Changed(flags.FlagSequence) {
    70  			accNum, _ = flagSet.GetUint64(flags.FlagAccountNumber)
    71  			accSeq, _ = flagSet.GetUint64(flags.FlagSequence)
    72  		} else {
    73  			return Factory{}, errors.New("account-number and sequence must be set in offline mode")
    74  		}
    75  	}
    76  
    77  	gasAdj, _ := flagSet.GetFloat64(flags.FlagGasAdjustment)
    78  	memo, _ := flagSet.GetString(flags.FlagNote)
    79  	timeoutHeight, _ := flagSet.GetUint64(flags.FlagTimeoutHeight)
    80  
    81  	gasStr, _ := flagSet.GetString(flags.FlagGas)
    82  	gasSetting, _ := flags.ParseGasSetting(gasStr)
    83  
    84  	f := Factory{
    85  		txConfig:           clientCtx.TxConfig,
    86  		accountRetriever:   clientCtx.AccountRetriever,
    87  		keybase:            clientCtx.Keyring,
    88  		chainID:            clientCtx.ChainID,
    89  		fromName:           clientCtx.FromName,
    90  		offline:            clientCtx.Offline,
    91  		generateOnly:       clientCtx.GenerateOnly,
    92  		gas:                gasSetting.Gas,
    93  		simulateAndExecute: gasSetting.Simulate,
    94  		accountNumber:      accNum,
    95  		sequence:           accSeq,
    96  		timeoutHeight:      timeoutHeight,
    97  		gasAdjustment:      gasAdj,
    98  		memo:               memo,
    99  		signMode:           signMode,
   100  		feeGranter:         clientCtx.FeeGranter,
   101  		feePayer:           clientCtx.FeePayer,
   102  	}
   103  
   104  	feesStr, _ := flagSet.GetString(flags.FlagFees)
   105  	f = f.WithFees(feesStr)
   106  
   107  	gasPricesStr, _ := flagSet.GetString(flags.FlagGasPrices)
   108  	f = f.WithGasPrices(gasPricesStr)
   109  
   110  	f = f.WithPreprocessTxHook(clientCtx.PreprocessTxHook)
   111  
   112  	return f, nil
   113  }
   114  
   115  func (f Factory) AccountNumber() uint64                     { return f.accountNumber }
   116  func (f Factory) Sequence() uint64                          { return f.sequence }
   117  func (f Factory) Gas() uint64                               { return f.gas }
   118  func (f Factory) GasAdjustment() float64                    { return f.gasAdjustment }
   119  func (f Factory) Keybase() keyring.Keyring                  { return f.keybase }
   120  func (f Factory) ChainID() string                           { return f.chainID }
   121  func (f Factory) Memo() string                              { return f.memo }
   122  func (f Factory) Fees() sdk.Coins                           { return f.fees }
   123  func (f Factory) GasPrices() sdk.DecCoins                   { return f.gasPrices }
   124  func (f Factory) AccountRetriever() client.AccountRetriever { return f.accountRetriever }
   125  func (f Factory) TimeoutHeight() uint64                     { return f.timeoutHeight }
   126  func (f Factory) FromName() string                          { return f.fromName }
   127  
   128  // SimulateAndExecute returns the option to simulate and then execute the transaction
   129  // using the gas from the simulation results
   130  func (f Factory) SimulateAndExecute() bool { return f.simulateAndExecute }
   131  
   132  // WithTxConfig returns a copy of the Factory with an updated TxConfig.
   133  func (f Factory) WithTxConfig(g client.TxConfig) Factory {
   134  	f.txConfig = g
   135  	return f
   136  }
   137  
   138  // WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever.
   139  func (f Factory) WithAccountRetriever(ar client.AccountRetriever) Factory {
   140  	f.accountRetriever = ar
   141  	return f
   142  }
   143  
   144  // WithChainID returns a copy of the Factory with an updated chainID.
   145  func (f Factory) WithChainID(chainID string) Factory {
   146  	f.chainID = chainID
   147  	return f
   148  }
   149  
   150  // WithGas returns a copy of the Factory with an updated gas value.
   151  func (f Factory) WithGas(gas uint64) Factory {
   152  	f.gas = gas
   153  	return f
   154  }
   155  
   156  // WithFees returns a copy of the Factory with an updated fee.
   157  func (f Factory) WithFees(fees string) Factory {
   158  	parsedFees, err := sdk.ParseCoinsNormalized(fees)
   159  	if err != nil {
   160  		panic(err)
   161  	}
   162  
   163  	f.fees = parsedFees
   164  	return f
   165  }
   166  
   167  // WithGasPrices returns a copy of the Factory with updated gas prices.
   168  func (f Factory) WithGasPrices(gasPrices string) Factory {
   169  	parsedGasPrices, err := sdk.ParseDecCoins(gasPrices)
   170  	if err != nil {
   171  		panic(err)
   172  	}
   173  
   174  	f.gasPrices = parsedGasPrices
   175  	return f
   176  }
   177  
   178  // WithKeybase returns a copy of the Factory with updated Keybase.
   179  func (f Factory) WithKeybase(keybase keyring.Keyring) Factory {
   180  	f.keybase = keybase
   181  	return f
   182  }
   183  
   184  // WithFromName returns a copy of the Factory with updated fromName
   185  // fromName will be use for building a simulation tx.
   186  func (f Factory) WithFromName(fromName string) Factory {
   187  	f.fromName = fromName
   188  	return f
   189  }
   190  
   191  // WithSequence returns a copy of the Factory with an updated sequence number.
   192  func (f Factory) WithSequence(sequence uint64) Factory {
   193  	f.sequence = sequence
   194  	return f
   195  }
   196  
   197  // WithMemo returns a copy of the Factory with an updated memo.
   198  func (f Factory) WithMemo(memo string) Factory {
   199  	f.memo = memo
   200  	return f
   201  }
   202  
   203  // WithAccountNumber returns a copy of the Factory with an updated account number.
   204  func (f Factory) WithAccountNumber(accnum uint64) Factory {
   205  	f.accountNumber = accnum
   206  	return f
   207  }
   208  
   209  // WithGasAdjustment returns a copy of the Factory with an updated gas adjustment.
   210  func (f Factory) WithGasAdjustment(gasAdj float64) Factory {
   211  	f.gasAdjustment = gasAdj
   212  	return f
   213  }
   214  
   215  // WithSimulateAndExecute returns a copy of the Factory with an updated gas
   216  // simulation value.
   217  func (f Factory) WithSimulateAndExecute(sim bool) Factory {
   218  	f.simulateAndExecute = sim
   219  	return f
   220  }
   221  
   222  // SignMode returns the sign mode configured in the Factory
   223  func (f Factory) SignMode() signing.SignMode {
   224  	return f.signMode
   225  }
   226  
   227  // WithSignMode returns a copy of the Factory with an updated sign mode value.
   228  func (f Factory) WithSignMode(mode signing.SignMode) Factory {
   229  	f.signMode = mode
   230  	return f
   231  }
   232  
   233  // WithTimeoutHeight returns a copy of the Factory with an updated timeout height.
   234  func (f Factory) WithTimeoutHeight(height uint64) Factory {
   235  	f.timeoutHeight = height
   236  	return f
   237  }
   238  
   239  // WithFeeGranter returns a copy of the Factory with an updated fee granter.
   240  func (f Factory) WithFeeGranter(fg sdk.AccAddress) Factory {
   241  	f.feeGranter = fg
   242  	return f
   243  }
   244  
   245  // WithFeePayer returns a copy of the Factory with an updated fee granter.
   246  func (f Factory) WithFeePayer(fp sdk.AccAddress) Factory {
   247  	f.feePayer = fp
   248  	return f
   249  }
   250  
   251  // WithPreprocessTxHook returns a copy of the Factory with an updated preprocess tx function,
   252  // allows for preprocessing of transaction data using the TxBuilder.
   253  func (f Factory) WithPreprocessTxHook(preprocessFn client.PreprocessTxFn) Factory {
   254  	f.preprocessTxHook = preprocessFn
   255  	return f
   256  }
   257  
   258  // PreprocessTx calls the preprocessing hook with the factory parameters and
   259  // returns the result.
   260  func (f Factory) PreprocessTx(keyname string, builder client.TxBuilder) error {
   261  	if f.preprocessTxHook == nil {
   262  		// Allow pass-through
   263  		return nil
   264  	}
   265  
   266  	key, err := f.Keybase().Key(keyname)
   267  	if err != nil {
   268  		return fmt.Errorf("error retrieving key from keyring: %w", err)
   269  	}
   270  
   271  	return f.preprocessTxHook(f.chainID, key.GetType(), builder)
   272  }
   273  
   274  // WithExtensionOptions returns a Factory with given extension options added to the existing options,
   275  // Example to add dynamic fee extension options:
   276  //
   277  //	extOpt := ethermint.ExtensionOptionDynamicFeeTx{
   278  //		MaxPriorityPrice: math.NewInt(1000000),
   279  //	}
   280  //
   281  //	extBytes, _ := extOpt.Marshal()
   282  //
   283  //	extOpts := []*types.Any{
   284  //		{
   285  //			TypeUrl: "/ethermint.types.v1.ExtensionOptionDynamicFeeTx",
   286  //			Value:   extBytes,
   287  //		},
   288  //	}
   289  //
   290  // txf.WithExtensionOptions(extOpts...)
   291  func (f Factory) WithExtensionOptions(extOpts ...*codectypes.Any) Factory {
   292  	f.extOptions = extOpts
   293  	return f
   294  }
   295  
   296  // BuildUnsignedTx builds a transaction to be signed given a set of messages.
   297  // Once created, the fee, memo, and messages are set.
   298  func (f Factory) BuildUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) {
   299  	if f.offline && f.generateOnly {
   300  		if f.chainID != "" {
   301  			return nil, errors.New("chain ID cannot be used when offline and generate-only flags are set")
   302  		}
   303  	} else if f.chainID == "" {
   304  		return nil, errors.New("chain ID required but not specified")
   305  	}
   306  
   307  	fees := f.fees
   308  
   309  	if !f.gasPrices.IsZero() {
   310  		if !fees.IsZero() {
   311  			return nil, errors.New("cannot provide both fees and gas prices")
   312  		}
   313  
   314  		glDec := math.LegacyNewDec(int64(f.gas))
   315  
   316  		// Derive the fees based on the provided gas prices, where
   317  		// fee = ceil(gasPrice * gasLimit).
   318  		fees = make(sdk.Coins, len(f.gasPrices))
   319  
   320  		for i, gp := range f.gasPrices {
   321  			fee := gp.Amount.Mul(glDec)
   322  			fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
   323  		}
   324  	}
   325  
   326  	// Prevent simple inclusion of a valid mnemonic in the memo field
   327  	if f.memo != "" && bip39.IsMnemonicValid(strings.ToLower(f.memo)) {
   328  		return nil, errors.New("cannot provide a valid mnemonic seed in the memo field")
   329  	}
   330  
   331  	tx := f.txConfig.NewTxBuilder()
   332  
   333  	if err := tx.SetMsgs(msgs...); err != nil {
   334  		return nil, err
   335  	}
   336  
   337  	tx.SetMemo(f.memo)
   338  	tx.SetFeeAmount(fees)
   339  	tx.SetGasLimit(f.gas)
   340  	tx.SetFeeGranter(f.feeGranter)
   341  	tx.SetFeePayer(f.feePayer)
   342  	tx.SetTimeoutHeight(f.TimeoutHeight())
   343  
   344  	if etx, ok := tx.(client.ExtendedTxBuilder); ok {
   345  		etx.SetExtensionOptions(f.extOptions...)
   346  	}
   347  
   348  	return tx, nil
   349  }
   350  
   351  // PrintUnsignedTx will generate an unsigned transaction and print it to the writer
   352  // specified by ctx.Output. If simulation was requested, the gas will be
   353  // simulated and also printed to the same writer before the transaction is
   354  // printed.
   355  func (f Factory) PrintUnsignedTx(clientCtx client.Context, msgs ...sdk.Msg) error {
   356  	if f.SimulateAndExecute() {
   357  		if clientCtx.Offline {
   358  			return errors.New("cannot estimate gas in offline mode")
   359  		}
   360  
   361  		// Prepare TxFactory with acc & seq numbers as CalculateGas requires
   362  		// account and sequence numbers to be set
   363  		preparedTxf, err := f.Prepare(clientCtx)
   364  		if err != nil {
   365  			return err
   366  		}
   367  
   368  		_, adjusted, err := CalculateGas(clientCtx, preparedTxf, msgs...)
   369  		if err != nil {
   370  			return err
   371  		}
   372  
   373  		f = f.WithGas(adjusted)
   374  		_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()})
   375  	}
   376  
   377  	unsignedTx, err := f.BuildUnsignedTx(msgs...)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	encoder := f.txConfig.TxJSONEncoder()
   383  	if encoder == nil {
   384  		return errors.New("cannot print unsigned tx: tx json encoder is nil")
   385  	}
   386  
   387  	json, err := encoder(unsignedTx.GetTx())
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
   393  }
   394  
   395  // BuildSimTx creates an unsigned tx with an empty single signature and returns
   396  // the encoded transaction or an error if the unsigned transaction cannot be
   397  // built.
   398  func (f Factory) BuildSimTx(msgs ...sdk.Msg) ([]byte, error) {
   399  	txb, err := f.BuildUnsignedTx(msgs...)
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  
   404  	pk, err := f.getSimPK()
   405  	if err != nil {
   406  		return nil, err
   407  	}
   408  
   409  	// Create an empty signature literal as the ante handler will populate with a
   410  	// sentinel pubkey.
   411  	sig := signing.SignatureV2{
   412  		PubKey:   pk,
   413  		Data:     f.getSimSignatureData(pk),
   414  		Sequence: f.Sequence(),
   415  	}
   416  	if err := txb.SetSignatures(sig); err != nil {
   417  		return nil, err
   418  	}
   419  
   420  	encoder := f.txConfig.TxEncoder()
   421  	if encoder == nil {
   422  		return nil, fmt.Errorf("cannot simulate tx: tx encoder is nil")
   423  	}
   424  
   425  	return encoder(txb.GetTx())
   426  }
   427  
   428  // getSimPK gets the public key to use for building a simulation tx.
   429  // Note, we should only check for keys in the keybase if we are in simulate and execute mode,
   430  // e.g. when using --gas=auto.
   431  // When using --dry-run, we are is simulation mode only and should not check the keybase.
   432  // Ref: https://github.com/cosmos/cosmos-sdk/issues/11283
   433  func (f Factory) getSimPK() (cryptotypes.PubKey, error) {
   434  	var (
   435  		ok bool
   436  		pk cryptotypes.PubKey = &secp256k1.PubKey{} // use default public key type
   437  	)
   438  
   439  	if f.simulateAndExecute && f.keybase != nil {
   440  		record, err := f.keybase.Key(f.fromName)
   441  		if err != nil {
   442  			return nil, err
   443  		}
   444  
   445  		pk, ok = record.PubKey.GetCachedValue().(cryptotypes.PubKey)
   446  		if !ok {
   447  			return nil, errors.New("cannot build signature for simulation, failed to convert proto Any to public key")
   448  		}
   449  	}
   450  
   451  	return pk, nil
   452  }
   453  
   454  // getSimSignatureData based on the pubKey type gets the correct SignatureData type
   455  // to use for building a simulation tx.
   456  func (f Factory) getSimSignatureData(pk cryptotypes.PubKey) signing.SignatureData {
   457  	multisigPubKey, ok := pk.(*multisig.LegacyAminoPubKey)
   458  	if !ok {
   459  		return &signing.SingleSignatureData{SignMode: f.signMode}
   460  	}
   461  
   462  	multiSignatureData := make([]signing.SignatureData, 0, multisigPubKey.Threshold)
   463  	for i := uint32(0); i < multisigPubKey.Threshold; i++ {
   464  		multiSignatureData = append(multiSignatureData, &signing.SingleSignatureData{
   465  			SignMode: f.SignMode(),
   466  		})
   467  	}
   468  
   469  	return &signing.MultiSignatureData{
   470  		Signatures: multiSignatureData,
   471  	}
   472  }
   473  
   474  // Prepare ensures the account defined by ctx.GetFromAddress() exists and
   475  // if the account number and/or the account sequence number are zero (not set),
   476  // they will be queried for and set on the provided Factory.
   477  // A new Factory with the updated fields will be returned.
   478  // Note: When in offline mode, the Prepare does nothing and returns the original factory.
   479  func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
   480  	if clientCtx.Offline {
   481  		return f, nil
   482  	}
   483  
   484  	fc := f
   485  	from := clientCtx.GetFromAddress()
   486  
   487  	if err := fc.accountRetriever.EnsureExists(clientCtx, from); err != nil {
   488  		return fc, err
   489  	}
   490  
   491  	initNum, initSeq := fc.accountNumber, fc.sequence
   492  	if initNum == 0 || initSeq == 0 {
   493  		num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, from)
   494  		if err != nil {
   495  			return fc, err
   496  		}
   497  
   498  		if initNum == 0 {
   499  			fc = fc.WithAccountNumber(num)
   500  		}
   501  
   502  		if initSeq == 0 {
   503  			fc = fc.WithSequence(seq)
   504  		}
   505  	}
   506  
   507  	return fc, nil
   508  }