github.com/cosmos/cosmos-sdk@v0.50.10/client/broadcast.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/cometbft/cometbft/mempool"
     9  	cmttypes "github.com/cometbft/cometbft/types"
    10  	"google.golang.org/grpc/codes"
    11  	"google.golang.org/grpc/status"
    12  
    13  	"github.com/cosmos/cosmos-sdk/client/flags"
    14  	sdk "github.com/cosmos/cosmos-sdk/types"
    15  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    16  	"github.com/cosmos/cosmos-sdk/types/tx"
    17  )
    18  
    19  // BroadcastTx broadcasts a transactions either synchronously or asynchronously
    20  // based on the context parameters. The result of the broadcast is parsed into
    21  // an intermediate structure which is logged if the context has a logger
    22  // defined.
    23  func (ctx Context) BroadcastTx(txBytes []byte) (res *sdk.TxResponse, err error) {
    24  	switch ctx.BroadcastMode {
    25  	case flags.BroadcastSync:
    26  		res, err = ctx.BroadcastTxSync(txBytes)
    27  
    28  	case flags.BroadcastAsync:
    29  		res, err = ctx.BroadcastTxAsync(txBytes)
    30  
    31  	default:
    32  		return nil, fmt.Errorf("unsupported return type %s; supported types: sync, async", ctx.BroadcastMode)
    33  	}
    34  
    35  	return res, err
    36  }
    37  
    38  // Deprecated: Use CheckCometError instead.
    39  func CheckTendermintError(err error, tx cmttypes.Tx) *sdk.TxResponse {
    40  	return CheckCometError(err, tx)
    41  }
    42  
    43  // CheckCometError checks if the error returned from BroadcastTx is a
    44  // CometBFT error that is returned before the tx is submitted due to
    45  // precondition checks that failed. If an CometBFT error is detected, this
    46  // function returns the correct code back in TxResponse.
    47  //
    48  // TODO: Avoid brittle string matching in favor of error matching. This requires
    49  // a change to CometBFT's RPCError type to allow retrieval or matching against
    50  // a concrete error type.
    51  func CheckCometError(err error, tx cmttypes.Tx) *sdk.TxResponse {
    52  	if err == nil {
    53  		return nil
    54  	}
    55  
    56  	errStr := strings.ToLower(err.Error())
    57  	txHash := fmt.Sprintf("%X", tx.Hash())
    58  
    59  	switch {
    60  	case strings.Contains(errStr, strings.ToLower(mempool.ErrTxInCache.Error())):
    61  		return &sdk.TxResponse{
    62  			Code:      sdkerrors.ErrTxInMempoolCache.ABCICode(),
    63  			Codespace: sdkerrors.ErrTxInMempoolCache.Codespace(),
    64  			TxHash:    txHash,
    65  		}
    66  
    67  	case strings.Contains(errStr, "mempool is full"):
    68  		return &sdk.TxResponse{
    69  			Code:      sdkerrors.ErrMempoolIsFull.ABCICode(),
    70  			Codespace: sdkerrors.ErrMempoolIsFull.Codespace(),
    71  			TxHash:    txHash,
    72  		}
    73  
    74  	case strings.Contains(errStr, "tx too large"):
    75  		return &sdk.TxResponse{
    76  			Code:      sdkerrors.ErrTxTooLarge.ABCICode(),
    77  			Codespace: sdkerrors.ErrTxTooLarge.Codespace(),
    78  			TxHash:    txHash,
    79  		}
    80  
    81  	default:
    82  		return nil
    83  	}
    84  }
    85  
    86  // BroadcastTxSync broadcasts transaction bytes to a CometBFT node
    87  // synchronously (i.e. returns after CheckTx execution).
    88  func (ctx Context) BroadcastTxSync(txBytes []byte) (*sdk.TxResponse, error) {
    89  	node, err := ctx.GetNode()
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	res, err := node.BroadcastTxSync(context.Background(), txBytes)
    95  	if errRes := CheckCometError(err, txBytes); errRes != nil {
    96  		return errRes, nil
    97  	}
    98  
    99  	return sdk.NewResponseFormatBroadcastTx(res), err
   100  }
   101  
   102  // BroadcastTxAsync broadcasts transaction bytes to a CometBFT node
   103  // asynchronously (i.e. returns immediately).
   104  func (ctx Context) BroadcastTxAsync(txBytes []byte) (*sdk.TxResponse, error) {
   105  	node, err := ctx.GetNode()
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	res, err := node.BroadcastTxAsync(context.Background(), txBytes)
   111  	if errRes := CheckCometError(err, txBytes); errRes != nil {
   112  		return errRes, nil
   113  	}
   114  
   115  	return sdk.NewResponseFormatBroadcastTx(res), err
   116  }
   117  
   118  // TxServiceBroadcast is a helper function to broadcast a Tx with the correct gRPC types
   119  // from the tx service. Calls `clientCtx.BroadcastTx` under the hood.
   120  func TxServiceBroadcast(_ context.Context, clientCtx Context, req *tx.BroadcastTxRequest) (*tx.BroadcastTxResponse, error) {
   121  	if req == nil || req.TxBytes == nil {
   122  		return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
   123  	}
   124  
   125  	clientCtx = clientCtx.WithBroadcastMode(normalizeBroadcastMode(req.Mode))
   126  	resp, err := clientCtx.BroadcastTx(req.TxBytes)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	return &tx.BroadcastTxResponse{
   132  		TxResponse: resp,
   133  	}, nil
   134  }
   135  
   136  // normalizeBroadcastMode converts a broadcast mode into a normalized string
   137  // to be passed into the clientCtx.
   138  func normalizeBroadcastMode(mode tx.BroadcastMode) string {
   139  	switch mode {
   140  	case tx.BroadcastMode_BROADCAST_MODE_ASYNC:
   141  		return "async"
   142  	case tx.BroadcastMode_BROADCAST_MODE_SYNC:
   143  		return "sync"
   144  	default:
   145  		return "unspecified"
   146  	}
   147  }