cosmos-sdk/client/v2/tx
Julián Toledano c8f4cf787b
feat(client/v2): factory (#20623)
Co-authored-by: Julien Robert <julien@rbrt.fr>
2024-10-03 12:45:10 +00:00
..
common_test.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
config_test.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
config.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
encoder_test.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
encoder.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
factory_test.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
factory.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
flags.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
README.md feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
signature_test.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
signature.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
tx.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
types.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00
wrapper.go feat(client/v2): factory (#20623) 2024-10-03 12:45:10 +00:00

The tx package provides a robust set of tools for building, signing, and managing transactions in a Cosmos SDK-based blockchain application.

Overview

This package includes several key components:

  1. Transaction Factory
  2. Transaction Config
  3. Transaction Encoder/Decoder
  4. Signature Handling

Architecture

graph TD
    A[Client] --> B[Factory]
    B --> D[TxConfig]
    D --> E[TxEncodingConfig]
    D --> F[TxSigningConfig]
    B --> G[Tx]
    G --> H[Encoder]
    G --> I[Decoder]
    F --> J[SignModeHandler]
    F --> K[SigningContext]
    B --> L[AuxTxBuilder]

Key Components

TxConfig

TxConfig provides configuration for transaction handling, including:

  • Encoding and decoding
  • Sign mode handling
  • Signature JSON marshaling/unmarshaling
classDiagram
    class TxConfig {
        <<interface>>
        TxEncodingConfig
        TxSigningConfig
    }

    class TxEncodingConfig {
        <<interface>>
        TxEncoder() txEncoder
        TxDecoder() txDecoder
        TxJSONEncoder() txEncoder
        TxJSONDecoder() txDecoder
        Decoder() Decoder
    }

    class TxSigningConfig {
        <<interface>>
        SignModeHandler() *signing.HandlerMap
        SigningContext() *signing.Context
        MarshalSignatureJSON([]Signature) ([]byte, error)
        UnmarshalSignatureJSON([]byte) ([]Signature, error)
    }

    class txConfig {
        TxEncodingConfig
        TxSigningConfig
    }

    class defaultEncodingConfig {
        cdc codec.BinaryCodec
        decoder Decoder
        TxEncoder() txEncoder
        TxDecoder() txDecoder
        TxJSONEncoder() txEncoder
        TxJSONDecoder() txDecoder
    }

    class defaultTxSigningConfig {
        signingCtx *signing.Context
        handlerMap *signing.HandlerMap
        cdc codec.BinaryCodec
        SignModeHandler() *signing.HandlerMap
        SigningContext() *signing.Context
        MarshalSignatureJSON([]Signature) ([]byte, error)
        UnmarshalSignatureJSON([]byte) ([]Signature, error)
    }

    TxConfig <|-- txConfig
    TxEncodingConfig <|.. defaultEncodingConfig
    TxSigningConfig <|.. defaultTxSigningConfig
    txConfig *-- defaultEncodingConfig
    txConfig *-- defaultTxSigningConfig

Factory

The Factory is the main entry point for creating and managing transactions. It handles:

  • Account preparation
  • Gas calculation
  • Unsigned transaction building
  • Transaction signing
  • Transaction simulation
  • Transaction broadcasting
classDiagram
    class Factory {
        keybase keyring.Keyring
        cdc codec.BinaryCodec
        accountRetriever account.AccountRetriever
        ac address.Codec
        conn gogogrpc.ClientConn
        txConfig TxConfig
        txParams TxParameters
        tx txState

        NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, parameters) Factory
        Prepare() error
        BuildUnsignedTx(msgs ...transaction.Msg) error
        BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) (Tx, error)
        calculateGas(msgs ...transaction.Msg) error
        Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error)
        UnsignedTxString(msgs ...transaction.Msg) (string, error)
        BuildSimTx(msgs ...transaction.Msg) ([]byte, error)
        sign(ctx context.Context, overwriteSig bool) (Tx, error)
        WithGas(gas uint64)
        WithSequence(sequence uint64)
        WithAccountNumber(accnum uint64)
        getTx() (Tx, error)
        getFee() (*apitx.Fee, error)
        getSigningTxData() (signing.TxData, error)
        setSignatures(...Signature) error
    }

    class TxParameters {
        <<struct>>
        chainID string
        AccountConfig
        GasConfig
        FeeConfig
        SignModeConfig
        TimeoutConfig
        MemoConfig
    }

    class TxConfig {
        <<interface>>
    }

    class Tx {
        <<interface>>
    }

    class txState {
        <<struct>>
        msgs []transaction.Msg
        memo string
        fees []*base.Coin
        gasLimit uint64
        feeGranter []byte
        feePayer []byte
        timeoutHeight uint64
        unordered bool
        timeoutTimestamp uint64
        signatures []Signature
        signerInfos []*apitx.SignerInfo
    }

    Factory *-- TxParameters
    Factory *-- TxConfig
    Factory *-- txState
    Factory ..> Tx : creates

