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 }