Skip to content

Commit

Permalink
fix: ensure TxFactory has acc & seq fields when simulating tx in gene…
Browse files Browse the repository at this point in the history
…rate-only mode (#11313)

* fix: ensure TxFactory has acc no & seq when simulating tx in generate only mode

* add changelog

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
(cherry picked from commit 0a7ad01)

# Conflicts:
#	client/tx/factory.go
  • Loading branch information
clevinson authored and mergify-bot committed Mar 3, 2022
1 parent 811cb8a commit f3bc06e
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (store) [\#11177](https://github.com/cosmos/cosmos-sdk/pull/11177) Update the prune `everything` strategy to store the last two heights.
* (store) [\#11117](https://github.com/cosmos/cosmos-sdk/pull/11117) Fix data race in store trace component
* (x/authz) [\#11252](https://github.com/cosmos/cosmos-sdk/pull/11252) Allow insufficient funds error for authz simulation
* (cli) [\#11313](https://github.com/cosmos/cosmos-sdk/pull/11313) Fixes `--gas auto` when executing CLI transactions in `--generate-only` mode

### Improvements

Expand Down
164 changes: 164 additions & 0 deletions client/tx/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,167 @@ func (f Factory) WithTimeoutHeight(height uint64) Factory {
f.timeoutHeight = height
return f
}
<<<<<<< HEAD
=======

// BuildUnsignedTx builds a transaction to be signed given a set of messages.
// Once created, the fee, memo, and messages are set.
func (f Factory) BuildUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) {
if f.offline && f.generateOnly {
if f.chainID != "" {
return nil, fmt.Errorf("chain ID cannot be used when offline and generate-only flags are set")
}
} else if f.chainID == "" {
return nil, fmt.Errorf("chain ID required but not specified")
}

fees := f.fees

if !f.gasPrices.IsZero() {
if !fees.IsZero() {
return nil, errors.New("cannot provide both fees and gas prices")
}

glDec := sdk.NewDec(int64(f.gas))

// Derive the fees based on the provided gas prices, where
// fee = ceil(gasPrice * gasLimit).
fees = make(sdk.Coins, len(f.gasPrices))

for i, gp := range f.gasPrices {
fee := gp.Amount.Mul(glDec)
fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}
}

tx := f.txConfig.NewTxBuilder()

if err := tx.SetMsgs(msgs...); err != nil {
return nil, err
}

tx.SetMemo(f.memo)
tx.SetFeeAmount(fees)
tx.SetGasLimit(f.gas)
tx.SetTimeoutHeight(f.TimeoutHeight())

return tx, nil
}

// PrintUnsignedTx will generate an unsigned transaction and print it to the writer
// specified by ctx.Output. If simulation was requested, the gas will be
// simulated and also printed to the same writer before the transaction is
// printed.
func (f Factory) PrintUnsignedTx(clientCtx client.Context, msgs ...sdk.Msg) error {
if f.SimulateAndExecute() {
if clientCtx.Offline {
return errors.New("cannot estimate gas in offline mode")
}

// Prepare TxFactory with acc & seq numbers as CalculateGas requires
// account and sequence numbers to be set
preparedTxf, err := f.Prepare(clientCtx)
if err != nil {
return err
}

_, adjusted, err := CalculateGas(clientCtx, preparedTxf, msgs...)
if err != nil {
return err
}

f = f.WithGas(adjusted)
_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()})
}

unsignedTx, err := f.BuildUnsignedTx(msgs...)
if err != nil {
return err
}

json, err := clientCtx.TxConfig.TxJSONEncoder()(unsignedTx.GetTx())
if err != nil {
return err
}

return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
}

// BuildSimTx creates an unsigned tx with an empty single signature and returns
// the encoded transaction or an error if the unsigned transaction cannot be
// built.
func (f Factory) BuildSimTx(msgs ...sdk.Msg) ([]byte, error) {
txb, err := f.BuildUnsignedTx(msgs...)
if err != nil {
return nil, err
}

// use the first element from the list of keys in order to generate a valid
// pubkey that supports multiple algorithms

var (
ok bool
pk cryptotypes.PubKey = &secp256k1.PubKey{} // use default public key type
)

if f.keybase != nil {
records, _ := f.keybase.List()
if len(records) == 0 {
return nil, errors.New("cannot build signature for simulation, key records slice is empty")
}

// take the first record just for simulation purposes
pk, ok = records[0].PubKey.GetCachedValue().(cryptotypes.PubKey)
if !ok {
return nil, errors.New("cannot build signature for simulation, failed to convert proto Any to public key")
}
}

// Create an empty signature literal as the ante handler will populate with a
// sentinel pubkey.
sig := signing.SignatureV2{
PubKey: pk,
Data: &signing.SingleSignatureData{
SignMode: f.signMode,
},
Sequence: f.Sequence(),
}
if err := txb.SetSignatures(sig); err != nil {
return nil, err
}

return f.txConfig.TxEncoder()(txb.GetTx())
}

// Prepare ensures the account defined by ctx.GetFromAddress() exists and
// if the account number and/or the account sequence number are zero (not set),
// they will be queried for and set on the provided Factory. A new Factory with
// the updated fields will be returned.
func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
fc := f

from := clientCtx.GetFromAddress()

if err := fc.accountRetriever.EnsureExists(clientCtx, from); err != nil {
return fc, err
}

initNum, initSeq := fc.accountNumber, fc.sequence
if initNum == 0 || initSeq == 0 {
num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, from)
if err != nil {
return fc, err
}

if initNum == 0 {
fc = fc.WithAccountNumber(num)
}

if initSeq == 0 {
fc = fc.WithSequence(seq)
}
}

return fc, nil
}
>>>>>>> 0a7ad0181 (fix: ensure TxFactory has acc & seq fields when simulating tx in generate-only mode (#11313))

0 comments on commit f3bc06e

Please sign in to comment.