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 }