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 }