cosmos-sdk/client/broadcast.go

172 lines
5.1 KiB
Go

package client
import (
"context"
"fmt"
"strings"
"github.com/tendermint/tendermint/mempool"
tmtypes "github.com/tendermint/tendermint/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx"
)
// BroadcastTx broadcasts a transactions either synchronously or asynchronously
// based on the context parameters. The result of the broadcast is parsed into
// an intermediate structure which is logged if the context has a logger
// defined.
func (ctx Context) BroadcastTx(txBytes []byte) (res *sdk.TxResponse, err error) {
switch ctx.BroadcastMode {
case flags.BroadcastSync:
res, err = ctx.BroadcastTxSync(txBytes)
case flags.BroadcastAsync:
res, err = ctx.BroadcastTxAsync(txBytes)
case flags.BroadcastBlock:
res, err = ctx.BroadcastTxCommit(txBytes)
default:
return nil, fmt.Errorf("unsupported return type %s; supported types: sync, async, block", ctx.BroadcastMode)
}
return res, err
}
// CheckTendermintError checks if the error returned from BroadcastTx is a
// Tendermint error that is returned before the tx is submitted due to
// precondition checks that failed. If an Tendermint error is detected, this
// function returns the correct code back in TxResponse.
//
// TODO: Avoid brittle string matching in favor of error matching. This requires
// a change to Tendermint's RPCError type to allow retrieval or matching against
// a concrete error type.
func CheckTendermintError(err error, tx tmtypes.Tx) *sdk.TxResponse {
if err == nil {
return nil
}
errStr := strings.ToLower(err.Error())
txHash := fmt.Sprintf("%X", tx.Hash())
switch {
case strings.Contains(errStr, strings.ToLower(mempool.ErrTxInCache.Error())):
return &sdk.TxResponse{
Code: sdkerrors.ErrTxInMempoolCache.ABCICode(),
Codespace: sdkerrors.ErrTxInMempoolCache.Codespace(),
TxHash: txHash,
}
case strings.Contains(errStr, "mempool is full"):
return &sdk.TxResponse{
Code: sdkerrors.ErrMempoolIsFull.ABCICode(),
Codespace: sdkerrors.ErrMempoolIsFull.Codespace(),
TxHash: txHash,
}
case strings.Contains(errStr, "tx too large"):
return &sdk.TxResponse{
Code: sdkerrors.ErrTxTooLarge.ABCICode(),
Codespace: sdkerrors.ErrTxTooLarge.Codespace(),
TxHash: txHash,
}
default:
return nil
}
}
// BroadcastTxCommit broadcasts transaction bytes to a Tendermint node and
// waits for a commit. An error is only returned if there is no RPC node
// connection or if broadcasting fails.
//
// NOTE: This should ideally not be used as the request may timeout but the tx
// may still be included in a block. Use BroadcastTxAsync or BroadcastTxSync
// instead.
func (ctx Context) BroadcastTxCommit(txBytes []byte) (*sdk.TxResponse, error) {
node, err := ctx.GetNode()
if err != nil {
return nil, err
}
res, err := node.BroadcastTxCommit(context.Background(), txBytes)
if err == nil {
return sdk.NewResponseFormatBroadcastTxCommit(res), nil
}
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
return errRes, nil
}
return sdk.NewResponseFormatBroadcastTxCommit(res), err
}
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node
// synchronously (i.e. returns after CheckTx execution).
func (ctx Context) BroadcastTxSync(txBytes []byte) (*sdk.TxResponse, error) {
node, err := ctx.GetNode()
if err != nil {
return nil, err
}
res, err := node.BroadcastTxSync(context.Background(), txBytes)
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
return errRes, nil
}
return sdk.NewResponseFormatBroadcastTx(res), err
}
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node
// asynchronously (i.e. returns immediately).
func (ctx Context) BroadcastTxAsync(txBytes []byte) (*sdk.TxResponse, error) {
node, err := ctx.GetNode()
if err != nil {
return nil, err
}
res, err := node.BroadcastTxAsync(context.Background(), txBytes)
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
return errRes, nil
}
return sdk.NewResponseFormatBroadcastTx(res), err
}
// TxServiceBroadcast is a helper function to broadcast a Tx with the correct gRPC types
// from the tx service. Calls `clientCtx.BroadcastTx` under the hood.
func TxServiceBroadcast(grpcCtx context.Context, clientCtx Context, req *tx.BroadcastTxRequest) (*tx.BroadcastTxResponse, error) {
if req == nil || req.TxBytes == nil {
return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
}
clientCtx = clientCtx.WithBroadcastMode(normalizeBroadcastMode(req.Mode))
resp, err := clientCtx.BroadcastTx(req.TxBytes)
if err != nil {
return nil, err
}
return &tx.BroadcastTxResponse{
TxResponse: resp,
}, nil
}
// normalizeBroadcastMode converts a broadcast mode into a normalized string
// to be passed into the clientCtx.
func normalizeBroadcastMode(mode tx.BroadcastMode) string {
switch mode {
case tx.BroadcastMode_BROADCAST_MODE_ASYNC:
return "async"
case tx.BroadcastMode_BROADCAST_MODE_BLOCK:
return "block"
case tx.BroadcastMode_BROADCAST_MODE_SYNC:
return "sync"
default:
return "unspecified"
}
}