200 lines
6.0 KiB
Go
200 lines
6.0 KiB
Go
package comet
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/cometbft/cometbft/mempool"
|
|
rpcclient "github.com/cometbft/cometbft/rpc/client"
|
|
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
|
|
coretypes "github.com/cometbft/cometbft/rpc/core/types"
|
|
cmttypes "github.com/cometbft/cometbft/types"
|
|
|
|
apiacbci "cosmossdk.io/api/cosmos/base/abci/v1beta1"
|
|
"cosmossdk.io/client/v2/broadcast"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
)
|
|
|
|
const (
|
|
// BroadcastSync defines a tx broadcasting mode where the client waits for
|
|
// a CheckTx execution response only.
|
|
BroadcastSync = "sync"
|
|
// BroadcastAsync defines a tx broadcasting mode where the client returns
|
|
// immediately.
|
|
BroadcastAsync = "async"
|
|
|
|
// cometBftConsensus is the identifier for the CometBFT consensus engine.
|
|
cometBFTConsensus = "comet"
|
|
)
|
|
|
|
// CometRPC defines the interface of a CometBFT RPC client needed for
|
|
// queries and transaction handling.
|
|
type CometRPC interface {
|
|
rpcclient.ABCIClient
|
|
|
|
Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error)
|
|
Status(context.Context) (*coretypes.ResultStatus, error)
|
|
Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error)
|
|
BlockByHash(ctx context.Context, hash []byte) (*coretypes.ResultBlock, error)
|
|
BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error)
|
|
BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error)
|
|
Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error)
|
|
Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error)
|
|
TxSearch(
|
|
ctx context.Context,
|
|
query string,
|
|
prove bool,
|
|
page, perPage *int,
|
|
orderBy string,
|
|
) (*coretypes.ResultTxSearch, error)
|
|
BlockSearch(
|
|
ctx context.Context,
|
|
query string,
|
|
page, perPage *int,
|
|
orderBy string,
|
|
) (*coretypes.ResultBlockSearch, error)
|
|
}
|
|
|
|
var _ broadcast.Broadcaster = &CometBFTBroadcaster{}
|
|
|
|
// CometBFTBroadcaster implements the Broadcaster interface for CometBFT consensus engine.
|
|
type CometBFTBroadcaster struct {
|
|
rpcClient CometRPC
|
|
mode string
|
|
cdc codec.Codec
|
|
}
|
|
|
|
// NewCometBFTBroadcaster creates a new CometBFTBroadcaster.
|
|
func NewCometBFTBroadcaster(rpcURL, mode string, cdc codec.Codec) (*CometBFTBroadcaster, error) {
|
|
if cdc == nil {
|
|
return nil, errors.New("codec can't be nil")
|
|
}
|
|
|
|
if mode == "" {
|
|
mode = BroadcastSync
|
|
}
|
|
|
|
rpcClient, err := rpchttp.New(rpcURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create CometBft RPC client: %w", err)
|
|
}
|
|
|
|
return &CometBFTBroadcaster{
|
|
rpcClient: rpcClient,
|
|
mode: mode,
|
|
cdc: cdc,
|
|
}, nil
|
|
}
|
|
|
|
// Consensus returns the consensus engine name used by the broadcaster.
|
|
// It always returns "comet" for CometBFTBroadcaster.
|
|
func (c *CometBFTBroadcaster) Consensus() string {
|
|
return cometBFTConsensus
|
|
}
|
|
|
|
// Broadcast sends a transaction to the network and returns the result.
|
|
// returns a byte slice containing the JSON-encoded result and an error if the broadcast failed.
|
|
func (c *CometBFTBroadcaster) Broadcast(ctx context.Context, txBytes []byte) ([]byte, error) {
|
|
if c.cdc == nil {
|
|
return []byte{}, fmt.Errorf("JSON codec is not initialized")
|
|
}
|
|
|
|
var broadcastFunc func(ctx context.Context, tx cmttypes.Tx) (*coretypes.ResultBroadcastTx, error)
|
|
switch c.mode {
|
|
case BroadcastSync:
|
|
broadcastFunc = c.rpcClient.BroadcastTxSync
|
|
case BroadcastAsync:
|
|
broadcastFunc = c.rpcClient.BroadcastTxAsync
|
|
default:
|
|
return []byte{}, fmt.Errorf("unknown broadcast mode: %s", c.mode)
|
|
}
|
|
|
|
res, err := c.broadcast(ctx, txBytes, broadcastFunc)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
return c.cdc.MarshalJSON(res)
|
|
}
|
|
|
|
// broadcast sends a transaction to the CometBFT network using the provided function.
|
|
func (c *CometBFTBroadcaster) broadcast(ctx context.Context, txBytes []byte,
|
|
fn func(ctx context.Context, tx cmttypes.Tx) (*coretypes.ResultBroadcastTx, error),
|
|
) (*apiacbci.TxResponse, error) {
|
|
res, err := fn(ctx, txBytes)
|
|
if errRes := checkCometError(err, txBytes); errRes != nil {
|
|
return errRes, nil
|
|
}
|
|
|
|
if res == nil {
|
|
return nil, err
|
|
}
|
|
|
|
parsedLogs, _ := parseABCILogs(res.Log)
|
|
return &apiacbci.TxResponse{
|
|
Code: res.Code,
|
|
Codespace: res.Codespace,
|
|
Data: res.Data.String(),
|
|
RawLog: res.Log,
|
|
Logs: parsedLogs,
|
|
Txhash: res.Hash.String(),
|
|
}, err
|
|
}
|
|
|
|
// checkCometError checks for errors returned by the CometBFT network and returns an appropriate TxResponse.
|
|
// It extracts error information and constructs a TxResponse with the error details.
|
|
func checkCometError(err error, tx cmttypes.Tx) *apiacbci.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())) ||
|
|
strings.Contains(errStr, strings.ToLower(sdkerrors.ErrTxInMempoolCache.Error())):
|
|
return &apiacbci.TxResponse{
|
|
Code: sdkerrors.ErrTxInMempoolCache.ABCICode(),
|
|
Codespace: sdkerrors.ErrTxInMempoolCache.Codespace(),
|
|
Txhash: txHash,
|
|
}
|
|
|
|
case strings.Contains(errStr, "mempool is full"):
|
|
return &apiacbci.TxResponse{
|
|
Code: sdkerrors.ErrMempoolIsFull.ABCICode(),
|
|
Codespace: sdkerrors.ErrMempoolIsFull.Codespace(),
|
|
Txhash: txHash,
|
|
}
|
|
|
|
case strings.Contains(errStr, "tx too large"):
|
|
return &apiacbci.TxResponse{
|
|
Code: sdkerrors.ErrTxTooLarge.ABCICode(),
|
|
Codespace: sdkerrors.ErrTxTooLarge.Codespace(),
|
|
Txhash: txHash,
|
|
}
|
|
|
|
case strings.Contains(errStr, "no signatures supplied"):
|
|
return &apiacbci.TxResponse{
|
|
Code: sdkerrors.ErrNoSignatures.ABCICode(),
|
|
Codespace: sdkerrors.ErrNoSignatures.Codespace(),
|
|
Txhash: txHash,
|
|
}
|
|
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// parseABCILogs attempts to parse a stringified ABCI tx log into a slice of
|
|
// ABCIMessageLog types. It returns an error upon JSON decoding failure.
|
|
func parseABCILogs(logs string) (res []*apiacbci.ABCIMessageLog, err error) {
|
|
err = json.Unmarshal([]byte(logs), &res)
|
|
return res, err
|
|
}
|