feat(client/v2): broadcast logic (#22282)

This commit is contained in:
Julián Toledano 2024-10-29 10:20:41 +01:00 committed by GitHub
parent 31f97e934e
commit 78cfc68c83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 688 additions and 20 deletions

View File

@ -44,6 +44,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [#18461](https://github.com/cosmos/cosmos-sdk/pull/18461) Support governance proposals.
* [#20623](https://github.com/cosmos/cosmos-sdk/pull/20623) Introduce client/v2 tx factory.
* [#20623](https://github.com/cosmos/cosmos-sdk/pull/20623) Extend client/v2 keyring interface with `KeyType` and `KeyInfo`.
* [#22282](https://github.com/cosmos/cosmos-sdk/pull/22282) Added custom broadcast logic.
### Improvements

View File

@ -0,0 +1,15 @@
package broadcast
import "context"
// Broadcaster defines an interface for broadcasting transactions to the consensus engine.
type Broadcaster interface {
// Broadcast sends a transaction to the network and returns the result.
//
// It returns a byte slice containing the formatted result that will be
// passed to the output writer, and an error if the broadcast failed.
Broadcast(ctx context.Context, txBytes []byte) ([]byte, error)
// Consensus returns the consensus engine identifier for this Broadcaster.
Consensus() string
}

View File

@ -0,0 +1,197 @@
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.JSONCodec
}
// NewCometBFTBroadcaster creates a new CometBFTBroadcaster.
func NewCometBFTBroadcaster(rpcURL, mode string, cdc codec.JSONCodec) (*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) {
bResult, err := fn(ctx, txBytes)
if errRes := checkCometError(err, txBytes); errRes != nil {
return errRes, nil
}
return newResponseFormatBroadcastTx(bResult), 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())):
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,
}
default:
return nil
}
}
// newResponseFormatBroadcastTx returns a TxResponse given a ResultBroadcastTx from cometbft
func newResponseFormatBroadcastTx(res *coretypes.ResultBroadcastTx) *apiacbci.TxResponse {
if res == nil {
return nil
}
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(),
}
}
// 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
}

View File

@ -0,0 +1,149 @@
package comet
import (
"context"
"errors"
"testing"
"github.com/cometbft/cometbft/mempool"
coretypes "github.com/cometbft/cometbft/rpc/core/types"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
apiacbci "cosmossdk.io/api/cosmos/base/abci/v1beta1"
mockrpc "cosmossdk.io/client/v2/broadcast/comet/testutil"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/testutil"
)
var cdc = testutil.CodecOptions{}.NewCodec()
func TestNewCometBftBroadcaster(t *testing.T) {
tests := []struct {
name string
cdc codec.JSONCodec
mode string
want *CometBFTBroadcaster
wantErr bool
}{
{
name: "constructor",
mode: BroadcastSync,
cdc: cdc,
want: &CometBFTBroadcaster{
mode: BroadcastSync,
cdc: cdc,
},
},
{
name: "nil codec",
mode: BroadcastSync,
cdc: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewCometBFTBroadcaster("localhost:26657", tt.mode, tt.cdc)
if tt.wantErr {
require.Error(t, err)
require.Nil(t, got)
} else {
require.Equal(t, got.mode, tt.want.mode)
require.Equal(t, got.cdc, tt.want.cdc)
}
})
}
}
func TestCometBftBroadcaster_Broadcast(t *testing.T) {
ctrl := gomock.NewController(t)
cometMock := mockrpc.NewMockCometRPC(ctrl)
c := CometBFTBroadcaster{
rpcClient: cometMock,
mode: BroadcastSync,
cdc: cdc,
}
tests := []struct {
name string
mode string
setupMock func(*mockrpc.MockCometRPC)
wantErr bool
}{
{
name: "sync",
mode: BroadcastSync,
setupMock: func(m *mockrpc.MockCometRPC) {
m.EXPECT().BroadcastTxSync(context.Background(), gomock.Any()).Return(&coretypes.ResultBroadcastTx{
Code: 0,
Data: []byte{},
Log: "",
Codespace: "",
Hash: []byte("%<25><><EFBFBD><EFBFBD><EFBFBD>\u0010\n<>T<EFBFBD>\u0017\u0016<31>N^H[5<>\u0006}<7D>n<EFBFBD>w<EFBFBD>/Vi<56> "),
}, nil)
},
},
{
name: "async",
mode: BroadcastAsync,
setupMock: func(m *mockrpc.MockCometRPC) {
m.EXPECT().BroadcastTxAsync(context.Background(), gomock.Any()).Return(&coretypes.ResultBroadcastTx{
Code: 0,
Data: []byte{},
Log: "",
Codespace: "",
Hash: []byte("%<25><><EFBFBD><EFBFBD><EFBFBD>\u0010\n<>T<EFBFBD>\u0017\u0016<31>N^H[5<>\u0006}<7D>n<EFBFBD>w<EFBFBD>/Vi<56> "),
}, nil)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c.mode = tt.mode
tt.setupMock(cometMock)
got, err := c.Broadcast(context.Background(), []byte{})
if tt.wantErr {
require.Error(t, err)
} else {
require.NotNil(t, got)
}
})
}
}
func Test_checkCometError(t *testing.T) {
tests := []struct {
name string
err error
want *apiacbci.TxResponse
}{
{
name: "tx already in cache",
err: errors.New("tx already exists in cache"),
want: &apiacbci.TxResponse{
Code: 19,
},
},
{
name: "mempool is full",
err: mempool.ErrMempoolIsFull{},
want: &apiacbci.TxResponse{
Code: 20,
},
},
{
name: "tx too large",
err: mempool.ErrTxTooLarge{},
want: &apiacbci.TxResponse{
Code: 21,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := checkCometError(tt.err, []byte{})
require.Equal(t, got.Code, tt.want.Code)
})
}
}

