cosmos-sdk/client/v2/tx/factory.go
peicuiping ba8d778df4
chore: fix comment (#23138)
Signed-off-by: peicuiping <ezc5@sina.cn>
Co-authored-by: Marko <marko@baricevic.me>
Co-authored-by: Alex | Skip <alex@skip.money>
2025-01-03 14:23:04 +00:00

761 lines
20 KiB
Go

package tx
import (
"context"
"errors"
"fmt"
"math/big"
"strings"
"github.com/cosmos/go-bip39"
gogogrpc "github.com/cosmos/gogoproto/grpc"
"github.com/spf13/pflag"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/timestamppb"
base "cosmossdk.io/api/cosmos/base/v1beta1"
apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1"
apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
apitx "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/client/v2/autocli/keyring"
"cosmossdk.io/client/v2/internal/account"
"cosmossdk.io/client/v2/internal/coins"
"cosmossdk.io/core/address"
"cosmossdk.io/core/transaction"
"cosmossdk.io/math"
"cosmossdk.io/x/tx/signing"
flags2 "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)
// Factory defines a client transaction factory that facilitates generating and
// signing an application-specific transaction.
type Factory struct {
keybase keyring.Keyring
cdc codec.BinaryCodec
accountRetriever account.AccountRetriever
ac address.Codec
conn gogogrpc.ClientConn
txConfig TxConfig
txParams TxParameters
tx *txState
}
func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever,
txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn,
) (Factory, error) {
offline, _ := flags.GetBool(flags2.FlagOffline)
if err := validateFlagSet(flags, offline); err != nil {
return Factory{}, err
}
params, err := txParamsFromFlagSet(flags, keybase, ac)
if err != nil {
return Factory{}, err
}
params, err = prepareTxParams(params, accRetriever, offline)
if err != nil {
return Factory{}, err
}
return NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, params)
}
// NewFactory returns a new instance of Factory.
func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever,
txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters,
) (Factory, error) {
return Factory{
keybase: keybase,
cdc: cdc,
accountRetriever: accRetriever,
ac: ac,
conn: conn,
txConfig: txConfig,
txParams: parameters,
tx: &txState{},
}, nil
}
// validateFlagSet checks the provided flags for consistency and requirements based on the operation mode.
func validateFlagSet(flags *pflag.FlagSet, offline bool) error {
dryRun, _ := flags.GetBool(flags2.FlagDryRun)
if offline && dryRun {
return errors.New("dry-run: cannot use offline mode")
}
generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly)
chainID, _ := flags.GetString(flags2.FlagChainID)
if offline {
if !generateOnly && (!flags.Changed(flags2.FlagAccountNumber) || !flags.Changed(flags2.FlagSequence)) {
return errors.New("account-number and sequence must be set in offline mode")
}
if generateOnly && chainID != "" {
return errors.New("chain ID cannot be used when offline and generate-only flags are set")
}
gas, _ := flags.GetString(flags2.FlagGas)
gasSetting, _ := flags2.ParseGasSetting(gas)
if gasSetting.Simulate {
return errors.New("simulate and offline flags cannot be set at the same time")
}
} else if chainID == "" {
return errors.New("chain ID required but not specified")
}
return nil
}
// prepareTxParams 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.
func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetriever, offline bool) (TxParameters, error) {
if offline {
return parameters, nil
}
if len(parameters.Address) == 0 {
return parameters, errors.New("missing 'from address' field")
}
if parameters.AccountNumber == 0 || parameters.Sequence == 0 {
num, seq, err := accRetriever.GetAccountNumberSequence(context.Background(), parameters.Address)
if err != nil {
return parameters, err
}
if parameters.AccountNumber == 0 {
parameters.AccountNumber = num
}
if parameters.Sequence == 0 {
parameters.Sequence = seq
}
}
return parameters, nil
}
// 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 ...transaction.Msg) error {
fees := f.txParams.fees
isGasPriceZero, err := coins.IsZero(f.txParams.gasPrices)
if err != nil {
return err
}
if !isGasPriceZero {
areFeesZero, err := coins.IsZero(fees)
if err != nil {
return err
}
if !areFeesZero {
return errors.New("cannot provide both fees and gas prices")
}
// f.gas is an uint64 and we should convert to LegacyDec
// without the risk of under/overflow via uint64->int64.
glDec := math.LegacyNewDecFromBigInt(new(big.Int).SetUint64(f.txParams.gas))
// Derive the fees based on the provided gas prices, where
// fee = ceil(gasPrice * gasLimit).
fees = make([]*base.Coin, len(f.txParams.gasPrices))
for i, gp := range f.txParams.gasPrices {
fee, err := math.LegacyNewDecFromStr(gp.Amount)
if err != nil {
return err
}
fee = fee.Mul(glDec)
fees[i] = &base.Coin{Denom: gp.Denom, Amount: fee.Ceil().RoundInt().String()}
}
}
if err := validateMemo(f.txParams.memo); err != nil {
return err
}
f.tx.msgs = msgs
f.tx.memo = f.txParams.memo
f.tx.fees = fees
f.tx.gasLimit = f.txParams.gas
f.tx.unordered = f.txParams.unordered
f.tx.timeoutTimestamp = f.txParams.timeoutTimestamp
err = f.setFeeGranter(f.txParams.feeGranter)
if err != nil {
return err
}
err = f.setFeePayer(f.txParams.feePayer)
if err != nil {
return err
}
return nil
}
func (f *Factory) BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) (Tx, error) {
err := f.BuildUnsignedTx(msgs...)
if err != nil {
return nil, err
}
return f.sign(ctx, true)
}
// calculateGas calculates the gas required for the given messages.
func (f *Factory) calculateGas(msgs ...transaction.Msg) error {
_, adjusted, err := f.Simulate(msgs...)
if err != nil {
return err
}
f.WithGas(adjusted)
return nil
}
// Simulate simulates the execution of a transaction and returns the
// simulation response obtained by the query and the adjusted gas amount.
func (f *Factory) Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error) {
txBytes, err := f.BuildSimTx(msgs...)
if err != nil {
return nil, 0, err
}
txSvcClient := apitx.NewServiceClient(f.conn)
simRes, err := txSvcClient.Simulate(context.Background(), &apitx.SimulateRequest{
TxBytes: txBytes,
})
if err != nil {
return nil, 0, err
}
return simRes, uint64(f.gasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil
}
// UnsignedTxString 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) UnsignedTxString(msgs ...transaction.Msg) (string, error) {
if f.simulateAndExecute() {
err := f.calculateGas(msgs...)
if err != nil {
return "", err
}
}
err := f.BuildUnsignedTx(msgs...)
if err != nil {
return "", err
}
encoder := f.txConfig.TxJSONEncoder()
if encoder == nil {
return "", errors.New("cannot print unsigned tx: tx json encoder is nil")
}
tx, err := f.getTx()
if err != nil {
return "", err
}
json, err := encoder(tx)
if err != nil {
return "", err
}
return fmt.Sprintf("%s\n", json), nil
}
// 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 ...transaction.Msg) ([]byte, error) {
err := f.BuildUnsignedTx(msgs...)
if err != nil {
return nil, err
}
pk, err := f.getSimPK()
if err != nil {
return nil, err
}
// Create an empty signature literal as the ante handler will populate with a
// sentinel pubkey.
sig := Signature{
PubKey: pk,
Data: f.getSimSignatureData(pk),
Sequence: f.sequence(),
}
if err := f.setSignatures(sig); err != nil {
return nil, err
}
encoder := f.txConfig.TxEncoder()
if encoder == nil {
return nil, fmt.Errorf("cannot simulate tx: tx encoder is nil")
}
tx, err := f.getTx()
if err != nil {
return nil, err
}
return encoder(tx)
}
// sign signs a given tx with a named key. The bytes signed over are canonical.
// The resulting signature will be added to the transaction builder overwriting the previous
// ones if overwrite=true (otherwise, the signature will be appended).
// Signing a transaction with multiple signers in the DIRECT mode is not supported and will
// return an error.
func (f *Factory) sign(ctx context.Context, overwriteSig bool) (Tx, error) {
if f.keybase == nil {
return nil, errors.New("keybase must be set prior to signing a transaction")
}
var err error
if f.txParams.SignMode == apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED {
f.txParams.SignMode = f.txConfig.SignModeHandler().DefaultMode()
}
pubKey, err := f.keybase.GetPubKey(f.txParams.FromName)
if err != nil {
return nil, err
}
addr, err := f.ac.BytesToString(pubKey.Address())
if err != nil {
return nil, err
}
signerData := signing.SignerData{
ChainID: f.txParams.ChainID,
AccountNumber: f.txParams.AccountNumber,
Sequence: f.txParams.Sequence,
PubKey: &anypb.Any{
TypeUrl: codectypes.MsgTypeURL(pubKey),
Value: pubKey.Bytes(),
},
Address: addr,
}
// For SIGN_MODE_DIRECT, we need to set the SignerInfos before generating
// the sign bytes. This is done by calling setSignatures with a nil
// signature, which in turn calls setSignerInfos internally.
//
// For SIGN_MODE_LEGACY_AMINO, this step is not strictly necessary,
// but we include it for consistency across all sign modes.
// It does not affect the generated sign bytes for LEGACY_AMINO.
//
// By setting the signatures here, we ensure that the correct SignerInfos
// are in place for all subsequent operations, regardless of the sign mode.
sigData := SingleSignatureData{
SignMode: f.txParams.SignMode,
Signature: nil,
}
sig := Signature{
PubKey: pubKey,
Data: &sigData,
Sequence: f.txParams.Sequence,
}
var prevSignatures []Signature
if !overwriteSig {
tx, err := f.getTx()
if err != nil {
return nil, err
}
prevSignatures, err = tx.GetSignatures()
if err != nil {
return nil, err
}
}
// Overwrite or append signer infos.
var sigs []Signature
if overwriteSig {
sigs = []Signature{sig}
} else {
sigs = append(sigs, prevSignatures...)
sigs = append(sigs, sig)
}
if err := f.setSignatures(sigs...); err != nil {
return nil, err
}
tx, err := f.getTx()
if err != nil {
return nil, err
}
if err := checkMultipleSigners(tx); err != nil {
return nil, err
}
bytesToSign, err := f.getSignBytesAdapter(ctx, signerData)
if err != nil {
return nil, err
}
// Sign those bytes
sigBytes, err := f.keybase.Sign(f.txParams.FromName, bytesToSign, f.txParams.SignMode)
if err != nil {
return nil, err
}
// Construct the SignatureV2 struct
sigData = SingleSignatureData{
SignMode: f.signMode(),
Signature: sigBytes,
}
sig = Signature{
PubKey: pubKey,
Data: &sigData,
Sequence: f.txParams.Sequence,
}
if overwriteSig {
err = f.setSignatures(sig)
} else {
prevSignatures = append(prevSignatures, sig)
err = f.setSignatures(prevSignatures...)
}
if err != nil {
return nil, fmt.Errorf("unable to set signatures on payload: %w", err)
}
return f.getTx()
}
// getSignBytesAdapter returns the sign bytes for a given transaction and sign mode.
func (f *Factory) getSignBytesAdapter(ctx context.Context, signerData signing.SignerData) ([]byte, error) {
txData, err := f.getSigningTxData()
if err != nil {
return nil, err
}
// Generate the bytes to be signed.
return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.signMode(), signerData, *txData)
}
// WithGas returns a copy of the Factory with an updated gas value.
func (f *Factory) WithGas(gas uint64) {
f.txParams.gas = gas
}
// WithSequence returns a copy of the Factory with an updated sequence number.
func (f *Factory) WithSequence(sequence uint64) {
f.txParams.Sequence = sequence
}
// WithAccountNumber returns a copy of the Factory with an updated account number.
func (f *Factory) WithAccountNumber(accnum uint64) {
f.txParams.AccountNumber = accnum
}
// sequence returns the sequence number.
func (f *Factory) sequence() uint64 { return f.txParams.Sequence }
// gasAdjustment returns the gas adjustment value.
func (f *Factory) gasAdjustment() float64 { return f.txParams.gasAdjustment }
// simulateAndExecute returns whether to simulate and execute.
func (f *Factory) simulateAndExecute() bool { return f.txParams.simulateAndExecute }
// signMode returns the sign mode.
func (f *Factory) signMode() apitxsigning.SignMode { return f.txParams.SignMode }
// getSimPK gets the public key to use for building a simulation tx.
// Note, we should only check for keys in the keybase if we are in simulate and execute mode,
// e.g. when using --gas=auto.
// When using --dry-run, we are in simulation mode only and should not check the keybase.
// Ref: https://github.com/cosmos/cosmos-sdk/issues/11283
func (f *Factory) getSimPK() (cryptotypes.PubKey, error) {
var (
err error
pk cryptotypes.PubKey = &secp256k1.PubKey{}
)
if f.txParams.simulateAndExecute && f.keybase != nil {
pk, err = f.keybase.GetPubKey(f.txParams.FromName)
if err != nil {
return nil, err
}
} else {
// When in dry-run mode, attempt to retrieve the account using the provided address.
// If the account retrieval fails, the default public key is used.
acc, err := f.accountRetriever.GetAccount(context.Background(), f.txParams.Address)
if err != nil {
// If there is an error retrieving the account, return the default public key.
return pk, nil
}
// If the account is successfully retrieved, use its public key.
pk = acc.GetPubKey()
}
return pk, nil
}
// getSimSignatureData based on the pubKey type gets the correct SignatureData type
// to use for building a simulation tx.
func (f *Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData {
multisigPubKey, ok := pk.(*multisig.LegacyAminoPubKey)
if !ok {
return &SingleSignatureData{SignMode: f.txParams.SignMode}
}
multiSignatureData := make([]SignatureData, 0, multisigPubKey.Threshold)
for i := uint32(0); i < multisigPubKey.Threshold; i++ {
multiSignatureData = append(multiSignatureData, &SingleSignatureData{
SignMode: f.signMode(),
})
}
return &MultiSignatureData{
BitArray: &apicrypto.CompactBitArray{},
Signatures: multiSignatureData,
}
}
func (f *Factory) getTx() (*wrappedTx, error) {
msgs, err := msgsV1toAnyV2(f.tx.msgs)
if err != nil {
return nil, err
}
body := &apitx.TxBody{
Messages: msgs,
Memo: f.tx.memo,
TimeoutHeight: f.tx.timeoutHeight,
TimeoutTimestamp: timestamppb.New(f.tx.timeoutTimestamp),
Unordered: f.tx.unordered,
ExtensionOptions: f.tx.extensionOptions,
NonCriticalExtensionOptions: f.tx.nonCriticalExtensionOptions,
}
fee, err := f.getFee()
if err != nil {
return nil, err
}
authInfo := &apitx.AuthInfo{
SignerInfos: f.tx.signerInfos,
Fee: fee,
}
bodyBytes, err := marshalOption.Marshal(body)
if err != nil {
return nil, err
}
authInfoBytes, err := marshalOption.Marshal(authInfo)
if err != nil {
return nil, err
}
txRawBytes, err := marshalOption.Marshal(&apitx.TxRaw{
BodyBytes: bodyBytes,
AuthInfoBytes: authInfoBytes,
Signatures: f.tx.signatures,
})
if err != nil {
return nil, err
}
decodedTx, err := f.txConfig.Decoder().Decode(txRawBytes)
if err != nil {
return nil, err
}
return newWrapperTx(f.cdc, decodedTx), nil
}
// getSigningTxData returns a TxData with the current txState info.
func (f *Factory) getSigningTxData() (*signing.TxData, error) {
tx, err := f.getTx()
if err != nil {
return nil, err
}
return &signing.TxData{
Body: tx.Tx.Body,
AuthInfo: tx.Tx.AuthInfo,
BodyBytes: tx.TxRaw.BodyBytes,
AuthInfoBytes: tx.TxRaw.AuthInfoBytes,
BodyHasUnknownNonCriticals: tx.TxBodyHasUnknownNonCriticals,
}, nil
}
// setSignatures sets the signatures for the transaction builder.
// It takes a variable number of Signature arguments and processes each one to extract the mode information and raw signature.
// It also converts the public key to the appropriate format and sets the signer information.
func (f *Factory) setSignatures(signatures ...Signature) error {
n := len(signatures)
signerInfos := make([]*apitx.SignerInfo, n)
rawSignatures := make([][]byte, n)
for i, sig := range signatures {
var (
modeInfo *apitx.ModeInfo
pubKey *codectypes.Any
err error
anyPk *anypb.Any
)
modeInfo, rawSignatures[i] = signatureDataToModeInfoAndSig(sig.Data)
if sig.PubKey != nil {
pubKey, err = codectypes.NewAnyWithValue(sig.PubKey)
if err != nil {
return err
}
anyPk = &anypb.Any{
TypeUrl: pubKey.TypeUrl,
Value: pubKey.Value,
}
}
signerInfos[i] = &apitx.SignerInfo{
PublicKey: anyPk,
ModeInfo: modeInfo,
Sequence: sig.Sequence,
}
}
f.tx.signerInfos = signerInfos
f.tx.signatures = rawSignatures
return nil
}
// getFee computes the transaction fee information.
// It returns a pointer to an apitx.Fee struct containing the fee amount, gas limit, payer, and granter information.
// If the granter or payer addresses are set, it converts them from bytes to string using the addressCodec.
func (f *Factory) getFee() (fee *apitx.Fee, err error) {
granterStr := ""
if f.tx.granter != nil {
granterStr, err = f.ac.BytesToString(f.tx.granter)
if err != nil {
return nil, err
}
}
payerStr := ""
if f.tx.payer != nil {
payerStr, err = f.ac.BytesToString(f.tx.payer)
if err != nil {
return nil, err
}
}
fee = &apitx.Fee{
Amount: f.tx.fees,
GasLimit: f.tx.gasLimit,
Payer: payerStr,
Granter: granterStr,
}
return fee, nil
}
// setFeePayer sets the fee payer for the transaction.
func (f *Factory) setFeePayer(feePayer string) error {
if feePayer == "" {
return nil
}
addr, err := f.ac.StringToBytes(feePayer)
if err != nil {
return err
}
f.tx.payer = addr
return nil
}
// setFeeGranter sets the fee granter's address in the transaction builder.
// If the feeGranter string is empty, the function returns nil without setting an address.
// It converts the feeGranter string to bytes using the address codec and sets it as the granter address.
// Returns an error if the conversion fails.
func (f *Factory) setFeeGranter(feeGranter string) error {
if feeGranter == "" {
return nil
}
addr, err := f.ac.StringToBytes(feeGranter)
if err != nil {
return err
}
f.tx.granter = addr
return nil
}
// msgsV1toAnyV2 converts a slice of transaction.Msg (v1) to a slice of anypb.Any (v2).
// It first converts each transaction.Msg into a codectypes.Any and then converts
// these into anypb.Any.
func msgsV1toAnyV2(msgs []transaction.Msg) ([]*anypb.Any, error) {
anys := make([]*codectypes.Any, len(msgs))
for i, msg := range msgs {
anyMsg, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, err
}
anys[i] = anyMsg
}
return intoAnyV2(anys), nil
}
// intoAnyV2 converts a slice of codectypes.Any (v1) to a slice of anypb.Any (v2).
func intoAnyV2(v1s []*codectypes.Any) []*anypb.Any {
v2s := make([]*anypb.Any, len(v1s))
for i, v1 := range v1s {
v2s[i] = &anypb.Any{
TypeUrl: v1.TypeUrl,
Value: v1.Value,
}
}
return v2s
}
// checkMultipleSigners checks that there can be maximum one DIRECT signer in
// a tx.
func checkMultipleSigners(tx Tx) error {
directSigners := 0
sigsV2, err := tx.GetSignatures()
if err != nil {
return err
}
for _, sig := range sigsV2 {
directSigners += countDirectSigners(sig.Data)
if directSigners > 1 {
return errors.New("txs signed with CLI can have maximum 1 DIRECT signer")
}
}
return nil
}
// validateMemo validates the memo field.
func validateMemo(memo string) error {
// Prevent simple inclusion of a valid mnemonic in the memo field
if memo != "" && bip39.IsMnemonicValid(strings.ToLower(memo)) {
return errors.New("cannot provide a valid mnemonic seed in the memo field")
}
return nil
}