Encoder/Decoder

The package includes functions for encoding and decoding transactions in both binary and JSON formats.

classDiagram
    class Decoder {
        <<interface>>
        Decode(txBytes []byte) (*txdecode.DecodedTx, error)
    }

    class txDecoder {
        <<function>>
        decode(txBytes []byte) (Tx, error)
    }

    class txEncoder {
        <<function>>
        encode(tx Tx) ([]byte, error)
    }

    class EncoderUtils {
        <<utility>>
        decodeTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder
        encodeTx(tx Tx) ([]byte, error)
        decodeJsonTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder
        encodeJsonTx(tx Tx) ([]byte, error)
        protoTxBytes(tx *txv1beta1.Tx) ([]byte, error)
    }

    class MarshalOptions {
        <<utility>>
        Deterministic bool
    }

    class JSONMarshalOptions {
        <<utility>>
        Indent string
        UseProtoNames bool
        UseEnumNumbers bool
    }

    Decoder <.. EncoderUtils : uses
    txDecoder <.. EncoderUtils : creates
    txEncoder <.. EncoderUtils : implements
    EncoderUtils ..> MarshalOptions : uses
    EncoderUtils ..> JSONMarshalOptions : uses

Sequence Diagrams

Generate Aux Signer Data

sequenceDiagram
    participant User
    participant GenerateOrBroadcastTxCLI
    participant generateAuxSignerData
    participant makeAuxSignerData
    participant AuxTxBuilder
    participant ctx.PrintProto

    User->>GenerateOrBroadcastTxCLI: Call with isAux flag
    GenerateOrBroadcastTxCLI->>generateAuxSignerData: Call

    generateAuxSignerData->>makeAuxSignerData: Call
    makeAuxSignerData->>AuxTxBuilder: NewAuxTxBuilder()
    
    makeAuxSignerData->>AuxTxBuilder: SetAddress(f.txParams.fromAddress)
    
    alt f.txParams.offline
        makeAuxSignerData->>AuxTxBuilder: SetAccountNumber(f.AccountNumber())
        makeAuxSignerData->>AuxTxBuilder: SetSequence(f.Sequence())
    else
        makeAuxSignerData->>f.accountRetriever: GetAccountNumberSequence()
        makeAuxSignerData->>AuxTxBuilder: SetAccountNumber(accNum)
        makeAuxSignerData->>AuxTxBuilder: SetSequence(seq)
    end
    
    makeAuxSignerData->>AuxTxBuilder: SetMsgs(msgs...)
    makeAuxSignerData->>AuxTxBuilder: SetSignMode(f.SignMode())
    
    makeAuxSignerData->>f.keybase: GetPubKey(f.txParams.fromName)
    makeAuxSignerData->>AuxTxBuilder: SetPubKey(pubKey)
    
    makeAuxSignerData->>AuxTxBuilder: SetChainID(f.txParams.chainID)
    makeAuxSignerData->>AuxTxBuilder: GetSignBytes()
    
    makeAuxSignerData->>f.keybase: Sign(f.txParams.fromName, signBz, f.SignMode())
    makeAuxSignerData->>AuxTxBuilder: SetSignature(sig)
    
    makeAuxSignerData->>AuxTxBuilder: GetAuxSignerData()
    AuxTxBuilder-->>makeAuxSignerData: Return AuxSignerData
    makeAuxSignerData-->>generateAuxSignerData: Return AuxSignerData
    
    generateAuxSignerData->>ctx.PrintProto: Print AuxSignerData
    ctx.PrintProto-->>GenerateOrBroadcastTxCLI: Return result
    GenerateOrBroadcastTxCLI-->>User: Return result

