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 }