144 lines
4.2 KiB
Go
144 lines
4.2 KiB
Go
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
"github.com/tendermint/tendermint/mempool"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
)
|
|
|
|
// 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 sdk.TxResponse{}, 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, txBytes []byte) *sdk.TxResponse {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
errStr := strings.ToLower(err.Error())
|
|
txHash := fmt.Sprintf("%X", tmhash.Sum(txBytes))
|
|
|
|
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 sdk.TxResponse{}, err
|
|
}
|
|
|
|
res, err := node.BroadcastTxCommit(txBytes)
|
|
if err != nil {
|
|
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
|
|
return *errRes, nil
|
|
}
|
|
|
|
return sdk.NewResponseFormatBroadcastTxCommit(res), err
|
|
}
|
|
|
|
if !res.CheckTx.IsOK() {
|
|
return sdk.NewResponseFormatBroadcastTxCommit(res), nil
|
|
}
|
|
|
|
if !res.DeliverTx.IsOK() {
|
|
return sdk.NewResponseFormatBroadcastTxCommit(res), nil
|
|
}
|
|
|
|
return sdk.NewResponseFormatBroadcastTxCommit(res), nil
|
|
}
|
|
|
|
// 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 sdk.TxResponse{}, err
|
|
}
|
|
|
|
res, err := node.BroadcastTxSync(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 sdk.TxResponse{}, err
|
|
}
|
|
|
|
res, err := node.BroadcastTxAsync(txBytes)
|
|
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
|
|
return *errRes, nil
|
|
}
|
|
|
|
return sdk.NewResponseFormatBroadcastTx(res), err
|
|
}
|