Generate Only

sequenceDiagram
    participant User
    participant GenerateOrBroadcastTxCLI
    participant generateOnly
    participant Factory
    participant ctx.PrintString

    User->>GenerateOrBroadcastTxCLI: Call with generateOnly flag
    GenerateOrBroadcastTxCLI->>generateOnly: Call

    generateOnly->>Factory: Prepare()
    alt Error in Prepare
        Factory-->>generateOnly: Return error
        generateOnly-->>GenerateOrBroadcastTxCLI: Return error
        GenerateOrBroadcastTxCLI-->>User: Return error
    end

    generateOnly->>Factory: UnsignedTxString(msgs...)
    Factory->>Factory: BuildUnsignedTx(msgs...)
    Factory->>Factory: setMsgs(msgs...)
    Factory->>Factory: setMemo(f.txParams.memo)
    Factory->>Factory: setFees(f.txParams.gasPrices)
    Factory->>Factory: setGasLimit(f.txParams.gas)
    Factory->>Factory: setFeeGranter(f.txParams.feeGranter)
    Factory->>Factory: setFeePayer(f.txParams.feePayer)
    Factory->>Factory: setTimeoutHeight(f.txParams.timeoutHeight)

    Factory->>Factory: getTx()
    Factory->>Factory: txConfig.TxJSONEncoder()
    Factory->>Factory: encoder(tx)

    Factory-->>generateOnly: Return unsigned tx string
    generateOnly->>ctx.PrintString: Print unsigned tx string
    ctx.PrintString-->>generateOnly: Return result
    generateOnly-->>GenerateOrBroadcastTxCLI: Return result
    GenerateOrBroadcastTxCLI-->>User: Return result

DryRun

sequenceDiagram
    participant User
    participant GenerateOrBroadcastTxCLI
    participant dryRun
    participant Factory
    participant os.Stderr

    User->>GenerateOrBroadcastTxCLI: Call with dryRun flag
    GenerateOrBroadcastTxCLI->>dryRun: Call

    dryRun->>Factory: Prepare()
    alt Error in Prepare
        Factory-->>dryRun: Return error
        dryRun-->>GenerateOrBroadcastTxCLI: Return error
        GenerateOrBroadcastTxCLI-->>User: Return error
    end

    dryRun->>Factory: Simulate(msgs...)
    Factory->>Factory: BuildSimTx(msgs...)
    Factory->>Factory: BuildUnsignedTx(msgs...)
    Factory->>Factory: getSimPK()
    Factory->>Factory: getSimSignatureData(pk)
    Factory->>Factory: setSignatures(sig)
    Factory->>Factory: getTx()
    Factory->>Factory: txConfig.TxEncoder()(tx)
    
    Factory->>ServiceClient: Simulate(context.Background(), &apitx.SimulateRequest{})
    ServiceClient->>Factory: Return result
    
    Factory-->>dryRun: Return (simulation, gas, error)
    alt Error in Simulate
        dryRun-->>GenerateOrBroadcastTxCLI: Return error
        GenerateOrBroadcastTxCLI-->>User: Return error
    end

    dryRun->>os.Stderr: Fprintf(GasEstimateResponse{GasEstimate: gas})
    os.Stderr-->>dryRun: Return result
    dryRun-->>GenerateOrBroadcastTxCLI: Return result
    GenerateOrBroadcastTxCLI-->>User: Return result

