github.com/Finschia/finschia-sdk@v0.48.1/client/broadcast.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"google.golang.org/grpc/codes"
     9  	"google.golang.org/grpc/status"
    10  
    11  	"github.com/Finschia/ostracon/mempool"
    12  	octypes "github.com/Finschia/ostracon/types"
    13  
    14  	"github.com/Finschia/finschia-sdk/client/flags"
    15  	sdk "github.com/Finschia/finschia-sdk/types"
    16  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
    17  	"github.com/Finschia/finschia-sdk/types/tx"
    18  )
    19  
    20  // BroadcastTx broadcasts a transactions either synchronously or asynchronously
    21  // based on the context parameters. The result of the broadcast is parsed into
    22  // an intermediate structure which is logged if the context has a logger
    23  // defined.
    24  func (ctx Context) BroadcastTx(txBytes []byte) (res *sdk.TxResponse, err error) {
    25  	switch ctx.BroadcastMode {
    26  	case flags.BroadcastSync:
    27  		res, err = ctx.BroadcastTxSync(txBytes)
    28  
    29  	case flags.BroadcastAsync:
    30  		res, err = ctx.BroadcastTxAsync(txBytes)
    31  
    32  	case flags.BroadcastBlock:
    33  		res, err = ctx.BroadcastTxCommit(txBytes)
    34  
    35  	default:
    36  		return nil, fmt.Errorf("unsupported return type %s; supported types: sync, async, block", ctx.BroadcastMode)
    37  	}
    38  
    39  	return res, err
    40  }
    41  
    42  // CheckTendermintError checks if the error returned from BroadcastTx is a
    43  // Tendermint error that is returned before the tx is submitted due to
    44  // precondition checks that failed. If an Tendermint error is detected, this
    45  // function returns the correct code back in TxResponse.
    46  //
    47  // TODO: Avoid brittle string matching in favor of error matching. This requires
    48  // a change to Tendermint's RPCError type to allow retrieval or matching against
    49  // a concrete error type.
    50  func CheckTendermintError(err error, tx octypes.Tx) *sdk.TxResponse {
    51  	if err == nil {
    52  		return nil
    53  	}
    54  
    55  	errStr := strings.ToLower(err.Error())
    56  	txHash := fmt.Sprintf("%X", tx.Hash())
    57  
    58  	switch {
    59  	case strings.Contains(errStr, strings.ToLower(mempool.ErrTxInCache.Error())),
    60  		strings.Contains(errStr, strings.ToLower(mempool.ErrTxInMap.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  // BroadcastTxCommit broadcasts transaction bytes to a Tendermint node and
    87  // waits for a commit. An error is only returned if there is no RPC node
    88  // connection or if broadcasting fails.
    89  //
    90  // NOTE: This should ideally not be used as the request may timeout but the tx
    91  // may still be included in a block. Use BroadcastTxAsync or BroadcastTxSync
    92  // instead.
    93  func (ctx Context) BroadcastTxCommit(txBytes []byte) (*sdk.TxResponse, error) {
    94  	node, err := ctx.GetNode()
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	res, err := node.BroadcastTxCommit(context.Background(), txBytes)
   100  	if err == nil {
   101  		return sdk.NewResponseFormatBroadcastTxCommit(res), nil
   102  	}
   103  
   104  	if errRes := CheckTendermintError(err, txBytes); errRes != nil {
   105  		return errRes, nil
   106  	}
   107  	return sdk.NewResponseFormatBroadcastTxCommit(res), err
   108  }
   109  
   110  // BroadcastTxSync broadcasts transaction bytes to a Tendermint node
   111  // synchronously (i.e. returns after CheckTx execution).
   112  func (ctx Context) BroadcastTxSync(txBytes []byte) (*sdk.TxResponse, error) {
   113  	node, err := ctx.GetNode()
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	res, err := node.BroadcastTxSync(context.Background(), txBytes)
   119  	if errRes := CheckTendermintError(err, txBytes); errRes != nil {
   120  		return errRes, nil
   121  	}
   122  
   123  	return sdk.NewResponseFormatBroadcastTx(res), err
   124  }
   125  
   126  // BroadcastTxAsync broadcasts transaction bytes to a Tendermint node
   127  // asynchronously (i.e. returns immediately).
   128  func (ctx Context) BroadcastTxAsync(txBytes []byte) (*sdk.TxResponse, error) {
   129  	node, err := ctx.GetNode()
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	res, err := node.BroadcastTxAsync(context.Background(), txBytes)
   135  	if errRes := CheckTendermintError(err, txBytes); errRes != nil {
   136  		return errRes, nil
   137  	}
   138  
   139  	return sdk.NewResponseFormatBroadcastTx(res), err
   140  }
   141  
   142  // TxServiceBroadcast is a helper function to broadcast a Tx with the correct gRPC types
   143  // from the tx service. Calls `clientCtx.BroadcastTx` under the hood.
   144  func TxServiceBroadcast(grpcCtx context.Context, clientCtx Context, req *tx.BroadcastTxRequest) (*tx.BroadcastTxResponse, error) {
   145  	if req == nil || req.TxBytes == nil {
   146  		return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
   147  	}
   148  
   149  	clientCtx = clientCtx.WithBroadcastMode(normalizeBroadcastMode(req.Mode))
   150  	resp, err := clientCtx.BroadcastTx(req.TxBytes)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	return &tx.BroadcastTxResponse{
   156  		TxResponse: resp,
   157  	}, nil
   158  }
   159  
   160  // normalizeBroadcastMode converts a broadcast mode into a normalized string
   161  // to be passed into the clientCtx.
   162  func normalizeBroadcastMode(mode tx.BroadcastMode) string {
   163  	switch mode {
   164  	case tx.BroadcastMode_BROADCAST_MODE_ASYNC:
   165  		return "async"
   166  	case tx.BroadcastMode_BROADCAST_MODE_BLOCK:
   167  		return "block"
   168  	case tx.BroadcastMode_BROADCAST_MODE_SYNC:
   169  		return "sync"
   170  	default:
   171  		return "unspecified"
   172  	}
   173  }