github.com/Finschia/finschia-sdk@v0.48.1/client/tx/factory.go (about)

     1  package tx
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  
     8  	"github.com/spf13/pflag"
     9  
    10  	"github.com/Finschia/finschia-sdk/client"
    11  	"github.com/Finschia/finschia-sdk/client/flags"
    12  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    13  	"github.com/Finschia/finschia-sdk/crypto/keys/secp256k1"
    14  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    15  	sdk "github.com/Finschia/finschia-sdk/types"
    16  	"github.com/Finschia/finschia-sdk/types/tx/signing"
    17  )
    18  
    19  // Factory defines a client transaction factory that facilitates generating and
    20  // signing an application-specific transaction.
    21  type Factory struct {
    22  	keybase            keyring.Keyring
    23  	txConfig           client.TxConfig
    24  	accountRetriever   client.AccountRetriever
    25  	accountNumber      uint64
    26  	sequence           uint64
    27  	gas                uint64
    28  	timeoutHeight      uint64
    29  	gasAdjustment      float64
    30  	chainID            string
    31  	memo               string
    32  	fees               sdk.Coins
    33  	gasPrices          sdk.DecCoins
    34  	signMode           signing.SignMode
    35  	simulateAndExecute bool
    36  }
    37  
    38  // NewFactoryCLI creates a new Factory.
    39  func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) Factory {
    40  	signModeStr := clientCtx.SignModeStr
    41  
    42  	signMode := signing.SignMode_SIGN_MODE_UNSPECIFIED
    43  	switch signModeStr {
    44  	case flags.SignModeDirect:
    45  		signMode = signing.SignMode_SIGN_MODE_DIRECT
    46  	case flags.SignModeLegacyAminoJSON:
    47  		signMode = signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
    48  	case flags.SignModeEIP191:
    49  		signMode = signing.SignMode_SIGN_MODE_EIP_191
    50  	}
    51  
    52  	accNum, _ := flagSet.GetUint64(flags.FlagAccountNumber)
    53  	accSeq, _ := flagSet.GetUint64(flags.FlagSequence)
    54  	gasAdj, _ := flagSet.GetFloat64(flags.FlagGasAdjustment)
    55  	memo, _ := flagSet.GetString(flags.FlagNote)
    56  	timeoutHeight, _ := flagSet.GetUint64(flags.FlagTimeoutHeight)
    57  
    58  	gasStr, _ := flagSet.GetString(flags.FlagGas)
    59  	gasSetting, _ := flags.ParseGasSetting(gasStr)
    60  
    61  	f := Factory{
    62  		txConfig:           clientCtx.TxConfig,
    63  		accountRetriever:   clientCtx.AccountRetriever,
    64  		keybase:            clientCtx.Keyring,
    65  		chainID:            clientCtx.ChainID,
    66  		gas:                gasSetting.Gas,
    67  		simulateAndExecute: gasSetting.Simulate,
    68  		accountNumber:      accNum,
    69  		sequence:           accSeq,
    70  		timeoutHeight:      timeoutHeight,
    71  		gasAdjustment:      gasAdj,
    72  		memo:               memo,
    73  		signMode:           signMode,
    74  	}
    75  
    76  	feesStr, _ := flagSet.GetString(flags.FlagFees)
    77  	f = f.WithFees(feesStr)
    78  
    79  	gasPricesStr, _ := flagSet.GetString(flags.FlagGasPrices)
    80  	f = f.WithGasPrices(gasPricesStr)
    81  
    82  	return f
    83  }
    84  
    85  func (f Factory) AccountNumber() uint64                     { return f.accountNumber }
    86  func (f Factory) Sequence() uint64                          { return f.sequence }
    87  func (f Factory) Gas() uint64                               { return f.gas }
    88  func (f Factory) GasAdjustment() float64                    { return f.gasAdjustment }
    89  func (f Factory) Keybase() keyring.Keyring                  { return f.keybase }
    90  func (f Factory) ChainID() string                           { return f.chainID }
    91  func (f Factory) Memo() string                              { return f.memo }
    92  func (f Factory) Fees() sdk.Coins                           { return f.fees }
    93  func (f Factory) GasPrices() sdk.DecCoins                   { return f.gasPrices }
    94  func (f Factory) AccountRetriever() client.AccountRetriever { return f.accountRetriever }
    95  func (f Factory) TimeoutHeight() uint64                     { return f.timeoutHeight }
    96  
    97  // SimulateAndExecute returns the option to simulate and then execute the transaction
    98  // using the gas from the simulation results
    99  func (f Factory) SimulateAndExecute() bool { return f.simulateAndExecute }
   100  
   101  // WithTxConfig returns a copy of the Factory with an updated TxConfig.
   102  func (f Factory) WithTxConfig(g client.TxConfig) Factory {
   103  	f.txConfig = g
   104  	return f
   105  }
   106  
   107  // WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever.
   108  func (f Factory) WithAccountRetriever(ar client.AccountRetriever) Factory {
   109  	f.accountRetriever = ar
   110  	return f
   111  }
   112  
   113  // WithChainID returns a copy of the Factory with an updated chainID.
   114  func (f Factory) WithChainID(chainID string) Factory {
   115  	f.chainID = chainID
   116  	return f
   117  }
   118  
   119  // WithGas returns a copy of the Factory with an updated gas value.
   120  func (f Factory) WithGas(gas uint64) Factory {
   121  	f.gas = gas
   122  	return f
   123  }
   124  
   125  // WithFees returns a copy of the Factory with an updated fee.
   126  func (f Factory) WithFees(fees string) Factory {
   127  	parsedFees, err := sdk.ParseCoinsNormalized(fees)
   128  	if err != nil {
   129  		panic(err)
   130  	}
   131  
   132  	f.fees = parsedFees
   133  	return f
   134  }
   135  
   136  // WithGasPrices returns a copy of the Factory with updated gas prices.
   137  func (f Factory) WithGasPrices(gasPrices string) Factory {
   138  	parsedGasPrices, err := sdk.ParseDecCoins(gasPrices)
   139  	if err != nil {
   140  		panic(err)
   141  	}
   142  
   143  	f.gasPrices = parsedGasPrices
   144  	return f
   145  }
   146  
   147  // WithKeybase returns a copy of the Factory with updated Keybase.
   148  func (f Factory) WithKeybase(keybase keyring.Keyring) Factory {
   149  	f.keybase = keybase
   150  	return f
   151  }
   152  
   153  // WithSequence returns a copy of the Factory with an updated sequence number.
   154  func (f Factory) WithSequence(sequence uint64) Factory {
   155  	f.sequence = sequence
   156  	return f
   157  }
   158  
   159  // WithMemo returns a copy of the Factory with an updated memo.
   160  func (f Factory) WithMemo(memo string) Factory {
   161  	f.memo = memo
   162  	return f
   163  }
   164  
   165  // WithAccountNumber returns a copy of the Factory with an updated account number.
   166  func (f Factory) WithAccountNumber(accnum uint64) Factory {
   167  	f.accountNumber = accnum
   168  	return f
   169  }
   170  
   171  // WithGasAdjustment returns a copy of the Factory with an updated gas adjustment.
   172  func (f Factory) WithGasAdjustment(gasAdj float64) Factory {
   173  	f.gasAdjustment = gasAdj
   174  	return f
   175  }
   176  
   177  // WithSimulateAndExecute returns a copy of the Factory with an updated gas
   178  // simulation value.
   179  func (f Factory) WithSimulateAndExecute(sim bool) Factory {
   180  	f.simulateAndExecute = sim
   181  	return f
   182  }
   183  
   184  // SignMode returns the sign mode configured in the Factory
   185  func (f Factory) SignMode() signing.SignMode {
   186  	return f.signMode
   187  }
   188  
   189  // WithSignMode returns a copy of the Factory with an updated sign mode value.
   190  func (f Factory) WithSignMode(mode signing.SignMode) Factory {
   191  	f.signMode = mode
   192  	return f
   193  }
   194  
   195  // WithTimeoutHeight returns a copy of the Factory with an updated timeout height.
   196  func (f Factory) WithTimeoutHeight(height uint64) Factory {
   197  	f.timeoutHeight = height
   198  	return f
   199  }
   200  
   201  // BuildUnsignedTx builds a transaction to be signed given a set of messages.
   202  // Once created, the fee, memo, and messages are set.
   203  func (f Factory) BuildUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) {
   204  	if f.chainID == "" {
   205  		return nil, fmt.Errorf("chain ID required but not specified")
   206  	}
   207  
   208  	fees := f.fees
   209  
   210  	if !f.gasPrices.IsZero() {
   211  		if !fees.IsZero() {
   212  			return nil, errors.New("cannot provide both fees and gas prices")
   213  		}
   214  
   215  		glDec := sdk.NewDec(int64(f.gas))
   216  
   217  		// Derive the fees based on the provided gas prices, where
   218  		// fee = ceil(gasPrice * gasLimit).
   219  		fees = make(sdk.Coins, len(f.gasPrices))
   220  
   221  		for i, gp := range f.gasPrices {
   222  			fee := gp.Amount.Mul(glDec)
   223  			fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
   224  		}
   225  	}
   226  
   227  	tx := f.txConfig.NewTxBuilder()
   228  
   229  	if err := tx.SetMsgs(msgs...); err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	tx.SetMemo(f.memo)
   234  	tx.SetFeeAmount(fees)
   235  	tx.SetGasLimit(f.gas)
   236  	tx.SetTimeoutHeight(f.TimeoutHeight())
   237  
   238  	return tx, nil
   239  }
   240  
   241  // PrintUnsignedTx will generate an unsigned transaction and print it to the writer
   242  // specified by ctx.Output. If simulation was requested, the gas will be
   243  // simulated and also printed to the same writer before the transaction is
   244  // printed.
   245  func (f Factory) PrintUnsignedTx(clientCtx client.Context, msgs ...sdk.Msg) error {
   246  	if f.SimulateAndExecute() {
   247  		if clientCtx.Offline {
   248  			return errors.New("cannot estimate gas in offline mode")
   249  		}
   250  
   251  		_, adjusted, err := CalculateGas(clientCtx, f, msgs...)
   252  		if err != nil {
   253  			return err
   254  		}
   255  
   256  		f = f.WithGas(adjusted)
   257  		_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()})
   258  	}
   259  
   260  	unsignedTx, err := f.BuildUnsignedTx(msgs...)
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	json, err := clientCtx.TxConfig.TxJSONEncoder()(unsignedTx.GetTx())
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
   271  }
   272  
   273  // BuildSimTx creates an unsigned tx with an empty single signature and returns
   274  // the encoded transaction or an error if the unsigned transaction cannot be
   275  // built.
   276  func (f Factory) BuildSimTx(msgs ...sdk.Msg) ([]byte, error) {
   277  	txb, err := f.BuildUnsignedTx(msgs...)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	// use the first element from the list of keys in order to generate a valid
   283  	// pubkey that supports multiple algorithms
   284  
   285  	var pk cryptotypes.PubKey = &secp256k1.PubKey{} // use default public key type
   286  
   287  	if f.keybase != nil {
   288  		infos, _ := f.keybase.List()
   289  		if len(infos) == 0 {
   290  			return nil, errors.New("cannot build signature for simulation, key infos slice is empty")
   291  		}
   292  
   293  		// take the first info record just for simulation purposes
   294  		pk = infos[0].GetPubKey()
   295  	}
   296  
   297  	// Create an empty signature literal as the ante handler will populate with a
   298  	// sentinel pubkey.
   299  	sig := signing.SignatureV2{
   300  		PubKey: pk,
   301  		Data: &signing.SingleSignatureData{
   302  			SignMode: f.signMode,
   303  		},
   304  		Sequence: f.Sequence(),
   305  	}
   306  	if err := txb.SetSignatures(sig); err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	return f.txConfig.TxEncoder()(txb.GetTx())
   311  }
   312  
   313  // Prepare ensures the account defined by ctx.GetFromAddress() exists and
   314  // if the account number and/or the account sequence number are zero (not set),
   315  // they will be queried for and set on the provided Factory. A new Factory with
   316  // the updated fields will be returned.
   317  func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
   318  	fc := f
   319  
   320  	from := clientCtx.GetFromAddress()
   321  
   322  	if err := fc.accountRetriever.EnsureExists(clientCtx, from); err != nil {
   323  		return fc, err
   324  	}
   325  
   326  	initNum, initSeq := fc.accountNumber, fc.sequence
   327  	if initNum == 0 || initSeq == 0 {
   328  		num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, from)
   329  		if err != nil {
   330  			return fc, err
   331  		}
   332  
   333  		if initNum == 0 {
   334  			fc = fc.WithAccountNumber(num)
   335  		}
   336  
   337  		if initSeq == 0 {
   338  			fc = fc.WithSequence(seq)
   339  		}
   340  	}
   341  
   342  	return fc, nil
   343  }