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

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