View File

@ -0,0 +1,285 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: client/v2/broadcast/comet/comet.go
//
// Generated by this command:
//
// mockgen -source=client/v2/broadcast/comet/comet.go -package testutil -destination client/v2/broadcast/comet/testutil/comet_mock.go
//
// Package testutil is a generated GoMock package.
package testutil
import (
context "context"
reflect "reflect"
bytes "github.com/cometbft/cometbft/libs/bytes"
client "github.com/cometbft/cometbft/rpc/client"
coretypes "github.com/cometbft/cometbft/rpc/core/types"
types "github.com/cometbft/cometbft/types"
gomock "go.uber.org/mock/gomock"
)
// MockCometRPC is a mock of CometRPC interface.
type MockCometRPC struct {
ctrl *gomock.Controller
recorder *MockCometRPCMockRecorder
isgomock struct{}
}
// MockCometRPCMockRecorder is the mock recorder for MockCometRPC.
type MockCometRPCMockRecorder struct {
mock *MockCometRPC
}
// NewMockCometRPC creates a new mock instance.
func NewMockCometRPC(ctrl *gomock.Controller) *MockCometRPC {
mock := &MockCometRPC{ctrl: ctrl}
mock.recorder = &MockCometRPCMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCometRPC) EXPECT() *MockCometRPCMockRecorder {
return m.recorder
}
// ABCIInfo mocks base method.
func (m *MockCometRPC) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ABCIInfo", ctx)
ret0, _ := ret[0].(*coretypes.ResultABCIInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ABCIInfo indicates an expected call of ABCIInfo.
func (mr *MockCometRPCMockRecorder) ABCIInfo(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ABCIInfo", reflect.TypeOf((*MockCometRPC)(nil).ABCIInfo), ctx)
}
// ABCIQuery mocks base method.
func (m *MockCometRPC) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ABCIQuery", ctx, path, data)
ret0, _ := ret[0].(*coretypes.ResultABCIQuery)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ABCIQuery indicates an expected call of ABCIQuery.
func (mr *MockCometRPCMockRecorder) ABCIQuery(ctx, path, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ABCIQuery", reflect.TypeOf((*MockCometRPC)(nil).ABCIQuery), ctx, path, data)
}
// ABCIQueryWithOptions mocks base method.
func (m *MockCometRPC) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ABCIQueryWithOptions", ctx, path, data, opts)
ret0, _ := ret[0].(*coretypes.ResultABCIQuery)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ABCIQueryWithOptions indicates an expected call of ABCIQueryWithOptions.
func (mr *MockCometRPCMockRecorder) ABCIQueryWithOptions(ctx, path, data, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ABCIQueryWithOptions", reflect.TypeOf((*MockCometRPC)(nil).ABCIQueryWithOptions), ctx, path, data, opts)
}
// Block mocks base method.
func (m *MockCometRPC) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Block", ctx, height)
ret0, _ := ret[0].(*coretypes.ResultBlock)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Block indicates an expected call of Block.
func (mr *MockCometRPCMockRecorder) Block(ctx, height any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Block", reflect.TypeOf((*MockCometRPC)(nil).Block), ctx, height)
}
// BlockByHash mocks base method.
func (m *MockCometRPC) BlockByHash(ctx context.Context, hash []byte) (*coretypes.ResultBlock, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockByHash", ctx, hash)
ret0, _ := ret[0].(*coretypes.ResultBlock)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockByHash indicates an expected call of BlockByHash.
func (mr *MockCometRPCMockRecorder) BlockByHash(ctx, hash any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByHash", reflect.TypeOf((*MockCometRPC)(nil).BlockByHash), ctx, hash)
}
// BlockResults mocks base method.
func (m *MockCometRPC) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockResults", ctx, height)
ret0, _ := ret[0].(*coretypes.ResultBlockResults)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockResults indicates an expected call of BlockResults.
func (mr *MockCometRPCMockRecorder) BlockResults(ctx, height any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockResults", reflect.TypeOf((*MockCometRPC)(nil).BlockResults), ctx, height)
}
// BlockSearch mocks base method.
func (m *MockCometRPC) BlockSearch(ctx context.Context, query string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockSearch", ctx, query, page, perPage, orderBy)
ret0, _ := ret[0].(*coretypes.ResultBlockSearch)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockSearch indicates an expected call of BlockSearch.
func (mr *MockCometRPCMockRecorder) BlockSearch(ctx, query, page, perPage, orderBy any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockSearch", reflect.TypeOf((*MockCometRPC)(nil).BlockSearch), ctx, query, page, perPage, orderBy)
}
// BlockchainInfo mocks base method.
func (m *MockCometRPC) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockchainInfo", ctx, minHeight, maxHeight)
ret0, _ := ret[0].(*coretypes.ResultBlockchainInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockchainInfo indicates an expected call of BlockchainInfo.
func (mr *MockCometRPCMockRecorder) BlockchainInfo(ctx, minHeight, maxHeight any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockchainInfo", reflect.TypeOf((*MockCometRPC)(nil).BlockchainInfo), ctx, minHeight, maxHeight)
}
// BroadcastTxAsync mocks base method.
func (m *MockCometRPC) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BroadcastTxAsync", ctx, tx)
ret0, _ := ret[0].(*coretypes.ResultBroadcastTx)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BroadcastTxAsync indicates an expected call of BroadcastTxAsync.
func (mr *MockCometRPCMockRecorder) BroadcastTxAsync(ctx, tx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastTxAsync", reflect.TypeOf((*MockCometRPC)(nil).BroadcastTxAsync), ctx, tx)
}
// BroadcastTxCommit mocks base method.
func (m *MockCometRPC) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BroadcastTxCommit", ctx, tx)
ret0, _ := ret[0].(*coretypes.ResultBroadcastTxCommit)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BroadcastTxCommit indicates an expected call of BroadcastTxCommit.
func (mr *MockCometRPCMockRecorder) BroadcastTxCommit(ctx, tx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastTxCommit", reflect.TypeOf((*MockCometRPC)(nil).BroadcastTxCommit), ctx, tx)
}
// BroadcastTxSync mocks base method.
func (m *MockCometRPC) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BroadcastTxSync", ctx, tx)
ret0, _ := ret[0].(*coretypes.ResultBroadcastTx)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BroadcastTxSync indicates an expected call of BroadcastTxSync.
func (mr *MockCometRPCMockRecorder) BroadcastTxSync(ctx, tx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastTxSync", reflect.TypeOf((*MockCometRPC)(nil).BroadcastTxSync), ctx, tx)
}
// Commit mocks base method.
func (m *MockCometRPC) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Commit", ctx, height)
ret0, _ := ret[0].(*coretypes.ResultCommit)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Commit indicates an expected call of Commit.
func (mr *MockCometRPCMockRecorder) Commit(ctx, height any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockCometRPC)(nil).Commit), ctx, height)
}
// Status mocks base method.
func (m *MockCometRPC) Status(arg0 context.Context) (*coretypes.ResultStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Status", arg0)
ret0, _ := ret[0].(*coretypes.ResultStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Status indicates an expected call of Status.
func (mr *MockCometRPCMockRecorder) Status(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockCometRPC)(nil).Status), arg0)
}
// Tx mocks base method.
func (m *MockCometRPC) Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Tx", ctx, hash, prove)
ret0, _ := ret[0].(*coretypes.ResultTx)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Tx indicates an expected call of Tx.
func (mr *MockCometRPCMockRecorder) Tx(ctx, hash, prove any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tx", reflect.TypeOf((*MockCometRPC)(nil).Tx), ctx, hash, prove)
}
// TxSearch mocks base method.
func (m *MockCometRPC) TxSearch(ctx context.Context, query string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TxSearch", ctx, query, prove, page, perPage, orderBy)
ret0, _ := ret[0].(*coretypes.ResultTxSearch)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TxSearch indicates an expected call of TxSearch.
func (mr *MockCometRPCMockRecorder) TxSearch(ctx, query, prove, page, perPage, orderBy any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TxSearch", reflect.TypeOf((*MockCometRPC)(nil).TxSearch), ctx, query, prove, page, perPage, orderBy)
}
// Validators mocks base method.
func (m *MockCometRPC) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validators", ctx, height, page, perPage)
ret0, _ := ret[0].(*coretypes.ResultValidators)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Validators indicates an expected call of Validators.
func (mr *MockCometRPCMockRecorder) Validators(ctx, height, page, perPage any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validators", reflect.TypeOf((*MockCometRPC)(nil).Validators), ctx, height, page, perPage)
}

View File

@ -13,6 +13,7 @@ require (
github.com/cosmos/cosmos-sdk v0.53.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
go.uber.org/mock v0.5.0
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1
gotest.tools/v3 v3.5.1
@ -27,7 +28,7 @@ require (
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/log v1.4.1 // indirect
cosmossdk.io/math v1.3.0
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac // indirect
cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc // indirect
cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
@ -47,7 +48,7 @@ require (
github.com/cockroachdb/pebble v1.1.2 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f // indirect
github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f
github.com/cometbft/cometbft-db v0.15.0 // indirect
github.com/cometbft/cometbft/api v1.0.0-rc.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
@ -150,7 +151,6 @@ require (
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect

View File

@ -18,8 +18,8 @@ cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM=
cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU=
cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 h1:DmOoS/1PeY6Ih0hAVlJ69kLMUrLV+TCbfICrZtB1vdU=
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac h1:3joNZZWZ3k7fMsrBDL1ktuQ2xQwYLZOaDhkruadDFmc=
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/x/protocolpool v0.0.0-20230925135524-a1bc045b3190 h1:XQJj9Dv9Gtze0l2TF79BU5lkP6MkUveTUuKICmxoz+o=
cosmossdk.io/x/protocolpool v0.0.0-20230925135524-a1bc045b3190/go.mod h1:7WUGupOvmlHJoIMBz1JbObQxeo6/TDiuDBxmtod8HRg=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=

View File

@ -2,6 +2,7 @@ package tx
import (
"bufio"
"context"
"errors"
"fmt"
"os"
@ -10,6 +11,8 @@ import (
"github.com/spf13/pflag"
apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
"cosmossdk.io/client/v2/broadcast"
"cosmossdk.io/client/v2/broadcast/comet"
"cosmossdk.io/client/v2/internal/account"
"cosmossdk.io/core/transaction"
@ -18,9 +21,9 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keyring"
)
// GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction
// or sign it and broadcast it returning an error upon failure.
func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error {
// GenerateOrBroadcastTxCLIWithBroadcaster will either generate and print an unsigned transaction
// or sign it and broadcast it with the specified broadcaster returning an error upon failure.
func GenerateOrBroadcastTxCLIWithBroadcaster(ctx client.Context, flagSet *pflag.FlagSet, broadcaster broadcast.Broadcaster, msgs ...transaction.Msg) error {
if err := validateMessages(msgs...); err != nil {
return err
}
@ -40,7 +43,25 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs .
return dryRun(txf, msgs...)
}
return BroadcastTx(ctx, txf, msgs...)
return BroadcastTx(ctx, txf, broadcaster, msgs...)
}
// GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction
// or sign it and broadcast it using default CometBFT broadcaster, returning an error upon failure.
func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error {
cometBroadcaster, err := getCometBroadcaster(ctx, flagSet)
if err != nil {
return err
}
return GenerateOrBroadcastTxCLIWithBroadcaster(ctx, flagSet, cometBroadcaster, msgs...)
}
// getCometBroadcaster returns a new CometBFT broadcaster based on the provided context and flag set.
func getCometBroadcaster(ctx client.Context, flagSet *pflag.FlagSet) (broadcast.Broadcaster, error) {
url, _ := flagSet.GetString("node")
mode, _ := flagSet.GetString("broadcast-mode")
return comet.NewCometBFTBroadcaster(url, mode, ctx.Codec)
}
// newFactory creates a new transaction Factory based on the provided context and flag set.
@ -129,7 +150,7 @@ func SimulateTx(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.
// BroadcastTx attempts to generate, sign and broadcast a transaction with the
// given set of messages. It will also simulate gas requirements if necessary.
// It will return an error upon failure.
func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) error {
func BroadcastTx(clientCtx client.Context, txf Factory, broadcaster broadcast.Broadcaster, msgs ...transaction.Msg) error {
if txf.simulateAndExecute() {
err := txf.calculateGas(msgs...)
if err != nil {
@ -183,13 +204,12 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg)
return err
}
// broadcast to a CometBFT node
res, err := clientCtx.BroadcastTx(txBytes)
res, err := broadcaster.Broadcast(context.Background(), txBytes)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
return clientCtx.PrintString(string(res))
}
// countDirectSigners counts the number of DIRECT signers in a signature data.

View File

@ -28,3 +28,4 @@ $mockgen_cmd -source=x/auth/vesting/types/expected_keepers.go -package testutil
$mockgen_cmd -source=x/protocolpool/types/expected_keepers.go -package testutil -destination x/protocolpool/testutil/expected_keepers_mocks.go
$mockgen_cmd -source=x/upgrade/types/expected_keepers.go -package testutil -destination x/upgrade/testutil/expected_keepers_mocks.go
$mockgen_cmd -source=core/gas/service.go -package gas -destination core/testing/gas/service_mocks.go
$mockgen_cmd -source=client/v2/broadcast/comet/comet.go -package testutil -destination client/v2/broadcast/comet/testutil/comet_mock.go

View File

@ -59,7 +59,7 @@ require (
cloud.google.com/go/iam v1.1.13 // indirect
cloud.google.com/go/storage v1.43.0 // indirect
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect

View File

@ -204,8 +204,8 @@ cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM=
cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU=
cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 h1:DmOoS/1PeY6Ih0hAVlJ69kLMUrLV+TCbfICrZtB1vdU=
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac h1:3joNZZWZ3k7fMsrBDL1ktuQ2xQwYLZOaDhkruadDFmc=
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=

View File

@ -11,7 +11,7 @@ require (
cosmossdk.io/math v1.3.0
cosmossdk.io/runtime/v2 v2.0.0-00010101000000-000000000000
cosmossdk.io/server/v2 v2.0.0-20240718121635-a877e3e8048a
cosmossdk.io/server/v2/cometbft v0.0.0-00010101000000-000000000000
cosmossdk.io/server/v2/cometbft v0.0.0-20241015140036-ee3d320eaa55
cosmossdk.io/store/v2 v2.0.0
cosmossdk.io/tools/confix v0.0.0-00010101000000-000000000000
cosmossdk.io/x/accounts v0.0.0-20240913065641-0064ccbce64e

View File

@ -65,7 +65,7 @@ require (
cloud.google.com/go/storage v1.43.0 // indirect
cosmossdk.io/client/v2 v2.0.0-20230630094428-02b760776860 // indirect
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 // indirect
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac // indirect
cosmossdk.io/x/circuit v0.0.0-20230613133644-0a778132a60f // indirect
cosmossdk.io/x/epochs v0.0.0-20240522060652-a1ae4c3e0337 // indirect
filippo.io/edwards25519 v1.1.0 // indirect

View File

@ -204,8 +204,8 @@ cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM=
cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU=
cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9 h1:DmOoS/1PeY6Ih0hAVlJ69kLMUrLV+TCbfICrZtB1vdU=
cosmossdk.io/schema v0.3.1-0.20240930054013-7c6e0388a3f9/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac h1:3joNZZWZ3k7fMsrBDL1ktuQ2xQwYLZOaDhkruadDFmc=
cosmossdk.io/schema v0.3.1-0.20241010135032-192601639cac/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=