Generate and Broadcast Tx

sequenceDiagram
    participant User
    participant GenerateOrBroadcastTxCLI
    participant BroadcastTx
    participant Factory
    participant clientCtx

    User->>GenerateOrBroadcastTxCLI: Call
    GenerateOrBroadcastTxCLI->>BroadcastTx: Call

    BroadcastTx->>Factory: Prepare()
    alt Error in Prepare
        Factory-->>BroadcastTx: Return error
        BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error
        GenerateOrBroadcastTxCLI-->>User: Return error
    end

    alt SimulateAndExecute is true
        BroadcastTx->>Factory: calculateGas(msgs...)
        Factory->>Factory: Simulate(msgs...)
        Factory->>Factory: WithGas(adjusted)
    end

    BroadcastTx->>Factory: BuildUnsignedTx(msgs...)
    Factory->>Factory: setMsgs(msgs...)
    Factory->>Factory: setMemo(f.txParams.memo)
    Factory->>Factory: setFees(f.txParams.gasPrices)
    Factory->>Factory: setGasLimit(f.txParams.gas)
    Factory->>Factory: setFeeGranter(f.txParams.feeGranter)
    Factory->>Factory: setFeePayer(f.txParams.feePayer)
    Factory->>Factory: setTimeoutHeight(f.txParams.timeoutHeight)

    alt !clientCtx.SkipConfirm
        BroadcastTx->>Factory: getTx()
        BroadcastTx->>Factory: txConfig.TxJSONEncoder()
        BroadcastTx->>clientCtx: PrintRaw(txBytes)
        BroadcastTx->>clientCtx: Input.GetConfirmation()
        alt Not confirmed
            BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error
            GenerateOrBroadcastTxCLI-->>User: Return error
        end
    end

    BroadcastTx->>Factory: BuildsSignedTx(ctx, msgs...)
    Factory->>Factory: sign(ctx, true)
    Factory->>Factory: keybase.GetPubKey(fromName)
    Factory->>Factory: getSignBytesAdapter()
    Factory->>Factory: keybase.Sign(fromName, bytesToSign, signMode)
    Factory->>Factory: setSignatures(sig)
    Factory->>Factory: getTx()

    BroadcastTx->>Factory: txConfig.TxEncoder()
    BroadcastTx->>clientCtx: BroadcastTx(txBytes)

    alt Error in BroadcastTx
        clientCtx-->>BroadcastTx: Return error
        BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error
        GenerateOrBroadcastTxCLI-->>User: Return error
    end

    BroadcastTx->>clientCtx: OutputTx(res)
    clientCtx-->>BroadcastTx: Return result
    BroadcastTx-->>GenerateOrBroadcastTxCLI: Return result
    GenerateOrBroadcastTxCLI-->>User: Return result

Usage

To use the tx package, typically you would:

  1. Create a Factory
  2. Simulate the transaction (optional)
  3. Build a signed transaction
  4. Encode the transaction
  5. Broadcast the transaction

Here's a simplified example:

// Create a Factory
factory, err := NewFactory(keybase, cdc, accountRetriever, txConfig, addressCodec, conn, txParameters)
if err != nil {
    return err
}

// Simulate the transaction (optional)
simRes, gas, err := factory.Simulate(msgs...)
if err != nil {
    return err
}
factory.WithGas(gas)

// Build a signed transaction
signedTx, err := factory.BuildsSignedTx(context.Background(), msgs...)
if err != nil {
    return err
}

// Encode the transaction
txBytes, err := factory.txConfig.TxEncoder()(signedTx)
if err != nil {
    return err
}

// Broadcast the transaction
// (This step depends on your specific client implementation)